@@ -2,20 +2,22 @@ package packager
2
2
3
3
import (
4
4
"fmt"
5
+ "io/ioutil"
5
6
"log"
6
7
"os"
7
- "os/exec"
8
8
"os/user"
9
9
"path"
10
+ "strings"
10
11
11
12
"github.com/docker/lunchbox/types"
12
13
"github.com/docker/lunchbox/utils"
14
+ "github.com/pkg/errors"
13
15
"gopkg.in/yaml.v2"
14
16
)
15
17
16
18
// Init is the entrypoint initialization function.
17
19
// It generates a new application package based on the provided parameters.
18
- func Init (name string , composeFiles [] string ) error {
20
+ func Init (name string , composeFile string ) error {
19
21
if err := utils .ValidateAppName (name ); err != nil {
20
22
return err
21
23
}
@@ -27,17 +29,10 @@ func Init(name string, composeFiles []string) error {
27
29
return err
28
30
}
29
31
30
- merger := NewPythonComposeConfigMerger ()
31
- if len (composeFiles ) == 0 {
32
- if _ , err := os .Stat ("./docker-compose.yml" ); os .IsNotExist (err ) {
33
- log .Println ("no compose file detected" )
34
- return initFromScratch (name )
35
- } else if err != nil {
36
- return err
37
- }
38
- return initFromComposeFiles (name , []string {"./docker-compose.yml" }, merger )
32
+ if composeFile == "" {
33
+ return initFromScratch (name )
39
34
}
40
- return initFromComposeFiles (name , composeFiles , merger )
35
+ return initFromComposeFile (name , composeFile )
41
36
}
42
37
43
38
func initFromScratch (name string ) error {
@@ -54,18 +49,135 @@ func initFromScratch(name string) error {
54
49
return utils .CreateFileWithData (path .Join (dirName , "settings.yml" ), []byte {'\n' })
55
50
}
56
51
57
- func initFromComposeFiles (name string , composeFiles []string , merger ComposeConfigMerger ) error {
52
+ func parseEnv (env string , target map [string ]string ) {
53
+ envlines := strings .Split (env , "\n " )
54
+ for _ , l := range envlines {
55
+ l = strings .Trim (l , "\r " )
56
+ if l == "" || l [0 ] == '#' {
57
+ continue
58
+ }
59
+ kv := strings .SplitN (l , "=" , 2 )
60
+ if len (kv ) != 2 {
61
+ continue
62
+ }
63
+ target [kv [0 ]] = kv [1 ]
64
+ }
65
+ }
66
+
67
+ func isAlNum (b byte ) bool {
68
+ return (b >= 'A' && b <= 'Z' ) || (b >= 'a' && b <= 'z' ) || (b >= '0' && b <= '9' ) || b == '_'
69
+ }
70
+
71
+ func extractString (data string , res * []string ) {
72
+ for {
73
+ dollar := strings .Index (data , "$" )
74
+ if dollar == - 1 || len (data ) == dollar + 1 {
75
+ break
76
+ }
77
+ if data [dollar + 1 ] == '$' {
78
+ data = data [dollar + 2 :]
79
+ continue
80
+ }
81
+ dollar ++
82
+ if data [dollar ] == '{' {
83
+ dollar ++
84
+ }
85
+ start := dollar
86
+ for dollar < len (data ) && isAlNum (data [dollar ]) {
87
+ dollar ++
88
+ }
89
+ * res = append (* res , data [start :dollar ])
90
+ data = data [dollar :]
91
+ }
92
+ }
93
+
94
+ func extractRecurseList (node []interface {}, res * []string ) error {
95
+ for _ , v := range node {
96
+ switch vv := v .(type ) {
97
+ case string :
98
+ extractString (vv , res )
99
+ case []interface {}:
100
+ if err := extractRecurseList (vv , res ); err != nil {
101
+ return err
102
+ }
103
+ case map [interface {}]interface {}:
104
+ if err := extractRecurse (vv , res ); err != nil {
105
+ return err
106
+ }
107
+ }
108
+ }
109
+ return nil
110
+ }
111
+
112
+ func extractRecurse (node map [interface {}]interface {}, res * []string ) error {
113
+ for _ , v := range node {
114
+ switch vv := v .(type ) {
115
+ case string :
116
+ extractString (vv , res )
117
+ case []interface {}:
118
+ if err := extractRecurseList (vv , res ); err != nil {
119
+ return err
120
+ }
121
+ case map [interface {}]interface {}:
122
+ if err := extractRecurse (vv , res ); err != nil {
123
+ return err
124
+ }
125
+ }
126
+ }
127
+ return nil
128
+ }
129
+
130
+ func extractVariables (composeRaw string ) ([]string , error ) {
131
+ compose := make (map [interface {}]interface {})
132
+ err := yaml .Unmarshal ([]byte (composeRaw ), compose )
133
+ if err != nil {
134
+ return nil , err
135
+ }
136
+ var res []string
137
+ err = extractRecurse (compose , & res )
138
+ return res , err
139
+ }
140
+
141
+ func initFromComposeFile (name string , composeFile string ) error {
58
142
log .Println ("init from compose" )
59
143
60
144
dirName := utils .DirNameFromAppName (name )
61
- composeConfig , err := merger . MergeComposeConfig ( composeFiles )
145
+ composeRaw , err := ioutil . ReadFile ( composeFile )
62
146
if err != nil {
63
- return err
147
+ return errors . Wrap ( err , "failed to read compose file" )
64
148
}
65
- if err = utils .CreateFileWithData (path .Join (dirName , "docker-compose.yml" ), composeConfig ); err != nil {
66
- return err
149
+ settings := make (map [string ]string )
150
+ envRaw , err := ioutil .ReadFile (path .Join (path .Dir (composeFile ), ".env" ))
151
+ if err == nil {
152
+ parseEnv (string (envRaw ), settings )
67
153
}
68
- return utils .CreateFileWithData (path .Join (dirName , "settings.yml" ), []byte {'\n' })
154
+ keys , err := extractVariables (string (composeRaw ))
155
+ if err != nil {
156
+ return errors .Wrap (err , "failed to parse compose file" )
157
+ }
158
+ needsFilling := false
159
+ for _ , k := range keys {
160
+ if _ , ok := settings [k ]; ! ok {
161
+ settings [k ] = "FILL ME"
162
+ needsFilling = true
163
+ }
164
+ }
165
+ settingsYAML , err := yaml .Marshal (settings )
166
+ if err != nil {
167
+ return errors .Wrap (err , "failed to marshal settings" )
168
+ }
169
+ err = ioutil .WriteFile (path .Join (dirName , "docker-compose.yml" ), composeRaw , 0644 )
170
+ if err != nil {
171
+ return errors .Wrap (err , "failed to write docker-compose.yml" )
172
+ }
173
+ err = ioutil .WriteFile (path .Join (dirName , "settings.yml" ), settingsYAML , 0644 )
174
+ if err != nil {
175
+ return errors .Wrap (err , "failed to write settings.yml" )
176
+ }
177
+ if needsFilling {
178
+ fmt .Println ("You will need to edit settings.yml to fill in default values." )
179
+ }
180
+ return nil
69
181
}
70
182
71
183
func composeFileFromScratch () ([]byte , error ) {
@@ -99,35 +211,3 @@ func newMetadata(name string) types.AppMetadata {
99
211
Author : userName ,
100
212
}
101
213
}
102
-
103
- // ComposeConfigMerger is an interface exposing methods to merge
104
- // multiple compose files into one configuration
105
- type ComposeConfigMerger interface {
106
- MergeComposeConfig (composeFiles []string ) ([]byte , error )
107
- }
108
-
109
- // PythonComposeConfigMerger implements the ComposeConfigMerger interface and
110
- // executes a `docker-compose` command to merge configs
111
- type PythonComposeConfigMerger struct {}
112
-
113
- // NewPythonComposeConfigMerger returns a ComposeConfigMerger implementor
114
- func NewPythonComposeConfigMerger () ComposeConfigMerger {
115
- return & PythonComposeConfigMerger {}
116
- }
117
-
118
- // MergeComposeConfig takes a list of paths and merges the Compose files
119
- // at those paths into a single configuration
120
- func (m * PythonComposeConfigMerger ) MergeComposeConfig (composeFiles []string ) ([]byte , error ) {
121
- var args []string
122
- for _ , filename := range composeFiles {
123
- args = append (args , fmt .Sprintf ("--file=%v" , filename ))
124
- }
125
- args = append (args , "config" )
126
- cmd := exec .Command ("docker-compose" , args ... )
127
- cmd .Stderr = nil
128
- out , err := cmd .Output ()
129
- if err != nil {
130
- log .Fatalln (string (err .(* exec.ExitError ).Stderr ))
131
- }
132
- return out , err
133
- }
0 commit comments