mirror of
https://github.com/storytold/irsa-manager.git
synced 2026-10-09 00:09:43 +00:00
manage selfhosted status
This commit is contained in:
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user