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] }