github.com/Azure/draft-classic@v0.16.0/cmd/draft/create.go (about) 1 package main 2 3 import ( 4 "errors" 5 "fmt" 6 "io" 7 "io/ioutil" 8 "os" 9 "path/filepath" 10 "strings" 11 "unicode" 12 13 "github.com/BurntSushi/toml" 14 log "github.com/sirupsen/logrus" 15 "github.com/spf13/cobra" 16 17 "github.com/Azure/draft/pkg/draft/draftpath" 18 "github.com/Azure/draft/pkg/draft/manifest" 19 "github.com/Azure/draft/pkg/draft/pack" 20 "github.com/Azure/draft/pkg/draft/pack/repo" 21 "github.com/Azure/draft/pkg/linguist" 22 "github.com/Azure/draft/pkg/osutil" 23 ) 24 25 const ( 26 draftToml = "draft.toml" 27 createDesc = `This command transforms the local directory to be deployable via 'draft up'. 28 ` 29 ) 30 31 // ErrNoLanguageDetected is raised when `draft create` does not detect source 32 // code for linguist to classify, or if there are no packs available for the detected languages. 33 var ErrNoLanguageDetected = errors.New("no languages were detected") 34 35 type createCmd struct { 36 appName string 37 out io.Writer 38 pack string 39 home draftpath.Home 40 dest string 41 repositoryName string 42 } 43 44 func newCreateCmd(out io.Writer) *cobra.Command { 45 cc := &createCmd{ 46 out: out, 47 } 48 49 cmd := &cobra.Command{ 50 Use: "create [path]", 51 Short: "transform the local directory to be deployable to Kubernetes", 52 Long: createDesc, 53 RunE: func(cmd *cobra.Command, args []string) error { 54 if len(args) > 0 { 55 cc.dest = args[0] 56 } 57 return cc.run() 58 }, 59 PreRun: func(cmd *cobra.Command, args []string) { 60 // Docker naming convention doesn't allow upper case names for repositories. 61 cc.normalizeApplicationName() 62 }, 63 } 64 65 cc.home = draftpath.Home(homePath()) 66 67 f := cmd.Flags() 68 f.StringVarP(&cc.appName, "app", "a", "", "name of the Helm release. By default, this is a randomly generated name") 69 f.StringVarP(&cc.pack, "pack", "p", "", "the named Draft starter pack to scaffold the app with") 70 71 return cmd 72 } 73 74 func (c *createCmd) run() error { 75 var err error 76 mfest := manifest.New() 77 78 if c.appName != "" { 79 mfest.Environments[manifest.DefaultEnvironmentName].Name = c.appName 80 } 81 82 chartExists, err := osutil.Exists(filepath.Join(c.dest, pack.ChartsDir)) 83 if err != nil { 84 return fmt.Errorf("there was an error checking if charts/ exists: %v", err) 85 } 86 if chartExists { 87 // chart dir already exists, so we just tell the user that we are happily skipping the 88 // process. 89 fmt.Fprintln(c.out, "--> chart directory charts/ already exists. Ready to sail!") 90 return nil 91 } 92 93 if c.pack != "" { 94 // --pack was explicitly defined, so we can just lazily use that here. No detection required. 95 packsFound, err := pack.Find(c.home.Packs(), c.pack) 96 if err != nil { 97 return err 98 } 99 if len(packsFound) == 0 { 100 return fmt.Errorf("No packs found with name %s", c.pack) 101 102 } else if len(packsFound) == 1 { 103 packSrc := packsFound[0] 104 if err = pack.CreateFrom(c.dest, packSrc, c.appName); err != nil { 105 return err 106 } 107 108 } else { 109 return fmt.Errorf("Multiple packs named %s found: %v", c.pack, packsFound) 110 } 111 112 } else { 113 // pack detection time 114 packPath, err := doPackDetection(c.home, c.out) 115 if err != nil { 116 return err 117 } 118 err = pack.CreateFrom(c.dest, packPath, c.appName) 119 if err != nil { 120 return err 121 } 122 } 123 tomlFile := filepath.Join(c.dest, draftToml) 124 draftToml, err := os.OpenFile(tomlFile, os.O_RDWR|os.O_CREATE, 0644) 125 if err != nil { 126 return err 127 } 128 defer draftToml.Close() 129 130 if err := toml.NewEncoder(draftToml).Encode(mfest); err != nil { 131 return fmt.Errorf("could not write metadata to draft.toml: %v", err) 132 } 133 134 ignoreFile := filepath.Join(c.dest, ignoreFileName) 135 if _, err := os.Stat(ignoreFile); os.IsNotExist(err) { 136 d1 := []byte("*.swp\n*.tmp\n*.temp\n.git*\n") 137 if err := ioutil.WriteFile(ignoreFile, d1, 0644); err != nil { 138 return err 139 } 140 } 141 142 fmt.Fprintln(c.out, "--> Ready to sail") 143 return nil 144 } 145 146 func (c *createCmd) normalizeApplicationName() { 147 if c.appName == "" { 148 return 149 } 150 151 nameIsUpperCase := false 152 for _, char := range c.appName { 153 if unicode.IsUpper(char) { 154 nameIsUpperCase = true 155 break 156 } 157 } 158 159 if !nameIsUpperCase { 160 return 161 } 162 163 lowerCaseName := strings.ToLower(c.appName) 164 fmt.Fprintf( 165 c.out, 166 "--> Application %s will be renamed to %s for docker compatibility\n", 167 c.appName, 168 lowerCaseName, 169 ) 170 c.appName = lowerCaseName 171 } 172 173 // doPackDetection performs pack detection across all the packs available in $(draft home)/packs in 174 // alphabetical order, returning the pack dirpath and any errors that occurred during the pack detection. 175 func doPackDetection(home draftpath.Home, out io.Writer) (string, error) { 176 langs, err := linguist.ProcessDir(".") 177 log.Debugf("linguist.ProcessDir('.') result:\n\nError: %v", err) 178 if err != nil { 179 return "", fmt.Errorf("there was an error detecting the language: %s", err) 180 } 181 for _, lang := range langs { 182 log.Debugf("%s:\t%f (%s)", lang.Language, lang.Percent, lang.Color) 183 } 184 if len(langs) == 0 { 185 return "", ErrNoLanguageDetected 186 } 187 for _, lang := range langs { 188 detectedLang := linguist.Alias(lang) 189 fmt.Fprintf(out, "--> Draft detected %s (%f%%)\n", detectedLang.Language, detectedLang.Percent) 190 for _, repository := range repo.FindRepositories(home.Packs()) { 191 packDir := filepath.Join(repository.Dir, repo.PackDirName) 192 packs, err := ioutil.ReadDir(packDir) 193 if err != nil { 194 return "", fmt.Errorf("there was an error reading %s: %v", packDir, err) 195 } 196 for _, file := range packs { 197 if file.IsDir() { 198 if strings.Compare(strings.ToLower(detectedLang.Language), strings.ToLower(file.Name())) == 0 { 199 packPath := filepath.Join(packDir, file.Name()) 200 log.Debugf("pack path: %s", packPath) 201 return packPath, nil 202 } 203 } 204 } 205 } 206 fmt.Fprintf(out, "--> Could not find a pack for %s. Trying to find the next likely language match...\n", detectedLang.Language) 207 } 208 return "", ErrNoLanguageDetected 209 }