compote 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +21 -0
- data/README.md +103 -0
- data/Rakefile +7 -0
- data/bin/pry +15 -0
- data/bin/rake +12 -0
- data/bin/rspec +12 -0
- data/compote.gemspec +43 -0
- data/config/gems/rspec/.rspec +2 -0
- data/config/gems/rspec/tasks.rake +9 -0
- data/exe/compote +6 -0
- data/lib/compote.rb +22 -0
- data/lib/compote/cli.rb +74 -0
- data/lib/compote/cli/command.rb +69 -0
- data/lib/compote/cli/commands.rb +31 -0
- data/lib/compote/cli/compose.rb +41 -0
- data/lib/compote/cli/env.rb +23 -0
- data/lib/compote/cli/help.rb +23 -0
- data/lib/compote/cli/version.rb +31 -0
- data/lib/compote/config.rb +272 -0
- data/lib/compote/error.rb +144 -0
- data/lib/compote/schema.json +486 -0
- data/lib/compote/schema.rb +371 -0
- data/lib/compote/service_config.rb +81 -0
- data/lib/compote/version.rb +5 -0
- metadata +267 -0
@@ -0,0 +1,41 @@
|
|
1
|
+
module Compote
|
2
|
+
|
3
|
+
class CLI < Thor
|
4
|
+
|
5
|
+
COMPOSE_COMMANDS = %w()
|
6
|
+
|
7
|
+
COMPOTE_COMMANDS = %w( env help version )
|
8
|
+
|
9
|
+
|
10
|
+
compose_help = `docker-compose --help`
|
11
|
+
|
12
|
+
compose_help = compose_help.split "\n"
|
13
|
+
|
14
|
+
compose_help = compose_help.slice ( compose_help.index( "Commands:" ) + 1 )..-1
|
15
|
+
|
16
|
+
compose_help.each do | command_info |
|
17
|
+
|
18
|
+
command, description = command_info.strip.split( /\s+/, 2 )
|
19
|
+
|
20
|
+
next if COMPOTE_COMMANDS.include? command
|
21
|
+
|
22
|
+
COMPOSE_COMMANDS.push command
|
23
|
+
|
24
|
+
|
25
|
+
desc command, description
|
26
|
+
|
27
|
+
define_method "compose_#{ command }" do | *arguments |
|
28
|
+
|
29
|
+
next exec 'docker-compose', command, *arguments if %w(-h --help).include? arguments[ 0 ]
|
30
|
+
|
31
|
+
run_compose command, *arguments
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
map command => "compose_#{ command }"
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Compote
|
2
|
+
|
3
|
+
class CLI < Thor
|
4
|
+
|
5
|
+
desc 'env', 'View the resulted compose environment'
|
6
|
+
|
7
|
+
def env
|
8
|
+
|
9
|
+
compote_config = load_config
|
10
|
+
|
11
|
+
compose_environment = compote_config.compose_environment
|
12
|
+
|
13
|
+
say YAML.dump compose_environment
|
14
|
+
|
15
|
+
rescue Error => error
|
16
|
+
|
17
|
+
exit_with_error error
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Compote
|
2
|
+
|
3
|
+
class CLI < Thor
|
4
|
+
|
5
|
+
desc 'help', 'Get help on a command'
|
6
|
+
|
7
|
+
def help ( *arguments )
|
8
|
+
|
9
|
+
if COMPOSE_COMMANDS.include? arguments.first
|
10
|
+
|
11
|
+
exec 'docker-compose', 'help', *arguments
|
12
|
+
|
13
|
+
else
|
14
|
+
|
15
|
+
super *arguments
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Compote
|
2
|
+
|
3
|
+
class CLI < Thor
|
4
|
+
|
5
|
+
desc 'version', 'Show version information'
|
6
|
+
|
7
|
+
method_option :short, type: :boolean, desc: "Shows only Compote's and Compose's version numbers."
|
8
|
+
|
9
|
+
def version
|
10
|
+
|
11
|
+
compote_version = "compote version: #{ Compote::VERSION }"
|
12
|
+
|
13
|
+
compose_version = if options[ :short ]
|
14
|
+
|
15
|
+
"docker-compose version: #{ `docker-compose version --short` }"
|
16
|
+
|
17
|
+
else
|
18
|
+
|
19
|
+
`docker-compose version`
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
say compote_version
|
24
|
+
|
25
|
+
say compose_version
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,272 @@
|
|
1
|
+
module Compote
|
2
|
+
|
3
|
+
class Config
|
4
|
+
|
5
|
+
def initialize ( file_name )
|
6
|
+
|
7
|
+
@file_name = file_name
|
8
|
+
|
9
|
+
@directory_name = File.dirname file_name
|
10
|
+
|
11
|
+
|
12
|
+
data = YAML.load_file file_name
|
13
|
+
|
14
|
+
data = Schema.normalize self, data
|
15
|
+
|
16
|
+
@data = apply_extends data
|
17
|
+
|
18
|
+
|
19
|
+
@compose_settings = @data.reject { | key, value | %w( compote services ).include? key }
|
20
|
+
|
21
|
+
@compote_settings = @data.fetch 'compote', {}
|
22
|
+
|
23
|
+
|
24
|
+
@services_configs = {}
|
25
|
+
|
26
|
+
@data.fetch( 'services', {} ).each do | name, data |
|
27
|
+
|
28
|
+
service_config = ServiceConfig.new self, name, data
|
29
|
+
|
30
|
+
@services_configs[ name ] = service_config
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
def file_name
|
37
|
+
|
38
|
+
@file_name
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
def directory_name
|
43
|
+
|
44
|
+
@directory_name
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
def compose_version
|
49
|
+
|
50
|
+
@compose_settings.fetch 'version'
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
def compose_environment
|
55
|
+
|
56
|
+
environment = {}
|
57
|
+
|
58
|
+
|
59
|
+
environment_setting = @compote_settings.fetch 'environment', {}
|
60
|
+
|
61
|
+
environment_setting.each do | key, value |
|
62
|
+
|
63
|
+
environment[ key ] = ENV.fetch key, value
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
env_file_setting = @compote_settings.fetch 'env_file' do
|
69
|
+
|
70
|
+
env_file_setting = []
|
71
|
+
|
72
|
+
env_file_setting.push '.env' if File.exist? get_path '.env'
|
73
|
+
|
74
|
+
env_file_setting
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
env_file_setting.each do | env_file_path |
|
79
|
+
|
80
|
+
begin
|
81
|
+
|
82
|
+
env_file_path = get_path env_file_path
|
83
|
+
|
84
|
+
env_file_content = File.read env_file_path
|
85
|
+
|
86
|
+
env_file_variables = Dotenv::Parser.call env_file_content
|
87
|
+
|
88
|
+
env_file_variables.each do | key, value |
|
89
|
+
|
90
|
+
environment[ key ] = ENV.fetch key, value
|
91
|
+
|
92
|
+
end
|
93
|
+
|
94
|
+
rescue Errno::ENOENT => error
|
95
|
+
|
96
|
+
raise EnvFileOpenError.new error: error, config: self
|
97
|
+
|
98
|
+
rescue Dotenv::FormatError => error
|
99
|
+
|
100
|
+
raise EnvFileFormatError.new error: error, path: env_file_path, config: self
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
|
107
|
+
raise ProjectNameNotProvidedError.new unless environment[ 'COMPOSE_PROJECT_NAME' ] || ENV[ 'COMPOSE_PROJECT_NAME' ]
|
108
|
+
|
109
|
+
|
110
|
+
environment
|
111
|
+
|
112
|
+
end
|
113
|
+
|
114
|
+
def compose_file
|
115
|
+
|
116
|
+
file = Tempfile.new 'docker-compose.yml'
|
117
|
+
|
118
|
+
file.write YAML.dump compose_config
|
119
|
+
|
120
|
+
file.close
|
121
|
+
|
122
|
+
file
|
123
|
+
|
124
|
+
end
|
125
|
+
|
126
|
+
def commands
|
127
|
+
|
128
|
+
commands = {}
|
129
|
+
|
130
|
+
@compote_settings.fetch( 'commands', {} ).each do | name, command |
|
131
|
+
|
132
|
+
commands[ name ] = command
|
133
|
+
|
134
|
+
end
|
135
|
+
|
136
|
+
@services_configs.each do | service_name, service_config |
|
137
|
+
|
138
|
+
service_config.commands.each do | command_name, command |
|
139
|
+
|
140
|
+
commands[ "#{ service_name }:#{ command_name }" ] = command
|
141
|
+
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
|
146
|
+
commands
|
147
|
+
|
148
|
+
end
|
149
|
+
|
150
|
+
def compose_config
|
151
|
+
|
152
|
+
compose_config = {}
|
153
|
+
|
154
|
+
compose_config[ 'version' ] = @compose_settings.fetch 'version'
|
155
|
+
|
156
|
+
compose_config[ 'volumes' ] = @compose_settings.fetch 'volumes', {}
|
157
|
+
|
158
|
+
compose_config[ 'networks' ] = @compose_settings.fetch 'networks', {}
|
159
|
+
|
160
|
+
|
161
|
+
compose_config[ 'services' ] = {}
|
162
|
+
|
163
|
+
@services_configs.each do | name, service_config |
|
164
|
+
|
165
|
+
service_compose_config = service_config.compose_config
|
166
|
+
|
167
|
+
compose_config[ 'services' ].merge! service_compose_config.fetch( 'services', {} )
|
168
|
+
|
169
|
+
compose_config[ 'volumes' ].merge! service_compose_config.fetch( 'volumes', {} )
|
170
|
+
|
171
|
+
compose_config[ 'networks' ].merge! service_compose_config.fetch( 'networks', {} )
|
172
|
+
|
173
|
+
end
|
174
|
+
|
175
|
+
|
176
|
+
compose_config
|
177
|
+
|
178
|
+
end
|
179
|
+
|
180
|
+
def get_service_config ( name )
|
181
|
+
|
182
|
+
@services_configs.fetch name
|
183
|
+
|
184
|
+
rescue KeyError => error
|
185
|
+
|
186
|
+
raise ServiceNotFoundError.new service: name, config: self
|
187
|
+
|
188
|
+
end
|
189
|
+
|
190
|
+
def has_service_config? ( name )
|
191
|
+
|
192
|
+
@services_configs.has_key? name
|
193
|
+
|
194
|
+
end
|
195
|
+
|
196
|
+
def load_config ( path )
|
197
|
+
|
198
|
+
Config.load_config path, self
|
199
|
+
|
200
|
+
end
|
201
|
+
|
202
|
+
def get_path ( path )
|
203
|
+
|
204
|
+
Config.get_path path, self
|
205
|
+
|
206
|
+
end
|
207
|
+
|
208
|
+
|
209
|
+
@@configs = {}
|
210
|
+
|
211
|
+
def self.load_config ( path, origin_config = nil )
|
212
|
+
|
213
|
+
path = get_path path, origin_config
|
214
|
+
|
215
|
+
return origin_config if path == origin_config&.directory_name
|
216
|
+
|
217
|
+
config = @@configs[ path ]
|
218
|
+
|
219
|
+
raise ConfigRecursionError path: path, origin_config: origin_config, configs: configs if config.is_a? String
|
220
|
+
|
221
|
+
return config if config
|
222
|
+
|
223
|
+
@@configs[ path ] = origin_config&.file_name || ''
|
224
|
+
|
225
|
+
config = Config.new path
|
226
|
+
|
227
|
+
@@configs[ path ] = config
|
228
|
+
|
229
|
+
config
|
230
|
+
|
231
|
+
rescue Errno::ENOENT => error
|
232
|
+
|
233
|
+
raise ConfigOpenError.new error: error, origin_config: origin_config
|
234
|
+
|
235
|
+
end
|
236
|
+
|
237
|
+
def self.get_path ( path, origin_config = nil )
|
238
|
+
|
239
|
+
path = File.absolute_path path, origin_config&.directory_name
|
240
|
+
|
241
|
+
path = Pathname.new( path ).cleanpath.to_path
|
242
|
+
|
243
|
+
path
|
244
|
+
|
245
|
+
end
|
246
|
+
|
247
|
+
|
248
|
+
protected
|
249
|
+
|
250
|
+
def data
|
251
|
+
|
252
|
+
@data
|
253
|
+
|
254
|
+
end
|
255
|
+
|
256
|
+
def apply_extends ( initial_data )
|
257
|
+
|
258
|
+
Schema.apply_extends initial_data do | path |
|
259
|
+
|
260
|
+
config = load_config path
|
261
|
+
|
262
|
+
data = config.data
|
263
|
+
|
264
|
+
data
|
265
|
+
|
266
|
+
end
|
267
|
+
|
268
|
+
end
|
269
|
+
|
270
|
+
end
|
271
|
+
|
272
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
module Compote
|
2
|
+
|
3
|
+
class Error < StandardError
|
4
|
+
|
5
|
+
attr_writer :message
|
6
|
+
|
7
|
+
end
|
8
|
+
|
9
|
+
|
10
|
+
class ConfigOpenError < Error
|
11
|
+
|
12
|
+
def initialize ( error:, origin_config: )
|
13
|
+
|
14
|
+
message = "Error loading config file: #{ error.message }"
|
15
|
+
|
16
|
+
message += "\n" + "required from #{ origin_config.file_name }" if origin_config
|
17
|
+
|
18
|
+
super message
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
class ConfigFormatError < Error
|
26
|
+
|
27
|
+
def initialize ( error:, data: )
|
28
|
+
|
29
|
+
message = "Error loading config file: #{ error.message }" + "\n\n" + "#{ YAML.dump data }"
|
30
|
+
|
31
|
+
super message
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
class ConfigRecursionError < Error
|
39
|
+
|
40
|
+
def initialize ( path:, origin_config:, configs: )
|
41
|
+
|
42
|
+
message = "Error loading config file: Recursive loading of config files - #{ path }."
|
43
|
+
|
44
|
+
message += "\n\n" + "Requiring trace:"
|
45
|
+
|
46
|
+
message += "\n" + "#{ path }"
|
47
|
+
|
48
|
+
requiring_path = origin_config.file_name
|
49
|
+
|
50
|
+
loop do
|
51
|
+
|
52
|
+
message += "\n" + "#{ requiring_path }"
|
53
|
+
|
54
|
+
requiring_path = configs[ requiring_path ]
|
55
|
+
|
56
|
+
break if requiring_path == path
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
super message
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
class EnvFileOpenError < Error
|
68
|
+
|
69
|
+
def initialize ( error:, config: )
|
70
|
+
|
71
|
+
message = "Error loading env file: #{ error.message }"
|
72
|
+
|
73
|
+
message += "\n" + "required from #{ config.file_name }"
|
74
|
+
|
75
|
+
super message
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
class EnvFileFormatError < Error
|
83
|
+
|
84
|
+
def initialize ( error:, path:, config: )
|
85
|
+
|
86
|
+
message = "Error loading env file: #{ error.message } - \"#{ path }\""
|
87
|
+
|
88
|
+
message += "\n" + "required from #{ config.file_name }"
|
89
|
+
|
90
|
+
super message
|
91
|
+
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
|
97
|
+
class ServiceNotFoundError < Error
|
98
|
+
|
99
|
+
attr_reader :service, :config
|
100
|
+
|
101
|
+
|
102
|
+
def initialize ( service:, config: )
|
103
|
+
|
104
|
+
@service = service
|
105
|
+
|
106
|
+
@config = config
|
107
|
+
|
108
|
+
message = "Error extending service: Service \"#{ service }\" not found in config \"#{ config.file_name }\"."
|
109
|
+
|
110
|
+
super message
|
111
|
+
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
115
|
+
|
116
|
+
|
117
|
+
class ProjectNameNotProvidedError < Error
|
118
|
+
|
119
|
+
def initialize
|
120
|
+
|
121
|
+
message = "Error running compote: Make sure that you provided env variable COMPOSE_PROJECT_NAME."
|
122
|
+
|
123
|
+
super message
|
124
|
+
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
128
|
+
|
129
|
+
|
130
|
+
class UndefinedCommandError < Error
|
131
|
+
|
132
|
+
def initialize ( service_name:, command_name:, config: )
|
133
|
+
|
134
|
+
message = "Error running compote command: Can't find command named \#{ command_name }\" for service \"#{ service_name }\" in config \"#{ config.file_name }\"."
|
135
|
+
|
136
|
+
message += "\n" + "Seems that config doesn't have a service with such name." unless config.has_service_config? service_name
|
137
|
+
|
138
|
+
super message
|
139
|
+
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|