sigs.k8s.io/cluster-api-provider-aws@v1.5.5/pkg/cloud/scope/machine.go (about)

     1  /*
     2  Copyright 2018 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8  	http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package scope
    18  
    19  import (
    20  	"context"
    21  	"encoding/base64"
    22  	"fmt"
    23  
    24  	"github.com/go-logr/logr"
    25  	"github.com/pkg/errors"
    26  	corev1 "k8s.io/api/core/v1"
    27  	"k8s.io/apimachinery/pkg/types"
    28  	"k8s.io/klog/v2/klogr"
    29  	"k8s.io/utils/pointer"
    30  	"sigs.k8s.io/controller-runtime/pkg/client"
    31  
    32  	infrav1 "sigs.k8s.io/cluster-api-provider-aws/api/v1beta1"
    33  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    34  	"sigs.k8s.io/cluster-api/controllers/noderefutil"
    35  	capierrors "sigs.k8s.io/cluster-api/errors"
    36  	"sigs.k8s.io/cluster-api/util"
    37  	"sigs.k8s.io/cluster-api/util/annotations"
    38  	"sigs.k8s.io/cluster-api/util/conditions"
    39  	"sigs.k8s.io/cluster-api/util/patch"
    40  )
    41  
    42  // MachineScopeParams defines the input parameters used to create a new MachineScope.
    43  type MachineScopeParams struct {
    44  	Client       client.Client
    45  	Logger       *logr.Logger
    46  	Cluster      *clusterv1.Cluster
    47  	Machine      *clusterv1.Machine
    48  	InfraCluster EC2Scope
    49  	AWSMachine   *infrav1.AWSMachine
    50  }
    51  
    52  // NewMachineScope creates a new MachineScope from the supplied parameters.
    53  // This is meant to be called for each reconcile iteration.
    54  func NewMachineScope(params MachineScopeParams) (*MachineScope, error) {
    55  	if params.Client == nil {
    56  		return nil, errors.New("client is required when creating a MachineScope")
    57  	}
    58  	if params.Machine == nil {
    59  		return nil, errors.New("machine is required when creating a MachineScope")
    60  	}
    61  	if params.Cluster == nil {
    62  		return nil, errors.New("cluster is required when creating a MachineScope")
    63  	}
    64  	if params.AWSMachine == nil {
    65  		return nil, errors.New("aws machine is required when creating a MachineScope")
    66  	}
    67  	if params.InfraCluster == nil {
    68  		return nil, errors.New("aws cluster is required when creating a MachineScope")
    69  	}
    70  
    71  	if params.Logger == nil {
    72  		log := klogr.New()
    73  		params.Logger = &log
    74  	}
    75  
    76  	helper, err := patch.NewHelper(params.AWSMachine, params.Client)
    77  	if err != nil {
    78  		return nil, errors.Wrap(err, "failed to init patch helper")
    79  	}
    80  	return &MachineScope{
    81  		Logger:      *params.Logger,
    82  		client:      params.Client,
    83  		patchHelper: helper,
    84  
    85  		Cluster:      params.Cluster,
    86  		Machine:      params.Machine,
    87  		InfraCluster: params.InfraCluster,
    88  		AWSMachine:   params.AWSMachine,
    89  	}, nil
    90  }
    91  
    92  // MachineScope defines a scope defined around a machine and its cluster.
    93  type MachineScope struct {
    94  	logr.Logger
    95  	client      client.Client
    96  	patchHelper *patch.Helper
    97  
    98  	Cluster      *clusterv1.Cluster
    99  	Machine      *clusterv1.Machine
   100  	InfraCluster EC2Scope
   101  	AWSMachine   *infrav1.AWSMachine
   102  }
   103  
   104  // Name returns the AWSMachine name.
   105  func (m *MachineScope) Name() string {
   106  	return m.AWSMachine.Name
   107  }
   108  
   109  // Namespace returns the namespace name.
   110  func (m *MachineScope) Namespace() string {
   111  	return m.AWSMachine.Namespace
   112  }
   113  
   114  // IsControlPlane returns true if the machine is a control plane.
   115  func (m *MachineScope) IsControlPlane() bool {
   116  	return util.IsControlPlaneMachine(m.Machine)
   117  }
   118  
   119  // Role returns the machine role from the labels.
   120  func (m *MachineScope) Role() string {
   121  	if util.IsControlPlaneMachine(m.Machine) {
   122  		return "control-plane"
   123  	}
   124  	return "node"
   125  }
   126  
   127  // GetInstanceID returns the AWSMachine instance id by parsing Spec.ProviderID.
   128  func (m *MachineScope) GetInstanceID() *string {
   129  	parsed, err := noderefutil.NewProviderID(m.GetProviderID())
   130  	if err != nil {
   131  		return nil
   132  	}
   133  	return pointer.StringPtr(parsed.ID())
   134  }
   135  
   136  // GetProviderID returns the AWSMachine providerID from the spec.
   137  func (m *MachineScope) GetProviderID() string {
   138  	if m.AWSMachine.Spec.ProviderID != nil {
   139  		return *m.AWSMachine.Spec.ProviderID
   140  	}
   141  	return ""
   142  }
   143  
   144  // SetProviderID sets the AWSMachine providerID in spec.
   145  func (m *MachineScope) SetProviderID(instanceID, availabilityZone string) {
   146  	providerID := fmt.Sprintf("aws:///%s/%s", availabilityZone, instanceID)
   147  	m.AWSMachine.Spec.ProviderID = pointer.StringPtr(providerID)
   148  }
   149  
   150  // SetInstanceID sets the AWSMachine instanceID in spec.
   151  func (m *MachineScope) SetInstanceID(instanceID string) {
   152  	m.AWSMachine.Spec.InstanceID = pointer.StringPtr(instanceID)
   153  }
   154  
   155  // GetInstanceState returns the AWSMachine instance state from the status.
   156  func (m *MachineScope) GetInstanceState() *infrav1.InstanceState {
   157  	return m.AWSMachine.Status.InstanceState
   158  }
   159  
   160  // SetInstanceState sets the AWSMachine status instance state.
   161  func (m *MachineScope) SetInstanceState(v infrav1.InstanceState) {
   162  	m.AWSMachine.Status.InstanceState = &v
   163  }
   164  
   165  // SetReady sets the AWSMachine Ready Status.
   166  func (m *MachineScope) SetReady() {
   167  	m.AWSMachine.Status.Ready = true
   168  }
   169  
   170  // SetNotReady sets the AWSMachine Ready Status to false.
   171  func (m *MachineScope) SetNotReady() {
   172  	m.AWSMachine.Status.Ready = false
   173  }
   174  
   175  // SetFailureMessage sets the AWSMachine status failure message.
   176  func (m *MachineScope) SetFailureMessage(v error) {
   177  	m.AWSMachine.Status.FailureMessage = pointer.StringPtr(v.Error())
   178  }
   179  
   180  // SetFailureReason sets the AWSMachine status failure reason.
   181  func (m *MachineScope) SetFailureReason(v capierrors.MachineStatusError) {
   182  	m.AWSMachine.Status.FailureReason = &v
   183  }
   184  
   185  // SetAnnotation sets a key value annotation on the AWSMachine.
   186  func (m *MachineScope) SetAnnotation(key, value string) {
   187  	if m.AWSMachine.Annotations == nil {
   188  		m.AWSMachine.Annotations = map[string]string{}
   189  	}
   190  	m.AWSMachine.Annotations[key] = value
   191  }
   192  
   193  // UseSecretsManager returns the computed value of whether or not
   194  // userdata should be stored using AWS Secrets Manager.
   195  func (m *MachineScope) UseSecretsManager(userDataFormat string) bool {
   196  	return !m.AWSMachine.Spec.CloudInit.InsecureSkipSecretsManager && !m.UseIgnition(userDataFormat)
   197  }
   198  
   199  func (m *MachineScope) UseIgnition(userDataFormat string) bool {
   200  	return userDataFormat == "ignition" || (m.AWSMachine.Spec.Ignition != nil)
   201  }
   202  
   203  // SecureSecretsBackend returns the chosen secret backend.
   204  func (m *MachineScope) SecureSecretsBackend() infrav1.SecretBackend {
   205  	return m.AWSMachine.Spec.CloudInit.SecureSecretsBackend
   206  }
   207  
   208  // CompressUserData returns the computed value of whether or not
   209  // userdata should be compressed using gzip.
   210  func (m *MachineScope) CompressUserData(userDataFormat string) bool {
   211  	if m.UseIgnition(userDataFormat) {
   212  		return false
   213  	}
   214  
   215  	return m.AWSMachine.Spec.UncompressedUserData != nil && !*m.AWSMachine.Spec.UncompressedUserData
   216  }
   217  
   218  // GetSecretPrefix returns the prefix for the secrets belonging
   219  // to the AWSMachine in AWS Secrets Manager.
   220  func (m *MachineScope) GetSecretPrefix() string {
   221  	return m.AWSMachine.Spec.CloudInit.SecretPrefix
   222  }
   223  
   224  // SetSecretPrefix sets the prefix for the secrets belonging
   225  // to the AWSMachine in AWS Secrets Manager.
   226  func (m *MachineScope) SetSecretPrefix(value string) {
   227  	m.AWSMachine.Spec.CloudInit.SecretPrefix = value
   228  }
   229  
   230  // DeleteSecretPrefix deletes the prefix for the secret belonging
   231  // to the AWSMachine in AWS Secrets Manager.
   232  func (m *MachineScope) DeleteSecretPrefix() {
   233  	m.AWSMachine.Spec.CloudInit.SecretPrefix = ""
   234  }
   235  
   236  // GetSecretCount returns the number of AWS Secret Manager entries making up
   237  // the complete userdata.
   238  func (m *MachineScope) GetSecretCount() int32 {
   239  	return m.AWSMachine.Spec.CloudInit.SecretCount
   240  }
   241  
   242  // SetSecretCount sets the number of AWS Secret Manager entries making up
   243  // the complete userdata.
   244  func (m *MachineScope) SetSecretCount(i int32) {
   245  	m.AWSMachine.Spec.CloudInit.SecretCount = i
   246  }
   247  
   248  // SetAddresses sets the AWSMachine address status.
   249  func (m *MachineScope) SetAddresses(addrs []clusterv1.MachineAddress) {
   250  	m.AWSMachine.Status.Addresses = addrs
   251  }
   252  
   253  // GetBootstrapData returns the bootstrap data from the secret in the Machine's bootstrap.dataSecretName as base64.
   254  func (m *MachineScope) GetBootstrapData() (string, error) {
   255  	value, err := m.GetRawBootstrapData()
   256  	if err != nil {
   257  		return "", err
   258  	}
   259  	return base64.StdEncoding.EncodeToString(value), nil
   260  }
   261  
   262  // GetRawBootstrapData returns the bootstrap data from the secret in the Machine's bootstrap.dataSecretName.
   263  func (m *MachineScope) GetRawBootstrapData() ([]byte, error) {
   264  	data, _, err := m.GetRawBootstrapDataWithFormat()
   265  
   266  	return data, err
   267  }
   268  
   269  func (m *MachineScope) GetRawBootstrapDataWithFormat() ([]byte, string, error) {
   270  	if m.Machine.Spec.Bootstrap.DataSecretName == nil {
   271  		return nil, "", errors.New("error retrieving bootstrap data: linked Machine's bootstrap.dataSecretName is nil")
   272  	}
   273  
   274  	secret := &corev1.Secret{}
   275  	key := types.NamespacedName{Namespace: m.Namespace(), Name: *m.Machine.Spec.Bootstrap.DataSecretName}
   276  	if err := m.client.Get(context.TODO(), key, secret); err != nil {
   277  		return nil, "", errors.Wrapf(err, "failed to retrieve bootstrap data secret for AWSMachine %s/%s", m.Namespace(), m.Name())
   278  	}
   279  
   280  	value, ok := secret.Data["value"]
   281  	if !ok {
   282  		return nil, "", errors.New("error retrieving bootstrap data: secret value key is missing")
   283  	}
   284  
   285  	return value, string(secret.Data["format"]), nil
   286  }
   287  
   288  // PatchObject persists the machine spec and status.
   289  func (m *MachineScope) PatchObject() error {
   290  	// Always update the readyCondition by summarizing the state of other conditions.
   291  	// A step counter is added to represent progress during the provisioning process (instead we are hiding during the deletion process).
   292  	applicableConditions := []clusterv1.ConditionType{
   293  		infrav1.InstanceReadyCondition,
   294  		infrav1.SecurityGroupsReadyCondition,
   295  	}
   296  
   297  	if m.IsControlPlane() {
   298  		applicableConditions = append(applicableConditions, infrav1.ELBAttachedCondition)
   299  	}
   300  
   301  	conditions.SetSummary(m.AWSMachine,
   302  		conditions.WithConditions(applicableConditions...),
   303  		conditions.WithStepCounterIf(m.AWSMachine.ObjectMeta.DeletionTimestamp.IsZero()),
   304  		conditions.WithStepCounter(),
   305  	)
   306  
   307  	return m.patchHelper.Patch(
   308  		context.TODO(),
   309  		m.AWSMachine,
   310  		patch.WithOwnedConditions{Conditions: []clusterv1.ConditionType{
   311  			clusterv1.ReadyCondition,
   312  			infrav1.InstanceReadyCondition,
   313  			infrav1.SecurityGroupsReadyCondition,
   314  			infrav1.ELBAttachedCondition,
   315  		}})
   316  }
   317  
   318  // Close the MachineScope by updating the machine spec, machine status.
   319  func (m *MachineScope) Close() error {
   320  	return m.PatchObject()
   321  }
   322  
   323  // AdditionalTags merges AdditionalTags from the scope's AWSCluster and AWSMachine. If the same key is present in both,
   324  // the value from AWSMachine takes precedence. The returned Tags will never be nil.
   325  func (m *MachineScope) AdditionalTags() infrav1.Tags {
   326  	tags := make(infrav1.Tags)
   327  
   328  	// Start with the cluster-wide tags...
   329  	tags.Merge(m.InfraCluster.AdditionalTags())
   330  	// ... and merge in the Machine's
   331  	tags.Merge(m.AWSMachine.Spec.AdditionalTags)
   332  
   333  	return tags
   334  }
   335  
   336  // HasFailed returns the failure state of the machine scope.
   337  func (m *MachineScope) HasFailed() bool {
   338  	return m.AWSMachine.Status.FailureReason != nil || m.AWSMachine.Status.FailureMessage != nil
   339  }
   340  
   341  // InstanceIsRunning returns the instance state of the machine scope.
   342  func (m *MachineScope) InstanceIsRunning() bool {
   343  	state := m.GetInstanceState()
   344  	return state != nil && infrav1.InstanceRunningStates.Has(string(*state))
   345  }
   346  
   347  // InstanceIsOperational returns the operational state of the machine scope.
   348  func (m *MachineScope) InstanceIsOperational() bool {
   349  	state := m.GetInstanceState()
   350  	return state != nil && infrav1.InstanceOperationalStates.Has(string(*state))
   351  }
   352  
   353  // InstanceIsInKnownState checks if the machine scope's instance state is known.
   354  func (m *MachineScope) InstanceIsInKnownState() bool {
   355  	state := m.GetInstanceState()
   356  	return state != nil && infrav1.InstanceKnownStates.Has(string(*state))
   357  }
   358  
   359  // AWSMachineIsDeleted checks if the machine was deleted.
   360  func (m *MachineScope) AWSMachineIsDeleted() bool {
   361  	return !m.AWSMachine.ObjectMeta.DeletionTimestamp.IsZero()
   362  }
   363  
   364  // IsEKSManaged checks if the machine is EKS managed.
   365  func (m *MachineScope) IsEKSManaged() bool {
   366  	return m.InfraCluster.InfraCluster().GetObjectKind().GroupVersionKind().Kind == "AWSManagedControlPlane"
   367  }
   368  
   369  // IsExternallyManaged checks if the machine is externally managed.
   370  func (m *MachineScope) IsExternallyManaged() bool {
   371  	return annotations.IsExternallyManaged(m.InfraCluster.InfraCluster())
   372  }
   373  
   374  // SetInterruptible sets the AWSMachine status Interruptible.
   375  func (m *MachineScope) SetInterruptible() {
   376  	if m.AWSMachine.Spec.SpotMarketOptions != nil {
   377  		m.AWSMachine.Status.Interruptible = true
   378  	}
   379  }