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  }