diff --git a/Makefile b/Makefile index 516b859531..3741fc6789 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. @@ -335,7 +335,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 +ALL_VERIFY_CHECKS = boilerplate modules gen conversions flavor .PHONY: verify verify: $(addprefix verify-,$(ALL_VERIFY_CHECKS)) lint-markdown lint-shell ## Run all verify-* targets @@ -370,6 +370,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 + @if !(git diff --quiet HEAD -- templates); then \ + git diff; \ + echo "flavor files in templates directory are out of date"; exit 1; \ + fi + ## -------------------------------------- ## Build ## -------------------------------------- @@ -561,12 +568,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..838f980bb4 100644 --- a/packaging/flavorgen/cmd/root.go +++ b/packaging/flavorgen/cmd/root.go @@ -17,17 +17,41 @@ 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/env" "sigs.k8s.io/cluster-api-provider-vsphere/packaging/flavorgen/flavors" - "sigs.k8s.io/cluster-api-provider-vsphere/packaging/flavorgen/flavors/env" "sigs.k8s.io/cluster-api-provider-vsphere/packaging/flavorgen/flavors/util" ) const flavorFlag = "flavor" +const outputDirFlag = "output-dir" + +var ( + flavorMappings = map[string]string{ + flavors.VIP: "cluster-template", + flavors.ExternalLoadBalancer: "cluster-template-external-loadbalancer", + flavors.ClusterClass: "clusterclass-template", + flavors.ClusterTopology: "cluster-template-topology", + flavors.Ignition: "cluster-template-ignition", + flavors.NodeIPAM: "cluster-template-node-ipam", + } + + 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()) } - switch flavor { - case flavors.VIP: - util.PrintObjects(flavors.MultiNodeTemplateWithKubeVIP()) - case flavors.ExternalLoadBalancer: - util.PrintObjects(flavors.MultiNodeTemplateWithExternalLoadBalancer()) - case flavors.ClusterClass: - util.PrintObjects(flavors.ClusterClassTemplateWithKubeVIP()) - case flavors.ClusterTopology: - additionalReplacements := []util.Replacement{ - { + 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 { + util.Replacements = append([]util.Replacement{}, util.DefaultReplacements...) + if f == flavors.ClusterTopology { + util.Replacements = append(util.Replacements, util.Replacement{ Kind: "Cluster", Name: "${CLUSTER_NAME}", Value: env.ControlPlaneMachineCountVar, FieldPath: []string{"spec", "topology", "controlPlane", "replicas"}, - }, + }) + } + + objs, err := generateSingle(f) + if err != nil { + return err } - util.Replacements = append(util.Replacements, additionalReplacements...) - util.PrintObjects(flavors.ClusterTopologyTemplateKubeVIP()) + + fileMapping, ok := flavorMappings[f] + if !ok { + return fmt.Errorf("file mapping for flavor %q is missng in flavorMappings", f) + } + + yamlFileName := fileMapping + ".yaml" + if outputDir == "-" { + if generateMultiFlavors { + // use the yaml filename as a section delimiter + fmt.Printf("### %s\n", yamlFileName) + } + util.PrintObjects(objs) + continue + } + + yamlPath := filepath.Join(outputDir, yamlFileName) + err = util.DumpObjectsToFile(objs, yamlPath) + if err != nil { + return errors.Wrapf(err, "failed to dump manifest content to file for flavor %s", f) + } + } + + return nil +} + +func generateSingle(flavor string) ([]runtime.Object, error) { + switch flavor { + case flavors.VIP: + return flavors.MultiNodeTemplateWithKubeVIP(), nil + case flavors.ExternalLoadBalancer: + return flavors.MultiNodeTemplateWithExternalLoadBalancer(), nil + case flavors.ClusterClass: + return flavors.ClusterClassTemplateWithKubeVIP(), nil + case flavors.ClusterTopology: + return flavors.ClusterTopologyTemplateKubeVIP(), nil case flavors.Ignition: - util.PrintObjects(flavors.MultiNodeTemplateWithKubeVIPIgnition()) + return flavors.MultiNodeTemplateWithKubeVIPIgnition(), nil case flavors.NodeIPAM: - util.PrintObjects(flavors.MultiNodeTemplateWithKubeVIPNodeIPAM()) + return flavors.MultiNodeTemplateWithKubeVIPNodeIPAM(), nil default: - return errors.Errorf("invalid flavor") + return nil, errors.Errorf("invalid flavor") } - return nil } diff --git a/packaging/flavorgen/flavors/util/helpers.go b/packaging/flavorgen/flavors/util/helpers.go index 3ed2f5d7bf..b8d0ee9bfb 100644 --- a/packaging/flavorgen/flavors/util/helpers.go +++ b/packaging/flavorgen/flavors/util/helpers.go @@ -18,6 +18,7 @@ package util import ( "fmt" + "os" "reflect" "regexp" "strings" @@ -37,7 +38,7 @@ type Replacement struct { } var ( - Replacements = []Replacement{ + DefaultReplacements = []Replacement{ { Kind: "KubeadmControlPlane", Name: "${CLUSTER_NAME}", @@ -63,6 +64,7 @@ var ( FieldPath: []string{"spec", "template", "spec"}, }, } + Replacements = append([]Replacement{}, DefaultReplacements...) stringVars = []string{ regexVar(env.ClusterNameVar), @@ -192,10 +194,12 @@ func GenerateManifestYaml(objs []runtime.Object) string { } func PrintObjects(objs []runtime.Object) { - for _, o := range objs { - o := o - fmt.Printf("---\n%s", GenerateObjectYAML(o, Replacements)) //nolint:forbidigo - } + fmt.Print(GenerateManifestYaml(objs)) +} + +func DumpObjectsToFile(objs []runtime.Object, path string) error { + manifest := GenerateManifestYaml(objs) + return os.WriteFile(path, []byte(manifest), 0600) } func TypeToKind(i interface{}) string {