mirror of
https://github.com/storytold/irsa-manager.git
synced 2026-10-09 00:09:43 +00:00
feature: cleanup serviceAccount
This commit is contained in:
@@ -17,8 +17,11 @@ limitations under the License.
|
|||||||
package v1alpha1
|
package v1alpha1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"slices"
|
||||||
|
|
||||||
apimeta "k8s.io/apimachinery/pkg/api/meta"
|
apimeta "k8s.io/apimachinery/pkg/api/meta"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -55,6 +58,18 @@ type IRSAServiceAccount struct {
|
|||||||
Namespaces []string `json:"namespaces,omitempty"`
|
Namespaces []string `json:"namespaces,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NamespacedNameList returns a slice of types.NamespacedName constructed from the Name and Namespace settings.
|
||||||
|
func (sa *IRSAServiceAccount) NamespacedNameList() []types.NamespacedName {
|
||||||
|
namespacedName := make([]types.NamespacedName, len(sa.Namespaces))
|
||||||
|
for i, ns := range sa.Namespaces {
|
||||||
|
namespacedName[i] = types.NamespacedName{
|
||||||
|
Name: sa.Name,
|
||||||
|
Namespace: ns,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return namespacedName
|
||||||
|
}
|
||||||
|
|
||||||
// IamRole represents the IAM role configuration
|
// IamRole represents the IAM role configuration
|
||||||
type IamRole struct {
|
type IamRole struct {
|
||||||
// Name represents the name of the IAM role.
|
// Name represents the name of the IAM role.
|
||||||
@@ -64,6 +79,60 @@ type IamRole struct {
|
|||||||
// IRSAStatus defines the observed state of IRSA.
|
// IRSAStatus defines the observed state of IRSA.
|
||||||
type IRSAStatus struct {
|
type IRSAStatus struct {
|
||||||
Conditions []metav1.Condition `json:"conditions,omitempty"`
|
Conditions []metav1.Condition `json:"conditions,omitempty"`
|
||||||
|
// Inventory of applied service resources
|
||||||
|
ServiceAccounts StatusServiceAccountList `json:"serviceAccounts,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type StatusServiceAccountList []IRSANamespacedNameWithTags
|
||||||
|
|
||||||
|
func (s *StatusServiceAccountList) IsExist(nsNames types.NamespacedName) bool {
|
||||||
|
return slices.ContainsFunc(*s, func(sa IRSANamespacedNameWithTags) bool {
|
||||||
|
return sa.Name == nsNames.Name && sa.Name == nsNames.Namespace
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append adds a new IRSANamespacedNameWithTags to the StatusServiceAccountList.
|
||||||
|
// If the provided NamespacedName already exists in the list, it will be ignored.
|
||||||
|
func (s *StatusServiceAccountList) Append(nsNames types.NamespacedName) {
|
||||||
|
*s = append(*s, IRSANamespacedNameWithTags{
|
||||||
|
Name: nsNames.Name,
|
||||||
|
Namespace: nsNames.Namespace,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete removes an IRSANamespacedNameWithTags from the StatusServiceAccountList
|
||||||
|
// that matches the provided NamespacedName. If the provided NamespacedName does
|
||||||
|
// not exist in the list, the method does nothing.
|
||||||
|
func (s *StatusServiceAccountList) Delete(nsNames types.NamespacedName) {
|
||||||
|
index := slices.IndexFunc(*s, func(sa IRSANamespacedNameWithTags) bool {
|
||||||
|
return sa.Name == nsNames.Name && sa.Namespace == nsNames.Namespace
|
||||||
|
})
|
||||||
|
if index != -1 {
|
||||||
|
*s = slices.Delete(*s, index, index+1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IRSANamespacedNameWithTags is like a types.NamespacedName with JSON tags
|
||||||
|
type IRSANamespacedNameWithTags struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Namespace string `json:"namespace"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *IRSAStatus) ServiceNamespacedNameList() []types.NamespacedName {
|
||||||
|
namespacedNameList := make([]types.NamespacedName, len(s.ServiceAccounts))
|
||||||
|
for i, n := range s.ServiceAccounts {
|
||||||
|
namespacedNameList[i] = types.NamespacedName{
|
||||||
|
Name: n.Name,
|
||||||
|
Namespace: n.Namespace,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return namespacedNameList
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetIRSAStatusServiceAccounts returns a pointer to the ServiceAccount slice
|
||||||
|
func (in *IRSA) GetIRSAStatusServiceAccounts() *StatusServiceAccountList {
|
||||||
|
return &in.Status.ServiceAccounts
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetIRSAStatusConditions returns a pointer to the Conditions slice
|
// GetIRSAStatusConditions returns a pointer to the Conditions slice
|
||||||
@@ -93,11 +162,38 @@ func IRSAStatusNotReady(irsa IRSA, reason, message string) IRSA {
|
|||||||
return irsa
|
return irsa
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IRSAStatusSetServiceAccount(irsa IRSA, namespacedNames []types.NamespacedName) IRSA {
|
||||||
|
for _, namespacedName := range namespacedNames {
|
||||||
|
setStatusServiceAccounts(irsa.GetIRSAStatusServiceAccounts(), namespacedName)
|
||||||
|
}
|
||||||
|
return irsa
|
||||||
|
}
|
||||||
|
|
||||||
|
func IRSAStatusRemoveServiceAccount(irsa IRSA, namespacedNames []types.NamespacedName) IRSA {
|
||||||
|
for _, namespacedName := range namespacedNames {
|
||||||
|
removeStatusServiceAccounts(irsa.GetIRSAStatusServiceAccounts(), namespacedName)
|
||||||
|
}
|
||||||
|
return irsa
|
||||||
|
}
|
||||||
|
|
||||||
|
func setStatusServiceAccounts(s *StatusServiceAccountList, namespacedName types.NamespacedName) {
|
||||||
|
if !s.IsExist(namespacedName) {
|
||||||
|
s.Append(namespacedName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeStatusServiceAccounts(s *StatusServiceAccountList, namespacedName types.NamespacedName) {
|
||||||
|
if s.IsExist(namespacedName) {
|
||||||
|
s.Append(namespacedName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type IRSAReason string
|
type IRSAReason string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
IRSAReasonFailedRoleUpdate IRSAReason = "IRSAFailedRoleUpdate"
|
IRSAReasonFailedRoleUpdate IRSAReason = "IRSAFailedRoleUpdate"
|
||||||
IRSAReasonFailedK8sApply IRSAReason = "IRSAFailedApplyingResources"
|
IRSAReasonFailedK8sApply IRSAReason = "IRSAFailedApplyingResources"
|
||||||
|
IRSAReasonFailedK8sCleanUp IRSAReason = "IRSAFailedDeletingResources"
|
||||||
IRSAReasonReady IRSAReason = "IRSAReady"
|
IRSAReasonReady IRSAReason = "IRSAReady"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
package v1alpha1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStatusServiceAccountList_Append(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
initial StatusServiceAccountList
|
||||||
|
toAppend types.NamespacedName
|
||||||
|
expected StatusServiceAccountList
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Append new item",
|
||||||
|
initial: StatusServiceAccountList{
|
||||||
|
{Name: "existing", Namespace: "default"},
|
||||||
|
},
|
||||||
|
toAppend: types.NamespacedName{Name: "new", Namespace: "default"},
|
||||||
|
expected: StatusServiceAccountList{
|
||||||
|
{Name: "existing", Namespace: "default"},
|
||||||
|
{Name: "new", Namespace: "default"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Append existing item",
|
||||||
|
initial: StatusServiceAccountList{
|
||||||
|
{Name: "existing", Namespace: "default"},
|
||||||
|
},
|
||||||
|
toAppend: types.NamespacedName{Name: "existing", Namespace: "default"},
|
||||||
|
expected: StatusServiceAccountList{
|
||||||
|
{Name: "existing", Namespace: "default"},
|
||||||
|
{Name: "existing", Namespace: "default"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
tt.initial.Append(tt.toAppend)
|
||||||
|
assert.Equal(t, tt.expected, tt.initial)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStatusServiceAccountList_Delete(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
initial StatusServiceAccountList
|
||||||
|
toDelete types.NamespacedName
|
||||||
|
expected StatusServiceAccountList
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Delete existing item",
|
||||||
|
initial: StatusServiceAccountList{
|
||||||
|
{Name: "existing", Namespace: "default"},
|
||||||
|
{Name: "todelete", Namespace: "default"},
|
||||||
|
},
|
||||||
|
toDelete: types.NamespacedName{Name: "todelete", Namespace: "default"},
|
||||||
|
expected: StatusServiceAccountList{
|
||||||
|
{Name: "existing", Namespace: "default"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Delete non-existing item",
|
||||||
|
initial: StatusServiceAccountList{
|
||||||
|
{Name: "existing", Namespace: "default"},
|
||||||
|
},
|
||||||
|
toDelete: types.NamespacedName{Name: "nonexisting", Namespace: "default"},
|
||||||
|
expected: StatusServiceAccountList{
|
||||||
|
{Name: "existing", Namespace: "default"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
tt.initial.Delete(tt.toDelete)
|
||||||
|
assert.Equal(t, tt.expected, tt.initial)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -100,6 +100,21 @@ func (in *IRSAList) DeepCopyObject() runtime.Object {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *IRSANamespacedNameWithTags) DeepCopyInto(out *IRSANamespacedNameWithTags) {
|
||||||
|
*out = *in
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IRSANamespacedNameWithTags.
|
||||||
|
func (in *IRSANamespacedNameWithTags) DeepCopy() *IRSANamespacedNameWithTags {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(IRSANamespacedNameWithTags)
|
||||||
|
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 *IRSAServiceAccount) DeepCopyInto(out *IRSAServiceAccount) {
|
func (in *IRSAServiceAccount) DeepCopyInto(out *IRSAServiceAccount) {
|
||||||
*out = *in
|
*out = *in
|
||||||
@@ -249,6 +264,11 @@ func (in *IRSAStatus) DeepCopyInto(out *IRSAStatus) {
|
|||||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if in.ServiceAccounts != nil {
|
||||||
|
in, out := &in.ServiceAccounts, &out.ServiceAccounts
|
||||||
|
*out = make(StatusServiceAccountList, len(*in))
|
||||||
|
copy(*out, *in)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IRSAStatus.
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IRSAStatus.
|
||||||
@@ -290,3 +310,22 @@ func (in *S3Discovery) DeepCopy() *S3Discovery {
|
|||||||
in.DeepCopyInto(out)
|
in.DeepCopyInto(out)
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in StatusServiceAccountList) DeepCopyInto(out *StatusServiceAccountList) {
|
||||||
|
{
|
||||||
|
in := &in
|
||||||
|
*out = make(StatusServiceAccountList, len(*in))
|
||||||
|
copy(*out, *in)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StatusServiceAccountList.
|
||||||
|
func (in StatusServiceAccountList) DeepCopy() StatusServiceAccountList {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(StatusServiceAccountList)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return *out
|
||||||
|
}
|
||||||
|
|||||||
@@ -152,6 +152,21 @@ spec:
|
|||||||
- type
|
- type
|
||||||
type: object
|
type: object
|
||||||
type: array
|
type: array
|
||||||
|
serviceAccounts:
|
||||||
|
description: Inventory of applied service resources
|
||||||
|
items:
|
||||||
|
description: IRSANamespacedNameWithTags is like a types.NamespacedName
|
||||||
|
with JSON tags
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
namespace:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- name
|
||||||
|
- namespace
|
||||||
|
type: object
|
||||||
|
type: array
|
||||||
type: object
|
type: object
|
||||||
type: object
|
type: object
|
||||||
served: true
|
served: true
|
||||||
|
|||||||
@@ -153,6 +153,21 @@ spec:
|
|||||||
- type
|
- type
|
||||||
type: object
|
type: object
|
||||||
type: array
|
type: array
|
||||||
|
serviceAccounts:
|
||||||
|
description: Inventory of applied service resources
|
||||||
|
items:
|
||||||
|
description: IRSANamespacedNameWithTags is like a types.NamespacedName
|
||||||
|
with JSON tags
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
namespace:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- name
|
||||||
|
- namespace
|
||||||
|
type: object
|
||||||
|
type: array
|
||||||
type: object
|
type: object
|
||||||
type: object
|
type: object
|
||||||
served: true
|
served: true
|
||||||
|
|||||||
@@ -51,6 +51,8 @@ IRSA is the Schema for the irsas API
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#### IRSAServiceAccount
|
#### IRSAServiceAccount
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import (
|
|||||||
"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"
|
||||||
"github.com/kkb0318/irsa-manager/internal/manifests"
|
"github.com/kkb0318/irsa-manager/internal/manifests"
|
||||||
|
"github.com/kkb0318/irsa-manager/internal/utils"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
ctrl "sigs.k8s.io/controller-runtime"
|
ctrl "sigs.k8s.io/controller-runtime"
|
||||||
@@ -118,8 +119,6 @@ func (r *IRSAReconciler) reconcileDelete(ctx context.Context, obj *irsav1alpha1.
|
|||||||
if !obj.Spec.Cleanup {
|
if !obj.Spec.Cleanup {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
serviceAccount := obj.Spec.ServiceAccount
|
|
||||||
kubeHandler := handler.NewKubernetesHandler(kubeClient)
|
|
||||||
roleManager := awsclient.RoleManager{
|
roleManager := awsclient.RoleManager{
|
||||||
RoleName: obj.Spec.IamRole.Name,
|
RoleName: obj.Spec.IamRole.Name,
|
||||||
Policies: obj.Spec.IamPolicies,
|
Policies: obj.Spec.IamPolicies,
|
||||||
@@ -131,15 +130,8 @@ func (r *IRSAReconciler) reconcileDelete(ctx context.Context, obj *irsav1alpha1.
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, ns := range serviceAccount.Namespaces {
|
deleted, err := cleanupKubernetesResources(ctx, kubeClient, obj.Spec.ServiceAccount.NamespacedNameList())
|
||||||
sa := manifests.NewServiceAccountBuilder().Build(types.NamespacedName{
|
*obj = irsav1alpha1.IRSAStatusSetServiceAccount(*obj, deleted)
|
||||||
Name: serviceAccount.Name,
|
|
||||||
Namespace: ns,
|
|
||||||
})
|
|
||||||
kubeHandler.Append(sa)
|
|
||||||
|
|
||||||
}
|
|
||||||
err = kubeHandler.DeleteAll(ctx)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -196,24 +188,46 @@ func (r *IRSAReconciler) reconcile(ctx context.Context, obj *irsav1alpha1.IRSA,
|
|||||||
}
|
}
|
||||||
|
|
||||||
kubeHandler := handler.NewKubernetesHandler(kubeClient)
|
kubeHandler := handler.NewKubernetesHandler(kubeClient)
|
||||||
|
for _, namespacedName := range serviceAccount.NamespacedNameList() {
|
||||||
for _, ns := range serviceAccount.Namespaces {
|
sa := manifests.NewServiceAccountBuilder().WithIRSAAnnotation(roleManager).Build(namespacedName)
|
||||||
sa := manifests.NewServiceAccountBuilder().WithIRSAAnnotation(roleManager).Build(types.NamespacedName{
|
|
||||||
Name: serviceAccount.Name,
|
|
||||||
Namespace: ns,
|
|
||||||
})
|
|
||||||
kubeHandler.Append(sa)
|
kubeHandler.Append(sa)
|
||||||
}
|
}
|
||||||
err = kubeHandler.ApplyAll(ctx)
|
applied, err := kubeHandler.ApplyAll(ctx)
|
||||||
|
*obj = irsav1alpha1.IRSAStatusSetServiceAccount(*obj, applied)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
e = err
|
e = err
|
||||||
reason = irsav1alpha1.IRSAReasonFailedK8sApply
|
reason = irsav1alpha1.IRSAReasonFailedK8sApply
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deleted, err := cleanupKubernetesResources(
|
||||||
|
ctx,
|
||||||
|
kubeClient,
|
||||||
|
utils.DiffNamespacedNames(obj.Status.ServiceNamespacedNameList(), serviceAccount.NamespacedNameList()),
|
||||||
|
)
|
||||||
|
*obj = irsav1alpha1.IRSAStatusSetServiceAccount(*obj, deleted)
|
||||||
|
if err != nil {
|
||||||
|
e = err
|
||||||
|
reason = irsav1alpha1.IRSAReasonFailedK8sCleanUp
|
||||||
|
return err
|
||||||
|
}
|
||||||
*obj = irsav1alpha1.IRSAStatusReady(*obj, string(irsav1alpha1.IRSAReasonReady), "successfully setup resources")
|
*obj = irsav1alpha1.IRSAStatusReady(*obj, string(irsav1alpha1.IRSAReasonReady), "successfully setup resources")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func cleanupKubernetesResources(ctx context.Context, client *kubernetes.KubernetesClient, nsNames []types.NamespacedName) ([]types.NamespacedName, error) {
|
||||||
|
kubeHandler := handler.NewKubernetesHandler(client)
|
||||||
|
for _, namespacedName := range nsNames {
|
||||||
|
sa := manifests.NewServiceAccountBuilder().Build(namespacedName)
|
||||||
|
kubeHandler.Append(sa)
|
||||||
|
}
|
||||||
|
deleted, err := kubeHandler.DeleteAll(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return deleted, err
|
||||||
|
}
|
||||||
|
return deleted, nil
|
||||||
|
}
|
||||||
|
|
||||||
// SetupWithManager sets up the controller with the Manager.
|
// SetupWithManager sets up the controller with the Manager.
|
||||||
func (r *IRSAReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
func (r *IRSAReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||||
return ctrl.NewControllerManagedBy(mgr).
|
return ctrl.NewControllerManagedBy(mgr).
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ limitations under the License.
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
. "github.com/onsi/ginkgo/v2"
|
. "github.com/onsi/ginkgo/v2"
|
||||||
@@ -80,10 +81,6 @@ var _ = Describe("IRSA Controller", func() {
|
|||||||
NamespacedName: typeNamespacedName,
|
NamespacedName: typeNamespacedName,
|
||||||
})
|
})
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
_, err = r.Reconcile(ctx, reconcile.Request{
|
|
||||||
NamespacedName: typeNamespacedName,
|
|
||||||
})
|
|
||||||
Expect(err).NotTo(HaveOccurred())
|
|
||||||
for _, expect := range expected {
|
for _, expect := range expected {
|
||||||
checkExist(expect)
|
checkExist(expect)
|
||||||
}
|
}
|
||||||
@@ -101,7 +98,7 @@ var _ = Describe("IRSA Controller", func() {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "error",
|
name: "AWS API Error Case",
|
||||||
obj: &irsav1alpha1.IRSA{
|
obj: &irsav1alpha1.IRSA{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Name: "test-resource2",
|
Name: "test-resource2",
|
||||||
@@ -169,6 +166,75 @@ var _ = Describe("IRSA Controller", func() {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "should update serviceaccount successfully",
|
||||||
|
obj: &irsav1alpha1.IRSA{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "test-resource3",
|
||||||
|
Namespace: "default",
|
||||||
|
},
|
||||||
|
Spec: irsav1alpha1.IRSASpec{
|
||||||
|
Cleanup: true,
|
||||||
|
ServiceAccount: irsav1alpha1.IRSAServiceAccount{
|
||||||
|
Name: "sa-3",
|
||||||
|
Namespaces: []string{
|
||||||
|
"kube-system",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
irsaSetupObj: newMockIRSASetup(),
|
||||||
|
f: func(r *IRSAReconciler, obj *irsav1alpha1.IRSA) {
|
||||||
|
expected := []expectedResource{
|
||||||
|
{
|
||||||
|
NamespacedName: types.NamespacedName{Name: "sa-3", Namespace: "kube-system"},
|
||||||
|
f: newServiceAccount,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
NamespacedName: types.NamespacedName{Name: "sa-3", 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-3", Namespace: "kube-system"},
|
||||||
|
f: newServiceAccount,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
f := createCallBack(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-3", Namespace: "kube-system"},
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
It(tt.name, func() {
|
It(tt.name, func() {
|
||||||
@@ -231,3 +297,31 @@ func newMockIRSASetup() *irsav1alpha1.IRSASetup {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func createCallBack(ctx context.Context, r *IRSAReconciler, typeNamespacedName types.NamespacedName, obj *irsav1alpha1.IRSA) func(name string, namespaces []string) {
|
||||||
|
return func(name string, namespaces []string) {
|
||||||
|
fixNamespacesAndReconcile(ctx, r, typeNamespacedName, obj, name, namespaces)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fixNamespacesAndReconcile(ctx context.Context, r *IRSAReconciler, typeNamespacedName types.NamespacedName, obj *irsav1alpha1.IRSA, name string, namespaces []string) {
|
||||||
|
Expect(k8sClient.Get(ctx, typeNamespacedName, obj)).NotTo(HaveOccurred())
|
||||||
|
Eventually(func() error {
|
||||||
|
obj.Spec.ServiceAccount.Namespaces = namespaces
|
||||||
|
return k8sClient.Update(ctx, obj)
|
||||||
|
}, timeout).Should(Succeed())
|
||||||
|
_, err := r.Reconcile(ctx, reconcile.Request{
|
||||||
|
NamespacedName: typeNamespacedName,
|
||||||
|
})
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
expected := []expectedResource{}
|
||||||
|
for _, ns := range namespaces {
|
||||||
|
expected = append(expected, expectedResource{
|
||||||
|
NamespacedName: types.NamespacedName{Name: name, Namespace: ns},
|
||||||
|
f: newServiceAccount,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for _, e := range expected {
|
||||||
|
checkExist(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ func (r *IRSASetupReconciler) reconcileDelete(ctx context.Context, obj *irsav1al
|
|||||||
for _, r := range webhookSetup.Resources() {
|
for _, r := range webhookSetup.Resources() {
|
||||||
kubeHandler.Append(r)
|
kubeHandler.Append(r)
|
||||||
}
|
}
|
||||||
err = kubeHandler.DeleteAll(ctx)
|
_, err = kubeHandler.DeleteAll(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -233,7 +233,7 @@ func reconcileSelfhosted(ctx context.Context, obj *irsav1alpha1.IRSASetup, awsCl
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if forceUpdate {
|
if forceUpdate {
|
||||||
err = kubeHandlerForOidc.ApplyAll(ctx)
|
_, err = kubeHandlerForOidc.ApplyAll(ctx)
|
||||||
} else {
|
} else {
|
||||||
err = kubeHandlerForOidc.CreateAll(ctx)
|
err = kubeHandlerForOidc.CreateAll(ctx)
|
||||||
}
|
}
|
||||||
@@ -247,7 +247,7 @@ func reconcileSelfhosted(ctx context.Context, obj *irsav1alpha1.IRSASetup, awsCl
|
|||||||
for _, r := range webhookSetup.Resources() {
|
for _, r := range webhookSetup.Resources() {
|
||||||
kubeHandlerForWebhook.Append(r)
|
kubeHandlerForWebhook.Append(r)
|
||||||
}
|
}
|
||||||
err = kubeHandlerForWebhook.ApplyAll(ctx)
|
_, err = kubeHandlerForWebhook.ApplyAll(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
e = err
|
e = err
|
||||||
reason = irsav1alpha1.SelfHostedReasonFailedWebhook
|
reason = irsav1alpha1.SelfHostedReasonFailedWebhook
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
|
|
||||||
"k8s.io/apimachinery/pkg/api/errors"
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/types"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -39,24 +40,28 @@ func (k *KubernetesHandler) CreateAll(ctx context.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *KubernetesHandler) ApplyAll(ctx context.Context) error {
|
func (k *KubernetesHandler) ApplyAll(ctx context.Context) ([]types.NamespacedName, error) {
|
||||||
|
applied := []types.NamespacedName{}
|
||||||
for _, obj := range k.objs {
|
for _, obj := range k.objs {
|
||||||
err := k.client.Apply(ctx, obj)
|
err := k.client.Apply(ctx, obj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return applied, err
|
||||||
}
|
}
|
||||||
|
applied = append(applied, client.ObjectKeyFromObject(obj))
|
||||||
}
|
}
|
||||||
return nil
|
return applied, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *KubernetesHandler) DeleteAll(ctx context.Context) error {
|
func (k *KubernetesHandler) DeleteAll(ctx context.Context) ([]types.NamespacedName, error) {
|
||||||
|
deleted := []types.NamespacedName{}
|
||||||
for _, obj := range k.objs {
|
for _, obj := range k.objs {
|
||||||
err := k.client.Delete(ctx, obj, DeleteOptions{
|
err := k.client.Delete(ctx, obj, DeleteOptions{
|
||||||
DeletionPropagation: metav1.DeletePropagationBackground,
|
DeletionPropagation: metav1.DeletePropagationBackground,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return deleted, err
|
||||||
}
|
}
|
||||||
|
deleted = append(deleted, client.ObjectKeyFromObject(obj))
|
||||||
}
|
}
|
||||||
return nil
|
return deleted, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DiffNamespacedNames returns the namespaced names that are in target but not in reference.
|
||||||
|
func DiffNamespacedNames(target, reference []types.NamespacedName) []types.NamespacedName {
|
||||||
|
referenceSet := make(map[types.NamespacedName]struct{})
|
||||||
|
|
||||||
|
for _, item := range reference {
|
||||||
|
referenceSet[item] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
diff := []types.NamespacedName{}
|
||||||
|
for _, item := range target {
|
||||||
|
if _, exists := referenceSet[item]; !exists {
|
||||||
|
diff = append(diff, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return diff
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDiffNamespacedNames(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
target []types.NamespacedName
|
||||||
|
reference []types.NamespacedName
|
||||||
|
expected []types.NamespacedName
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"NoDifference",
|
||||||
|
[]types.NamespacedName{
|
||||||
|
{Namespace: "default", Name: "resource1"},
|
||||||
|
{Namespace: "default", Name: "resource2"},
|
||||||
|
},
|
||||||
|
[]types.NamespacedName{
|
||||||
|
{Namespace: "default", Name: "resource1"},
|
||||||
|
{Namespace: "default", Name: "resource2"},
|
||||||
|
},
|
||||||
|
[]types.NamespacedName{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"SomeDifference",
|
||||||
|
[]types.NamespacedName{
|
||||||
|
{Namespace: "default", Name: "resource1"},
|
||||||
|
{Namespace: "default", Name: "resource3"},
|
||||||
|
},
|
||||||
|
[]types.NamespacedName{
|
||||||
|
{Namespace: "default", Name: "resource1"},
|
||||||
|
{Namespace: "default", Name: "resource2"},
|
||||||
|
},
|
||||||
|
[]types.NamespacedName{
|
||||||
|
{Namespace: "default", Name: "resource3"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"AllDifferent",
|
||||||
|
[]types.NamespacedName{
|
||||||
|
{Namespace: "default", Name: "resource1"},
|
||||||
|
{Namespace: "default", Name: "resource2"},
|
||||||
|
},
|
||||||
|
[]types.NamespacedName{
|
||||||
|
{Namespace: "other", Name: "resource3"},
|
||||||
|
{Namespace: "other", Name: "resource4"},
|
||||||
|
},
|
||||||
|
[]types.NamespacedName{
|
||||||
|
{Namespace: "default", Name: "resource1"},
|
||||||
|
{Namespace: "default", Name: "resource2"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"EmptyTarget",
|
||||||
|
[]types.NamespacedName{},
|
||||||
|
[]types.NamespacedName{
|
||||||
|
{Namespace: "default", Name: "resource1"},
|
||||||
|
{Namespace: "default", Name: "resource2"},
|
||||||
|
},
|
||||||
|
[]types.NamespacedName{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"EmptyReference",
|
||||||
|
[]types.NamespacedName{
|
||||||
|
{Namespace: "default", Name: "resource1"},
|
||||||
|
{Namespace: "default", Name: "resource2"},
|
||||||
|
},
|
||||||
|
[]types.NamespacedName{},
|
||||||
|
[]types.NamespacedName{
|
||||||
|
{Namespace: "default", Name: "resource1"},
|
||||||
|
{Namespace: "default", Name: "resource2"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result := DiffNamespacedNames(tt.target, tt.reference)
|
||||||
|
assert.Equal(t, tt.expected, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user