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
|
||||
|
||||
import (
|
||||
"slices"
|
||||
|
||||
apimeta "k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -55,6 +58,18 @@ type IRSAServiceAccount struct {
|
||||
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
|
||||
type IamRole struct {
|
||||
// Name represents the name of the IAM role.
|
||||
@@ -64,6 +79,60 @@ type IamRole struct {
|
||||
// IRSAStatus defines the observed state of IRSA.
|
||||
type IRSAStatus struct {
|
||||
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
|
||||
@@ -93,11 +162,38 @@ func IRSAStatusNotReady(irsa IRSA, reason, message string) 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
|
||||
|
||||
const (
|
||||
IRSAReasonFailedRoleUpdate IRSAReason = "IRSAFailedRoleUpdate"
|
||||
IRSAReasonFailedK8sApply IRSAReason = "IRSAFailedApplyingResources"
|
||||
IRSAReasonFailedK8sCleanUp IRSAReason = "IRSAFailedDeletingResources"
|
||||
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
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (in *IRSAServiceAccount) DeepCopyInto(out *IRSAServiceAccount) {
|
||||
*out = *in
|
||||
@@ -249,6 +264,11 @@ func (in *IRSAStatus) DeepCopyInto(out *IRSAStatus) {
|
||||
(*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.
|
||||
@@ -290,3 +310,22 @@ func (in *S3Discovery) DeepCopy() *S3Discovery {
|
||||
in.DeepCopyInto(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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user