diff --git a/api/main.go b/api/main.go index 9b69850c0..8e6d0bfaf 100644 --- a/api/main.go +++ b/api/main.go @@ -128,14 +128,14 @@ func main() { privilegedCRClient, userClientFactory, nsPermissions, - conditions.NewConditionAwaiter[*korifiv1alpha1.CFOrg, korifiv1alpha1.CFOrgList](conditionTimeout), + conditions.NewConditionAwaiter[*korifiv1alpha1.CFOrg, korifiv1alpha1.CFOrg, korifiv1alpha1.CFOrgList](conditionTimeout), ) spaceRepo := repositories.NewSpaceRepo( namespaceRetriever, orgRepo, userClientFactory, nsPermissions, - conditions.NewConditionAwaiter[*korifiv1alpha1.CFSpace, korifiv1alpha1.CFSpaceList](conditionTimeout), + conditions.NewConditionAwaiter[*korifiv1alpha1.CFSpace, korifiv1alpha1.CFSpace, korifiv1alpha1.CFSpaceList](conditionTimeout), ) processRepo := repositories.NewProcessRepo( namespaceRetriever, @@ -149,7 +149,7 @@ func main() { namespaceRetriever, userClientFactory, nsPermissions, - conditions.NewConditionAwaiter[*korifiv1alpha1.CFApp, korifiv1alpha1.CFAppList](conditionTimeout), + conditions.NewConditionAwaiter[*korifiv1alpha1.CFApp, korifiv1alpha1.CFApp, korifiv1alpha1.CFAppList](conditionTimeout), ) dropletRepo := repositories.NewDropletRepo( userClientFactory, @@ -189,19 +189,19 @@ func main() { nsPermissions, toolsregistry.NewRepositoryCreator(cfg.ContainerRegistryType), cfg.ContainerRepositoryPrefix, - conditions.NewConditionAwaiter[*korifiv1alpha1.CFPackage, korifiv1alpha1.CFPackageList](conditionTimeout), + conditions.NewConditionAwaiter[*korifiv1alpha1.CFPackage, korifiv1alpha1.CFPackage, korifiv1alpha1.CFPackageList](conditionTimeout), ) serviceInstanceRepo := repositories.NewServiceInstanceRepo( namespaceRetriever, userClientFactory, nsPermissions, - conditions.NewConditionAwaiter[*korifiv1alpha1.CFServiceInstance, korifiv1alpha1.CFServiceInstanceList](conditionTimeout), + conditions.NewConditionAwaiter[*korifiv1alpha1.CFServiceInstance, korifiv1alpha1.CFServiceInstance, korifiv1alpha1.CFServiceInstanceList](conditionTimeout), ) serviceBindingRepo := repositories.NewServiceBindingRepo( namespaceRetriever, userClientFactory, nsPermissions, - conditions.NewConditionAwaiter[*korifiv1alpha1.CFServiceBinding, korifiv1alpha1.CFServiceBindingList](conditionTimeout), + conditions.NewConditionAwaiter[*korifiv1alpha1.CFServiceBinding, korifiv1alpha1.CFServiceBinding, korifiv1alpha1.CFServiceBindingList](conditionTimeout), ) buildpackRepo := repositories.NewBuildpackRepository(cfg.BuilderName, userClientFactory, @@ -228,7 +228,7 @@ func main() { userClientFactory, namespaceRetriever, nsPermissions, - conditions.NewConditionAwaiter[*korifiv1alpha1.CFTask, korifiv1alpha1.CFTaskList](conditionTimeout), + conditions.NewConditionAwaiter[*korifiv1alpha1.CFTask, korifiv1alpha1.CFTask, korifiv1alpha1.CFTaskList](conditionTimeout), ) metricsRepo := repositories.NewMetricsRepo(userClientFactory) serviceBrokerRepo := repositories.NewServiceBrokerRepo(userClientFactory, cfg.RootNamespace) diff --git a/api/repositories/app_repository_test.go b/api/repositories/app_repository_test.go index 94aa8ab0b..7cb5de02b 100644 --- a/api/repositories/app_repository_test.go +++ b/api/repositories/app_repository_test.go @@ -38,6 +38,7 @@ var _ = Describe("AppRepository", func() { var ( appAwaiter *fakeawaiter.FakeAwaiter[ *korifiv1alpha1.CFApp, + korifiv1alpha1.CFApp, korifiv1alpha1.CFAppList, *korifiv1alpha1.CFAppList, ] @@ -50,6 +51,7 @@ var _ = Describe("AppRepository", func() { BeforeEach(func() { appAwaiter = &fakeawaiter.FakeAwaiter[ *korifiv1alpha1.CFApp, + korifiv1alpha1.CFApp, korifiv1alpha1.CFAppList, *korifiv1alpha1.CFAppList, ]{} diff --git a/api/repositories/conditions/await.go b/api/repositories/conditions/await.go index 40e0b7be1..bacf1ea9b 100644 --- a/api/repositories/conditions/await.go +++ b/api/repositories/conditions/await.go @@ -5,38 +5,34 @@ import ( "fmt" "time" + "code.cloudfoundry.org/korifi/tools/k8s" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" ) -type RuntimeObjectWithStatusConditions interface { - client.Object - StatusConditions() []metav1.Condition -} - type ObjectList[L any] interface { *L client.ObjectList } -type Awaiter[T RuntimeObjectWithStatusConditions, L any, PL ObjectList[L]] struct { +type Awaiter[T k8s.RuntimeObjectWithStatusConditions[TT], TT any, L any, PL ObjectList[L]] struct { timeout time.Duration } -func NewConditionAwaiter[T RuntimeObjectWithStatusConditions, L any, PL ObjectList[L]](timeout time.Duration) *Awaiter[T, L, PL] { - return &Awaiter[T, L, PL]{ +func NewConditionAwaiter[T k8s.RuntimeObjectWithStatusConditions[TT], TT any, L any, PL ObjectList[L]](timeout time.Duration) *Awaiter[T, TT, L, PL] { + return &Awaiter[T, TT, L, PL]{ timeout: timeout, } } -func (a *Awaiter[T, L, PL]) AwaitCondition(ctx context.Context, k8sClient client.WithWatch, object client.Object, conditionType string) (T, error) { +func (a *Awaiter[T, TT, L, PL]) AwaitCondition(ctx context.Context, k8sClient client.WithWatch, object client.Object, conditionType string) (T, error) { return a.AwaitState(ctx, k8sClient, object, func(o T) error { return checkConditionIsTrue(o, conditionType) }) } -func (a *Awaiter[T, L, PL]) AwaitState(ctx context.Context, k8sClient client.WithWatch, object client.Object, checkState func(T) error) (T, error) { +func (a *Awaiter[T, TT, L, PL]) AwaitState(ctx context.Context, k8sClient client.WithWatch, object client.Object, checkState func(T) error) (T, error) { var empty T objList := PL(new(L)) @@ -71,8 +67,8 @@ func (a *Awaiter[T, L, PL]) AwaitState(ctx context.Context, k8sClient client.Wit ) } -func checkConditionIsTrue[T RuntimeObjectWithStatusConditions](obj T, conditionType string) error { - condition := meta.FindStatusCondition(obj.StatusConditions(), conditionType) +func checkConditionIsTrue[T k8s.RuntimeObjectWithStatusConditions[L], L any](obj T, conditionType string) error { + condition := meta.FindStatusCondition(*obj.StatusConditions(), conditionType) if condition == nil { return fmt.Errorf("condition %s not set yet", conditionType) diff --git a/api/repositories/conditions/await_test.go b/api/repositories/conditions/await_test.go index f0e79994b..1ba277bcc 100644 --- a/api/repositories/conditions/await_test.go +++ b/api/repositories/conditions/await_test.go @@ -17,7 +17,7 @@ import ( var _ = Describe("ConditionAwaiter", func() { var ( - awaiter *conditions.Awaiter[*korifiv1alpha1.CFTask, korifiv1alpha1.CFTaskList, *korifiv1alpha1.CFTaskList] + awaiter *conditions.Awaiter[*korifiv1alpha1.CFTask, korifiv1alpha1.CFTask, korifiv1alpha1.CFTaskList, *korifiv1alpha1.CFTaskList] task *korifiv1alpha1.CFTask awaitedTask *korifiv1alpha1.CFTask awaitErr error @@ -43,7 +43,7 @@ var _ = Describe("ConditionAwaiter", func() { } BeforeEach(func() { - awaiter = conditions.NewConditionAwaiter[*korifiv1alpha1.CFTask, korifiv1alpha1.CFTaskList](time.Second) + awaiter = conditions.NewConditionAwaiter[*korifiv1alpha1.CFTask, korifiv1alpha1.CFTask, korifiv1alpha1.CFTaskList](time.Second) awaitedTask = nil awaitErr = nil diff --git a/api/repositories/fakeawaiter/await.go b/api/repositories/fakeawaiter/await.go index 29b33a470..de2a88d6f 100644 --- a/api/repositories/fakeawaiter/await.go +++ b/api/repositories/fakeawaiter/await.go @@ -4,10 +4,11 @@ import ( "context" "code.cloudfoundry.org/korifi/api/repositories/conditions" + "code.cloudfoundry.org/korifi/tools/k8s" "sigs.k8s.io/controller-runtime/pkg/client" ) -type FakeAwaiter[T conditions.RuntimeObjectWithStatusConditions, L any, PL conditions.ObjectList[L]] struct { +type FakeAwaiter[T k8s.RuntimeObjectWithStatusConditions[TT], TT any, L any, PL conditions.ObjectList[L]] struct { awaitConditionCalls []struct { obj client.Object conditionType string @@ -20,7 +21,7 @@ type FakeAwaiter[T conditions.RuntimeObjectWithStatusConditions, L any, PL condi } } -func (a *FakeAwaiter[T, L, PL]) AwaitCondition(ctx context.Context, k8sClient client.WithWatch, object client.Object, conditionType string) (T, error) { +func (a *FakeAwaiter[T, TT, L, PL]) AwaitCondition(ctx context.Context, k8sClient client.WithWatch, object client.Object, conditionType string) (T, error) { a.awaitConditionCalls = append(a.awaitConditionCalls, struct { obj client.Object conditionType string @@ -36,21 +37,21 @@ func (a *FakeAwaiter[T, L, PL]) AwaitCondition(ctx context.Context, k8sClient cl return a.AwaitConditionStub(ctx, k8sClient, object, conditionType) } -func (a *FakeAwaiter[T, L, PL]) AwaitConditionReturns(object T, err error) { +func (a *FakeAwaiter[T, TT, L, PL]) AwaitConditionReturns(object T, err error) { a.AwaitConditionStub = func(ctx context.Context, k8sClient client.WithWatch, object client.Object, conditionType string) (T, error) { return object.(T), err } } -func (a *FakeAwaiter[T, L, PL]) AwaitConditionCallCount() int { +func (a *FakeAwaiter[T, TT, L, PL]) AwaitConditionCallCount() int { return len(a.awaitConditionCalls) } -func (a *FakeAwaiter[T, L, PL]) AwaitConditionArgsForCall(i int) (client.Object, string) { +func (a *FakeAwaiter[T, TT, L, PL]) AwaitConditionArgsForCall(i int) (client.Object, string) { return a.awaitConditionCalls[i].obj, a.awaitConditionCalls[i].conditionType } -func (a *FakeAwaiter[T, L, PL]) AwaitState(ctx context.Context, k8sClient client.WithWatch, object client.Object, checkState func(T) error) (T, error) { +func (a *FakeAwaiter[T, TT, L, PL]) AwaitState(ctx context.Context, k8sClient client.WithWatch, object client.Object, checkState func(T) error) (T, error) { a.awaitStateCalls = append(a.awaitStateCalls, struct { obj client.Object checkState func(T) error @@ -66,16 +67,16 @@ func (a *FakeAwaiter[T, L, PL]) AwaitState(ctx context.Context, k8sClient client return a.AwaitStateStub(ctx, k8sClient, object, checkState) } -func (a *FakeAwaiter[T, L, PL]) AwaitStateReturns(object T, err error) { +func (a *FakeAwaiter[T, TT, L, PL]) AwaitStateReturns(object T, err error) { a.AwaitStateStub = func(ctx context.Context, k8sClient client.WithWatch, object client.Object, _ func(T) error) (T, error) { return object.(T), err } } -func (a *FakeAwaiter[T, L, PL]) AwaitStateCallCount() int { +func (a *FakeAwaiter[T, TT, L, PL]) AwaitStateCallCount() int { return len(a.awaitStateCalls) } -func (a *FakeAwaiter[T, L, PL]) AwaitStateArgsForCall(i int) (client.Object, func(T) error) { +func (a *FakeAwaiter[T, TT, L, PL]) AwaitStateArgsForCall(i int) (client.Object, func(T) error) { return a.awaitStateCalls[i].obj, a.awaitStateCalls[i].checkState } diff --git a/api/repositories/org_repository_test.go b/api/repositories/org_repository_test.go index 325cbe3a2..8622889a1 100644 --- a/api/repositories/org_repository_test.go +++ b/api/repositories/org_repository_test.go @@ -27,6 +27,7 @@ var _ = Describe("OrgRepository", func() { var ( conditionAwaiter *fakeawaiter.FakeAwaiter[ *korifiv1alpha1.CFOrg, + korifiv1alpha1.CFOrg, korifiv1alpha1.CFOrgList, *korifiv1alpha1.CFOrgList, ] @@ -36,6 +37,7 @@ var _ = Describe("OrgRepository", func() { BeforeEach(func() { conditionAwaiter = &fakeawaiter.FakeAwaiter[ *korifiv1alpha1.CFOrg, + korifiv1alpha1.CFOrg, korifiv1alpha1.CFOrgList, *korifiv1alpha1.CFOrgList, ]{} diff --git a/api/repositories/package_repository_test.go b/api/repositories/package_repository_test.go index 107c900f7..875ed00c2 100644 --- a/api/repositories/package_repository_test.go +++ b/api/repositories/package_repository_test.go @@ -31,6 +31,7 @@ var _ = Describe("PackageRepository", func() { repoCreator *fake.RepositoryCreator conditionAwaiter *fakeawaiter.FakeAwaiter[ *korifiv1alpha1.CFPackage, + korifiv1alpha1.CFPackage, korifiv1alpha1.CFPackageList, *korifiv1alpha1.CFPackageList, ] @@ -45,6 +46,7 @@ var _ = Describe("PackageRepository", func() { repoCreator = new(fake.RepositoryCreator) conditionAwaiter = &fakeawaiter.FakeAwaiter[ *korifiv1alpha1.CFPackage, + korifiv1alpha1.CFPackage, korifiv1alpha1.CFPackageList, *korifiv1alpha1.CFPackageList, ]{} diff --git a/api/repositories/role_repository_test.go b/api/repositories/role_repository_test.go index ef4cb86c8..29748afe2 100644 --- a/api/repositories/role_repository_test.go +++ b/api/repositories/role_repository_test.go @@ -46,11 +46,13 @@ var _ = Describe("RoleRepository", func() { } orgRepo := repositories.NewOrgRepo(rootNamespace, k8sClient, userClientFactory, nsPerms, &fakeawaiter.FakeAwaiter[ *korifiv1alpha1.CFOrg, + korifiv1alpha1.CFOrg, korifiv1alpha1.CFOrgList, *korifiv1alpha1.CFOrgList, ]{}) spaceRepo := repositories.NewSpaceRepo(namespaceRetriever, orgRepo, userClientFactory, nsPerms, &fakeawaiter.FakeAwaiter[ *korifiv1alpha1.CFSpace, + korifiv1alpha1.CFSpace, korifiv1alpha1.CFSpaceList, *korifiv1alpha1.CFSpaceList, ]{}) diff --git a/api/repositories/service_binding_repository_test.go b/api/repositories/service_binding_repository_test.go index 0cd4688f5..99d183538 100644 --- a/api/repositories/service_binding_repository_test.go +++ b/api/repositories/service_binding_repository_test.go @@ -33,6 +33,7 @@ var _ = Describe("ServiceBindingRepo", func() { bindingName *string conditionAwaiter *fakeawaiter.FakeAwaiter[ *korifiv1alpha1.CFServiceBinding, + korifiv1alpha1.CFServiceBinding, korifiv1alpha1.CFServiceBindingList, *korifiv1alpha1.CFServiceBindingList, ] @@ -42,6 +43,7 @@ var _ = Describe("ServiceBindingRepo", func() { testCtx = context.Background() conditionAwaiter = &fakeawaiter.FakeAwaiter[ *korifiv1alpha1.CFServiceBinding, + korifiv1alpha1.CFServiceBinding, korifiv1alpha1.CFServiceBindingList, *korifiv1alpha1.CFServiceBindingList, ]{} diff --git a/api/repositories/service_instance_repository_test.go b/api/repositories/service_instance_repository_test.go index b7d1379e5..937424e17 100644 --- a/api/repositories/service_instance_repository_test.go +++ b/api/repositories/service_instance_repository_test.go @@ -33,6 +33,7 @@ var _ = Describe("ServiceInstanceRepository", func() { serviceInstanceRepo *repositories.ServiceInstanceRepo conditionAwaiter *fakeawaiter.FakeAwaiter[ *korifiv1alpha1.CFServiceInstance, + korifiv1alpha1.CFServiceInstance, korifiv1alpha1.CFServiceInstanceList, *korifiv1alpha1.CFServiceInstanceList, ] @@ -46,6 +47,7 @@ var _ = Describe("ServiceInstanceRepository", func() { testCtx = context.Background() conditionAwaiter = &fakeawaiter.FakeAwaiter[ *korifiv1alpha1.CFServiceInstance, + korifiv1alpha1.CFServiceInstance, korifiv1alpha1.CFServiceInstanceList, *korifiv1alpha1.CFServiceInstanceList, ]{} diff --git a/api/repositories/service_plan_repository_test.go b/api/repositories/service_plan_repository_test.go index a2a5b7d77..c6b28e4a7 100644 --- a/api/repositories/service_plan_repository_test.go +++ b/api/repositories/service_plan_repository_test.go @@ -28,6 +28,7 @@ var _ = Describe("ServicePlanRepo", func() { BeforeEach(func() { orgRepo := repositories.NewOrgRepo(rootNamespace, k8sClient, userClientFactory, nsPerms, &fakeawaiter.FakeAwaiter[ *korifiv1alpha1.CFOrg, + korifiv1alpha1.CFOrg, korifiv1alpha1.CFOrgList, *korifiv1alpha1.CFOrgList, ]{}) diff --git a/api/repositories/space_repository_test.go b/api/repositories/space_repository_test.go index 751e4668e..c27ebcdf1 100644 --- a/api/repositories/space_repository_test.go +++ b/api/repositories/space_repository_test.go @@ -27,6 +27,7 @@ var _ = Describe("SpaceRepository", func() { orgRepo *repositories.OrgRepo conditionAwaiter *fakeawaiter.FakeAwaiter[ *korifiv1alpha1.CFSpace, + korifiv1alpha1.CFSpace, korifiv1alpha1.CFSpaceList, *korifiv1alpha1.CFSpaceList, ] @@ -36,12 +37,14 @@ var _ = Describe("SpaceRepository", func() { BeforeEach(func() { orgRepo = repositories.NewOrgRepo(rootNamespace, k8sClient, userClientFactory, nsPerms, &fakeawaiter.FakeAwaiter[ *korifiv1alpha1.CFOrg, + korifiv1alpha1.CFOrg, korifiv1alpha1.CFOrgList, *korifiv1alpha1.CFOrgList, ]{}) conditionAwaiter = &fakeawaiter.FakeAwaiter[ *korifiv1alpha1.CFSpace, + korifiv1alpha1.CFSpace, korifiv1alpha1.CFSpaceList, *korifiv1alpha1.CFSpaceList, ]{} diff --git a/api/repositories/task_repository_test.go b/api/repositories/task_repository_test.go index 09b36469f..1e53f8340 100644 --- a/api/repositories/task_repository_test.go +++ b/api/repositories/task_repository_test.go @@ -28,6 +28,7 @@ var _ = Describe("TaskRepository", func() { var ( conditionAwaiter *fakeawaiter.FakeAwaiter[ *korifiv1alpha1.CFTask, + korifiv1alpha1.CFTask, korifiv1alpha1.CFTaskList, *korifiv1alpha1.CFTaskList, ] @@ -40,6 +41,7 @@ var _ = Describe("TaskRepository", func() { BeforeEach(func() { conditionAwaiter = &fakeawaiter.FakeAwaiter[ *korifiv1alpha1.CFTask, + korifiv1alpha1.CFTask, korifiv1alpha1.CFTaskList, *korifiv1alpha1.CFTaskList, ]{} diff --git a/controllers/api/v1alpha1/appworkload_types.go b/controllers/api/v1alpha1/appworkload_types.go index 2fe0bcfd8..da33e23fa 100644 --- a/controllers/api/v1alpha1/appworkload_types.go +++ b/controllers/api/v1alpha1/appworkload_types.go @@ -78,6 +78,10 @@ type AppWorkload struct { Status AppWorkloadStatus `json:"status,omitempty"` } +func (w *AppWorkload) StatusConditions() *[]metav1.Condition { + return &w.Status.Conditions +} + //+kubebuilder:object:root=true // AppWorkloadList contains a list of AppWorkload diff --git a/controllers/api/v1alpha1/builderinfo_types.go b/controllers/api/v1alpha1/builderinfo_types.go index 9378156bb..a546809eb 100644 --- a/controllers/api/v1alpha1/builderinfo_types.go +++ b/controllers/api/v1alpha1/builderinfo_types.go @@ -64,6 +64,10 @@ type BuilderInfo struct { Status BuilderInfoStatus `json:"status,omitempty"` } +func (i *BuilderInfo) StatusConditions() *[]metav1.Condition { + return &i.Status.Conditions +} + //+kubebuilder:object:root=true // BuilderInfoList contains a list of BuilderInfo diff --git a/controllers/api/v1alpha1/buildworkload_types.go b/controllers/api/v1alpha1/buildworkload_types.go index d0129ee5a..1051f82db 100644 --- a/controllers/api/v1alpha1/buildworkload_types.go +++ b/controllers/api/v1alpha1/buildworkload_types.go @@ -70,6 +70,10 @@ type BuildWorkload struct { Status BuildWorkloadStatus `json:"status,omitempty"` } +func (w *BuildWorkload) StatusConditions() *[]metav1.Condition { + return &w.Status.Conditions +} + //+kubebuilder:object:root=true // BuildWorkloadList contains a list of BuildWorkload diff --git a/controllers/api/v1alpha1/cfapp_types.go b/controllers/api/v1alpha1/cfapp_types.go index 1c3ea88ba..c634072d0 100644 --- a/controllers/api/v1alpha1/cfapp_types.go +++ b/controllers/api/v1alpha1/cfapp_types.go @@ -100,8 +100,8 @@ func init() { SchemeBuilder.Register(&CFApp{}, &CFAppList{}) } -func (a CFApp) StatusConditions() []metav1.Condition { - return a.Status.Conditions +func (a *CFApp) StatusConditions() *[]metav1.Condition { + return &a.Status.Conditions } func (a CFApp) UniqueName() string { diff --git a/controllers/api/v1alpha1/cfbuild_types.go b/controllers/api/v1alpha1/cfbuild_types.go index 4c68ccbe3..25fb513db 100644 --- a/controllers/api/v1alpha1/cfbuild_types.go +++ b/controllers/api/v1alpha1/cfbuild_types.go @@ -86,6 +86,10 @@ type CFBuild struct { Status CFBuildStatus `json:"status,omitempty"` } +func (b *CFBuild) StatusConditions() *[]metav1.Condition { + return &b.Status.Conditions +} + //+kubebuilder:object:root=true // CFBuildList contains a list of CFBuild diff --git a/controllers/api/v1alpha1/cfdomain_types.go b/controllers/api/v1alpha1/cfdomain_types.go index 0b5f08d93..9e0c9f8e0 100644 --- a/controllers/api/v1alpha1/cfdomain_types.go +++ b/controllers/api/v1alpha1/cfdomain_types.go @@ -54,6 +54,10 @@ type CFDomain struct { Status CFDomainStatus `json:"status,omitempty"` } +func (d *CFDomain) StatusConditions() *[]metav1.Condition { + return &d.Status.Conditions +} + //+kubebuilder:object:root=true // CFDomainList contains a list of CFDomain diff --git a/controllers/api/v1alpha1/cforg_types.go b/controllers/api/v1alpha1/cforg_types.go index 1bfad9c78..55d20cfaf 100644 --- a/controllers/api/v1alpha1/cforg_types.go +++ b/controllers/api/v1alpha1/cforg_types.go @@ -90,8 +90,8 @@ func (o *CFOrg) GetStatus() status.NamespaceStatus { return &o.Status } -func (o CFOrg) StatusConditions() []metav1.Condition { - return o.Status.Conditions +func (o *CFOrg) StatusConditions() *[]metav1.Condition { + return &o.Status.Conditions } func (s *CFOrgStatus) GetConditions() *[]metav1.Condition { diff --git a/controllers/api/v1alpha1/cfpackage_types.go b/controllers/api/v1alpha1/cfpackage_types.go index b7fb0dc05..630b2bd3b 100644 --- a/controllers/api/v1alpha1/cfpackage_types.go +++ b/controllers/api/v1alpha1/cfpackage_types.go @@ -83,6 +83,6 @@ func init() { SchemeBuilder.Register(&CFPackage{}, &CFPackageList{}) } -func (p CFPackage) StatusConditions() []metav1.Condition { - return p.Status.Conditions +func (p *CFPackage) StatusConditions() *[]metav1.Condition { + return &p.Status.Conditions } diff --git a/controllers/api/v1alpha1/cfprocess_types.go b/controllers/api/v1alpha1/cfprocess_types.go index 0b845ff19..239741736 100644 --- a/controllers/api/v1alpha1/cfprocess_types.go +++ b/controllers/api/v1alpha1/cfprocess_types.go @@ -116,12 +116,16 @@ type CFProcessList struct { Items []CFProcess `json:"items"` } -func (r *CFProcess) SetStableName(appGUID string) { - r.Name = strings.Join([]string{processNamePrefix, appGUID, r.Spec.ProcessType}, "-") - if r.Labels == nil { - r.Labels = map[string]string{} +func (p *CFProcess) SetStableName(appGUID string) { + p.Name = strings.Join([]string{processNamePrefix, appGUID, p.Spec.ProcessType}, "-") + if p.Labels == nil { + p.Labels = map[string]string{} } - r.Labels[CFProcessGUIDLabelKey] = r.Name + p.Labels[CFProcessGUIDLabelKey] = p.Name +} + +func (p *CFProcess) StatusConditions() *[]metav1.Condition { + return &p.Status.Conditions } func init() { diff --git a/controllers/api/v1alpha1/cfroute_types.go b/controllers/api/v1alpha1/cfroute_types.go index f8dd7ce00..06c3927df 100644 --- a/controllers/api/v1alpha1/cfroute_types.go +++ b/controllers/api/v1alpha1/cfroute_types.go @@ -125,3 +125,7 @@ func (r CFRoute) UniqueValidationErrorMessage() string { return fmt.Sprintf("Route already exists with host '%s'%s for domain '%s'.", r.Spec.Host, pathDetails, r.Status.FQDN) } + +func (r *CFRoute) StatusConditions() *[]metav1.Condition { + return &r.Status.Conditions +} diff --git a/controllers/api/v1alpha1/cfservicebinding_types.go b/controllers/api/v1alpha1/cfservicebinding_types.go index 698a800ad..d62083e40 100644 --- a/controllers/api/v1alpha1/cfservicebinding_types.go +++ b/controllers/api/v1alpha1/cfservicebinding_types.go @@ -81,8 +81,8 @@ type CFServiceBindingList struct { Items []CFServiceBinding `json:"items"` } -func (b CFServiceBinding) StatusConditions() []metav1.Condition { - return b.Status.Conditions +func (b *CFServiceBinding) StatusConditions() *[]metav1.Condition { + return &b.Status.Conditions } func (b CFServiceBinding) UniqueName() string { diff --git a/controllers/api/v1alpha1/cfservicebroker_types.go b/controllers/api/v1alpha1/cfservicebroker_types.go index 2f3d3d019..6069a1b86 100644 --- a/controllers/api/v1alpha1/cfservicebroker_types.go +++ b/controllers/api/v1alpha1/cfservicebroker_types.go @@ -51,6 +51,10 @@ func (b CFServiceBroker) UniqueValidationErrorMessage() string { return "Name must be unique" } +func (b *CFServiceBroker) StatusConditions() *[]metav1.Condition { + return &b.Status.Conditions +} + // +kubebuilder:object:root=true type CFServiceBrokerList struct { metav1.TypeMeta `json:",inline"` diff --git a/controllers/api/v1alpha1/cfserviceinstance_types.go b/controllers/api/v1alpha1/cfserviceinstance_types.go index a7ad18313..b66c7805e 100644 --- a/controllers/api/v1alpha1/cfserviceinstance_types.go +++ b/controllers/api/v1alpha1/cfserviceinstance_types.go @@ -97,8 +97,8 @@ func (si CFServiceInstance) UniqueValidationErrorMessage() string { return fmt.Sprintf("The service instance name is taken: %s", si.Spec.DisplayName) } -func (si CFServiceInstance) StatusConditions() []metav1.Condition { - return si.Status.Conditions +func (si *CFServiceInstance) StatusConditions() *[]metav1.Condition { + return &si.Status.Conditions } //+kubebuilder:object:root=true diff --git a/controllers/api/v1alpha1/cfspace_types.go b/controllers/api/v1alpha1/cfspace_types.go index cc46c9ac4..5bffa6839 100644 --- a/controllers/api/v1alpha1/cfspace_types.go +++ b/controllers/api/v1alpha1/cfspace_types.go @@ -81,8 +81,8 @@ type CFSpaceList struct { Items []CFSpace `json:"items"` } -func (s CFSpace) StatusConditions() []metav1.Condition { - return s.Status.Conditions +func (s *CFSpace) StatusConditions() *[]metav1.Condition { + return &s.Status.Conditions } func (s *CFSpace) GetStatus() status.NamespaceStatus { diff --git a/controllers/api/v1alpha1/cftask_types.go b/controllers/api/v1alpha1/cftask_types.go index 70d623d42..0720fc1ac 100644 --- a/controllers/api/v1alpha1/cftask_types.go +++ b/controllers/api/v1alpha1/cftask_types.go @@ -83,6 +83,6 @@ func init() { SchemeBuilder.Register(&CFTask{}, &CFTaskList{}) } -func (t CFTask) StatusConditions() []metav1.Condition { - return t.Status.Conditions +func (t *CFTask) StatusConditions() *[]metav1.Condition { + return &t.Status.Conditions } diff --git a/controllers/api/v1alpha1/runnerinfo_types.go b/controllers/api/v1alpha1/runnerinfo_types.go index e005f1029..af661eb64 100644 --- a/controllers/api/v1alpha1/runnerinfo_types.go +++ b/controllers/api/v1alpha1/runnerinfo_types.go @@ -55,6 +55,10 @@ type RunnerInfo struct { Status RunnerInfoStatus `json:"status,omitempty"` } +func (i *RunnerInfo) StatusConditions() *[]metav1.Condition { + return &i.Status.Conditions +} + //+kubebuilder:object:root=true // RunnerInfoList contains a list of RunnerInfo diff --git a/controllers/api/v1alpha1/taskworkload_types.go b/controllers/api/v1alpha1/taskworkload_types.go index 247318f20..c0580fb76 100644 --- a/controllers/api/v1alpha1/taskworkload_types.go +++ b/controllers/api/v1alpha1/taskworkload_types.go @@ -73,6 +73,6 @@ func init() { SchemeBuilder.Register(&TaskWorkload{}, &TaskWorkloadList{}) } -func (t TaskWorkload) StatusConditions() []metav1.Condition { - return t.Status.Conditions +func (t *TaskWorkload) StatusConditions() *[]metav1.Condition { + return &t.Status.Conditions } diff --git a/controllers/controllers/networking/domains/controller.go b/controllers/controllers/networking/domains/controller.go index c006af28f..927026c75 100644 --- a/controllers/controllers/networking/domains/controller.go +++ b/controllers/controllers/networking/domains/controller.go @@ -25,7 +25,6 @@ import ( "code.cloudfoundry.org/korifi/tools/k8s" "github.com/go-logr/logr" - "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" @@ -60,12 +59,6 @@ func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) *builder.Builder { func (r *Reconciler) ReconcileResource(ctx context.Context, cfDomain *korifiv1alpha1.CFDomain) (ctrl.Result, error) { log := logr.FromContextOrDiscard(ctx) - var err error - readyConditionBuilder := k8s.NewReadyConditionBuilder(cfDomain) - defer func() { - meta.SetStatusCondition(&cfDomain.Status.Conditions, readyConditionBuilder.WithError(err).Build()) - }() - if !cfDomain.GetDeletionTimestamp().IsZero() { return r.finalizeCFDomain(ctx, cfDomain) } @@ -73,7 +66,6 @@ func (r *Reconciler) ReconcileResource(ctx context.Context, cfDomain *korifiv1al cfDomain.Status.ObservedGeneration = cfDomain.Generation log.V(1).Info("set observed generation", "generation", cfDomain.Status.ObservedGeneration) - readyConditionBuilder.Ready() return ctrl.Result{}, nil } diff --git a/controllers/controllers/networking/routes/controller.go b/controllers/controllers/networking/routes/controller.go index b06391139..0c438a704 100644 --- a/controllers/controllers/networking/routes/controller.go +++ b/controllers/controllers/networking/routes/controller.go @@ -29,7 +29,6 @@ import ( "github.com/go-logr/logr" corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" @@ -113,18 +112,12 @@ func (r *Reconciler) enqueueCFAppRequests(ctx context.Context, o client.Object) func (r *Reconciler) ReconcileResource(ctx context.Context, cfRoute *korifiv1alpha1.CFRoute) (ctrl.Result, error) { log := logr.FromContextOrDiscard(ctx) - var err error - readyConditionBuilder := k8s.NewReadyConditionBuilder(cfRoute) - defer func() { - meta.SetStatusCondition(&cfRoute.Status.Conditions, readyConditionBuilder.WithError(err).Build()) - }() - log.V(1).Info("set observed generation", "generation", cfRoute.Status.ObservedGeneration) cfRoute.Status.ObservedGeneration = cfRoute.Generation if !cfRoute.GetDeletionTimestamp().IsZero() { - err = r.finalizeCFRoute(ctx, cfRoute) + err := r.finalizeCFRoute(ctx, cfRoute) if err != nil { log.Info("failed to finalize cf route", "reason", err) } @@ -132,22 +125,19 @@ func (r *Reconciler) ReconcileResource(ctx context.Context, cfRoute *korifiv1alp } cfDomain := &korifiv1alpha1.CFDomain{} - err = r.client.Get(ctx, types.NamespacedName{Name: cfRoute.Spec.DomainRef.Name, Namespace: cfRoute.Spec.DomainRef.Namespace}, cfDomain) + err := r.client.Get(ctx, types.NamespacedName{Name: cfRoute.Spec.DomainRef.Name, Namespace: cfRoute.Spec.DomainRef.Namespace}, cfDomain) if err != nil { - readyConditionBuilder.WithReason("InvalidDomainRef") - return ctrl.Result{}, err + return ctrl.Result{}, k8s.NewNotReadyError().WithCause(err).WithReason("InvalidDomainRef") } err = r.createOrPatchServices(ctx, cfRoute) if err != nil { - readyConditionBuilder.WithReason("CreatePatchServices") - return ctrl.Result{}, err + return ctrl.Result{}, k8s.NewNotReadyError().WithCause(err).WithReason("CreatePatchServices") } err = r.reconcileHTTPRoute(ctx, cfRoute, cfDomain) if err != nil { - readyConditionBuilder.WithReason("ReconcileHTTPRoute") - return ctrl.Result{}, err + return ctrl.Result{}, k8s.NewNotReadyError().WithCause(err).WithReason("ReconcileHTTPRoute") } fqdn := buildFQDN(cfRoute, cfDomain) @@ -156,13 +146,10 @@ func (r *Reconciler) ReconcileResource(ctx context.Context, cfRoute *korifiv1alp effectiveDestinations, err := r.buildEffectiveDestinations(ctx, cfRoute) if err != nil { - readyConditionBuilder.WithReason("BuildEffectiveDestinations") - return ctrl.Result{}, err + return ctrl.Result{}, k8s.NewNotReadyError().WithCause(err).WithReason("BuildEffectiveDestinations") } cfRoute.Status.Destinations = effectiveDestinations - readyConditionBuilder.Ready() - if cleanupErr := r.deleteOrphanedServices(ctx, cfRoute); cleanupErr != nil { // technically, failing to delete the orphaned services does not make // the CFRoute invalid or not ready so we don't mess with the cfRoute diff --git a/controllers/controllers/services/bindings/controller.go b/controllers/controllers/services/bindings/controller.go index 2c5dabfc5..f8e835f79 100644 --- a/controllers/controllers/services/bindings/controller.go +++ b/controllers/controllers/services/bindings/controller.go @@ -137,14 +137,8 @@ func (r *Reconciler) ReconcileResource(ctx context.Context, cfServiceBinding *ko cfServiceBinding.Status.ObservedGeneration = cfServiceBinding.Generation log.V(1).Info("set observed generation", "generation", cfServiceBinding.Status.ObservedGeneration) - var err error - readyConditionBuilder := k8s.NewReadyConditionBuilder(cfServiceBinding) - defer func() { - meta.SetStatusCondition(&cfServiceBinding.Status.Conditions, readyConditionBuilder.WithError(err).Build()) - }() - cfServiceInstance := new(korifiv1alpha1.CFServiceInstance) - err = r.k8sClient.Get(ctx, types.NamespacedName{Name: cfServiceBinding.Spec.Service.Name, Namespace: cfServiceBinding.Namespace}, cfServiceInstance) + err := r.k8sClient.Get(ctx, types.NamespacedName{Name: cfServiceBinding.Spec.Service.Name, Namespace: cfServiceBinding.Namespace}, cfServiceInstance) if err != nil { log.Info("service instance not found", "service-instance", cfServiceBinding.Spec.Service.Name, "error", err) return ctrl.Result{}, err @@ -157,10 +151,10 @@ func (r *Reconciler) ReconcileResource(ctx context.Context, cfServiceBinding *ko } if cfServiceInstance.Status.Credentials.Name == "" { - readyConditionBuilder. + return ctrl.Result{}, k8s.NewNotReadyError(). WithReason("CredentialsSecretNotAvailable"). - WithMessage("Service instance credentials not available yet") - return ctrl.Result{RequeueAfter: time.Second}, nil + WithMessage("Service instance credentials not available yet"). + WithRequeueAfter(time.Second) } credentialsSecret, err := r.reconcileCredentials(ctx, cfServiceInstance, cfServiceBinding) @@ -193,11 +187,9 @@ func (r *Reconciler) ReconcileResource(ctx context.Context, cfServiceBinding *ko } if !isSbServiceBindingReady(sbServiceBinding) { - readyConditionBuilder.WithReason("ServiceBindingNotReady") - return ctrl.Result{}, nil + return ctrl.Result{}, k8s.NewNotReadyError().WithReason("ServiceBindingNotReady") } - readyConditionBuilder.Ready() return ctrl.Result{}, nil } diff --git a/controllers/controllers/services/brokers/controller.go b/controllers/controllers/services/brokers/controller.go index 58254922d..1199cef94 100644 --- a/controllers/controllers/services/brokers/controller.go +++ b/controllers/controllers/services/brokers/controller.go @@ -32,7 +32,6 @@ import ( "github.com/go-logr/logr" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" @@ -108,30 +107,23 @@ func (r *Reconciler) ReconcileResource(ctx context.Context, cfServiceBroker *kor cfServiceBroker.Status.ObservedGeneration = cfServiceBroker.Generation log.V(1).Info("set observed generation", "generation", cfServiceBroker.Status.ObservedGeneration) - var err error - readyConditionBuilder := k8s.NewReadyConditionBuilder(cfServiceBroker) - defer func() { - meta.SetStatusCondition(&cfServiceBroker.Status.Conditions, readyConditionBuilder.WithError(err).Build()) - }() - credentialsSecret := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Namespace: cfServiceBroker.Namespace, Name: cfServiceBroker.Spec.Credentials.Name, }, } - err = r.k8sClient.Get(ctx, client.ObjectKeyFromObject(credentialsSecret), credentialsSecret) + err := r.k8sClient.Get(ctx, client.ObjectKeyFromObject(credentialsSecret), credentialsSecret) if err != nil { - readyConditionBuilder.WithReason("CredentialsSecretNotAvailable") + notReadyErr := k8s.NewNotReadyError().WithCause(err).WithReason("CredentialsSecretNotAvailable") if apierrors.IsNotFound(err) { - return ctrl.Result{RequeueAfter: 2 * time.Second}, nil + notReadyErr = notReadyErr.WithRequeueAfter(2 * time.Second) } - return ctrl.Result{}, err + return ctrl.Result{}, notReadyErr } if err = r.validateCredentials(credentialsSecret); err != nil { - readyConditionBuilder.WithReason("SecretInvalid") - return ctrl.Result{}, err + return ctrl.Result{}, k8s.NewNotReadyError().WithCause(err).WithReason("SecretInvalid") } log.V(1).Info("credentials secret", "name", credentialsSecret.Name, "version", credentialsSecret.ResourceVersion) @@ -140,8 +132,7 @@ func (r *Reconciler) ReconcileResource(ctx context.Context, cfServiceBroker *kor catalog, err := r.catalogClient.GetCatalog(ctx, cfServiceBroker) if err != nil { log.Error(err, "failed to get catalog from broker", "broker", cfServiceBroker.Name) - readyConditionBuilder.WithReason("GetCatalogFailed") - return ctrl.Result{}, err + return ctrl.Result{}, k8s.NewNotReadyError().WithCause(err).WithReason("GetCatalogFailed") } err = r.reconcileCatalog(ctx, cfServiceBroker, catalog) @@ -150,7 +141,6 @@ func (r *Reconciler) ReconcileResource(ctx context.Context, cfServiceBroker *kor return ctrl.Result{}, fmt.Errorf("failed to reconcile catalog: %v", err) } - readyConditionBuilder.Ready() return ctrl.Result{}, nil } diff --git a/controllers/controllers/services/instances/controller.go b/controllers/controllers/services/instances/controller.go index 449f9994a..bb2cede4a 100644 --- a/controllers/controllers/services/instances/controller.go +++ b/controllers/controllers/services/instances/controller.go @@ -32,7 +32,6 @@ import ( "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" @@ -101,43 +100,34 @@ func (r *Reconciler) ReconcileResource(ctx context.Context, cfServiceInstance *k cfServiceInstance.Status.ObservedGeneration = cfServiceInstance.Generation log.V(1).Info("set observed generation", "generation", cfServiceInstance.Status.ObservedGeneration) - var err error - readyConditionBuilder := k8s.NewReadyConditionBuilder(cfServiceInstance) - defer func() { - meta.SetStatusCondition(&cfServiceInstance.Status.Conditions, readyConditionBuilder.WithError(err).Build()) - }() - credentialsSecret := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Namespace: cfServiceInstance.Namespace, Name: cfServiceInstance.Spec.SecretName, }, } - err = r.k8sClient.Get(ctx, client.ObjectKeyFromObject(credentialsSecret), credentialsSecret) + err := r.k8sClient.Get(ctx, client.ObjectKeyFromObject(credentialsSecret), credentialsSecret) if err != nil { - readyConditionBuilder.WithReason("CredentialsSecretNotAvailable") + notReadyErr := k8s.NewNotReadyError().WithCause(err).WithReason("CredentialsSecretNotAvailable") if apierrors.IsNotFound(err) { - return ctrl.Result{RequeueAfter: 2 * time.Second}, nil + notReadyErr = notReadyErr.WithRequeueAfter(2 * time.Second) } - return ctrl.Result{}, err + return ctrl.Result{}, notReadyErr } credentialsSecret, err = r.reconcileCredentials(ctx, credentialsSecret, cfServiceInstance) if err != nil { - readyConditionBuilder.WithReason("FailedReconcilingCredentialsSecret") - return ctrl.Result{}, err + return ctrl.Result{}, k8s.NewNotReadyError().WithCause(err).WithReason("FailedReconcilingCredentialsSecret") } if err = r.validateCredentials(ctx, credentialsSecret); err != nil { - readyConditionBuilder.WithReason("SecretInvalid") - return ctrl.Result{}, err + return ctrl.Result{}, k8s.NewNotReadyError().WithCause(err).WithReason("SecretInvalid") } log.V(1).Info("credentials secret", "name", credentialsSecret.Name, "version", credentialsSecret.ResourceVersion) cfServiceInstance.Status.Credentials = corev1.LocalObjectReference{Name: credentialsSecret.Name} cfServiceInstance.Status.CredentialsObservedVersion = credentialsSecret.ResourceVersion - readyConditionBuilder.Ready() return ctrl.Result{}, nil } diff --git a/controllers/controllers/workloads/apps/controller.go b/controllers/controllers/workloads/apps/controller.go index a0875c323..d6bb688e3 100644 --- a/controllers/controllers/workloads/apps/controller.go +++ b/controllers/controllers/workloads/apps/controller.go @@ -103,12 +103,6 @@ func serviceBindingToApp(ctx context.Context, o client.Object) []reconcile.Reque func (r *Reconciler) ReconcileResource(ctx context.Context, cfApp *korifiv1alpha1.CFApp) (ctrl.Result, error) { log := logr.FromContextOrDiscard(ctx) - var err error - readyConditionBuilder := k8s.NewReadyConditionBuilder(cfApp) - defer func() { - meta.SetStatusCondition(&cfApp.Status.Conditions, readyConditionBuilder.WithError(err).Build()) - }() - cfApp.Status.ObservedGeneration = cfApp.Generation log.V(1).Info("set observed generation", "generation", cfApp.Status.ObservedGeneration) @@ -131,8 +125,7 @@ func (r *Reconciler) ReconcileResource(ctx context.Context, cfApp *korifiv1alpha } if !bindingsReady { - readyConditionBuilder.WithReason("BindingNotReady") - return ctrl.Result{}, nil + return ctrl.Result{}, k8s.NewNotReadyError().WithReason("BindingNotReady") } secretName := cfApp.Name + "-vcap-application" @@ -151,14 +144,12 @@ func (r *Reconciler) ReconcileResource(ctx context.Context, cfApp *korifiv1alpha cfApp.Status.VCAPServicesSecretName = secretName if cfApp.Spec.CurrentDropletRef.Name == "" { - readyConditionBuilder.WithReason("DropletNotAssigned") - return ctrl.Result{}, nil + return ctrl.Result{}, k8s.NewNotReadyError().WithReason("DropletNotAssigned") } droplet, err := r.getDroplet(ctx, cfApp) if err != nil { - readyConditionBuilder.WithReason("CannotResolveCurrentDropletRef") - return ctrl.Result{}, err + return ctrl.Result{}, k8s.NewNotReadyError().WithReason("CannotResolveCurrentDropletRef") } reconciledProcesses, err := r.reconcileProcesses(ctx, cfApp, droplet) @@ -168,11 +159,9 @@ func (r *Reconciler) ReconcileResource(ctx context.Context, cfApp *korifiv1alpha cfApp.Status.ActualState = getActualState(reconciledProcesses) if cfApp.Status.ActualState != cfApp.Spec.DesiredState { - readyConditionBuilder.WithReason("DesiredStateNotReached") - return ctrl.Result{}, nil + return ctrl.Result{}, k8s.NewNotReadyError().WithReason("DesiredStateNotReached") } - readyConditionBuilder.Ready() return ctrl.Result{}, nil } diff --git a/controllers/controllers/workloads/orgs/controller.go b/controllers/controllers/workloads/orgs/controller.go index 539262006..9f0e74530 100644 --- a/controllers/controllers/workloads/orgs/controller.go +++ b/controllers/controllers/workloads/orgs/controller.go @@ -27,7 +27,6 @@ import ( "github.com/go-logr/logr" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" - "k8s.io/apimachinery/pkg/api/meta" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/client" @@ -121,18 +120,11 @@ func (r *Reconciler) enqueueCFOrgRequests(ctx context.Context, object client.Obj //+kubebuilder:rbac:groups="policy",resources=podsecuritypolicies,verbs=use func (r *Reconciler) ReconcileResource(ctx context.Context, cfOrg *korifiv1alpha1.CFOrg) (ctrl.Result, error) { - var err error - readyConditionBuilder := k8s.NewReadyConditionBuilder(cfOrg) - defer func() { - meta.SetStatusCondition(&cfOrg.Status.Conditions, readyConditionBuilder.WithError(err).Build()) - }() - nsReconcileResult, err := r.namespaceReconciler.ReconcileResource(ctx, cfOrg) if (nsReconcileResult != ctrl.Result{}) || (err != nil) { return nsReconcileResult, err } - readyConditionBuilder.Ready() return ctrl.Result{}, nil } diff --git a/controllers/controllers/workloads/packages/controller.go b/controllers/controllers/workloads/packages/controller.go index ca349ab87..21f5cb23c 100644 --- a/controllers/controllers/workloads/packages/controller.go +++ b/controllers/controllers/workloads/packages/controller.go @@ -89,12 +89,6 @@ func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) *builder.Builder { func (r *Reconciler) ReconcileResource(ctx context.Context, cfPackage *korifiv1alpha1.CFPackage) (ctrl.Result, error) { log := logr.FromContextOrDiscard(ctx) - var err error - readyConditionBuilder := k8s.NewReadyConditionBuilder(cfPackage) - defer func() { - meta.SetStatusCondition(&cfPackage.Status.Conditions, readyConditionBuilder.WithError(err).Build()) - }() - cfPackage.Status.ObservedGeneration = cfPackage.Generation log.V(1).Info("set observed generation", "generation", cfPackage.Status.ObservedGeneration) @@ -103,7 +97,7 @@ func (r *Reconciler) ReconcileResource(ctx context.Context, cfPackage *korifiv1a } var cfApp korifiv1alpha1.CFApp - err = r.k8sClient.Get(ctx, types.NamespacedName{Name: cfPackage.Spec.AppRef.Name, Namespace: cfPackage.Namespace}, &cfApp) + err := r.k8sClient.Get(ctx, types.NamespacedName{Name: cfPackage.Spec.AppRef.Name, Namespace: cfPackage.Namespace}, &cfApp) if err != nil { log.Info("error when fetching CFApp", "reason", err) return ctrl.Result{}, err @@ -132,13 +126,9 @@ func (r *Reconciler) ReconcileResource(ctx context.Context, cfPackage *korifiv1a }() if cfPackage.Spec.Source.Registry.Image == "" { - readyConditionBuilder.WithReason("Initialized") - return ctrl.Result{}, nil + return ctrl.Result{}, k8s.NewNotReadyError().WithReason("Initialized").WithNoRequeue() } - readyConditionBuilder.WithReason("SourceImageSet") - readyConditionBuilder.Ready() - return ctrl.Result{}, nil } diff --git a/controllers/controllers/workloads/processes/controller.go b/controllers/controllers/workloads/processes/controller.go index 387ed073e..3d6bf7a77 100644 --- a/controllers/controllers/workloads/processes/controller.go +++ b/controllers/controllers/workloads/processes/controller.go @@ -30,7 +30,6 @@ import ( "github.com/go-logr/logr" corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -127,19 +126,13 @@ func (r *Reconciler) enqueueCFProcessRequestsForRoute(ctx context.Context, o cli func (r *Reconciler) ReconcileResource(ctx context.Context, cfProcess *korifiv1alpha1.CFProcess) (ctrl.Result, error) { log := logr.FromContextOrDiscard(ctx) - var err error - readyConditionBuilder := k8s.NewReadyConditionBuilder(cfProcess) - defer func() { - meta.SetStatusCondition(&cfProcess.Status.Conditions, readyConditionBuilder.WithError(err).Build()) - }() - cfProcess.Status.ObservedGeneration = cfProcess.Generation log.V(1).Info("set observed generation", "generation", cfProcess.Status.ObservedGeneration) shared.GetConditionOrSetAsUnknown(&cfProcess.Status.Conditions, korifiv1alpha1.StatusConditionReady, cfProcess.Generation) cfApp := new(korifiv1alpha1.CFApp) - err = r.k8sClient.Get(ctx, types.NamespacedName{Name: cfProcess.Spec.AppRef.Name, Namespace: cfProcess.Namespace}, cfApp) + err := r.k8sClient.Get(ctx, types.NamespacedName{Name: cfProcess.Spec.AppRef.Name, Namespace: cfProcess.Namespace}, cfApp) if err != nil { log.Info("error when trying to fetch CFApp", "namespace", cfProcess.Namespace, "name", cfProcess.Spec.AppRef.Name, "reason", err) return ctrl.Result{}, err @@ -179,7 +172,6 @@ func (r *Reconciler) ReconcileResource(ctx context.Context, cfProcess *korifiv1a cfProcess.Status.ActualInstances = getActualInstances(appWorkloads) - readyConditionBuilder.Ready() return ctrl.Result{}, nil } diff --git a/controllers/controllers/workloads/spaces/controller.go b/controllers/controllers/workloads/spaces/controller.go index 7c4ee4540..8305f7c30 100644 --- a/controllers/controllers/workloads/spaces/controller.go +++ b/controllers/controllers/workloads/spaces/controller.go @@ -29,7 +29,6 @@ import ( "github.com/go-logr/logr" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" - "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" k8s_labels "k8s.io/apimachinery/pkg/labels" ctrl "sigs.k8s.io/controller-runtime" @@ -139,12 +138,6 @@ func (r *Reconciler) enqueueCFSpaceRequestsForServiceAccount(ctx context.Context //+kubebuilder:rbac:groups="",resources=serviceaccounts,verbs=get;list;watch;create;patch;delete func (r *Reconciler) ReconcileResource(ctx context.Context, cfSpace *korifiv1alpha1.CFSpace) (ctrl.Result, error) { - var err error - readyConditionBuilder := k8s.NewReadyConditionBuilder(cfSpace) - defer func() { - meta.SetStatusCondition(&cfSpace.Status.Conditions, readyConditionBuilder.WithError(err).Build()) - }() - nsReconcileResult, err := r.namespaceReconciler.ReconcileResource(ctx, cfSpace) if (nsReconcileResult != ctrl.Result{}) || (err != nil) { return nsReconcileResult, err @@ -155,12 +148,9 @@ func (r *Reconciler) ReconcileResource(ctx context.Context, cfSpace *korifiv1alp err = r.reconcileServiceAccounts(ctx, cfSpace) if err != nil { log.Info("not ready yet", "reason", "error propagating service accounts", "error", err) - - readyConditionBuilder.WithReason("ServiceAccountPropagation") - return ctrl.Result{}, err + return ctrl.Result{}, k8s.NewNotReadyError().WithCause(err).WithReason("ServiceAccountPropagation") } - readyConditionBuilder.Ready() return ctrl.Result{}, nil } diff --git a/kpack-image-builder/controllers/kpack_build_controller.go b/kpack-image-builder/controllers/kpack_build_controller.go index 7a31d0e35..27ca5a61c 100644 --- a/kpack-image-builder/controllers/kpack_build_controller.go +++ b/kpack-image-builder/controllers/kpack_build_controller.go @@ -7,13 +7,16 @@ import ( "code.cloudfoundry.org/korifi/tools/image" "code.cloudfoundry.org/korifi/tools/k8s" "github.com/go-logr/logr" + "github.com/google/uuid" kpackv1alpha2 "github.com/pivotal/kpack/pkg/apis/build/v1alpha2" + k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/manager" "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" ) const KpackBuildFinalizer string = "korifi.cloudfoundry.org/kpackBuild" @@ -36,16 +39,16 @@ func NewKpackBuildController( log logr.Logger, imageDeleter ImageDeleter, registryServiceAccount string, -) *k8s.PatchingReconciler[kpackv1alpha2.Build, *kpackv1alpha2.Build] { - return k8s.NewPatchingReconciler[kpackv1alpha2.Build, *kpackv1alpha2.Build](log, k8sClient, KpackBuildController{ +) *KpackBuildController { + return &KpackBuildController{ log: log, k8sClient: k8sClient, imageDeleter: imageDeleter, registryServiceAccount: registryServiceAccount, - }) + } } -func (c KpackBuildController) SetupWithManager(mgr manager.Manager) *ctrl.Builder { +func (c *KpackBuildController) SetupWithManager(mgr manager.Manager) error { // ignoring error as this construction is not dynamic labelSelector, _ := predicate.LabelSelectorPredicate(metav1.LabelSelector{ MatchExpressions: []metav1.LabelSelectorRequirement{ @@ -59,15 +62,29 @@ func (c KpackBuildController) SetupWithManager(mgr manager.Manager) *ctrl.Builde return ctrl.NewControllerManagedBy(mgr). For(&kpackv1alpha2.Build{}). - WithEventFilter(labelSelector) + WithEventFilter(labelSelector). + Complete(c) } //+kubebuilder:rbac:groups=kpack.io,resources=builds,verbs=get;list;watch;patch //+kubebuilder:rbac:groups=kpack.io,resources=builds/status,verbs=get;patch //+kubebuilder:rbac:groups=kpack.io,resources=builds/finalizers,verbs=get;patch -func (c KpackBuildController) ReconcileResource(ctx context.Context, kpackBuild *kpackv1alpha2.Build) (ctrl.Result, error) { - log := logr.FromContextOrDiscard(ctx) +func (c *KpackBuildController) Reconcile(ctx context.Context, req reconcile.Request) (ctrl.Result, error) { + log := c.log.WithName("KpackBuild"). + WithValues("namespace", req.Namespace). + WithValues("name", req.Name). + WithValues("logID", uuid.NewString()) + + kpackBuild := &kpackv1alpha2.Build{} + err := c.k8sClient.Get(ctx, req.NamespacedName, kpackBuild) + if err != nil { + if k8serrors.IsNotFound(err) { + return ctrl.Result{}, nil + } + log.Info("unable to fetch kpack build", "reason", err) + return ctrl.Result{}, err + } if !kpackBuild.GetDeletionTimestamp().IsZero() { if !controllerutil.ContainsFinalizer(kpackBuild, KpackBuildFinalizer) { @@ -83,7 +100,7 @@ func (c KpackBuildController) ReconcileResource(ctx context.Context, kpackBuild } } - err := c.imageDeleter.Delete(ctx, image.Creds{ + err = c.imageDeleter.Delete(ctx, image.Creds{ Namespace: kpackBuild.Namespace, ServiceAccountName: c.registryServiceAccount, }, kpackBuild.Status.LatestImage, tagsToDelete...) @@ -92,11 +109,16 @@ func (c KpackBuildController) ReconcileResource(ctx context.Context, kpackBuild } } - if controllerutil.RemoveFinalizer(kpackBuild, KpackBuildFinalizer) { - log.V(1).Info("finalizer removed") - } + err = k8s.Patch(ctx, c.k8sClient, kpackBuild, func() { + if controllerutil.RemoveFinalizer(kpackBuild, KpackBuildFinalizer) { + log.V(1).Info("finalizer removed") + } + }) + if err != nil { + log.Info("unable to remove finalizer from kpack build", "reason", err) + return ctrl.Result{}, err - return ctrl.Result{}, nil + } } return ctrl.Result{}, nil diff --git a/statefulset-runner/controllers/appworkload_controller.go b/statefulset-runner/controllers/appworkload_controller.go index 758f6ff96..96d994b99 100644 --- a/statefulset-runner/controllers/appworkload_controller.go +++ b/statefulset-runner/controllers/appworkload_controller.go @@ -25,7 +25,6 @@ import ( "github.com/go-logr/logr" appsv1 "k8s.io/api/apps/v1" - "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" @@ -151,12 +150,6 @@ func filterAppWorkloads(object client.Object) bool { func (r *AppWorkloadReconciler) ReconcileResource(ctx context.Context, appWorkload *korifiv1alpha1.AppWorkload) (ctrl.Result, error) { log := logr.FromContextOrDiscard(ctx) - var err error - readyConditionBuilder := k8s.NewReadyConditionBuilder(appWorkload) - defer func() { - meta.SetStatusCondition(&appWorkload.Status.Conditions, readyConditionBuilder.WithError(err).Build()) - }() - appWorkload.Status.ObservedGeneration = appWorkload.Generation log.V(1).Info("set observed generation", "generation", appWorkload.Status.ObservedGeneration) @@ -196,6 +189,5 @@ func (r *AppWorkloadReconciler) ReconcileResource(ctx context.Context, appWorklo appWorkload.Status.ActualInstances = createdStSet.Status.Replicas - readyConditionBuilder.Ready() return ctrl.Result{}, nil } diff --git a/tests/crds/crds_suite_test.go b/tests/crds/crds_suite_test.go index 825bb1b2a..03399de66 100644 --- a/tests/crds/crds_suite_test.go +++ b/tests/crds/crds_suite_test.go @@ -138,7 +138,7 @@ var _ = BeforeEach(func() { Expect(k8sClient.Create(ctx, testOrg)).To(Succeed()) Eventually(func(g Gomega) { g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(testOrg), testOrg)).To(Succeed()) - g.Expect(meta.IsStatusConditionTrue(testOrg.StatusConditions(), korifiv1alpha1.StatusConditionReady)).To(BeTrue()) + g.Expect(meta.IsStatusConditionTrue(*testOrg.StatusConditions(), korifiv1alpha1.StatusConditionReady)).To(BeTrue()) }).Should(Succeed()) testSpace = &korifiv1alpha1.CFSpace{ @@ -154,7 +154,7 @@ var _ = BeforeEach(func() { Expect(k8sClient.Create(ctx, testSpace)).To(Succeed()) Eventually(func(g Gomega) { g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(testSpace), testSpace)).To(Succeed()) - g.Expect(meta.IsStatusConditionTrue(testSpace.StatusConditions(), korifiv1alpha1.StatusConditionReady)).To(BeTrue()) + g.Expect(meta.IsStatusConditionTrue(*testSpace.StatusConditions(), korifiv1alpha1.StatusConditionReady)).To(BeTrue()) }).Should(Succeed()) }) diff --git a/tools/k8s/condition_builder.go b/tools/k8s/condition_builder.go index 7bb713018..0e9d05c1d 100644 --- a/tools/k8s/condition_builder.go +++ b/tools/k8s/condition_builder.go @@ -4,6 +4,7 @@ import ( "time" korifiv1alpha1 "code.cloudfoundry.org/korifi/controllers/api/v1alpha1" + "code.cloudfoundry.org/korifi/tools" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -34,7 +35,9 @@ func (b *ReadyConditionBuilder) WithReason(reason string) *ReadyConditionBuilder } func (b *ReadyConditionBuilder) WithMessage(message string) *ReadyConditionBuilder { - b.message = message + if message != "" { + b.message = message + } return b } @@ -68,3 +71,47 @@ func (b *ReadyConditionBuilder) Build() metav1.Condition { return result } + +type NotReadyError struct { + cause error + reason string + message string + requeueAfter *time.Duration + noRequeue bool +} + +func (e NotReadyError) Error() string { + if e.cause == nil { + return "" + } + return e.cause.Error() +} + +func NewNotReadyError() NotReadyError { + return NotReadyError{} +} + +func (e NotReadyError) WithCause(cause error) NotReadyError { + e.cause = cause + return e +} + +func (e NotReadyError) WithRequeueAfter(duration time.Duration) NotReadyError { + e.requeueAfter = tools.PtrTo(duration) + return e +} + +func (e NotReadyError) WithNoRequeue() NotReadyError { + e.noRequeue = true + return e +} + +func (e NotReadyError) WithReason(reason string) NotReadyError { + e.reason = reason + return e +} + +func (e NotReadyError) WithMessage(message string) NotReadyError { + e.message = message + return e +} diff --git a/tools/k8s/condition_builder_test.go b/tools/k8s/condition_builder_test.go index 86e2b9dc2..3d96c337e 100644 --- a/tools/k8s/condition_builder_test.go +++ b/tools/k8s/condition_builder_test.go @@ -74,9 +74,19 @@ var _ = Describe("ReadyConditionBuilder", func() { builder.WithError(errors.New("some-error")) }) - It("sets the reason", func() { + It("sets the message", func() { Expect(condition.Message).To(Equal("some-error")) }) + + When("message is set to empty string", func() { + BeforeEach(func() { + builder.WithMessage("") + }) + + It("sets the message from the error", func() { + Expect(condition.Message).To(Equal("some-error")) + }) + }) }) When("nil error is set", func() { diff --git a/tools/k8s/reconcile.go b/tools/k8s/reconcile.go index 1c7d817f0..d8da8d8e9 100644 --- a/tools/k8s/reconcile.go +++ b/tools/k8s/reconcile.go @@ -2,29 +2,37 @@ package k8s import ( "context" + "errors" "fmt" "reflect" "github.com/go-logr/logr" "github.com/google/uuid" - apierrors "k8s.io/apimachinery/pkg/api/errors" + k8serrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/client" ) -type ObjectReconciler[T any, PT ObjectWithDeepCopy[T]] interface { +type RuntimeObjectWithStatusConditions[T any] interface { + ObjectWithDeepCopy[T] + StatusConditions() *[]metav1.Condition +} + +type ObjectReconciler[T any, PT RuntimeObjectWithStatusConditions[T]] interface { ReconcileResource(ctx context.Context, obj PT) (ctrl.Result, error) SetupWithManager(mgr ctrl.Manager) *builder.Builder } -type PatchingReconciler[T any, PT ObjectWithDeepCopy[T]] struct { +type PatchingReconciler[T any, PT RuntimeObjectWithStatusConditions[T]] struct { log logr.Logger k8sClient client.Client objectReconciler ObjectReconciler[T, PT] } -func NewPatchingReconciler[T any, PT ObjectWithDeepCopy[T]](log logr.Logger, k8sClient client.Client, objectReconciler ObjectReconciler[T, PT]) *PatchingReconciler[T, PT] { +func NewPatchingReconciler[T any, PT RuntimeObjectWithStatusConditions[T]](log logr.Logger, k8sClient client.Client, objectReconciler ObjectReconciler[T, PT]) *PatchingReconciler[T, PT] { return &PatchingReconciler[T, PT]{ log: log, k8sClient: k8sClient, @@ -41,7 +49,7 @@ func (r *PatchingReconciler[T, PT]) Reconcile(ctx context.Context, req ctrl.Requ obj := PT(new(T)) err := r.k8sClient.Get(ctx, req.NamespacedName, obj) if err != nil { - if apierrors.IsNotFound(err) { + if k8serrors.IsNotFound(err) { return ctrl.Result{}, nil } log.Info(fmt.Sprintf("unable to fetch %T", obj), "reason", err) @@ -54,7 +62,31 @@ func (r *PatchingReconciler[T, PT]) Reconcile(ctx context.Context, req ctrl.Requ ) err = Patch(ctx, r.k8sClient, obj, func() { + readyConditionBuilder := NewReadyConditionBuilder(obj) + defer func() { + meta.SetStatusCondition(obj.StatusConditions(), readyConditionBuilder.WithError(delegateErr).Build()) + }() + result, delegateErr = r.objectReconciler.ReconcileResource(ctx, obj) + if delegateErr == nil { + readyConditionBuilder.Ready() + return + } + + var notReadyErr NotReadyError + if errors.As(delegateErr, ¬ReadyErr) { + readyConditionBuilder.WithReason(notReadyErr.reason).WithMessage(notReadyErr.message) + + if notReadyErr.noRequeue { + result = ctrl.Result{} + delegateErr = nil + } + + if notReadyErr.requeueAfter != nil { + result = ctrl.Result{RequeueAfter: *notReadyErr.requeueAfter} + delegateErr = nil + } + } }) if err != nil { log.Info("patch object failed", "reason", err) diff --git a/tools/k8s/reconcile_test.go b/tools/k8s/reconcile_test.go index 287b6e46c..ec1560a1a 100644 --- a/tools/k8s/reconcile_test.go +++ b/tools/k8s/reconcile_test.go @@ -3,13 +3,15 @@ package k8s_test import ( "context" "errors" + "time" + korifiv1alpha1 "code.cloudfoundry.org/korifi/controllers/api/v1alpha1" + . "code.cloudfoundry.org/korifi/tests/matchers" "github.com/go-logr/logr" "github.com/google/uuid" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/onsi/gomega/gbytes" - corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" @@ -27,18 +29,18 @@ import ( type fakeObjectReconciler struct { reconcileResourceError error reconcileResourceCallCount int - reconcileResourceObj *corev1.Pod + reconcileResourceObj *korifiv1alpha1.CFOrg } -func (f *fakeObjectReconciler) ReconcileResource(ctx context.Context, obj *corev1.Pod) (ctrl.Result, error) { +func (f *fakeObjectReconciler) ReconcileResource(ctx context.Context, obj *korifiv1alpha1.CFOrg) (ctrl.Result, error) { log := logr.FromContextOrDiscard(ctx) log.V(1).Info("fake reconciler reconciling") f.reconcileResourceCallCount++ f.reconcileResourceObj = obj - obj.Spec.RestartPolicy = corev1.RestartPolicyOnFailure - obj.Status.Message = "hello" + obj.Spec.DisplayName = "reconciled-display-name" + obj.Status.GUID = "hello" return ctrl.Result{ RequeueAfter: 1, @@ -54,9 +56,9 @@ var _ = Describe("Reconcile", func() { ctx context.Context fakeClient *fake.Client fakeStatusWriter *fake.StatusWriter - patchingReconciler *k8s.PatchingReconciler[corev1.Pod, *corev1.Pod] + patchingReconciler *k8s.PatchingReconciler[korifiv1alpha1.CFOrg, *korifiv1alpha1.CFOrg] objectReconciler *fakeObjectReconciler - pod *corev1.Pod + org *korifiv1alpha1.CFOrg result ctrl.Result err error ) @@ -68,38 +70,36 @@ var _ = Describe("Reconcile", func() { fakeClient.StatusReturns(fakeStatusWriter) ctx = context.Background() - pod = &corev1.Pod{ + org = &korifiv1alpha1.CFOrg{ ObjectMeta: metav1.ObjectMeta{ Namespace: uuid.NewString(), Name: uuid.NewString(), }, - Spec: corev1.PodSpec{}, - Status: corev1.PodStatus{}, } fakeClient.PatchStub = func(_ context.Context, obj client.Object, _ client.Patch, _ ...client.PatchOption) error { - o, ok := obj.(*corev1.Pod) + o, ok := obj.(*korifiv1alpha1.CFOrg) Expect(ok).To(BeTrue()) - o.Status = corev1.PodStatus{} + o.Status = korifiv1alpha1.CFOrgStatus{} return nil } fakeClient.GetStub = func(_ context.Context, _ types.NamespacedName, obj client.Object, _ ...client.GetOption) error { - o, ok := obj.(*corev1.Pod) + o, ok := obj.(*korifiv1alpha1.CFOrg) Expect(ok).To(BeTrue()) - *o = *pod + *o = *org return nil } - patchingReconciler = k8s.NewPatchingReconciler[corev1.Pod, *corev1.Pod](ctrl.Log, fakeClient, objectReconciler) + patchingReconciler = k8s.NewPatchingReconciler[korifiv1alpha1.CFOrg, *korifiv1alpha1.CFOrg](ctrl.Log, fakeClient, objectReconciler) }) JustBeforeEach(func() { result, err = patchingReconciler.Reconcile(ctx, ctrl.Request{ NamespacedName: types.NamespacedName{ - Namespace: pod.Namespace, - Name: pod.Name, + Namespace: org.Namespace, + Name: org.Name, }, }) }) @@ -107,14 +107,14 @@ var _ = Describe("Reconcile", func() { It("fetches the object", func() { Expect(fakeClient.GetCallCount()).To(Equal(1)) _, namespacedName, obj, _ := fakeClient.GetArgsForCall(0) - Expect(namespacedName.Namespace).To(Equal(pod.Namespace)) - Expect(namespacedName.Name).To(Equal(pod.Name)) - Expect(obj).To(BeAssignableToTypeOf(&corev1.Pod{})) + Expect(namespacedName.Namespace).To(Equal(org.Namespace)) + Expect(namespacedName.Name).To(Equal(org.Name)) + Expect(obj).To(BeAssignableToTypeOf(&korifiv1alpha1.CFOrg{})) }) When("the object does not exist", func() { BeforeEach(func() { - fakeClient.GetReturns(apierrors.NewNotFound(schema.GroupResource{}, "pod")) + fakeClient.GetReturns(apierrors.NewNotFound(schema.GroupResource{}, "cforg")) }) It("does not call the object reconciler and succeeds", func() { @@ -137,16 +137,27 @@ var _ = Describe("Reconcile", func() { It("calls the object reconciler", func() { Expect(objectReconciler.reconcileResourceCallCount).To(Equal(1)) - Expect(objectReconciler.reconcileResourceObj.Namespace).To(Equal(pod.Namespace)) - Expect(objectReconciler.reconcileResourceObj.Name).To(Equal(pod.Name)) + Expect(objectReconciler.reconcileResourceObj.Namespace).To(Equal(org.Namespace)) + Expect(objectReconciler.reconcileResourceObj.Name).To(Equal(org.Name)) }) It("patches the object via the k8s client", func() { Expect(fakeClient.PatchCallCount()).To(Equal(1)) _, updatedObject, _, _ := fakeClient.PatchArgsForCall(0) - updatedPod, ok := updatedObject.(*corev1.Pod) + updatedOrg, ok := updatedObject.(*korifiv1alpha1.CFOrg) Expect(ok).To(BeTrue()) - Expect(updatedPod.Spec.RestartPolicy).To(Equal(corev1.RestartPolicyOnFailure)) + Expect(updatedOrg.Spec.DisplayName).To(Equal("reconciled-display-name")) + }) + + It("sets the Ready condition to true", func() { + Expect(fakeStatusWriter.PatchCallCount()).To(Equal(1)) + _, updatedObject, _, _ := fakeStatusWriter.PatchArgsForCall(0) + updatedOrg, ok := updatedObject.(*korifiv1alpha1.CFOrg) + Expect(ok).To(BeTrue()) + Expect(updatedOrg.Status.Conditions).To(ContainElement(SatisfyAll( + HasType(Equal(korifiv1alpha1.StatusConditionReady)), + HasStatus(Equal(metav1.ConditionTrue)), + ))) }) When("patching the object fails", func() { @@ -162,9 +173,9 @@ var _ = Describe("Reconcile", func() { It("patches the object status via the k8s client", func() { Expect(fakeStatusWriter.PatchCallCount()).To(Equal(1)) _, updatedObject, _, _ := fakeStatusWriter.PatchArgsForCall(0) - updatedPod, ok := updatedObject.(*corev1.Pod) + updatedOrg, ok := updatedObject.(*korifiv1alpha1.CFOrg) Expect(ok).To(BeTrue()) - Expect(updatedPod.Status.Message).To(Equal("hello")) + Expect(updatedOrg.Status.GUID).To(Equal("hello")) }) When("patching the object status fails", func() { @@ -195,6 +206,84 @@ var _ = Describe("Reconcile", func() { Expect(fakeClient.PatchCallCount()).To(Equal(1)) Expect(fakeStatusWriter.PatchCallCount()).To(Equal(1)) }) + + It("sets the ready condition to false with unknown reason", func() { + Expect(fakeStatusWriter.PatchCallCount()).To(Equal(1)) + _, updatedObject, _, _ := fakeStatusWriter.PatchArgsForCall(0) + updatedOrg, ok := updatedObject.(*korifiv1alpha1.CFOrg) + Expect(ok).To(BeTrue()) + + Expect(updatedOrg.Status.Conditions).To(ContainElement(SatisfyAll( + HasType(Equal(korifiv1alpha1.StatusConditionReady)), + HasStatus(Equal(metav1.ConditionFalse)), + HasReason(Equal("Unknown")), + ))) + }) + + When("the object reconciliation fails with NotReady error", func() { + BeforeEach(func() { + objectReconciler.reconcileResourceError = k8s.NewNotReadyError().WithReason("TestReason") + }) + + It("sets the ready condition to false with the reason specified", func() { + Expect(fakeStatusWriter.PatchCallCount()).To(Equal(1)) + _, updatedObject, _, _ := fakeStatusWriter.PatchArgsForCall(0) + updatedOrg, ok := updatedObject.(*korifiv1alpha1.CFOrg) + Expect(ok).To(BeTrue()) + + Expect(updatedOrg.Status.Conditions).To(ContainElement(SatisfyAll( + HasType(Equal(korifiv1alpha1.StatusConditionReady)), + HasStatus(Equal(metav1.ConditionFalse)), + HasReason(Equal("TestReason")), + ))) + }) + + When("requeueAfter is specified", func() { + BeforeEach(func() { + objectReconciler.reconcileResourceError = k8s.NewNotReadyError().WithRequeueAfter(time.Minute) + }) + + It("sets the ready condition to false", func() { + Expect(fakeStatusWriter.PatchCallCount()).To(Equal(1)) + _, updatedObject, _, _ := fakeStatusWriter.PatchArgsForCall(0) + updatedOrg, ok := updatedObject.(*korifiv1alpha1.CFOrg) + Expect(ok).To(BeTrue()) + + Expect(updatedOrg.Status.Conditions).To(ContainElement(SatisfyAll( + HasType(Equal(korifiv1alpha1.StatusConditionReady)), + HasStatus(Equal(metav1.ConditionFalse)), + ))) + }) + + It("requeues the reconcile event", func() { + Expect(result).To(Equal(ctrl.Result{RequeueAfter: time.Minute})) + Expect(err).To(BeNil()) + }) + }) + + When("no requeue is specified", func() { + BeforeEach(func() { + objectReconciler.reconcileResourceError = k8s.NewNotReadyError().WithNoRequeue() + }) + + It("sets the ready condition to false", func() { + Expect(fakeStatusWriter.PatchCallCount()).To(Equal(1)) + _, updatedObject, _, _ := fakeStatusWriter.PatchArgsForCall(0) + updatedOrg, ok := updatedObject.(*korifiv1alpha1.CFOrg) + Expect(ok).To(BeTrue()) + + Expect(updatedOrg.Status.Conditions).To(ContainElement(SatisfyAll( + HasType(Equal(korifiv1alpha1.StatusConditionReady)), + HasStatus(Equal(metav1.ConditionFalse)), + ))) + }) + + It("does not requeue the reconcile event", func() { + Expect(result).To(Equal(ctrl.Result{})) + Expect(err).To(BeNil()) + }) + }) + }) }) Describe("logging", func() {