From 0b5ec318750d6c21441eb8945d5d64db6378862e Mon Sep 17 00:00:00 2001 From: kkb0318 Date: Sun, 26 May 2024 16:59:07 +0900 Subject: [PATCH] irsa Status Management --- api/v1alpha1/conditions.go | 8 ++ api/v1alpha1/irsa_types.go | 40 +++++++++- api/v1alpha1/irsasetup_types.go | 11 ++- api/v1alpha1/zz_generated.deepcopy.go | 9 ++- .../bases/irsa.kkb0318.github.io_irsas.yaml | 76 ++++++++++++++++++- docs/api.md | 2 + internal/controller/irsa_controller.go | 31 ++++++-- internal/controller/irsasetup_controller.go | 1 + 8 files changed, 162 insertions(+), 16 deletions(-) create mode 100644 api/v1alpha1/conditions.go diff --git a/api/v1alpha1/conditions.go b/api/v1alpha1/conditions.go new file mode 100644 index 0000000..91d2bc9 --- /dev/null +++ b/api/v1alpha1/conditions.go @@ -0,0 +1,8 @@ +package v1alpha1 + +const ( + // ReadyCondition indicates the resource is ready and fully reconciled. + // If the Condition is False, the resource SHOULD be considered to be in the process of reconciling and not a + // representation of actual state. + ReadyCondition string = "Ready" +) diff --git a/api/v1alpha1/irsa_types.go b/api/v1alpha1/irsa_types.go index ed85014..ca59616 100644 --- a/api/v1alpha1/irsa_types.go +++ b/api/v1alpha1/irsa_types.go @@ -17,6 +17,7 @@ limitations under the License. package v1alpha1 import ( + apimeta "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -55,12 +56,47 @@ type IamRole struct { // IRSAStatus defines the observed state of IRSA type IRSAStatus struct { - // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster - // Important: Run "make" to regenerate code after modifying this file + Conditions []metav1.Condition `json:"conditions,omitempty"` } +// GetIRSAStatusConditions returns a pointer to the Status.Conditions slice +func (in *IRSA) GetIRSAStatusConditions() *[]metav1.Condition { + return &in.Status.Conditions +} + +func IRSAStatusReady(irsa IRSA, reason, message string) IRSA { + newCondition := metav1.Condition{ + Type: ReadyCondition, + Status: metav1.ConditionTrue, + Reason: reason, + Message: message, + } + apimeta.SetStatusCondition(irsa.GetIRSAStatusConditions(), newCondition) + return irsa +} + +func IRSAStatusNotReady(irsa IRSA, reason, message string) IRSA { + newCondition := metav1.Condition{ + Type: ReadyCondition, + Status: metav1.ConditionFalse, + Reason: reason, + Message: message, + } + apimeta.SetStatusCondition(irsa.GetIRSAStatusConditions(), newCondition) + return irsa +} + +type IRSAReason string + +const ( + IRSAReasonFailedRoleUpdate IRSAReason = "IRSAFailedRoleUpdate" + IRSAReasonFailedK8sApply IRSAReason = "IRSAFailedApplyingResources" + IRSAReasonReady IRSAReason = "IRSAReady" +) + //+kubebuilder:object:root=true //+kubebuilder:subresource:status +//+kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type==\"Ready\")].status",description="" // IRSA is the Schema for the irsas API type IRSA struct { diff --git a/api/v1alpha1/irsasetup_types.go b/api/v1alpha1/irsasetup_types.go index 56ea1be..d5a0677 100644 --- a/api/v1alpha1/irsasetup_types.go +++ b/api/v1alpha1/irsasetup_types.go @@ -17,7 +17,6 @@ limitations under the License. package v1alpha1 import ( - "github.com/fluxcd/pkg/apis/meta" apimeta "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -81,14 +80,14 @@ type IRSASetupStatus struct { SelfHostedSetup []metav1.Condition `json:"selfHostedSetup,omitempty"` } -// GetStatusConditions returns a pointer to the Status.Conditions slice +// GetSelfhostedStatusConditions returns a pointer to the Status.Conditions slice func (in *IRSASetup) GetSelfhostedStatusConditions() *[]metav1.Condition { return &in.Status.SelfHostedSetup } func SetupSelfHostedStatusReady(irsa IRSASetup, reason, message string) IRSASetup { newCondition := metav1.Condition{ - Type: meta.ReadyCondition, + Type: ReadyCondition, Status: metav1.ConditionTrue, Reason: reason, Message: message, @@ -99,7 +98,7 @@ func SetupSelfHostedStatusReady(irsa IRSASetup, reason, message string) IRSASetu func SelfHostedStatusNotReady(irsa IRSASetup, reason, message string) IRSASetup { newCondition := metav1.Condition{ - Type: meta.ReadyCondition, + Type: ReadyCondition, Status: metav1.ConditionFalse, Reason: reason, Message: message, @@ -110,7 +109,7 @@ func SelfHostedStatusNotReady(irsa IRSASetup, reason, message string) IRSASetup // SelfHostedReadyStatus func SelfHostedReadyStatus(irsa IRSASetup) *metav1.Condition { - if c := apimeta.FindStatusCondition(irsa.Status.SelfHostedSetup, meta.ReadyCondition); c != nil { + if c := apimeta.FindStatusCondition(irsa.Status.SelfHostedSetup, ReadyCondition); c != nil { return c } return nil @@ -130,7 +129,7 @@ func HasConditionReason(cond *metav1.Condition, reasons ...string) bool { } func IsSelfHostedReadyConditionTrue(irsa IRSASetup) bool { - return apimeta.IsStatusConditionTrue(irsa.Status.SelfHostedSetup, meta.ReadyCondition) + return apimeta.IsStatusConditionTrue(irsa.Status.SelfHostedSetup, ReadyCondition) } type SelfHostedReason string diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index d81dc27..fcbbf51 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -63,7 +63,7 @@ func (in *IRSA) DeepCopyInto(out *IRSA) { out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.Spec.DeepCopyInto(&out.Spec) - out.Status = in.Status + in.Status.DeepCopyInto(&out.Status) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IRSA. @@ -259,6 +259,13 @@ func (in *IRSASpec) DeepCopy() *IRSASpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *IRSAStatus) DeepCopyInto(out *IRSAStatus) { *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IRSAStatus. diff --git a/config/crd/bases/irsa.kkb0318.github.io_irsas.yaml b/config/crd/bases/irsa.kkb0318.github.io_irsas.yaml index d9fbdf0..0e059c1 100644 --- a/config/crd/bases/irsa.kkb0318.github.io_irsas.yaml +++ b/config/crd/bases/irsa.kkb0318.github.io_irsas.yaml @@ -14,7 +14,11 @@ spec: singular: irsa scope: Namespaced versions: - - name: v1alpha1 + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=="Ready")].status + name: Ready + type: string + name: v1alpha1 schema: openAPIV3Schema: description: IRSA is the Schema for the irsas API @@ -78,6 +82,76 @@ spec: type: object status: description: IRSAStatus defines the observed state of IRSA + properties: + conditions: + items: + description: "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: |- + type of condition in CamelCase or in foo.example.com/CamelCase. + --- + Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be + useful (see .node.status.conditions), the ability to deconflict is important. + The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array type: object type: object served: true diff --git a/docs/api.md b/docs/api.md index 2d8ae5f..43937af 100644 --- a/docs/api.md +++ b/docs/api.md @@ -65,6 +65,8 @@ IRSA is the Schema for the irsas API | `spec` _[IRSASpec](#irsaspec)_ | | | | + + #### IRSAServiceAccount diff --git a/internal/controller/irsa_controller.go b/internal/controller/irsa_controller.go index 0c8590b..c9058b1 100644 --- a/internal/controller/irsa_controller.go +++ b/internal/controller/irsa_controller.go @@ -159,10 +159,23 @@ func (r *IRSAReconciler) reconcile(ctx context.Context, obj *irsav1alpha1.IRSA, if err != nil { return fmt.Errorf("error converting to IRSASetup for %s: %v", list.Items[0].GetName(), err) } - serviceAccount := obj.Spec.ServiceAccount + issuerMeta, err := issuer.NewS3IssuerMeta(&irsaSetup.Spec.Discovery.S3) + if err != nil { + return err + } + // e is set only when an error occurs in an external dependency process and is reflected in the CRs status + var e error + var reason irsav1alpha1.IRSAReason + defer func() { + if e != nil { + *obj = irsav1alpha1.IRSAStatusNotReady(*obj, string(reason), e.Error()) + } + }() + accountId, err := r.AwsClient.StsClient().GetAccountId() if err != nil { + e = err return err } roleManager := awsclient.RoleManager{ @@ -171,18 +184,17 @@ func (r *IRSAReconciler) reconcile(ctx context.Context, obj *irsav1alpha1.IRSA, Policies: obj.Spec.IamPolicies, AccountId: accountId, } - issuerMeta, err := issuer.NewS3IssuerMeta(&irsaSetup.Spec.Discovery.S3) - if err != nil { - return err - } err = r.AwsClient.IamClient().CreateIRSARole( ctx, issuerMeta, roleManager, ) if err != nil { + e = err + reason = irsav1alpha1.IRSAReasonFailedRoleUpdate return err } + kubeHandler := handler.NewKubernetesHandler(kubeClient) for _, ns := range serviceAccount.Namespaces { @@ -192,7 +204,14 @@ func (r *IRSAReconciler) reconcile(ctx context.Context, obj *irsav1alpha1.IRSA, }) kubeHandler.Append(sa) } - return kubeHandler.ApplyAll(ctx) + err = kubeHandler.ApplyAll(ctx) + if err != nil { + e = err + reason = irsav1alpha1.IRSAReasonFailedK8sApply + return err + } + *obj = irsav1alpha1.IRSAStatusReady(*obj, string(irsav1alpha1.IRSAReasonReady), "successfully setup resources") + return nil } // SetupWithManager sets up the controller with the Manager. diff --git a/internal/controller/irsasetup_controller.go b/internal/controller/irsasetup_controller.go index ad17d72..dbae914 100644 --- a/internal/controller/irsasetup_controller.go +++ b/internal/controller/irsasetup_controller.go @@ -203,6 +203,7 @@ func reconcileSelfhosted(ctx context.Context, obj *irsav1alpha1.IRSASetup, awsCl return err } + // e is set only when an error occurs in an external dependency process and is reflected in the CRs status var e error var reason irsav1alpha1.SelfHostedReason defer func() {