diff --git a/pkg/model/bootstrapscript.go b/pkg/model/bootstrapscript.go index daef1adae9..5f495e72b1 100644 --- a/pkg/model/bootstrapscript.go +++ b/pkg/model/bootstrapscript.go @@ -26,6 +26,7 @@ import ( "strconv" "strings" + "k8s.io/apimachinery/pkg/util/errors" "k8s.io/klog/v2" "k8s.io/kops/pkg/apis/kops/model" "k8s.io/kops/upup/pkg/fi/utils" @@ -210,35 +211,40 @@ func (b *BootstrapScript) buildEnvironmentVariables(cluster *kops.Cluster) (map[ } if cluster.Spec.GetCloudProvider() == kops.CloudProviderScaleway { + errList := []error(nil) region, err := scw.ParseRegion(os.Getenv("SCW_DEFAULT_REGION")) if err != nil { - return nil, fmt.Errorf("error parsing SCW_DEFAULT_REGION: %w", err) + errList = append(errList, fmt.Errorf("error parsing SCW_DEFAULT_REGION: %w", err)) } - env["SCW_DEFAULT_REGION"] = string(region) - zone, err := scw.ParseZone(os.Getenv("SCW_DEFAULT_ZONE")) if err != nil { - return nil, fmt.Errorf("error parsing SCW_DEFAULT_ZONE: %w", err) + errList = append(errList, fmt.Errorf("error parsing SCW_DEFAULT_ZONE: %w", err)) } - env["SCW_DEFAULT_ZONE"] = string(zone) + // We make sure that the credentials env vars are defined scwAccessKey := os.Getenv("SCW_ACCESS_KEY") if scwAccessKey == "" { - return nil, fmt.Errorf("SCW_ACCESS_KEY has to be set as an environment variable") + errList = append(errList, fmt.Errorf("SCW_ACCESS_KEY has to be set as an environment variable")) } - env["SCW_ACCESS_KEY"] = scwAccessKey - scwSecretKey := os.Getenv("SCW_SECRET_KEY") if scwSecretKey == "" { - return nil, fmt.Errorf("SCW_SECRET_KEY has to be set as an environment variable") + errList = append(errList, fmt.Errorf("SCW_SECRET_KEY has to be set as an environment variable")) } - env["SCW_SECRET_KEY"] = scwSecretKey - scwProjectID := os.Getenv("SCW_DEFAULT_PROJECT_ID") if scwProjectID == "" { - return nil, fmt.Errorf("SCW_DEFAULT_PROJECT_ID has to be set as an environment variable") + errList = append(errList, fmt.Errorf("SCW_DEFAULT_PROJECT_ID has to be set as an environment variable")) } + + // In theory all these variables will have been checked in NewScwCloud already + if len(errList) != 0 { + return nil, errors.NewAggregate(errList) + } + + env["SCW_DEFAULT_REGION"] = string(region) + env["SCW_DEFAULT_ZONE"] = string(zone) + env["SCW_ACCESS_KEY"] = scwAccessKey + env["SCW_SECRET_KEY"] = scwSecretKey env["SCW_DEFAULT_PROJECT_ID"] = scwProjectID } diff --git a/pkg/model/scalewaymodel/context.go b/pkg/model/scalewaymodel/context.go index d67b89c98e..799f0b1e0b 100644 --- a/pkg/model/scalewaymodel/context.go +++ b/pkg/model/scalewaymodel/context.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package scalewaymodel import ( diff --git a/pkg/model/scalewaymodel/instances.go b/pkg/model/scalewaymodel/instances.go index ceaa04f23b..015756c0a9 100644 --- a/pkg/model/scalewaymodel/instances.go +++ b/pkg/model/scalewaymodel/instances.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package scalewaymodel import ( diff --git a/pkg/model/scalewaymodel/sshkey.go b/pkg/model/scalewaymodel/sshkey.go index 071e00d140..d430223a52 100644 --- a/pkg/model/scalewaymodel/sshkey.go +++ b/pkg/model/scalewaymodel/sshkey.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package scalewaymodel import ( diff --git a/upup/pkg/fi/cloudup/scaleway/OWNERS b/upup/pkg/fi/cloudup/scaleway/OWNERS index 40b07322c1..d5b228c526 100644 --- a/upup/pkg/fi/cloudup/scaleway/OWNERS +++ b/upup/pkg/fi/cloudup/scaleway/OWNERS @@ -1,3 +1,3 @@ # See the OWNERS docs at https://go.k8s.io/owners labels: -- area/provider/scaleway \ No newline at end of file +- area/provider/scaleway diff --git a/upup/pkg/fi/cloudup/scaleway/api_target.go b/upup/pkg/fi/cloudup/scaleway/api_target.go index f7fc2939cc..8ce126add5 100644 --- a/upup/pkg/fi/cloudup/scaleway/api_target.go +++ b/upup/pkg/fi/cloudup/scaleway/api_target.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package scaleway import "k8s.io/kops/upup/pkg/fi" diff --git a/upup/pkg/fi/cloudup/scaleway/cloud.go b/upup/pkg/fi/cloudup/scaleway/cloud.go index afcbf77375..e6d319fbe4 100644 --- a/upup/pkg/fi/cloudup/scaleway/cloud.go +++ b/upup/pkg/fi/cloudup/scaleway/cloud.go @@ -25,6 +25,7 @@ import ( "github.com/scaleway/scaleway-sdk-go/api/instance/v1" "github.com/scaleway/scaleway-sdk-go/scw" v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/util/errors" "k8s.io/klog/v2" kopsv "k8s.io/kops" "k8s.io/kops/dnsprovider/pkg/dnsprovider" @@ -44,23 +45,23 @@ const ( type ScwCloud interface { fi.Cloud + ClusterName(tags []string) string + DNS() (dnsprovider.Interface, error) + ProviderID() kops.CloudProviderID Region() string Zone() string - ProviderID() kops.CloudProviderID - DNS() (dnsprovider.Interface, error) - ClusterName(tags []string) string AccountService() *account.API InstanceService() *instance.API - GetApiIngressStatus(cluster *kops.Cluster) ([]fi.ApiIngressStatus, error) - FindClusterStatus(cluster *kops.Cluster) (*kops.ClusterStatus, error) - GetCloudGroups(cluster *kops.Cluster, instancegroups []*kops.InstanceGroup, warnUnmatched bool, nodes []v1.Node) (map[string]*cloudinstances.CloudInstanceGroup, error) DeleteGroup(group *cloudinstances.CloudInstanceGroup) error - FindVPCInfo(id string) (*fi.VPCInfo, error) - DetachInstance(instance *cloudinstances.CloudInstance) error - DeregisterInstance(instance *cloudinstances.CloudInstance) error DeleteInstance(i *cloudinstances.CloudInstance) error + DeregisterInstance(instance *cloudinstances.CloudInstance) error + DetachInstance(instance *cloudinstances.CloudInstance) error + FindClusterStatus(cluster *kops.Cluster) (*kops.ClusterStatus, error) + FindVPCInfo(id string) (*fi.VPCInfo, error) + GetApiIngressStatus(cluster *kops.Cluster) ([]fi.ApiIngressStatus, error) + GetCloudGroups(cluster *kops.Cluster, instancegroups []*kops.InstanceGroup, warnUnmatched bool, nodes []v1.Node) (map[string]*cloudinstances.CloudInstanceGroup, error) GetClusterServers(clusterName string, serverName *string) ([]*instance.Server, error) GetClusterVolumes(clusterName string) ([]*instance.Volume, error) @@ -86,31 +87,37 @@ type scwCloudImplementation struct { // NewScwCloud returns a Cloud with a Scaleway Client using the env vars SCW_ACCESS_KEY, SCW_SECRET_KEY, // SCW_DEFAULT_PROJECT_ID, SCW_DEFAULT_REGION and SCW_DEFAULT_ZONE func NewScwCloud(tags map[string]string) (ScwCloud, error) { + errList := []error(nil) + region, err := scw.ParseRegion(os.Getenv("SCW_DEFAULT_REGION")) if err != nil { - return nil, fmt.Errorf("error parsing SCW_DEFAULT_REGION: %w", err) + errList = append(errList, fmt.Errorf("error parsing SCW_DEFAULT_REGION: %w", err)) } zone, err := scw.ParseZone(os.Getenv("SCW_DEFAULT_ZONE")) if err != nil { - return nil, fmt.Errorf("error parsing SCW_DEFAULT_ZONE: %w", err) + errList = append(errList, fmt.Errorf("error parsing SCW_DEFAULT_ZONE: %w", err)) } // We make sure that the credentials env vars are defined scwAccessKey := os.Getenv("SCW_ACCESS_KEY") if scwAccessKey == "" { - return nil, fmt.Errorf("SCW_ACCESS_KEY has to be set as an environment variable") + errList = append(errList, fmt.Errorf("SCW_ACCESS_KEY has to be set as an environment variable")) } scwSecretKey := os.Getenv("SCW_SECRET_KEY") if scwSecretKey == "" { - return nil, fmt.Errorf("SCW_SECRET_KEY has to be set as an environment variable") + errList = append(errList, fmt.Errorf("SCW_SECRET_KEY has to be set as an environment variable")) } scwProjectID := os.Getenv("SCW_DEFAULT_PROJECT_ID") if scwProjectID == "" { - return nil, fmt.Errorf("SCW_DEFAULT_PROJECT_ID has to be set as an environment variable") + errList = append(errList, fmt.Errorf("SCW_DEFAULT_PROJECT_ID has to be set as an environment variable")) + } + + if len(errList) != 0 { + return nil, errors.NewAggregate(errList) } scwClient, err := scw.NewClient( - scw.WithUserAgent("kubernetes-kops/"+kopsv.Version), + scw.WithUserAgent(KopsUserAgentPrefix+kopsv.Version), scw.WithEnv(), ) if err != nil { @@ -127,14 +134,6 @@ func NewScwCloud(tags map[string]string) (ScwCloud, error) { }, nil } -func (s *scwCloudImplementation) Region() string { - return string(s.region) -} - -func (s *scwCloudImplementation) Zone() string { - return string(s.zone) -} - func (s *scwCloudImplementation) ClusterName(tags []string) string { for _, tag := range tags { if strings.HasPrefix(tag, TagClusterName) { @@ -144,13 +143,21 @@ func (s *scwCloudImplementation) ClusterName(tags []string) string { return "" } +func (s *scwCloudImplementation) DNS() (dnsprovider.Interface, error) { + klog.V(8).Infof("Scaleway DNS is not implemented yet") + return nil, fmt.Errorf("DNS is not implemented yet for Scaleway") +} + func (s *scwCloudImplementation) ProviderID() kops.CloudProviderID { return kops.CloudProviderScaleway } -func (s *scwCloudImplementation) DNS() (dnsprovider.Interface, error) { - //TODO(Mia-Cross) implement me - panic("Scaleway doesn't have a DNS yet") +func (s *scwCloudImplementation) Region() string { + return string(s.region) +} + +func (s *scwCloudImplementation) Zone() string { + return string(s.zone) } func (s *scwCloudImplementation) AccountService() *account.API { @@ -161,10 +168,15 @@ func (s *scwCloudImplementation) InstanceService() *instance.API { return s.instanceAPI } -// FindVPCInfo is not implemented yet, it's only here to satisfy the fi.Cloud interface -func (s *scwCloudImplementation) FindVPCInfo(id string) (*fi.VPCInfo, error) { - klog.V(8).Info("FindVPCInfo is not implemented yet for Scaleway") - return nil, fmt.Errorf("scaleway cloud provider does not support VPC at this time") +func (s *scwCloudImplementation) DeleteGroup(group *cloudinstances.CloudInstanceGroup) error { + toDelete := append(group.NeedUpdate, group.Ready...) + for _, cloudInstance := range toDelete { + err := s.DeleteInstance(cloudInstance) + if err != nil { + return fmt.Errorf("error deleting group %q: %w", group.HumanName, err) + } + } + return nil } func (s *scwCloudImplementation) DeleteInstance(i *cloudinstances.CloudInstance) error { @@ -189,24 +201,30 @@ func (s *scwCloudImplementation) DeleteInstance(i *cloudinstances.CloudInstance) } func (s *scwCloudImplementation) DeregisterInstance(i *cloudinstances.CloudInstance) error { - //TODO(Mia-Cross) implement me - panic("implement me") -} - -func (s *scwCloudImplementation) DeleteGroup(group *cloudinstances.CloudInstanceGroup) error { - toDelete := append(group.NeedUpdate, group.Ready...) - for _, cloudInstance := range toDelete { - err := s.DeleteInstance(cloudInstance) - if err != nil { - return fmt.Errorf("error deleting group %q: %w", group.HumanName, err) - } - } - return nil + klog.V(8).Infof("Scaleway DeregisterInstance is not implemented yet") + return fmt.Errorf("DeregisterInstance is not implemented yet for Scaleway") } func (s *scwCloudImplementation) DetachInstance(i *cloudinstances.CloudInstance) error { - //TODO(Mia-Cross) implement me - panic("implement me") + klog.V(8).Infof("Scaleway DetachInstance is not implemented yet") + return fmt.Errorf("DetachInstance is not implemented yet for Scaleway") +} + +// FindClusterStatus was used before etcd-manager to check the etcd cluster status and prevent unsupported changes. +func (s *scwCloudImplementation) FindClusterStatus(cluster *kops.Cluster) (*kops.ClusterStatus, error) { + klog.V(8).Info("Scaleway FindClusterStatus is not implemented") + return nil, nil +} + +// FindVPCInfo is not implemented yet, it's only here to satisfy the fi.Cloud interface +func (s *scwCloudImplementation) FindVPCInfo(id string) (*fi.VPCInfo, error) { + klog.V(8).Info("Scaleway doesn't have a VPC yet so FindVPCInfo is not implemented") + return nil, fmt.Errorf("FindVPCInfo is not implemented yet for Scaleway") +} + +func (s *scwCloudImplementation) GetApiIngressStatus(cluster *kops.Cluster) ([]fi.ApiIngressStatus, error) { + klog.V(8).Info("Scaleway doesn't have load-balancers yet so GetApiIngressStatus is not implemented") + return nil, fmt.Errorf("GetApiIngressStatus is not implemented yet for Scaleway") } func (s *scwCloudImplementation) GetCloudGroups(cluster *kops.Cluster, instancegroups []*kops.InstanceGroup, warnUnmatched bool, nodes []v1.Node) (map[string]*cloudinstances.CloudInstanceGroup, error) { @@ -219,24 +237,18 @@ func (s *scwCloudImplementation) GetCloudGroups(cluster *kops.Cluster, instanceg return nil, fmt.Errorf("failed to find server groups: %w", err) } - for igName, serverGroup := range serverGroups { - var instanceGroup *kops.InstanceGroup - for _, ig := range instancegroups { - if igName == ig.Name { - instanceGroup = ig - break - } - } - if instanceGroup == nil { + for _, ig := range instancegroups { + serverGroup, ok := serverGroups[ig.Name] + if !ok { if warnUnmatched { - klog.Warningf("Server group %q has no corresponding instance group", igName) + klog.Warningf("Server group %q has no corresponding instance group", ig.Name) } continue } - groups[instanceGroup.Name], err = buildCloudGroup(instanceGroup, serverGroup, nodeMap) + groups[ig.Name], err = buildCloudGroup(ig, serverGroup, nodeMap) if err != nil { - return nil, fmt.Errorf("failed to build cloud group for instance group %q: %w", instanceGroup.Name, err) + return nil, fmt.Errorf("failed to build cloud group for instance group %q: %w", ig.Name, err) } } @@ -295,16 +307,6 @@ func buildCloudGroup(ig *kops.InstanceGroup, sg []*instance.Server, nodeMap map[ return cloudInstanceGroup, nil } -func (s *scwCloudImplementation) FindClusterStatus(cluster *kops.Cluster) (*kops.ClusterStatus, error) { - klog.V(8).Info("FindClusterStatus is not implemented yet for Scaleway") - return nil, nil -} - -func (s *scwCloudImplementation) GetApiIngressStatus(cluster *kops.Cluster) ([]fi.ApiIngressStatus, error) { - //TODO(Mia-Cross) implement me - panic("implement me") -} - func (s *scwCloudImplementation) GetClusterServers(clusterName string, serverName *string) ([]*instance.Server, error) { request := &instance.ListServersRequest{ Zone: s.zone, @@ -372,14 +374,12 @@ func (s *scwCloudImplementation) DeleteServer(server *instance.Server) error { if err != nil { return fmt.Errorf("delete server %s: error deleting instance: %w", server.ID, err) } - for { - _, err := s.instanceAPI.GetServer(&instance.GetServerRequest{ - Zone: s.zone, - ServerID: server.ID, - }) - if is404Error(err) { - break - } + _, err = s.instanceAPI.WaitForServer(&instance.WaitForServerRequest{ + ServerID: server.ID, + Zone: s.zone, + }) + if !is404Error(err) { + return fmt.Errorf("delete server %s: error waiting for instance after deletion: %w", server.ID, err) } // We delete the volumes that were attached to the server (including etcd volumes) @@ -404,5 +404,14 @@ func (s *scwCloudImplementation) DeleteVolume(volume *instance.Volume) error { if err != nil { return fmt.Errorf("failed to delete volume %s: %w", volume.ID, err) } + + _, err = s.instanceAPI.WaitForVolume(&instance.WaitForVolumeRequest{ + VolumeID: volume.ID, + Zone: s.zone, + }) + if !is404Error(err) { + return fmt.Errorf("delete server %s: error waiting for volume after deletion: %w", volume.ID, err) + } + return nil } diff --git a/upup/pkg/fi/cloudup/scaleway/utils.go b/upup/pkg/fi/cloudup/scaleway/utils.go index 2fc319d37f..1ab8d88115 100644 --- a/upup/pkg/fi/cloudup/scaleway/utils.go +++ b/upup/pkg/fi/cloudup/scaleway/utils.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package scaleway import ( diff --git a/upup/pkg/fi/cloudup/scalewaytasks/OWNERS b/upup/pkg/fi/cloudup/scalewaytasks/OWNERS index 40b07322c1..d5b228c526 100644 --- a/upup/pkg/fi/cloudup/scalewaytasks/OWNERS +++ b/upup/pkg/fi/cloudup/scalewaytasks/OWNERS @@ -1,3 +1,3 @@ # See the OWNERS docs at https://go.k8s.io/owners labels: -- area/provider/scaleway \ No newline at end of file +- area/provider/scaleway diff --git a/upup/pkg/fi/cloudup/scalewaytasks/instance.go b/upup/pkg/fi/cloudup/scalewaytasks/instance.go index df30a67966..a326ff369a 100644 --- a/upup/pkg/fi/cloudup/scalewaytasks/instance.go +++ b/upup/pkg/fi/cloudup/scalewaytasks/instance.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package scalewaytasks import ( @@ -59,59 +75,37 @@ func (s *Instance) Run(c *fi.Context) error { return fi.DefaultDeltaRunMethod(s, c) } -func (_ *Instance) RenderScw(c *fi.Context, a, e, changes *Instance) error { +func (_ *Instance) RenderScw(c *fi.Context, actual, expected, changes *Instance) error { cloud := c.Cloud.(scaleway.ScwCloud) instanceService := cloud.InstanceService() - zone := scw.Zone(fi.StringValue(e.Zone)) + zone := scw.Zone(fi.StringValue(expected.Zone)) - userData, err := fi.ResourceAsBytes(*e.UserData) + userData, err := fi.ResourceAsBytes(*expected.UserData) if err != nil { return fmt.Errorf("error rendering instances: %w", err) } - var newInstanceCount int - if a == nil { - newInstanceCount = e.Count - } else { - expectedCount := e.Count - actualCount := a.Count - - if expectedCount == actualCount { + newInstanceCount := expected.Count + if actual != nil { + if expected.Count == actual.Count { return nil } - - if actualCount > expectedCount { - igInstances, err := cloud.GetClusterServers(cloud.ClusterName(a.Tags), a.Name) - if err != nil { - return fmt.Errorf("error deleting instance: %w", err) - } - for _, igInstance := range igInstances { - err = cloud.DeleteServer(igInstance) - if err != nil { - return fmt.Errorf("error deleting instance of group %s: %w", igInstance.Name, err) - } - actualCount-- - if expectedCount == actualCount { - break - } - } - } - - newInstanceCount = expectedCount - actualCount + newInstanceCount = expected.Count - actual.Count } + // If newInstanceCount > 0, we need to create new instances for this group for i := 0; i < newInstanceCount; i++ { // We create the instance srv, err := instanceService.CreateServer(&instance.CreateServerRequest{ Zone: zone, - Name: fi.StringValue(e.Name), - CommercialType: fi.StringValue(e.CommercialType), - Image: fi.StringValue(e.Image), - Tags: e.Tags, + Name: fi.StringValue(expected.Name), + CommercialType: fi.StringValue(expected.CommercialType), + Image: fi.StringValue(expected.Image), + Tags: expected.Tags, }) if err != nil { - return fmt.Errorf("error creating instance of group %q: %w", fi.StringValue(e.Name), err) + return fmt.Errorf("error creating instance of group %q: %w", fi.StringValue(expected.Name), err) } // We wait for the instance to be ready @@ -120,7 +114,7 @@ func (_ *Instance) RenderScw(c *fi.Context, a, e, changes *Instance) error { Zone: zone, }) if err != nil { - return fmt.Errorf("error waiting for instance %s of group %q: %w", srv.Server.ID, fi.StringValue(e.Name), err) + return fmt.Errorf("error waiting for instance %s of group %q: %w", srv.Server.ID, fi.StringValue(expected.Name), err) } // We load the cloud-init script in the instance user data @@ -131,7 +125,7 @@ func (_ *Instance) RenderScw(c *fi.Context, a, e, changes *Instance) error { Content: bytes.NewBuffer(userData), }) if err != nil { - return fmt.Errorf("error setting 'cloud-init' in user-data for instance %s of group %q: %w", srv.Server.ID, fi.StringValue(e.Name), err) + return fmt.Errorf("error setting 'cloud-init' in user-data for instance %s of group %q: %w", srv.Server.ID, fi.StringValue(expected.Name), err) } // We start the instance @@ -141,7 +135,7 @@ func (_ *Instance) RenderScw(c *fi.Context, a, e, changes *Instance) error { Action: instance.ServerActionPoweron, }) if err != nil { - return fmt.Errorf("error powering on instance %s of group %q: %w", srv.Server.ID, fi.StringValue(e.Name), err) + return fmt.Errorf("error powering on instance %s of group %q: %w", srv.Server.ID, fi.StringValue(expected.Name), err) } // We wait for the instance to be ready @@ -150,15 +144,31 @@ func (_ *Instance) RenderScw(c *fi.Context, a, e, changes *Instance) error { Zone: zone, }) if err != nil { - return fmt.Errorf("error waiting for instance %s of group %q: %w", srv.Server.ID, fi.StringValue(e.Name), err) + return fmt.Errorf("error waiting for instance %s of group %q: %w", srv.Server.ID, fi.StringValue(expected.Name), err) + } + } + + // If newInstanceCount < 0, we need to delete instances of this group + for i := 0; i > expected.Count; i-- { + + igInstances, err := cloud.GetClusterServers(cloud.ClusterName(actual.Tags), actual.Name) + if err != nil { + return fmt.Errorf("error deleting instance: %w", err) + } + + for _, igInstance := range igInstances { + err = cloud.DeleteServer(igInstance) + if err != nil { + return fmt.Errorf("error deleting instance of group %s: %w", igInstance.Name, err) + } } } return nil } -func (_ *Instance) CheckChanges(a, e, changes *Instance) error { - if a != nil { +func (_ *Instance) CheckChanges(actual, expected, changes *Instance) error { + if actual != nil { if changes.Name != nil { return fi.CannotChangeField("Name") } @@ -172,16 +182,16 @@ func (_ *Instance) CheckChanges(a, e, changes *Instance) error { return fi.CannotChangeField("Image") } } else { - if e.Name == nil { + if expected.Name == nil { return fi.RequiredField("Name") } - if e.Zone == nil { + if expected.Zone == nil { return fi.RequiredField("Zone") } - if e.CommercialType == nil { + if expected.CommercialType == nil { return fi.RequiredField("CommercialType") } - if e.Image == nil { + if expected.Image == nil { return fi.RequiredField("Image") } } diff --git a/upup/pkg/fi/cloudup/scalewaytasks/instance_fitask.go b/upup/pkg/fi/cloudup/scalewaytasks/instance_fitask.go new file mode 100644 index 0000000000..a28163cc7c --- /dev/null +++ b/upup/pkg/fi/cloudup/scalewaytasks/instance_fitask.go @@ -0,0 +1,52 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by fitask. DO NOT EDIT. + +package scalewaytasks + +import ( + "k8s.io/kops/upup/pkg/fi" +) + +// Instance + +var _ fi.HasLifecycle = &Instance{} + +// GetLifecycle returns the Lifecycle of the object, implementing fi.HasLifecycle +func (o *Instance) GetLifecycle() fi.Lifecycle { + return o.Lifecycle +} + +// SetLifecycle sets the Lifecycle of the object, implementing fi.SetLifecycle +func (o *Instance) SetLifecycle(lifecycle fi.Lifecycle) { + o.Lifecycle = lifecycle +} + +var _ fi.HasName = &Instance{} + +// GetName returns the Name of the object, implementing fi.HasName +func (o *Instance) GetName() *string { + return o.Name +} + +// String is the stringer function for the task, producing readable output using fi.TaskAsString +func (o *Instance) String() string { + return fi.TaskAsString(o) +} diff --git a/upup/pkg/fi/cloudup/scalewaytasks/sshkey.go b/upup/pkg/fi/cloudup/scalewaytasks/sshkey.go index cc5d4e21be..0d29f94384 100644 --- a/upup/pkg/fi/cloudup/scalewaytasks/sshkey.go +++ b/upup/pkg/fi/cloudup/scalewaytasks/sshkey.go @@ -1,3 +1,19 @@ +/* +Copyright 2022 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package scalewaytasks import ( @@ -46,25 +62,25 @@ func (s *SSHKey) Find(c *fi.Context) (*SSHKey, error) { klog.V(2).Infof("found matching SSH key named %q", *s.Name) k := keysResp.SSHKeys[0] - actual := &SSHKey{ + sshKey := &SSHKey{ ID: fi.String(k.ID), Name: fi.String(k.Name), KeyPairFingerPrint: fi.String(k.Fingerprint), } // Avoid spurious changes - if strings.Contains(fi.StringValue(actual.KeyPairFingerPrint), fi.StringValue(s.KeyPairFingerPrint)) { + if strings.Contains(fi.StringValue(sshKey.KeyPairFingerPrint), fi.StringValue(s.KeyPairFingerPrint)) { klog.V(2).Infof("SSH key fingerprints match; assuming public keys match") - actual.PublicKey = s.PublicKey - actual.KeyPairFingerPrint = s.KeyPairFingerPrint + sshKey.PublicKey = s.PublicKey + sshKey.KeyPairFingerPrint = s.KeyPairFingerPrint } else { - klog.V(2).Infof("Computed SSH key fingerprint mismatch: %q %q", fi.StringValue(s.KeyPairFingerPrint), fi.StringValue(actual.KeyPairFingerPrint)) + klog.V(2).Infof("Computed SSH key fingerprint mismatch: %q %q", fi.StringValue(s.KeyPairFingerPrint), fi.StringValue(sshKey.KeyPairFingerPrint)) } // Ignore "system" fields - actual.Lifecycle = s.Lifecycle + sshKey.Lifecycle = s.Lifecycle - return actual, nil + return sshKey, nil } func (s *SSHKey) Run(c *fi.Context) error { @@ -84,8 +100,8 @@ func (s *SSHKey) Run(c *fi.Context) error { return fi.DefaultDeltaRunMethod(s, c) } -func (s *SSHKey) CheckChanges(a, e, changes *SSHKey) error { - if a != nil { +func (s *SSHKey) CheckChanges(actual, expected, changes *SSHKey) error { + if actual != nil { if changes.Name != nil { return fi.CannotChangeField("Name") } @@ -93,12 +109,12 @@ func (s *SSHKey) CheckChanges(a, e, changes *SSHKey) error { return nil } -func (*SSHKey) RenderScw(c *fi.Context, a, e, changes *SSHKey) error { +func (*SSHKey) RenderScw(c *fi.Context, actual, expected, changes *SSHKey) error { cloud := c.Cloud.(scaleway.ScwCloud) - if a == nil { + if actual == nil { - name := fi.StringValue(e.Name) + name := fi.StringValue(expected.Name) if name == "" { return fi.RequiredField("Name") } @@ -107,8 +123,8 @@ func (*SSHKey) RenderScw(c *fi.Context, a, e, changes *SSHKey) error { keyArgs := &account.CreateSSHKeyRequest{ Name: name, } - if e.PublicKey != nil { - d, err := fi.ResourceAsString(*e.PublicKey) + if expected.PublicKey != nil { + d, err := fi.ResourceAsString(*expected.PublicKey) if err != nil { return fmt.Errorf("error rendering SSH public key: %w", err) } @@ -119,14 +135,14 @@ func (*SSHKey) RenderScw(c *fi.Context, a, e, changes *SSHKey) error { if err != nil { return fmt.Errorf("error creating SSH keypair: %w", err) } - e.KeyPairFingerPrint = fi.String(key.Fingerprint) + expected.KeyPairFingerPrint = fi.String(key.Fingerprint) klog.V(2).Infof("Created a new SSH keypair, id=%q fingerprint=%q", key.ID, key.Fingerprint) return nil } - e.KeyPairFingerPrint = a.KeyPairFingerPrint - klog.V(2).Infof("Using an existing SSH keypair, fingerprint=%q", fi.StringValue(e.KeyPairFingerPrint)) + expected.KeyPairFingerPrint = actual.KeyPairFingerPrint + klog.V(2).Infof("Using an existing SSH keypair, fingerprint=%q", fi.StringValue(expected.KeyPairFingerPrint)) return nil } diff --git a/upup/pkg/fi/cloudup/scalewaytasks/sshkey_fitask.go b/upup/pkg/fi/cloudup/scalewaytasks/sshkey_fitask.go new file mode 100644 index 0000000000..1e58baeca2 --- /dev/null +++ b/upup/pkg/fi/cloudup/scalewaytasks/sshkey_fitask.go @@ -0,0 +1,52 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by fitask. DO NOT EDIT. + +package scalewaytasks + +import ( + "k8s.io/kops/upup/pkg/fi" +) + +// SSHKey + +var _ fi.HasLifecycle = &SSHKey{} + +// GetLifecycle returns the Lifecycle of the object, implementing fi.HasLifecycle +func (o *SSHKey) GetLifecycle() fi.Lifecycle { + return o.Lifecycle +} + +// SetLifecycle sets the Lifecycle of the object, implementing fi.SetLifecycle +func (o *SSHKey) SetLifecycle(lifecycle fi.Lifecycle) { + o.Lifecycle = lifecycle +} + +var _ fi.HasName = &SSHKey{} + +// GetName returns the Name of the object, implementing fi.HasName +func (o *SSHKey) GetName() *string { + return o.Name +} + +// String is the stringer function for the task, producing readable output using fi.TaskAsString +func (o *SSHKey) String() string { + return fi.TaskAsString(o) +}