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.
@@ -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