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 }