Files
argo-cd/hack/gen-crd-spec/main.go
2026-02-12 09:29:40 -05:00

158 lines
4.2 KiB
Go

package main
import (
"context"
"encoding/json"
"errors"
"fmt"
"os"
"os/exec"
"strings"
"github.com/argoproj/argo-cd/v3/pkg/apis/application"
"github.com/argoproj/argo-cd/gitops-engine/pkg/utils/kube"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"sigs.k8s.io/yaml"
)
var kindToCRDPath = map[string]string{
application.ApplicationFullName: "manifests/crds/application-crd.yaml",
application.AppProjectFullName: "manifests/crds/appproject-crd.yaml",
application.ApplicationSetFullName: "manifests/crds/applicationset-crd.yaml",
}
func getCustomResourceDefinitions(ctx context.Context) map[string]*apiextensionsv1.CustomResourceDefinition {
crdYamlBytes, err := exec.CommandContext(ctx,
"controller-gen",
"paths=./pkg/apis/application/...",
"crd:crdVersions=v1",
"output:crd:stdout",
).Output()
checkErr(err)
// clean up stuff left by controller-gen
deleteFile("config/webhook/manifests.yaml")
deleteFile("config/webhook")
deleteFile("config/argoproj.io_applications.yaml")
deleteFile("config/argoproj.io_appprojects.yaml")
deleteFile("config/argoproj.io_applicationsets.yaml")
deleteFile("config")
objs, err := kube.SplitYAML(crdYamlBytes)
checkErr(err)
crds := make(map[string]*apiextensionsv1.CustomResourceDefinition)
for i := range objs {
un := objs[i]
// We need to completely remove validation of problematic fields such as creationTimestamp,
// which get marshalled to `null`, but are typed as as a `string` during Open API validation
removeValidation(un, "metadata.creationTimestamp")
// remove status validation for AppProject CRD as workaround for https://github.com/argoproj/argo-cd/issues/4158
if un.GetName() == "appprojects.argoproj.io" {
removeValidation(un, "status")
}
crd := toCRD(un, un.GetName() == "applicationsets.argoproj.io")
crd.Labels = map[string]string{
"app.kubernetes.io/name": crd.Name,
"app.kubernetes.io/part-of": "argocd",
}
delete(crd.Annotations, "controller-gen.kubebuilder.io/version")
crd.Spec.Scope = "Namespaced"
crds[crd.Name] = crd
}
return crds
}
func deleteFile(path string) {
if _, err := os.Stat(path); os.IsNotExist(err) {
return
}
checkErr(os.Remove(path))
}
func removeValidation(un *unstructured.Unstructured, path string) {
schemaPath := []string{"spec", "versions[*]", "schema", "openAPIV3Schema"}
for part := range strings.SplitSeq(path, ".") {
schemaPath = append(schemaPath, "properties", part)
}
unstructured.RemoveNestedField(un.Object, schemaPath...)
}
func toCRD(un *unstructured.Unstructured, removeDesc bool) *apiextensionsv1.CustomResourceDefinition {
if removeDesc {
removeDescription(un.Object)
}
unBytes, err := json.Marshal(un)
checkErr(err)
var crd apiextensionsv1.CustomResourceDefinition
err = json.Unmarshal(unBytes, &crd)
checkErr(err)
return &crd
}
func removeDescription(v any) {
switch v := v.(type) {
case []any:
for _, v := range v {
removeDescription(v)
}
case map[string]any:
if _, ok := v["description"]; ok {
_, ok := v["description"].(string)
if ok {
delete(v, "description")
}
}
for _, v := range v {
removeDescription(v)
}
}
}
func checkErr(err error) {
if err != nil {
var execError *exec.ExitError
if errors.As(err, &execError) {
fmt.Println(string(execError.Stderr))
}
panic(err)
}
}
func main() {
crdsapp := getCustomResourceDefinitions(context.Background())
for kind, path := range kindToCRDPath {
crd := crdsapp[kind]
if crd == nil {
panic(fmt.Sprintf("CRD of kind %s was not generated", kind))
}
writeCRDintoFile(crd, path)
}
}
func writeCRDintoFile(crd *apiextensionsv1.CustomResourceDefinition, path string) {
jsonBytes, err := json.Marshal(crd)
checkErr(err)
var r unstructured.Unstructured
err = json.Unmarshal(jsonBytes, &r.Object)
checkErr(err)
// clean up crd yaml before marshalling
unstructured.RemoveNestedField(r.Object, "status")
unstructured.RemoveNestedField(r.Object, "metadata", "creationTimestamp")
jsonBytes, err = json.MarshalIndent(r.Object, "", " ")
checkErr(err)
yamlBytes, err := yaml.JSONToYAML(jsonBytes)
checkErr(err)
err = os.WriteFile(path, yamlBytes, 0o644)
checkErr(err)
}