project.go 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. package compose
  2. import (
  3. "fmt"
  4. "io/ioutil"
  5. "os"
  6. "path/filepath"
  7. "regexp"
  8. "strings"
  9. "github.com/compose-spec/compose-go/loader"
  10. "github.com/compose-spec/compose-go/types"
  11. "github.com/sirupsen/logrus"
  12. )
  13. type Project struct {
  14. types.Config
  15. projectDir string
  16. Name string `yaml:"-" json:"-"`
  17. }
  18. func NewProject(config types.ConfigDetails, name string) (*Project, error) {
  19. model, err := loader.Load(config)
  20. if err != nil {
  21. return nil, err
  22. }
  23. err = Normalize(model)
  24. if err != nil {
  25. return nil, err
  26. }
  27. p := Project{
  28. Config: *model,
  29. projectDir: config.WorkingDir,
  30. Name: name,
  31. }
  32. return &p, nil
  33. }
  34. // projectFromOptions load a compose project based on command line options
  35. func ProjectFromOptions(options *ProjectOptions) (*Project, error) {
  36. configPath, err := getConfigPathFromOptions(options)
  37. if err != nil {
  38. return nil, err
  39. }
  40. name := options.Name
  41. if name == "" {
  42. name = os.Getenv("COMPOSE_PROJECT_NAME")
  43. }
  44. workingDir := filepath.Dir(configPath[0])
  45. if name == "" {
  46. r := regexp.MustCompile(`[^a-z0-9\\-_]+`)
  47. name = r.ReplaceAllString(strings.ToLower(filepath.Base(workingDir)), "")
  48. }
  49. configs, err := parseConfigs(configPath)
  50. if err != nil {
  51. return nil, err
  52. }
  53. return NewProject(types.ConfigDetails{
  54. WorkingDir: workingDir,
  55. ConfigFiles: configs,
  56. Environment: environment(),
  57. }, name)
  58. }
  59. func getConfigPathFromOptions(options *ProjectOptions) ([]string, error) {
  60. paths := []string{}
  61. pwd, err := os.Getwd()
  62. if err != nil {
  63. return nil, err
  64. }
  65. if len(options.ConfigPaths) != 0 {
  66. for _, f := range options.ConfigPaths {
  67. if f == "-" {
  68. paths = append(paths, f)
  69. continue
  70. }
  71. if !filepath.IsAbs(f) {
  72. f = filepath.Join(pwd, f)
  73. }
  74. if _, err := os.Stat(f); err != nil {
  75. return nil, err
  76. }
  77. paths = append(paths, f)
  78. }
  79. return paths, nil
  80. }
  81. sep := os.Getenv("COMPOSE_FILE_SEPARATOR")
  82. if sep == "" {
  83. sep = string(os.PathListSeparator)
  84. }
  85. f := os.Getenv("COMPOSE_FILE")
  86. if f != "" {
  87. return strings.Split(f, sep), nil
  88. }
  89. for {
  90. candidates := []string{}
  91. for _, n := range SupportedFilenames {
  92. f := filepath.Join(pwd, n)
  93. if _, err := os.Stat(f); err == nil {
  94. candidates = append(candidates, f)
  95. }
  96. }
  97. if len(candidates) > 0 {
  98. winner := candidates[0]
  99. if len(candidates) > 1 {
  100. logrus.Warnf("Found multiple config files with supported names: %s", strings.Join(candidates, ", "))
  101. logrus.Warnf("Using %s\n", winner)
  102. }
  103. return []string{winner}, nil
  104. }
  105. parent := filepath.Dir(pwd)
  106. if parent == pwd {
  107. return nil, fmt.Errorf("Can't find a suitable configuration file in this directory or any parent. Are you in the right directory?")
  108. }
  109. pwd = parent
  110. }
  111. }
  112. var SupportedFilenames = []string{"compose.yaml", "compose.yml", "docker-compose.yml", "docker-compose.yaml"}
  113. func parseConfigs(configPaths []string) ([]types.ConfigFile, error) {
  114. files := []types.ConfigFile{}
  115. for _, f := range configPaths {
  116. var (
  117. b []byte
  118. err error
  119. )
  120. if f == "-" {
  121. b, err = ioutil.ReadAll(os.Stdin)
  122. } else {
  123. if _, err := os.Stat(f); err != nil {
  124. return nil, err
  125. }
  126. b, err = ioutil.ReadFile(f)
  127. }
  128. if err != nil {
  129. return nil, err
  130. }
  131. config, err := loader.ParseYAML(b)
  132. if err != nil {
  133. return nil, err
  134. }
  135. files = append(files, types.ConfigFile{Filename: f, Config: config})
  136. }
  137. return files, nil
  138. }
  139. func environment() map[string]string {
  140. return getAsEqualsMap(os.Environ())
  141. }
  142. // getAsEqualsMap split key=value formatted strings into a key : value map
  143. func getAsEqualsMap(em []string) map[string]string {
  144. m := make(map[string]string)
  145. for _, v := range em {
  146. kv := strings.SplitN(v, "=", 2)
  147. m[kv[0]] = kv[1]
  148. }
  149. return m
  150. }