tag instances that need to be updated

This commit is contained in:
Leïla MARABESE 2023-08-29 16:47:34 +02:00
parent 9ff54f51cb
commit 8b21162d77
2 changed files with 155 additions and 23 deletions

View File

@ -25,6 +25,7 @@ import (
iam "github.com/scaleway/scaleway-sdk-go/api/iam/v1alpha1"
"github.com/scaleway/scaleway-sdk-go/api/instance/v1"
"github.com/scaleway/scaleway-sdk-go/api/lb/v1"
"github.com/scaleway/scaleway-sdk-go/api/marketplace/v2"
"github.com/scaleway/scaleway-sdk-go/scw"
v1 "k8s.io/api/core/v1"
"k8s.io/klog/v2"
@ -39,8 +40,9 @@ import (
const (
TagClusterName = "noprefix=kops.k8s.io/cluster"
TagInstanceGroup = "noprefix=kops.k8s.io/instance-group"
TagNameRolePrefix = "noprefix=kops.k8s.io/role"
TagNameEtcdClusterPrefix = "noprefix=kops.k8s.io/etcd"
TagNeedsUpdate = "noprefix=kops.k8s.io/needs-update"
TagNameRolePrefix = "noprefix=kops.k8s.io/role"
TagRoleControlPlane = "ControlPlane"
TagRoleWorker = "Node"
KopsUserAgentPrefix = "kubernetes-kops/"
@ -60,6 +62,7 @@ type ScwCloud interface {
IamService() *iam.API
InstanceService() *instance.API
LBService() *lb.ZonedAPI
MarketplaceService() *marketplace.API
DeleteGroup(group *cloudinstances.CloudInstanceGroup) error
DeleteInstance(i *cloudinstances.CloudInstance) error
@ -94,10 +97,11 @@ type scwCloudImplementation struct {
dns dnsprovider.Interface
tags map[string]string
domainAPI *domain.API
iamAPI *iam.API
instanceAPI *instance.API
lbAPI *lb.ZonedAPI
domainAPI *domain.API
iamAPI *iam.API
instanceAPI *instance.API
lbAPI *lb.ZonedAPI
marketplaceAPI *marketplace.API
}
// NewScwCloud returns a Cloud with a Scaleway Client using the env vars SCW_PROFILE or
@ -134,15 +138,16 @@ func NewScwCloud(tags map[string]string) (ScwCloud, error) {
}
return &scwCloudImplementation{
client: scwClient,
region: region,
zone: zone,
dns: dns.NewProvider(domain.NewAPI(scwClient)),
tags: tags,
domainAPI: domain.NewAPI(scwClient),
iamAPI: iam.NewAPI(scwClient),
instanceAPI: instance.NewAPI(scwClient),
lbAPI: lb.NewZonedAPI(scwClient),
client: scwClient,
region: region,
zone: zone,
dns: dns.NewProvider(domain.NewAPI(scwClient)),
tags: tags,
domainAPI: domain.NewAPI(scwClient),
iamAPI: iam.NewAPI(scwClient),
instanceAPI: instance.NewAPI(scwClient),
lbAPI: lb.NewZonedAPI(scwClient),
marketplaceAPI: marketplace.NewAPI(scwClient),
}, nil
}
@ -186,6 +191,10 @@ func (s *scwCloudImplementation) LBService() *lb.ZonedAPI {
return s.lbAPI
}
func (s *scwCloudImplementation) MarketplaceService() *marketplace.API {
return s.marketplaceAPI
}
func (s *scwCloudImplementation) DeleteGroup(group *cloudinstances.CloudInstanceGroup) error {
toDelete := append(group.NeedUpdate, group.Ready...)
for _, cloudInstance := range toDelete {
@ -359,6 +368,11 @@ func buildCloudGroup(ig *kops.InstanceGroup, sg []*instance.Server, nodeMap map[
for _, server := range sg {
status := cloudinstances.CloudInstanceStatusUpToDate
for _, tag := range server.Tags {
if tag == TagNeedsUpdate {
status = cloudinstances.CloudInstanceStatusNeedsUpdate
}
}
cloudInstance, err := cloudInstanceGroup.NewCloudInstance(server.ID, status, nodeMap[server.ID])
if err != nil {
return nil, fmt.Errorf("failed to create cloud instance for server %s(%s): %w", server.Name, server.ID, err)

View File

@ -18,11 +18,14 @@ package scalewaytasks
import (
"bytes"
"crypto/sha256"
"fmt"
"io"
"strings"
"github.com/scaleway/scaleway-sdk-go/api/instance/v1"
"github.com/scaleway/scaleway-sdk-go/api/lb/v1"
"github.com/scaleway/scaleway-sdk-go/api/marketplace/v2"
"github.com/scaleway/scaleway-sdk-go/scw"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/cloudup/scaleway"
@ -43,6 +46,7 @@ type Instance struct {
Image *string
Tags []string
Count int
NeedsUpdate []string
UserData *fi.Resource
LoadBalancer *LoadBalancer
@ -65,6 +69,48 @@ func (s *Instance) Find(c *fi.CloudupContext) (*Instance, error) {
if len(servers) == 0 {
return nil, nil
}
// Check if servers updates are needed
var needsUpdate []string
for _, server := range servers {
// Check if server is already marked as needing update
alreadyTagged := false
for _, tag := range server.Tags {
if tag == scaleway.TagNeedsUpdate {
alreadyTagged = true
}
}
if alreadyTagged == true {
continue
}
// Check commercial type differences
if server.CommercialType != *s.CommercialType {
needsUpdate = append(needsUpdate, server.ID)
continue
}
// Check image differences
diff, err := checkImageDifferences(c, cloud, server, fi.ValueOf(s.Image))
if err != nil {
return nil, fmt.Errorf("checking image differences in server %s (%s): %w", server.Name, server.ID, err)
}
if diff == true {
needsUpdate = append(needsUpdate, server.ID)
continue
}
// Check user-data differences
diff, err = checkUserDataDifferences(c, cloud, server, s.UserData)
if err != nil {
return nil, fmt.Errorf("checking user-data differences in server %s (%s): %w", server.Name, server.ID, err)
}
if diff == true {
needsUpdate = append(needsUpdate, server.ID)
}
}
server := servers[0]
igName := ""
@ -81,16 +127,22 @@ func (s *Instance) Find(c *fi.CloudupContext) (*Instance, error) {
}
}
imageLabel, err := imageLabelFromID(c, cloud, server.Image.ID)
if err != nil {
return nil, err
}
return &Instance{
Name: fi.PtrTo(igName),
Count: len(servers),
Lifecycle: s.Lifecycle,
Zone: fi.PtrTo(server.Zone.String()),
Role: fi.PtrTo(role),
CommercialType: fi.PtrTo(server.CommercialType),
Image: s.Image,
Image: fi.PtrTo(imageLabel),
Tags: server.Tags,
Count: len(servers),
NeedsUpdate: needsUpdate,
UserData: s.UserData,
Lifecycle: s.Lifecycle,
}, nil
}
@ -106,12 +158,6 @@ func (_ *Instance) CheckChanges(actual, expected, changes *Instance) error {
if changes.Zone != nil {
return fi.CannotChangeField("Zone")
}
if changes.CommercialType != nil {
return fi.CannotChangeField("CommercialType")
}
if changes.Image != nil {
return fi.CannotChangeField("Image")
}
} else {
if expected.Name == nil {
return fi.RequiredField("Name")
@ -142,10 +188,31 @@ func (_ *Instance) RenderScw(t *scaleway.ScwAPITarget, actual, expected, changes
newInstanceCount := expected.Count
if actual != nil {
// Add "kops.k8s.io/needs-update" label to servers needing update
for _, serverID := range actual.NeedsUpdate {
server, err := instanceService.GetServer(&instance.GetServerRequest{
Zone: zone,
ServerID: serverID,
})
if err != nil {
return fmt.Errorf("rendering server group: listing existing servers: %w", err)
}
_, err = instanceService.UpdateServer(&instance.UpdateServerRequest{
Zone: zone,
ServerID: serverID,
Tags: scw.StringsPtr(append(server.Server.Tags, scaleway.TagNeedsUpdate)),
})
if err != nil {
return fmt.Errorf("rendering server group: adding update tag to server %q (%s): %w", server.Server.Name, serverID, err)
}
}
if expected.Count == actual.Count {
return nil
}
newInstanceCount = expected.Count - actual.Count
}
// If newInstanceCount > 0, we need to create new instances for this group
@ -359,3 +426,54 @@ func (_ *Instance) RenderTerraform(t *terraform.TerraformTarget, actual, expecte
}
return t.RenderResource("scaleway_instance_server", tfName, tfInstance)
}
func checkImageDifferences(c *fi.CloudupContext, cloud scaleway.ScwCloud, actualServer *instance.Server, expectedImage string) (bool, error) {
localImage, err := cloud.MarketplaceService().GetLocalImageByLabel(&marketplace.GetLocalImageByLabelRequest{
ImageLabel: expectedImage,
Zone: actualServer.Zone,
CommercialType: actualServer.CommercialType,
}, scw.WithContext(c.Context()))
if err != nil {
return false, fmt.Errorf("getting image from the marketplace: %w", err)
}
if actualServer.Image.ID != localImage.ID {
return true, nil
}
return false, nil
}
func checkUserDataDifferences(c *fi.CloudupContext, cloud scaleway.ScwCloud, actualServer *instance.Server, expectedUserData *fi.Resource) (bool, error) {
actualUserData, err := cloud.InstanceService().GetServerUserData(&instance.GetServerUserDataRequest{
Zone: actualServer.Zone,
ServerID: actualServer.ID,
Key: "cloud-init",
}, scw.WithContext(c.Context()))
if err != nil {
return false, fmt.Errorf("error getting actual user-data: %w", err)
}
actualUserDataBytes, err := io.ReadAll(actualUserData)
if err != nil {
return false, fmt.Errorf("error reading actual user-data: %w", err)
}
expectedUserDataBytes, err := fi.ResourceAsBytes(*expectedUserData)
if err != nil {
return false, fmt.Errorf("error reading expected user-data: %w", err)
}
if sha256.Sum256(actualUserDataBytes) != sha256.Sum256(expectedUserDataBytes) {
return true, nil
}
return false, nil
}
func imageLabelFromID(c *fi.CloudupContext, cloud scaleway.ScwCloud, id string) (string, error) {
localImage, err := cloud.MarketplaceService().GetLocalImage(&marketplace.GetLocalImageRequest{
LocalImageID: id,
}, scw.WithContext(c.Context()))
if err != nil {
return "", fmt.Errorf("getting image from the marketplace: %w", err)
}
return localImage.Label, nil
}