tooling/mcerepkg/internal/customize/customize.go (135 lines of code) (raw):
// Copyright 2025 Microsoft Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package customize
import (
"fmt"
"strings"
appsv1 "k8s.io/api/apps/v1"
rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
var (
operandImageEnvVarPrefix = "OPERAND_IMAGE_"
imageRegistryParamName = "imageRegistry"
)
type Customizer func(unstructured.Unstructured) (unstructured.Unstructured, map[string]string, error)
var customizerFuncs = []Customizer{
parameterizeNamespace,
parameterizeRoleBindingSubjectsNamespace,
parameterizeClusterRoleBindingSubjectsNamespace,
parameterizeOperandsImageRegistries,
parameterizeDeployment,
annotationCleaner,
}
func CustomizeManifests(objects []unstructured.Unstructured) ([]unstructured.Unstructured, map[string]interface{}, error) {
parameters := make(map[string]string)
customizedManifests := make([]unstructured.Unstructured, len(objects))
for i, obj := range objects {
var err error
var newParams map[string]string
for _, customerFunc := range customizerFuncs {
obj, newParams, err = customerFunc(obj)
if err != nil {
return nil, nil, fmt.Errorf("failed to apply customer function: %v", err)
}
for k, v := range newParams {
parameters[k] = v
}
}
customizedManifests[i] = obj
}
return customizedManifests, makeNestedMap(parameters), nil
}
func parameterizeNamespace(obj unstructured.Unstructured) (unstructured.Unstructured, map[string]string, error) {
// check if the resource is a namespaced resource
if obj.GetNamespace() != "" {
obj.SetNamespace("{{ .Release.Namespace }}")
}
return obj, nil, nil
}
func parameterizeRoleBindingSubjectsNamespace(obj unstructured.Unstructured) (unstructured.Unstructured, map[string]string, error) {
if isRoleBinding(obj) {
roleBinding := &rbacv1.RoleBinding{}
err := convertFromUnstructured(obj, roleBinding)
if err != nil {
return unstructured.Unstructured{}, map[string]string{}, fmt.Errorf("failed to convert unstructured object to RoleBinding: %v", err)
}
for s, subject := range roleBinding.Subjects {
if subject.Kind == "ServiceAccount" {
roleBinding.Subjects[s].Namespace = "{{ .Release.Namespace }}"
}
}
modifiedObj, err := convertToUnstructured(roleBinding)
return modifiedObj, nil, err
}
return obj, nil, nil
}
func parameterizeClusterRoleBindingSubjectsNamespace(obj unstructured.Unstructured) (unstructured.Unstructured, map[string]string, error) {
if isClusterRoleBinding(obj) {
clusterRoleBinding := &rbacv1.ClusterRoleBinding{}
err := convertFromUnstructured(obj, clusterRoleBinding)
if err != nil {
return unstructured.Unstructured{}, nil, fmt.Errorf("failed to convert unstructured object to ClusterRoleBinding: %v", err)
}
for s, subject := range clusterRoleBinding.Subjects {
if subject.Kind == "ServiceAccount" {
clusterRoleBinding.Subjects[s].Namespace = "{{ .Release.Namespace }}"
}
}
modifiedObj, err := convertToUnstructured(clusterRoleBinding)
return modifiedObj, nil, err
}
return obj, nil, nil
}
func parameterizeOperandsImageRegistries(obj unstructured.Unstructured) (unstructured.Unstructured, map[string]string, error) {
if isOperatorDeployment(obj) {
deployment := &appsv1.Deployment{}
err := convertFromUnstructured(obj, deployment)
if err != nil {
return unstructured.Unstructured{}, nil, fmt.Errorf("failed to convert unstructured object to Deployment: %v", err)
}
for c, container := range deployment.Spec.Template.Spec.Containers {
for e, env := range container.Env {
if isOperandImageEnvVar(env.Name) {
deployment.Spec.Template.Spec.Containers[c].Env[e].Value = parameterizeImageRegistry(env.Value, imageRegistryParamName)
}
}
}
modifiedObj, err := convertToUnstructured(deployment)
return modifiedObj, map[string]string{imageRegistryParamName: ""}, err
}
return obj, nil, nil
}
func isOperandImageEnvVar(envVarName string) bool {
return strings.HasPrefix(envVarName, operandImageEnvVarPrefix)
}
func parameterizeDeployment(obj unstructured.Unstructured) (unstructured.Unstructured, map[string]string, error) {
if isDeployment(obj) {
deployment := &appsv1.Deployment{}
err := convertFromUnstructured(obj, deployment)
if err != nil {
return unstructured.Unstructured{}, nil, fmt.Errorf("failed to convert unstructured object to Deployment: %v", err)
}
// image registry
for c, container := range deployment.Spec.Template.Spec.Containers {
deployment.Spec.Template.Spec.Containers[c].Image = parameterizeImageRegistry(container.Image, imageRegistryParamName)
}
modifiedObj, err := convertToUnstructured(deployment)
return modifiedObj, map[string]string{imageRegistryParamName: ""}, err
}
return obj, nil, nil
}
func annotationCleaner(obj unstructured.Unstructured) (unstructured.Unstructured, map[string]string, error) {
annotationToScrape := []string{"openshift.io", "operatorframework.io", "olm", "alm-examples", "createdAt"}
annotations := obj.GetAnnotations()
for k := range annotations {
for _, prefix := range annotationToScrape {
if strings.Contains(k, prefix) {
delete(annotations, k)
break
}
}
}
if len(annotations) == 0 {
obj.SetAnnotations(nil)
} else {
obj.SetAnnotations(annotations)
}
return obj, nil, nil
}