github.com/argoproj/argo-cd/v3@v3.2.1/hack/gen-crd-spec/main.go (about)

     1  package main
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"os"
     8  	"os/exec"
     9  	"strings"
    10  
    11  	"github.com/argoproj/argo-cd/v3/pkg/apis/application"
    12  
    13  	"github.com/argoproj/gitops-engine/pkg/utils/kube"
    14  	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
    15  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    16  	"sigs.k8s.io/yaml"
    17  )
    18  
    19  var kindToCRDPath = map[string]string{
    20  	application.ApplicationFullName:    "manifests/crds/application-crd.yaml",
    21  	application.AppProjectFullName:     "manifests/crds/appproject-crd.yaml",
    22  	application.ApplicationSetFullName: "manifests/crds/applicationset-crd.yaml",
    23  }
    24  
    25  func getCustomResourceDefinitions() map[string]*apiextensionsv1.CustomResourceDefinition {
    26  	crdYamlBytes, err := exec.Command(
    27  		"controller-gen",
    28  		"paths=./pkg/apis/application/...",
    29  		"crd:crdVersions=v1",
    30  		"output:crd:stdout",
    31  	).Output()
    32  	checkErr(err)
    33  
    34  	// clean up stuff left by controller-gen
    35  	deleteFile("config/webhook/manifests.yaml")
    36  	deleteFile("config/webhook")
    37  	deleteFile("config/argoproj.io_applications.yaml")
    38  	deleteFile("config/argoproj.io_appprojects.yaml")
    39  	deleteFile("config/argoproj.io_applicationsets.yaml")
    40  	deleteFile("config")
    41  
    42  	objs, err := kube.SplitYAML(crdYamlBytes)
    43  	checkErr(err)
    44  	crds := make(map[string]*apiextensionsv1.CustomResourceDefinition)
    45  	for i := range objs {
    46  		un := objs[i]
    47  
    48  		// We need to completely remove validation of problematic fields such as creationTimestamp,
    49  		// which get marshalled to `null`, but are typed as as a `string` during Open API validation
    50  		removeValidation(un, "metadata.creationTimestamp")
    51  		// remove status validation for AppProject CRD as workaround for https://github.com/argoproj/argo-cd/issues/4158
    52  		if un.GetName() == "appprojects.argoproj.io" {
    53  			removeValidation(un, "status")
    54  		}
    55  
    56  		crd := toCRD(un, un.GetName() == "applicationsets.argoproj.io")
    57  		crd.Labels = map[string]string{
    58  			"app.kubernetes.io/name":    crd.Name,
    59  			"app.kubernetes.io/part-of": "argocd",
    60  		}
    61  		delete(crd.Annotations, "controller-gen.kubebuilder.io/version")
    62  		crd.Spec.Scope = "Namespaced"
    63  		crds[crd.Name] = crd
    64  	}
    65  	return crds
    66  }
    67  
    68  func deleteFile(path string) {
    69  	if _, err := os.Stat(path); os.IsNotExist(err) {
    70  		return
    71  	}
    72  	checkErr(os.Remove(path))
    73  }
    74  
    75  func removeValidation(un *unstructured.Unstructured, path string) {
    76  	schemaPath := []string{"spec", "versions[*]", "schema", "openAPIV3Schema"}
    77  	for _, part := range strings.Split(path, ".") {
    78  		schemaPath = append(schemaPath, "properties", part)
    79  	}
    80  	unstructured.RemoveNestedField(un.Object, schemaPath...)
    81  }
    82  
    83  func toCRD(un *unstructured.Unstructured, removeDesc bool) *apiextensionsv1.CustomResourceDefinition {
    84  	if removeDesc {
    85  		removeDescription(un.Object)
    86  	}
    87  	unBytes, err := json.Marshal(un)
    88  	checkErr(err)
    89  
    90  	var crd apiextensionsv1.CustomResourceDefinition
    91  	err = json.Unmarshal(unBytes, &crd)
    92  	checkErr(err)
    93  
    94  	return &crd
    95  }
    96  
    97  func removeDescription(v any) {
    98  	switch v := v.(type) {
    99  	case []any:
   100  		for _, v := range v {
   101  			removeDescription(v)
   102  		}
   103  	case map[string]any:
   104  		if _, ok := v["description"]; ok {
   105  			_, ok := v["description"].(string)
   106  			if ok {
   107  				delete(v, "description")
   108  			}
   109  		}
   110  		for _, v := range v {
   111  			removeDescription(v)
   112  		}
   113  	}
   114  }
   115  
   116  func checkErr(err error) {
   117  	if err != nil {
   118  		var execError *exec.ExitError
   119  		if errors.As(err, &execError) {
   120  			fmt.Println(string(execError.Stderr))
   121  		}
   122  		panic(err)
   123  	}
   124  }
   125  
   126  func main() {
   127  	crdsapp := getCustomResourceDefinitions()
   128  	for kind, path := range kindToCRDPath {
   129  		crd := crdsapp[kind]
   130  		if crd == nil {
   131  			panic(fmt.Sprintf("CRD of kind %s was not generated", kind))
   132  		}
   133  		writeCRDintoFile(crd, path)
   134  	}
   135  }
   136  
   137  func writeCRDintoFile(crd *apiextensionsv1.CustomResourceDefinition, path string) {
   138  	jsonBytes, err := json.Marshal(crd)
   139  	checkErr(err)
   140  
   141  	var r unstructured.Unstructured
   142  	err = json.Unmarshal(jsonBytes, &r.Object)
   143  	checkErr(err)
   144  
   145  	// clean up crd yaml before marshalling
   146  	unstructured.RemoveNestedField(r.Object, "status")
   147  	unstructured.RemoveNestedField(r.Object, "metadata", "creationTimestamp")
   148  	jsonBytes, err = json.MarshalIndent(r.Object, "", "    ")
   149  	checkErr(err)
   150  
   151  	yamlBytes, err := yaml.JSONToYAML(jsonBytes)
   152  	checkErr(err)
   153  
   154  	err = os.WriteFile(path, yamlBytes, 0o644)
   155  	checkErr(err)
   156  }