github.com/hazelops/ize@v1.1.12-0.20230915191306-97d7c0e48f11/internal/commands/initialize.go (about)

     1  package commands
     2  
     3  import (
     4  	"fmt"
     5  	"github.com/AlecAivazis/survey/v2"
     6  	"github.com/hazelops/ize/internal/schema"
     7  	"github.com/hazelops/ize/internal/version"
     8  	"golang.org/x/term"
     9  	"os"
    10  	"path/filepath"
    11  	"sort"
    12  	"strings"
    13  
    14  	"github.com/hazelops/ize/examples"
    15  	"github.com/hazelops/ize/internal/generate"
    16  	"github.com/hazelops/ize/pkg/templates"
    17  	"github.com/pterm/pterm"
    18  	"github.com/sirupsen/logrus"
    19  	"github.com/spf13/cobra"
    20  	"github.com/spf13/viper"
    21  )
    22  
    23  type InitOptions struct {
    24  	Output       string
    25  	Template     string
    26  	ShowList     bool
    27  	SkipExamples bool
    28  }
    29  
    30  var initLongDesc = templates.LongDesc(`
    31  	Initialize a new ize project from a template
    32  `)
    33  
    34  var initExample = templates.Examples(`
    35  	# Init project from url
    36  	ize init --template https://github.com/<org>/<repo>
    37  
    38  	# Init project from internal examples (https://github.com/hazelops/ize/tree/main/examples)
    39  	ize init --template simple-monorepo
    40  
    41  	# Display all internal templates
    42  	ize init --list
    43  `)
    44  
    45  func NewInitFlags() *InitOptions {
    46  	return &InitOptions{}
    47  }
    48  
    49  func NewCmdInit() *cobra.Command {
    50  	o := NewInitFlags()
    51  
    52  	cmd := &cobra.Command{
    53  		Use:     "init [flags] <path>",
    54  		Short:   "Initialize project",
    55  		Args:    cobra.MaximumNArgs(1),
    56  		Long:    initLongDesc,
    57  		Example: initExample,
    58  		RunE: func(cmd *cobra.Command, args []string) error {
    59  			cmd.SilenceUsage = true
    60  
    61  			if o.ShowList {
    62  				internalTemplates()
    63  				return nil
    64  			}
    65  
    66  			err := o.Complete(cmd)
    67  			if err != nil {
    68  				return err
    69  			}
    70  
    71  			err = o.Validate()
    72  			if err != nil {
    73  				return err
    74  			}
    75  
    76  			err = o.Run()
    77  			if err != nil {
    78  				return err
    79  			}
    80  
    81  			return nil
    82  		},
    83  	}
    84  
    85  	cmd.Flags().StringVar(&o.Template, "template", "", "set template (url or internal)")
    86  	cmd.Flags().BoolVar(&o.ShowList, "list", false, "show list of examples")
    87  	cmd.Flags().BoolVar(&o.SkipExamples, "skip-examples", false, "generate ize.toml without commented examples")
    88  
    89  	return cmd
    90  }
    91  
    92  func (o *InitOptions) Complete(cmd *cobra.Command) error {
    93  	o.Output = "."
    94  	if len(cmd.Flags().Args()) != 0 {
    95  		o.Output = cmd.Flags().Args()[0]
    96  	}
    97  
    98  	return nil
    99  }
   100  
   101  func (o *InitOptions) Validate() error {
   102  	return nil
   103  }
   104  
   105  func (o *InitOptions) Run() error {
   106  	isTTY := term.IsTerminal(int(os.Stdout.Fd()))
   107  	if len(o.Template) != 0 {
   108  		dest, err := generate.GenerateFiles(o.Template, o.Output)
   109  		if err != nil {
   110  			return err
   111  		}
   112  
   113  		pterm.Success.Printfln(`Initialized project from template "%s" to %s`, o.Template, dest)
   114  
   115  		return nil
   116  	}
   117  
   118  	namespace := ""
   119  	var envList []string
   120  
   121  	env := os.Getenv("ENV")
   122  	if len(env) == 0 {
   123  		env = "dev"
   124  	}
   125  
   126  	dir := o.Output
   127  	if dir == "." {
   128  		cwd, err := os.Getwd()
   129  		if err != nil {
   130  			return fmt.Errorf("can't get current work directory: %s", cwd)
   131  		}
   132  	}
   133  
   134  	dir, err := filepath.Abs(o.Output)
   135  	if err != nil {
   136  		return fmt.Errorf("can't init: %w", err)
   137  	}
   138  
   139  	namespace = filepath.Base(dir)
   140  
   141  	if isTTY {
   142  		err = survey.AskOne(
   143  			&survey.Input{
   144  				Message: fmt.Sprintf("Namespace:"),
   145  				Default: namespace,
   146  			},
   147  			&namespace,
   148  			survey.WithValidator(survey.Required),
   149  		)
   150  		if err != nil {
   151  			return fmt.Errorf("can't init: %w", err)
   152  		}
   153  
   154  		err = survey.AskOne(
   155  			&survey.Input{
   156  				Message: fmt.Sprintf("Environment:"),
   157  				Default: env,
   158  			},
   159  			&env,
   160  			survey.WithValidator(survey.Required),
   161  		)
   162  		if err != nil {
   163  			return fmt.Errorf("can't init: %w", err)
   164  		}
   165  
   166  		envList = append(envList, env)
   167  		env = ""
   168  
   169  		for {
   170  			err = survey.AskOne(
   171  				&survey.Input{
   172  					Message: fmt.Sprintf("Another environment? [enter - skip]"),
   173  					Default: env,
   174  				},
   175  				&env,
   176  			)
   177  			if err != nil {
   178  				return fmt.Errorf("can't init: %w", err)
   179  			}
   180  
   181  			if env == "" {
   182  				break
   183  			}
   184  
   185  			envList = append(envList, env)
   186  			env = ""
   187  		}
   188  	} else {
   189  		envList = append(envList, env)
   190  	}
   191  
   192  	for _, v := range envList {
   193  		envPath := filepath.Join(dir, ".ize", "env", v)
   194  		err := os.MkdirAll(envPath, 0755)
   195  		if err != nil {
   196  			return fmt.Errorf("can't create dir by path %s: %w", envPath, err)
   197  		}
   198  
   199  		viper.Reset()
   200  		cfg := make(map[string]string)
   201  		cfg["namespace"] = namespace
   202  		cfg["env"] = v
   203  
   204  		raw := make(map[string]interface{}, len(cfg))
   205  		for k, v := range cfg {
   206  			raw[k] = v
   207  		}
   208  
   209  		if o.SkipExamples {
   210  			err := viper.MergeConfigMap(raw)
   211  			if err != nil {
   212  				return err
   213  			}
   214  
   215  			err = viper.WriteConfigAs(filepath.Join(envPath, "ize.toml"))
   216  			if err != nil {
   217  				return fmt.Errorf("can't write config: %w", err)
   218  			}
   219  		} else {
   220  			err = writeConfig(filepath.Join(envPath, "ize.toml"), cfg)
   221  			if err != nil {
   222  				return fmt.Errorf("can't write config: %w", err)
   223  			}
   224  		}
   225  
   226  		return nil
   227  	}
   228  
   229  	pterm.Success.Printfln(`Created ize skeleton for %s in %s`, strings.Join(envList, ", "), dir)
   230  	return nil
   231  }
   232  
   233  func internalTemplates() {
   234  	dirs, err := examples.Examples.ReadDir(".")
   235  	if err != nil {
   236  		logrus.Fatal(err)
   237  	}
   238  
   239  	var internal [][]string
   240  	internal = append(internal, []string{"Name", "Version"})
   241  
   242  	for _, d := range dirs {
   243  		if d.IsDir() {
   244  			internal = append(internal, []string{d.Name(), version.GitCommit})
   245  		}
   246  	}
   247  
   248  	_ = pterm.DefaultTable.WithHasHeader().WithData(internal).Render()
   249  }
   250  
   251  func writeConfig(path string, existsValues map[string]string) error {
   252  	allSettings := schema.GetJsonSchema()
   253  
   254  	var str string
   255  
   256  	str += getProperties(allSettings, existsValues)
   257  
   258  	f, err := os.Create(path)
   259  	if err != nil {
   260  		return err
   261  	}
   262  
   263  	defer func() {
   264  		cerr := f.Close()
   265  		if err == nil {
   266  			err = cerr
   267  		}
   268  	}()
   269  
   270  	_, err = f.WriteString(str)
   271  	if err != nil {
   272  		return err
   273  	}
   274  
   275  	return err
   276  }
   277  
   278  func getProperties(settings interface{}, existsValues map[string]string) string {
   279  	var strRoot string
   280  	var strBlocks string
   281  
   282  	properties, ok := settings.(map[string]interface{})["properties"]
   283  	if ok {
   284  		propertiesMap, ok := properties.(map[string]interface{})
   285  		if ok {
   286  			for pn, pv := range propertiesMap {
   287  				pm, ok := pv.(map[string]interface{})
   288  				if ok {
   289  					_, ok := pm["deprecationMessage"]
   290  					if ok {
   291  						continue
   292  					}
   293  					_, ok = pm["patternProperties"].(map[string]interface{})
   294  					if ok {
   295  						pd, ok := settings.(map[string]interface{})["definitions"].(map[string]interface{})[pn]
   296  						desc := ""
   297  						if ok {
   298  							if pn == "terraform" {
   299  								strBlocks += fmt.Sprintf("\n# [%s.infra]%s\n", pn, desc)
   300  							} else {
   301  								strBlocks += fmt.Sprintf("\n# [%s.<name>]%s\n", pn, desc)
   302  							}
   303  							strBlocks += getProperties(pd, map[string]string{})
   304  						}
   305  					}
   306  					pt, ok := pm["type"].(string)
   307  					if !ok {
   308  						pt = "boolean"
   309  					}
   310  					pd := pm["description"].(string)
   311  					switch pt {
   312  					case "string":
   313  						v, ok := existsValues[pn]
   314  						if ok && pn != "env" {
   315  							strRoot += fmt.Sprintf("%-36s\t# %s\n", fmt.Sprintf("%s = \"%s\"", pn, v), pd)
   316  						} else {
   317  							strRoot += fmt.Sprintf("# %-36s\t# %s\n", fmt.Sprintf("%s = \"%s\"", pn, v), pd)
   318  						}
   319  					case "boolean":
   320  						strRoot += fmt.Sprintf("# %-36s\t# %s\n", fmt.Sprintf("%s = false", pn), pd)
   321  					}
   322  				}
   323  			}
   324  		}
   325  	}
   326  
   327  	lines := strings.Split(strRoot, "\n")
   328  	sort.Sort(parametersSort(lines))
   329  	strRoot = strings.Join(lines, "\n")
   330  
   331  	strRoot += strBlocks
   332  
   333  	return strRoot
   334  }
   335  
   336  type parametersSort []string
   337  
   338  func (p parametersSort) Less(i, _ int) bool {
   339  	if len(p[i]) == 0 {
   340  		return false
   341  	}
   342  	return (p[i][0]) != '#' || strings.Contains(p[i], "required")
   343  }
   344  func (p parametersSort) Len() int      { return len(p) }
   345  func (p parametersSort) Swap(i, j int) { p[i], p[j] = p[j], p[i] }