The eco-goinfra project contains a collection of generic packages that can be used across various test projects.
- golang v1.22.x
In order to re-use code from this project you need to import relevant package/packages in to your project code.
import "github.com/openshift-kni/eco-goinfra/pkg/NAME_OF_A_NEEDED_PACKAGE"
In addition, you need to add link to the project github.com/openshift-kni/eco-goinfra to your local go.mod file
require(
github.com/openshift-kni/eco-goinfra latest
)
The clients package contains several api clients combined into a single struct. The New function of the clients package returns a ready connection to the cluster api. If the path to kubeconfig is not specified to the new function then the KUBECONFIG environment variable is used. In case of failure client.New("") returns nil.
import "github.com/openshift-kni/eco-goinfra/pkg/clients"
func main() {
apiClients := clients.New("")
if apiClients == nil {
panic("Failed to load api client")
}
}
)
Every cluster object namespace, configmap, daemonset, deployment and other has its own package under packages directory. The structure of any object has common interface:
func NewBuilder() or New[ObjectName]Builder() // Initiates object struct. This function require minimum set of parameters that are required to create the object on a cluster.
func Pull() or Pull[ObjectName]() // Pulls existing object to struct.
func Create() // Creates new object on cluster if it does not exist.
func Delete() // Removes object from cluster if it exists.
func Update() // Updates object based on new object's definition.
func Exist() // Returns bool if object exist.
func With***() // Set of mutation functions that can mutate any part of the object.
Please refer to namespace example for more info.
In order to ensure safe access to objects and members, each builder struct should include a validate
method. This method should be invoked inside packages before accessing potentially uninitialized code to mitigate unintended errors. Example:
func (builder *Builder) WithMethod(someString string) *Builder {
if valid, _ := builder.validate(); !valid {
return builder
}
glog.V(100).Infof(
"Updating builder %s in namespace %s with the string: %s",
builder.Definition.Name, builder.Definition.Namespace, someString
)
builder.Definition.StringHolder = someString
return builder
}
Typically, validate methods will check that pointers are not nil and that errorMsg has not been set. Here is an example of how the secret package validate method ensures that Builder.apiClient has properly been initialized before being called:
func main() {
apiClient := clients.New("bad api client")
_, err := secret.NewBuilder(
apiClient, "mysecret", "mynamespace", v1SecretTypeDockerConfigJson).Create()
if err != nil {
log.Fatal(err)
}
}
Instead of causing a panic, the method will return a proper error message:
2023/06/16 11:55:58 Loading kube client config from path "bad api client"
2023/06/16 11:55:58 Secret builder cannot have nil apiClient
exit status 1
Please refer to the secret pkg's use of the validate method for more information.
The BMC package can be used to access the BMC's Redfish API, run BMC's CLI commands, or get the systems' serial console. Only the host must be provided in New()
while Redfish and SSH credentials, along with other options, can be configured using separate methods.
bmc := bmc.New("1.2.3.4").
WithRedfishUser("redfishuser1", "redfishpass1").
WithSSHUser("sshuser1", "sshpass1").
WithSSHPort(1234)
You can check an example program for the BMC package in usage.
The access to BMC's Redfish API is done by methods that encapsulate the underlaying HTTP calls made by the external gofish library. The redfish system index is defaulted to 0, but it can be changed with SetSystemIndex()
:
const systemIndex = 3
err = bmc.SetSystemIndex(systemIndex)
if err != nil {
...
}
manufacturer, err := bmc.SystemManufacturer()
if err != nil {
...
}
fmt.Printf("System %d's manufacturer: %v", systemIndex, manufacturer)
The method RunCLICommand
has been implemented to run CLI commands.
func (bmc *BMC) RunCLICommand(cmd string, combineOutput bool, timeout time.Duration) stdout string, stderr string, err error)
This method is not interactive: it blocks the caller until the command ends, copying its output into stdout and stderr strings.
The method OpenSerialConsole
can be used to get the systems's serial console, which is tunneled in the an underlaying SSH session.
func (bmc *BMC) OpenSerialConsole(openConsoleCliCmd string) (io.Reader, io.WriteCloser, error)
The user gets a (piped) reader and writer interfaces in order to read the output or write custom input (like CLI commands) in a interactive fashion. A use case for this is a test case that needs to wait for some pattern to appear in the system's serial console after rebooting the system.
The openConsoleCliCmd
is the command that will be sent to the BMC's (SSH'd) CLI to open the serial console. In case the user doesn't know the command,
it can be left empty. In that case, there's a best effort mechanism that will try to guess the CLI command based on the system's manufacturer, which will
be internally retrieved using the Redfish API.
It's important to close the serial console using the method bmc.CloseSerialConsole()
, which closes the underlying SSH session. Otherwise, BMC's can reach
the maximum number of concurrent SSH sessions making other (SSH'd CLI) commands to fail. See an example program here.
The project uses a development method - forking workflow
- A developer forks the eco-goinfra project
- A new local feature branch is created
- A developer makes changes on the new branch.
- New commits are created for the changes.
- The branch gets pushed to developer's own server-side copy.
- Changes are tested.
- A developer opens a pull request(
PR
) from the new branch to the eco-goinfra. - The pull request gets approved from at least 2 reviewers for merge and is merged into the eco-goinfra .
Note: Every new package requires a coverage of ALL its public functions with unit tests. Unit tests are located in the same package as the resource, in a file with the name resource_test.go. Examples can be found in configmap_test.go and metallb_test.go.
Push requested are tested in a pipeline with golangci-lint. It is advised to add Golangci-lint integration to your development editor. It is recommended to run make lint
before uploading a PR.
If the function's arguments fit in a single line - use the following format:
func Function(argInt1, argInt2 int, argString1, argString2 string) {
...
}
If the function's arguments do not fit in a single line - use the following format:
func Function(
argInt1 int,
argInt2 int,
argInt3 int,
argInt4 int,
argString1 string,
argString2 string,
argString3 string,
argString4 string) {
...
}
One more acceptable format example:
func Function(
argInt1, argInt2 int, argString1, argString2 string, argSlice1, argSlice2 []string) {
}