manage selfhosted status

This commit is contained in:
kkb0318
2024-04-16 22:44:14 +09:00
parent 09167b9469
commit f7208963ae
6 changed files with 167 additions and 53 deletions
+49 -6
View File
@@ -17,6 +17,8 @@ limitations under the License.
package v1alpha1 package v1alpha1
import ( import (
"github.com/fluxcd/pkg/apis/meta"
apimeta "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
) )
@@ -71,18 +73,59 @@ type SecretRef struct {
// IRSASetupStatus defines the observed state of IRSASetup // IRSASetupStatus defines the observed state of IRSASetup
type IRSASetupStatus struct { type IRSASetupStatus struct {
SelfHostedSetup CommonStatus `json:"selfHostedSetup,omitempty"` SelfHostedSetup []metav1.Condition `json:"selfHostedSetup,omitempty"`
CommonSetup CommonStatus `json:"commonSetup,omitempty"`
} }
// CommonStatus is a set of status attributes // GetStatusConditions returns a pointer to the Status.Conditions slice
type CommonStatus struct { func (in *IRSASetup) GetSelfhostedStatusConditions() *[]metav1.Condition {
Healthy bool `json:"healthy"` return &in.Status.SelfHostedSetup
Errors []string `json:"errors,omitempty"`
} }
func IRSASetupSelfHostedReady(irsa IRSASetup, reason, message string) IRSASetup {
newCondition := metav1.Condition{
Type: meta.ReadyCondition,
Status: metav1.ConditionTrue,
Reason: reason,
Message: message,
}
apimeta.SetStatusCondition(irsa.GetSelfhostedStatusConditions(), newCondition)
return irsa
}
func IRSASetupSelfHostedNotReady(irsa IRSASetup, reason, message string) IRSASetup {
newCondition := metav1.Condition{
Type: meta.ReadyCondition,
Status: metav1.ConditionFalse,
Reason: reason,
Message: message,
}
apimeta.SetStatusCondition(irsa.GetSelfhostedStatusConditions(), newCondition)
return irsa
}
// IRSASetupSelfHostedReadyStatus
func IRSASetupSelfHostedReadyStatus(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
}
func IsSelfHostedReadyConditionTrue(irsa IRSASetup) bool {
return apimeta.IsStatusConditionTrue(irsa.Status.SelfHostedSetup, meta.ReadyCondition)
}
type SelfHostedReason string
const (
SelfHostedReasonFailedOidc SelfHostedReason = "SelfHostedSetupFailedOidcCreation"
SelfHostedReasonFailedKeys SelfHostedReason = "SelfHostedSetupFailedKeysCreation"
)
//+kubebuilder:object:root=true //+kubebuilder:object:root=true
//+kubebuilder:subresource:status //+kubebuilder:subresource:status
// +kubebuilder:printcolumn:name="SelfHostedReady",type="string",JSONPath=".status.selfHostedSetup.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 {
+8 -22
View File
@@ -21,6 +21,7 @@ limitations under the License.
package v1alpha1 package v1alpha1
import ( import (
"k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime" runtime "k8s.io/apimachinery/pkg/runtime"
) )
@@ -40,26 +41,6 @@ func (in *Auth) DeepCopy() *Auth {
return out return out
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CommonStatus) DeepCopyInto(out *CommonStatus) {
*out = *in
if in.Errors != nil {
in, out := &in.Errors, &out.Errors
*out = make([]string, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CommonStatus.
func (in *CommonStatus) DeepCopy() *CommonStatus {
if in == nil {
return nil
}
out := new(CommonStatus)
in.DeepCopyInto(out)
return out
}
// 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 *Discovery) DeepCopyInto(out *Discovery) { func (in *Discovery) DeepCopyInto(out *Discovery) {
*out = *in *out = *in
@@ -155,8 +136,13 @@ 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
in.SelfHostedSetup.DeepCopyInto(&out.SelfHostedSetup) if in.SelfHostedSetup != nil {
in.CommonSetup.DeepCopyInto(&out.CommonSetup) in, out := &in.SelfHostedSetup, &out.SelfHostedSetup
*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 IRSASetupStatus. // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IRSASetupStatus.
@@ -14,7 +14,11 @@ spec:
singular: irsasetup singular: irsasetup
scope: Namespaced scope: Namespaced
versions: versions:
- name: v1alpha1 - additionalPrinterColumns:
- jsonPath: .status.selfHostedSetup.conditions[?(@.type=="Ready")].status
name: SelfHostedReady
type: string
name: v1alpha1
schema: schema:
openAPIV3Schema: openAPIV3Schema:
description: IRSASetup represents a configuration for setting up IAM Roles description: IRSASetup represents a configuration for setting up IAM Roles
@@ -92,30 +96,75 @@ spec:
status: status:
description: IRSASetupStatus defines the observed state of IRSASetup description: IRSASetupStatus defines the observed state of IRSASetup
properties: properties:
commonSetup:
description: CommonStatus is a set of status attributes
properties:
errors:
items:
type: string
type: array
healthy:
type: boolean
required:
- healthy
type: object
selfHostedSetup: selfHostedSetup:
description: CommonStatus is a set of status attributes items:
properties: description: "Condition contains details for one aspect of the current
errors: state of this API Resource.\n---\nThis struct is intended for
items: 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 type: string
type: array message:
healthy: description: |-
type: boolean message is a human readable message indicating details about the transition.
required: This may be an empty string.
- healthy maxLength: 32768
type: object 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
type: object type: object
served: true served: true
+3 -2
View File
@@ -1,8 +1,8 @@
module github.com/kkb0318/irsa-manager module github.com/kkb0318/irsa-manager
go 1.21 go 1.22
toolchain go1.21.8 toolchain go1.22.2
require ( require (
github.com/aws/aws-sdk-go-v2 v1.26.1 github.com/aws/aws-sdk-go-v2 v1.26.1
@@ -10,6 +10,7 @@ require (
github.com/aws/aws-sdk-go-v2/service/iam v1.32.0 github.com/aws/aws-sdk-go-v2/service/iam v1.32.0
github.com/aws/aws-sdk-go-v2/service/s3 v1.53.1 github.com/aws/aws-sdk-go-v2/service/s3 v1.53.1
github.com/aws/smithy-go v1.20.2 github.com/aws/smithy-go v1.20.2
github.com/fluxcd/pkg/apis/meta v1.4.0
github.com/go-jose/go-jose/v4 v4.0.1 github.com/go-jose/go-jose/v4 v4.0.1
github.com/onsi/ginkgo/v2 v2.17.1 github.com/onsi/ginkgo/v2 v2.17.1
github.com/onsi/gomega v1.30.0 github.com/onsi/gomega v1.30.0
+2
View File
@@ -49,6 +49,8 @@ github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH
github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg= github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg=
github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ=
github.com/fluxcd/pkg/apis/meta v1.4.0 h1:nNdgB6FFHP3cubxZCViaCFDUVlAbpq9+hvKEIveOGMg=
github.com/fluxcd/pkg/apis/meta v1.4.0/go.mod h1:81sZ01ShTuLc1C3M1dFJNkINareBysvmrO1b8zJFFKs=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/go-jose/go-jose/v4 v4.0.1 h1:QVEPDE3OluqXBQZDcnNvQrInro2h0e4eqNbnZSWqS6U= github.com/go-jose/go-jose/v4 v4.0.1 h1:QVEPDE3OluqXBQZDcnNvQrInro2h0e4eqNbnZSWqS6U=
@@ -18,6 +18,7 @@ package controller
import ( import (
"context" "context"
"fmt"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime" ctrl "sigs.k8s.io/controller-runtime"
@@ -120,7 +121,16 @@ func (r *IRSASetupReconciler) reconcileDelete(ctx context.Context, obj *irsav1al
return nil return nil
} }
// reconcileSelfhosted ensures that the self-hosted resources are set up correctly.
// This function performs the following operations based on the state of the object:
// - If the self-hosted setup has previously succeeded, the function returns immediately without making changes.
// - If the self-hosted setup was previously attempted but failed, or if it's being run for the first time, it will attempt to create all necessary resources. This includes the creation of key pairs, JWKs, OIDC IDP configurations, and Kubernetes secrets.
// - 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 {
if irsav1alpha1.IsSelfHostedReadyConditionTrue(*obj) {
// Selfhosted Setup have already succeeded
return nil
}
keyPair, err := selfhosted.CreateKeyPair() keyPair, err := selfhosted.CreateKeyPair()
if err != nil { if err != nil {
return err return err
@@ -139,14 +149,37 @@ func reconcileSelfhosted(ctx context.Context, obj *irsav1alpha1.IRSASetup, awsCl
} }
kubeHandler := handler.NewKubernetesHandler(kubeClient) kubeHandler := handler.NewKubernetesHandler(kubeClient)
kubeHandler.Append(secret) kubeHandler.Append(secret)
var e error
var reason irsav1alpha1.SelfHostedReason
defer func() {
if e != nil {
*obj = irsav1alpha1.IRSASetupSelfHostedNotReady(*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) err = selfhosted.Execute(ctx, factory)
if err != nil { if err != nil {
e = err
reason = irsav1alpha1.SelfHostedReasonFailedOidc
return err return err
} }
err = kubeHandler.CreateAll(ctx) err = kubeHandler.CreateAll(ctx)
if err != nil { if err != nil {
e = err
reason = irsav1alpha1.SelfHostedReasonFailedKeys
return err return err
} }
*obj = irsav1alpha1.IRSASetupSelfHostedReady(*obj, "SelfHostedSetupReady", e.Error())
return nil return nil
} }