Merge pull request #1 from kkb0318/feature/eks

EKS support
This commit is contained in:
kkb
2024-08-09 20:18:40 +09:00
committed by GitHub
15 changed files with 339 additions and 77 deletions
+48 -23
View File
@@ -33,17 +33,34 @@ type IRSASetupSpec struct {
// +required // +required
Cleanup bool `json:"cleanup"` Cleanup bool `json:"cleanup"`
// Mode (Optional, Future Feature) Defines how the controller will operate once this feature is enabled. // Mode specifies the operation mode of the controller.
// Currently unused. Planned values: // Possible values:
// - "selfhosted": For self-managed Kubernetes clusters. // - "selfhosted": For self-managed Kubernetes clusters.
// - "eks": For Amazon EKS environments. // - "eks": For Amazon EKS environments.
Mode string `json:"mode,omitempty"` // Default: "selfhosted"
Mode SetupMode `json:"mode,omitempty"`
// Discovery configures the IdP Discovery process, essential for setting up IRSA by locating // Discovery configures the IdP Discovery process, essential for setting up IRSA by locating
// the OIDC provider information. // the OIDC provider information.
Discovery Discovery `json:"discovery"` // Only applicable when Mode is "selfhosted".
// +optional
Discovery Discovery `json:"discovery,omitempty"`
// IamOIDCProvider configures IAM OIDC IamOIDCProvider Name
// Only applicable when Mode is "eks".
IamOIDCProvider string `json:"iamOIDCProvider,omitempty"`
} }
// +kubebuilder:default=selfhosted
// +kubebuilder:validation:Enum=selfhosted;eks
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable"
type SetupMode string
const (
ModeSelfhosted = SetupMode("selfhosted")
ModeEks = SetupMode("eks")
)
// Discovery holds the configuration for IdP Discovery, which is crucial for locating // Discovery holds the configuration for IdP Discovery, which is crucial for locating
// the OIDC provider in a self-hosted environment. // the OIDC provider in a self-hosted environment.
type Discovery struct { type Discovery struct {
@@ -62,39 +79,39 @@ type S3Discovery struct {
// IRSASetupStatus defines the observed state of IRSASetup // IRSASetupStatus defines the observed state of IRSASetup
type IRSASetupStatus struct { type IRSASetupStatus struct {
SelfHostedSetup []metav1.Condition `json:"selfHostedSetup,omitempty"` Conditions []metav1.Condition `json:"conditions,omitempty"`
} }
// GetSelfhostedStatusConditions returns a pointer to the Status.Conditions slice // GetStatusConditions returns a pointer to the Status.Conditions slice
func (in *IRSASetup) GetSelfhostedStatusConditions() *[]metav1.Condition { func (in *IRSASetup) GetStatusConditions() *[]metav1.Condition {
return &in.Status.SelfHostedSetup return &in.Status.Conditions
} }
func SetupSelfHostedStatusReady(irsa IRSASetup, reason, message string) IRSASetup { func SetupStatusReady(irsa IRSASetup, reason, message string) IRSASetup {
newCondition := metav1.Condition{ newCondition := metav1.Condition{
Type: ReadyCondition, Type: ReadyCondition,
Status: metav1.ConditionTrue, Status: metav1.ConditionTrue,
Reason: reason, Reason: reason,
Message: message, Message: message,
} }
apimeta.SetStatusCondition(irsa.GetSelfhostedStatusConditions(), newCondition) apimeta.SetStatusCondition(irsa.GetStatusConditions(), newCondition)
return irsa return irsa
} }
func SelfHostedStatusNotReady(irsa IRSASetup, reason, message string) IRSASetup { func StatusNotReady(irsa IRSASetup, reason, message string) IRSASetup {
newCondition := metav1.Condition{ newCondition := metav1.Condition{
Type: ReadyCondition, Type: ReadyCondition,
Status: metav1.ConditionFalse, Status: metav1.ConditionFalse,
Reason: reason, Reason: reason,
Message: message, Message: message,
} }
apimeta.SetStatusCondition(irsa.GetSelfhostedStatusConditions(), newCondition) apimeta.SetStatusCondition(irsa.GetStatusConditions(), newCondition)
return irsa return irsa
} }
// SelfHostedReadyStatus // ReadyStatus
func SelfHostedReadyStatus(irsa IRSASetup) *metav1.Condition { func ReadyStatus(irsa IRSASetup) *metav1.Condition {
if c := apimeta.FindStatusCondition(irsa.Status.SelfHostedSetup, ReadyCondition); c != nil { if c := apimeta.FindStatusCondition(irsa.Status.Conditions, ReadyCondition); c != nil {
return c return c
} }
return nil return nil
@@ -113,22 +130,30 @@ func HasConditionReason(cond *metav1.Condition, reasons ...string) bool {
return false return false
} }
func IsSelfHostedReadyConditionTrue(irsa IRSASetup) bool { func IsReadyConditionTrue(irsa IRSASetup) bool {
return apimeta.IsStatusConditionTrue(irsa.Status.SelfHostedSetup, ReadyCondition) return apimeta.IsStatusConditionTrue(irsa.Status.Conditions, ReadyCondition)
} }
type SelfHostedReason string type SelfhostedConditionReason string
const ( const (
SelfHostedReasonFailedWebhook SelfHostedReason = "SelfHostedSetupFailedWebhookCreation" SelfHostedReasonFailedWebhook SelfhostedConditionReason = "SelfHostedSetupFailedWebhookCreation"
SelfHostedReasonFailedOidc SelfHostedReason = "SelfHostedSetupFailedOidcCreation" SelfHostedReasonFailedOidc SelfhostedConditionReason = "SelfHostedSetupFailedOidcCreation"
SelfHostedReasonFailedKeys SelfHostedReason = "SelfHostedSetupFailedKeysCreation" SelfHostedReasonFailedIssuer SelfhostedConditionReason = "SelfHostedSetupFailedIssuer"
SelfHostedReasonReady SelfHostedReason = "SelfHostedSetupReady" SelfHostedReasonFailedKeys SelfhostedConditionReason = "SelfHostedSetupFailedKeysCreation"
SelfHostedReasonReady SelfhostedConditionReason = "SelfHostedSetupReady"
)
type EksConditionReason string
const (
EksNotReady EksConditionReason = "EksOIDCNotReady"
EksReasonReady EksConditionReason = "EksOIDCSetupReady"
) )
//+kubebuilder:object:root=true //+kubebuilder:object:root=true
//+kubebuilder:subresource:status //+kubebuilder:subresource:status
//+kubebuilder:printcolumn:name="SelfHostedReady",type="string",JSONPath=".status.selfHostedSetup[?(@.type==\"Ready\")].status",description="" //+kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type==\"Ready\")].status",description=""
// IRSASetup represents a configuration for setting up IAM Roles for Service Accounts (IRSA) in a Kubernetes cluster. // IRSASetup represents a configuration for setting up IAM Roles for Service Accounts (IRSA) in a Kubernetes cluster.
type IRSASetup struct { type IRSASetup struct {
+2 -2
View File
@@ -213,8 +213,8 @@ func (in *IRSASetupSpec) DeepCopy() *IRSASetupSpec {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *IRSASetupStatus) DeepCopyInto(out *IRSASetupStatus) { func (in *IRSASetupStatus) DeepCopyInto(out *IRSASetupStatus) {
*out = *in *out = *in
if in.SelfHostedSetup != nil { if in.Conditions != nil {
in, out := &in.SelfHostedSetup, &out.SelfHostedSetup in, out := &in.Conditions, &out.Conditions
*out = make([]v1.Condition, len(*in)) *out = make([]v1.Condition, len(*in))
for i := range *in { for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i]) (*in)[i].DeepCopyInto(&(*out)[i])
+18 -6
View File
@@ -14,8 +14,8 @@ spec:
scope: Namespaced scope: Namespaced
versions: versions:
- additionalPrinterColumns: - additionalPrinterColumns:
- jsonPath: .status.selfHostedSetup[?(@.type=="Ready")].status - jsonPath: .status.conditions[?(@.type=="Ready")].status
name: SelfHostedReady name: Ready
type: string type: string
name: v1alpha1 name: v1alpha1
schema: schema:
@@ -52,6 +52,7 @@ spec:
description: |- description: |-
Discovery configures the IdP Discovery process, essential for setting up IRSA by locating Discovery configures the IdP Discovery process, essential for setting up IRSA by locating
the OIDC provider information. the OIDC provider information.
Only applicable when Mode is "selfhosted".
properties: properties:
s3: s3:
description: S3 specifies the AWS S3 bucket details where the description: S3 specifies the AWS S3 bucket details where the
@@ -70,21 +71,32 @@ spec:
- region - region
type: object type: object
type: object type: object
iamOIDCProvider:
description: |-
IamOIDCProvider configures IAM OIDC IamOIDCProvider Name
Only applicable when Mode is "eks".
type: string
mode: mode:
description: |- description: |-
Mode (Optional, Future Feature) Defines how the controller will operate once this feature is enabled. Mode specifies the operation mode of the controller.
Currently unused. Planned values: Possible values:
- "selfhosted": For self-managed Kubernetes clusters. - "selfhosted": For self-managed Kubernetes clusters.
- "eks": For Amazon EKS environments. - "eks": For Amazon EKS environments.
Default: "selfhosted"
enum:
- selfhosted
- eks
type: string type: string
x-kubernetes-validations:
- message: Value is immutable
rule: self == oldSelf
required: required:
- cleanup - cleanup
- discovery
type: object type: object
status: status:
description: IRSASetupStatus defines the observed state of IRSASetup description: IRSASetupStatus defines the observed state of IRSASetup
properties: properties:
selfHostedSetup: conditions:
items: items:
description: "Condition contains details for one aspect of the current description: "Condition contains details for one aspect of the current
state of this API Resource.\n---\nThis struct is intended for state of this API Resource.\n---\nThis struct is intended for
@@ -71,5 +71,7 @@ spec:
| nindent 10 }} | nindent 10 }}
securityContext: securityContext:
runAsNonRoot: true runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
serviceAccountName: {{ include "irsa-manager.fullname" . }}-controller-manager serviceAccountName: {{ include "irsa-manager.fullname" . }}-controller-manager
terminationGracePeriodSeconds: 10 terminationGracePeriodSeconds: 10
@@ -15,8 +15,8 @@ spec:
scope: Namespaced scope: Namespaced
versions: versions:
- additionalPrinterColumns: - additionalPrinterColumns:
- jsonPath: .status.selfHostedSetup[?(@.type=="Ready")].status - jsonPath: .status.conditions[?(@.type=="Ready")].status
name: SelfHostedReady name: Ready
type: string type: string
name: v1alpha1 name: v1alpha1
schema: schema:
@@ -53,6 +53,7 @@ spec:
description: |- description: |-
Discovery configures the IdP Discovery process, essential for setting up IRSA by locating Discovery configures the IdP Discovery process, essential for setting up IRSA by locating
the OIDC provider information. the OIDC provider information.
Only applicable when Mode is "selfhosted".
properties: properties:
s3: s3:
description: S3 specifies the AWS S3 bucket details where the description: S3 specifies the AWS S3 bucket details where the
@@ -71,21 +72,32 @@ spec:
- region - region
type: object type: object
type: object type: object
iamOIDCProvider:
description: |-
IamOIDCProvider configures IAM OIDC IamOIDCProvider Name
Only applicable when Mode is "eks".
type: string
mode: mode:
description: |- description: |-
Mode (Optional, Future Feature) Defines how the controller will operate once this feature is enabled. Mode specifies the operation mode of the controller.
Currently unused. Planned values: Possible values:
- "selfhosted": For self-managed Kubernetes clusters. - "selfhosted": For self-managed Kubernetes clusters.
- "eks": For Amazon EKS environments. - "eks": For Amazon EKS environments.
Default: "selfhosted"
enum:
- selfhosted
- eks
type: string type: string
x-kubernetes-validations:
- message: Value is immutable
rule: self == oldSelf
required: required:
- cleanup - cleanup
- discovery
type: object type: object
status: status:
description: IRSASetupStatus defines the observed state of IRSASetup description: IRSASetupStatus defines the observed state of IRSASetup
properties: properties:
selfHostedSetup: conditions:
items: items:
description: "Condition contains details for one aspect of the current description: "Condition contains details for one aspect of the current
state of this API Resource.\n---\nThis struct is intended for state of this API Resource.\n---\nThis struct is intended for
+6
View File
@@ -1,2 +1,8 @@
resources: resources:
- manager.yaml - manager.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
images:
- name: controller
newName: ghcr.io/kkb0318/irsa-manager
newTag: latest
+19 -2
View File
@@ -31,6 +31,8 @@ _Appears in:_
| `s3` _[S3Discovery](#s3discovery)_ | S3 specifies the AWS S3 bucket details where the OIDC provider's discovery information is hosted. | | | | `s3` _[S3Discovery](#s3discovery)_ | S3 specifies the AWS S3 bucket details where the OIDC provider's discovery information is hosted. | | |
#### IRSA #### IRSA
@@ -102,8 +104,9 @@ _Appears in:_
| Field | Description | Default | Validation | | Field | Description | Default | Validation |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| `cleanup` _boolean_ | Cleanup, when enabled, allows the IRSASetup to perform garbage collection<br />of resources that are no longer needed or managed. | | | | `cleanup` _boolean_ | Cleanup, when enabled, allows the IRSASetup to perform garbage collection<br />of resources that are no longer needed or managed. | | |
| `mode` _string_ | Mode (Optional, Future Feature) Defines how the controller will operate once this feature is enabled.<br />Currently unused. Planned values:<br /> - "selfhosted": For self-managed Kubernetes clusters.<br /> - "eks": For Amazon EKS environments. | | | | `mode` _[SetupMode](#setupmode)_ | Mode specifies the operation mode of the controller.<br />Possible values:<br /> - "selfhosted": For self-managed Kubernetes clusters.<br /> - "eks": For Amazon EKS environments.<br />Default: "selfhosted" | | Enum: [selfhosted eks] <br /> |
| `discovery` _[Discovery](#discovery)_ | Discovery configures the IdP Discovery process, essential for setting up IRSA by locating<br />the OIDC provider information. | | | | `discovery` _[Discovery](#discovery)_ | Discovery configures the IdP Discovery process, essential for setting up IRSA by locating<br />the OIDC provider information.<br />Only applicable when Mode is "selfhosted". | | |
| `iamOIDCProvider` _string_ | IamOIDCProvider configures IAM OIDC IamOIDCProvider Name<br />Only applicable when Mode is "eks". | | |
@@ -164,3 +167,17 @@ _Appears in:_
#### SetupMode
_Underlying type:_ _string_
_Validation:_
- Enum: [selfhosted eks]
_Appears in:_
- [IRSASetupSpec](#irsasetupspec)
+9
View File
@@ -0,0 +1,9 @@
apiVersion: irsa-manager.kkb0318.github.io/v1alpha1
kind: IRSASetup
metadata:
name: irsa-init
namespace: irsa-manager-system
spec:
mode: eks
cleanup: true
iamOIDCProvider: "oidc.eks.<region>.amazonaws.com/id/<id>"
+2 -2
View File
@@ -6,11 +6,11 @@ metadata:
spec: spec:
cleanup: true cleanup: true
serviceAccount: serviceAccount:
name: irsa1-sa name: irsa111-sa
namespaces: namespaces:
- kube-system - kube-system
- default - default
iamRole: iamRole:
name: irsa1-role name: irsa111-role
iamPolicies: iamPolicies:
- AmazonS3FullAccess - AmazonS3FullAccess
+1 -8
View File
@@ -51,13 +51,6 @@ type IRSAReconciler struct {
// Reconcile is part of the main kubernetes reconciliation loop which aims to // Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the cluster closer to the desired state. // move the current state of the cluster closer to the desired state.
// TODO(user): Modify the Reconcile function to compare the state specified by
// the IRSA object against the actual cluster state, and then
// perform operations to make the cluster state reflect the state specified by
// the user.
//
// For more details, check Reconcile and its Result here:
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.16.3/pkg/reconcile
func (r *IRSAReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { func (r *IRSAReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
log := ctrllog.FromContext(ctx) log := ctrllog.FromContext(ctx)
obj := &irsav1alpha1.IRSA{} obj := &irsav1alpha1.IRSA{}
@@ -152,7 +145,7 @@ func (r *IRSAReconciler) reconcile(ctx context.Context, obj *irsav1alpha1.IRSA,
return fmt.Errorf("error converting to IRSASetup for %s: %v", list.Items[0].GetName(), err) return fmt.Errorf("error converting to IRSASetup for %s: %v", list.Items[0].GetName(), err)
} }
serviceAccount := obj.Spec.ServiceAccount serviceAccount := obj.Spec.ServiceAccount
issuerMeta, err := issuer.NewS3IssuerMeta(&irsaSetup.Spec.Discovery.S3) issuerMeta, err := issuer.NewOIDCIssuerMeta(irsaSetup)
if err != nil { if err != nil {
return err return err
} }
+84 -2
View File
@@ -211,7 +211,7 @@ var _ = Describe("IRSA Controller", func() {
f: newServiceAccount, f: newServiceAccount,
}, },
) )
f := createCallBack(ctx, r, typeNamespacedName, obj) f := createCallBackForFixingNamespace(ctx, r, typeNamespacedName, obj)
By("Add Namespace 'default'") By("Add Namespace 'default'")
f(obj.Spec.ServiceAccount.Name, []string{"default", "kube-system"}) f(obj.Spec.ServiceAccount.Name, []string{"default", "kube-system"})
@@ -222,6 +222,75 @@ var _ = Describe("IRSA Controller", func() {
f: newServiceAccount, f: newServiceAccount,
}) })
By("removing the custom resource for the Kind")
Eventually(func() error {
return k8sClient.Delete(ctx, obj)
}, timeout).Should(Succeed())
_, err = r.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
})
Expect(err).To(Not(HaveOccurred()))
for _, expect := range expected {
checkNoExist(expect)
}
},
},
{
name: "should update serviceaccount successfully with EKS mode",
obj: &irsav1alpha1.IRSA{
ObjectMeta: metav1.ObjectMeta{
Name: "test-resource-eks-1",
Namespace: "default",
},
Spec: irsav1alpha1.IRSASpec{
Cleanup: true,
ServiceAccount: irsav1alpha1.IRSAServiceAccount{
Name: "sa-eks-1",
Namespaces: []string{
"kube-system",
},
},
},
},
irsaSetupObj: newMockIRSASetupForEKS(),
f: func(r *IRSAReconciler, obj *irsav1alpha1.IRSA) {
expected := []expectedResource{
{
NamespacedName: types.NamespacedName{Name: "sa-eks-1", Namespace: "kube-system"},
f: newServiceAccount,
},
{
NamespacedName: types.NamespacedName{Name: "sa-eks-1", Namespace: "default"},
f: newServiceAccount,
},
}
By("Reconciling the created resource")
typeNamespacedName := types.NamespacedName{
Name: obj.Name,
Namespace: obj.Namespace,
}
_, err := r.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
})
Expect(err).NotTo(HaveOccurred())
checkExist(
expectedResource{
NamespacedName: types.NamespacedName{Name: "sa-eks-1", Namespace: "kube-system"},
f: newServiceAccount,
},
)
f := createCallBackForFixingNamespace(ctx, r, typeNamespacedName, obj)
By("Add Namespace 'default'")
f(obj.Spec.ServiceAccount.Name, []string{"default", "kube-system"})
By("Remove Namespace 'kube-system'")
f(obj.Spec.ServiceAccount.Name, []string{"default"})
checkNoExist(expectedResource{
NamespacedName: types.NamespacedName{Name: "sa-eks-1", Namespace: "kube-system"},
f: newServiceAccount,
})
By("removing the custom resource for the Kind") By("removing the custom resource for the Kind")
Eventually(func() error { Eventually(func() error {
return k8sClient.Delete(ctx, obj) return k8sClient.Delete(ctx, obj)
@@ -298,7 +367,20 @@ func newMockIRSASetup() *irsav1alpha1.IRSASetup {
} }
} }
func createCallBack(ctx context.Context, r *IRSAReconciler, typeNamespacedName types.NamespacedName, obj *irsav1alpha1.IRSA) func(name string, namespaces []string) { func newMockIRSASetupForEKS() *irsav1alpha1.IRSASetup {
return &irsav1alpha1.IRSASetup{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
Namespace: "default",
},
Spec: irsav1alpha1.IRSASetupSpec{
Mode: irsav1alpha1.ModeEks,
IamOIDCProvider: "oidc.example",
},
}
}
func createCallBackForFixingNamespace(ctx context.Context, r *IRSAReconciler, typeNamespacedName types.NamespacedName, obj *irsav1alpha1.IRSA) func(name string, namespaces []string) {
return func(name string, namespaces []string) { return func(name string, namespaces []string) {
fixNamespacesAndReconcile(ctx, r, typeNamespacedName, obj, name, namespaces) fixNamespacesAndReconcile(ctx, r, typeNamespacedName, obj, name, namespaces)
} }
+47 -18
View File
@@ -27,6 +27,7 @@ import (
irsav1alpha1 "github.com/kkb0318/irsa-manager/api/v1alpha1" irsav1alpha1 "github.com/kkb0318/irsa-manager/api/v1alpha1"
awsclient "github.com/kkb0318/irsa-manager/internal/aws" awsclient "github.com/kkb0318/irsa-manager/internal/aws"
"github.com/kkb0318/irsa-manager/internal/eks"
"github.com/kkb0318/irsa-manager/internal/handler" "github.com/kkb0318/irsa-manager/internal/handler"
"github.com/kkb0318/irsa-manager/internal/issuer" "github.com/kkb0318/irsa-manager/internal/issuer"
"github.com/kkb0318/irsa-manager/internal/kubernetes" "github.com/kkb0318/irsa-manager/internal/kubernetes"
@@ -59,13 +60,6 @@ type IRSASetupReconciler struct {
// Reconcile is part of the main kubernetes reconciliation loop which aims to // Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the cluster closer to the desired state. // move the current state of the cluster closer to the desired state.
// TODO(user): Modify the Reconcile function to compare the state specified by
// the IRSASetup object against the actual cluster state, and then
// perform operations to make the cluster state reflect the state specified by
// the user.
//
// For more details, check Reconcile and its Result here:
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.16.3/pkg/reconcile
func (r *IRSASetupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { func (r *IRSASetupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
log := ctrllog.FromContext(ctx) log := ctrllog.FromContext(ctx)
obj := &irsav1alpha1.IRSASetup{} obj := &irsav1alpha1.IRSASetup{}
@@ -104,7 +98,11 @@ func (r *IRSASetupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
}() }()
if !obj.DeletionTimestamp.IsZero() { if !obj.DeletionTimestamp.IsZero() {
err = r.reconcileDelete(ctx, obj, kubeClient) if obj.Spec.Mode == irsav1alpha1.ModeEks {
err = r.reconcileDeleteEks()
} else {
err = r.reconcileDeleteSelfhosted(ctx, obj, kubeClient)
}
if err != nil { if err != nil {
return ctrl.Result{}, err return ctrl.Result{}, err
} }
@@ -125,11 +123,17 @@ func (r *IRSASetupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
} }
func (r *IRSASetupReconciler) reconcile(ctx context.Context, obj *irsav1alpha1.IRSASetup, kubeClient *kubernetes.KubernetesClient) error { func (r *IRSASetupReconciler) reconcile(ctx context.Context, obj *irsav1alpha1.IRSASetup, kubeClient *kubernetes.KubernetesClient) error {
err := reconcileSelfhosted(ctx, obj, r.AwsClient, kubeClient) if obj.Spec.Mode == irsav1alpha1.ModeEks {
return err return reconcileEks(ctx, obj)
}
return reconcileSelfhosted(ctx, obj, r.AwsClient, kubeClient)
} }
func (r *IRSASetupReconciler) reconcileDelete(ctx context.Context, obj *irsav1alpha1.IRSASetup, kubeClient *kubernetes.KubernetesClient) error { func (r *IRSASetupReconciler) reconcileDeleteEks() error {
return nil
}
func (r *IRSASetupReconciler) reconcileDeleteSelfhosted(ctx context.Context, obj *irsav1alpha1.IRSASetup, kubeClient *kubernetes.KubernetesClient) error {
if !obj.Spec.Cleanup { if !obj.Spec.Cleanup {
return nil return nil
} }
@@ -154,7 +158,7 @@ func (r *IRSASetupReconciler) reconcileDelete(ctx context.Context, obj *irsav1al
if err != nil { if err != nil {
return err return err
} }
issuerMeta, err := issuer.NewS3IssuerMeta(&obj.Spec.Discovery.S3) issuerMeta, err := issuer.NewOIDCIssuerMeta(obj)
if err != nil { if err != nil {
return err return err
} }
@@ -172,7 +176,7 @@ func (r *IRSASetupReconciler) reconcileDelete(ctx context.Context, obj *irsav1al
// - The function enforces a 'force update' strategy in case of failures related to kubernetes Secrets creation or OIDC setup. This means it starts from scratch to ensure all components are correctly configured. // - The function enforces a 'force update' strategy in case of failures related to kubernetes Secrets creation or OIDC setup. This means it starts from scratch to ensure all components are correctly configured.
func reconcileSelfhosted(ctx context.Context, obj *irsav1alpha1.IRSASetup, awsClient awsclient.AwsClient, kubeClient *kubernetes.KubernetesClient) error { func reconcileSelfhosted(ctx context.Context, obj *irsav1alpha1.IRSASetup, awsClient awsclient.AwsClient, kubeClient *kubernetes.KubernetesClient) error {
log := ctrllog.FromContext(ctx) log := ctrllog.FromContext(ctx)
if irsav1alpha1.IsSelfHostedReadyConditionTrue(*obj) { if irsav1alpha1.IsReadyConditionTrue(*obj) {
// Selfhosted Setup have already succeeded // Selfhosted Setup have already succeeded
log.Info("the self-hosted resources have already set up") log.Info("the self-hosted resources have already set up")
return nil return nil
@@ -205,20 +209,22 @@ func reconcileSelfhosted(ctx context.Context, obj *irsav1alpha1.IRSASetup, awsCl
// e is set only when an error occurs in an external dependency process and is reflected in the CRs status // e is set only when an error occurs in an external dependency process and is reflected in the CRs status
var e error var e error
var reason irsav1alpha1.SelfHostedReason var reason irsav1alpha1.SelfhostedConditionReason
defer func() { defer func() {
if e != nil { if e != nil {
*obj = irsav1alpha1.SelfHostedStatusNotReady(*obj, string(reason), e.Error()) *obj = irsav1alpha1.StatusNotReady(*obj, string(reason), e.Error())
} }
}() }()
forceUpdate := irsav1alpha1.HasConditionReason( forceUpdate := irsav1alpha1.HasConditionReason(
irsav1alpha1.SelfHostedReadyStatus(*obj), irsav1alpha1.ReadyStatus(*obj),
string(irsav1alpha1.SelfHostedReasonFailedKeys), string(irsav1alpha1.SelfHostedReasonFailedKeys),
string(irsav1alpha1.SelfHostedReasonFailedOidc), string(irsav1alpha1.SelfHostedReasonFailedOidc),
) )
issuerMeta, err := issuer.NewS3IssuerMeta(&obj.Spec.Discovery.S3) issuerMeta, err := issuer.NewOIDCIssuerMeta(obj)
if err != nil { if err != nil {
e = err
reason = irsav1alpha1.SelfHostedReasonFailedIssuer
return err return err
} }
err = selfhosted.Execute( err = selfhosted.Execute(
@@ -253,11 +259,34 @@ func reconcileSelfhosted(ctx context.Context, obj *irsav1alpha1.IRSASetup, awsCl
reason = irsav1alpha1.SelfHostedReasonFailedWebhook reason = irsav1alpha1.SelfHostedReasonFailedWebhook
return err return err
} }
*obj = irsav1alpha1.SetupSelfHostedStatusReady(*obj, string(irsav1alpha1.SelfHostedReasonReady), "successfully setup resources for self-hosted") *obj = irsav1alpha1.SetupStatusReady(*obj, string(irsav1alpha1.SelfHostedReasonReady), "successfully setup resources for self-hosted")
log.Info("the self-hosted resources have successfully set up") log.Info("the self-hosted resources have successfully set up")
return nil return nil
} }
// reconcileEks iterates tasks for EKS mode.
func reconcileEks(ctx context.Context, obj *irsav1alpha1.IRSASetup) error {
log := ctrllog.FromContext(ctx)
var reason irsav1alpha1.EksConditionReason
// e is set only when an error occurs in an external dependency process and is reflected in the CRs status
var e error
defer func() {
if e != nil {
*obj = irsav1alpha1.StatusNotReady(*obj, string(reason), e.Error())
}
}()
err := eks.Validate(obj)
if err != nil {
e = err
reason = irsav1alpha1.EksNotReady
return err
}
*obj = irsav1alpha1.SetupStatusReady(*obj, string(irsav1alpha1.EksReasonReady), "successfully setup for eks")
log.Info("The OIDC for EKS has been successfully set up")
return nil
}
func newOIDCIdpFactory(ctx context.Context, obj *irsav1alpha1.IRSASetup, jwk *selfhosted.JWK, awsClient awsclient.AwsClient) (selfhosted.OIDCIdPFactory, error) { func newOIDCIdpFactory(ctx context.Context, obj *irsav1alpha1.IRSASetup, jwk *selfhosted.JWK, awsClient awsclient.AwsClient) (selfhosted.OIDCIdPFactory, error) {
region := obj.Spec.Discovery.S3.Region region := obj.Spec.Discovery.S3.Region
bucketName := obj.Spec.Discovery.S3.BucketName bucketName := obj.Spec.Discovery.S3.BucketName
@@ -288,6 +288,42 @@ var _ = Describe("IRSASetup Controller", func() {
} }
}, },
}, },
{
name: "EKS mode",
obj: &irsav1alpha1.IRSASetup{
ObjectMeta: metav1.ObjectMeta{
Name: "test-resource-eks1",
Namespace: "default",
},
Spec: irsav1alpha1.IRSASetupSpec{
Cleanup: false,
Mode: irsav1alpha1.ModeEks,
IamOIDCProvider: "oidc.example",
},
},
f: func(r *IRSASetupReconciler, obj *irsav1alpha1.IRSASetup) {
typeNamespacedName := types.NamespacedName{
Name: obj.Name,
Namespace: obj.Namespace,
}
_, err := r.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
})
Expect(err).NotTo(HaveOccurred())
_, err = r.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
})
Expect(err).To(Not(HaveOccurred()))
By("removing the custom resource (not cleanup)")
Eventually(func() error {
return k8sClient.Delete(ctx, obj)
}, timeout).Should(Succeed())
_, err = r.Reconcile(ctx, reconcile.Request{
NamespacedName: typeNamespacedName,
})
Expect(err).To(Not(HaveOccurred()))
},
},
} }
for _, tt := range tests { for _, tt := range tests {
It(tt.name, func() { It(tt.name, func() {
+14
View File
@@ -0,0 +1,14 @@
package eks
import (
"fmt"
irsav1alpha1 "github.com/kkb0318/irsa-manager/api/v1alpha1"
)
func Validate(obj *irsav1alpha1.IRSASetup) error {
if obj.Spec.IamOIDCProvider == "" {
return fmt.Errorf("IamOIDCProvider parameter must be set when Mode is 'eks'")
}
return nil
}
+32 -7
View File
@@ -11,27 +11,52 @@ type OIDCIssuerMeta interface {
IssuerUrl() string IssuerUrl() string
} }
type S3IssuerMeta struct { type s3IssuerMeta struct {
region string region string
bucketName string bucketName string
} }
func NewS3IssuerMeta(s3 *irsav1alpha1.S3Discovery) (*S3IssuerMeta, error) { func NewOIDCIssuerMeta(i *irsav1alpha1.IRSASetup) (OIDCIssuerMeta, error) {
if i.Spec.Mode == irsav1alpha1.ModeEks {
return newIamOIDCProviderIssuerMeta(i.Spec.IamOIDCProvider)
}
return newS3IssuerMeta(&i.Spec.Discovery.S3)
}
func newS3IssuerMeta(s3 *irsav1alpha1.S3Discovery) (*s3IssuerMeta, error) {
region := s3.Region region := s3.Region
bucketName := s3.BucketName bucketName := s3.BucketName
if region == "" || bucketName == "" { if region == "" || bucketName == "" {
return nil, fmt.Errorf("s3 region and bucket name must not be empty. region: %s, bucketName: %s", region, bucketName) return nil, fmt.Errorf("s3 region and bucket name must not be empty. region: %s, bucketName: %s", region, bucketName)
} }
return &S3IssuerMeta{region, bucketName}, nil return &s3IssuerMeta{region, bucketName}, nil
} }
func (i *S3IssuerMeta) IssuerHostPath() string { func (i *s3IssuerMeta) IssuerHostPath() string {
return fmt.Sprintf("s3-%s.amazonaws.com/%s", i.region, i.bucketName) return fmt.Sprintf("s3-%s.amazonaws.com/%s", i.region, i.bucketName)
} }
// IssuerUrl constructs the URL path for the OIDC issuer based on the provided AWS region and bucket name. // IssuerUrl constructs the URL path for the OIDC issuer based on the provided AWS region and bucket name.
// This utility function generates the expected host path for accessing the OIDC configuration stored in an S3 bucket. // This utility function generates the expected host path for accessing the OIDC configuration stored in an S3 bucket.
func (i *S3IssuerMeta) IssuerUrl() string { func (i *s3IssuerMeta) IssuerUrl() string {
return fmt.Sprintf("https://%s", i. return fmt.Sprintf("https://%s", i.IssuerHostPath())
IssuerHostPath()) }
func newIamOIDCProviderIssuerMeta(providerName string) (*iamOIDCProviderIssuerMeta, error) {
if providerName == "" {
return nil, fmt.Errorf("IAM OIDC Provider Name must not be empty")
}
return &iamOIDCProviderIssuerMeta{providerName}, nil
}
type iamOIDCProviderIssuerMeta struct {
providerName string
}
func (i *iamOIDCProviderIssuerMeta) IssuerHostPath() string {
return i.providerName
}
func (i *iamOIDCProviderIssuerMeta) IssuerUrl() string {
return fmt.Sprintf("https://%s", i.IssuerHostPath())
} }