diff --git a/Makefile b/Makefile index 5c1883ce34..bc7c0da0d4 100644 --- a/Makefile +++ b/Makefile @@ -223,7 +223,7 @@ help: # Display this help .PHONY: generate generate: ## Run all generate targets - $(MAKE) generate-modules generate-manifests generate-go-deepcopy generate-go-conversions + $(MAKE) generate-modules generate-manifests generate-go-deepcopy generate-go-conversions generate-flavors .PHONY: generate-manifests generate-manifests: $(CONTROLLER_GEN) ## Generate manifests e.g. CRD, RBAC etc. @@ -339,7 +339,7 @@ APIDIFF_OLD_COMMIT ?= $(shell git rev-parse origin/main) apidiff: $(GO_APIDIFF) ## Check for API differences $(GO_APIDIFF) $(APIDIFF_OLD_COMMIT) --print-compatible -ALL_VERIFY_CHECKS = boilerplate modules gen conversions doctoc +ALL_VERIFY_CHECKS = boilerplate modules gen conversions doctoc flavors .PHONY: verify verify: $(addprefix verify-,$(ALL_VERIFY_CHECKS)) lint-markdown lint-shell ## Run all verify-* targets @@ -381,6 +381,13 @@ verify-boilerplate: ## Verify boilerplate text exists in each file verify-container-images: ## Verify container images TRACE=$(TRACE) ./hack/verify-container-images.sh +.PHONY: verify-flavors +verify-flavors: $(FLAVOR_DIR) generate-flavors ## Verify generated flavors + @if !(git diff --quiet HEAD -- $(FLAVOR_DIR)); then \ + git diff $(FLAVOR_DIR); \ + echo "flavor files in templates directory are out of date"; exit 1; \ + fi + ## -------------------------------------- ## Build ## -------------------------------------- @@ -572,12 +579,7 @@ dev-flavors: $(OVERRIDES_DIR) .PHONY: generate-flavors generate-flavors: $(FLAVOR_DIR) - go run ./packaging/flavorgen -f vip > $(FLAVOR_DIR)/cluster-template.yaml - go run ./packaging/flavorgen -f external-loadbalancer > $(FLAVOR_DIR)/cluster-template-external-loadbalancer.yaml - go run ./packaging/flavorgen -f cluster-class > $(FLAVOR_DIR)/clusterclass-template.yaml - go run ./packaging/flavorgen -f cluster-topology > $(FLAVOR_DIR)/cluster-template-topology.yaml - go run ./packaging/flavorgen -f ignition > $(FLAVOR_DIR)/cluster-template-ignition.yaml - go run ./packaging/flavorgen -f node-ipam > $(FLAVOR_DIR)/cluster-template-node-ipam.yaml + go run ./packaging/flavorgen --output-dir $(FLAVOR_DIR) .PHONY: release-staging release-staging: ## Build and push container images to the staging registry diff --git a/packaging/flavorgen/cmd/root.go b/packaging/flavorgen/cmd/root.go index a327b91627..0151e909cb 100644 --- a/packaging/flavorgen/cmd/root.go +++ b/packaging/flavorgen/cmd/root.go @@ -17,10 +17,13 @@ limitations under the License. package cmd import ( + "fmt" "os" + "path/filepath" "github.com/pkg/errors" "github.com/spf13/cobra" + "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/cluster-api-provider-vsphere/packaging/flavorgen/flavors" "sigs.k8s.io/cluster-api-provider-vsphere/packaging/flavorgen/flavors/env" @@ -28,6 +31,27 @@ import ( ) const flavorFlag = "flavor" +const outputDirFlag = "output-dir" + +var ( + flavorMappings = map[string]string{ + flavors.VIP: "cluster-template.yaml", + flavors.ExternalLoadBalancer: "cluster-template-external-loadbalancer.yaml", + flavors.ClusterClass: "clusterclass-template.yaml", + flavors.ClusterTopology: "cluster-template-topology.yaml", + flavors.Ignition: "cluster-template-ignition.yaml", + flavors.NodeIPAM: "cluster-template-node-ipam.yaml", + } + + allFlavors = []string{ + flavors.VIP, + flavors.ExternalLoadBalancer, + flavors.ClusterClass, + flavors.Ignition, + flavors.NodeIPAM, + flavors.ClusterTopology, + } +) func RootCmd() *cobra.Command { rootCmd := &cobra.Command{ @@ -36,8 +60,11 @@ func RootCmd() *cobra.Command { RunE: func(command *cobra.Command, args []string) error { return RunRoot(command) }, + SilenceUsage: true, } rootCmd.Flags().StringP(flavorFlag, "f", "", "Name of flavor to compile") + rootCmd.Flags().StringP(outputDirFlag, "o", "", "Directory to store the generated flavor templates.\nBy default the current directory is used.\nUse '-' to output the result to stdout.") + return rootCmd } @@ -52,30 +79,73 @@ func RunRoot(command *cobra.Command) error { if err != nil { return errors.Wrapf(err, "error accessing flag %s for command %s", flavorFlag, command.Name()) } + outputDir, err := command.Flags().GetString(outputDirFlag) + if err != nil { + return errors.Wrapf(err, "error accessing flag %s for command %s", outputDirFlag, command.Name()) + } + var outputFlavors []string + if flavor != "" { + outputFlavors = append(outputFlavors, flavor) + } else { + outputFlavors = allFlavors + } + generateMultiFlavors := len(outputFlavors) > 1 + for _, f := range outputFlavors { + manifest, err := generateSingle(f) + if err != nil { + return err + } + + yamlFileName, ok := flavorMappings[f] + if !ok { + return fmt.Errorf("file mapping for flavor %q is missng in flavorMappings", f) + } + + if outputDir == "-" { + if generateMultiFlavors { + // use the yaml filename as a section delimiter + fmt.Printf("### %s\n", yamlFileName) + } + fmt.Print(manifest) + continue + } + + yamlPath := filepath.Join(outputDir, yamlFileName) + err = os.WriteFile(yamlPath, []byte(manifest), 0600) + if err != nil { + return errors.Wrapf(err, "failed to save manifest content to file for flavor %s", f) + } + } + + return nil +} + +func generateSingle(flavor string) (string, error) { + replacements := append([]util.Replacement{}, util.DefaultReplacements...) + + var objs []runtime.Object switch flavor { case flavors.VIP: - util.PrintObjects(flavors.MultiNodeTemplateWithKubeVIP()) + objs = flavors.MultiNodeTemplateWithKubeVIP() case flavors.ExternalLoadBalancer: - util.PrintObjects(flavors.MultiNodeTemplateWithExternalLoadBalancer()) + objs = flavors.MultiNodeTemplateWithExternalLoadBalancer() case flavors.ClusterClass: - util.PrintObjects(flavors.ClusterClassTemplateWithKubeVIP()) + objs = flavors.ClusterClassTemplateWithKubeVIP() case flavors.ClusterTopology: - additionalReplacements := []util.Replacement{ - { - Kind: "Cluster", - Name: "${CLUSTER_NAME}", - Value: env.ControlPlaneMachineCountVar, - FieldPath: []string{"spec", "topology", "controlPlane", "replicas"}, - }, - } - util.Replacements = append(util.Replacements, additionalReplacements...) - util.PrintObjects(flavors.ClusterTopologyTemplateKubeVIP()) + objs = flavors.ClusterTopologyTemplateKubeVIP() + replacements = append(replacements, util.Replacement{ + Kind: "Cluster", + Name: "${CLUSTER_NAME}", + Value: env.ControlPlaneMachineCountVar, + FieldPath: []string{"spec", "topology", "controlPlane", "replicas"}, + }) case flavors.Ignition: - util.PrintObjects(flavors.MultiNodeTemplateWithKubeVIPIgnition()) + objs = flavors.MultiNodeTemplateWithKubeVIPIgnition() case flavors.NodeIPAM: - util.PrintObjects(flavors.MultiNodeTemplateWithKubeVIPNodeIPAM()) + objs = flavors.MultiNodeTemplateWithKubeVIPNodeIPAM() default: - return errors.Errorf("invalid flavor") + return "", errors.Errorf("invalid flavor") } - return nil + + return util.GenerateManifestYaml(objs, replacements), nil } diff --git a/packaging/flavorgen/flavors/crs/util.go b/packaging/flavorgen/flavors/crs/util.go index ed69bf9a78..4272a88ee8 100644 --- a/packaging/flavorgen/flavors/crs/util.go +++ b/packaging/flavorgen/flavors/crs/util.go @@ -78,7 +78,7 @@ func newConfigMapManifests(name string, o []runtime.Object) *v1.ConfigMap { Namespace: env.NamespaceVar, }, Data: map[string]string{ - "data": util.GenerateManifestYaml(o), + "data": util.GenerateManifestYaml(o, util.DefaultReplacements), }, } } diff --git a/packaging/flavorgen/flavors/util/helpers.go b/packaging/flavorgen/flavors/util/helpers.go index 3ed2f5d7bf..3a775044a2 100644 --- a/packaging/flavorgen/flavors/util/helpers.go +++ b/packaging/flavorgen/flavors/util/helpers.go @@ -17,7 +17,6 @@ limitations under the License. package util import ( - "fmt" "reflect" "regexp" "strings" @@ -37,7 +36,7 @@ type Replacement struct { } var ( - Replacements = []Replacement{ + DefaultReplacements = []Replacement{ { Kind: "KubeadmControlPlane", Name: "${CLUSTER_NAME}", @@ -180,24 +179,17 @@ func GenerateObjectYAML(obj runtime.Object, replacements []Replacement) string { return str } -func GenerateManifestYaml(objs []runtime.Object) string { +func GenerateManifestYaml(objs []runtime.Object, replacements []Replacement) string { var sb strings.Builder for _, o := range objs { sb.WriteString("---\n") - sb.WriteString(GenerateObjectYAML(o, Replacements)) + sb.WriteString(GenerateObjectYAML(o, replacements)) } return sb.String() } -func PrintObjects(objs []runtime.Object) { - for _, o := range objs { - o := o - fmt.Printf("---\n%s", GenerateObjectYAML(o, Replacements)) //nolint:forbidigo - } -} - func TypeToKind(i interface{}) string { return reflect.ValueOf(i).Elem().Type().Name() }