Files
open-cluster-management/test/e2e/addonmanagement_test.go
Jian Zhu 3f9b8867eb Add a configmap to handle the proxy ca bundle (#772)
* Add a configmap to handle the proxy ca bundle

Signed-off-by: zhujian <jiazhu@redhat.com>

* Use contextual logger

Signed-off-by: zhujian <jiazhu@redhat.com>

---------

Signed-off-by: zhujian <jiazhu@redhat.com>
2024-12-19 03:29:03 +00:00

985 lines
34 KiB
Go

package e2e
import (
"context"
"embed"
"encoding/json"
"fmt"
ginkgo "github.com/onsi/ginkgo/v2"
"github.com/onsi/gomega"
"github.com/valyala/fasttemplate"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/rand"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/klog/v2"
addonapiv1alpha1 "open-cluster-management.io/api/addon/v1alpha1"
clusterv1 "open-cluster-management.io/api/cluster/v1"
workapiv1 "open-cluster-management.io/api/work/v1"
"open-cluster-management.io/ocm/pkg/addon/templateagent"
"open-cluster-management.io/ocm/test/e2e/manifests"
)
const (
nodePlacementDeploymentConfigName = "node-placement-deploy-config"
imageOverrideDeploymentConfigName = "image-override-deploy-config"
namespaceOverrideConfigName = "namespace-override-config"
proxyDeploymentConfigName = "proxy-deploy-config"
originalImageValue = "quay.io/open-cluster-management/addon-examples:latest"
overrideImageValue = "quay.io/ocm/addon-examples:latest"
customSignerName = "example.com/signer-name"
//#nosec G101
customSignerSecretName = "addon-signer-secret"
)
var (
nodeSelector = map[string]string{"kubernetes.io/os": "linux"}
tolerations = []corev1.Toleration{{Key: "foo", Operator: corev1.TolerationOpExists, Effect: corev1.TaintEffectNoExecute}}
registries = []addonapiv1alpha1.ImageMirror{
{
Source: "quay.io/open-cluster-management/addon-examples",
Mirror: "quay.io/ocm/addon-examples",
},
}
proxyConfig = addonapiv1alpha1.ProxyConfig{
HTTPProxy: "http://proxy.example.com",
HTTPSProxy: "http://proxy.example.com",
NoProxy: "localhost",
CABundle: []byte("test-ca-bundle"),
}
)
var _ = ginkgo.Describe("Enable addon management feature gate", ginkgo.Ordered, ginkgo.Label("addon-manager"), func() {
addOnName := "hello-template"
addonInstallNamespace := "test-addon-template"
s := runtime.NewScheme()
_ = scheme.AddToScheme(s)
_ = clusterv1.Install(s)
_ = addonapiv1alpha1.Install(s)
templateResources := []string{
"addon/addon_template.yaml",
"addon/cluster_management_addon.yaml",
"addon/cluster_role.yaml",
"addon/signca_secret_role.yaml",
"addon/signca_secret_rolebinding.yaml",
}
var signerSecretNamespace string
ginkgo.BeforeEach(func() {
signerSecretNamespace = "signer-secret-test-ns" + rand.String(6)
ginkgo.By("create addon custom sign secret namespace")
_, err := hub.KubeClient.CoreV1().Namespaces().Create(context.TODO(), &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: signerSecretNamespace,
},
}, metav1.CreateOptions{})
if err != nil && !errors.IsAlreadyExists(err) {
gomega.Expect(err).ToNot(gomega.HaveOccurred())
}
ginkgo.By("create addon custom sign secret")
err = copySignerSecret(context.TODO(), hub.KubeClient, "open-cluster-management-hub",
"signer-secret", signerSecretNamespace, customSignerSecretName)
gomega.Expect(err).ToNot(gomega.HaveOccurred())
// the addon manager deployment should be running
gomega.Eventually(func() error {
return hub.CheckHubReady()
}).Should(gomega.Succeed())
ginkgo.By(fmt.Sprintf("create addon template resources for cluster %v", universalClusterName))
err = createResourcesFromYamlFiles(context.Background(), hub.DynamicClient, hub.RestMapper, s,
defaultAddonTemplateReaderManifestsFunc(manifests.AddonManifestFiles, map[string]interface{}{
"Namespace": universalClusterName,
"AddonInstallNamespace": addonInstallNamespace,
"CustomSignerName": customSignerName,
"AddonManagerNamespace": templateagent.AddonManagerNamespace(),
"CustomSignerSecretName": customSignerSecretName,
"CustomSignerSecretNamespace": signerSecretNamespace,
}),
templateResources,
)
gomega.Expect(err).ToNot(gomega.HaveOccurred())
ginkgo.By(fmt.Sprintf("create the addon %v on the managed cluster namespace %v", addOnName, universalClusterName))
err = hub.CreateManagedClusterAddOn(universalClusterName, addOnName, "test-ns") // the install namespace will be ignored
if err != nil {
klog.Errorf("failed to create managed cluster addon %v on the managed cluster namespace %v: %v", addOnName, universalClusterName, err)
gomega.Expect(errors.IsAlreadyExists(err)).To(gomega.BeTrue())
}
ginkgo.By(fmt.Sprintf("wait the addon %v/%v available condition to be true", universalClusterName, addOnName))
gomega.Eventually(func() error {
return hub.CheckManagedClusterAddOnStatus(universalClusterName, addOnName)
}).Should(gomega.Succeed())
})
ginkgo.AfterEach(func() {
ginkgo.By(fmt.Sprintf("delete the addon %v on the managed cluster namespace %v", addOnName, universalClusterName))
err := hub.AddonClient.AddonV1alpha1().ManagedClusterAddOns(universalClusterName).Delete(
context.TODO(), addOnName, metav1.DeleteOptions{})
if err != nil && !errors.IsNotFound(err) {
ginkgo.Fail(fmt.Sprintf("failed to delete managed cluster addon %v on cluster %v: %v", addOnName, universalClusterName, err))
}
gomega.Eventually(func() error {
_, err := hub.AddonClient.AddonV1alpha1().ManagedClusterAddOns(universalClusterName).Get(
context.TODO(), addOnName, metav1.GetOptions{})
if err != nil {
if errors.IsNotFound(err) {
return nil
}
return err
}
return fmt.Errorf("the managedClusterAddon should be deleted")
}).ShouldNot(gomega.HaveOccurred())
ginkgo.By(fmt.Sprintf("delete addon template resources for cluster %v", universalClusterName))
err = deleteResourcesFromYamlFiles(context.Background(), hub.DynamicClient, hub.RestMapper, s,
defaultAddonTemplateReaderManifestsFunc(manifests.AddonManifestFiles, map[string]interface{}{
"Namespace": universalClusterName,
"AddonInstallNamespace": addonInstallNamespace,
"CustomSignerName": customSignerName,
"AddonManagerNamespace": templateagent.AddonManagerNamespace(),
"CustomSignerSecretName": customSignerSecretName,
"CustomSignerSecretNamespace": signerSecretNamespace,
}),
templateResources,
)
gomega.Expect(err).ToNot(gomega.HaveOccurred())
ginkgo.By("delete addon custom sign secret")
err = hub.KubeClient.CoreV1().Secrets(signerSecretNamespace).Delete(context.TODO(),
customSignerSecretName, metav1.DeleteOptions{})
if err != nil && !errors.IsNotFound(err) {
ginkgo.Fail(fmt.Sprintf("failed to delete custom signer secret %v/%v: %v",
signerSecretNamespace, customSignerSecretName, err))
}
ginkgo.By("delete addon custom sign secret namespace")
err = hub.KubeClient.CoreV1().Namespaces().Delete(context.TODO(), signerSecretNamespace, metav1.DeleteOptions{})
if err != nil && !errors.IsNotFound(err) {
ginkgo.Fail(fmt.Sprintf("failed to delete custom signer secret namespace %v: %v", signerSecretNamespace, err))
}
// delete all CSR created for the addon on the hub cluster, otherwise if it reches the limit number 10, the
// other tests will fail
gomega.Eventually(func() error {
csrs, err := hub.KubeClient.CertificatesV1().CertificateSigningRequests().List(context.TODO(),
metav1.ListOptions{
LabelSelector: fmt.Sprintf("%s=%s,%s=%s", addonapiv1alpha1.AddonLabelKey, addOnName,
clusterv1.ClusterNameLabelKey, universalClusterName),
})
if err != nil {
return err
}
for _, csr := range csrs.Items {
err = hub.KubeClient.CertificatesV1().CertificateSigningRequests().Delete(context.TODO(),
csr.Name, metav1.DeleteOptions{})
if err != nil {
return err
}
}
return nil
}).ShouldNot(gomega.HaveOccurred())
})
ginkgo.It("Template type addon should be functioning", func() {
ginkgo.By("Check hub kubeconfig secret is created")
gomega.Eventually(func() error {
_, err := hub.KubeClient.CoreV1().Secrets(addonInstallNamespace).Get(context.TODO(),
templateagent.HubKubeconfigSecretName(addOnName), metav1.GetOptions{})
return err
}).Should(gomega.Succeed())
ginkgo.By("Check custom client cert secret is created")
gomega.Eventually(func() error {
_, err := hub.KubeClient.CoreV1().Secrets(addonInstallNamespace).Get(context.TODO(),
templateagent.CustomSignedSecretName(addOnName, customSignerName), metav1.GetOptions{})
return err
}).Should(gomega.Succeed())
ginkgo.By("Make sure addon is functioning")
configmap := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("config-%s", rand.String(6)),
Namespace: universalClusterName,
},
Data: map[string]string{
"key1": rand.String(6),
"key2": rand.String(6),
},
}
_, err := hub.KubeClient.CoreV1().ConfigMaps(universalClusterName).Create(
context.Background(), configmap, metav1.CreateOptions{})
gomega.Expect(err).ToNot(gomega.HaveOccurred())
gomega.Eventually(func() error {
copyiedConfig, err := spoke.KubeClient.CoreV1().ConfigMaps(addonInstallNamespace).Get(
context.Background(), configmap.Name, metav1.GetOptions{})
if err != nil {
return err
}
if !equality.Semantic.DeepEqual(copyiedConfig.Data, configmap.Data) {
return fmt.Errorf("expected configmap is not correct, %v", copyiedConfig.Data)
}
return nil
}).ShouldNot(gomega.HaveOccurred())
ginkgo.By("Make sure manifestwork config is configured")
manifestWork, err := hub.WorkClient.WorkV1().ManifestWorks(universalClusterName).Get(context.Background(),
fmt.Sprintf("addon-%s-deploy-0", addOnName), metav1.GetOptions{})
gomega.Expect(err).ToNot(gomega.HaveOccurred())
foundDeploymentConfig := false
expectedDeploymentResourceIdentifier := workapiv1.ResourceIdentifier{
Group: "apps",
Resource: "deployments",
Name: "hello-template-agent",
Namespace: addonInstallNamespace,
}
foundDaemonSetConfig := false
expectedDaemonSetResourceIdentifier := workapiv1.ResourceIdentifier{
Group: "apps",
Resource: "daemonsets",
Name: "hello-template-agent-ds",
Namespace: addonInstallNamespace,
}
for _, mc := range manifestWork.Spec.ManifestConfigs {
if mc.ResourceIdentifier == expectedDeploymentResourceIdentifier {
foundDeploymentConfig = true
gomega.Expect(mc.UpdateStrategy.Type).To(gomega.Equal(workapiv1.UpdateStrategyTypeServerSideApply))
}
if mc.ResourceIdentifier == expectedDaemonSetResourceIdentifier {
foundDaemonSetConfig = true
}
}
if !foundDeploymentConfig || !foundDaemonSetConfig {
gomega.Expect(fmt.Errorf("expected manifestwork is not correct, %v",
manifestWork.Spec.ManifestConfigs)).ToNot(gomega.HaveOccurred())
}
ginkgo.By(fmt.Sprintf("delete the addon %v on the managed cluster namespace %v", addOnName, universalClusterName))
err = hub.AddonClient.AddonV1alpha1().ManagedClusterAddOns(universalClusterName).Delete(
context.TODO(), addOnName, metav1.DeleteOptions{})
gomega.Expect(err).ToNot(gomega.HaveOccurred())
ginkgo.By("The pre-delete job should clean up the configmap after the addon is deleted")
gomega.Eventually(func() error {
_, err := spoke.KubeClient.CoreV1().ConfigMaps(addonInstallNamespace).Get(
context.Background(), configmap.Name, metav1.GetOptions{})
if err != nil {
if errors.IsNotFound(err) {
return nil
}
return err
}
return fmt.Errorf("the configmap should be deleted")
}).ShouldNot(gomega.HaveOccurred())
gomega.Eventually(func() error {
_, err := hub.AddonClient.AddonV1alpha1().ManagedClusterAddOns(universalClusterName).Get(
context.TODO(), addOnName, metav1.GetOptions{})
if err != nil {
if errors.IsNotFound(err) {
return nil
}
return err
}
return fmt.Errorf("the managedClusterAddon should be deleted")
}).ShouldNot(gomega.HaveOccurred())
ginkgo.By("The pre-delete job should be deleted ")
gomega.Eventually(func() error {
_, err := spoke.KubeClient.BatchV1().Jobs(addonInstallNamespace).Get(
context.Background(), "hello-template-cleanup-configmap", metav1.GetOptions{})
if err != nil {
if errors.IsNotFound(err) {
return nil
}
return err
}
return fmt.Errorf("the job should be deleted")
}).ShouldNot(gomega.HaveOccurred())
})
ginkgo.It("Template type addon should be configured by addon deployment config for image override"+
"even there are cluster annotation config", func() {
ginkgo.By("Prepare cluster annotation for addon image override config")
overrideRegistries := addonapiv1alpha1.AddOnDeploymentConfigSpec{
// should be different from the registries in the addonDeploymentConfig
Registries: []addonapiv1alpha1.ImageMirror{
{
Source: "quay.io/open-cluster-management/addon-examples",
Mirror: "quay.io/ocm/addon-examples-test",
},
},
}
registriesJson, err := json.Marshal(overrideRegistries)
gomega.Expect(err).ToNot(gomega.HaveOccurred())
gomega.Eventually(func() error {
cluster, err := hub.ClusterClient.ClusterV1().ManagedClusters().Get(
context.Background(), universalClusterName, metav1.GetOptions{})
if err != nil {
return err
}
newCluster := cluster.DeepCopy()
annotations := cluster.Annotations
if annotations == nil {
annotations = make(map[string]string)
}
annotations[clusterv1.ClusterImageRegistriesAnnotationKey] = string(registriesJson)
newCluster.Annotations = annotations
_, err = hub.ClusterClient.ClusterV1().ManagedClusters().Update(
context.Background(), newCluster, metav1.UpdateOptions{})
return err
}).ShouldNot(gomega.HaveOccurred())
ginkgo.By("Prepare a AddOnDeploymentConfig for addon image override config")
gomega.Eventually(func() error {
return prepareImageOverrideAddOnDeploymentConfig(universalClusterName, addonInstallNamespace)
}).ShouldNot(gomega.HaveOccurred())
ginkgo.By("Add the configs to ManagedClusterAddOn")
gomega.Eventually(func() error {
addon, err := hub.AddonClient.AddonV1alpha1().ManagedClusterAddOns(universalClusterName).Get(
context.Background(), addOnName, metav1.GetOptions{})
if err != nil {
return err
}
newAddon := addon.DeepCopy()
newAddon.Spec.Configs = []addonapiv1alpha1.AddOnConfig{
{
ConfigGroupResource: addonapiv1alpha1.ConfigGroupResource{
Group: "addon.open-cluster-management.io",
Resource: "addondeploymentconfigs",
},
ConfigReferent: addonapiv1alpha1.ConfigReferent{
Namespace: universalClusterName,
Name: imageOverrideDeploymentConfigName,
},
},
}
_, err = hub.AddonClient.AddonV1alpha1().ManagedClusterAddOns(universalClusterName).Update(
context.Background(), newAddon, metav1.UpdateOptions{})
if err != nil {
return err
}
return nil
}).ShouldNot(gomega.HaveOccurred())
ginkgo.By("Make sure addon is configured")
gomega.Eventually(func() error {
agentDeploy, err := spoke.KubeClient.AppsV1().Deployments(addonInstallNamespace).Get(
context.Background(), "hello-template-agent", metav1.GetOptions{})
if err != nil {
return err
}
containers := agentDeploy.Spec.Template.Spec.Containers
if len(containers) != 1 {
return fmt.Errorf("expect one container, but %v", containers)
}
if containers[0].Image != overrideImageValue {
return fmt.Errorf("unexpected image %s", containers[0].Image)
}
return nil
}).ShouldNot(gomega.HaveOccurred())
ginkgo.By("Restore the managed cluster annotation")
gomega.Eventually(func() error {
cluster, err := hub.ClusterClient.ClusterV1().ManagedClusters().Get(
context.Background(), universalClusterName, metav1.GetOptions{})
if err != nil {
return err
}
newCluster := cluster.DeepCopy()
delete(newCluster.Annotations, clusterv1.ClusterImageRegistriesAnnotationKey)
_, err = hub.ClusterClient.ClusterV1().ManagedClusters().Update(
context.Background(), newCluster, metav1.UpdateOptions{})
return err
}).ShouldNot(gomega.HaveOccurred())
// restore the image override config, because the override image is not available
// but it is needed by the pre-delete job
ginkgo.By("Restore the configs to ManagedClusterAddOn")
gomega.Eventually(func() error {
addon, err := hub.AddonClient.AddonV1alpha1().ManagedClusterAddOns(universalClusterName).Get(
context.Background(), addOnName, metav1.GetOptions{})
if err != nil {
return err
}
newAddon := addon.DeepCopy()
newAddon.Spec.Configs = []addonapiv1alpha1.AddOnConfig{}
_, err = hub.AddonClient.AddonV1alpha1().ManagedClusterAddOns(universalClusterName).Update(
context.Background(), newAddon, metav1.UpdateOptions{})
if err != nil {
return err
}
return nil
}).ShouldNot(gomega.HaveOccurred())
ginkgo.By("Make sure addon config is restored")
gomega.Eventually(func() error {
agentDeploy, err := spoke.KubeClient.AppsV1().Deployments(addonInstallNamespace).Get(
context.Background(), "hello-template-agent", metav1.GetOptions{})
if err != nil {
return err
}
containers := agentDeploy.Spec.Template.Spec.Containers
if len(containers) != 1 {
return fmt.Errorf("expect one container, but %v", containers)
}
if containers[0].Image != originalImageValue {
return fmt.Errorf("unexpected image %s", containers[0].Image)
}
return nil
}).ShouldNot(gomega.HaveOccurred())
})
ginkgo.It("Template type addon should be configured by addon deployment config for node placement", func() {
ginkgo.By("Prepare a AddOnDeploymentConfig for addon image override config")
gomega.Eventually(func() error {
return prepareNodePlacementAddOnDeploymentConfig(universalClusterName, addonInstallNamespace)
}).ShouldNot(gomega.HaveOccurred())
ginkgo.By("Add the configs to ManagedClusterAddOn")
gomega.Eventually(func() error {
addon, err := hub.AddonClient.AddonV1alpha1().ManagedClusterAddOns(universalClusterName).Get(
context.Background(), addOnName, metav1.GetOptions{})
if err != nil {
return err
}
newAddon := addon.DeepCopy()
newAddon.Spec.Configs = []addonapiv1alpha1.AddOnConfig{
{
ConfigGroupResource: addonapiv1alpha1.ConfigGroupResource{
Group: "addon.open-cluster-management.io",
Resource: "addondeploymentconfigs",
},
ConfigReferent: addonapiv1alpha1.ConfigReferent{
Namespace: universalClusterName,
Name: nodePlacementDeploymentConfigName,
},
},
}
_, err = hub.AddonClient.AddonV1alpha1().ManagedClusterAddOns(universalClusterName).Update(
context.Background(), newAddon, metav1.UpdateOptions{})
if err != nil {
return err
}
return nil
}).ShouldNot(gomega.HaveOccurred())
ginkgo.By("Make sure addon is configured")
gomega.Eventually(func() error {
agentDeploy, err := spoke.KubeClient.AppsV1().Deployments(addonInstallNamespace).Get(
context.Background(), "hello-template-agent", metav1.GetOptions{})
if err != nil {
return err
}
if !equality.Semantic.DeepEqual(agentDeploy.Spec.Template.Spec.NodeSelector, nodeSelector) {
return fmt.Errorf("unexpected nodeSeletcor %v", agentDeploy.Spec.Template.Spec.NodeSelector)
}
if !equality.Semantic.DeepEqual(agentDeploy.Spec.Template.Spec.Tolerations, tolerations) {
return fmt.Errorf("unexpected tolerations %v", agentDeploy.Spec.Template.Spec.Tolerations)
}
return nil
}).ShouldNot(gomega.HaveOccurred())
})
ginkgo.It("Template type addon should be configured by addon deployment config for namespace", func() {
ginkgo.By("Prepare a AddOnDeploymentConfig for namespace config")
overrideNamespace := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "another-addon-namespace",
},
}
_, err := spoke.KubeClient.CoreV1().Namespaces().Create(context.TODO(), overrideNamespace, metav1.CreateOptions{})
gomega.Expect(err).ToNot(gomega.HaveOccurred())
gomega.Eventually(func() error {
return prepareInstallNamespace(universalClusterName, overrideNamespace.Name)
}).ShouldNot(gomega.HaveOccurred())
ginkgo.By("Add the configs to ManagedClusterAddOn")
gomega.Eventually(func() error {
addon, err := hub.AddonClient.AddonV1alpha1().ManagedClusterAddOns(universalClusterName).Get(
context.Background(), addOnName, metav1.GetOptions{})
if err != nil {
return err
}
newAddon := addon.DeepCopy()
newAddon.Spec.Configs = []addonapiv1alpha1.AddOnConfig{
{
ConfigGroupResource: addonapiv1alpha1.ConfigGroupResource{
Group: "addon.open-cluster-management.io",
Resource: "addondeploymentconfigs",
},
ConfigReferent: addonapiv1alpha1.ConfigReferent{
Namespace: universalClusterName,
Name: namespaceOverrideConfigName,
},
},
}
_, err = hub.AddonClient.AddonV1alpha1().ManagedClusterAddOns(universalClusterName).Update(
context.Background(), newAddon, metav1.UpdateOptions{})
if err != nil {
return err
}
return nil
}).ShouldNot(gomega.HaveOccurred())
ginkgo.By("Make sure addon is configured")
gomega.Eventually(func() error {
_, err := spoke.KubeClient.AppsV1().Deployments(overrideNamespace.Name).Get(
context.Background(), "hello-template-agent", metav1.GetOptions{})
return err
}).ShouldNot(gomega.HaveOccurred())
})
ginkgo.It("Template type addon's image should be overrode by cluster annotation", func() {
ginkgo.By("Prepare cluster annotation for addon image override config")
overrideRegistries := addonapiv1alpha1.AddOnDeploymentConfigSpec{
Registries: registries,
}
registriesJson, err := json.Marshal(overrideRegistries)
gomega.Expect(err).ToNot(gomega.HaveOccurred())
gomega.Eventually(func() error {
cluster, err := hub.ClusterClient.ClusterV1().ManagedClusters().Get(
context.Background(), universalClusterName, metav1.GetOptions{})
if err != nil {
return err
}
newCluster := cluster.DeepCopy()
annotations := cluster.Annotations
if annotations == nil {
annotations = make(map[string]string)
}
annotations[clusterv1.ClusterImageRegistriesAnnotationKey] = string(registriesJson)
newCluster.Annotations = annotations
_, err = hub.ClusterClient.ClusterV1().ManagedClusters().Update(
context.Background(), newCluster, metav1.UpdateOptions{})
return err
}).ShouldNot(gomega.HaveOccurred())
ginkgo.By("Make sure addon is configured")
gomega.Eventually(func() error {
agentDeploy, err := spoke.KubeClient.AppsV1().Deployments(addonInstallNamespace).Get(
context.Background(), "hello-template-agent", metav1.GetOptions{})
if err != nil {
return err
}
containers := agentDeploy.Spec.Template.Spec.Containers
if len(containers) != 1 {
return fmt.Errorf("expect one container, but %v", containers)
}
if containers[0].Image != overrideImageValue {
return fmt.Errorf("unexpected image %s", containers[0].Image)
}
return nil
}).ShouldNot(gomega.HaveOccurred())
// restore the image override config, because the override image is not available
// but it is needed by the pre-delete job
ginkgo.By("Restore the managed cluster annotation")
gomega.Eventually(func() error {
cluster, err := hub.ClusterClient.ClusterV1().ManagedClusters().Get(
context.Background(), universalClusterName, metav1.GetOptions{})
if err != nil {
return err
}
newCluster := cluster.DeepCopy()
delete(newCluster.Annotations, clusterv1.ClusterImageRegistriesAnnotationKey)
_, err = hub.ClusterClient.ClusterV1().ManagedClusters().Update(
context.Background(), newCluster, metav1.UpdateOptions{})
return err
}).ShouldNot(gomega.HaveOccurred())
ginkgo.By("Make sure addon config is restored")
gomega.Eventually(func() error {
agentDeploy, err := spoke.KubeClient.AppsV1().Deployments(addonInstallNamespace).Get(
context.Background(), "hello-template-agent", metav1.GetOptions{})
if err != nil {
return err
}
containers := agentDeploy.Spec.Template.Spec.Containers
if len(containers) != 1 {
return fmt.Errorf("expect one container, but %v", containers)
}
if containers[0].Image != originalImageValue {
return fmt.Errorf("unexpected image %s", containers[0].Image)
}
return nil
}).ShouldNot(gomega.HaveOccurred())
})
ginkgo.It("Template type addon should be configured by addon deployment config for proxy", func() {
ginkgo.By("Prepare a AddOnDeploymentConfig for addon proxy config")
gomega.Eventually(func() error {
return prepareProxyAddOnDeploymentConfig(universalClusterName, addonInstallNamespace)
}).ShouldNot(gomega.HaveOccurred())
ginkgo.By("Add the configs to ManagedClusterAddOn")
gomega.Eventually(func() error {
addon, err := hub.AddonClient.AddonV1alpha1().ManagedClusterAddOns(universalClusterName).Get(
context.Background(), addOnName, metav1.GetOptions{})
if err != nil {
return err
}
newAddon := addon.DeepCopy()
newAddon.Spec.Configs = []addonapiv1alpha1.AddOnConfig{
{
ConfigGroupResource: addonapiv1alpha1.ConfigGroupResource{
Group: "addon.open-cluster-management.io",
Resource: "addondeploymentconfigs",
},
ConfigReferent: addonapiv1alpha1.ConfigReferent{
Namespace: universalClusterName,
Name: proxyDeploymentConfigName,
},
},
}
_, err = hub.AddonClient.AddonV1alpha1().ManagedClusterAddOns(universalClusterName).Update(
context.Background(), newAddon, metav1.UpdateOptions{})
if err != nil {
return err
}
return nil
}).ShouldNot(gomega.HaveOccurred())
ginkgo.By("Make sure addon is configured")
gomega.Eventually(func() error {
agentDeploy, err := spoke.KubeClient.AppsV1().Deployments(addonInstallNamespace).Get(
context.Background(), "hello-template-agent", metav1.GetOptions{})
if err != nil {
return err
}
for _, container := range agentDeploy.Spec.Template.Spec.Containers {
found := 0
for _, env := range container.Env {
if env.Name == "HTTP_PROXY" || env.Name == "http_proxy" {
if env.Value != proxyConfig.HTTPProxy {
return fmt.Errorf("unexpected HTTP_PROXY %s", env.Value)
}
found++
}
if env.Name == "HTTPS_PROXY" || env.Name == "https_proxy" {
if env.Value != proxyConfig.HTTPSProxy {
return fmt.Errorf("unexpected HTTPS_PROXY %s", env.Value)
}
found++
}
if env.Name == "NO_PROXY" || env.Name == "no_proxy" {
if env.Value != proxyConfig.NoProxy {
return fmt.Errorf("unexpected NO_PROXY %s", env.Value)
}
found++
}
if env.Name == "CA_BUNDLE_FILE_PATH" {
if env.Value != "/managed/proxy-ca/ca-bundle.crt" {
return fmt.Errorf("unexpected CA_BUNDLE_FILE_PATH %s", env.Value)
}
found++
}
}
if found != 7 {
return fmt.Errorf("unexpected env %v", container.Env)
}
}
return nil
}).ShouldNot(gomega.HaveOccurred())
cm, err := spoke.KubeClient.CoreV1().ConfigMaps(addonInstallNamespace).Get(context.TODO(),
fmt.Sprintf("%s-proxy-ca", addOnName), metav1.GetOptions{})
gomega.Expect(err).ToNot(gomega.HaveOccurred())
gomega.Expect(cm.Data).To(gomega.HaveKey("ca-bundle.crt"))
gomega.Expect(cm.Data["ca-bundle.crt"]).To(gomega.Equal(string(proxyConfig.CABundle)))
})
})
func prepareInstallNamespace(namespace, installNamespace string) error {
_, err := hub.AddonClient.AddonV1alpha1().AddOnDeploymentConfigs(namespace).Get(
context.Background(), namespaceOverrideConfigName, metav1.GetOptions{})
if errors.IsNotFound(err) {
if _, err := hub.AddonClient.AddonV1alpha1().AddOnDeploymentConfigs(namespace).Create(
context.Background(),
&addonapiv1alpha1.AddOnDeploymentConfig{
ObjectMeta: metav1.ObjectMeta{
Name: namespaceOverrideConfigName,
Namespace: namespace,
},
Spec: addonapiv1alpha1.AddOnDeploymentConfigSpec{
AgentInstallNamespace: installNamespace,
},
},
metav1.CreateOptions{},
); err != nil {
return err
}
return nil
}
return err
}
func prepareImageOverrideAddOnDeploymentConfig(namespace, installNamespace string) error {
_, err := hub.AddonClient.AddonV1alpha1().AddOnDeploymentConfigs(namespace).Get(
context.Background(), imageOverrideDeploymentConfigName, metav1.GetOptions{})
if errors.IsNotFound(err) {
if _, err := hub.AddonClient.AddonV1alpha1().AddOnDeploymentConfigs(namespace).Create(
context.Background(),
&addonapiv1alpha1.AddOnDeploymentConfig{
ObjectMeta: metav1.ObjectMeta{
Name: imageOverrideDeploymentConfigName,
Namespace: namespace,
},
Spec: addonapiv1alpha1.AddOnDeploymentConfigSpec{
Registries: registries,
AgentInstallNamespace: installNamespace,
},
},
metav1.CreateOptions{},
); err != nil {
return err
}
return nil
}
return err
}
func prepareNodePlacementAddOnDeploymentConfig(namespace, installNamespace string) error {
_, err := hub.AddonClient.AddonV1alpha1().AddOnDeploymentConfigs(namespace).Get(
context.Background(), nodePlacementDeploymentConfigName, metav1.GetOptions{})
if errors.IsNotFound(err) {
if _, err := hub.AddonClient.AddonV1alpha1().AddOnDeploymentConfigs(namespace).Create(
context.Background(),
&addonapiv1alpha1.AddOnDeploymentConfig{
ObjectMeta: metav1.ObjectMeta{
Name: nodePlacementDeploymentConfigName,
Namespace: namespace,
},
Spec: addonapiv1alpha1.AddOnDeploymentConfigSpec{
NodePlacement: &addonapiv1alpha1.NodePlacement{
NodeSelector: nodeSelector,
Tolerations: tolerations,
},
AgentInstallNamespace: installNamespace,
},
},
metav1.CreateOptions{},
); err != nil {
return err
}
return nil
}
return err
}
func prepareProxyAddOnDeploymentConfig(namespace, installNamespace string) error {
_, err := hub.AddonClient.AddonV1alpha1().AddOnDeploymentConfigs(namespace).Get(
context.Background(), proxyDeploymentConfigName, metav1.GetOptions{})
if errors.IsNotFound(err) {
if _, err := hub.AddonClient.AddonV1alpha1().AddOnDeploymentConfigs(namespace).Create(
context.Background(),
&addonapiv1alpha1.AddOnDeploymentConfig{
ObjectMeta: metav1.ObjectMeta{
Name: proxyDeploymentConfigName,
Namespace: namespace,
},
Spec: addonapiv1alpha1.AddOnDeploymentConfigSpec{
NodePlacement: &addonapiv1alpha1.NodePlacement{
NodeSelector: nodeSelector,
Tolerations: tolerations,
},
AgentInstallNamespace: installNamespace,
ProxyConfig: proxyConfig,
},
},
metav1.CreateOptions{},
); err != nil {
return err
}
return nil
}
return err
}
func copySignerSecret(ctx context.Context, kubeClient kubernetes.Interface, srcNs, srcName, dstNs, dstName string) error {
src, err := kubeClient.CoreV1().Secrets(srcNs).Get(context.Background(), srcName, metav1.GetOptions{})
if err != nil {
return err
}
dst := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: dstName,
Namespace: dstNs,
},
Data: src.Data,
StringData: src.StringData,
Type: src.Type,
}
_, err = kubeClient.CoreV1().Secrets(dstNs).Create(ctx, dst, metav1.CreateOptions{})
if err != nil && !errors.IsAlreadyExists(err) {
return err
}
return nil
}
func createResourcesFromYamlFiles(
ctx context.Context,
dynamicClient dynamic.Interface,
restMapper meta.RESTMapper,
scheme *runtime.Scheme,
manifests func(name string) ([]byte, error),
resourceFiles []string) error {
var appliedErrs []error
decoder := serializer.NewCodecFactory(scheme).UniversalDeserializer()
for _, fileName := range resourceFiles {
objData, err := manifests(fileName)
if err != nil {
return err
}
required := unstructured.Unstructured{}
_, gvk, err := decoder.Decode(objData, nil, &required)
if err != nil {
return err
}
mapping, err := restMapper.RESTMapping(gvk.GroupKind(), gvk.Version)
if err != nil {
return err
}
_, err = dynamicClient.Resource(mapping.Resource).Namespace(required.GetNamespace()).Create(
ctx, &required, metav1.CreateOptions{})
if errors.IsAlreadyExists(err) {
continue
}
if err != nil {
fmt.Printf("Error creating %q (%T): %v\n", fileName, mapping.Resource, err)
appliedErrs = append(appliedErrs, fmt.Errorf("%q (%T): %v", fileName, mapping.Resource, err))
}
}
return utilerrors.NewAggregate(appliedErrs)
}
func deleteResourcesFromYamlFiles(
ctx context.Context,
dynamicClient dynamic.Interface,
restMapper meta.RESTMapper,
scheme *runtime.Scheme,
manifests func(name string) ([]byte, error),
resourceFiles []string) error {
var appliedErrs []error
decoder := serializer.NewCodecFactory(scheme).UniversalDeserializer()
for _, fileName := range resourceFiles {
objData, err := manifests(fileName)
if err != nil {
return err
}
required := unstructured.Unstructured{}
_, gvk, err := decoder.Decode(objData, nil, &required)
if err != nil {
return err
}
mapping, err := restMapper.RESTMapping(gvk.GroupKind(), gvk.Version)
if err != nil {
return err
}
err = dynamicClient.Resource(mapping.Resource).Namespace(required.GetNamespace()).Delete(
ctx, required.GetName(), metav1.DeleteOptions{})
if errors.IsNotFound(err) {
continue
}
if err != nil {
fmt.Printf("Error deleting %q (%T): %v\n", fileName, mapping.Resource, err)
appliedErrs = append(appliedErrs, fmt.Errorf("%q (%T): %v", fileName, mapping.Resource, err))
}
}
return utilerrors.NewAggregate(appliedErrs)
}
// defaultAddonTemplateReaderManifestsFunc returns a function that reads the addon template from the embed.FS,
// and replaces the placeholder in format of "<< placeholder >>" with the value in configValues.
func defaultAddonTemplateReaderManifestsFunc(
fs embed.FS,
configValues map[string]interface{},
) func(string) ([]byte, error) {
return func(fileName string) ([]byte, error) {
template, err := fs.ReadFile(fileName)
if err != nil {
return nil, err
}
t := fasttemplate.New(string(template), "<< ", " >>")
objData := t.ExecuteString(configValues)
return []byte(objData), nil
}
}