gofish

repository·main·Indexed 43 days ago

https://github.com/stmcginnis/gofish

A Golang client library for interacting with devices that support the DMTF Redfish and SNIA Swordfish management standards. It provides an APIClient for RESTful operations, session management, and a service layer to query hardware resources such as Chassis, Systems, and Storage.

Tokens
5.2K
Snippets
12
Records
15
Agent score
56%

What's inside gofish

  1. Connect to a Redfish or Swordfish device

    main

    To start interacting with a device, use gofish.ConnectDefault(url) to establish a connection. This function returns a client instance and an error. The client provides access to the underlying service layer used to query device resources like Chassis.

    package main
    
    import (
        "fmt"
    
        "github.com/stmcginnis/gofish"
    )
    
    func main() {
        c, err := gofish.ConnectDefault("http://localhost:5000")
        if err != nil {
            panic(err)
        }
    
        service := c.Service
        chassis, err := service.Chassis()
        if err != nil {
            panic(err)
        }
    
        for _, chass := range chassis {
            fmt.Printf("Chassis: %#v\n\n", chass)
        }
    }
  2. Debug HTTP traffic with DumpWriter

    main
    You can inspect the raw HTTP requests and responses sent by the APIClient by providing an io.Writer to the DumpWriter field in ClientConfig or by using SetDumpWriter(writer io.Writer) on an existing client. This is useful for troubleshooting protocol issues or verifying payload structures.
  3. Connect to a Redfish service

    main

    Use Connect or ConnectContext to establish an authenticated connection to a Redfish/Swordfish service using a ClientConfig. If you need an unauthenticated connection, use ConnectDefault or ConnectDefaultContext.

    config := gofish.ClientConfig{
        Endpoint: "https://redfish-service.example.com",
        Username: "admin",
        Password: "password",
        Insecure: true,
    }
    
    client, err := gofish.Connect(config)
    if err != nil {
        log.Fatal(err)
    }
  4. List drives in a system using gofish

    main

    To list drive information (such as manufacturer, model, capacity, and serial number) in a system, follow this hierarchy of calls:

    1. Connect to the BMC using gofish.Connect with a gofish.ClientConfig.
    2. Access the Service root from the client.
    3. Call Systems() on the service to get a list of systems.
    4. For each system, call Storage() to retrieve storage resources.
    5. For each storage resource, call Drives() to get the list of individual drives.

    Note: When connecting to hardware with self-signed certificates, set Insecure: true in your ClientConfig.

    //
    // SPDX-License-Identifier: BSD-3-Clause
    //
    package main
    
    import (
    	"fmt"
    
    	"github.com/stmcginnis/gofish"
    )
    
    func main() {
    	// Create a new instance of gofish client, ignoring self-signed certs
    	config := gofish.ClientConfig{
    		Endpoint: "https://bmc-ip",
    		Username: "my-username",
    		Password: "my-password",
    		Insecure: true,
    	}
    	c, err := gofish.Connect(config)
    	if err != nil {
    		panic(err)
    	}
    	defer c.Logout()
    
    	// Retrieve the service root
    	service := c.Service
    
    	systems, err := service.Systems()
    	if err != nil {
    		panic(err)
    	}
    
    	for _, system := range systems {
    		storage, err := system.Storage()
    		if err != nil {
    			continue
    		}
    
    		for _, ss := range storage {
    			drives, err := ss.Drives()
    			if err != nil {
    				continue
    			}
    
    			for i, drive := range drives {
    				fmt.Printf("Drive %d\n", i)
    				fmt.Printf("\tManufacturer: %s\n", drive.Manufacturer)
    				fmt.Printf("\tModel: %s\n", drive.Model)
    				fmt.Printf("\tSize: %d GiB\n", (drive.CapacityBytes / 1024 / 1024 / 1024))
    				fmt.Printf("\tSerial number: %s\n", drive.SerialNumber)
    				fmt.Printf("\tPart number: %s\n", drive.PartNumber)
    				fmt.Printf("\tLocation: %s %d\n", drive.PhysicalLocation.PartLocation.LocationType, drive.PhysicalLocation.PartLocation.LocationOrdinalValue)
    			}
    		}
    	}
    }
  5. Get Chassis information using the Service layer

    main

    Once a connection is established, you can access the Service property of the client. The Service object provides methods to retrieve hardware resources. For example, calling service.Chassis() returns a list of available chassis on the device.

    service := c.Service
    chassis, err := service.Chassis()
    if err != nil {
        panic(err)
    }
    
    for _, chass := range chassis {
        fmt.Printf("Chassis: %#v\n\n", chass)
    }
  6. Reboot a system and set a boot override

    main

    This example demonstrates how to connect to a Redfish/Swordfish endpoint, iterate through available computer systems, configure a one-time PXE boot override, and perform a forced restart.

    To use this pattern:

    1. Initialize a gofish.ClientConfig with the target Endpoint, Username, Password, and set Insecure: true if using self-signed certificates.
    2. Connect using gofish.Connect(config).
    3. Access the Service root from the client.
    4. Retrieve systems using service.Systems().
    5. For each system, use system.SetBoot(&bootOverride) to configure boot behavior.
    6. Use system.Reset(schemas.ForceRestartResetType) to trigger the reboot.
    //
    // SPDX-License-Identifier: BSD-3-Clause
    //
    package main
    
    import (
    	"fmt"
    
    	"github.com/stmcginnis/gofish"
    	"github.com/stmcginnis/gofish/schemas"
    )
    
    func main() {
    	// Create a new instance of gofish client, ignoring self-signed certs
    	config := gofish.ClientConfig{
    		Endpoint: "https://bmc-ip",
    		Username: "my-username",
    		Password: "my-password",
    		Insecure: true,
    	}
    
    	c, err := gofish.Connect(config)
    	if err != nil {
    		panic(err)
    	}
    	defer c.Logout()
    
    	// Attached the client to service root
    	service := c.Service
    
    	// Query the computer systems
    	ss, err := service.Systems()
    	if err != nil {
    		panic(err)
    	}
    
    	// Creates a boot override to pxe once
    	bootOverride := schemas.Boot{
    		BootSourceOverrideTarget:  schemas.PxeBootSource,
    		BootSourceOverrideEnabled: schemas.OnceBootSourceOverrideEnabled,
    	}
    
    	for _, system := range ss {
    		fmt.Printf("System: %#v\n\n", system)
    		err := system.SetBoot(&bootOverride)
    		if err != nil {
    			panic(err)
    		}
    		_, err = system.Reset(schemas.ForceRestartResetType)
    		if err != nil {
    			panic(err)
    		}
    	}
    }
  7. Change account username and password

    main

    To update the credentials of an existing account, you must first connect to the service, retrieve the AccountService, and locate the specific account object within the list of accounts. Once the account object is identified, you can modify its UserName and Password fields and call the Update() method.

    Note: The new password must comply with the constraints defined in the AccountService, specifically MinPasswordLength and MaxPasswordLength.

    // Create a new instance of gofish client, ignoring self-signed certs
    username := "my-username"
    config := gofish.ClientConfig{
    	Endpoint: "https://bmc-ip",
    	Username: username,
    	Password: "my-password",
    	Insecure: true,
    }
    c, err := gofish.Connect(config)
    if err != nil {
    	panic(err)
    }
    defer c.Logout()
    
    // Retrieve the service root
    service := c.Service
    
    // Query the AccountService using the session token
    accountService, err := service.AccountService()
    if err != nil {
    	panic(err)
    }
    
    // Get list of accounts
    accounts, err := accountService.Accounts()
    if err != nil {
    	panic(err)
    }
    
    // Iterate over accounts to find the current user
    for _, account := range accounts {
    	if account.UserName == username {
    		account.UserName = "new-username"
    		// New password must follow the rules set in AccountService : 
    		// MinPasswordLength and MaxPasswordLength
    		account.Password = "new-password"
    		err := account.Update()
    		if err != nil {
    			panic(err)
    		}
    	}
    }
  8. Query active sessions using the SessionService

    main

    To retrieve information about currently active sessions from a Redfish or Swordfish service, you must first establish a connection using gofish.Connect, access the Service root, and then instantiate the SessionService. Once you have the session service, calling .Sessions() returns a list of active sessions which you can iterate over to inspect properties like Name, UserName, and SessionType.

    // Create a new instance of gofish client, ignoring self-signed certs
    config := gofish.ClientConfig{
    	Endpoint: "https://bmc-ip",
    	Username: "my-username",
    	Password: "my-password",
    	Insecure: true,
    }
    
    c, err := gofish.Connect(config)
    if err != nil {
    	panic(err)
    }
    defer c.Logout()
    
    // Retrieve the service root
    service := c.Service
    
    // Query the session service
    sessionService, err := service.SessionService()
    if err != nil {
    	panic(err)
    }
    
    // Get the active sessions
    sessions, err := sessionService.Sessions()
    if err != nil {
    	panic(err)
    }
    
    for _, session := range sessions {
    	fmt.Printf("Session: %s  user=%s  type=%s\n",
    		session.Name, session.UserName, session.SessionType)
    }
  9. Query chassis information using gofish

    main

    To retrieve chassis data from a Redfish/Swordfish endpoint, initialize a gofish.ClientConfig, connect using gofish.Connect(), and then call the Chassis() method on the Service object. The Chassis() method returns a slice of chassis objects containing details such as Name, ChassisType, Manufacturer, SerialNumber, and PowerState.

    //
    // SPDX-License-Identifier: BSD-3-Clause
    //
    package main
    
    import (
    	"fmt"
    
    	"github.com/stmcginnis/gofish"
    )
    
    func main() {
    	// Create a new instance of gofish client, ignoring self-signed certs
    	config := gofish.ClientConfig{
    		Endpoint: "https://bmc-ip",
    		Username: "my-username",
    		Password: "my-password",
    		Insecure: true,
    	}
    	c, err := gofish.Connect(config)
    	if err != nil {
    		panic(err)
    	}
    	defer c.Logout()
    
    	// Retrieve the service root
    	service := c.Service
    
    	// Query the chassis data using the session token
    	chassis, err := service.Chassis()
    	if err != nil {
    		panic(err)
    	}
    
    	for _, ch := range chassis {
    		fmt.Printf("Chassis: %s\n", ch.Name)
    		fmt.Printf("\tType:         %s\n", ch.ChassisType)
    		fmt.Printf("\tManufacturer: %s\n", ch.Manufacturer)
    		fmt.Printf("\tSerial:       %s\n", ch.SerialNumber)
    		fmt.Printf("\tPower state:  %s\n", ch.PowerState)
    	}
    }
  10. Configure ClientSettings

    main

    The ClientSettings struct allows you to define global configuration for a Client. Currently, it supports DefaultQueryOptions, which is a slice of QueryGroupOption that can be applied to requests.

    settings := schemas.ClientSettings{
        DefaultQueryOptions: []schemas.QueryGroupOption{ /* options here */ },
    }
  11. Configure the APIClient via ClientConfig

    main

    The ClientConfig struct defines how the APIClient is initialized. Key configuration options include:

    • Endpoint: The base URL of the Redfish service (must start with http or https).
    • Username / Password: Credentials for authentication.
    • Session: An optional *Session (ID and Token) to reuse an existing session instead of credentials.
    • Insecure: If true, enforces no SSL certificate validity (skips verification).
    • TLSHandshakeTimeout: Controls the TLS handshake timeout in seconds.
    • HTTPClient: An optional custom *http.Client.
    • DumpWriter: An optional io.Writer to receive dumps of HTTP requests and responses.
    • BasicAuth: If true, uses Basic Auth; if false, attempts to create a session via the service.
    • MaxConcurrentRequests: Limits concurrent HTTP requests (defaults to 1).
    • ReuseConnections: If true, allows TCP sessions to remain open and be reused.
    • NoModifyTransport: If true, the client won't attempt to modify the provided HTTPClient's transport.
    • AutoExpand: Enables $expand if supported by the service, with automatic fallback.
  12. Handle optional values with Deref and ToRef

    main

    Redfish schemas often use pointers to represent optional fields. Gofish provides two utility functions to simplify working with these:

    • Deref[T any](v *T) T: Returns the value of the pointer if it is not nil; otherwise, returns the zero-value of the type. Use this when you don't care if a value was explicitly reported by the service.
    • ToRef[T any](v T) *T: Returns a pointer to the provided value. Use this when assigning values to API objects that require pointers for optional fields.
    // Using Deref to avoid nil checks
    val := gofish.Deref(attribute.DisplayOrder)
    
    // Using ToRef to satisfy pointer requirements
    newObj := schemas.SomeObject{
        Name: gofish.ToRef("MyName"),
    }