Skip to content

Commit

Permalink
Improve Templates deletion validation
Browse files Browse the repository at this point in the history
The following rules are validated:
1. ClusterTemplate or ServiceTemplate can't be removed if it is in use by
   ManagedCluster
2. ClusterTemplate or ServiceTemplate can't be removed if the request
   was triggered by user and the template is managed by the
   TemplateManagement
3. ProviderTemplates can't be removed if it's a part of an existing HMC Release
  • Loading branch information
eromanova committed Oct 22, 2024
1 parent 351dc53 commit 83d4900
Show file tree
Hide file tree
Showing 32 changed files with 581 additions and 65 deletions.
38 changes: 37 additions & 1 deletion api/v1alpha1/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,14 @@ func SetupIndexers(ctx context.Context, mgr ctrl.Manager) error {
return err
}

return SetupManagedClusterServicesIndexer(ctx, mgr)
if err := SetupManagedClusterServicesIndexer(ctx, mgr); err != nil {
return err
}

if err := SetupClusterTemplateChainIndexer(ctx, mgr); err != nil {
return err
}
return SetupServiceTemplateChainIndexer(ctx, mgr)
}

const TemplateKey = ".spec.template"
Expand Down Expand Up @@ -145,6 +152,35 @@ func ExtractServiceTemplateName(rawObj client.Object) []string {
return templates
}

const SupportedTemplateKey = ".spec.supportedTemplates[].Name"

func SetupClusterTemplateChainIndexer(ctx context.Context, mgr ctrl.Manager) error {
return mgr.GetFieldIndexer().IndexField(ctx, &ClusterTemplateChain{}, SupportedTemplateKey, ExtractSupportedTemplatesNames)
}

func SetupServiceTemplateChainIndexer(ctx context.Context, mgr ctrl.Manager) error {
return mgr.GetFieldIndexer().IndexField(ctx, &ServiceTemplateChain{}, SupportedTemplateKey, ExtractSupportedTemplatesNames)
}

func ExtractSupportedTemplatesNames(rawObj client.Object) []string {
chainSpec := TemplateChainSpec{}
switch chain := rawObj.(type) {
case *ClusterTemplateChain:
chainSpec = chain.Spec
case *ServiceTemplateChain:
chainSpec = chain.Spec
default:
return nil
}

supportedTemplates := make([]string, 0, len(chainSpec.SupportedTemplates))
for _, t := range chainSpec.SupportedTemplates {
supportedTemplates = append(supportedTemplates, t.Name)
}

return supportedTemplates
}

func (c ProvidersTupled) BootstrapProvidersNames() []string {
return c.names(bootstrapProvidersType)
}
Expand Down
3 changes: 3 additions & 0 deletions api/v1alpha1/providertemplate_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ import (
)

const (
// Denotes the providertemplate resource Kind.
ProviderTemplateKind = "ProviderTemplate"

// ChartAnnotationCAPIVersion is an annotation containing the CAPI exact version in the SemVer format associated with a ProviderTemplate.
ChartAnnotationCAPIVersion = "hmc.mirantis.com/capi-version"
// ChartAnnotationCAPIVersionConstraint is an annotation containing the CAPI version constraint in the SemVer format associated with a ProviderTemplate.
Expand Down
3 changes: 3 additions & 0 deletions api/v1alpha1/templates_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ const (
ChartAnnotationBootstrapProviders = "hmc.mirantis.com/bootstrap-providers"
// ChartAnnotationControlPlaneProviders is an annotation containing the CAPI control plane providers associated with Template.
ChartAnnotationControlPlaneProviders = "hmc.mirantis.com/control-plane-providers"

// HMCReleaseLabelKey is the label key used to identify the release name
HMCReleaseLabelKey = "hmc.mirantis.com/release"
)

// +kubebuilder:validation:XValidation:rule="(has(self.chartName) && !has(self.chartRef)) || (!has(self.chartName) && has(self.chartRef))", message="either chartName or chartRef must be set"
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ require (
k8s.io/api v0.31.1
k8s.io/apiextensions-apiserver v0.31.1
k8s.io/apimachinery v0.31.1
k8s.io/apiserver v0.31.1
k8s.io/client-go v0.31.1
k8s.io/utils v0.0.0-20240921022957-49e7df575cb6
sigs.k8s.io/cluster-api v1.8.4
Expand Down Expand Up @@ -175,7 +176,6 @@ require (
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
k8s.io/apiserver v0.31.1 // indirect
k8s.io/cli-runtime v0.31.1 // indirect
k8s.io/component-base v0.31.1 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
Expand Down
8 changes: 7 additions & 1 deletion hack/templates.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,16 @@ set -eu
TEMPLATES_DIR=${TEMPLATES_DIR:-templates}
# Output directory for the generated Template manifests
TEMPLATES_OUTPUT_DIR=${TEMPLATES_OUTPUT_DIR:-templates/provider/hmc-templates/files/templates}
# The file name of the Release object
RELEASE_OUTPUT_FILE=${RELEASE_OUTPUT_FILE:-templates/provider/hmc-templates/files/release.yaml}
# The name of the HMC templates helm chart
HMC_TEMPLATES_CHART_NAME='hmc-templates'

mkdir -p $TEMPLATES_OUTPUT_DIR
rm -f $TEMPLATES_OUTPUT_DIR/*.yaml

release_name=$(awk '/metadata:/ {f=1} f && /name:/ {gsub(" ", "", $1); print $2; exit}' "$RELEASE_OUTPUT_FILE")

for type in "$TEMPLATES_DIR"/*; do
kind="$(echo "${type#*/}Template" | awk '{$1=toupper(substr($1,0,1))substr($1,2)}1')"
for chart in "$type"/*; do
Expand All @@ -43,6 +47,8 @@ EOF
cat <<EOF >> $TEMPLATES_OUTPUT_DIR/$file_name.yaml
metadata:
name: $template_name
labels:
hmc.mirantis.com/release: $release_name
annotations:
helm.sh/resource-policy: keep
spec:
Expand All @@ -51,7 +57,7 @@ spec:
chartVersion: $version
EOF

echo "Generated $TEMPLATES_OUTPUT_DIR/$name.yaml"
echo "Generated $TEMPLATES_OUTPUT_DIR/$file_name.yaml"
fi
done
done
29 changes: 25 additions & 4 deletions internal/controller/suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,12 @@ import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
admissionv1 "k8s.io/api/admissionregistration/v1"
authenticationv1 "k8s.io/api/authentication/v1"
utilyaml "sigs.k8s.io/cluster-api/util/yaml"
ctrl "sigs.k8s.io/controller-runtime"
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
"sigs.k8s.io/controller-runtime/pkg/webhook"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"

helmcontrollerv2 "github.com/fluxcd/helm-controller/api/v2"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
Expand All @@ -43,9 +45,11 @@ import (
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/log/zap"

sveltosv1beta1 "github.com/projectsveltos/addon-controller/api/v1beta1"

hmcmirantiscomv1alpha1 "github.com/Mirantis/hmc/api/v1alpha1"
"github.com/Mirantis/hmc/internal/utils"
hmcwebhook "github.com/Mirantis/hmc/internal/webhook"
sveltosv1beta1 "github.com/projectsveltos/addon-controller/api/v1beta1"
// +kubebuilder:scaffold:imports
)

Expand All @@ -55,6 +59,8 @@ import (
const (
mutatingWebhookKind = "MutatingWebhookConfiguration"
validatingWebhookKind = "ValidatingWebhookConfiguration"

hmcServiceAccountName = "hmc-controller-manager"
)

var (
Expand All @@ -63,6 +69,8 @@ var (
testEnv *envtest.Environment
ctx context.Context
cancel context.CancelFunc

userInfo = authenticationv1.UserInfo{Username: fmt.Sprintf("system:serviceaccount:%s:%s", utils.DefaultSystemNamespace, hmcServiceAccountName)}
)

func TestControllers(t *testing.T) {
Expand All @@ -83,6 +91,9 @@ var _ = BeforeSuite(func() {
)
Expect(err).NotTo(HaveOccurred())

err = os.Setenv(hmcwebhook.ServiceAccountEnvName, hmcServiceAccountName)
Expect(err).To(Succeed())

testEnv = &envtest.Environment{
CRDDirectoryPaths: []string{
filepath.Join("..", "..", "templates", "provider", "hmc", "templates", "crds"),
Expand Down Expand Up @@ -156,13 +167,20 @@ var _ = BeforeSuite(func() {
err = (&hmcwebhook.ServiceTemplateChainValidator{}).SetupWebhookWithManager(mgr)
Expect(err).NotTo(HaveOccurred())

err = (&hmcwebhook.ClusterTemplateValidator{}).SetupWebhookWithManager(mgr)
templateValidator := hmcwebhook.TemplateValidator{
SystemNamespace: utils.DefaultSystemNamespace,
InjectUserInfo: func(req *admission.Request) {
req.UserInfo = userInfo
},
}

err = (&hmcwebhook.ClusterTemplateValidator{TemplateValidator: templateValidator}).SetupWebhookWithManager(mgr)
Expect(err).NotTo(HaveOccurred())

err = (&hmcwebhook.ServiceTemplateValidator{}).SetupWebhookWithManager(mgr)
err = (&hmcwebhook.ServiceTemplateValidator{TemplateValidator: templateValidator}).SetupWebhookWithManager(mgr)
Expect(err).NotTo(HaveOccurred())

err = (&hmcwebhook.ProviderTemplateValidator{}).SetupWebhookWithManager(mgr)
err = (&hmcwebhook.ProviderTemplateValidator{TemplateValidator: templateValidator}).SetupWebhookWithManager(mgr)
Expect(err).NotTo(HaveOccurred())

go func() {
Expand All @@ -188,6 +206,9 @@ var _ = AfterSuite(func() {
cancel()
err := testEnv.Stop()
Expect(err).NotTo(HaveOccurred())

err = os.Unsetenv(hmcwebhook.ServiceAccountEnvName)
Expect(err).To(Succeed())
})

func loadWebhooks(path string) ([]*admissionv1.ValidatingWebhookConfiguration, []*admissionv1.MutatingWebhookConfiguration, error) {
Expand Down
2 changes: 1 addition & 1 deletion internal/controller/template_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ var _ = Describe("Template Controller", func() {
err = k8sClient.Get(ctx, typeNamespacedName, providerTemplateResource)
Expect(err).NotTo(HaveOccurred())

By("Cleanup the specific resource instance ClusterTemplate")
By("Cleanup the specific resource instance ProviderTemplate")
Expect(k8sClient.Delete(ctx, providerTemplateResource)).To(Succeed())
})

Expand Down
26 changes: 13 additions & 13 deletions internal/controller/templatechain_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,19 +213,6 @@ var _ = Describe("Template Chain Controller", func() {
})

AfterEach(func() {
for _, template := range []*hmcmirantiscomv1alpha1.ClusterTemplate{
ctTemplates["test"], ctTemplates["ct0"], ctTemplates["ct1"], ctTemplates["ct2"],
} {
err := k8sClient.Delete(ctx, template)
Expect(crclient.IgnoreNotFound(err)).To(Succeed())
}
for _, template := range []*hmcmirantiscomv1alpha1.ServiceTemplate{
stTemplates["test"], stTemplates["st0"], stTemplates["st1"], stTemplates["st2"],
} {
err := k8sClient.Delete(ctx, template)
Expect(crclient.IgnoreNotFound(err)).To(Succeed())
}

for _, chain := range ctChainNames {
clusterTemplateChainResource := &hmcmirantiscomv1alpha1.ClusterTemplateChain{}
err := k8sClient.Get(ctx, chain, clusterTemplateChainResource)
Expand All @@ -243,6 +230,19 @@ var _ = Describe("Template Chain Controller", func() {
Expect(k8sClient.Delete(ctx, serviceTemplateChainResource)).To(Succeed())
}

for _, template := range []*hmcmirantiscomv1alpha1.ClusterTemplate{
ctTemplates["test"], ctTemplates["ct0"], ctTemplates["ct1"], ctTemplates["ct2"],
} {
err := k8sClient.Delete(ctx, template)
Expect(crclient.IgnoreNotFound(err)).To(Succeed())
}
for _, template := range []*hmcmirantiscomv1alpha1.ServiceTemplate{
stTemplates["test"], stTemplates["st0"], stTemplates["st1"], stTemplates["st2"],
} {
err := k8sClient.Delete(ctx, template)
Expect(crclient.IgnoreNotFound(err)).To(Succeed())
}

By("Cleanup the namespace")
err := k8sClient.Get(ctx, types.NamespacedName{Name: namespace.Name}, namespace)
Expect(err).NotTo(HaveOccurred())
Expand Down
Loading

0 comments on commit 83d4900

Please sign in to comment.