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  }