diff --git a/Makefile b/Makefile index 181e180..8c130f8 100644 --- a/Makefile +++ b/Makefile @@ -15,6 +15,8 @@ generate: deps mockgen --destination ./internal/mocks/racks_service.go --package=mocks --source ./vendor/github.com/serverscom/serverscom-go-client/pkg/racks.go mockgen --destination ./internal/mocks/invoices_service.go --package=mocks --source ./vendor/github.com/serverscom/serverscom-go-client/pkg/invoices.go mockgen --destination ./internal/mocks/account_service.go --package=mocks --source ./vendor/github.com/serverscom/serverscom-go-client/pkg/accounts.go + mockgen --destination ./internal/mocks/locations_service.go --package=mocks --source ./vendor/github.com/serverscom/serverscom-go-client/pkg/locations.go + mockgen --destination ./internal/mocks/kubernetes_clusters_service.go --package=mocks --source ./vendor/github.com/serverscom/serverscom-go-client/pkg/kubernetes_clusters.go sed -i '' 's|github.com/serverscom/srvctl/vendor/github.com/serverscom/serverscom-go-client/pkg|github.com/serverscom/serverscom-go-client/pkg|g' \ ./internal/mocks/ssh_service.go \ ./internal/mocks/hosts_service.go \ @@ -23,5 +25,7 @@ generate: deps ./internal/mocks/racks_service.go \ ./internal/mocks/invoices_service.go \ ./internal/mocks/account_service.go \ - ./internal/mocks/collection.go + ./internal/mocks/collection.go \ + ./internal/mocks/locations_service.go \ + ./internal/mocks/kubernetes_clusters_service.go diff --git a/cmd/base/flags.go b/cmd/base/flags.go index c780b10..3baeed1 100644 --- a/cmd/base/flags.go +++ b/cmd/base/flags.go @@ -11,7 +11,6 @@ func AddGlobalFlags(cmd *cobra.Command) { cmd.PersistentFlags().StringP("output", "o", "text", "output format (text/json/yaml)") // define help flag without shorthand before cobra adds it by default to avoid conflict with no-header flag shorthand cmd.PersistentFlags().Bool("help", false, "Print usage") - cmd.PersistentFlags().Lookup("help").Hidden = true cmd.PersistentFlags().BoolP("no-header", "h", false, "print output without headers") } diff --git a/cmd/entities/hosts/add.go b/cmd/entities/hosts/add.go index 60e58b3..0339def 100644 --- a/cmd/entities/hosts/add.go +++ b/cmd/entities/hosts/add.go @@ -1,53 +1,210 @@ package hosts import ( - "context" "fmt" "log" + "os" + + "maps" serverscom "github.com/serverscom/serverscom-go-client/pkg" "github.com/serverscom/srvctl/cmd/base" "github.com/spf13/cobra" + "github.com/spf13/pflag" ) -type HostCreator interface { - Create(ctx context.Context, client *serverscom.Client, input any) (any, error) - NewCreateInput() any +type AddDSFlags struct { + InputPath string + LocationID int + ServerModelID int + OperatingSystemID int + Features []string + RAMSize int + PublicUplinkID int + PublicBandwidthID int + PrivateUplinkID int + DriveSlots map[string]int + Layout []string + Partitions []string + IPv6 bool + UserDataFile string + UserData string + Labels map[string]string } -type DSCreateMgr struct{} +func applyFlagsToInput( + input *serverscom.DedicatedServerCreateInput, + flags *AddDSFlags, + pflags *pflag.FlagSet, +) error { + if pflags.Changed("location-id") { + input.LocationID = int64(flags.LocationID) + } + if pflags.Changed("server-model-id") { + input.ServerModelID = int64(flags.ServerModelID) + } + if pflags.Changed("operating-system-id") { + val := int64(flags.OperatingSystemID) + input.OperatingSystemID = &val + } + if pflags.Changed("feature") { + input.Features = flags.Features + } + if pflags.Changed("ram-size") { + input.RAMSize = flags.RAMSize + } + if pflags.Changed("public-uplink-id") { + if input.UplinkModels.Public == nil { + input.UplinkModels.Public = &serverscom.DedicatedServerPublicUplinkInput{} + } + input.UplinkModels.Public.ID = int64(flags.PublicUplinkID) + } + if pflags.Changed("public-bandwidth-id") { + if input.UplinkModels.Public == nil { + input.UplinkModels.Public = &serverscom.DedicatedServerPublicUplinkInput{} + } + input.UplinkModels.Public.BandwidthModelID = int64(flags.PublicBandwidthID) + } + if pflags.Changed("private-uplink-id") { + input.UplinkModels.Private.ID = int64(flags.PrivateUplinkID) + } -func (c *DSCreateMgr) Create(ctx context.Context, client *serverscom.Client, input any) (any, error) { - dsInput, ok := input.(*serverscom.DedicatedServerCreateInput) - if !ok { - return nil, fmt.Errorf("invalid input type for dedicated server") + if pflags.Changed("drive-slots") { + slots, err := parseDriveSlots(flags.DriveSlots) + if err != nil { + return err + } + input.Drives.Slots = slots } - return client.Hosts.CreateDedicatedServers(ctx, *dsInput) -} -func (c *DSCreateMgr) NewCreateInput() any { - return &serverscom.DedicatedServerCreateInput{} -} + if pflags.Changed("layout") { + layouts, err := parseLayout(flags.Layout) + if err != nil { + return err + } + input.Drives.Layout = mergeLayouts(input.Drives.Layout, layouts) + } -type SBMCreateMgr struct{} + if pflags.Changed("partition") { + if len(input.Drives.Layout) == 0 { + return fmt.Errorf("partition given but layout is empty") + } + partitions, err := parsePartitions(flags.Partitions) + if err != nil { + return err + } + err = applyPartitions(input.Drives.Layout, partitions) + if err != nil { + return err + } + } + if pflags.Changed("ipv6") { + input.IPv6 = flags.IPv6 + } + if pflags.Changed("user-data") && pflags.Changed("user-data-file") { + return fmt.Errorf("'user-data' and 'user-data-file' can't be used together") + } + if pflags.Changed("user-data-file") { + data, err := os.ReadFile(flags.UserDataFile) + if err != nil { + return fmt.Errorf("can't read user-data-file: %v", err) + } + dataStr := string(data) + input.UserData = &dataStr + } + if pflags.Changed("user-data") { + input.UserData = &flags.UserData + } -func (c *SBMCreateMgr) Create(ctx context.Context, client *serverscom.Client, input any) (any, error) { - sbmInput, ok := input.(*serverscom.SBMServerCreateInput) - if !ok { - return nil, fmt.Errorf("invalid input type for SBM server") + if pflags.Changed("labels") { + for i := range input.Hosts { + maps.Copy(input.Hosts[i].Labels, flags.Labels) + } } - return client.Hosts.CreateSBMServers(ctx, *sbmInput) + return nil } -func (c *SBMCreateMgr) NewCreateInput() any { - return &serverscom.SBMServerCreateInput{} +func newAddDSCmd(cmdContext *base.CmdContext) *cobra.Command { + flags := &AddDSFlags{} + + cmd := &cobra.Command{ + Use: "add", + Short: "Create a dedicated server", + Args: cobra.ArbitraryArgs, + RunE: func(cmd *cobra.Command, args []string) error { + + manager := cmdContext.GetManager() + ctx, cancel := base.SetupContext(cmd, manager) + defer cancel() + + base.SetupProxy(cmd, manager) + + input := serverscom.DedicatedServerCreateInput{} + + if flags.InputPath != "" { + if err := base.ReadInputJSON(flags.InputPath, cmd.InOrStdin(), &input); err != nil { + return err + } + } + + if len(input.Hosts) == 0 && len(args) == 0 { + return fmt.Errorf("no hosts found from positional args and no hosts found from input, can't continue") + } + + for _, hostname := range args { + input.Hosts = append(input.Hosts, serverscom.DedicatedServerHostInput{ + Hostname: hostname, + Labels: make(map[string]string), + }) + } + + err := applyFlagsToInput(&input, flags, cmd.Flags()) + if err != nil { + return err + } + + scClient := cmdContext.GetClient().SetVerbose(manager.GetVerbose(cmd)).GetScClient() + + server, err := scClient.Hosts.CreateDedicatedServers(ctx, input) + if err != nil { + return err + } + + if server != nil { + formatter := cmdContext.GetOrCreateFormatter(cmd) + return formatter.Format(server) + } + + return nil + }, + } + + cmd.Flags().StringVarP(&flags.InputPath, "input", "i", "", "path to input file or '-' to read from stdin") + + cmd.Flags().IntVar(&flags.LocationID, "location-id", 0, "Create the server(s) in the specific location ID") + cmd.Flags().IntVar(&flags.ServerModelID, "server-model-id", 0, "Use specific server model ID to create the server") + cmd.Flags().IntVar(&flags.OperatingSystemID, "operating-system-id", 0, "Install the specific operating system") + cmd.Flags().StringSliceVar(&flags.Features, "feature", nil, "Set of features") + cmd.Flags().IntVar(&flags.RAMSize, "ram-size", 0, "Desired amount of RAM in GB") + cmd.Flags().IntVar(&flags.PublicUplinkID, "public-uplink-id", 0, "The public uplink ID, can be omitted if do not want public uplink") + cmd.Flags().IntVar(&flags.PrivateUplinkID, "private-uplink-id", 0, "The private uplink ID") + cmd.Flags().IntVar(&flags.PublicBandwidthID, "public-bandwidth-id", 0, "The public bandwidth ID, MUST be omitted if public uplink id is not passed") + cmd.Flags().StringToIntVar(&flags.DriveSlots, "drive-slots", nil, "mapping of the specific slot to the specific drive model") + cmd.Flags().StringArrayVar(&flags.Layout, "layout", nil, "Configuration of drives layout") + cmd.Flags().StringArrayVar(&flags.Partitions, "partition", nil, "Configuration of the specific partitions") + cmd.Flags().BoolVar(&flags.IPv6, "ipv6", false, "Enable IPv6") + cmd.Flags().StringVar(&flags.UserDataFile, "user-data-file", "", "Path to user data which should be readed") + cmd.Flags().StringVar(&flags.UserData, "user-data", "", "Content of user data") + cmd.Flags().StringToStringVar(&flags.Labels, "labels", nil, "The set of labels which will be applied to the all hosts of this operation") + + return cmd } -func newAddCmd(cmdContext *base.CmdContext, hostType *HostTypeCmd) *cobra.Command { +func newAddSBMCmd(cmdContext *base.CmdContext) *cobra.Command { var path string cmd := &cobra.Command{ Use: "add --input ", - Short: fmt.Sprintf("Create a %s", hostType.entityName), + Short: "Create an SBM server", Args: cobra.ExactArgs(0), RunE: func(cmd *cobra.Command, args []string) error { manager := cmdContext.GetManager() @@ -56,15 +213,15 @@ func newAddCmd(cmdContext *base.CmdContext, hostType *HostTypeCmd) *cobra.Comman base.SetupProxy(cmd, manager) - input := hostType.managers.createMgr.NewCreateInput() + input := serverscom.SBMServerCreateInput{} - if err := base.ReadInputJSON(path, cmd.InOrStdin(), input); err != nil { + if err := base.ReadInputJSON(path, cmd.InOrStdin(), &input); err != nil { return err } scClient := cmdContext.GetClient().SetVerbose(manager.GetVerbose(cmd)).GetScClient() - server, err := hostType.managers.createMgr.Create(ctx, scClient, input) + server, err := scClient.Hosts.CreateSBMServers(ctx, input) if err != nil { return err } diff --git a/cmd/entities/hosts/hosts.go b/cmd/entities/hosts/hosts.go index 59d7b1a..ce68792 100644 --- a/cmd/entities/hosts/hosts.go +++ b/cmd/entities/hosts/hosts.go @@ -19,8 +19,7 @@ type HostTypeCmd struct { } type HostManagers struct { - getMgr HostGetter - createMgr HostCreator + getMgr HostGetter // for update we use simple commands in sake of simplicity powerMgr HostPowerer reinstallMgr HostReinstaller @@ -40,11 +39,11 @@ func NewCmd(cmdContext *base.CmdContext) *cobra.Command { typeFlag: "dedicated_server", managers: HostManagers{ getMgr: &DSGetMgr{}, - createMgr: &DSCreateMgr{}, powerMgr: &DSPowerMgr{}, reinstallMgr: &DSReinstallMgr{}, }, extraCmds: []func(*base.CmdContext) *cobra.Command{ + newAddDSCmd, newUpdateDSCmd, newListDSDriveSlotsCmd, newListDSConnectionsCmd, @@ -73,11 +72,11 @@ func NewCmd(cmdContext *base.CmdContext) *cobra.Command { typeFlag: "sbm_server", managers: HostManagers{ getMgr: &SBMGetMgr{}, - createMgr: &SBMCreateMgr{}, powerMgr: &SBMPowerMgr{}, reinstallMgr: &SBMReinstallMgr{}, }, extraCmds: []func(*base.CmdContext) *cobra.Command{ + newAddSBMCmd, newUpdateSBMCmd, newSBMReleaseCmd, }, @@ -119,9 +118,6 @@ func newHostTypeCmd(cmdContext *base.CmdContext, hostTypeCmd HostTypeCmd) *cobra hostCmd.AddCommand(newListCmd(cmdContext, &hostTypeCmd)) hostCmd.AddCommand(newGetCmd(cmdContext, &hostTypeCmd)) - if hostTypeCmd.managers.createMgr != nil { - hostCmd.AddCommand(newAddCmd(cmdContext, &hostTypeCmd)) - } if hostTypeCmd.managers.powerMgr != nil { hostCmd.AddCommand(newPowerCmd(cmdContext, &hostTypeCmd)) hostCmd.AddCommand(newListPowerFeedsCmd(cmdContext, &hostTypeCmd)) diff --git a/cmd/entities/hosts/hosts_test.go b/cmd/entities/hosts/hosts_test.go index f16b44a..2989874 100644 --- a/cmd/entities/hosts/hosts_test.go +++ b/cmd/entities/hosts/hosts_test.go @@ -26,6 +26,7 @@ var ( } testDS = serverscom.DedicatedServer{ ID: testId, + RackID: testId, Type: "dedicated_server", Title: "example.aa", Status: "active", @@ -33,15 +34,20 @@ var ( Updated: fixedTime, } testKBM = serverscom.KubernetesBaremetalNode{ - ID: testId, - Type: "kubernetes_baremetal_node", - Title: "example.aa", - Status: "active", - Created: fixedTime, - Updated: fixedTime, + ID: testId, + RackID: testId, + KubernetesClusterID: testId, + KubernetesClusterNodeID: testId, + KubernetesClusterNodeNumber: 1, + Type: "kubernetes_baremetal_node", + Title: "example.aa", + Status: "active", + Created: fixedTime, + Updated: fixedTime, } testSBM = serverscom.SBMServer{ ID: testId, + RackID: testId, Type: "sbm_server", Title: "example.aa", Status: "active", @@ -51,6 +57,57 @@ var ( ) func TestAddDSCmd(t *testing.T) { + expectedInput := serverscom.DedicatedServerCreateInput{ + ServerModelID: 1234, + LocationID: 5678, + RAMSize: 16, + UplinkModels: serverscom.DedicatedServerUplinkModelsInput{ + Public: &serverscom.DedicatedServerPublicUplinkInput{ + ID: 4321, + BandwidthModelID: 8765, + }, + Private: serverscom.DedicatedServerPrivateUplinkInput{ + ID: 7890, + }, + }, + Drives: serverscom.DedicatedServerDrivesInput{ + Slots: []serverscom.DedicatedServerSlotInput{ + { + Position: 1, + DriveModelID: testutils.PtrInt64(3456), + }, + { + Position: 2, + DriveModelID: testutils.PtrInt64(3456), + }, + }, + Layout: []serverscom.DedicatedServerLayoutInput{ + { + SlotPositions: []int{1, 2}, + Raid: testutils.PtrInt(1), + Partitions: []serverscom.DedicatedServerLayoutPartitionInput{ + { + Target: "/boot", + Size: 500, + Fill: false, + Fs: testutils.PtrString("ext4"), + }, + }, + }, + }, + }, + Hosts: []serverscom.DedicatedServerHostInput{ + { + Hostname: "example.aa", + PublicIPv4NetworkID: testutils.PtrString("PublicNet123"), + PrivateIPv4NetworkID: testutils.PtrString("PrivateNet456"), + Labels: map[string]string{ + "environment": "testing", + }, + }, + }, + } + testCases := []struct { name string output string @@ -66,56 +123,36 @@ func TestAddDSCmd(t *testing.T) { args: []string{"--input", filepath.Join(fixtureBasePath, "create_ds_input.json")}, configureMock: func(mock *mocks.MockHostsService) { mock.EXPECT(). - CreateDedicatedServers(gomock.Any(), serverscom.DedicatedServerCreateInput{ - ServerModelID: 1234, - LocationID: 5678, - RAMSize: 16, - UplinkModels: serverscom.DedicatedServerUplinkModelsInput{ - Public: &serverscom.DedicatedServerPublicUplinkInput{ - ID: 4321, - BandwidthModelID: 8765, - }, - Private: serverscom.DedicatedServerPrivateUplinkInput{ - ID: 7890, - }, - }, - Drives: serverscom.DedicatedServerDrivesInput{ - Slots: []serverscom.DedicatedServerSlotInput{ - { - Position: 1, - DriveModelID: testutils.PtrInt64(3456), - }, - { - Position: 2, - DriveModelID: testutils.PtrInt64(3456), - }, - }, - Layout: []serverscom.DedicatedServerLayoutInput{ - { - SlotPositions: []int{1, 2}, - Raid: testutils.PtrInt(1), - Partitions: []serverscom.DedicatedServerLayoutPartitionInput{ - { - Target: "/boot", - Size: 500, - Fill: false, - Fs: testutils.PtrString("ext4"), - }, - }, - }, - }, - }, - Hosts: []serverscom.DedicatedServerHostInput{ - { - Hostname: "example.aa", - PublicIPv4NetworkID: testutils.PtrString("PublicNet123"), - PrivateIPv4NetworkID: testutils.PtrString("PrivateNet456"), - Labels: map[string]string{ - "environment": "testing", - }, - }, + CreateDedicatedServers(gomock.Any(), expectedInput). + Return([]serverscom.DedicatedServer{testDS}, nil) + }, + }, + { + name: "create dedicated server with merge input with flags", + output: "json", + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "create_ds_resp.json")), + args: []string{ + "--input", filepath.Join(fixtureBasePath, "create_ds_input.json"), + "--layout", "slot=3,slot=4,raid=0", + "--partition", "slot=3,slot=4,target=/boot,fs=ext4,size=500", + }, + configureMock: func(mock *mocks.MockHostsService) { + input := expectedInput + input.Drives.Layout = append(input.Drives.Layout, serverscom.DedicatedServerLayoutInput{ + SlotPositions: []int{3, 4}, + Raid: testutils.PtrInt(0), + Partitions: []serverscom.DedicatedServerLayoutPartitionInput{ + { + Target: "/boot", + Size: 500, + Fill: false, + Fs: testutils.PtrString("ext4"), }, - }). + }, + }) + + mock.EXPECT(). + CreateDedicatedServers(gomock.Any(), input). Return([]serverscom.DedicatedServer{testDS}, nil) }, }, diff --git a/cmd/entities/hosts/utils.go b/cmd/entities/hosts/utils.go new file mode 100644 index 0000000..2a1f197 --- /dev/null +++ b/cmd/entities/hosts/utils.go @@ -0,0 +1,209 @@ +package hosts + +import ( + "fmt" + "slices" + "sort" + "strconv" + "strings" + + serverscom "github.com/serverscom/serverscom-go-client/pkg" +) + +type parsedPartition struct { + Slots []int + Partition serverscom.DedicatedServerLayoutPartitionInput +} + +// parseDriveSlots parses driveSlots map into []DedicatedServerSlotInput +func parseDriveSlots(driveSlots map[string]int) ([]serverscom.DedicatedServerSlotInput, error) { + slots := make([]serverscom.DedicatedServerSlotInput, 0, len(driveSlots)) + for pos, id := range driveSlots { + dId := int64(id) + posNum, err := strconv.Atoi(pos) + if err != nil { + return nil, fmt.Errorf("can't parse drive slot position '%s' as integer", pos) + } + slots = append(slots, serverscom.DedicatedServerSlotInput{ + Position: posNum, + DriveModelID: &dId, + }) + } + return slots, nil +} + +// parseLayout parses layouts strings into []DedicatedServerLayoutInput +func parseLayout(layouts []string) ([]serverscom.DedicatedServerLayoutInput, error) { + var result []serverscom.DedicatedServerLayoutInput + + for _, l := range layouts { + var lInput serverscom.DedicatedServerLayoutInput + parts := strings.Split(l, ",") + + for _, part := range parts { + pair := strings.SplitN(part, "=", 2) + if len(pair) != 2 { + continue + } + key := pair[0] + val := pair[1] + + switch key { + case "slot": + num, err := strconv.Atoi(val) + if err != nil { + return nil, fmt.Errorf("can't parse layout slot '%s' as integer", val) + } + lInput.SlotPositions = append(lInput.SlotPositions, num) + case "raid": + raid, err := strconv.Atoi(val) + if err != nil { + return nil, fmt.Errorf("can't parse layout raid '%s' as integer", val) + } + lInput.Raid = &raid + } + } + if len(lInput.SlotPositions) == 0 { + return nil, fmt.Errorf("slots not passed for layout '%s", l) + } + if lInput.Raid == nil { + return nil, fmt.Errorf("raid not passed for layout '%s'", l) + } + result = append(result, lInput) + } + return result, nil +} + +// parsePartitions parses partitions strings into []parsedPartition +func parsePartitions(partitions []string) ([]parsedPartition, error) { + var result []parsedPartition + + for _, p := range partitions { + var pp parsedPartition + parts := strings.Split(p, ",") + + for _, part := range parts { + pair := strings.SplitN(part, "=", 2) + if len(pair) != 2 { + continue + } + key, val := pair[0], pair[1] + + switch key { + case "slot": + slot, err := strconv.Atoi(val) + if err != nil { + return nil, fmt.Errorf("invalid slot value: %s", val) + } + pp.Slots = append(pp.Slots, slot) + case "target": + pp.Partition.Target = val + case "size": + size, err := strconv.Atoi(val) + if err != nil { + return nil, fmt.Errorf("invalid size: %s", val) + } + pp.Partition.Size = size + case "fs": + pp.Partition.Fs = &val + case "fill": + pp.Partition.Fill = (strings.ToLower(val) == "true") + } + } + + if len(pp.Slots) == 0 { + return nil, fmt.Errorf("no slot specified for partition: %s", p) + } + + sort.Ints(pp.Slots) + result = append(result, pp) + } + + return result, nil +} + +// mergeLayouts merges layouts by merging slots if overlaps or just appends new layout +func mergeLayouts(inputLayouts, newLayouts []serverscom.DedicatedServerLayoutInput) []serverscom.DedicatedServerLayoutInput { + for _, newLayout := range newLayouts { + merged := false + + for i := range inputLayouts { + existing := &inputLayouts[i] + + slotSet := make(map[int]struct{}, len(existing.SlotPositions)) + for _, s := range existing.SlotPositions { + slotSet[s] = struct{}{} + } + + overlap := false + for _, s := range newLayout.SlotPositions { + if _, ok := slotSet[s]; ok { + overlap = true + break + } + } + + if !overlap { + continue + } + + for _, s := range newLayout.SlotPositions { + if _, ok := slotSet[s]; !ok { + existing.SlotPositions = append(existing.SlotPositions, s) + } + } + sort.Ints(existing.SlotPositions) + existing.Raid = newLayout.Raid + merged = true + break + } + + if !merged { + inputLayouts = append(inputLayouts, newLayout) + } + } + + return inputLayouts +} + +// applyPartitions finds layout with slots matched partition slots an adds partitions to it. +// Partition will be overrided if target matched. +func applyPartitions(layouts []serverscom.DedicatedServerLayoutInput, parsed []parsedPartition) error { + for _, p := range parsed { + foundLayout := false + + for i := range layouts { + lSlots := slices.Clone(layouts[i].SlotPositions) + pSlots := slices.Clone(p.Slots) + + sort.Ints(lSlots) + sort.Ints(pSlots) + + if !slices.Equal(lSlots, pSlots) { + continue + } + + foundLayout = true + override := false + + for j := range layouts[i].Partitions { + if layouts[i].Partitions[j].Target == p.Partition.Target { + layouts[i].Partitions[j] = p.Partition + override = true + break + } + } + + if !override { + layouts[i].Partitions = append(layouts[i].Partitions, p.Partition) + } + break + } + + if !foundLayout { + return fmt.Errorf("can't apply partition: no layout found with slots: %v", p.Slots) + } + } + + return nil +} diff --git a/cmd/entities/k8s/get.go b/cmd/entities/k8s/get.go new file mode 100644 index 0000000..9973d3b --- /dev/null +++ b/cmd/entities/k8s/get.go @@ -0,0 +1,80 @@ +package k8s + +import ( + "log" + + "github.com/serverscom/srvctl/cmd/base" + "github.com/spf13/cobra" +) + +func newGetCmd(cmdContext *base.CmdContext) *cobra.Command { + cmd := &cobra.Command{ + Use: "get ", + Short: "Get a kubernetes cluster", + Long: "Get a kubernetes cluster by id", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + manager := cmdContext.GetManager() + + ctx, cancel := base.SetupContext(cmd, manager) + defer cancel() + + base.SetupProxy(cmd, manager) + + scClient := cmdContext.GetClient().SetVerbose(manager.GetVerbose(cmd)).GetScClient() + + id := args[0] + k8s, err := scClient.KubernetesClusters.Get(ctx, id) + if err != nil { + return err + } + + if k8s != nil { + formatter := cmdContext.GetOrCreateFormatter(cmd) + return formatter.Format(k8s) + } + return nil + }, + } + + return cmd +} + +func newGetNodeCmd(cmdContext *base.CmdContext) *cobra.Command { + var clusterID string + cmd := &cobra.Command{ + Use: "get-node --cluster-id ", + Short: "Get a kubernetes cluster node", + Long: "Get a kubernetes cluster node by ID", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + manager := cmdContext.GetManager() + + ctx, cancel := base.SetupContext(cmd, manager) + defer cancel() + + base.SetupProxy(cmd, manager) + + scClient := cmdContext.GetClient().SetVerbose(manager.GetVerbose(cmd)).GetScClient() + + nodeID := args[0] + k8s, err := scClient.KubernetesClusters.GetNode(ctx, clusterID, nodeID) + if err != nil { + return err + } + + if k8s != nil { + formatter := cmdContext.GetOrCreateFormatter(cmd) + return formatter.Format(k8s) + } + return nil + }, + } + + cmd.Flags().StringVar(&clusterID, "cluster-id", "", "cluster id") + if err := cmd.MarkFlagRequired("cluster-id"); err != nil { + log.Fatal(err) + } + + return cmd +} diff --git a/cmd/entities/k8s/k8s.go b/cmd/entities/k8s/k8s.go new file mode 100644 index 0000000..17ec702 --- /dev/null +++ b/cmd/entities/k8s/k8s.go @@ -0,0 +1,59 @@ +package k8s + +import ( + "log" + + serverscom "github.com/serverscom/serverscom-go-client/pkg" + "github.com/serverscom/srvctl/cmd/base" + "github.com/serverscom/srvctl/internal/output/entities" + "github.com/spf13/cobra" +) + +func NewCmd(cmdContext *base.CmdContext) *cobra.Command { + entitiesMap, err := getK8sEntities() + if err != nil { + log.Fatal(err) + } + + cmd := &cobra.Command{ + Use: "k8s", + Short: "Manage kubernetes clusters", + PersistentPreRunE: base.CombinePreRunE( + base.CheckFormatterFlags(cmdContext, entitiesMap), + base.CheckEmptyContexts(cmdContext), + ), + Args: base.NoArgs, + Run: base.UsageRun, + } + + cmd.AddCommand( + newListCmd(cmdContext), + newListNodesCmd(cmdContext), + newGetCmd(cmdContext), + newGetNodeCmd(cmdContext), + newUpdateCmd(cmdContext), + ) + + base.AddFormatFlags(cmd) + + return cmd +} + +func getK8sEntities() (map[string]entities.EntityInterface, error) { + result := make(map[string]entities.EntityInterface) + + k8sEntity, err := entities.Registry.GetEntityFromValue(serverscom.KubernetesCluster{}) + if err != nil { + log.Fatal(err) + } + result["k8s"] = k8sEntity + + k8sNodeEntity, err := entities.Registry.GetEntityFromValue(serverscom.KubernetesClusterNode{}) + if err != nil { + log.Fatal(err) + } + result["list-nodes"] = k8sNodeEntity + result["get-node"] = k8sNodeEntity + + return result, nil +} diff --git a/cmd/entities/k8s/k8s_test.go b/cmd/entities/k8s/k8s_test.go new file mode 100644 index 0000000..7d9cd06 --- /dev/null +++ b/cmd/entities/k8s/k8s_test.go @@ -0,0 +1,558 @@ +package k8s + +import ( + "errors" + "path/filepath" + "testing" + "time" + + "fmt" + + . "github.com/onsi/gomega" + serverscom "github.com/serverscom/serverscom-go-client/pkg" + "github.com/serverscom/srvctl/cmd/testutils" + "github.com/serverscom/srvctl/internal/mocks" + "go.uber.org/mock/gomock" +) + +var ( + testId = "testId" + testNodeId = "testNodeId" + fixtureBasePath = filepath.Join("..", "..", "..", "testdata", "entities", "k8s") + fixedTime = time.Date(2025, 1, 1, 12, 0, 0, 0, time.UTC) + testKubernetesCluster = serverscom.KubernetesCluster{ + ID: testId, + Name: "test-cluster", + Status: "active", + LocationID: 1, + Created: fixedTime, + Updated: fixedTime, + } + testKubernetesClusterNode = serverscom.KubernetesClusterNode{ + ID: testNodeId, + ClusterID: testId, + Number: 1, + Hostname: "test-node-1", + Type: "cloud", + Role: "master", + Status: "active", + PrivateIPv4Address: "10.0.0.1", + PublicIPv4Address: "127.0.0.1", + RefID: "1", + Configuration: "SSD.50", + Created: fixedTime, + Updated: fixedTime, + } +) + +func TestGetKubernetesClusterCmd(t *testing.T) { + testCases := []struct { + name string + id string + output string + expectedOutput []byte + expectError bool + }{ + { + name: "get k8s cluster in default format", + id: testId, + output: "", + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "get.txt")), + }, + { + name: "get k8s cluster in JSON format", + id: testId, + output: "json", + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "get.json")), + }, + { + name: "get k8s cluster in YAML format", + id: testId, + output: "yaml", + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "get.yaml")), + }, + { + name: "get k8s cluster with error", + id: testId, + expectError: true, + }, + } + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + k8sServiceHandler := mocks.NewMockKubernetesClustersService(mockCtrl) + + scClient := serverscom.NewClientWithEndpoint("", "") + scClient.KubernetesClusters = k8sServiceHandler + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + g := NewWithT(t) + + var err error + if tc.expectError { + err = errors.New("some error") + } + k8sServiceHandler.EXPECT(). + Get(gomock.Any(), testId). + Return(&testKubernetesCluster, err) + + testCmdContext := testutils.NewTestCmdContext(scClient) + k8sCmd := NewCmd(testCmdContext) + + args := []string{"k8s", "get", fmt.Sprint(tc.id)} + if tc.output != "" { + args = append(args, "--output", tc.output) + } + + builder := testutils.NewTestCommandBuilder(). + WithCommand(k8sCmd). + WithArgs(args) + + cmd := builder.Build() + + err = cmd.Execute() + + if tc.expectError { + g.Expect(err).To(HaveOccurred()) + } else { + g.Expect(err).To(BeNil()) + g.Expect(builder.GetOutput()).To(BeEquivalentTo(string(tc.expectedOutput))) + } + }) + } +} + +func TestListKubernetesClustersCmd(t *testing.T) { + testK8sCluster1 := testKubernetesCluster + testK8sCluster2 := testKubernetesCluster + testK8sCluster1.ID += "1" + testK8sCluster2.Name = "test-cluster 2" + testK8sCluster2.ID += "2" + + testCases := []struct { + name string + output string + args []string + expectedOutput []byte + expectError bool + configureMock func(*mocks.MockCollection[serverscom.KubernetesCluster]) + }{ + { + name: "list all k8s clusters", + output: "json", + args: []string{"-A"}, + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "list_all.json")), + configureMock: func(mock *mocks.MockCollection[serverscom.KubernetesCluster]) { + mock.EXPECT(). + Collect(gomock.Any()). + Return([]serverscom.KubernetesCluster{ + testK8sCluster1, + testK8sCluster2, + }, nil) + }, + }, + { + name: "list k8s clusters", + output: "json", + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "list.json")), + configureMock: func(mock *mocks.MockCollection[serverscom.KubernetesCluster]) { + mock.EXPECT(). + List(gomock.Any()). + Return([]serverscom.KubernetesCluster{ + testK8sCluster1, + }, nil) + }, + }, + { + name: "list k8s clusters with template", + args: []string{"--template", "{{range .}}Name: {{.Name}}\n{{end}}"}, + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "list_template.txt")), + configureMock: func(mock *mocks.MockCollection[serverscom.KubernetesCluster]) { + mock.EXPECT(). + List(gomock.Any()). + Return([]serverscom.KubernetesCluster{ + testK8sCluster1, + testK8sCluster2, + }, nil) + }, + }, + { + name: "list k8s clusters with pageView", + args: []string{"--page-view"}, + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "list_pageview.txt")), + configureMock: func(mock *mocks.MockCollection[serverscom.KubernetesCluster]) { + mock.EXPECT(). + List(gomock.Any()). + Return([]serverscom.KubernetesCluster{ + testK8sCluster1, + testK8sCluster2, + }, nil) + }, + }, + { + name: "list k8s clusters with error", + expectError: true, + configureMock: func(mock *mocks.MockCollection[serverscom.KubernetesCluster]) { + mock.EXPECT(). + List(gomock.Any()). + Return(nil, errors.New("some error")) + }, + }, + } + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + k8sServiceHandler := mocks.NewMockKubernetesClustersService(mockCtrl) + collectionHandler := mocks.NewMockCollection[serverscom.KubernetesCluster](mockCtrl) + + k8sServiceHandler.EXPECT(). + Collection(). + Return(collectionHandler). + AnyTimes() + + collectionHandler.EXPECT(). + SetParam(gomock.Any(), gomock.Any()). + Return(collectionHandler). + AnyTimes() + + scClient := serverscom.NewClientWithEndpoint("", "") + scClient.KubernetesClusters = k8sServiceHandler + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + g := NewWithT(t) + + if tc.configureMock != nil { + tc.configureMock(collectionHandler) + } + + testCmdContext := testutils.NewTestCmdContext(scClient) + k8sCmd := NewCmd(testCmdContext) + + args := []string{"k8s", "list"} + if len(tc.args) > 0 { + args = append(args, tc.args...) + } + if tc.output != "" { + args = append(args, "--output", tc.output) + } + + builder := testutils.NewTestCommandBuilder(). + WithCommand(k8sCmd). + WithArgs(args) + + cmd := builder.Build() + + err := cmd.Execute() + + if tc.expectError { + g.Expect(err).To(HaveOccurred()) + } else { + g.Expect(err).To(BeNil()) + g.Expect(builder.GetOutput()).To(BeEquivalentTo(string(tc.expectedOutput))) + } + }) + } +} + +func TestUpdateKubernetesClusteCmd(t *testing.T) { + newCluster := testKubernetesCluster + newCluster.Labels = map[string]string{"new": "label"} + + testCases := []struct { + name string + id string + output string + args []string + configureMock func(*mocks.MockKubernetesClustersService) + expectedOutput []byte + expectError bool + }{ + { + name: "update k8s cluster", + id: testId, + output: "json", + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "update.json")), + args: []string{"--label", "new=label"}, + configureMock: func(mock *mocks.MockKubernetesClustersService) { + mock.EXPECT(). + Update(gomock.Any(), testId, serverscom.KubernetesClusterUpdateInput{ + Labels: map[string]string{"new": "label"}, + }). + Return(&newCluster, nil) + }, + }, + { + name: "update k8s cluster with error", + id: testId, + configureMock: func(mock *mocks.MockKubernetesClustersService) { + mock.EXPECT(). + Update(gomock.Any(), testId, serverscom.KubernetesClusterUpdateInput{ + Labels: make(map[string]string), + }). + Return(nil, errors.New("some error")) + }, + expectError: true, + }, + } + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + k8sServiceHandler := mocks.NewMockKubernetesClustersService(mockCtrl) + + scClient := serverscom.NewClientWithEndpoint("", "") + scClient.KubernetesClusters = k8sServiceHandler + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + g := NewWithT(t) + + if tc.configureMock != nil { + tc.configureMock(k8sServiceHandler) + } + + testCmdContext := testutils.NewTestCmdContext(scClient) + k8sCmd := NewCmd(testCmdContext) + + args := []string{"k8s", "update", tc.id} + if len(tc.args) > 0 { + args = append(args, tc.args...) + } + if tc.output != "" { + args = append(args, "--output", tc.output) + } + + builder := testutils.NewTestCommandBuilder(). + WithCommand(k8sCmd). + WithArgs(args) + + cmd := builder.Build() + + err := cmd.Execute() + + if tc.expectError { + g.Expect(err).To(HaveOccurred()) + } else { + g.Expect(err).To(BeNil()) + g.Expect(builder.GetOutput()).To(BeEquivalentTo(string(tc.expectedOutput))) + } + }) + } +} + +func TestGetKubernetesClusterNodeCmd(t *testing.T) { + testCases := []struct { + name string + id string + output string + expectedOutput []byte + expectError bool + }{ + { + name: "get k8s cluster node in default format", + id: testNodeId, + output: "", + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "get_node.txt")), + }, + { + name: "get k8s cluster node in JSON format", + id: testNodeId, + output: "json", + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "get_node.json")), + }, + { + name: "get k8s cluster node in YAML format", + id: testNodeId, + output: "yaml", + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "get_node.yaml")), + }, + { + name: "get k8s cluster node with error", + id: testNodeId, + expectError: true, + }, + } + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + k8sServiceHandler := mocks.NewMockKubernetesClustersService(mockCtrl) + + scClient := serverscom.NewClientWithEndpoint("", "") + scClient.KubernetesClusters = k8sServiceHandler + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + g := NewWithT(t) + + var err error + if tc.expectError { + err = errors.New("some error") + } + k8sServiceHandler.EXPECT(). + GetNode(gomock.Any(), testId, testNodeId). + Return(&testKubernetesClusterNode, err) + + testCmdContext := testutils.NewTestCmdContext(scClient) + k8sCmd := NewCmd(testCmdContext) + + args := []string{"k8s", "get-node", fmt.Sprint(tc.id), "--cluster-id", testId} + if tc.output != "" { + args = append(args, "--output", tc.output) + } + + builder := testutils.NewTestCommandBuilder(). + WithCommand(k8sCmd). + WithArgs(args) + + cmd := builder.Build() + + err = cmd.Execute() + + if tc.expectError { + g.Expect(err).To(HaveOccurred()) + } else { + g.Expect(err).To(BeNil()) + g.Expect(builder.GetOutput()).To(BeEquivalentTo(string(tc.expectedOutput))) + } + }) + } +} + +func TestListKubernetesClusterNodesCmd(t *testing.T) { + testK8sClusterNode1 := testKubernetesClusterNode + testK8sClusterNode2 := testKubernetesClusterNode + testK8sClusterNode1.ID += "1" + testK8sClusterNode2.Hostname = "test-node-2" + testK8sClusterNode2.ID += "2" + + testCases := []struct { + name string + output string + args []string + expectedOutput []byte + expectError bool + configureMock func(*mocks.MockCollection[serverscom.KubernetesClusterNode]) + }{ + { + name: "list all k8s clusters", + output: "json", + args: []string{"-A"}, + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "list_all_nodes.json")), + configureMock: func(mock *mocks.MockCollection[serverscom.KubernetesClusterNode]) { + mock.EXPECT(). + Collect(gomock.Any()). + Return([]serverscom.KubernetesClusterNode{ + testK8sClusterNode1, + testK8sClusterNode2, + }, nil) + }, + }, + { + name: "list k8s clusters", + output: "json", + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "list_nodes.json")), + configureMock: func(mock *mocks.MockCollection[serverscom.KubernetesClusterNode]) { + mock.EXPECT(). + List(gomock.Any()). + Return([]serverscom.KubernetesClusterNode{ + testK8sClusterNode1, + }, nil) + }, + }, + { + name: "list k8s clusters with template", + args: []string{"--template", "{{range .}}Hostname: {{.Hostname}}\n{{end}}"}, + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "list_template_nodes.txt")), + configureMock: func(mock *mocks.MockCollection[serverscom.KubernetesClusterNode]) { + mock.EXPECT(). + List(gomock.Any()). + Return([]serverscom.KubernetesClusterNode{ + testK8sClusterNode1, + testK8sClusterNode2, + }, nil) + }, + }, + { + name: "list k8s clusters with pageView", + args: []string{"--page-view"}, + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "list_pageview_nodes.txt")), + configureMock: func(mock *mocks.MockCollection[serverscom.KubernetesClusterNode]) { + mock.EXPECT(). + List(gomock.Any()). + Return([]serverscom.KubernetesClusterNode{ + testK8sClusterNode1, + testK8sClusterNode2, + }, nil) + }, + }, + { + name: "list k8s clusters with error", + expectError: true, + configureMock: func(mock *mocks.MockCollection[serverscom.KubernetesClusterNode]) { + mock.EXPECT(). + List(gomock.Any()). + Return(nil, errors.New("some error")) + }, + }, + } + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + k8sServiceHandler := mocks.NewMockKubernetesClustersService(mockCtrl) + collectionHandler := mocks.NewMockCollection[serverscom.KubernetesClusterNode](mockCtrl) + + k8sServiceHandler.EXPECT(). + Nodes(testId). + Return(collectionHandler). + AnyTimes() + + collectionHandler.EXPECT(). + SetParam(gomock.Any(), gomock.Any()). + Return(collectionHandler). + AnyTimes() + + scClient := serverscom.NewClientWithEndpoint("", "") + scClient.KubernetesClusters = k8sServiceHandler + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + g := NewWithT(t) + + if tc.configureMock != nil { + tc.configureMock(collectionHandler) + } + + testCmdContext := testutils.NewTestCmdContext(scClient) + k8sCmd := NewCmd(testCmdContext) + + args := []string{"k8s", "list-nodes", testId} + if len(tc.args) > 0 { + args = append(args, tc.args...) + } + if tc.output != "" { + args = append(args, "--output", tc.output) + } + + builder := testutils.NewTestCommandBuilder(). + WithCommand(k8sCmd). + WithArgs(args) + + cmd := builder.Build() + + err := cmd.Execute() + + if tc.expectError { + g.Expect(err).To(HaveOccurred()) + } else { + g.Expect(err).To(BeNil()) + g.Expect(builder.GetOutput()).To(BeEquivalentTo(string(tc.expectedOutput))) + } + }) + } +} diff --git a/cmd/entities/k8s/list.go b/cmd/entities/k8s/list.go new file mode 100644 index 0000000..a79f7fb --- /dev/null +++ b/cmd/entities/k8s/list.go @@ -0,0 +1,38 @@ +package k8s + +import ( + serverscom "github.com/serverscom/serverscom-go-client/pkg" + "github.com/serverscom/srvctl/cmd/base" + "github.com/spf13/cobra" +) + +func newListCmd(cmdContext *base.CmdContext) *cobra.Command { + factory := func(verbose bool, args ...string) serverscom.Collection[serverscom.KubernetesCluster] { + scClient := cmdContext.GetClient().SetVerbose(verbose).GetScClient() + return scClient.KubernetesClusters.Collection() + } + + opts := base.NewListOptions( + &base.BaseListOptions[serverscom.KubernetesCluster]{}, + &base.SearchPatternOption[serverscom.KubernetesCluster]{}, + &base.LabelSelectorOption[serverscom.KubernetesCluster]{}, + &base.LocationIDOption[serverscom.KubernetesCluster]{}, + ) + + return base.NewListCmd("list", "k8s", factory, cmdContext, opts...) +} + +func newListNodesCmd(cmdContext *base.CmdContext) *cobra.Command { + factory := func(verbose bool, args ...string) serverscom.Collection[serverscom.KubernetesClusterNode] { + scClient := cmdContext.GetClient().SetVerbose(verbose).GetScClient() + return scClient.KubernetesClusters.Nodes(args[0]) + } + + opts := base.NewListOptions( + &base.BaseListOptions[serverscom.KubernetesClusterNode]{}, + &base.SearchPatternOption[serverscom.KubernetesClusterNode]{}, + &base.LabelSelectorOption[serverscom.KubernetesClusterNode]{}, + ) + + return base.NewListCmd("list-nodes ", "kubernetes cluster nodes by ID", factory, cmdContext, opts...) +} diff --git a/cmd/entities/k8s/update.go b/cmd/entities/k8s/update.go new file mode 100644 index 0000000..3ba1bbf --- /dev/null +++ b/cmd/entities/k8s/update.go @@ -0,0 +1,54 @@ +package k8s + +import ( + "log" + + serverscom "github.com/serverscom/serverscom-go-client/pkg" + "github.com/serverscom/srvctl/cmd/base" + "github.com/spf13/cobra" +) + +func newUpdateCmd(cmdContext *base.CmdContext) *cobra.Command { + var labels []string + + cmd := &cobra.Command{ + Use: "update ", + Short: "Update a kubernetes cluster", + Long: "Update a kubernetes cluster by ID", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + manager := cmdContext.GetManager() + + ctx, cancel := base.SetupContext(cmd, manager) + defer cancel() + + base.SetupProxy(cmd, manager) + + labelsMap, err := base.ParseLabels(labels) + if err != nil { + log.Fatal(err) + } + input := serverscom.KubernetesClusterUpdateInput{ + Labels: labelsMap, + } + + scClient := cmdContext.GetClient().SetVerbose(manager.GetVerbose(cmd)).GetScClient() + + id := args[0] + k8sCluster, err := scClient.KubernetesClusters.Update(ctx, id, input) + if err != nil { + return err + } + + if k8sCluster != nil { + formatter := cmdContext.GetOrCreateFormatter(cmd) + return formatter.Format(k8sCluster) + } + return nil + }, + } + + cmd.Flags().StringArrayVarP(&labels, "label", "l", []string{}, "string in key=value format") + + return cmd +} diff --git a/cmd/entities/locations/get.go b/cmd/entities/locations/get.go new file mode 100644 index 0000000..10b7be6 --- /dev/null +++ b/cmd/entities/locations/get.go @@ -0,0 +1,45 @@ +package locations + +import ( + "fmt" + "strconv" + + "github.com/serverscom/srvctl/cmd/base" + "github.com/spf13/cobra" +) + +func newGetCmd(cmdContext *base.CmdContext) *cobra.Command { + cmd := &cobra.Command{ + Use: "get ", + Short: "Get a location", + Long: "Get a location by id", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + manager := cmdContext.GetManager() + + ctx, cancel := base.SetupContext(cmd, manager) + defer cancel() + + base.SetupProxy(cmd, manager) + + scClient := cmdContext.GetClient().SetVerbose(manager.GetVerbose(cmd)).GetScClient() + + id, err := strconv.Atoi(args[0]) + if err != nil { + return fmt.Errorf("location id should be integer") + } + location, err := scClient.Locations.GetLocation(ctx, int64(id)) + if err != nil { + return err + } + + if location != nil { + formatter := cmdContext.GetOrCreateFormatter(cmd) + return formatter.Format(location) + } + return nil + }, + } + + return cmd +} diff --git a/cmd/entities/locations/list.go b/cmd/entities/locations/list.go new file mode 100644 index 0000000..f23ed49 --- /dev/null +++ b/cmd/entities/locations/list.go @@ -0,0 +1,21 @@ +package locations + +import ( + serverscom "github.com/serverscom/serverscom-go-client/pkg" + "github.com/serverscom/srvctl/cmd/base" + "github.com/spf13/cobra" +) + +func newListCmd(cmdContext *base.CmdContext) *cobra.Command { + factory := func(verbose bool, args ...string) serverscom.Collection[serverscom.Location] { + scClient := cmdContext.GetClient().SetVerbose(verbose).GetScClient() + return scClient.Locations.Collection() + } + + opts := base.NewListOptions( + &base.BaseListOptions[serverscom.Location]{}, + &base.SearchPatternOption[serverscom.Location]{}, + ) + + return base.NewListCmd("list", "locations", factory, cmdContext, opts...) +} diff --git a/cmd/entities/locations/locations.go b/cmd/entities/locations/locations.go new file mode 100644 index 0000000..97b51f5 --- /dev/null +++ b/cmd/entities/locations/locations.go @@ -0,0 +1,38 @@ +package locations + +import ( + "log" + + serverscom "github.com/serverscom/serverscom-go-client/pkg" + "github.com/serverscom/srvctl/cmd/base" + "github.com/serverscom/srvctl/internal/output/entities" + "github.com/spf13/cobra" +) + +func NewCmd(cmdContext *base.CmdContext) *cobra.Command { + locationEntity, err := entities.Registry.GetEntityFromValue(serverscom.Location{}) + if err != nil { + log.Fatal(err) + } + entitiesMap := make(map[string]entities.EntityInterface) + entitiesMap["locations"] = locationEntity + cmd := &cobra.Command{ + Use: "locations", + Short: "Manage locations", + PersistentPreRunE: base.CombinePreRunE( + base.CheckFormatterFlags(cmdContext, entitiesMap), + base.CheckEmptyContexts(cmdContext), + ), + Args: base.NoArgs, + Run: base.UsageRun, + } + + cmd.AddCommand( + newListCmd(cmdContext), + newGetCmd(cmdContext), + ) + + base.AddFormatFlags(cmd) + + return cmd +} diff --git a/cmd/entities/locations/locations_test.go b/cmd/entities/locations/locations_test.go new file mode 100644 index 0000000..6e7b51e --- /dev/null +++ b/cmd/entities/locations/locations_test.go @@ -0,0 +1,240 @@ +package locations + +import ( + "errors" + "path/filepath" + "testing" + + "fmt" + + . "github.com/onsi/gomega" + serverscom "github.com/serverscom/serverscom-go-client/pkg" + "github.com/serverscom/srvctl/cmd/testutils" + "github.com/serverscom/srvctl/internal/mocks" + "go.uber.org/mock/gomock" +) + +var ( + testId = int64(1) + fixtureBasePath = filepath.Join("..", "..", "..", "testdata", "entities", "locations") + testLocation = serverscom.Location{ + ID: testId, + Name: "test-location", + Status: "active", + Code: "test", + SupportedFeatures: []string{"feature1", "feature2"}, + } +) + +func TestGetLocationCmd(t *testing.T) { + testCases := []struct { + name string + id int64 + output string + expectedOutput []byte + expectError bool + }{ + { + name: "get location in default format", + id: testId, + output: "", + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "get.txt")), + }, + { + name: "get location in JSON format", + id: testId, + output: "json", + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "get.json")), + }, + { + name: "get location in YAML format", + id: testId, + output: "yaml", + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "get.yaml")), + }, + { + name: "get location with error", + id: testId, + expectError: true, + }, + } + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + locationsServiceHandler := mocks.NewMockLocationsService(mockCtrl) + + scClient := serverscom.NewClientWithEndpoint("", "") + scClient.Locations = locationsServiceHandler + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + g := NewWithT(t) + + var err error + if tc.expectError { + err = errors.New("some error") + } + locationsServiceHandler.EXPECT(). + GetLocation(gomock.Any(), testId). + Return(&testLocation, err) + + testCmdContext := testutils.NewTestCmdContext(scClient) + locationCmd := NewCmd(testCmdContext) + + args := []string{"locations", "get", fmt.Sprint(tc.id)} + if tc.output != "" { + args = append(args, "--output", tc.output) + } + + builder := testutils.NewTestCommandBuilder(). + WithCommand(locationCmd). + WithArgs(args) + + cmd := builder.Build() + + err = cmd.Execute() + + if tc.expectError { + g.Expect(err).To(HaveOccurred()) + } else { + g.Expect(err).To(BeNil()) + g.Expect(builder.GetOutput()).To(BeEquivalentTo(string(tc.expectedOutput))) + } + }) + } +} + +func TestListLocationsCmd(t *testing.T) { + testLocation1 := testLocation + testLocation2 := testLocation + testLocation1.ID = 1 + testLocation2.Name = "test-location 2" + testLocation2.ID = 2 + + testCases := []struct { + name string + output string + args []string + expectedOutput []byte + expectError bool + configureMock func(*mocks.MockCollection[serverscom.Location]) + }{ + { + name: "list all locations", + output: "json", + args: []string{"-A"}, + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "list_all.json")), + configureMock: func(mock *mocks.MockCollection[serverscom.Location]) { + mock.EXPECT(). + Collect(gomock.Any()). + Return([]serverscom.Location{ + testLocation1, + testLocation2, + }, nil) + }, + }, + { + name: "list locations", + output: "json", + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "list.json")), + configureMock: func(mock *mocks.MockCollection[serverscom.Location]) { + mock.EXPECT(). + List(gomock.Any()). + Return([]serverscom.Location{ + testLocation1, + }, nil) + }, + }, + { + name: "list locations with template", + args: []string{"--template", "{{range .}}Name: {{.Name}}\n{{end}}"}, + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "list_template.txt")), + configureMock: func(mock *mocks.MockCollection[serverscom.Location]) { + mock.EXPECT(). + List(gomock.Any()). + Return([]serverscom.Location{ + testLocation1, + testLocation2, + }, nil) + }, + }, + { + name: "list locations with pageView", + args: []string{"--page-view"}, + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "list_pageview.txt")), + configureMock: func(mock *mocks.MockCollection[serverscom.Location]) { + mock.EXPECT(). + List(gomock.Any()). + Return([]serverscom.Location{ + testLocation1, + testLocation2, + }, nil) + }, + }, + { + name: "list locations with error", + expectError: true, + configureMock: func(mock *mocks.MockCollection[serverscom.Location]) { + mock.EXPECT(). + List(gomock.Any()). + Return(nil, errors.New("some error")) + }, + }, + } + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + locationsServiceHandler := mocks.NewMockLocationsService(mockCtrl) + collectionHandler := mocks.NewMockCollection[serverscom.Location](mockCtrl) + + locationsServiceHandler.EXPECT(). + Collection(). + Return(collectionHandler). + AnyTimes() + + collectionHandler.EXPECT(). + SetParam(gomock.Any(), gomock.Any()). + Return(collectionHandler). + AnyTimes() + + scClient := serverscom.NewClientWithEndpoint("", "") + scClient.Locations = locationsServiceHandler + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + g := NewWithT(t) + + if tc.configureMock != nil { + tc.configureMock(collectionHandler) + } + + testCmdContext := testutils.NewTestCmdContext(scClient) + locationCmd := NewCmd(testCmdContext) + + args := []string{"locations", "list"} + if len(tc.args) > 0 { + args = append(args, tc.args...) + } + if tc.output != "" { + args = append(args, "--output", tc.output) + } + + builder := testutils.NewTestCommandBuilder(). + WithCommand(locationCmd). + WithArgs(args) + + cmd := builder.Build() + + err := cmd.Execute() + + if tc.expectError { + g.Expect(err).To(HaveOccurred()) + } else { + g.Expect(err).To(BeNil()) + g.Expect(builder.GetOutput()).To(BeEquivalentTo(string(tc.expectedOutput))) + } + }) + } +} diff --git a/cmd/root.go b/cmd/root.go index 6b7b6a4..e36aa6c 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -7,7 +7,9 @@ import ( "github.com/serverscom/srvctl/cmd/entities/account" "github.com/serverscom/srvctl/cmd/entities/hosts" "github.com/serverscom/srvctl/cmd/entities/invoices" + "github.com/serverscom/srvctl/cmd/entities/k8s" loadbalancers "github.com/serverscom/srvctl/cmd/entities/load_balancers" + "github.com/serverscom/srvctl/cmd/entities/locations" "github.com/serverscom/srvctl/cmd/entities/racks" sshkeys "github.com/serverscom/srvctl/cmd/entities/ssh-keys" "github.com/serverscom/srvctl/cmd/entities/ssl" @@ -48,6 +50,8 @@ func NewRootCmd(version string) *cobra.Command { cmd.AddCommand(racks.NewCmd(cmdContext)) cmd.AddCommand(invoices.NewCmd(cmdContext)) cmd.AddCommand(account.NewCmd(cmdContext)) + cmd.AddCommand(locations.NewCmd(cmdContext)) + cmd.AddCommand(k8s.NewCmd(cmdContext)) return cmd } diff --git a/go.mod b/go.mod index 6b8203d..2186b3c 100644 --- a/go.mod +++ b/go.mod @@ -6,14 +6,14 @@ require ( github.com/creack/pty v1.1.24 github.com/jmespath/go-jmespath v0.4.0 github.com/onsi/gomega v1.36.2 - github.com/serverscom/serverscom-go-client v1.0.14 + github.com/serverscom/serverscom-go-client v1.0.17 github.com/spf13/cobra v1.8.1 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.19.0 github.com/stoewer/go-strcase v1.3.0 go.uber.org/mock v0.5.0 - golang.org/x/term v0.30.0 - golang.org/x/text v0.23.0 + golang.org/x/term v0.32.0 + golang.org/x/text v0.25.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -35,7 +35,7 @@ require ( go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect - golang.org/x/net v0.37.0 // indirect - golang.org/x/sys v0.31.0 // indirect + golang.org/x/net v0.40.0 // indirect + golang.org/x/sys v0.33.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect ) diff --git a/go.sum b/go.sum index cc06d94..7a2fca1 100644 --- a/go.sum +++ b/go.sum @@ -51,8 +51,8 @@ github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6ke github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= -github.com/serverscom/serverscom-go-client v1.0.14 h1:/SR4moqSL6MqW+gt6wtF9Wl5KfckP4RcqeS0AECwwAs= -github.com/serverscom/serverscom-go-client v1.0.14/go.mod h1:o4lNYX+shv5TZ6miuGAaMDJP8y7Z7TdPEhMsCcL9PrU= +github.com/serverscom/serverscom-go-client v1.0.17 h1:noPj7s8G3CdnhfwHadMu7f0cqt+H9rpv01wjHHaoafs= +github.com/serverscom/serverscom-go-client v1.0.17/go.mod h1:/Nf+XygKOxm19Sl2gvMzT55O4X+tWDkj/UM4mjzfKgM= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= @@ -88,14 +88,14 @@ go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= -golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= -golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= -golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= -golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= +golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= +golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= +golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= +golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8= diff --git a/internal/mocks/kubernetes_clusters_service.go b/internal/mocks/kubernetes_clusters_service.go new file mode 100644 index 0000000..cb559c1 --- /dev/null +++ b/internal/mocks/kubernetes_clusters_service.go @@ -0,0 +1,115 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: ./vendor/github.com/serverscom/serverscom-go-client/pkg/kubernetes_clusters.go +// +// Generated by this command: +// +// mockgen --destination ./internal/mocks/kubernetes_clusters_service.go --package=mocks --source ./vendor/github.com/serverscom/serverscom-go-client/pkg/kubernetes_clusters.go +// + +// Package mocks is a generated GoMock package. +package mocks + +import ( + context "context" + reflect "reflect" + + serverscom "github.com/serverscom/serverscom-go-client/pkg" + gomock "go.uber.org/mock/gomock" +) + +// MockKubernetesClustersService is a mock of KubernetesClustersService interface. +type MockKubernetesClustersService struct { + ctrl *gomock.Controller + recorder *MockKubernetesClustersServiceMockRecorder + isgomock struct{} +} + +// MockKubernetesClustersServiceMockRecorder is the mock recorder for MockKubernetesClustersService. +type MockKubernetesClustersServiceMockRecorder struct { + mock *MockKubernetesClustersService +} + +// NewMockKubernetesClustersService creates a new mock instance. +func NewMockKubernetesClustersService(ctrl *gomock.Controller) *MockKubernetesClustersService { + mock := &MockKubernetesClustersService{ctrl: ctrl} + mock.recorder = &MockKubernetesClustersServiceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockKubernetesClustersService) EXPECT() *MockKubernetesClustersServiceMockRecorder { + return m.recorder +} + +// Collection mocks base method. +func (m *MockKubernetesClustersService) Collection() serverscom.Collection[serverscom.KubernetesCluster] { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Collection") + ret0, _ := ret[0].(serverscom.Collection[serverscom.KubernetesCluster]) + return ret0 +} + +// Collection indicates an expected call of Collection. +func (mr *MockKubernetesClustersServiceMockRecorder) Collection() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Collection", reflect.TypeOf((*MockKubernetesClustersService)(nil).Collection)) +} + +// Get mocks base method. +func (m *MockKubernetesClustersService) Get(ctx context.Context, id string) (*serverscom.KubernetesCluster, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Get", ctx, id) + ret0, _ := ret[0].(*serverscom.KubernetesCluster) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Get indicates an expected call of Get. +func (mr *MockKubernetesClustersServiceMockRecorder) Get(ctx, id any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockKubernetesClustersService)(nil).Get), ctx, id) +} + +// GetNode mocks base method. +func (m *MockKubernetesClustersService) GetNode(ctx context.Context, clusterID, nodeID string) (*serverscom.KubernetesClusterNode, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetNode", ctx, clusterID, nodeID) + ret0, _ := ret[0].(*serverscom.KubernetesClusterNode) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetNode indicates an expected call of GetNode. +func (mr *MockKubernetesClustersServiceMockRecorder) GetNode(ctx, clusterID, nodeID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNode", reflect.TypeOf((*MockKubernetesClustersService)(nil).GetNode), ctx, clusterID, nodeID) +} + +// Nodes mocks base method. +func (m *MockKubernetesClustersService) Nodes(id string) serverscom.Collection[serverscom.KubernetesClusterNode] { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Nodes", id) + ret0, _ := ret[0].(serverscom.Collection[serverscom.KubernetesClusterNode]) + return ret0 +} + +// Nodes indicates an expected call of Nodes. +func (mr *MockKubernetesClustersServiceMockRecorder) Nodes(id any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Nodes", reflect.TypeOf((*MockKubernetesClustersService)(nil).Nodes), id) +} + +// Update mocks base method. +func (m *MockKubernetesClustersService) Update(ctx context.Context, id string, input serverscom.KubernetesClusterUpdateInput) (*serverscom.KubernetesCluster, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Update", ctx, id, input) + ret0, _ := ret[0].(*serverscom.KubernetesCluster) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Update indicates an expected call of Update. +func (mr *MockKubernetesClustersServiceMockRecorder) Update(ctx, id, input any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockKubernetesClustersService)(nil).Update), ctx, id, input) +} diff --git a/internal/mocks/locations_service.go b/internal/mocks/locations_service.go new file mode 100644 index 0000000..e504469 --- /dev/null +++ b/internal/mocks/locations_service.go @@ -0,0 +1,288 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: ./vendor/github.com/serverscom/serverscom-go-client/pkg/locations.go +// +// Generated by this command: +// +// mockgen --destination ./internal/mocks/locations_service.go --package=mocks --source ./vendor/github.com/serverscom/serverscom-go-client/pkg/locations.go +// + +// Package mocks is a generated GoMock package. +package mocks + +import ( + context "context" + reflect "reflect" + + serverscom "github.com/serverscom/serverscom-go-client/pkg" + gomock "go.uber.org/mock/gomock" +) + +// MockLocationsService is a mock of LocationsService interface. +type MockLocationsService struct { + ctrl *gomock.Controller + recorder *MockLocationsServiceMockRecorder + isgomock struct{} +} + +// MockLocationsServiceMockRecorder is the mock recorder for MockLocationsService. +type MockLocationsServiceMockRecorder struct { + mock *MockLocationsService +} + +// NewMockLocationsService creates a new mock instance. +func NewMockLocationsService(ctrl *gomock.Controller) *MockLocationsService { + mock := &MockLocationsService{ctrl: ctrl} + mock.recorder = &MockLocationsServiceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockLocationsService) EXPECT() *MockLocationsServiceMockRecorder { + return m.recorder +} + +// BandwidthOptions mocks base method. +func (m *MockLocationsService) BandwidthOptions(locationID, serverModelID, uplinkID int64) serverscom.Collection[serverscom.BandwidthOption] { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "BandwidthOptions", locationID, serverModelID, uplinkID) + ret0, _ := ret[0].(serverscom.Collection[serverscom.BandwidthOption]) + return ret0 +} + +// BandwidthOptions indicates an expected call of BandwidthOptions. +func (mr *MockLocationsServiceMockRecorder) BandwidthOptions(locationID, serverModelID, uplinkID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BandwidthOptions", reflect.TypeOf((*MockLocationsService)(nil).BandwidthOptions), locationID, serverModelID, uplinkID) +} + +// Collection mocks base method. +func (m *MockLocationsService) Collection() serverscom.Collection[serverscom.Location] { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Collection") + ret0, _ := ret[0].(serverscom.Collection[serverscom.Location]) + return ret0 +} + +// Collection indicates an expected call of Collection. +func (mr *MockLocationsServiceMockRecorder) Collection() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Collection", reflect.TypeOf((*MockLocationsService)(nil).Collection)) +} + +// DriveModelOptions mocks base method. +func (m *MockLocationsService) DriveModelOptions(locationID, serverModelID int64) serverscom.Collection[serverscom.DriveModel] { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DriveModelOptions", locationID, serverModelID) + ret0, _ := ret[0].(serverscom.Collection[serverscom.DriveModel]) + return ret0 +} + +// DriveModelOptions indicates an expected call of DriveModelOptions. +func (mr *MockLocationsServiceMockRecorder) DriveModelOptions(locationID, serverModelID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DriveModelOptions", reflect.TypeOf((*MockLocationsService)(nil).DriveModelOptions), locationID, serverModelID) +} + +// GetBandwidthOption mocks base method. +func (m *MockLocationsService) GetBandwidthOption(ctx context.Context, locationID, serverModelID, uplinkModelID, bandwidthID int64) (*serverscom.BandwidthOption, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetBandwidthOption", ctx, locationID, serverModelID, uplinkModelID, bandwidthID) + ret0, _ := ret[0].(*serverscom.BandwidthOption) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetBandwidthOption indicates an expected call of GetBandwidthOption. +func (mr *MockLocationsServiceMockRecorder) GetBandwidthOption(ctx, locationID, serverModelID, uplinkModelID, bandwidthID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBandwidthOption", reflect.TypeOf((*MockLocationsService)(nil).GetBandwidthOption), ctx, locationID, serverModelID, uplinkModelID, bandwidthID) +} + +// GetDriveModelOption mocks base method. +func (m *MockLocationsService) GetDriveModelOption(ctx context.Context, locationID, serverModelID, driveModelID int64) (*serverscom.DriveModel, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetDriveModelOption", ctx, locationID, serverModelID, driveModelID) + ret0, _ := ret[0].(*serverscom.DriveModel) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetDriveModelOption indicates an expected call of GetDriveModelOption. +func (mr *MockLocationsServiceMockRecorder) GetDriveModelOption(ctx, locationID, serverModelID, driveModelID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDriveModelOption", reflect.TypeOf((*MockLocationsService)(nil).GetDriveModelOption), ctx, locationID, serverModelID, driveModelID) +} + +// GetLocation mocks base method. +func (m *MockLocationsService) GetLocation(ctx context.Context, locationID int64) (*serverscom.Location, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetLocation", ctx, locationID) + ret0, _ := ret[0].(*serverscom.Location) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetLocation indicates an expected call of GetLocation. +func (mr *MockLocationsServiceMockRecorder) GetLocation(ctx, locationID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLocation", reflect.TypeOf((*MockLocationsService)(nil).GetLocation), ctx, locationID) +} + +// GetOperatingSystemOption mocks base method. +func (m *MockLocationsService) GetOperatingSystemOption(ctx context.Context, locationID, serverModelID, operatingSystemID int64) (*serverscom.OperatingSystemOption, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetOperatingSystemOption", ctx, locationID, serverModelID, operatingSystemID) + ret0, _ := ret[0].(*serverscom.OperatingSystemOption) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetOperatingSystemOption indicates an expected call of GetOperatingSystemOption. +func (mr *MockLocationsServiceMockRecorder) GetOperatingSystemOption(ctx, locationID, serverModelID, operatingSystemID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetOperatingSystemOption", reflect.TypeOf((*MockLocationsService)(nil).GetOperatingSystemOption), ctx, locationID, serverModelID, operatingSystemID) +} + +// GetSBMFlavorOption mocks base method. +func (m *MockLocationsService) GetSBMFlavorOption(ctx context.Context, locationID, sbmFlavorModelID int64) (*serverscom.SBMFlavor, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSBMFlavorOption", ctx, locationID, sbmFlavorModelID) + ret0, _ := ret[0].(*serverscom.SBMFlavor) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetSBMFlavorOption indicates an expected call of GetSBMFlavorOption. +func (mr *MockLocationsServiceMockRecorder) GetSBMFlavorOption(ctx, locationID, sbmFlavorModelID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSBMFlavorOption", reflect.TypeOf((*MockLocationsService)(nil).GetSBMFlavorOption), ctx, locationID, sbmFlavorModelID) +} + +// GetSBMOperatingSystemOption mocks base method. +func (m *MockLocationsService) GetSBMOperatingSystemOption(ctx context.Context, locationID, sbmFlavorModelID, operatingSystemID int64) (*serverscom.OperatingSystemOption, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSBMOperatingSystemOption", ctx, locationID, sbmFlavorModelID, operatingSystemID) + ret0, _ := ret[0].(*serverscom.OperatingSystemOption) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetSBMOperatingSystemOption indicates an expected call of GetSBMOperatingSystemOption. +func (mr *MockLocationsServiceMockRecorder) GetSBMOperatingSystemOption(ctx, locationID, sbmFlavorModelID, operatingSystemID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSBMOperatingSystemOption", reflect.TypeOf((*MockLocationsService)(nil).GetSBMOperatingSystemOption), ctx, locationID, sbmFlavorModelID, operatingSystemID) +} + +// GetServerModelOption mocks base method. +func (m *MockLocationsService) GetServerModelOption(ctx context.Context, locationID, serverModelID int64) (*serverscom.ServerModelOptionDetail, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetServerModelOption", ctx, locationID, serverModelID) + ret0, _ := ret[0].(*serverscom.ServerModelOptionDetail) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetServerModelOption indicates an expected call of GetServerModelOption. +func (mr *MockLocationsServiceMockRecorder) GetServerModelOption(ctx, locationID, serverModelID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetServerModelOption", reflect.TypeOf((*MockLocationsService)(nil).GetServerModelOption), ctx, locationID, serverModelID) +} + +// GetUplinkOption mocks base method. +func (m *MockLocationsService) GetUplinkOption(ctx context.Context, locationID, serverModelID, uplinkModelID int64) (*serverscom.UplinkOption, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetUplinkOption", ctx, locationID, serverModelID, uplinkModelID) + ret0, _ := ret[0].(*serverscom.UplinkOption) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetUplinkOption indicates an expected call of GetUplinkOption. +func (mr *MockLocationsServiceMockRecorder) GetUplinkOption(ctx, locationID, serverModelID, uplinkModelID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUplinkOption", reflect.TypeOf((*MockLocationsService)(nil).GetUplinkOption), ctx, locationID, serverModelID, uplinkModelID) +} + +// OperatingSystemOptions mocks base method. +func (m *MockLocationsService) OperatingSystemOptions(locationID, serverModelID int64) serverscom.Collection[serverscom.OperatingSystemOption] { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "OperatingSystemOptions", locationID, serverModelID) + ret0, _ := ret[0].(serverscom.Collection[serverscom.OperatingSystemOption]) + return ret0 +} + +// OperatingSystemOptions indicates an expected call of OperatingSystemOptions. +func (mr *MockLocationsServiceMockRecorder) OperatingSystemOptions(locationID, serverModelID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OperatingSystemOptions", reflect.TypeOf((*MockLocationsService)(nil).OperatingSystemOptions), locationID, serverModelID) +} + +// RAMOptions mocks base method. +func (m *MockLocationsService) RAMOptions(locationID, serverModelID int64) serverscom.Collection[serverscom.RAMOption] { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RAMOptions", locationID, serverModelID) + ret0, _ := ret[0].(serverscom.Collection[serverscom.RAMOption]) + return ret0 +} + +// RAMOptions indicates an expected call of RAMOptions. +func (mr *MockLocationsServiceMockRecorder) RAMOptions(locationID, serverModelID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RAMOptions", reflect.TypeOf((*MockLocationsService)(nil).RAMOptions), locationID, serverModelID) +} + +// SBMFlavorOptions mocks base method. +func (m *MockLocationsService) SBMFlavorOptions(locationID int64) serverscom.Collection[serverscom.SBMFlavor] { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SBMFlavorOptions", locationID) + ret0, _ := ret[0].(serverscom.Collection[serverscom.SBMFlavor]) + return ret0 +} + +// SBMFlavorOptions indicates an expected call of SBMFlavorOptions. +func (mr *MockLocationsServiceMockRecorder) SBMFlavorOptions(locationID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SBMFlavorOptions", reflect.TypeOf((*MockLocationsService)(nil).SBMFlavorOptions), locationID) +} + +// SBMOperatingSystemOptions mocks base method. +func (m *MockLocationsService) SBMOperatingSystemOptions(locationID, sbmFlavorModelID int64) serverscom.Collection[serverscom.OperatingSystemOption] { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SBMOperatingSystemOptions", locationID, sbmFlavorModelID) + ret0, _ := ret[0].(serverscom.Collection[serverscom.OperatingSystemOption]) + return ret0 +} + +// SBMOperatingSystemOptions indicates an expected call of SBMOperatingSystemOptions. +func (mr *MockLocationsServiceMockRecorder) SBMOperatingSystemOptions(locationID, sbmFlavorModelID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SBMOperatingSystemOptions", reflect.TypeOf((*MockLocationsService)(nil).SBMOperatingSystemOptions), locationID, sbmFlavorModelID) +} + +// ServerModelOptions mocks base method. +func (m *MockLocationsService) ServerModelOptions(locationID int64) serverscom.Collection[serverscom.ServerModelOption] { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ServerModelOptions", locationID) + ret0, _ := ret[0].(serverscom.Collection[serverscom.ServerModelOption]) + return ret0 +} + +// ServerModelOptions indicates an expected call of ServerModelOptions. +func (mr *MockLocationsServiceMockRecorder) ServerModelOptions(locationID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ServerModelOptions", reflect.TypeOf((*MockLocationsService)(nil).ServerModelOptions), locationID) +} + +// UplinkOptions mocks base method. +func (m *MockLocationsService) UplinkOptions(locationID, serverModelID int64) serverscom.Collection[serverscom.UplinkOption] { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UplinkOptions", locationID, serverModelID) + ret0, _ := ret[0].(serverscom.Collection[serverscom.UplinkOption]) + return ret0 +} + +// UplinkOptions indicates an expected call of UplinkOptions. +func (mr *MockLocationsServiceMockRecorder) UplinkOptions(locationID, serverModelID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UplinkOptions", reflect.TypeOf((*MockLocationsService)(nil).UplinkOptions), locationID, serverModelID) +} diff --git a/internal/output/entities/hosts.go b/internal/output/entities/hosts.go index d79e7c9..4807221 100644 --- a/internal/output/entities/hosts.go +++ b/internal/output/entities/hosts.go @@ -12,6 +12,10 @@ var ( DedicatedServerType = reflect.TypeOf(serverscom.DedicatedServer{}) KubernetesBaremetalNodeType = reflect.TypeOf(serverscom.KubernetesBaremetalNode{}) SBMServerType = reflect.TypeOf(serverscom.SBMServer{}) + HostConnectionType = reflect.TypeOf(serverscom.HostConnection{}) + HostPowerFeedType = reflect.TypeOf(serverscom.HostPowerFeed{}) + HostDriveSlotType = reflect.TypeOf(serverscom.HostDriveSlot{}) + HostPTRRecordType = reflect.TypeOf(serverscom.PTRRecord{}) HostListDefaultFields = []string{"ID", "Type", "Title", "Status"} CmdDefaultFields = map[string][]string{ "list": HostListDefaultFields, @@ -43,10 +47,31 @@ func getConfigurationDetailsField() Field { return f } +func getDriveModel() Field { + f := Field{ + ID: "DriveModel", + Name: "DriveModel", + Path: "DriveModel", + PageViewHandlerFunc: structPVHandler, + } + childs := []Field{ + {ID: "ID", Name: "ID", Path: "ID", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Parent: &f}, + {ID: "Name", Name: "Name", Path: "Name", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Parent: &f}, + {ID: "Capacity", Name: "Capacity", Path: "Capacity", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Parent: &f}, + {ID: "Interface", Name: "Interface", Path: "Interface", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Parent: &f}, + {ID: "FormFactor", Name: "FormFactor", Path: "FormFactor", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Parent: &f}, + {ID: "MediaType", Name: "MediaType", Path: "MediaType", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Parent: &f}, + } + f.ChildFields = append(f.ChildFields, childs...) + + return f +} + func RegisterHostDefinition() { hostEntity := &Entity{ fields: []Field{ {ID: "ID", Name: "ID", Path: "ID", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + // {ID: "RackID", Name: "RackID", Path: "RackID", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, {ID: "Type", Name: "Type", Path: "Type", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, {ID: "Title", Name: "Title", Path: "Title", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, {ID: "LocationID", Name: "LocationID", Path: "LocationID", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler}, @@ -57,7 +82,9 @@ func RegisterHostDefinition() { {ID: "Configuration", Name: "Configuration", Path: "Configuration", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler}, {ID: "PrivateIPv4Address", Name: "PrivateIPv4Address", Path: "PrivateIPv4Address", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler}, {ID: "PublicIPv4Address", Name: "PublicIPv4Address", Path: "PublicIPv4Address", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler}, + // {ID: "LeaseStart", Name: "LeaseStart", Path: "LeaseStart", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler}, {ID: "ScheduledRelease", Name: "ScheduledRelease", Path: "ScheduledRelease", ListHandlerFunc: timeHandler, PageViewHandlerFunc: timeHandler}, + // {ID: "OobIPv4Address", Name: "OobIPv4Address", Path: "OobIPv4Address", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler}, {ID: "Created", Name: "Created", Path: "Created", ListHandlerFunc: timeHandler, PageViewHandlerFunc: timeHandler, Default: true}, {ID: "Updated", Name: "Updated", Path: "Updated", ListHandlerFunc: timeHandler, PageViewHandlerFunc: timeHandler, Default: true}, }, @@ -73,6 +100,7 @@ func RegisterDedicatedServerDefinition() { serverEntity := &Entity{ fields: []Field{ {ID: "ID", Name: "ID", Path: "ID", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + {ID: "RackID", Name: "RackID", Path: "RackID", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, {ID: "Type", Name: "Type", Path: "Type", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler}, {ID: "Title", Name: "Title", Path: "Title", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, {ID: "LocationID", Name: "LocationID", Path: "LocationID", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler}, @@ -83,7 +111,9 @@ func RegisterDedicatedServerDefinition() { {ID: "Configuration", Name: "Configuration", Path: "Configuration", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler}, {ID: "PrivateIPv4Address", Name: "PrivateIPv4Address", Path: "PrivateIPv4Address", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler}, {ID: "PublicIPv4Address", Name: "PublicIPv4Address", Path: "PublicIPv4Address", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler}, + {ID: "LeaseStart", Name: "LeaseStart", Path: "LeaseStart", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler}, {ID: "ScheduledRelease", Name: "ScheduledRelease", Path: "ScheduledRelease", ListHandlerFunc: timeHandler, PageViewHandlerFunc: timeHandler}, + {ID: "OobIPv4Address", Name: "OobIPv4Address", Path: "OobIPv4Address", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler}, {ID: "Labels", Name: "Labels", Path: "Labels", PageViewHandlerFunc: mapPvHandler}, {ID: "Created", Name: "Created", Path: "Created", ListHandlerFunc: timeHandler, PageViewHandlerFunc: timeHandler, Default: true}, {ID: "Updated", Name: "Updated", Path: "Updated", ListHandlerFunc: timeHandler, PageViewHandlerFunc: timeHandler, Default: true}, @@ -101,6 +131,10 @@ func RegisterKubernetesBaremetalNodeDefinition() { serverEntity := &Entity{ fields: []Field{ {ID: "ID", Name: "ID", Path: "ID", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + {ID: "KubernetesClusterId", Name: "KubernetesClusterId", Path: "KubernetesClusterId", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler}, + {ID: "KubernetesClusterNodeId", Name: "KubernetesClusterNodeId", Path: "KubernetesClusterNodeId", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler}, + {ID: "KubernetesClusterNodeNumber", Name: "KubernetesClusterNodeNumber", Path: "KubernetesClusterNodeNumber", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler}, + {ID: "RackID", Name: "RackID", Path: "RackID", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, {ID: "Type", Name: "Type", Path: "Type", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler}, {ID: "Title", Name: "Title", Path: "Title", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, {ID: "LocationID", Name: "LocationID", Path: "LocationID", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler}, @@ -111,7 +145,9 @@ func RegisterKubernetesBaremetalNodeDefinition() { {ID: "Configuration", Name: "Configuration", Path: "Configuration", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler}, {ID: "PrivateIPv4Address", Name: "PrivateIPv4Address", Path: "PrivateIPv4Address", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler}, {ID: "PublicIPv4Address", Name: "PublicIPv4Address", Path: "PublicIPv4Address", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler}, + {ID: "LeaseStart", Name: "LeaseStart", Path: "LeaseStart", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler}, {ID: "ScheduledRelease", Name: "ScheduledRelease", Path: "ScheduledRelease", ListHandlerFunc: timeHandler, PageViewHandlerFunc: timeHandler}, + {ID: "OobIPv4Address", Name: "OobIPv4Address", Path: "OobIPv4Address", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler}, {ID: "Labels", Name: "Labels", Path: "Labels", PageViewHandlerFunc: mapPvHandler}, {ID: "Created", Name: "Created", Path: "Created", ListHandlerFunc: timeHandler, PageViewHandlerFunc: timeHandler, Default: true}, {ID: "Updated", Name: "Updated", Path: "Updated", ListHandlerFunc: timeHandler, PageViewHandlerFunc: timeHandler, Default: true}, @@ -129,6 +165,7 @@ func RegisterSBMServerDefinition() { serverEntity := &Entity{ fields: []Field{ {ID: "ID", Name: "ID", Path: "ID", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + {ID: "RackID", Name: "RackID", Path: "RackID", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, {ID: "Type", Name: "Type", Path: "Type", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler}, {ID: "Title", Name: "Title", Path: "Title", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, {ID: "LocationID", Name: "LocationID", Path: "LocationID", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler}, @@ -139,7 +176,9 @@ func RegisterSBMServerDefinition() { {ID: "Configuration", Name: "Configuration", Path: "Configuration", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler}, {ID: "PrivateIPv4Address", Name: "PrivateIPv4Address", Path: "PrivateIPv4Address", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler}, {ID: "PublicIPv4Address", Name: "PublicIPv4Address", Path: "PublicIPv4Address", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler}, + {ID: "LeaseStart", Name: "LeaseStart", Path: "LeaseStart", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler}, {ID: "ScheduledRelease", Name: "ScheduledRelease", Path: "ScheduledRelease", ListHandlerFunc: timeHandler, PageViewHandlerFunc: timeHandler}, + {ID: "OobIPv4Address", Name: "OobIPv4Address", Path: "OobIPv4Address", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler}, {ID: "Labels", Name: "Labels", Path: "Labels", PageViewHandlerFunc: mapPvHandler}, {ID: "Created", Name: "Created", Path: "Created", ListHandlerFunc: timeHandler, PageViewHandlerFunc: timeHandler, Default: true}, {ID: "Updated", Name: "Updated", Path: "Updated", ListHandlerFunc: timeHandler, PageViewHandlerFunc: timeHandler, Default: true}, @@ -152,3 +191,56 @@ func RegisterSBMServerDefinition() { log.Fatal(err) } } + +func RegisterHostsSubDefinitions() { + hostConnectionEntity := &Entity{ + fields: []Field{ + {ID: "Port", Name: "Port", Path: "Port", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + {ID: "Type", Name: "Type", Path: "Type", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + {ID: "MACAddress", Name: "MACAddress", Path: "MACAddress", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + }, + eType: HostConnectionType, + } + if err := Registry.Register(hostConnectionEntity); err != nil { + log.Fatal(err) + } + + hostPowerFeedEntity := &Entity{ + fields: []Field{ + {ID: "Name", Name: "Name", Path: "Name", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + {ID: "Status", Name: "Status", Path: "Status", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + }, + eType: HostPowerFeedType, + } + if err := Registry.Register(hostPowerFeedEntity); err != nil { + log.Fatal(err) + } + + hostDriveSlotEntity := &Entity{ + fields: []Field{ + {ID: "Position", Name: "Position", Path: "Position", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + {ID: "Interface", Name: "Interface", Path: "Interface", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + {ID: "FormFactor", Name: "FormFactor", Path: "FormFactor", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + getDriveModel(), + }, + eType: HostDriveSlotType, + } + if err := Registry.Register(hostDriveSlotEntity); err != nil { + log.Fatal(err) + } + + hostPTRRecordEntity := &Entity{ + fields: []Field{ + {ID: "ID", Name: "ID", Path: "ID", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + {ID: "IP", Name: "IP", Path: "IP", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + {ID: "Domain", Name: "Domain", Path: "Domain", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + {ID: "Priority", Name: "Priority", Path: "Priority", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + {ID: "TTL", Name: "TTL", Path: "TTL", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + }, + eType: HostPTRRecordType, + } + if err := Registry.Register(hostPTRRecordEntity); err != nil { + log.Fatal(err) + } + +} diff --git a/internal/output/entities/init.go b/internal/output/entities/init.go index d88bf12..3665b36 100644 --- a/internal/output/entities/init.go +++ b/internal/output/entities/init.go @@ -8,6 +8,7 @@ var ( func init() { RegisterSSHKeyDefinition() RegisterHostDefinition() + RegisterHostsSubDefinitions() RegisterDedicatedServerDefinition() RegisterKubernetesBaremetalNodeDefinition() RegisterSBMServerDefinition() @@ -18,4 +19,7 @@ func init() { RegisterRackDefinition() RegisterInvoiceDefinition() RegisterAccountDefinition() + RegisterLocationDefinition() + RegisterKubernetesClusterDefinition() + RegisterKubernetesClusterNodeDefinition() } diff --git a/internal/output/entities/k8s.go b/internal/output/entities/k8s.go new file mode 100644 index 0000000..0834caa --- /dev/null +++ b/internal/output/entities/k8s.go @@ -0,0 +1,64 @@ +package entities + +import ( + "log" + "reflect" + + serverscom "github.com/serverscom/serverscom-go-client/pkg" +) + +var ( + KubernetesClusterType = reflect.TypeOf(serverscom.KubernetesCluster{}) + KubernetesClusterNodeType = reflect.TypeOf(serverscom.KubernetesClusterNode{}) + KubernetesClusterListDefaultFields = []string{"ID", "Name", "Status", "LocationID"} + KubernetesClusterNodeListDefaultFields = []string{"ID", "Number", "Hostname", "Type", "Role", "Status", "PrivateIPv4Address", "PublicIPv4Address"} +) + +func RegisterKubernetesClusterDefinition() { + k8sEntity := &Entity{ + fields: []Field{ + {ID: "ID", Name: "ID", Path: "ID", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + {ID: "Name", Name: "Name", Path: "Name", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + {ID: "Status", Name: "Status", Path: "Status", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + {ID: "LocationID", Name: "LocationID", Path: "LocationID", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + {ID: "Labels", Name: "Labels", Path: "Labels", PageViewHandlerFunc: mapPvHandler}, + {ID: "Created", Name: "Created", Path: "Created", ListHandlerFunc: timeHandler, PageViewHandlerFunc: timeHandler, Default: true}, + {ID: "Updated", Name: "Updated", Path: "Updated", ListHandlerFunc: timeHandler, PageViewHandlerFunc: timeHandler, Default: true}, + }, + cmdDefaultFields: map[string][]string{ + "list": KubernetesClusterListDefaultFields, + }, + eType: KubernetesClusterType, + } + if err := Registry.Register(k8sEntity); err != nil { + log.Fatal(err) + } +} + +func RegisterKubernetesClusterNodeDefinition() { + k8sNodeEntity := &Entity{ + fields: []Field{ + {ID: "ID", Name: "ID", Path: "ID", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + {ID: "Number", Name: "Number", Path: "Number", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + {ID: "Hostname", Name: "Hostname", Path: "Hostname", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + {ID: "Configuration", Name: "Configuration", Path: "Configuration", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + {ID: "Type", Name: "Type", Path: "Type", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + {ID: "Role", Name: "Role", Path: "Role", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + {ID: "Status", Name: "Status", Path: "Status", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + {ID: "PrivateIPv4Address", Name: "PrivateIPv4Address", Path: "PrivateIPv4Address", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + {ID: "PublicIPv4Address", Name: "PublicIPv4Address", Path: "PublicIPv4Address", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + {ID: "RefID", Name: "RefID", Path: "RefID", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + {ID: "ClusterID", Name: "ClusterID", Path: "ClusterID", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + {ID: "Labels", Name: "Labels", Path: "Labels", PageViewHandlerFunc: mapPvHandler}, + {ID: "Created", Name: "Created", Path: "Created", ListHandlerFunc: timeHandler, PageViewHandlerFunc: timeHandler, Default: true}, + {ID: "Updated", Name: "Updated", Path: "Updated", ListHandlerFunc: timeHandler, PageViewHandlerFunc: timeHandler, Default: true}, + }, + cmdDefaultFields: map[string][]string{ + "list-nodes": KubernetesClusterNodeListDefaultFields, + }, + eType: KubernetesClusterNodeType, + } + if err := Registry.Register(k8sNodeEntity); err != nil { + log.Fatal(err) + } +} diff --git a/internal/output/entities/location.go b/internal/output/entities/location.go new file mode 100644 index 0000000..fc55977 --- /dev/null +++ b/internal/output/entities/location.go @@ -0,0 +1,31 @@ +package entities + +import ( + "log" + "reflect" + + serverscom "github.com/serverscom/serverscom-go-client/pkg" +) + +var ( + LocationType = reflect.TypeOf(serverscom.Location{}) +) + +func RegisterLocationDefinition() { + locationEntity := &Entity{ + fields: []Field{ + {ID: "ID", Name: "ID", Path: "ID", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + {ID: "Name", Name: "Name", Path: "Name", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + {ID: "Status", Name: "Status", Path: "Status", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + {ID: "Code", Name: "Code", Path: "Code", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + {ID: "SupportedFeatures", Name: "SupportedFeatures", Path: "SupportedFeatures", PageViewHandlerFunc: slicePvHandler}, + {ID: "L2SegmentsEnabled", Name: "L2SegmentsEnabled", Path: "L2SegmentsEnabled", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler}, + {ID: "PrivateRacksEnabled", Name: "PrivateRacksEnabled", Path: "PrivateRacksEnabled", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler}, + {ID: "LoadBalancersEnabled", Name: "LoadBalancersEnabled", Path: "LoadBalancersEnabled", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler}, + }, + eType: LocationType, + } + if err := Registry.Register(locationEntity); err != nil { + log.Fatal(err) + } +} diff --git a/testdata/entities/hosts/create_ds_resp.json b/testdata/entities/hosts/create_ds_resp.json index 5e5b5a4..11e39b6 100644 --- a/testdata/entities/hosts/create_ds_resp.json +++ b/testdata/entities/hosts/create_ds_resp.json @@ -1,6 +1,7 @@ [ { "id": "testId", + "rack_id": "testId", "type": "dedicated_server", "title": "example.aa", "location_id": 0, @@ -11,7 +12,9 @@ "configuration": "", "private_ipv4_address": null, "public_ipv4_address": null, + "lease_start_at": "", "scheduled_release_at": null, + "oob_ipv4_address": "", "configuration_details": { "ram_size": 0, "server_model_id": null, diff --git a/testdata/entities/hosts/create_sbm_resp.json b/testdata/entities/hosts/create_sbm_resp.json index 546b2de..e6724f4 100644 --- a/testdata/entities/hosts/create_sbm_resp.json +++ b/testdata/entities/hosts/create_sbm_resp.json @@ -1,6 +1,7 @@ [ { "id": "testId", + "rack_id": "testId", "type": "sbm_server", "title": "example.aa", "location_id": 0, @@ -11,7 +12,9 @@ "configuration": "", "private_ipv4_address": null, "public_ipv4_address": null, + "lease_start_at": "", "scheduled_release_at": null, + "oob_ipv4_address": "", "configuration_details": { "ram_size": 0, "server_model_id": null, diff --git a/testdata/entities/hosts/get_ds.json b/testdata/entities/hosts/get_ds.json index 0b9c042..ed236e3 100644 --- a/testdata/entities/hosts/get_ds.json +++ b/testdata/entities/hosts/get_ds.json @@ -1,5 +1,6 @@ { "id": "testId", + "rack_id": "testId", "type": "dedicated_server", "title": "example.aa", "location_id": 0, @@ -10,7 +11,9 @@ "configuration": "", "private_ipv4_address": null, "public_ipv4_address": null, + "lease_start_at": "", "scheduled_release_at": null, + "oob_ipv4_address": "", "configuration_details": { "ram_size": 0, "server_model_id": null, diff --git a/testdata/entities/hosts/get_ds.txt b/testdata/entities/hosts/get_ds.txt index 1b3e9d2..ff67a25 100644 --- a/testdata/entities/hosts/get_ds.txt +++ b/testdata/entities/hosts/get_ds.txt @@ -1,2 +1,2 @@ -ID Title Status Created Updated -testId example.aa active 2025-01-01T12:00:00Z 2025-01-01T12:00:00Z +ID RackID Title Status Created Updated +testId testId example.aa active 2025-01-01T12:00:00Z 2025-01-01T12:00:00Z diff --git a/testdata/entities/hosts/get_ds.yaml b/testdata/entities/hosts/get_ds.yaml index f1ab606..013c84b 100644 --- a/testdata/entities/hosts/get_ds.yaml +++ b/testdata/entities/hosts/get_ds.yaml @@ -1,4 +1,5 @@ id: testId +rackid: testId type: dedicated_server title: example.aa locationid: 0 @@ -9,7 +10,9 @@ powerstatus: "" configuration: "" privateipv4address: null publicipv4address: null +leasestart: "" scheduledrelease: null +oobipv4address: "" configurationdetails: ramsize: 0 servermodelid: null diff --git a/testdata/entities/hosts/get_kbm.json b/testdata/entities/hosts/get_kbm.json index c427763..9c836d8 100644 --- a/testdata/entities/hosts/get_kbm.json +++ b/testdata/entities/hosts/get_kbm.json @@ -1,5 +1,9 @@ { "id": "testId", + "kubernetes_cluster_id": "testId", + "kubernetes_cluster_node_id": "testId", + "kubernetes_cluster_node_number": 1, + "rack_id": "testId", "type": "kubernetes_baremetal_node", "title": "example.aa", "location_id": 0, @@ -10,7 +14,9 @@ "configuration": "", "private_ipv4_address": null, "public_ipv4_address": null, + "lease_start_at": "", "scheduled_release_at": null, + "oob_ipv4_address": "", "configuration_details": { "ram_size": 0, "server_model_id": null, diff --git a/testdata/entities/hosts/get_kbm.txt b/testdata/entities/hosts/get_kbm.txt index 1b3e9d2..ff67a25 100644 --- a/testdata/entities/hosts/get_kbm.txt +++ b/testdata/entities/hosts/get_kbm.txt @@ -1,2 +1,2 @@ -ID Title Status Created Updated -testId example.aa active 2025-01-01T12:00:00Z 2025-01-01T12:00:00Z +ID RackID Title Status Created Updated +testId testId example.aa active 2025-01-01T12:00:00Z 2025-01-01T12:00:00Z diff --git a/testdata/entities/hosts/get_kbm.yaml b/testdata/entities/hosts/get_kbm.yaml index ab9a00b..8e49d21 100644 --- a/testdata/entities/hosts/get_kbm.yaml +++ b/testdata/entities/hosts/get_kbm.yaml @@ -1,4 +1,8 @@ id: testId +kubernetesclusterid: testId +kubernetesclusternodeid: testId +kubernetesclusternodenumber: 1 +rackid: testId type: kubernetes_baremetal_node title: example.aa locationid: 0 @@ -9,7 +13,9 @@ powerstatus: "" configuration: "" privateipv4address: null publicipv4address: null +leasestart: "" scheduledrelease: null +oobipv4address: "" configurationdetails: ramsize: 0 servermodelid: null diff --git a/testdata/entities/hosts/get_sbm.json b/testdata/entities/hosts/get_sbm.json index c9f75a0..fd10f4f 100644 --- a/testdata/entities/hosts/get_sbm.json +++ b/testdata/entities/hosts/get_sbm.json @@ -1,5 +1,6 @@ { "id": "testId", + "rack_id": "testId", "type": "sbm_server", "title": "example.aa", "location_id": 0, @@ -10,7 +11,9 @@ "configuration": "", "private_ipv4_address": null, "public_ipv4_address": null, + "lease_start_at": "", "scheduled_release_at": null, + "oob_ipv4_address": "", "configuration_details": { "ram_size": 0, "server_model_id": null, diff --git a/testdata/entities/hosts/get_sbm.txt b/testdata/entities/hosts/get_sbm.txt index 1b3e9d2..ff67a25 100644 --- a/testdata/entities/hosts/get_sbm.txt +++ b/testdata/entities/hosts/get_sbm.txt @@ -1,2 +1,2 @@ -ID Title Status Created Updated -testId example.aa active 2025-01-01T12:00:00Z 2025-01-01T12:00:00Z +ID RackID Title Status Created Updated +testId testId example.aa active 2025-01-01T12:00:00Z 2025-01-01T12:00:00Z diff --git a/testdata/entities/hosts/get_sbm.yaml b/testdata/entities/hosts/get_sbm.yaml index 990b8bf..10958d6 100644 --- a/testdata/entities/hosts/get_sbm.yaml +++ b/testdata/entities/hosts/get_sbm.yaml @@ -1,4 +1,5 @@ id: testId +rackid: testId type: sbm_server title: example.aa locationid: 0 @@ -9,7 +10,9 @@ powerstatus: "" configuration: "" privateipv4address: null publicipv4address: null +leasestart: "" scheduledrelease: null +oobipv4address: "" configurationdetails: ramsize: 0 servermodelid: null diff --git a/testdata/entities/hosts/release_ds_resp.json b/testdata/entities/hosts/release_ds_resp.json index c187c7e..531ade2 100644 --- a/testdata/entities/hosts/release_ds_resp.json +++ b/testdata/entities/hosts/release_ds_resp.json @@ -1,5 +1,6 @@ { "id": "testId", + "rack_id": "testId", "type": "dedicated_server", "title": "example.aa", "location_id": 0, @@ -10,7 +11,9 @@ "configuration": "", "private_ipv4_address": null, "public_ipv4_address": null, + "lease_start_at": "", "scheduled_release_at": "2025-01-01T12:00:00Z", + "oob_ipv4_address": "", "configuration_details": { "ram_size": 0, "server_model_id": null, diff --git a/testdata/entities/hosts/update_ds_resp.json b/testdata/entities/hosts/update_ds_resp.json index e4555e8..c76aced 100644 --- a/testdata/entities/hosts/update_ds_resp.json +++ b/testdata/entities/hosts/update_ds_resp.json @@ -1,5 +1,6 @@ { "id": "testId", + "rack_id": "testId", "type": "dedicated_server", "title": "example.aa", "location_id": 0, @@ -10,7 +11,9 @@ "configuration": "", "private_ipv4_address": null, "public_ipv4_address": null, + "lease_start_at": "", "scheduled_release_at": null, + "oob_ipv4_address": "", "configuration_details": { "ram_size": 0, "server_model_id": null, diff --git a/testdata/entities/hosts/update_kbm_resp.json b/testdata/entities/hosts/update_kbm_resp.json index 50d7f63..153bc4e 100644 --- a/testdata/entities/hosts/update_kbm_resp.json +++ b/testdata/entities/hosts/update_kbm_resp.json @@ -1,5 +1,9 @@ { "id": "testId", + "kubernetes_cluster_id": "testId", + "kubernetes_cluster_node_id": "testId", + "kubernetes_cluster_node_number": 1, + "rack_id": "testId", "type": "kubernetes_baremetal_node", "title": "example.aa", "location_id": 0, @@ -10,7 +14,9 @@ "configuration": "", "private_ipv4_address": null, "public_ipv4_address": null, + "lease_start_at": "", "scheduled_release_at": null, + "oob_ipv4_address": "", "configuration_details": { "ram_size": 0, "server_model_id": null, diff --git a/testdata/entities/hosts/update_sbm_resp.json b/testdata/entities/hosts/update_sbm_resp.json index bf5318d..7edd9d1 100644 --- a/testdata/entities/hosts/update_sbm_resp.json +++ b/testdata/entities/hosts/update_sbm_resp.json @@ -1,5 +1,6 @@ { "id": "testId", + "rack_id": "testId", "type": "sbm_server", "title": "example.aa", "location_id": 0, @@ -10,7 +11,9 @@ "configuration": "", "private_ipv4_address": null, "public_ipv4_address": null, + "lease_start_at": "", "scheduled_release_at": null, + "oob_ipv4_address": "", "configuration_details": { "ram_size": 0, "server_model_id": null, diff --git a/testdata/entities/k8s/get.json b/testdata/entities/k8s/get.json new file mode 100644 index 0000000..ce8acdf --- /dev/null +++ b/testdata/entities/k8s/get.json @@ -0,0 +1,9 @@ +{ + "id": "testId", + "name": "test-cluster", + "status": "active", + "location_id": 1, + "labels": null, + "created_at": "2025-01-01T12:00:00Z", + "updated_at": "2025-01-01T12:00:00Z" +} \ No newline at end of file diff --git a/testdata/entities/k8s/get.txt b/testdata/entities/k8s/get.txt new file mode 100644 index 0000000..82f99b4 --- /dev/null +++ b/testdata/entities/k8s/get.txt @@ -0,0 +1,2 @@ +ID Name Status LocationID Created Updated +testId test-cluster active 1 2025-01-01T12:00:00Z 2025-01-01T12:00:00Z diff --git a/testdata/entities/k8s/get.yaml b/testdata/entities/k8s/get.yaml new file mode 100644 index 0000000..e798b7a --- /dev/null +++ b/testdata/entities/k8s/get.yaml @@ -0,0 +1,7 @@ +id: testId +name: test-cluster +status: active +locationid: 1 +labels: {} +created: 2025-01-01T12:00:00Z +updated: 2025-01-01T12:00:00Z diff --git a/testdata/entities/k8s/get_node.json b/testdata/entities/k8s/get_node.json new file mode 100644 index 0000000..50948c6 --- /dev/null +++ b/testdata/entities/k8s/get_node.json @@ -0,0 +1,16 @@ +{ + "id": "testNodeId", + "number": 1, + "hostname": "test-node-1", + "configuration": "SSD.50", + "type": "cloud", + "role": "master", + "status": "active", + "private_ipv4_address": "10.0.0.1", + "public_ipv4_address": "127.0.0.1", + "ref_id": "1", + "cluster_id": "testId", + "labels": null, + "created_at": "2025-01-01T12:00:00Z", + "updated_at": "2025-01-01T12:00:00Z" +} \ No newline at end of file diff --git a/testdata/entities/k8s/get_node.txt b/testdata/entities/k8s/get_node.txt new file mode 100644 index 0000000..2f98bbb --- /dev/null +++ b/testdata/entities/k8s/get_node.txt @@ -0,0 +1,2 @@ +ID Number Hostname Configuration Type Role Status PrivateIPv4Address PublicIPv4Address RefID ClusterID Created Updated +testNodeId 1 test-node-1 SSD.50 cloud master active 10.0.0.1 127.0.0.1 1 testId 2025-01-01T12:00:00Z 2025-01-01T12:00:00Z diff --git a/testdata/entities/k8s/get_node.yaml b/testdata/entities/k8s/get_node.yaml new file mode 100644 index 0000000..410c3c8 --- /dev/null +++ b/testdata/entities/k8s/get_node.yaml @@ -0,0 +1,14 @@ +id: testNodeId +number: 1 +hostname: test-node-1 +configuration: SSD.50 +type: cloud +role: master +status: active +privateipv4address: 10.0.0.1 +publicipv4address: 127.0.0.1 +refid: "1" +clusterid: testId +labels: {} +created: 2025-01-01T12:00:00Z +updated: 2025-01-01T12:00:00Z diff --git a/testdata/entities/k8s/list.json b/testdata/entities/k8s/list.json new file mode 100644 index 0000000..571acab --- /dev/null +++ b/testdata/entities/k8s/list.json @@ -0,0 +1,11 @@ +[ + { + "id": "testId1", + "name": "test-cluster", + "status": "active", + "location_id": 1, + "labels": null, + "created_at": "2025-01-01T12:00:00Z", + "updated_at": "2025-01-01T12:00:00Z" + } +] \ No newline at end of file diff --git a/testdata/entities/k8s/list_all.json b/testdata/entities/k8s/list_all.json new file mode 100644 index 0000000..84d3e3e --- /dev/null +++ b/testdata/entities/k8s/list_all.json @@ -0,0 +1,20 @@ +[ + { + "id": "testId1", + "name": "test-cluster", + "status": "active", + "location_id": 1, + "labels": null, + "created_at": "2025-01-01T12:00:00Z", + "updated_at": "2025-01-01T12:00:00Z" + }, + { + "id": "testId2", + "name": "test-cluster 2", + "status": "active", + "location_id": 1, + "labels": null, + "created_at": "2025-01-01T12:00:00Z", + "updated_at": "2025-01-01T12:00:00Z" + } +] \ No newline at end of file diff --git a/testdata/entities/k8s/list_all_nodes.json b/testdata/entities/k8s/list_all_nodes.json new file mode 100644 index 0000000..1bcc7b0 --- /dev/null +++ b/testdata/entities/k8s/list_all_nodes.json @@ -0,0 +1,34 @@ +[ + { + "id": "testNodeId1", + "number": 1, + "hostname": "test-node-1", + "configuration": "SSD.50", + "type": "cloud", + "role": "master", + "status": "active", + "private_ipv4_address": "10.0.0.1", + "public_ipv4_address": "127.0.0.1", + "ref_id": "1", + "cluster_id": "testId", + "labels": null, + "created_at": "2025-01-01T12:00:00Z", + "updated_at": "2025-01-01T12:00:00Z" + }, + { + "id": "testNodeId2", + "number": 1, + "hostname": "test-node-2", + "configuration": "SSD.50", + "type": "cloud", + "role": "master", + "status": "active", + "private_ipv4_address": "10.0.0.1", + "public_ipv4_address": "127.0.0.1", + "ref_id": "1", + "cluster_id": "testId", + "labels": null, + "created_at": "2025-01-01T12:00:00Z", + "updated_at": "2025-01-01T12:00:00Z" + } +] \ No newline at end of file diff --git a/testdata/entities/k8s/list_nodes.json b/testdata/entities/k8s/list_nodes.json new file mode 100644 index 0000000..83339be --- /dev/null +++ b/testdata/entities/k8s/list_nodes.json @@ -0,0 +1,18 @@ +[ + { + "id": "testNodeId1", + "number": 1, + "hostname": "test-node-1", + "configuration": "SSD.50", + "type": "cloud", + "role": "master", + "status": "active", + "private_ipv4_address": "10.0.0.1", + "public_ipv4_address": "127.0.0.1", + "ref_id": "1", + "cluster_id": "testId", + "labels": null, + "created_at": "2025-01-01T12:00:00Z", + "updated_at": "2025-01-01T12:00:00Z" + } +] \ No newline at end of file diff --git a/testdata/entities/k8s/list_pageview.txt b/testdata/entities/k8s/list_pageview.txt new file mode 100644 index 0000000..7c98089 --- /dev/null +++ b/testdata/entities/k8s/list_pageview.txt @@ -0,0 +1,15 @@ +ID: testId1 +Name: test-cluster +Status: active +LocationID: 1 +Labels: +Created: 2025-01-01T12:00:00Z +Updated: 2025-01-01T12:00:00Z +--- +ID: testId2 +Name: test-cluster 2 +Status: active +LocationID: 1 +Labels: +Created: 2025-01-01T12:00:00Z +Updated: 2025-01-01T12:00:00Z diff --git a/testdata/entities/k8s/list_pageview_nodes.txt b/testdata/entities/k8s/list_pageview_nodes.txt new file mode 100644 index 0000000..ebef2de --- /dev/null +++ b/testdata/entities/k8s/list_pageview_nodes.txt @@ -0,0 +1,29 @@ +ID: testNodeId1 +Number: 1 +Hostname: test-node-1 +Configuration: SSD.50 +Type: cloud +Role: master +Status: active +PrivateIPv4Address: 10.0.0.1 +PublicIPv4Address: 127.0.0.1 +RefID: 1 +ClusterID: testId +Labels: +Created: 2025-01-01T12:00:00Z +Updated: 2025-01-01T12:00:00Z +--- +ID: testNodeId2 +Number: 1 +Hostname: test-node-2 +Configuration: SSD.50 +Type: cloud +Role: master +Status: active +PrivateIPv4Address: 10.0.0.1 +PublicIPv4Address: 127.0.0.1 +RefID: 1 +ClusterID: testId +Labels: +Created: 2025-01-01T12:00:00Z +Updated: 2025-01-01T12:00:00Z diff --git a/testdata/entities/k8s/list_template.txt b/testdata/entities/k8s/list_template.txt new file mode 100644 index 0000000..5ae83f6 --- /dev/null +++ b/testdata/entities/k8s/list_template.txt @@ -0,0 +1,2 @@ +Name: test-cluster +Name: test-cluster 2 diff --git a/testdata/entities/k8s/list_template_nodes.txt b/testdata/entities/k8s/list_template_nodes.txt new file mode 100644 index 0000000..94fdaca --- /dev/null +++ b/testdata/entities/k8s/list_template_nodes.txt @@ -0,0 +1,2 @@ +Hostname: test-node-1 +Hostname: test-node-2 diff --git a/testdata/entities/k8s/update.json b/testdata/entities/k8s/update.json new file mode 100644 index 0000000..9028f01 --- /dev/null +++ b/testdata/entities/k8s/update.json @@ -0,0 +1,11 @@ +{ + "id": "testId", + "name": "test-cluster", + "status": "active", + "location_id": 1, + "labels": { + "new": "label" + }, + "created_at": "2025-01-01T12:00:00Z", + "updated_at": "2025-01-01T12:00:00Z" +} \ No newline at end of file diff --git a/testdata/entities/locations/get.json b/testdata/entities/locations/get.json new file mode 100644 index 0000000..0c1c187 --- /dev/null +++ b/testdata/entities/locations/get.json @@ -0,0 +1,13 @@ +{ + "id": 1, + "name": "test-location", + "status": "active", + "code": "test", + "supported_features": [ + "feature1", + "feature2" + ], + "l2_segments_enabled": false, + "private_racks_enabled": false, + "load_balancers_enabled": false +} \ No newline at end of file diff --git a/testdata/entities/locations/get.txt b/testdata/entities/locations/get.txt new file mode 100644 index 0000000..53d9457 --- /dev/null +++ b/testdata/entities/locations/get.txt @@ -0,0 +1,2 @@ +ID Name Status Code +1 test-location active test diff --git a/testdata/entities/locations/get.yaml b/testdata/entities/locations/get.yaml new file mode 100644 index 0000000..f593b8f --- /dev/null +++ b/testdata/entities/locations/get.yaml @@ -0,0 +1,10 @@ +id: 1 +name: test-location +status: active +code: test +supportedfeatures: + - feature1 + - feature2 +l2segmentsenabled: false +privateracksenabled: false +loadbalancersenabled: false diff --git a/testdata/entities/locations/list.json b/testdata/entities/locations/list.json new file mode 100644 index 0000000..b5cfeab --- /dev/null +++ b/testdata/entities/locations/list.json @@ -0,0 +1,15 @@ +[ + { + "id": 1, + "name": "test-location", + "status": "active", + "code": "test", + "supported_features": [ + "feature1", + "feature2" + ], + "l2_segments_enabled": false, + "private_racks_enabled": false, + "load_balancers_enabled": false + } +] \ No newline at end of file diff --git a/testdata/entities/locations/list_all.json b/testdata/entities/locations/list_all.json new file mode 100644 index 0000000..b54c022 --- /dev/null +++ b/testdata/entities/locations/list_all.json @@ -0,0 +1,28 @@ +[ + { + "id": 1, + "name": "test-location", + "status": "active", + "code": "test", + "supported_features": [ + "feature1", + "feature2" + ], + "l2_segments_enabled": false, + "private_racks_enabled": false, + "load_balancers_enabled": false + }, + { + "id": 2, + "name": "test-location 2", + "status": "active", + "code": "test", + "supported_features": [ + "feature1", + "feature2" + ], + "l2_segments_enabled": false, + "private_racks_enabled": false, + "load_balancers_enabled": false + } +] \ No newline at end of file diff --git a/testdata/entities/locations/list_pageview.txt b/testdata/entities/locations/list_pageview.txt new file mode 100644 index 0000000..f883ce4 --- /dev/null +++ b/testdata/entities/locations/list_pageview.txt @@ -0,0 +1,19 @@ +ID: 1 +Name: test-location +Status: active +Code: test +SupportedFeatures: feature1 + feature2 +L2SegmentsEnabled: false +PrivateRacksEnabled: false +LoadBalancersEnabled: false +--- +ID: 2 +Name: test-location 2 +Status: active +Code: test +SupportedFeatures: feature1 + feature2 +L2SegmentsEnabled: false +PrivateRacksEnabled: false +LoadBalancersEnabled: false diff --git a/testdata/entities/locations/list_template.txt b/testdata/entities/locations/list_template.txt new file mode 100644 index 0000000..3f39e39 --- /dev/null +++ b/testdata/entities/locations/list_template.txt @@ -0,0 +1,2 @@ +Name: test-location +Name: test-location 2