From a43c657c5d7743289358143678be47a2643d309a Mon Sep 17 00:00:00 2001 From: Devon Bautista Date: Tue, 28 Jan 2025 14:54:09 -0700 Subject: [PATCH] feat: add smd-group-member-set command --- cmd/smd-group-member-set.go | 63 +++++++++++++++++++++++++++++++++++++ internal/client/smd.go | 57 +++++++++++++++++++++++++++++++++ man/ochami-smd.1.sc | 21 +++++++++++++ 3 files changed, 141 insertions(+) create mode 100644 cmd/smd-group-member-set.go diff --git a/cmd/smd-group-member-set.go b/cmd/smd-group-member-set.go new file mode 100644 index 0000000..a7ebd33 --- /dev/null +++ b/cmd/smd-group-member-set.go @@ -0,0 +1,63 @@ +// This source code is licensed under the license found in the LICENSE file at +// the root directory of this source tree. +package cmd + +import ( + "errors" + "os" + + "github.com/OpenCHAMI/ochami/internal/client" + "github.com/OpenCHAMI/ochami/internal/log" + "github.com/spf13/cobra" +) + +// groupMemberSetCmd represents the smd-group-member-set command +var groupMemberSetCmd = &cobra.Command{ + Use: "set ...", + Args: cobra.MinimumNArgs(2), + Short: "Set group membership list to a list of components", + Long: `Set group membership list to a list of components. The components specified +in the list are set as the only members of the group. If a component +specified is already in the group, it remains in the group. If a +component specified is not already in te group, it is added to the +group. If a component is in the group but not specified, it is +removed from the group.`, + Example: ` ochami smd group member set compute x1000c1s7b1n0 x1000c1s7b2n0`, + Run: func(cmd *cobra.Command, args []string) { + // Without a base URI, we cannot do anything + smdBaseURI, err := getBaseURI(cmd) + if err != nil { + log.Logger.Error().Err(err).Msg("failed to get base URI for SMD") + os.Exit(1) + } + + // This endpoint requires authentication, so a token is needed + setTokenFromEnvVar(cmd) + checkToken(cmd) + + // Create client to make request to SMD + smdClient, err := client.NewSMDClient(smdBaseURI, insecure) + if err != nil { + log.Logger.Error().Err(err).Msg("error creating new SMD client") + os.Exit(1) + } + + // Check if a CA certificate was passed and load it into client if valid + useCACert(smdClient.OchamiClient) + + // Send off request + _, err = smdClient.PutGroupMembers(token, args[0], args[1:]...) + if err != nil { + if errors.Is(err, client.UnsuccessfulHTTPError) { + log.Logger.Error().Err(err).Msgf("SMD group member request for group %s yielded unsuccessful HTTP response", args[0]) + } else { + log.Logger.Error().Err(err).Msgf("failed to set group membership for group %s in SMD", args[0]) + } + os.Exit(1) + } + }, +} + +func init() { + groupMemberCmd.AddCommand(groupMemberSetCmd) +} diff --git a/internal/client/smd.go b/internal/client/smd.go index 48af020..54d8c57 100644 --- a/internal/client/smd.go +++ b/internal/client/smd.go @@ -118,6 +118,13 @@ type Group struct { } `json:"members,omitempty"` } +// GroupMembers represents the payload structure for SMD group membership for +// PUT requests. It consists of only the group label and list of group IDs. +type GroupMembers struct { + Label string `json:"label"` + IDs []string `json:"ids"` +} + // NewSMDClient takes a baseURI and basePath and returns a pointer to a new // SMDClient. If an error occurred creating the embedded OchamiClient, it is // returned. If insecure is true, TLS certificates will not be verified. @@ -775,6 +782,56 @@ func (sc *SMDClient) PutRedfishEndpointsV2(rfes RedfishEndpointSliceV2, token st return henvs, errors, nil } +// PutGroupMembers is a wrapper function around OchamiClient.PutData that takes +// a token, group name, and a list of one or more component IDs. It puts the +// token in the request headers as an authorization bearer and calls +// OchamiClient.PostData on the SMD group members API endpoint with the group +// and member list. +func (sc *SMDClient) PutGroupMembers(token, group string, members ...string) (HTTPEnvelope, error) { + var ( + henv HTTPEnvelope + headers *HTTPHeaders + body HTTPBody + ) + + // Check that group and member list are non-empty + if group == "" { + return henv, fmt.Errorf("PutGroupMembers(): no group label specified to set members of") + } + if len(members) == 0 { + return henv, fmt.Errorf("PutGroupMembers(): no members specified") + } + + // Add token to headers + headers = NewHTTPHeaders() + if token != "" { + if err := headers.SetAuthorization(token); err != nil { + return henv, fmt.Errorf("PutGroupMembers(): error setting token in HTTP headers: %w", err) + } + } + + // Calculate endpoint path for group + groupPath, err := url.JoinPath(SMDRelpathGroups, group, "members") + if err != nil { + return henv, fmt.Errorf("PutGroupMembers(): failed to join group path (%s) with group label (%s): %w", SMDRelpathGroups, group) + } + + // Send request and return response + g := GroupMembers{ + Label: group, + IDs: members, + } + if body, err = json.Marshal(g); err != nil { + return henv, fmt.Errorf("PutGroupMembers(): failed to marshal group data: %w", err) + } + henv, err = sc.PutData(groupPath, "", headers, body) + if err != nil { + err = fmt.Errorf("PutGroupMembers(): failed to PUT members to group %s: %w", group, err) + } + + return henv, err +} + // PatchComponentsNID is a wrapper function around OchamiClient.PatchData that // takes a slice of Components and a token. It doesn't read any data fields // within each Component except ID (xname) and NID, and for each Component, all diff --git a/man/ochami-smd.1.sc b/man/ochami-smd.1.sc index 9883efc..dde312c 100644 --- a/man/ochami-smd.1.sc +++ b/man/ochami-smd.1.sc @@ -132,6 +132,21 @@ Below is an example of a single *Group* in JSON form. ] ``` +If performing a PUT on group membership, e.g. with *ochami smd group member +set*, then the form uses _label_ and _ids_ as: + +``` +{ + "label": "blue", + "ids": [ + "x1c0s1b0n0", + "x1c0s1b0n1", + "x1c0s2b0n0", + "x1c0s2b0n1" + ] +} +``` + ## EthernetInterface The *EthernetInterface* contains information on a network interface for a @@ -569,6 +584,12 @@ Subcommands for this command are as follows: - _json_ (default) - _yaml_ +*set* _group_name_ _xname_... + Set the membership list of _group_name_ to _xname_.... Xnames specified that + are not already in the group are added to it, xnames specified that are + already in the group remain in the group, and xnames not specified that are + already in the group are removed from the group. + # AUTHOR Written by Devon T. Bautista and maintained by the OpenCHAMI developers.