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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,5 @@ jobs:
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- name: Checkout code
uses: actions/checkout@v4
- name: Test
run: go test ./...
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ generate: deps
mockgen --destination ./internal/mocks/collection.go --package=mocks --source ./vendor/github.com/serverscom/serverscom-go-client/pkg/collection.go
mockgen --destination ./internal/mocks/hosts_service.go --package=mocks --source ./vendor/github.com/serverscom/serverscom-go-client/pkg/hosts.go
mockgen --destination ./internal/mocks/ssh_service.go --package=mocks --source ./vendor/github.com/serverscom/serverscom-go-client/pkg/ssh_keys.go
mockgen --destination ./internal/mocks/ssl_service.go --package=mocks --source ./vendor/github.com/serverscom/serverscom-go-client/pkg/ssl_certificates.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 \
./internal/mocks/ssl_service.go \
./internal/mocks/collection.go

3 changes: 3 additions & 0 deletions cmd/base/hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ func CheckFormatterFlags(cmdContext *CmdContext, entities map[string]entities.En
if entity == nil {
return fmt.Errorf("can't find entity")
}
if err := entity.SetCmdDefaultFields(cmd.Name()); err != nil {
return err
}
if fieldList {
formatter.ListEntityFields(entity.GetFields())
os.Exit(0)
Expand Down
58 changes: 38 additions & 20 deletions cmd/base/list.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package base

import (
"log"
"strings"

serverscom "github.com/serverscom/serverscom-go-client/pkg"
Expand All @@ -10,9 +11,16 @@ import (
type ListOptions[T any] interface {
AddFlags(*cobra.Command)
ApplyToCollection(serverscom.Collection[T])
}

type AllPager interface {
AllPages() bool
}

func NewListOptions[T any](opts ...ListOptions[T]) []ListOptions[T] {
return opts
}

// CollectionFactory is a function type that creates a typed resource collection
// with configurable verbosity level
// type CollectionFactory[T any] func(verbose bool) serverscom.Collection[T]
Expand All @@ -35,6 +43,11 @@ func (o *BaseListOptions[T]) AddFlags(cmd *cobra.Command) {
flags.StringVar(&o.sorting, "sorting", "", "Sort field")
flags.StringVar(&o.direction, "direction", "", "Sort direction (ASC or DESC)")
flags.BoolVarP(&o.allPages, "all", "A", false, "Get all pages of resources")

flags.String("type", "", "")
if err := flags.MarkHidden("type"); err != nil {
log.Fatal(err)
}
}

// ApplyToCollection applies the options to a collection
Expand All @@ -59,28 +72,36 @@ func (o *BaseListOptions[T]) AllPages() bool {
return o.allPages
}

// BaseLabelsListOptions is a base options struct for list commands with label selector option
type BaseLabelsListOptions[T any] struct {
BaseListOptions[T]
type LabelSelectorOption[T any] struct {
labelSelector string
}

// AddFlags adds common list flags to the command
func (o *BaseLabelsListOptions[T]) AddFlags(cmd *cobra.Command) {
o.BaseListOptions.AddFlags(cmd)
flags := cmd.Flags()
flags.StringVar(&o.labelSelector, "label-selector", "", "Filter by label selector")
func (o *LabelSelectorOption[T]) AddFlags(cmd *cobra.Command) {
cmd.Flags().StringVar(&o.labelSelector, "label-selector", "", "Filter results by labels")
}

// ApplyToCollection applies the options to a collection
func (o *BaseLabelsListOptions[T]) ApplyToCollection(collection serverscom.Collection[T]) {
func (o *LabelSelectorOption[T]) ApplyToCollection(collection serverscom.Collection[T]) {
if o.labelSelector != "" {
collection.SetParam("label_selector", o.labelSelector)
}
}

type SearchPatternOption[T any] struct {
searchPattern string
}

func (o *SearchPatternOption[T]) AddFlags(cmd *cobra.Command) {
cmd.Flags().StringVar(&o.searchPattern, "search-pattern", "", "Return resources containing the parameter value in its name")
}

func (o *SearchPatternOption[T]) ApplyToCollection(collection serverscom.Collection[T]) {
if o.searchPattern != "" {
collection.SetParam("search_pattern", o.searchPattern)
}
}

// NewListCmd base list command for different collections
func NewListCmd[T any](use string, entityName string, colFactory CollectionFactory[T], cmdContext *CmdContext, opts ListOptions[T]) *cobra.Command {
func NewListCmd[T any](use string, entityName string, colFactory CollectionFactory[T], cmdContext *CmdContext, opts ...ListOptions[T]) *cobra.Command {
aliases := []string{}
if use == "list" {
aliases = append(aliases, "ls")
Expand All @@ -99,16 +120,11 @@ func NewListCmd[T any](use string, entityName string, colFactory CollectionFacto
SetupProxy(cmd, manager)

collection := colFactory(manager.GetVerbose(cmd), args...)
opts.ApplyToCollection(collection)

var items []T
var err error
if opts.AllPages() {
items, err = collection.Collect(ctx)
} else {
items, err = collection.List(ctx)
for _, opt := range opts {
opt.ApplyToCollection(collection)
}

items, err := fetchItems(ctx, collection, opts)
if err != nil {
return err
}
Expand All @@ -118,7 +134,9 @@ func NewListCmd[T any](use string, entityName string, colFactory CollectionFacto
},
}

opts.AddFlags(cmd)
for _, opt := range opts {
opt.AddFlags(cmd)
}

return cmd
}
11 changes: 11 additions & 0 deletions cmd/base/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"strings"
"time"

serverscom "github.com/serverscom/serverscom-go-client/pkg"
"github.com/serverscom/srvctl/internal/client"
"github.com/serverscom/srvctl/internal/config"
"github.com/serverscom/srvctl/internal/output"
Expand Down Expand Up @@ -183,3 +184,13 @@ func NoArgs(cmd *cobra.Command, args []string) error {
}
return nil
}

func fetchItems[T any](ctx context.Context, collection serverscom.Collection[T], opts []ListOptions[T]) ([]T, error) {
for _, opt := range opts {
if baseOpts, ok := opt.(AllPager); ok && baseOpts.AllPages() {
return collection.Collect(ctx)
}
}

return collection.List(ctx)
}
8 changes: 5 additions & 3 deletions cmd/entities/hosts/hosts.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ type HostTypeCmd struct {
}

type HostManagers struct {
getMgr HostGetter
createMgr HostCreator
getMgr HostGetter
createMgr HostCreator
// for update we use simple commands in sake of simplicity
powerMgr HostPowerer
reinstallMgr HostReinstaller
}
Expand Down Expand Up @@ -119,7 +120,7 @@ func newHostTypeCmd(cmdContext *base.CmdContext, hostTypeCmd HostTypeCmd) *cobra
hostCmd.AddCommand(newListCmd(cmdContext, &hostTypeCmd))
hostCmd.AddCommand(newGetCmd(cmdContext, &hostTypeCmd))

if hostTypeCmd.managers.getMgr != nil {
if hostTypeCmd.managers.createMgr != nil {
hostCmd.AddCommand(newAddCmd(cmdContext, &hostTypeCmd))
}
if hostTypeCmd.managers.powerMgr != nil {
Expand All @@ -144,6 +145,7 @@ func getHostsEntities() (map[string]entities.EntityInterface, error) {
return nil, err
}
result["hosts"] = hostsEntity
result["list"] = hostsEntity

dsEntity, err := entities.Registry.GetEntityFromValue(serverscom.DedicatedServer{})
if err != nil {
Expand Down
17 changes: 8 additions & 9 deletions cmd/entities/hosts/list.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
package hosts

import (
"log"

serverscom "github.com/serverscom/serverscom-go-client/pkg"
"github.com/serverscom/srvctl/cmd/base"
"github.com/spf13/cobra"
)

type hostListOptions struct {
base.BaseLabelsListOptions[serverscom.Host]
base.BaseListOptions[serverscom.Host]
rackID string
locationID string
}
Expand All @@ -20,11 +18,6 @@ func (o *hostListOptions) AddFlags(cmd *cobra.Command) {
flags := cmd.Flags()
flags.StringVar(&o.rackID, "rack-id", "", "Filter by rack ID")
flags.StringVar(&o.locationID, "location-id", "", "Filter by location ID")

flags.String("type", "", "")
if err := flags.MarkHidden("type"); err != nil {
log.Fatal(err)
}
}

func (o *hostListOptions) ApplyToCollection(collection serverscom.Collection[serverscom.Host]) {
Expand All @@ -50,10 +43,16 @@ func newListCmd(cmdContext *base.CmdContext, hostType *HostTypeCmd) *cobra.Comma
return collection
}

opts := base.NewListOptions(
&hostListOptions{},
&base.LabelSelectorOption[serverscom.Host]{},
&base.SearchPatternOption[serverscom.Host]{},
)

entityName := "Hosts"
if hostType != nil {
entityName = hostType.entityName
}

return base.NewListCmd("list", entityName, factory, cmdContext, &hostListOptions{})
return base.NewListCmd("list", entityName, factory, cmdContext, opts...)
}
7 changes: 5 additions & 2 deletions cmd/entities/ssh-keys/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ func newListCmd(cmdContext *base.CmdContext) *cobra.Command {
return scClient.SSHKeys.Collection()
}

opts := &base.BaseLabelsListOptions[serverscom.SSHKey]{}
opts := base.NewListOptions(
&base.BaseListOptions[serverscom.SSHKey]{},
&base.LabelSelectorOption[serverscom.SSHKey]{},
)

return base.NewListCmd("list", "SSH Keys", factory, cmdContext, opts)
return base.NewListCmd("list", "SSH Keys", factory, cmdContext, opts...)
}
73 changes: 73 additions & 0 deletions cmd/entities/ssl/add.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package ssl

import (
"context"
"fmt"
"log"

serverscom "github.com/serverscom/serverscom-go-client/pkg"
"github.com/serverscom/srvctl/cmd/base"
"github.com/spf13/cobra"
)

type SSLCreator interface {
Create(ctx context.Context, client *serverscom.Client, input any) (any, error)
NewCreateInput() any
}

type SSLCustomCreateMgr struct{}

func (c *SSLCustomCreateMgr) Create(ctx context.Context, client *serverscom.Client, input any) (any, error) {
sslInput, ok := input.(*serverscom.SSLCertificateCreateCustomInput)
if !ok {
return nil, fmt.Errorf("invalid input type for custom SSL")
}
return client.SSLCertificates.CreateCustom(ctx, *sslInput)
}

func (c *SSLCustomCreateMgr) NewCreateInput() any {
return &serverscom.SSLCertificateCreateCustomInput{}
}

func newAddCmd(cmdContext *base.CmdContext, sslType *SSLTypeCmd) *cobra.Command {
var path string
cmd := &cobra.Command{
Use: "add --input <path>",
Short: fmt.Sprintf("Create a %s", sslType.entityName),
Args: cobra.ExactArgs(0),
RunE: func(cmd *cobra.Command, args []string) error {
manager := cmdContext.GetManager()
ctx, cancel := base.SetupContext(cmd, manager)
defer cancel()

base.SetupProxy(cmd, manager)

input := sslType.managers.createMgr.NewCreateInput()

if err := base.ReadInputJSON(path, cmd.InOrStdin(), input); err != nil {
return err
}

scClient := cmdContext.GetClient().SetVerbose(manager.GetVerbose(cmd)).GetScClient()

server, err := sslType.managers.createMgr.Create(ctx, scClient, input)
if err != nil {
return err
}

if server != nil {
formatter := cmdContext.GetOrCreateFormatter(cmd)
return formatter.Format(server)
}

return nil
},
}

cmd.Flags().StringVarP(&path, "input", "i", "", "path to input file or '-' to read from stdin")
if err := cmd.MarkFlagRequired("input"); err != nil {
log.Fatal(err)
}

return cmd
}
53 changes: 53 additions & 0 deletions cmd/entities/ssl/delete.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package ssl

import (
"context"

serverscom "github.com/serverscom/serverscom-go-client/pkg"
"github.com/serverscom/srvctl/cmd/base"
"github.com/spf13/cobra"
)

type SSLDeleter interface {
Delete(ctx context.Context, client *serverscom.Client, id string) error
}

type SSLCustomDeleteMgr struct{}

func (d *SSLCustomDeleteMgr) Delete(ctx context.Context, client *serverscom.Client, id string) error {
return client.SSLCertificates.DeleteCustom(ctx, id)
}

type SSLLeDeleteMgr struct{}

func (d *SSLLeDeleteMgr) Delete(ctx context.Context, client *serverscom.Client, id string) error {
return client.SSLCertificates.DeleteLE(ctx, id)
}

func newDeleteCmd(cmdContext *base.CmdContext, sslType *SSLTypeCmd) *cobra.Command {
cmd := &cobra.Command{
Use: "delete <id>",
Short: "Delete an SSL certificate",
Long: "Delete an SSL certificate 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]
err := sslType.managers.deleteMgr.Delete(ctx, scClient, id)
if err != nil {
return err
}

return nil
},
}
return cmd
}
Loading
Loading