codegen + updated headers + refactoring after reviews

This commit is contained in:
Leïla MARABESE 2022-10-19 09:55:58 +02:00
parent 284e98288e
commit f7f89080c6
13 changed files with 376 additions and 151 deletions

View File

@ -26,6 +26,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"k8s.io/apimachinery/pkg/util/errors"
"k8s.io/klog/v2" "k8s.io/klog/v2"
"k8s.io/kops/pkg/apis/kops/model" "k8s.io/kops/pkg/apis/kops/model"
"k8s.io/kops/upup/pkg/fi/utils" "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 { if cluster.Spec.GetCloudProvider() == kops.CloudProviderScaleway {
errList := []error(nil)
region, err := scw.ParseRegion(os.Getenv("SCW_DEFAULT_REGION")) region, err := scw.ParseRegion(os.Getenv("SCW_DEFAULT_REGION"))
if err != nil { 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")) zone, err := scw.ParseZone(os.Getenv("SCW_DEFAULT_ZONE"))
if err != nil { 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") scwAccessKey := os.Getenv("SCW_ACCESS_KEY")
if scwAccessKey == "" { 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") scwSecretKey := os.Getenv("SCW_SECRET_KEY")
if scwSecretKey == "" { 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") scwProjectID := os.Getenv("SCW_DEFAULT_PROJECT_ID")
if scwProjectID == "" { 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 env["SCW_DEFAULT_PROJECT_ID"] = scwProjectID
} }

View File

@ -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 package scalewaymodel
import ( import (

View File

@ -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 package scalewaymodel
import ( import (

View File

@ -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 package scalewaymodel
import ( import (

View File

@ -1,3 +1,3 @@
# See the OWNERS docs at https://go.k8s.io/owners # See the OWNERS docs at https://go.k8s.io/owners
labels: labels:
- area/provider/scaleway - area/provider/scaleway

View File

@ -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 package scaleway
import "k8s.io/kops/upup/pkg/fi" import "k8s.io/kops/upup/pkg/fi"

View File

@ -25,6 +25,7 @@ import (
"github.com/scaleway/scaleway-sdk-go/api/instance/v1" "github.com/scaleway/scaleway-sdk-go/api/instance/v1"
"github.com/scaleway/scaleway-sdk-go/scw" "github.com/scaleway/scaleway-sdk-go/scw"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/errors"
"k8s.io/klog/v2" "k8s.io/klog/v2"
kopsv "k8s.io/kops" kopsv "k8s.io/kops"
"k8s.io/kops/dnsprovider/pkg/dnsprovider" "k8s.io/kops/dnsprovider/pkg/dnsprovider"
@ -44,23 +45,23 @@ const (
type ScwCloud interface { type ScwCloud interface {
fi.Cloud fi.Cloud
ClusterName(tags []string) string
DNS() (dnsprovider.Interface, error)
ProviderID() kops.CloudProviderID
Region() string Region() string
Zone() string Zone() string
ProviderID() kops.CloudProviderID
DNS() (dnsprovider.Interface, error)
ClusterName(tags []string) string
AccountService() *account.API AccountService() *account.API
InstanceService() *instance.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 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 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) GetClusterServers(clusterName string, serverName *string) ([]*instance.Server, error)
GetClusterVolumes(clusterName string) ([]*instance.Volume, 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, // 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 // SCW_DEFAULT_PROJECT_ID, SCW_DEFAULT_REGION and SCW_DEFAULT_ZONE
func NewScwCloud(tags map[string]string) (ScwCloud, error) { func NewScwCloud(tags map[string]string) (ScwCloud, error) {
errList := []error(nil)
region, err := scw.ParseRegion(os.Getenv("SCW_DEFAULT_REGION")) region, err := scw.ParseRegion(os.Getenv("SCW_DEFAULT_REGION"))
if err != nil { 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")) zone, err := scw.ParseZone(os.Getenv("SCW_DEFAULT_ZONE"))
if err != nil { 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 // We make sure that the credentials env vars are defined
scwAccessKey := os.Getenv("SCW_ACCESS_KEY") scwAccessKey := os.Getenv("SCW_ACCESS_KEY")
if scwAccessKey == "" { 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") scwSecretKey := os.Getenv("SCW_SECRET_KEY")
if scwSecretKey == "" { 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") scwProjectID := os.Getenv("SCW_DEFAULT_PROJECT_ID")
if scwProjectID == "" { 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( scwClient, err := scw.NewClient(
scw.WithUserAgent("kubernetes-kops/"+kopsv.Version), scw.WithUserAgent(KopsUserAgentPrefix+kopsv.Version),
scw.WithEnv(), scw.WithEnv(),
) )
if err != nil { if err != nil {
@ -127,14 +134,6 @@ func NewScwCloud(tags map[string]string) (ScwCloud, error) {
}, nil }, 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 { func (s *scwCloudImplementation) ClusterName(tags []string) string {
for _, tag := range tags { for _, tag := range tags {
if strings.HasPrefix(tag, TagClusterName) { if strings.HasPrefix(tag, TagClusterName) {
@ -144,13 +143,21 @@ func (s *scwCloudImplementation) ClusterName(tags []string) string {
return "" 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 { func (s *scwCloudImplementation) ProviderID() kops.CloudProviderID {
return kops.CloudProviderScaleway return kops.CloudProviderScaleway
} }
func (s *scwCloudImplementation) DNS() (dnsprovider.Interface, error) { func (s *scwCloudImplementation) Region() string {
//TODO(Mia-Cross) implement me return string(s.region)
panic("Scaleway doesn't have a DNS yet") }
func (s *scwCloudImplementation) Zone() string {
return string(s.zone)
} }
func (s *scwCloudImplementation) AccountService() *account.API { func (s *scwCloudImplementation) AccountService() *account.API {
@ -161,10 +168,15 @@ func (s *scwCloudImplementation) InstanceService() *instance.API {
return s.instanceAPI return s.instanceAPI
} }
// FindVPCInfo is not implemented yet, it's only here to satisfy the fi.Cloud interface func (s *scwCloudImplementation) DeleteGroup(group *cloudinstances.CloudInstanceGroup) error {
func (s *scwCloudImplementation) FindVPCInfo(id string) (*fi.VPCInfo, error) { toDelete := append(group.NeedUpdate, group.Ready...)
klog.V(8).Info("FindVPCInfo is not implemented yet for Scaleway") for _, cloudInstance := range toDelete {
return nil, fmt.Errorf("scaleway cloud provider does not support VPC at this time") 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 { 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 { func (s *scwCloudImplementation) DeregisterInstance(i *cloudinstances.CloudInstance) error {
//TODO(Mia-Cross) implement me klog.V(8).Infof("Scaleway DeregisterInstance is not implemented yet")
panic("implement me") return fmt.Errorf("DeregisterInstance is not implemented yet for Scaleway")
}
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) DetachInstance(i *cloudinstances.CloudInstance) error { func (s *scwCloudImplementation) DetachInstance(i *cloudinstances.CloudInstance) error {
//TODO(Mia-Cross) implement me klog.V(8).Infof("Scaleway DetachInstance is not implemented yet")
panic("implement me") 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) { 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) return nil, fmt.Errorf("failed to find server groups: %w", err)
} }
for igName, serverGroup := range serverGroups { for _, ig := range instancegroups {
var instanceGroup *kops.InstanceGroup serverGroup, ok := serverGroups[ig.Name]
for _, ig := range instancegroups { if !ok {
if igName == ig.Name {
instanceGroup = ig
break
}
}
if instanceGroup == nil {
if warnUnmatched { 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 continue
} }
groups[instanceGroup.Name], err = buildCloudGroup(instanceGroup, serverGroup, nodeMap) groups[ig.Name], err = buildCloudGroup(ig, serverGroup, nodeMap)
if err != nil { 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 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) { func (s *scwCloudImplementation) GetClusterServers(clusterName string, serverName *string) ([]*instance.Server, error) {
request := &instance.ListServersRequest{ request := &instance.ListServersRequest{
Zone: s.zone, Zone: s.zone,
@ -372,14 +374,12 @@ func (s *scwCloudImplementation) DeleteServer(server *instance.Server) error {
if err != nil { if err != nil {
return fmt.Errorf("delete server %s: error deleting instance: %w", server.ID, err) return fmt.Errorf("delete server %s: error deleting instance: %w", server.ID, err)
} }
for { _, err = s.instanceAPI.WaitForServer(&instance.WaitForServerRequest{
_, err := s.instanceAPI.GetServer(&instance.GetServerRequest{ ServerID: server.ID,
Zone: s.zone, Zone: s.zone,
ServerID: server.ID, })
}) if !is404Error(err) {
if is404Error(err) { return fmt.Errorf("delete server %s: error waiting for instance after deletion: %w", server.ID, err)
break
}
} }
// We delete the volumes that were attached to the server (including etcd volumes) // 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 { if err != nil {
return fmt.Errorf("failed to delete volume %s: %w", volume.ID, err) 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 return nil
} }

View File

@ -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 package scaleway
import ( import (

View File

@ -1,3 +1,3 @@
# See the OWNERS docs at https://go.k8s.io/owners # See the OWNERS docs at https://go.k8s.io/owners
labels: labels:
- area/provider/scaleway - area/provider/scaleway

View File

@ -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 package scalewaytasks
import ( import (
@ -59,59 +75,37 @@ func (s *Instance) Run(c *fi.Context) error {
return fi.DefaultDeltaRunMethod(s, c) 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) cloud := c.Cloud.(scaleway.ScwCloud)
instanceService := cloud.InstanceService() 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 { if err != nil {
return fmt.Errorf("error rendering instances: %w", err) return fmt.Errorf("error rendering instances: %w", err)
} }
var newInstanceCount int newInstanceCount := expected.Count
if a == nil { if actual != nil {
newInstanceCount = e.Count if expected.Count == actual.Count {
} else {
expectedCount := e.Count
actualCount := a.Count
if expectedCount == actualCount {
return nil return nil
} }
newInstanceCount = expected.Count - actual.Count
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
} }
// If newInstanceCount > 0, we need to create new instances for this group
for i := 0; i < newInstanceCount; i++ { for i := 0; i < newInstanceCount; i++ {
// We create the instance // We create the instance
srv, err := instanceService.CreateServer(&instance.CreateServerRequest{ srv, err := instanceService.CreateServer(&instance.CreateServerRequest{
Zone: zone, Zone: zone,
Name: fi.StringValue(e.Name), Name: fi.StringValue(expected.Name),
CommercialType: fi.StringValue(e.CommercialType), CommercialType: fi.StringValue(expected.CommercialType),
Image: fi.StringValue(e.Image), Image: fi.StringValue(expected.Image),
Tags: e.Tags, Tags: expected.Tags,
}) })
if err != nil { 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 // 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, Zone: zone,
}) })
if err != nil { 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 // 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), Content: bytes.NewBuffer(userData),
}) })
if err != nil { 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 // We start the instance
@ -141,7 +135,7 @@ func (_ *Instance) RenderScw(c *fi.Context, a, e, changes *Instance) error {
Action: instance.ServerActionPoweron, Action: instance.ServerActionPoweron,
}) })
if err != nil { 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 // 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, Zone: zone,
}) })
if err != nil { 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 return nil
} }
func (_ *Instance) CheckChanges(a, e, changes *Instance) error { func (_ *Instance) CheckChanges(actual, expected, changes *Instance) error {
if a != nil { if actual != nil {
if changes.Name != nil { if changes.Name != nil {
return fi.CannotChangeField("Name") return fi.CannotChangeField("Name")
} }
@ -172,16 +182,16 @@ func (_ *Instance) CheckChanges(a, e, changes *Instance) error {
return fi.CannotChangeField("Image") return fi.CannotChangeField("Image")
} }
} else { } else {
if e.Name == nil { if expected.Name == nil {
return fi.RequiredField("Name") return fi.RequiredField("Name")
} }
if e.Zone == nil { if expected.Zone == nil {
return fi.RequiredField("Zone") return fi.RequiredField("Zone")
} }
if e.CommercialType == nil { if expected.CommercialType == nil {
return fi.RequiredField("CommercialType") return fi.RequiredField("CommercialType")
} }
if e.Image == nil { if expected.Image == nil {
return fi.RequiredField("Image") return fi.RequiredField("Image")
} }
} }

View File

@ -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)
}

View File

@ -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 package scalewaytasks
import ( 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) klog.V(2).Infof("found matching SSH key named %q", *s.Name)
k := keysResp.SSHKeys[0] k := keysResp.SSHKeys[0]
actual := &SSHKey{ sshKey := &SSHKey{
ID: fi.String(k.ID), ID: fi.String(k.ID),
Name: fi.String(k.Name), Name: fi.String(k.Name),
KeyPairFingerPrint: fi.String(k.Fingerprint), KeyPairFingerPrint: fi.String(k.Fingerprint),
} }
// Avoid spurious changes // 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") klog.V(2).Infof("SSH key fingerprints match; assuming public keys match")
actual.PublicKey = s.PublicKey sshKey.PublicKey = s.PublicKey
actual.KeyPairFingerPrint = s.KeyPairFingerPrint sshKey.KeyPairFingerPrint = s.KeyPairFingerPrint
} else { } 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 // 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 { 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) return fi.DefaultDeltaRunMethod(s, c)
} }
func (s *SSHKey) CheckChanges(a, e, changes *SSHKey) error { func (s *SSHKey) CheckChanges(actual, expected, changes *SSHKey) error {
if a != nil { if actual != nil {
if changes.Name != nil { if changes.Name != nil {
return fi.CannotChangeField("Name") return fi.CannotChangeField("Name")
} }
@ -93,12 +109,12 @@ func (s *SSHKey) CheckChanges(a, e, changes *SSHKey) error {
return nil 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) cloud := c.Cloud.(scaleway.ScwCloud)
if a == nil { if actual == nil {
name := fi.StringValue(e.Name) name := fi.StringValue(expected.Name)
if name == "" { if name == "" {
return fi.RequiredField("Name") return fi.RequiredField("Name")
} }
@ -107,8 +123,8 @@ func (*SSHKey) RenderScw(c *fi.Context, a, e, changes *SSHKey) error {
keyArgs := &account.CreateSSHKeyRequest{ keyArgs := &account.CreateSSHKeyRequest{
Name: name, Name: name,
} }
if e.PublicKey != nil { if expected.PublicKey != nil {
d, err := fi.ResourceAsString(*e.PublicKey) d, err := fi.ResourceAsString(*expected.PublicKey)
if err != nil { if err != nil {
return fmt.Errorf("error rendering SSH public key: %w", err) 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 { if err != nil {
return fmt.Errorf("error creating SSH keypair: %w", err) 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) klog.V(2).Infof("Created a new SSH keypair, id=%q fingerprint=%q", key.ID, key.Fingerprint)
return nil return nil
} }
e.KeyPairFingerPrint = a.KeyPairFingerPrint expected.KeyPairFingerPrint = actual.KeyPairFingerPrint
klog.V(2).Infof("Using an existing SSH keypair, fingerprint=%q", fi.StringValue(e.KeyPairFingerPrint)) klog.V(2).Infof("Using an existing SSH keypair, fingerprint=%q", fi.StringValue(expected.KeyPairFingerPrint))
return nil return nil
} }

View File

@ -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)
}