project.go 3.4 KB

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