From cc09480f405a7cf663caa5588be20b9cc8bc4296 Mon Sep 17 00:00:00 2001 From: Pierre-Emmanuel Jacquier <15922119+pierre-emmanuelJ@users.noreply.github.com> Date: Wed, 24 Jul 2024 11:25:01 +0200 Subject: [PATCH] Instance Create: Migrate to egoscale v3 and add multiple sshkeys (#620) Signed-off-by: Pierre-Emmanuel Jacquier <15922119+pierre-emmanuelJ@users.noreply.github.com> --- CHANGELOG.md | 6 + cmd/instance_create.go | 163 ++++++++++++------ go.mod | 2 +- go.sum | 4 +- utils/utils.go | 20 +++ utils/utils_test.go | 30 ++++ .../github.com/exoscale/egoscale/v3/client.go | 10 +- .../exoscale/egoscale/v3/operations.go | 155 ++++++++++++++--- vendor/modules.txt | 2 +- 9 files changed, 303 insertions(+), 89 deletions(-) create mode 100644 utils/utils_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 6438b2f7..ec444aa0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +### Improvements + +- Instance Create: Migrate to egoscale v3 and add multiple sshkeys #620 + ## 1.78.4 ### Improvements diff --git a/cmd/instance_create.go b/cmd/instance_create.go index ec34aa88..c2d3e4b0 100644 --- a/cmd/instance_create.go +++ b/cmd/instance_create.go @@ -20,8 +20,7 @@ import ( exossh "github.com/exoscale/cli/pkg/ssh" "github.com/exoscale/cli/pkg/userdata" "github.com/exoscale/cli/utils" - egoscale "github.com/exoscale/egoscale/v2" - exoapi "github.com/exoscale/egoscale/v2/api" + v3 "github.com/exoscale/egoscale/v3" ) type instanceCreateCmd struct { @@ -41,11 +40,11 @@ type instanceCreateCmd struct { Labels map[string]string `cli-flag:"label" cli-usage:"instance label (format: key=value)"` PrivateNetworks []string `cli-flag:"private-network" cli-usage:"instance Private Network NAME|ID (can be specified multiple times)"` PrivateInstance bool `cli-flag:"private-instance" cli-usage:"enable private instance to be created"` - SSHKey string `cli-flag:"ssh-key" cli-usage:"SSH key to deploy on the instance"` + SSHKeys []string `cli-flag:"ssh-key" cli-usage:"SSH key to deploy on the instance (can be specified multiple times)"` SecurityGroups []string `cli-flag:"security-group" cli-usage:"instance Security Group NAME|ID (can be specified multiple times)"` Template string `cli-usage:"instance template NAME|ID"` TemplateVisibility string `cli-usage:"instance template visibility (public|private)"` - Zone string `cli-short:"z" cli-usage:"instance zone"` + Zone v3.ZoneName `cli-short:"z" cli-usage:"instance zone"` } func (c *instanceCreateCmd) cmdAliases() []string { return gCreateAlias } @@ -75,59 +74,83 @@ func (c *instanceCreateCmd) cmdRun(_ *cobra.Command, _ []string) error { //nolin var ( singleUseSSHPrivateKey *rsa.PrivateKey singleUseSSHPublicKey ssh.PublicKey - sshKey *egoscale.SSHKey ) + ctx := gContext + client, err := switchClientZoneV3(ctx, globalstate.EgoscaleV3Client, c.Zone) + if err != nil { + return err + } - instance := &egoscale.Instance{ - DiskSize: &c.DiskSize, - IPv6Enabled: &c.IPv6, - Labels: func() (v *map[string]string) { - if len(c.Labels) > 0 { - return &c.Labels - } - return - }(), - Name: &c.Name, - SSHKey: utils.NonEmptyStringPtr(c.SSHKey), + var sshKeys []v3.SSHKey + for _, sshkeyName := range c.SSHKeys { + sshKeys = append(sshKeys, v3.SSHKey{Name: sshkeyName}) } - if c.PrivateInstance { - t := "none" - instance.PublicIPAssignment = &t + instanceReq := v3.CreateInstanceRequest{ + DiskSize: c.DiskSize, + Ipv6Enabled: &c.IPv6, + Labels: c.Labels, + Name: c.Name, + SSHKeys: sshKeys, } - ctx := exoapi.WithEndpoint(gContext, exoapi.NewReqEndpoint(account.CurrentAccount.Environment, c.Zone)) + if c.PrivateInstance { + instanceReq.PublicIPAssignment = v3.PublicIPAssignmentNone + } if l := len(c.AntiAffinityGroups); l > 0 { - antiAffinityGroupIDs := make([]string, l) + instanceReq.AntiAffinityGroups = make([]v3.AntiAffinityGroup, l) + af, err := client.ListAntiAffinityGroups(ctx) + if err != nil { + return fmt.Errorf("error listing Anti-Affinity Group: %w", err) + } for i := range c.AntiAffinityGroups { - antiAffinityGroup, err := globalstate.EgoscaleClient.FindAntiAffinityGroup(ctx, c.Zone, c.AntiAffinityGroups[i]) + antiAffinityGroup, err := af.FindAntiAffinityGroup(c.AntiAffinityGroups[i]) if err != nil { return fmt.Errorf("error retrieving Anti-Affinity Group: %w", err) } - antiAffinityGroupIDs[i] = *antiAffinityGroup.ID + instanceReq.AntiAffinityGroups[i] = v3.AntiAffinityGroup{ID: antiAffinityGroup.ID} } - instance.AntiAffinityGroupIDs = &antiAffinityGroupIDs } if c.DeployTarget != "" { - deployTarget, err := globalstate.EgoscaleClient.FindDeployTarget(ctx, c.Zone, c.DeployTarget) + targets, err := client.ListDeployTargets(ctx) + if err != nil { + return fmt.Errorf("error listing Deploy Target: %w", err) + } + deployTarget, err := targets.FindDeployTarget(c.DeployTarget) if err != nil { return fmt.Errorf("error retrieving Deploy Target: %w", err) } - instance.DeployTargetID = deployTarget.ID + instanceReq.DeployTarget = &deployTarget } - instanceType, err := globalstate.EgoscaleClient.FindInstanceType(ctx, c.Zone, c.InstanceType) + instanceTypes, err := client.ListInstanceTypes(ctx) if err != nil { - return fmt.Errorf("error retrieving instance type: %w", err) + return fmt.Errorf("error listing instance type: %w", err) } - instance.InstanceTypeID = instanceType.ID - privateNetworks := make([]*egoscale.PrivateNetwork, len(c.PrivateNetworks)) + // c.InstanceType is never empty + instanceType := utils.ParseInstanceType(c.InstanceType) + for i, it := range instanceTypes.InstanceTypes { + if it.Family == instanceType.Family && it.Size == instanceType.Size { + instanceReq.InstanceType = &instanceTypes.InstanceTypes[i] + break + } + } + if instanceReq.InstanceType == nil { + return fmt.Errorf("error retrieving instance type %s: not found", c.InstanceType) + } + + privateNetworks := make([]v3.PrivateNetwork, len(c.PrivateNetworks)) if l := len(c.PrivateNetworks); l > 0 { + pNetworks, err := client.ListPrivateNetworks(ctx) + if err != nil { + return fmt.Errorf("error listing Private Network: %w", err) + } + for i := range c.PrivateNetworks { - privateNetwork, err := globalstate.EgoscaleClient.FindPrivateNetwork(ctx, c.Zone, c.PrivateNetworks[i]) + privateNetwork, err := pNetworks.FindPrivateNetwork(c.PrivateNetworks[i]) if err != nil { return fmt.Errorf("error retrieving Private Network: %w", err) } @@ -136,23 +159,26 @@ func (c *instanceCreateCmd) cmdRun(_ *cobra.Command, _ []string) error { //nolin } if l := len(c.SecurityGroups); l > 0 { - securityGroupIDs := make([]string, l) + sgs, err := client.ListSecurityGroups(ctx) + if err != nil { + return fmt.Errorf("error listing Security Group: %w", err) + } + instanceReq.SecurityGroups = make([]v3.SecurityGroup, l) for i := range c.SecurityGroups { - securityGroup, err := globalstate.EgoscaleClient.FindSecurityGroup(ctx, c.Zone, c.SecurityGroups[i]) + securityGroup, err := sgs.FindSecurityGroup(c.SecurityGroups[i]) if err != nil { return fmt.Errorf("error retrieving Security Group: %w", err) } - securityGroupIDs[i] = *securityGroup.ID + instanceReq.SecurityGroups[i] = v3.SecurityGroup{ID: securityGroup.ID} } - instance.SecurityGroupIDs = &securityGroupIDs } - if instance.SSHKey == nil && account.CurrentAccount.DefaultSSHKey != "" { - instance.SSHKey = &account.CurrentAccount.DefaultSSHKey + if instanceReq.SSHKeys == nil && account.CurrentAccount.DefaultSSHKey != "" { + instanceReq.SSHKeys = []v3.SSHKey{{Name: account.CurrentAccount.DefaultSSHKey}} } // Generating a single-use SSH key pair for this instance. - if instance.SSHKey == nil { + if instanceReq.SSHKeys == nil { singleUseSSHPrivateKey, err = rsa.GenerateKey(rand.Reader, 2048) if err != nil { return fmt.Errorf("error generating SSH private key: %w", err) @@ -166,20 +192,30 @@ func (c *instanceCreateCmd) cmdRun(_ *cobra.Command, _ []string) error { //nolin return fmt.Errorf("error generating SSH public key: %w", err) } - sshKey, err = globalstate.EgoscaleClient.RegisterSSHKey( + sshKeyName := fmt.Sprintf("%s-%d", c.Name, time.Now().Unix()) + op, err := client.RegisterSSHKey( ctx, - c.Zone, - fmt.Sprintf("%s-%d", c.Name, time.Now().Unix()), - string(ssh.MarshalAuthorizedKey(singleUseSSHPublicKey)), + v3.RegisterSSHKeyRequest{ + Name: sshKeyName, + PublicKey: string(ssh.MarshalAuthorizedKey(singleUseSSHPublicKey)), + }, ) if err != nil { return fmt.Errorf("error registering SSH key: %w", err) } + _, err = client.Wait(ctx, op, v3.OperationStateSuccess) + if err != nil { + return fmt.Errorf("error wait registering SSH key: %w", err) + } - instance.SSHKey = sshKey.Name + instanceReq.SSHKeys = []v3.SSHKey{{Name: sshKeyName}} } - template, err := globalstate.EgoscaleClient.FindTemplate(ctx, c.Zone, c.Template, c.TemplateVisibility) + templates, err := client.ListTemplates(ctx, v3.ListTemplatesWithVisibility(v3.ListTemplatesVisibility(c.TemplateVisibility))) + if err != nil { + return fmt.Errorf("error listing template with visibility %q: %w", c.TemplateVisibility, err) + } + template, err := templates.FindTemplate(c.Template) if err != nil { return fmt.Errorf( "no template %q found with visibility %s in zone %s", @@ -188,24 +224,41 @@ func (c *instanceCreateCmd) cmdRun(_ *cobra.Command, _ []string) error { //nolin c.Zone, ) } - instance.TemplateID = template.ID + instanceReq.Template = &template if c.CloudInitFile != "" { userData, err := userdata.GetUserDataFromFile(c.CloudInitFile, c.CloudInitCompress) if err != nil { return fmt.Errorf("error parsing cloud-init user data: %w", err) } - instance.UserData = &userData + instanceReq.UserData = userData } + var instanceID v3.UUID decorateAsyncOperation(fmt.Sprintf("Creating instance %q...", c.Name), func() { - instance, err = globalstate.EgoscaleClient.CreateInstance(ctx, c.Zone, instance) + var op *v3.Operation + op, err = client.CreateInstance(ctx, instanceReq) if err != nil { return } + op, err = client.Wait(ctx, op, v3.OperationStateSuccess) + if err != nil { + return + } + if op.Reference != nil { + instanceID = op.Reference.ID + } + for _, p := range privateNetworks { - if err = globalstate.EgoscaleClient.AttachInstanceToPrivateNetwork(ctx, c.Zone, instance, p); err != nil { + op, err = client.AttachInstanceToPrivateNetwork(ctx, p.ID, v3.AttachInstanceToPrivateNetworkRequest{ + Instance: &v3.AttachInstanceToPrivateNetworkRequestInstance{ID: instanceID}, + }) + if err != nil { + return + } + _, err = client.Wait(ctx, op) + if err != nil { return } } @@ -215,7 +268,7 @@ func (c *instanceCreateCmd) cmdRun(_ *cobra.Command, _ []string) error { //nolin } if singleUseSSHPrivateKey != nil { - privateKeyFilePath := exossh.GetInstanceSSHKeyPath(*instance.ID) + privateKeyFilePath := exossh.GetInstanceSSHKeyPath(instanceID.String()) if err = os.MkdirAll(path.Dir(privateKeyFilePath), 0o700); err != nil { return fmt.Errorf("error writing SSH private key file: %w", err) @@ -232,16 +285,22 @@ func (c *instanceCreateCmd) cmdRun(_ *cobra.Command, _ []string) error { //nolin return fmt.Errorf("error writing SSH private key file: %w", err) } - if err = globalstate.EgoscaleClient.DeleteSSHKey(ctx, c.Zone, sshKey); err != nil { + op, err := client.DeleteSSHKey(ctx, instanceReq.SSHKeys[0].Name) + if err != nil { return fmt.Errorf("error deleting SSH key: %w", err) } + _, err = client.Wait(ctx, op, v3.OperationStateSuccess) + if err != nil { + return fmt.Errorf("error wait deleting SSH key: %w", err) + } } if !globalstate.Quiet { return (&instanceShowCmd{ cliCommandSettings: c.cliCommandSettings, - Instance: *instance.ID, - Zone: c.Zone, + Instance: instanceID.String(), + // TODO migrate instanceShow to v3 to pass v3.ZoneName + Zone: string(c.Zone), }).cmdRun(nil, nil) } diff --git a/go.mod b/go.mod index 8d711474..0329b951 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/aws/smithy-go v1.1.0 github.com/dustin/go-humanize v1.0.1 github.com/exoscale/egoscale v0.102.4 - github.com/exoscale/egoscale/v3 v3.1.0 + github.com/exoscale/egoscale/v3 v3.1.1 github.com/exoscale/openapi-cli-generator v1.1.0 github.com/fatih/camelcase v1.0.0 github.com/google/uuid v1.4.0 diff --git a/go.sum b/go.sum index 00994b31..62f86e80 100644 --- a/go.sum +++ b/go.sum @@ -197,8 +197,8 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws= github.com/exoscale/egoscale v0.102.4 h1:GBKsZMIOzwBfSu+4ZmWka3Ejf2JLiaBDHp4CQUgvp2E= github.com/exoscale/egoscale v0.102.4/go.mod h1:ROSmPtle0wvf91iLZb09++N/9BH2Jo9XxIpAEumvocA= -github.com/exoscale/egoscale/v3 v3.1.0 h1:8MSA0j4TZbUiE6iIzTmoY0URa3RoGGuHhX5oamCql4o= -github.com/exoscale/egoscale/v3 v3.1.0/go.mod h1:lPsza7G+giSxdzvzaHSEcjEAYz/YTiu2bEEha9KVAc4= +github.com/exoscale/egoscale/v3 v3.1.1 h1:NwTlXE2sKe2kBWm+c3bsHV+aWDFiEJ9JQpS6X3j4wbc= +github.com/exoscale/egoscale/v3 v3.1.1/go.mod h1:lPsza7G+giSxdzvzaHSEcjEAYz/YTiu2bEEha9KVAc4= github.com/exoscale/openapi-cli-generator v1.1.0 h1:fYjmPqHR5vxlOBrbvde7eo7bISNQIFxsGn4A5/acwKA= github.com/exoscale/openapi-cli-generator v1.1.0/go.mod h1:TZBnbT7f3hJ5ImyUphJwRM+X5xF/zCQZ6o8a42gQeTs= github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8= diff --git a/utils/utils.go b/utils/utils.go index 404bdb50..734b66dd 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -12,6 +12,7 @@ import ( "github.com/hashicorp/go-multierror" exoapi "github.com/exoscale/egoscale/v2/api" + v3 "github.com/exoscale/egoscale/v3" "github.com/exoscale/cli/pkg/account" v2 "github.com/exoscale/egoscale/v2" @@ -236,3 +237,22 @@ func ForEachZone(zones []string, f func(zone string) error) error { return meg.Wait().ErrorOrNil() } + +// ParseInstanceType returns an v3.InstanceType with family and name. +func ParseInstanceType(instanceType string) v3.InstanceType { + var typeFamily, typeSize string + + parts := strings.SplitN(instanceType, ".", 2) + if l := len(parts); l > 0 { + if l == 1 { + typeFamily, typeSize = "standard", strings.ToLower(parts[0]) + } else { + typeFamily, typeSize = strings.ToLower(parts[0]), strings.ToLower(parts[1]) + } + } + + return v3.InstanceType{ + Family: v3.InstanceTypeFamily(typeFamily), + Size: v3.InstanceTypeSize(typeSize), + } +} diff --git a/utils/utils_test.go b/utils/utils_test.go new file mode 100644 index 00000000..2141e2b4 --- /dev/null +++ b/utils/utils_test.go @@ -0,0 +1,30 @@ +package utils + +import ( + "testing" + + v3 "github.com/exoscale/egoscale/v3" + "github.com/stretchr/testify/assert" +) + +func TestParseInstanceType(t *testing.T) { + testCases := []struct { + instanceType string + expectedFamily v3.InstanceTypeFamily + expectedSize v3.InstanceTypeSize + }{ + {"standard.large", v3.InstanceTypeFamily("standard"), v3.InstanceTypeSize("large")}, + {"gpu2.mega", v3.InstanceTypeFamily("gpu2"), v3.InstanceTypeSize("mega")}, + {"colossus", v3.InstanceTypeFamily("standard"), v3.InstanceTypeSize("colossus")}, + {"", v3.InstanceTypeFamily("standard"), v3.InstanceTypeSize("")}, + {"invalid-format", v3.InstanceTypeFamily("standard"), v3.InstanceTypeSize("invalid-format")}, + } + + for _, tc := range testCases { + t.Run(tc.instanceType, func(t *testing.T) { + result := ParseInstanceType(tc.instanceType) + assert.Equal(t, tc.expectedFamily, result.Family) + assert.Equal(t, tc.expectedSize, result.Size) + }) + } +} diff --git a/vendor/github.com/exoscale/egoscale/v3/client.go b/vendor/github.com/exoscale/egoscale/v3/client.go index 912271d5..804a3780 100644 --- a/vendor/github.com/exoscale/egoscale/v3/client.go +++ b/vendor/github.com/exoscale/egoscale/v3/client.go @@ -46,13 +46,13 @@ func (c Client) GetZoneAPIEndpoint(ctx context.Context, zoneName ZoneName) (Endp if err != nil { return "", fmt.Errorf("get zone api endpoint: %w", err) } - for _, zone := range resp.Zones { - if zone.Name == zoneName { - return zone.APIEndpoint, nil - } + + zone, err := resp.FindZone(string(zoneName)) + if err != nil { + return "", fmt.Errorf("get zone api endpoint: %w", err) } - return "", fmt.Errorf("get zone api endpoint: zone name %s not found", zoneName) + return zone.APIEndpoint, nil } // Client represents an Exoscale API client. diff --git a/vendor/github.com/exoscale/egoscale/v3/operations.go b/vendor/github.com/exoscale/egoscale/v3/operations.go index df0383c0..8a103355 100644 --- a/vendor/github.com/exoscale/egoscale/v3/operations.go +++ b/vendor/github.com/exoscale/egoscale/v3/operations.go @@ -16,10 +16,10 @@ type ListAntiAffinityGroupsResponse struct { AntiAffinityGroups []AntiAffinityGroup `json:"anti-affinity-groups,omitempty"` } -// FindAntiAffinityGroup attempts to find an AntiAffinityGroup by name or ID. +// FindAntiAffinityGroup attempts to find an AntiAffinityGroup by nameOrID. func (l ListAntiAffinityGroupsResponse) FindAntiAffinityGroup(nameOrID string) (AntiAffinityGroup, error) { for i, elem := range l.AntiAffinityGroups { - if elem.Name == nameOrID || elem.ID.String() == nameOrID { + if string(elem.Name) == nameOrID || elem.ID.String() == nameOrID { return l.AntiAffinityGroups[i], nil } } @@ -217,6 +217,17 @@ type ListAPIKeysResponse struct { APIKeys []IAMAPIKey `json:"api-keys,omitempty"` } +// FindIAMAPIKey attempts to find an IAMAPIKey by name. +func (l ListAPIKeysResponse) FindIAMAPIKey(name string) (IAMAPIKey, error) { + for i, elem := range l.APIKeys { + if string(elem.Name) == name { + return l.APIKeys[i], nil + } + } + + return IAMAPIKey{}, fmt.Errorf("%q not found in ListAPIKeysResponse: %w", name, ErrNotFound) +} + // List API keys func (c Client) ListAPIKeys(ctx context.Context) (*ListAPIKeysResponse, error) { path := "/api-key" @@ -407,10 +418,10 @@ type ListBlockStorageVolumesResponse struct { BlockStorageVolumes []BlockStorageVolume `json:"block-storage-volumes,omitempty"` } -// FindBlockStorageVolume attempts to find an BlockStorageVolume by name or ID. +// FindBlockStorageVolume attempts to find an BlockStorageVolume by nameOrID. func (l ListBlockStorageVolumesResponse) FindBlockStorageVolume(nameOrID string) (BlockStorageVolume, error) { for i, elem := range l.BlockStorageVolumes { - if elem.Name == nameOrID || elem.ID.String() == nameOrID { + if string(elem.Name) == nameOrID || elem.ID.String() == nameOrID { return l.BlockStorageVolumes[i], nil } } @@ -542,10 +553,10 @@ type ListBlockStorageSnapshotsResponse struct { BlockStorageSnapshots []BlockStorageSnapshot `json:"block-storage-snapshots,omitempty"` } -// FindBlockStorageSnapshot attempts to find an BlockStorageSnapshot by name or ID. +// FindBlockStorageSnapshot attempts to find an BlockStorageSnapshot by nameOrID. func (l ListBlockStorageSnapshotsResponse) FindBlockStorageSnapshot(nameOrID string) (BlockStorageSnapshot, error) { for i, elem := range l.BlockStorageSnapshots { - if elem.Name == nameOrID || elem.ID.String() == nameOrID { + if string(elem.Name) == nameOrID || elem.ID.String() == nameOrID { return l.BlockStorageSnapshots[i], nil } } @@ -5691,6 +5702,17 @@ type ListDBAASServicesResponse struct { DBAASServices []DBAASServiceCommon `json:"dbaas-services,omitempty"` } +// FindDBAASServiceCommon attempts to find an DBAASServiceCommon by name. +func (l ListDBAASServicesResponse) FindDBAASServiceCommon(name string) (DBAASServiceCommon, error) { + for i, elem := range l.DBAASServices { + if string(elem.Name) == name { + return l.DBAASServices[i], nil + } + } + + return DBAASServiceCommon{}, fmt.Errorf("%q not found in ListDBAASServicesResponse: %w", name, ErrNotFound) +} + // List DBaaS services func (c Client) ListDBAASServices(ctx context.Context) (*ListDBAASServicesResponse, error) { path := "/dbaas-service" @@ -5865,6 +5887,17 @@ type ListDBAASServiceTypesResponse struct { DBAASServiceTypes []DBAASServiceType `json:"dbaas-service-types,omitempty"` } +// FindDBAASServiceType attempts to find an DBAASServiceType by name. +func (l ListDBAASServiceTypesResponse) FindDBAASServiceType(name string) (DBAASServiceType, error) { + for i, elem := range l.DBAASServiceTypes { + if string(elem.Name) == name { + return l.DBAASServiceTypes[i], nil + } + } + + return DBAASServiceType{}, fmt.Errorf("%q not found in ListDBAASServiceTypesResponse: %w", name, ErrNotFound) +} + // List available service types for DBaaS func (c Client) ListDBAASServiceTypes(ctx context.Context) (*ListDBAASServiceTypesResponse, error) { path := "/dbaas-service-type" @@ -6519,10 +6552,10 @@ type ListDeployTargetsResponse struct { DeployTargets []DeployTarget `json:"deploy-targets,omitempty"` } -// FindDeployTarget attempts to find an DeployTarget by name or ID. +// FindDeployTarget attempts to find an DeployTarget by nameOrID. func (l ListDeployTargetsResponse) FindDeployTarget(nameOrID string) (DeployTarget, error) { for i, elem := range l.DeployTargets { - if elem.Name == nameOrID || elem.ID.String() == nameOrID { + if string(elem.Name) == nameOrID || elem.ID.String() == nameOrID { return l.DeployTargets[i], nil } } @@ -6620,6 +6653,17 @@ type ListDNSDomainsResponse struct { DNSDomains []DNSDomain `json:"dns-domains,omitempty"` } +// FindDNSDomain attempts to find an DNSDomain by ID. +func (l ListDNSDomainsResponse) FindDNSDomain(ID string) (DNSDomain, error) { + for i, elem := range l.DNSDomains { + if elem.ID.String() == ID { + return l.DNSDomains[i], nil + } + } + + return DNSDomain{}, fmt.Errorf("%q not found in ListDNSDomainsResponse: %w", ID, ErrNotFound) +} + // List DNS domains func (c Client) ListDNSDomains(ctx context.Context) (*ListDNSDomainsResponse, error) { path := "/dns-domain" @@ -6723,10 +6767,10 @@ type ListDNSDomainRecordsResponse struct { DNSDomainRecords []DNSDomainRecord `json:"dns-domain-records,omitempty"` } -// FindDNSDomainRecord attempts to find an DNSDomainRecord by name or ID. +// FindDNSDomainRecord attempts to find an DNSDomainRecord by nameOrID. func (l ListDNSDomainRecordsResponse) FindDNSDomainRecord(nameOrID string) (DNSDomainRecord, error) { for i, elem := range l.DNSDomainRecords { - if elem.Name == nameOrID || elem.ID.String() == nameOrID { + if string(elem.Name) == nameOrID || elem.ID.String() == nameOrID { return l.DNSDomainRecords[i], nil } } @@ -7144,6 +7188,17 @@ type ListElasticIPSResponse struct { ElasticIPS []ElasticIP `json:"elastic-ips,omitempty"` } +// FindElasticIP attempts to find an ElasticIP by ID. +func (l ListElasticIPSResponse) FindElasticIP(ID string) (ElasticIP, error) { + for i, elem := range l.ElasticIPS { + if elem.ID.String() == ID { + return l.ElasticIPS[i], nil + } + } + + return ElasticIP{}, fmt.Errorf("%q not found in ListElasticIPSResponse: %w", ID, ErrNotFound) +} + // List Elastic IPs func (c Client) ListElasticIPS(ctx context.Context) (*ListElasticIPSResponse, error) { path := "/elastic-ip" @@ -7721,10 +7776,10 @@ type ListIAMRolesResponse struct { IAMRoles []IAMRole `json:"iam-roles,omitempty"` } -// FindIAMRole attempts to find an IAMRole by name or ID. +// FindIAMRole attempts to find an IAMRole by nameOrID. func (l ListIAMRolesResponse) FindIAMRole(nameOrID string) (IAMRole, error) { for i, elem := range l.IAMRoles { - if elem.Name == nameOrID || elem.ID.String() == nameOrID { + if string(elem.Name) == nameOrID || elem.ID.String() == nameOrID { return l.IAMRoles[i], nil } } @@ -8078,10 +8133,10 @@ type ListInstancesResponse struct { Instances []ListInstancesResponseInstances `json:"instances,omitempty"` } -// FindListInstancesResponseInstances attempts to find an ListInstancesResponseInstances by name or ID. +// FindListInstancesResponseInstances attempts to find an ListInstancesResponseInstances by nameOrID. func (l ListInstancesResponse) FindListInstancesResponseInstances(nameOrID string) (ListInstancesResponseInstances, error) { for i, elem := range l.Instances { - if elem.Name == nameOrID || elem.ID.String() == nameOrID { + if string(elem.Name) == nameOrID || elem.ID.String() == nameOrID { return l.Instances[i], nil } } @@ -8249,10 +8304,10 @@ type ListInstancePoolsResponse struct { InstancePools []InstancePool `json:"instance-pools,omitempty"` } -// FindInstancePool attempts to find an InstancePool by name or ID. +// FindInstancePool attempts to find an InstancePool by nameOrID. func (l ListInstancePoolsResponse) FindInstancePool(nameOrID string) (InstancePool, error) { for i, elem := range l.InstancePools { - if elem.Name == nameOrID || elem.ID.String() == nameOrID { + if string(elem.Name) == nameOrID || elem.ID.String() == nameOrID { return l.InstancePools[i], nil } } @@ -8753,6 +8808,17 @@ type ListInstanceTypesResponse struct { InstanceTypes []InstanceType `json:"instance-types,omitempty"` } +// FindInstanceType attempts to find an InstanceType by ID. +func (l ListInstanceTypesResponse) FindInstanceType(ID string) (InstanceType, error) { + for i, elem := range l.InstanceTypes { + if elem.ID.String() == ID { + return l.InstanceTypes[i], nil + } + } + + return InstanceType{}, fmt.Errorf("%q not found in ListInstanceTypesResponse: %w", ID, ErrNotFound) +} + // List Compute instance Types func (c Client) ListInstanceTypes(ctx context.Context) (*ListInstanceTypesResponse, error) { path := "/instance-type" @@ -9627,10 +9693,10 @@ type ListLoadBalancersResponse struct { LoadBalancers []LoadBalancer `json:"load-balancers,omitempty"` } -// FindLoadBalancer attempts to find an LoadBalancer by name or ID. +// FindLoadBalancer attempts to find an LoadBalancer by nameOrID. func (l ListLoadBalancersResponse) FindLoadBalancer(nameOrID string) (LoadBalancer, error) { for i, elem := range l.LoadBalancers { - if elem.Name == nameOrID || elem.ID.String() == nameOrID { + if string(elem.Name) == nameOrID || elem.ID.String() == nameOrID { return l.LoadBalancers[i], nil } } @@ -10324,10 +10390,10 @@ type ListPrivateNetworksResponse struct { PrivateNetworks []PrivateNetwork `json:"private-networks,omitempty"` } -// FindPrivateNetwork attempts to find an PrivateNetwork by name or ID. +// FindPrivateNetwork attempts to find an PrivateNetwork by nameOrID. func (l ListPrivateNetworksResponse) FindPrivateNetwork(nameOrID string) (PrivateNetwork, error) { for i, elem := range l.PrivateNetworks { - if elem.Name == nameOrID || elem.ID.String() == nameOrID { + if string(elem.Name) == nameOrID || elem.ID.String() == nameOrID { return l.PrivateNetworks[i], nil } } @@ -11194,10 +11260,10 @@ type ListSecurityGroupsResponse struct { SecurityGroups []SecurityGroup `json:"security-groups,omitempty"` } -// FindSecurityGroup attempts to find an SecurityGroup by name or ID. +// FindSecurityGroup attempts to find an SecurityGroup by nameOrID. func (l ListSecurityGroupsResponse) FindSecurityGroup(nameOrID string) (SecurityGroup, error) { for i, elem := range l.SecurityGroups { - if elem.Name == nameOrID || elem.ID.String() == nameOrID { + if string(elem.Name) == nameOrID || elem.ID.String() == nameOrID { return l.SecurityGroups[i], nil } } @@ -11779,10 +11845,10 @@ type ListSKSClustersResponse struct { SKSClusters []SKSCluster `json:"sks-clusters,omitempty"` } -// FindSKSCluster attempts to find an SKSCluster by name or ID. +// FindSKSCluster attempts to find an SKSCluster by nameOrID. func (l ListSKSClustersResponse) FindSKSCluster(nameOrID string) (SKSCluster, error) { for i, elem := range l.SKSClusters { - if elem.Name == nameOrID || elem.ID.String() == nameOrID { + if string(elem.Name) == nameOrID || elem.ID.String() == nameOrID { return l.SKSClusters[i], nil } } @@ -12966,10 +13032,10 @@ type ListSnapshotsResponse struct { Snapshots []Snapshot `json:"snapshots,omitempty"` } -// FindSnapshot attempts to find an Snapshot by name or ID. +// FindSnapshot attempts to find an Snapshot by nameOrID. func (l ListSnapshotsResponse) FindSnapshot(nameOrID string) (Snapshot, error) { for i, elem := range l.Snapshots { - if elem.Name == nameOrID || elem.ID.String() == nameOrID { + if string(elem.Name) == nameOrID || elem.ID.String() == nameOrID { return l.Snapshots[i], nil } } @@ -13216,6 +13282,17 @@ type ListSOSBucketsUsageResponse struct { SOSBucketsUsage []SOSBucketUsage `json:"sos-buckets-usage,omitempty"` } +// FindSOSBucketUsage attempts to find an SOSBucketUsage by name. +func (l ListSOSBucketsUsageResponse) FindSOSBucketUsage(name string) (SOSBucketUsage, error) { + for i, elem := range l.SOSBucketsUsage { + if string(elem.Name) == name { + return l.SOSBucketsUsage[i], nil + } + } + + return SOSBucketUsage{}, fmt.Errorf("%q not found in ListSOSBucketsUsageResponse: %w", name, ErrNotFound) +} + // List SOS Buckets Usage func (c Client) ListSOSBucketsUsage(ctx context.Context) (*ListSOSBucketsUsageResponse, error) { path := "/sos-buckets-usage" @@ -13326,6 +13403,17 @@ type ListSSHKeysResponse struct { SSHKeys []SSHKey `json:"ssh-keys,omitempty"` } +// FindSSHKey attempts to find an SSHKey by name. +func (l ListSSHKeysResponse) FindSSHKey(name string) (SSHKey, error) { + for i, elem := range l.SSHKeys { + if string(elem.Name) == name { + return l.SSHKeys[i], nil + } + } + + return SSHKey{}, fmt.Errorf("%q not found in ListSSHKeysResponse: %w", name, ErrNotFound) +} + // List SSH keys func (c Client) ListSSHKeys(ctx context.Context) (*ListSSHKeysResponse, error) { path := "/ssh-key" @@ -13516,10 +13604,10 @@ type ListTemplatesResponse struct { Templates []Template `json:"templates,omitempty"` } -// FindTemplate attempts to find an Template by name or ID. +// FindTemplate attempts to find an Template by nameOrID. func (l ListTemplatesResponse) FindTemplate(nameOrID string) (Template, error) { for i, elem := range l.Templates { - if elem.Name == nameOrID || elem.ID.String() == nameOrID { + if string(elem.Name) == nameOrID || elem.ID.String() == nameOrID { return l.Templates[i], nil } } @@ -13885,6 +13973,17 @@ type ListZonesResponse struct { Zones []Zone `json:"zones,omitempty"` } +// FindZone attempts to find an Zone by name. +func (l ListZonesResponse) FindZone(name string) (Zone, error) { + for i, elem := range l.Zones { + if string(elem.Name) == name { + return l.Zones[i], nil + } + } + + return Zone{}, fmt.Errorf("%q not found in ListZonesResponse: %w", name, ErrNotFound) +} + // List Zones func (c Client) ListZones(ctx context.Context) (*ListZonesResponse, error) { path := "/zone" diff --git a/vendor/modules.txt b/vendor/modules.txt index da07b495..ea3975ac 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -220,7 +220,7 @@ github.com/exoscale/egoscale/v2 github.com/exoscale/egoscale/v2/api github.com/exoscale/egoscale/v2/oapi github.com/exoscale/egoscale/version -# github.com/exoscale/egoscale/v3 v3.1.0 +# github.com/exoscale/egoscale/v3 v3.1.1 ## explicit; go 1.22 github.com/exoscale/egoscale/v3 github.com/exoscale/egoscale/v3/credentials