github.com/microsoft/fabrikate@v1.0.0-alpha.1.0.20210115014322-dc09194d0885/internal/cmd/generate.go (about) 1 package cmd 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "os" 7 "os/exec" 8 "path" 9 "strings" 10 11 "github.com/kyokomi/emoji" 12 "github.com/microsoft/fabrikate/internal/core" 13 "github.com/microsoft/fabrikate/internal/generators" 14 "github.com/microsoft/fabrikate/internal/logger" 15 "github.com/spf13/cobra" 16 ) 17 18 func writeGeneratedManifests(generationPath string, components []core.Component) (err error) { 19 // Delete the old version, so we don't end up with a mishmash of two builds. 20 os.RemoveAll(generationPath) 21 22 for _, component := range components { 23 componentGenerationPath := path.Join(generationPath, component.LogicalPath) 24 if err = os.MkdirAll(componentGenerationPath, 0777); err != nil { 25 return err 26 } 27 28 componentYAMLFilename := fmt.Sprintf("%s.yaml", component.Name) 29 componentYAMLFilePath := path.Join(componentGenerationPath, componentYAMLFilename) 30 31 logger.Info(emoji.Sprintf(":floppy_disk: Writing %s", componentYAMLFilePath)) 32 33 err = ioutil.WriteFile(componentYAMLFilePath, []byte(component.Manifest), 0644) 34 if err != nil { 35 return err 36 } 37 } 38 39 return nil 40 } 41 42 func validateGeneratedManifests(generationPath string) (err error) { 43 logger.Info(emoji.Sprintf(":microscope: Validating generated manifests in path %s", generationPath)) 44 if output, err := exec.Command("kubectl", "apply", "--validate=true", "--dry-run", "--recursive", "-f", generationPath).Output(); err != nil { 45 if ee, ok := err.(*exec.ExitError); ok { 46 logger.Error(fmt.Sprintf("Validating generated manifests failed with: %s: output: %s", ee.Stderr, output)) 47 return err 48 } 49 } 50 51 return nil 52 } 53 54 // Generate implements the 'generate' command. It takes a set of environments and a validation flag 55 // and iterates through the component tree, generating components as it reaches them, and writing all 56 // of the generated manifests at the very end. 57 func Generate(startPath string, environments []string, validate bool) (components []core.Component, err error) { 58 // Iterate through component tree and generate 59 60 rootInit := func(startPath string, environments []string, c core.Component) (component core.Component, err error) { 61 return c.UpdateComponentPath(startPath, environments) 62 } 63 64 results := core.WalkComponentTree(startPath, environments, func(path string, component *core.Component) (err error) { 65 66 var generator core.Generator 67 switch component.ComponentType { 68 case "helm": 69 generator = &generators.HelmGenerator{} 70 case "static": 71 generator = &generators.StaticGenerator{} 72 case "": 73 fallthrough 74 case "component": 75 // noop 76 default: 77 return fmt.Errorf(`invalid component type %v in component %+v`, component.ComponentType, component) 78 } 79 80 return component.Generate(generator) 81 }, rootInit) 82 83 components, err = core.SynchronizeWalkResult(results) 84 85 if err != nil { 86 return nil, err 87 } 88 89 environmentName := strings.Join(environments, "-") 90 if len(environmentName) == 0 { 91 environmentName = "common" 92 } 93 94 generationPath := path.Join(startPath, "generated", environmentName) 95 96 if err = writeGeneratedManifests(generationPath, components); err != nil { 97 return nil, err 98 } 99 100 if validate { 101 if err = validateGeneratedManifests(generationPath); err != nil { 102 return nil, err 103 } 104 } 105 106 if err == nil { 107 logger.Info(emoji.Sprintf(":raised_hands: Finished generate")) 108 } 109 110 return components, err 111 } 112 113 var generateCmd = &cobra.Command{ 114 Use: "generate <config1> <config2> ... <configN>", 115 Short: "Generates Kubernetes resource definitions from deployment definition.", 116 Long: `Generate produces Kubernetes resource manifests from a deployment definition. 117 118 Where the generate command takes a list of the configurations that should be used to generate the resource 119 definitions for the deployment. These configurations should be specified in priority order. For example, 120 if you specified "prod azure east", prod's config would be applied first, and azure's config 121 would only be applied if they did not conflict with prod. Likewise, east's config would only be applied 122 if it did not conflict with prod or azure.`, 123 RunE: func(cmd *cobra.Command, args []string) error { 124 PrintVersion() 125 126 validation := cmd.Flag("validate").Value.String() 127 _, err := Generate("./", args, validation == "true") 128 129 return err 130 }, 131 } 132 133 func init() { 134 generateCmd.PersistentFlags().Bool("validate", false, "Validate generated resource manifest YAML") 135 rootCmd.AddCommand(generateCmd) 136 }