Files
deploy/hack/gen-crd-spec/main.go
zeekayandHanzo Dev 1ab6e799c1 cd: the module is github.com/hanzoai/cd, where it is actually hosted
go.mod still declared `module github.com/hanzoai/deploy` after the repo was
renamed. A Go module path is not cosmetic — it is the resolvable location. So
`go get github.com/hanzoai/cd` could not work, and anything importing this
module had to spell a name that no longer exists at that address. The build log
of v3.6.0 is where it surfaced: the compiler was still emitting
github.com/hanzoai/deploy/cmd/cd/commands.

3541 import paths across 690 .go, 19 .proto, 4 .yaml, 3 .sh, 2 .mod, Procfile
and Makefile. Plus the 62 ghcr.io/hanzoai/deploy image references, moved to
ghcr.io/hanzoai/cd to match hanzo.yml's build target — a distinct string from
the module path, replaced separately so neither could clobber the other.

go mod tidy clean, CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build ./... clean.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-28 21:16:24 -07:00

158 lines
4.2 KiB
Go

package main
import (
"context"
"encoding/json"
"errors"
"fmt"
"os"
"os/exec"
"strings"
"github.com/hanzoai/cd/pkg/apis/application"
"github.com/hanzoai/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/apps.hanzo.ai_applications.yaml")
deleteFile("config/apps.hanzo.ai_appprojects.yaml")
deleteFile("config/apps.hanzo.ai_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.apps.hanzo.ai" {
removeValidation(un, "status")
}
crd := toCRD(un, un.GetName() == "applicationsets.apps.hanzo.ai")
crd.Labels = map[string]string{
"app.kubernetes.io/name": crd.Name,
"app.kubernetes.io/part-of": "cd",
}
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)
}