diff --git a/api/v1alpha1/irsasetup_types.go b/api/v1alpha1/irsasetup_types.go index ca2cfb4..9c21e42 100644 --- a/api/v1alpha1/irsasetup_types.go +++ b/api/v1alpha1/irsasetup_types.go @@ -81,7 +81,7 @@ func (in *IRSASetup) GetSelfhostedStatusConditions() *[]metav1.Condition { return &in.Status.SelfHostedSetup } -func IRSASetupSelfHostedReady(irsa IRSASetup, reason, message string) IRSASetup { +func SetupSelfHostedStatusReady(irsa IRSASetup, reason, message string) IRSASetup { newCondition := metav1.Condition{ Type: meta.ReadyCondition, Status: metav1.ConditionTrue, @@ -92,7 +92,7 @@ func IRSASetupSelfHostedReady(irsa IRSASetup, reason, message string) IRSASetup return irsa } -func IRSASetupSelfHostedNotReady(irsa IRSASetup, reason, message string) IRSASetup { +func SelfHostedStatusNotReady(irsa IRSASetup, reason, message string) IRSASetup { newCondition := metav1.Condition{ Type: meta.ReadyCondition, Status: metav1.ConditionFalse, @@ -103,15 +103,27 @@ func IRSASetupSelfHostedNotReady(irsa IRSASetup, reason, message string) IRSASet return irsa } -// IRSASetupSelfHostedReadyStatus -func IRSASetupSelfHostedReadyStatus(irsa IRSASetup) *metav1.Condition { +// SelfHostedReadyStatus +func SelfHostedReadyStatus(irsa IRSASetup) *metav1.Condition { if c := apimeta.FindStatusCondition(irsa.Status.SelfHostedSetup, meta.ReadyCondition); c != nil { - // return c, c.Status == metav1.ConditionTrue return c } return nil } +// HasConditionReason +func HasConditionReason(cond *metav1.Condition, reasons ...string) bool { + if cond == nil { + return false + } + for _, reason := range reasons { + if cond.Reason == reason { + return true + } + } + return false +} + func IsSelfHostedReadyConditionTrue(irsa IRSASetup) bool { return apimeta.IsStatusConditionTrue(irsa.Status.SelfHostedSetup, meta.ReadyCondition) } @@ -121,6 +133,7 @@ type SelfHostedReason string const ( SelfHostedReasonFailedOidc SelfHostedReason = "SelfHostedSetupFailedOidcCreation" SelfHostedReasonFailedKeys SelfHostedReason = "SelfHostedSetupFailedKeysCreation" + SelfHostedReasonReady SelfHostedReason = "SelfHostedSetupReady" ) //+kubebuilder:object:root=true diff --git a/docs/api.md b/docs/api.md index 51432fb..63a6c18 100644 --- a/docs/api.md +++ b/docs/api.md @@ -118,3 +118,5 @@ _Appears in:_ | `namespace` _string_ | Namespace specifies the namespace of the secret. | | | + + diff --git a/internal/client/aws.go b/internal/client/aws.go index 4e77b41..a237c52 100644 --- a/internal/client/aws.go +++ b/internal/client/aws.go @@ -85,17 +85,31 @@ func (a *AwsS3Client) CheckObjectExists(ctx context.Context, key string) (bool, return true, nil } +type ObjectInput struct { + Key string + Body []byte +} + +func (a *AwsS3Client) CreateObjectsPublic(ctx context.Context, inputs []ObjectInput) error { + for _, input := range inputs { + if err := a.CreateObjectPublic(ctx, input); err != nil { + return err + } + } + return nil +} + // CreateObjectPublic creates a file to an S3 bucket and sets its access level to public read. // This means the file can be read by anyone on the internet. -func (a *AwsS3Client) CreateObjectPublic(ctx context.Context, key string, body []byte) error { - exists, err := a.CheckObjectExists(ctx, key) +func (a *AwsS3Client) CreateObjectPublic(ctx context.Context, input ObjectInput) error { + exists, err := a.CheckObjectExists(ctx, input.Key) if err != nil { return err } if exists { - log.Printf("skipped to create bucket object %s \n", key) + log.Printf("skipped to create bucket object %s \n", input.Key) } else { - err := a.PutObjectPublic(ctx, key, body) + err := a.PutObjectPublic(ctx, input) if err != nil { return err } @@ -103,14 +117,23 @@ func (a *AwsS3Client) CreateObjectPublic(ctx context.Context, key string, body [ return nil } +func (a *AwsS3Client) PutObjectsPublic(ctx context.Context, inputs []ObjectInput) error { + for _, input := range inputs { + if err := a.PutObjectPublic(ctx, input); err != nil { + return err + } + } + return nil +} + // PutObjectPublic uploads a file to an S3 bucket and sets its access level to public read. // This means the file can be read by anyone on the internet. -func (a *AwsS3Client) PutObjectPublic(ctx context.Context, key string, body []byte) error { +func (a *AwsS3Client) PutObjectPublic(ctx context.Context, input ObjectInput) error { _, err := a.Client.PutObject(ctx, &s3.PutObjectInput{ Bucket: aws.String(a.bucketName), - Key: aws.String(key), + Key: aws.String(input.Key), ACL: types.ObjectCannedACLPublicRead, - Body: bytes.NewReader(body), + Body: bytes.NewReader(input.Body), ContentType: aws.String("application/json"), }) return err diff --git a/internal/controller/irsasetup_controller.go b/internal/controller/irsasetup_controller.go index 72ffe1e..28150c7 100644 --- a/internal/controller/irsasetup_controller.go +++ b/internal/controller/irsasetup_controller.go @@ -18,7 +18,6 @@ package controller import ( "context" - "fmt" "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" @@ -154,32 +153,32 @@ func reconcileSelfhosted(ctx context.Context, obj *irsav1alpha1.IRSASetup, awsCl var reason irsav1alpha1.SelfHostedReason defer func() { if e != nil { - *obj = irsav1alpha1.IRSASetupSelfHostedNotReady(*obj, string(reason), e.Error()) + *obj = irsav1alpha1.SelfHostedStatusNotReady(*obj, string(reason), e.Error()) } }() - var forceUpdate bool - condition := irsav1alpha1.IRSASetupSelfHostedReadyStatus(*obj) - switch irsav1alpha1.SelfHostedReason(condition.Reason) { - case irsav1alpha1.SelfHostedReasonFailedKeys, irsav1alpha1.SelfHostedReasonFailedOidc: - forceUpdate = true - default: - forceUpdate = false - } - fmt.Println(forceUpdate) // TODO: force Update logic - err = selfhosted.Execute(ctx, factory) + forceUpdate := irsav1alpha1.HasConditionReason( + irsav1alpha1.SelfHostedReadyStatus(*obj), + string(irsav1alpha1.SelfHostedReasonFailedKeys), + string(irsav1alpha1.SelfHostedReasonFailedOidc), + ) + err = selfhosted.Execute(ctx, factory, forceUpdate) if err != nil { e = err reason = irsav1alpha1.SelfHostedReasonFailedOidc return err } - err = kubeHandler.CreateAll(ctx) + if forceUpdate { + err = kubeHandler.ApplyAll(ctx) + } else { + err = kubeHandler.CreateAll(ctx) + } if err != nil { e = err reason = irsav1alpha1.SelfHostedReasonFailedKeys return err } - *obj = irsav1alpha1.IRSASetupSelfHostedReady(*obj, "SelfHostedSetupReady", e.Error()) + *obj = irsav1alpha1.SetupSelfHostedStatusReady(*obj, string(irsav1alpha1.SelfHostedReasonReady), "successfully setup resources for self-hosted") return nil } diff --git a/internal/selfhosted/oidc.go b/internal/selfhosted/oidc.go index 7bf47e1..a9fcb8d 100644 --- a/internal/selfhosted/oidc.go +++ b/internal/selfhosted/oidc.go @@ -22,7 +22,7 @@ type OIDCIdPDiscoveryContents interface { type OIDCIdPDiscovery interface { CreateStorage(ctx context.Context) error - Upload(ctx context.Context, o OIDCIdPDiscoveryContents) error + Upload(ctx context.Context, o OIDCIdPDiscoveryContents, forceUpdate bool) error Delete(ctx context.Context, o OIDCIdPDiscoveryContents) error } diff --git a/internal/selfhosted/oidc/id_provider_discovery.go b/internal/selfhosted/oidc/id_provider_discovery.go index f818c94..90dc50c 100644 --- a/internal/selfhosted/oidc/id_provider_discovery.go +++ b/internal/selfhosted/oidc/id_provider_discovery.go @@ -32,30 +32,32 @@ func (s *S3IdPDiscovery) CreateStorage(ctx context.Context) error { // Upload uploads the OIDC provider's discovery configuration and JSON Web Key Set (JWKS) to the specified AWS S3 bucket. // This method is responsible for uploading the necessary OIDC configuration files to S3, making them accessible for OIDC clients. -func (s *S3IdPDiscovery) Upload(ctx context.Context, o selfhosted.OIDCIdPDiscoveryContents) error { +func (s *S3IdPDiscovery) Upload(ctx context.Context, o selfhosted.OIDCIdPDiscoveryContents, forceUpdate bool) error { discovery, err := o.Discovery() if err != nil { return nil } - err = s.s3Client.CreateObjectPublic(ctx, - CONFIGURATION_PATH, - discovery, - ) - if err != nil { - return fmt.Errorf("unable to upload discovery document, %w", err) - } - - // Uplaod JWK jwk, err := o.JWK() if err != nil { return nil } - err = s.s3Client.CreateObjectPublic(ctx, - o.JWKsFileName(), - jwk, - ) + inputs := []client.ObjectInput{ + { + Key: CONFIGURATION_PATH, + Body: discovery, + }, + { + Key: o.JWKsFileName(), + Body: jwk, + }, + } + if forceUpdate { + err = s.s3Client.PutObjectsPublic(ctx, inputs) + } else { + err = s.s3Client.CreateObjectsPublic(ctx, inputs) + } if err != nil { - return fmt.Errorf("unable to upload JWK, %w", err) + return fmt.Errorf("unable to upload object, %w", err) } return nil } diff --git a/internal/selfhosted/selfhosted.go b/internal/selfhosted/selfhosted.go index a90eb77..92ef63d 100644 --- a/internal/selfhosted/selfhosted.go +++ b/internal/selfhosted/selfhosted.go @@ -2,7 +2,7 @@ package selfhosted import "context" -func Execute(ctx context.Context, factory OIDCIdPFactory) error { +func Execute(ctx context.Context, factory OIDCIdPFactory, forceUpdate bool) error { issuerMeta := factory.IssuerMeta() discovery := factory.IdPDiscovery() discoveryContents := factory.IdPDiscoveryContents(issuerMeta) @@ -14,7 +14,7 @@ func Execute(ctx context.Context, factory OIDCIdPFactory) error { if err != nil { return err } - err = discovery.Upload(ctx, discoveryContents) + err = discovery.Upload(ctx, discoveryContents, forceUpdate) if err != nil { return err }