diff --git a/go.mod b/go.mod index ded9e7444..af20d8419 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,7 @@ require ( k8s.io/klog/v2 v2.90.1 k8s.io/kube-aggregator v0.27.2 k8s.io/utils v0.0.0-20230313181309-38a27ef9d749 - open-cluster-management.io/addon-framework v0.7.1-0.20230626092851-963716af4eed + open-cluster-management.io/addon-framework v0.7.1-0.20230705031704-6a328fa5cd63 open-cluster-management.io/api v0.11.1-0.20230703133341-6d7212c2e941 sigs.k8s.io/controller-runtime v0.15.0 sigs.k8s.io/kube-storage-version-migrator v0.0.5 diff --git a/go.sum b/go.sum index 021f069cd..e8042de55 100644 --- a/go.sum +++ b/go.sum @@ -1156,8 +1156,8 @@ k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20230313181309-38a27ef9d749 h1:xMMXJlJbsU8w3V5N2FLDQ8YgU8s1EoULdbQBcAeNJkY= k8s.io/utils v0.0.0-20230313181309-38a27ef9d749/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -open-cluster-management.io/addon-framework v0.7.1-0.20230626092851-963716af4eed h1:fOOKf8kzVNizc5fYvMwkPy9TT/vOpojd4IIxpzh/vhw= -open-cluster-management.io/addon-framework v0.7.1-0.20230626092851-963716af4eed/go.mod h1:Cyt5knxR+sXaKvOfUKseZDAGulS2AJz6o7a9J0WXbak= +open-cluster-management.io/addon-framework v0.7.1-0.20230705031704-6a328fa5cd63 h1:GCsAD1jb6wqhXTHdUM/HcWzv5b2NbZ6FxpLZcxa/jhI= +open-cluster-management.io/addon-framework v0.7.1-0.20230705031704-6a328fa5cd63/go.mod h1:V+WUFC7GD89Lc68eXSN/FJebnCH4NjrfF44VsO0YAC8= open-cluster-management.io/api v0.11.1-0.20230703133341-6d7212c2e941 h1:k10Sx7Th1UDyJ+GYFqWddFq+m6U7x9MHk1g8KwrYy8Y= open-cluster-management.io/api v0.11.1-0.20230703133341-6d7212c2e941/go.mod h1:WgKUCJ7+Bf40DsOmH1Gdkpyj3joco+QLzrlM6Ak39zE= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/pkg/addon/controllers/addontemplate/controller.go b/pkg/addon/controllers/addontemplate/controller.go index 0ebcc4350..82566eb5a 100644 --- a/pkg/addon/controllers/addontemplate/controller.go +++ b/pkg/addon/controllers/addontemplate/controller.go @@ -93,6 +93,7 @@ func (c *addonTemplateController) stopUnusedManagers( stopFunc, ok := c.addonManagers[addOnName] if ok { stopFunc() + delete(c.addonManagers, addOnName) klog.Infof("Stop the manager for addon %s", addOnName) } } diff --git a/pkg/addon/controllers/addontemplate/controller_test.go b/pkg/addon/controllers/addontemplate/controller_test.go index 46571195e..b090ab120 100644 --- a/pkg/addon/controllers/addontemplate/controller_test.go +++ b/pkg/addon/controllers/addontemplate/controller_test.go @@ -7,10 +7,12 @@ import ( "time" "github.com/openshift/library-go/pkg/operator/events/eventstesting" + "github.com/stretchr/testify/assert" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/dynamic/dynamicinformer" dynamicfake "k8s.io/client-go/dynamic/fake" fakekube "k8s.io/client-go/kubernetes/fake" + "k8s.io/client-go/rest" "open-cluster-management.io/addon-framework/pkg/addonmanager/addontesting" "open-cluster-management.io/addon-framework/pkg/utils" @@ -215,3 +217,52 @@ func TestReconcile(t *testing.T) { } } } + +func TestRunController(t *testing.T) { + cases := []struct { + name string + addonName string + expectedErr string + }{ + { + name: "addon name empty", + addonName: "", + expectedErr: "addon name should be set", + }, + { + name: "fake kubeconfig", + addonName: "test", + expectedErr: `Get "http://localhost/api": dial tcp [::1]:80: connect: connection refused`, + }, + } + + for _, c := range cases { + fakeAddonClient := fakeaddon.NewSimpleClientset() + addonInformers := addoninformers.NewSharedInformerFactory(fakeAddonClient, 10*time.Minute) + fakeDynamicClient := dynamicfake.NewSimpleDynamicClient(runtime.NewScheme()) + dynamicInformerFactory := dynamicinformer.NewDynamicSharedInformerFactory(fakeDynamicClient, 0) + fakeClusterClient := fakecluster.NewSimpleClientset() + clusterInformers := clusterv1informers.NewSharedInformerFactory(fakeClusterClient, 10*time.Minute) + fakeWorkClient := fakework.NewSimpleClientset() + workInformers := workinformers.NewSharedInformerFactory(fakeWorkClient, 10*time.Minute) + hubKubeClient := fakekube.NewSimpleClientset() + controller := &addonTemplateController{ + kubeConfig: &rest.Config{}, + kubeClient: hubKubeClient, + addonClient: fakeAddonClient, + cmaLister: addonInformers.Addon().V1alpha1().ClusterManagementAddOns().Lister(), + addonManagers: make(map[string]context.CancelFunc), + addonInformers: addonInformers, + clusterInformers: clusterInformers, + dynamicInformers: dynamicInformerFactory, + workInformers: workInformers, + } + ctx := context.TODO() + + err := controller.runController(ctx, c.addonName) + if len(c.expectedErr) == 0 { + assert.NoError(t, err) + } + assert.EqualErrorf(t, err, c.expectedErr, "name : %s, expected error %v, but got %v", c.name, c.expectedErr, err) + } +} diff --git a/pkg/addon/templateagent/template_agent.go b/pkg/addon/templateagent/template_agent.go index c558e1932..db175a1e8 100644 --- a/pkg/addon/templateagent/template_agent.go +++ b/pkg/addon/templateagent/template_agent.go @@ -4,7 +4,6 @@ import ( "fmt" "github.com/valyala/fasttemplate" - appsv1 "k8s.io/api/apps/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" @@ -105,9 +104,11 @@ func (a *CRDTemplateAgentAddon) GetAgentAddonOptions() agent.AgentAddonOptions { supportedConfigGVRs = append(supportedConfigGVRs, gvr) } return agent.AgentAddonOptions{ - AddonName: a.addonName, - InstallStrategy: nil, - HealthProber: nil, + AddonName: a.addonName, + InstallStrategy: nil, + HealthProber: &agent.HealthProber{ + Type: agent.HealthProberTypeDeploymentAvailability, + }, SupportedConfigGVRs: supportedConfigGVRs, Registration: &agent.RegistrationOption{ CSRConfigurations: a.TemplateCSRConfigurationsFunc(), @@ -159,7 +160,7 @@ func (a *CRDTemplateAgentAddon) decorateObjects( newImageDecorator(privateValues), } for index, obj := range objects { - deployment, err := a.convertToDeployment(obj) + deployment, err := utils.ConvertToDeployment(obj) if err != nil { continue } @@ -176,26 +177,6 @@ func (a *CRDTemplateAgentAddon) decorateObjects( return objects, nil } -func (a *CRDTemplateAgentAddon) convertToDeployment(obj runtime.Object) (*appsv1.Deployment, error) { - if obj.GetObjectKind().GroupVersionKind().Group != "apps" || - obj.GetObjectKind().GroupVersionKind().Kind != "Deployment" { - return nil, fmt.Errorf("not deployment object, %v", obj.GetObjectKind()) - } - - deployment := &appsv1.Deployment{} - uobj, ok := obj.(*unstructured.Unstructured) - if !ok { - return deployment, fmt.Errorf("not unstructured object, %v", obj.GetObjectKind()) - } - - err := runtime.DefaultUnstructuredConverter. - FromUnstructured(uobj.Object, deployment) - if err != nil { - return nil, err - } - return deployment, nil -} - // GetDesiredAddOnTemplateByAddon returns the desired template of the addon func (a *CRDTemplateAgentAddon) GetDesiredAddOnTemplateByAddon( addon *addonapiv1alpha1.ManagedClusterAddOn) (*addonapiv1alpha1.AddOnTemplate, error) { diff --git a/test/e2e/addonmanagement_test.go b/test/e2e/addonmanagement_test.go index d9195741b..a46fcbac4 100644 --- a/test/e2e/addonmanagement_test.go +++ b/test/e2e/addonmanagement_test.go @@ -169,6 +169,10 @@ var _ = ginkgo.Describe("Enable addon management feature gate", ginkgo.Label("ad ginkgo.By(fmt.Sprintf("clean klusterlet %v resources after the test case", klusterletName)) gomega.Expect(t.cleanKlusterletResources(klusterletName, clusterName)).To(gomega.BeNil()) + ginkgo.By(fmt.Sprintf("Cleaning managed cluster namespace %s", clusterName)) + err = t.HubKubeClient.CoreV1().Namespaces().Delete(context.TODO(), clusterName, metav1.DeleteOptions{}) + gomega.Expect(err).ToNot(gomega.HaveOccurred()) + // disable addon management feature gate gomega.Eventually(func() error { clusterManager, err := t.OperatorClient.OperatorV1().ClusterManagers().Get(context.TODO(), "cluster-manager", metav1.GetOptions{}) diff --git a/vendor/modules.txt b/vendor/modules.txt index ce2298372..e6dfeb365 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1406,7 +1406,7 @@ k8s.io/utils/path k8s.io/utils/pointer k8s.io/utils/strings/slices k8s.io/utils/trace -# open-cluster-management.io/addon-framework v0.7.1-0.20230626092851-963716af4eed +# open-cluster-management.io/addon-framework v0.7.1-0.20230705031704-6a328fa5cd63 ## explicit; go 1.19 open-cluster-management.io/addon-framework/pkg/addonfactory open-cluster-management.io/addon-framework/pkg/addonmanager diff --git a/vendor/open-cluster-management.io/addon-framework/pkg/addonmanager/controllers/agentdeploy/controller.go b/vendor/open-cluster-management.io/addon-framework/pkg/addonmanager/controllers/agentdeploy/controller.go index 113632498..c7e974781 100644 --- a/vendor/open-cluster-management.io/addon-framework/pkg/addonmanager/controllers/agentdeploy/controller.go +++ b/vendor/open-cluster-management.io/addon-framework/pkg/addonmanager/controllers/agentdeploy/controller.go @@ -346,7 +346,7 @@ func (c *addonDeployController) buildDeployManifestWorks(installMode, workNamesp return nil, nil, nil } - manifestOptions := getManifestConfigOption(agentAddon) + manifestOptions := getManifestConfigOption(agentAddon, cluster, addon) existingWorksCopy := []workapiv1.ManifestWork{} for _, work := range existingWorks { existingWorksCopy = append(existingWorksCopy, *work) diff --git a/vendor/open-cluster-management.io/addon-framework/pkg/addonmanager/controllers/agentdeploy/healthcheck_sync.go b/vendor/open-cluster-management.io/addon-framework/pkg/addonmanager/controllers/agentdeploy/healthcheck_sync.go index 97ffe032f..e65e7c454 100644 --- a/vendor/open-cluster-management.io/addon-framework/pkg/addonmanager/controllers/agentdeploy/healthcheck_sync.go +++ b/vendor/open-cluster-management.io/addon-framework/pkg/addonmanager/controllers/agentdeploy/healthcheck_sync.go @@ -14,6 +14,7 @@ import ( "open-cluster-management.io/addon-framework/pkg/addonmanager/constants" "open-cluster-management.io/addon-framework/pkg/agent" "open-cluster-management.io/addon-framework/pkg/basecontroller/factory" + "open-cluster-management.io/addon-framework/pkg/utils" ) type healthCheckSyncer struct { @@ -33,7 +34,7 @@ func (s *healthCheckSyncer) sync(ctx context.Context, } switch s.agentAddon.GetAgentAddonOptions().HealthProber.Type { - case agent.HealthProberTypeWork, agent.HealthProberTypeNone: + case agent.HealthProberTypeWork, agent.HealthProberTypeNone, agent.HealthProberTypeDeploymentAvailability: expectedHealthCheckMode = addonapiv1alpha1.HealthCheckModeCustomized case agent.HealthProberTypeLease: expectedHealthCheckMode = addonapiv1alpha1.HealthCheckModeLease @@ -45,11 +46,25 @@ func (s *healthCheckSyncer) sync(ctx context.Context, addon.Status.HealthCheck.Mode = expectedHealthCheckMode } - err := s.probeAddonStatus(addon) + err := s.probeAddonStatus(cluster, addon) return addon, err } -func (s *healthCheckSyncer) probeAddonStatus(addon *addonapiv1alpha1.ManagedClusterAddOn) error { +func (s *healthCheckSyncer) probeAddonStatus( + cluster *clusterv1.ManagedCluster, + addon *addonapiv1alpha1.ManagedClusterAddOn) error { + switch s.agentAddon.GetAgentAddonOptions().HealthProber.Type { + case agent.HealthProberTypeWork: + return s.probeWorkAddonStatus(cluster, addon) + case agent.HealthProberTypeDeploymentAvailability: + return s.probeDeploymentAvailabilityAddonStatus(cluster, addon) + default: + return nil + } +} +func (s *healthCheckSyncer) probeWorkAddonStatus( + cluster *clusterv1.ManagedCluster, + addon *addonapiv1alpha1.ManagedClusterAddOn) error { if s.agentAddon.GetAgentAddonOptions().HealthProber.Type != agent.HealthProberTypeWork { return nil } @@ -69,6 +84,27 @@ func (s *healthCheckSyncer) probeAddonStatus(addon *addonapiv1alpha1.ManagedClus return nil } + return s.probeAddonStatusByWorks(cluster, addon) +} + +func (s *healthCheckSyncer) probeDeploymentAvailabilityAddonStatus( + cluster *clusterv1.ManagedCluster, addon *addonapiv1alpha1.ManagedClusterAddOn) error { + + if s.agentAddon.GetAgentAddonOptions().HealthProber.Type != agent.HealthProberTypeDeploymentAvailability { + return nil + } + + // wait for the addon manifest applied + if meta.FindStatusCondition(addon.Status.Conditions, addonapiv1alpha1.ManagedClusterAddOnManifestApplied) == nil { + return nil + } + + return s.probeAddonStatusByWorks(cluster, addon) +} + +func (s *healthCheckSyncer) probeAddonStatusByWorks( + cluster *clusterv1.ManagedCluster, addon *addonapiv1alpha1.ManagedClusterAddOn) error { + addonWorks, err := s.getWorkByAddon(addon.Name, addon.Namespace) if err != nil || len(addonWorks) == 0 { meta.SetStatusCondition(&addon.Status.Conditions, metav1.Condition{ @@ -109,7 +145,11 @@ func (s *healthCheckSyncer) probeAddonStatus(addon *addonapiv1alpha1.ManagedClus manifestConditions = append(manifestConditions, work.Status.ResourceStatus.Manifests...) } - probeFields := s.agentAddon.GetAgentAddonOptions().HealthProber.WorkProber.ProbeFields + probeFields, healthChecker, err := s.analyzeWorkProber(s.agentAddon, cluster, addon) + if err != nil { + // should not happen, return + return err + } for _, field := range probeFields { result := findResultByIdentifier(field.ResourceIdentifier, manifestConditions) @@ -125,7 +165,7 @@ func (s *healthCheckSyncer) probeAddonStatus(addon *addonapiv1alpha1.ManagedClus return nil } - err := s.agentAddon.GetAgentAddonOptions().HealthProber.WorkProber.HealthCheck(field.ResourceIdentifier, *result) + err := healthChecker(field.ResourceIdentifier, *result) if err != nil { meta.SetStatusCondition(&addon.Status.Conditions, metav1.Condition{ Type: addonapiv1alpha1.ManagedClusterAddOnConditionAvailable, @@ -146,6 +186,50 @@ func (s *healthCheckSyncer) probeAddonStatus(addon *addonapiv1alpha1.ManagedClus return nil } +func (s *healthCheckSyncer) analyzeWorkProber( + agentAddon agent.AgentAddon, + cluster *clusterv1.ManagedCluster, + addon *addonapiv1alpha1.ManagedClusterAddOn, +) ([]agent.ProbeField, agent.AddonHealthCheckFunc, error) { + + switch agentAddon.GetAgentAddonOptions().HealthProber.Type { + case agent.HealthProberTypeWork: + workProber := agentAddon.GetAgentAddonOptions().HealthProber.WorkProber + if workProber != nil { + return workProber.ProbeFields, workProber.HealthCheck, nil + } + return nil, nil, fmt.Errorf("work prober is not configured") + case agent.HealthProberTypeDeploymentAvailability: + return s.analyzeDeploymentWorkProber(agentAddon, cluster, addon) + default: + return nil, nil, fmt.Errorf("unsupported health prober type %s", agentAddon.GetAgentAddonOptions().HealthProber.Type) + } +} + +func (s *healthCheckSyncer) analyzeDeploymentWorkProber( + agentAddon agent.AgentAddon, + cluster *clusterv1.ManagedCluster, + addon *addonapiv1alpha1.ManagedClusterAddOn, +) ([]agent.ProbeField, agent.AddonHealthCheckFunc, error) { + probeFields := []agent.ProbeField{} + + manifests, err := agentAddon.Manifests(cluster, addon) + if err != nil { + return nil, nil, err + } + + deployments := utils.FilterDeployments(manifests) + for _, deployment := range deployments { + manifestConfig := utils.DeploymentWellKnowManifestConfig(deployment.Namespace, deployment.Name) + probeFields = append(probeFields, agent.ProbeField{ + ResourceIdentifier: manifestConfig.ResourceIdentifier, + ProbeRules: manifestConfig.FeedbackRules, + }) + } + + return probeFields, utils.DeploymentAvailabilityHealthCheck, nil +} + func findResultByIdentifier(identifier workapiv1.ResourceIdentifier, manifestConditions []workapiv1.ManifestCondition) *workapiv1.StatusFeedbackResult { for _, status := range manifestConditions { if identifier.Group != status.ResourceMeta.Group { diff --git a/vendor/open-cluster-management.io/addon-framework/pkg/addonmanager/controllers/agentdeploy/utils.go b/vendor/open-cluster-management.io/addon-framework/pkg/addonmanager/controllers/agentdeploy/utils.go index b550cae6e..9450dbdf1 100644 --- a/vendor/open-cluster-management.io/addon-framework/pkg/addonmanager/controllers/agentdeploy/utils.go +++ b/vendor/open-cluster-management.io/addon-framework/pkg/addonmanager/controllers/agentdeploy/utils.go @@ -10,11 +10,13 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/klog/v2" addonapiv1alpha1 "open-cluster-management.io/api/addon/v1alpha1" + clusterv1 "open-cluster-management.io/api/cluster/v1" "open-cluster-management.io/api/utils/work/v1/workbuilder" workapiv1 "open-cluster-management.io/api/work/v1" "open-cluster-management.io/addon-framework/pkg/addonmanager/constants" "open-cluster-management.io/addon-framework/pkg/agent" + "open-cluster-management.io/addon-framework/pkg/utils" ) func addonHasFinalizer(addon *addonapiv1alpha1.ManagedClusterAddOn, finalizer string) bool { @@ -473,7 +475,9 @@ func newAddonWorkObjectMeta(namePrefix, addonName, addonNamespace, workNamespace } } -func getManifestConfigOption(agentAddon agent.AgentAddon) []workapiv1.ManifestConfigOption { +func getManifestConfigOption(agentAddon agent.AgentAddon, + cluster *clusterv1.ManagedCluster, + addon *addonapiv1alpha1.ManagedClusterAddOn) []workapiv1.ManifestConfigOption { manifestConfigs := []workapiv1.ManifestConfigOption{} if agentAddon.GetAgentAddonOptions().HealthProber != nil && @@ -488,6 +492,21 @@ func getManifestConfigOption(agentAddon agent.AgentAddon) []workapiv1.ManifestCo } } + if agentAddon.GetAgentAddonOptions().HealthProber != nil && + agentAddon.GetAgentAddonOptions().HealthProber.Type == agent.HealthProberTypeDeploymentAvailability { + + manifests, err := agentAddon.Manifests(cluster, addon) + if err != nil { + return manifestConfigs + } + + deployments := utils.FilterDeployments(manifests) + for _, deployment := range deployments { + manifestConfig := utils.DeploymentWellKnowManifestConfig(deployment.Namespace, deployment.Name) + manifestConfigs = append(manifestConfigs, manifestConfig) + } + } + if updaters := agentAddon.GetAgentAddonOptions().Updaters; updaters != nil { for _, updater := range updaters { manifestConfigs = append(manifestConfigs, workapiv1.ManifestConfigOption{ diff --git a/vendor/open-cluster-management.io/addon-framework/pkg/agent/inteface.go b/vendor/open-cluster-management.io/addon-framework/pkg/agent/inteface.go index 3138067a4..69415dbea 100644 --- a/vendor/open-cluster-management.io/addon-framework/pkg/agent/inteface.go +++ b/vendor/open-cluster-management.io/addon-framework/pkg/agent/inteface.go @@ -192,6 +192,10 @@ const ( // clusters. The addon framework will check if the work is Available on the spoke. In addition // user can define a prober to check more detailed status based on status feedback from work. HealthProberTypeWork HealthProberType = "Work" + // HealthProberTypeDeploymentAvailability indicates the healthiness of the addon is connected + // with the availability of the corresponding agent deployment resources on the managed cluster. + // It's a special case of HealthProberTypeWork. + HealthProberTypeDeploymentAvailability HealthProberType = "DeploymentAvailability" ) func KubeClientSignerConfigurations(addonName, agentName string) func(cluster *clusterv1.ManagedCluster) []addonapiv1alpha1.RegistrationConfig { diff --git a/vendor/open-cluster-management.io/addon-framework/pkg/utils/probe_helper.go b/vendor/open-cluster-management.io/addon-framework/pkg/utils/probe_helper.go index 2dd4c1323..5f0a52837 100644 --- a/vendor/open-cluster-management.io/addon-framework/pkg/utils/probe_helper.go +++ b/vendor/open-cluster-management.io/addon-framework/pkg/utils/probe_helper.go @@ -3,6 +3,9 @@ package utils import ( "fmt" + appsv1 "k8s.io/api/apps/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" workapiv1 "open-cluster-management.io/api/work/v1" @@ -18,25 +21,17 @@ type DeploymentProber struct { func NewDeploymentProber(deployments ...types.NamespacedName) *agent.HealthProber { probeFields := []agent.ProbeField{} for _, deploy := range deployments { + mc := DeploymentWellKnowManifestConfig(deploy.Namespace, deploy.Name) probeFields = append(probeFields, agent.ProbeField{ - ResourceIdentifier: workapiv1.ResourceIdentifier{ - Group: "apps", - Resource: "deployments", - Name: deploy.Name, - Namespace: deploy.Namespace, - }, - ProbeRules: []workapiv1.FeedbackRule{ - { - Type: workapiv1.WellKnownStatusType, - }, - }, + ResourceIdentifier: mc.ResourceIdentifier, + ProbeRules: mc.FeedbackRules, }) } return &agent.HealthProber{ Type: agent.HealthProberTypeWork, WorkProber: &agent.WorkHealthProber{ ProbeFields: probeFields, - HealthCheck: HealthCheck, + HealthCheck: DeploymentAvailabilityHealthCheck, }, } } @@ -61,7 +56,14 @@ func (d *DeploymentProber) ProbeFields() []agent.ProbeField { return probeFields } -func HealthCheck(identifier workapiv1.ResourceIdentifier, result workapiv1.StatusFeedbackResult) error { +func DeploymentAvailabilityHealthCheck(identifier workapiv1.ResourceIdentifier, result workapiv1.StatusFeedbackResult) error { + if identifier.Resource != "deployments" { + return fmt.Errorf("unsupported resource type %s", identifier.Resource) + } + if identifier.Group != "apps" { + return fmt.Errorf("unsupported resource group %s", identifier.Group) + } + if len(result.Values) == 0 { return fmt.Errorf("no values are probed for deployment %s/%s", identifier.Namespace, identifier.Name) } @@ -78,3 +80,54 @@ func HealthCheck(identifier workapiv1.ResourceIdentifier, result workapiv1.Statu } return fmt.Errorf("readyReplica is not probed") } + +func FilterDeployments(objects []runtime.Object) []*appsv1.Deployment { + deployments := []*appsv1.Deployment{} + for _, obj := range objects { + deployment, err := ConvertToDeployment(obj) + if err != nil { + continue + } + deployments = append(deployments, deployment) + } + return deployments +} + +func ConvertToDeployment(obj runtime.Object) (*appsv1.Deployment, error) { + if deployment, ok := obj.(*appsv1.Deployment); ok { + return deployment, nil + } + + if obj.GetObjectKind().GroupVersionKind().Group != "apps" || + obj.GetObjectKind().GroupVersionKind().Kind != "Deployment" { + return nil, fmt.Errorf("not deployment object, %v", obj.GetObjectKind()) + } + + deployment := &appsv1.Deployment{} + uobj, ok := obj.(*unstructured.Unstructured) + if !ok { + return deployment, fmt.Errorf("not unstructured object, %v", obj.GetObjectKind()) + } + + err := runtime.DefaultUnstructuredConverter.FromUnstructured(uobj.Object, deployment) + if err != nil { + return nil, err + } + return deployment, nil +} + +func DeploymentWellKnowManifestConfig(namespace, name string) workapiv1.ManifestConfigOption { + return workapiv1.ManifestConfigOption{ + ResourceIdentifier: workapiv1.ResourceIdentifier{ + Group: "apps", + Resource: "deployments", + Name: name, + Namespace: namespace, + }, + FeedbackRules: []workapiv1.FeedbackRule{ + { + Type: workapiv1.WellKnownStatusType, + }, + }, + } +}