Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
225 changes: 225 additions & 0 deletions cmd/nvfleetint/computezone.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@
package main

import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"strings"

"github.com/NVIDIA/fleet-intelligence-client/internal/clihelpers"
clioutput "github.com/NVIDIA/fleet-intelligence-client/internal/output"
Expand All @@ -22,6 +25,20 @@ type computeZoneListFlags struct {
zoneIDs string
}

// Stores local flag values for computezone update
type computeZoneUpdateFlags struct {
zoneType string
contactEmail string
contactPIC string
geoCity string
geoCountry string
geoRegion string
geoLatitude string
geoLongitude string
yes bool
dryRun bool
}

// Stores data ready for computezone list rendering
type computeZoneListOutput struct {
ComputeZones []nvfleetint.ComputeZone
Expand All @@ -40,6 +57,7 @@ func newComputeZoneCmd() *cobra.Command {
}

cmd.AddCommand(newComputeZoneListCmd())
cmd.AddCommand(newComputeZoneUpdateCmd())
rejectUnknownSubcommands(cmd)

return cmd
Expand Down Expand Up @@ -70,6 +88,35 @@ func newComputeZoneListCmd() *cobra.Command {
return cmd
}

// Creates the compute zone update command
func newComputeZoneUpdateCmd() *cobra.Command {
flags := computeZoneUpdateFlags{}
common := newCommonFlags()

cmd := &cobra.Command{
Use: "update <id>",
Short: "Update compute zone metadata",
Args: requireSingleArg("compute zone ID"),
RunE: func(cmd *cobra.Command, args []string) error {
return runComputeZoneUpdate(cmd, args[0], flags, resolveCommonFlags(cmd, common))
},
}

cmd.Flags().StringVar(&flags.zoneType, "type", "", `Compute zone type: datacenter or "cloud provider"`)
cmd.Flags().StringVar(&flags.contactEmail, "contact-email", "", "Contact email; pass an empty value to clear")
cmd.Flags().StringVar(&flags.contactPIC, "contact-pic", "", "Contact person in charge; pass an empty value to clear")
cmd.Flags().StringVar(&flags.geoCity, "geo-city", "", "Location city; pass an empty value to clear")
cmd.Flags().StringVar(&flags.geoCountry, "geo-country", "", "Location country; pass an empty value to clear")
cmd.Flags().StringVar(&flags.geoRegion, "geo-region", "", "Location region; pass an empty value to clear")
cmd.Flags().StringVar(&flags.geoLatitude, "geo-latitude", "", "Location latitude between -90 and 90; pass an empty value to clear")
cmd.Flags().StringVar(&flags.geoLongitude, "geo-longitude", "", "Location longitude between -180 and 180; pass an empty value to clear")
cmd.Flags().BoolVar(&flags.yes, "yes", false, "Skip the confirmation prompt")
cmd.Flags().BoolVar(&flags.dryRun, "dry-run", false, "Preview the request without sending it")
registerReadCommonFlags(cmd, common)

return cmd
}

// Validates flags, calls the SDK, and writes output
func runComputeZoneList(cmd *cobra.Command, flags computeZoneListFlags, common resolvedCommonFlags) error {
if err := validateComputeZoneListFlags(flags, common); err != nil {
Expand Down Expand Up @@ -143,6 +190,39 @@ func runComputeZoneList(cmd *cobra.Command, flags computeZoneListFlags, common r
})
}

// Validates flags, calls the SDK, and writes output
func runComputeZoneUpdate(cmd *cobra.Command, id string, flags computeZoneUpdateFlags, common resolvedCommonFlags) error {
if err := validateComputeZoneUpdateFlags(cmd, flags, common); err != nil {
return err
}

client, err := newConfiguredClient(common)
if err != nil {
return err
}
opts := computeZoneUpdateOptionsFromFlags(cmd, id, flags)

if flags.dryRun {
preview, err := client.PreviewUpdateComputeZone(cmd.Context(), opts)
if err != nil {
return err
}
return writeComputeZoneUpdatePreview(cmd.OutOrStdout(), common, preview)
}

if !flags.yes {
if err := clihelpers.Confirm(cmd.InOrStdin(), cmd.ErrOrStderr(), computeZoneUpdateSummary(id, opts)); err != nil {
return err
}
}

result, err := client.UpdateComputeZone(cmd.Context(), opts)
if err != nil {
return err
}
return writeComputeZoneUpdateOutput(cmd.OutOrStdout(), common, result)
}

// Checks compute zone list flags
func validateComputeZoneListFlags(flags computeZoneListFlags, common resolvedCommonFlags) error {
if err := validateListCommonFlags(common); err != nil {
Expand All @@ -154,6 +234,92 @@ func validateComputeZoneListFlags(flags computeZoneListFlags, common resolvedCom
return nil
}

// Checks compute zone update flags
func validateComputeZoneUpdateFlags(cmd *cobra.Command, flags computeZoneUpdateFlags, common resolvedCommonFlags) error {
if err := validateReadCommonFlags(common); err != nil {
return err
}
if !hasComputeZoneUpdateFlag(cmd) {
return errors.New("at least one update flag must be set")
}
if cmd.Flags().Changed("type") {
zoneType := strings.TrimSpace(flags.zoneType)
if zoneType == "" {
return errors.New("--type cannot be empty")
}
if !nvfleetint.ComputeZoneType(zoneType).Valid() {
return fmt.Errorf("invalid --type %q: expected datacenter or cloud provider", zoneType)
}
}
// An empty coordinate clears the stored value, so only real values are checked.
if cmd.Flags().Changed("geo-latitude") && strings.TrimSpace(flags.geoLatitude) != "" {
if err := nvfleetint.ValidateLatitude(flags.geoLatitude); err != nil {
return fmt.Errorf("--geo-latitude: %w", err)
}
}
if cmd.Flags().Changed("geo-longitude") && strings.TrimSpace(flags.geoLongitude) != "" {
if err := nvfleetint.ValidateLongitude(flags.geoLongitude); err != nil {
return fmt.Errorf("--geo-longitude: %w", err)
}
}
return nil
}

func hasComputeZoneUpdateFlag(cmd *cobra.Command) bool {
for _, name := range []string{
"type",
"contact-email",
"contact-pic",
"geo-city",
"geo-country",
"geo-region",
"geo-latitude",
"geo-longitude",
} {
if cmd.Flags().Changed(name) {
return true
}
}
return false
}

func computeZoneUpdateOptionsFromFlags(cmd *cobra.Command, id string, flags computeZoneUpdateFlags) nvfleetint.UpdateComputeZoneOptions {
opts := nvfleetint.UpdateComputeZoneOptions{ID: id}
if cmd.Flags().Changed("type") {
value := strings.TrimSpace(flags.zoneType)
opts.Type = &value
}
if cmd.Flags().Changed("contact-email") {
value := strings.TrimSpace(flags.contactEmail)
opts.ContactEmail = &value
}
if cmd.Flags().Changed("contact-pic") {
value := strings.TrimSpace(flags.contactPIC)
opts.ContactPIC = &value
}
if cmd.Flags().Changed("geo-city") {
value := strings.TrimSpace(flags.geoCity)
opts.GeoCity = &value
}
if cmd.Flags().Changed("geo-country") {
value := strings.TrimSpace(flags.geoCountry)
opts.GeoCountry = &value
}
if cmd.Flags().Changed("geo-region") {
value := strings.TrimSpace(flags.geoRegion)
opts.GeoRegion = &value
}
if cmd.Flags().Changed("geo-latitude") {
value := strings.TrimSpace(flags.geoLatitude)
opts.GeoLatitude = &value
}
if cmd.Flags().Changed("geo-longitude") {
value := strings.TrimSpace(flags.geoLongitude)
opts.GeoLongitude = &value
}
return opts
}

// Writes JSON or table output for compute zone list results
func writeComputeZoneListOutput(w io.Writer, common resolvedCommonFlags, result computeZoneListOutput) error {
if common.output == clioutput.FormatJSON {
Expand All @@ -169,6 +335,65 @@ func writeComputeZoneListOutput(w io.Writer, common resolvedCommonFlags, result
return clioutput.WritePaginationFooter(w, *result.Page)
}

// Writes JSON or text output for a successful compute zone update
func writeComputeZoneUpdateOutput(w io.Writer, common resolvedCommonFlags, result nvfleetint.UpdateComputeZoneResult) error {
if common.output == clioutput.FormatJSON {
return clioutput.WriteRawJSON(w, result.RawJSON)
}

id := clioutput.DisplayString(result.ID)
_, err := fmt.Fprintf(w, "Compute zone %q updated.\n", id)
return err
}

// Writes the dry-run request preview
func writeComputeZoneUpdatePreview(w io.Writer, common resolvedCommonFlags, preview nvfleetint.RequestPreview) error {
if common.output == clioutput.FormatJSON {
return clioutput.WriteJSON(w, preview)
}

prettyBody := preview.Body
var formatted bytes.Buffer
if len(preview.Body) > 0 && json.Indent(&formatted, preview.Body, "", " ") == nil {
prettyBody = formatted.Bytes()
}

if _, err := fmt.Fprintf(w, "Dry run: no write request sent.\nMETHOD: %s\nURL: %s\nBODY:\n%s\n", preview.Method, preview.URL, prettyBody); err != nil {
return err
}
return nil
Comment thread
emilyzhangbg marked this conversation as resolved.
}

func computeZoneUpdateSummary(id string, opts nvfleetint.UpdateComputeZoneOptions) string {
var fields []string
if opts.Type != nil {
fields = append(fields, "type")
}
if opts.ContactEmail != nil {
fields = append(fields, "contact email")
}
if opts.ContactPIC != nil {
fields = append(fields, "contact PIC")
}
if opts.GeoCity != nil {
fields = append(fields, "geo city")
}
if opts.GeoCountry != nil {
fields = append(fields, "geo country")
}
if opts.GeoRegion != nil {
fields = append(fields, "geo region")
}
if opts.GeoLatitude != nil {
fields = append(fields, "geo latitude")
}
if opts.GeoLongitude != nil {
fields = append(fields, "geo longitude")
}

return fmt.Sprintf("Update compute zone %q fields: %s.", id, strings.Join(fields, ", "))
}

// Renders compute zones using the selected view columns
func writeComputeZoneTable(w io.Writer, view string, zones []nvfleetint.ComputeZone) error {
if nvfleetint.ComputeZoneView(view) == nvfleetint.ComputeZoneViewBasic {
Expand Down
Loading
Loading