3
3
require 'yaml'
4
4
5
5
require 'kube_deploy_tools/deploy_config_file/util'
6
+ require 'kube_deploy_tools/deploy_config_file/deep_merge'
6
7
require 'kube_deploy_tools/formatted_logger'
7
8
require 'kube_deploy_tools/image_registry'
9
+ require 'kube_deploy_tools/shellrunner'
8
10
9
11
DEPLOY_YAML = 'deploy.yaml'
10
12
DEPLOY_YML_V1 = 'deploy.yml'
@@ -16,6 +18,10 @@ class DeployConfigFile
16
18
17
19
include DeployConfigFileUtil
18
20
21
+ # TODO(joshk): Refactor into initialize(fp) which takes a file-like object;
22
+ # after this, auto discovery should go into DeployConfigFile.locate
23
+ # classmethod. This would require erasing auto-upgrade capability, which
24
+ # should be possible if we major version bump.
19
25
def initialize ( filename )
20
26
config = nil
21
27
if !filename . nil? && Pathname . new ( filename ) . absolute?
@@ -69,93 +75,36 @@ def initialize(filename)
69
75
case version
70
76
when 2
71
77
fetch_and_parse_version2_config!
72
- when 1
73
- fetch_and_parse_version1_config!
74
78
end
75
79
end
76
80
77
81
def fetch_and_parse_version2_config!
82
+ # The literal contents of your deploy.yaml are now populated into |self|.
78
83
config = @original_config
79
- @image_registries = parse_image_registries ( config . fetch ( 'image_registries' , [ ] ) )
80
- @default_flags = parse_default_flags ( config . fetch ( 'default_flags' , { } ) )
81
- @artifacts = parse_artifacts ( config . fetch ( 'artifacts' , [ ] ) , @default_flags , @image_registries )
82
- @flavors = parse_flavors ( config . fetch ( 'flavors' , { } ) )
83
- @hooks = parse_hooks ( config . fetch ( 'hooks' , [ 'default' ] ) )
84
- @expiration = parse_expiration ( config . fetch ( 'expiration' , [ ] ) )
85
- end
86
-
87
- # Fetches and parse a version 1 config as a version 2 config, with the
88
- # defaults set as previously with KDT 1.x behavior
89
- def fetch_and_parse_version1_config!
90
- config = @original_config
91
- @image_registries = parse_image_registries ( [
92
- {
93
- 'name' => 'aws' ,
94
- 'driver' => 'aws' ,
95
- 'prefix' => '***REMOVED***' ,
96
- 'config' => {
97
- 'region' => 'us-west-2'
98
- }
99
- } ,
100
- {
101
- 'name' => 'gcp' ,
102
- 'driver' => 'gcp' ,
103
- 'prefix' => '***REMOVED***'
104
- } ,
105
- {
106
- 'name' => 'local' ,
107
- 'driver' => 'noop' ,
108
- 'prefix' => 'local-registry'
109
- }
110
- ] )
111
- @default_flags = parse_default_flags ( {
112
- 'pull_policy' => 'IfNotPresent' ,
113
- } )
114
- @artifacts = parse_artifacts ( config . fetch ( 'deploy' ) . fetch ( 'clusters' , [ ] )
115
- . map . with_index { |c , i |
116
- target = c . fetch ( 'target' )
117
- environment = c . fetch ( 'environment' )
118
- case target
119
- when 'local'
120
- cloud = 'local'
121
- image_registry = 'local'
122
- when 'colo-service'
123
- cloud = 'colo'
124
- image_registry = 'aws'
125
- when 'us-east-1' , 'us-west-2' , 'eu-west-1'
126
- cloud = 'aws'
127
- image_registry = 'aws'
128
- when 'gcp'
129
- cloud = 'gcp'
130
- image_registry = 'gcp'
131
- else
132
- raise ArgumentError , "Expected a valid KDT 1.x .target for .deploy.clusters[#{ i } ].target, but got '#{ target } '"
133
- end
134
-
135
- flags = c . fetch ( 'extra_flags' , { } )
136
- . merge ( {
137
- 'target' => target ,
138
- 'environment' => environment ,
139
- 'cloud' => cloud
140
- } )
141
-
142
- if flags . key? ( 'pull_policy' ) && flags . fetch ( 'pull_policy' ) == @default_flags . fetch ( 'pull_policy' )
143
- flags . delete ( 'pull_policy' )
144
- end
145
84
146
- artifact = {
147
- 'name' => target + '-' + environment ,
148
- 'image_registry' => image_registry ,
149
- 'flags' => flags ,
150
- }
85
+ @image_registries = parse_image_registries ( config . fetch ( 'image_registries' , [ ] ) )
86
+ @default_flags = config . fetch ( 'default_flags' , { } )
87
+ @artifacts = config . fetch ( 'artifacts' , [ ] )
88
+ @flavors = config . fetch ( 'flavors' , { } )
89
+ @hooks = config . fetch ( 'hooks' , [ 'default' ] )
90
+ @expiration = config . fetch ( 'expiration' , [ ] )
91
+
92
+ validate_default_flags
93
+ validate_flavors
94
+ validate_hooks
95
+ validate_expiration
96
+
97
+ # Augment these literal contents by resolving all libraries.
98
+ # extend! typically gives the current file precedence when merge conflicts occur,
99
+ # but the expected precedence of library inclusion is the reverse (library 2 should
100
+ # overwrite what library 1 specifies), so reverse the libraries list first.
101
+ config . fetch ( 'libraries' , [ ] ) . reverse . each do |libfn |
102
+ extend! ( load_library ( libfn ) )
103
+ end
151
104
152
- artifact
153
- } ,
154
- @default_flags ,
155
- @image_registries
156
- )
157
- @flavors = parse_flavors ( config . fetch ( 'deploy' , { } ) . fetch ( 'flavors' , { } ) )
158
- @hooks = parse_hooks ( config . fetch ( 'deploy' , { } ) . fetch ( 'hooks' , [ 'default' ] ) )
105
+ # Now that we have a complete list of image registries, validation is now possible.
106
+ # Note that this also populates @valid_image_registries.
107
+ validate_artifacts!
159
108
end
160
109
161
110
def parse_image_registries ( image_registries )
@@ -182,8 +131,8 @@ def map_image_registry(image_registries)
182
131
valid_image_registries
183
132
end
184
133
185
- # .artifacts depends on .default_flags
186
- def parse_artifacts ( artifacts , default_flags , image_registries )
134
+ # .artifacts depends on .default_flags and .image_registries
135
+ def validate_artifacts!
187
136
check_and_err ( artifacts . is_a? ( Array ) , '.artifacts is not an Array' )
188
137
189
138
duplicates = select_duplicates ( artifacts . map { |i | i . fetch ( 'name' ) } )
@@ -192,7 +141,7 @@ def parse_artifacts(artifacts, default_flags, image_registries)
192
141
"Expected .artifacts names to be unique, but found duplicates: #{ duplicates } "
193
142
)
194
143
195
- @valid_image_registries = map_image_registry ( image_registries )
144
+ @valid_image_registries = map_image_registry ( @ image_registries)
196
145
197
146
artifacts . each_with_index { |artifact , index |
198
147
check_and_err (
@@ -218,28 +167,20 @@ def parse_artifacts(artifacts, default_flags, image_registries)
218
167
}
219
168
end
220
169
221
- def parse_default_flags ( default_flags )
222
- check_and_err ( default_flags . is_a? ( Hash ) , '.default_flags is not a Hash' )
223
-
224
- default_flags
170
+ def validate_default_flags
171
+ check_and_err ( @default_flags . is_a? ( Hash ) , '.default_flags is not a Hash' )
225
172
end
226
173
227
- def parse_flavors ( flavors )
228
- check_and_err ( flavors . is_a? ( Hash ) , '.flavors is not a Hash' )
229
-
230
- flavors
174
+ def validate_flavors
175
+ check_and_err ( @flavors . is_a? ( Hash ) , '.flavors is not a Hash' )
231
176
end
232
177
233
- def parse_hooks ( hooks )
234
- check_and_err ( hooks . is_a? ( Array ) , '.hooks is not an Array' )
235
-
236
- hooks
178
+ def validate_hooks
179
+ check_and_err ( @hooks . is_a? ( Array ) , '.hooks is not an Array' )
237
180
end
238
181
239
- def parse_expiration ( expiration )
240
- check_and_err ( expiration . is_a? ( Array ) , '.expiration is not an Array' )
241
-
242
- expiration
182
+ def validate_expiration
183
+ check_and_err ( @expiration . is_a? ( Array ) , '.expiration is not an Array' )
243
184
end
244
185
245
186
# upgrade! converts the config to a YAML string in the format
@@ -250,48 +191,55 @@ def upgrade!
250
191
version = @original_config . fetch ( 'version' , 1 )
251
192
case version
252
193
when 2
253
- config = @original_config
254
- when 1
255
- Logger . info ( 'Upgrading v1 deploy.yml to v2 deploy.yaml' )
256
- config = {
257
- 'version' => 2 ,
258
- 'artifacts' => @artifacts . map { |a |
259
- {
260
- 'name' => a . fetch ( 'name' ) ,
261
- 'image_registry' => a . fetch ( 'image_registry' ) ,
262
- 'flags' => a . fetch ( 'flags' , { } )
263
- }
264
- } ,
265
- 'flavors' => @flavors ,
266
- 'default_flags' => @default_flags ,
267
- 'hooks' => @hooks ,
268
- 'image_registries' => @image_registries . map { |_ , i |
269
- image_registry = {
270
- 'name' => i . name ,
271
- 'driver' => i . driver ,
272
- 'prefix' => i . prefix ,
273
- }
274
-
275
- image_registry [ 'config' ] = i . config if !i . config . nil?
276
-
277
- image_registry
278
- }
279
- }
280
- end
281
-
282
- File . open ( @filename , 'w+' ) { |file | file . write ( config . to_yaml ) }
283
-
284
- # Rename deploy.yml to deploy.yaml, if necessary
285
- dirname = File . dirname ( @filename )
286
- basename = File . basename ( @filename )
287
- if basename == DEPLOY_YML_V1
288
- Logger . info ( 'Renaming deploy.yml => deploy.yaml' )
289
- File . rename ( @filename , "#{ dirname } /#{ DEPLOY_YAML } " )
194
+ # TODO(joshk): Any required updates to v3 or remove this entire method
195
+ true
290
196
end
291
197
end
292
198
293
199
def select_duplicates ( array )
294
200
array . select { |n | array . count ( n ) > 1 } . uniq
295
201
end
202
+
203
+ # Extend this DeployConfigFile with another instance.
204
+ def extend! ( other )
205
+ # Any image_registries entry in |self| should take precedence
206
+ # over any identical key in |other|. The behavior of merge is that
207
+ # the 'other' hash wins.
208
+ @image_registries = other . image_registries . merge ( @image_registries )
209
+
210
+ # Same behavior as above for #default_flags.
211
+ @default_flags = other . default_flags . merge ( @default_flags )
212
+
213
+ # artifacts should be merged by 'name'. In other words, if |self| and |other|
214
+ # specify the same 'name' of a registry, self's config for that registry
215
+ # should win wholesale (no merging of flags.)
216
+ @artifacts = ( @artifacts + other . artifacts ) . uniq { |h | h . fetch ( 'name' ) }
217
+
218
+ # Same behavior as for flags and registries, but the flags within the flavor
219
+ # are in a Hash, so we need a deep merge.
220
+ @flavors = other . flavors . deep_merge ( @flavors )
221
+
222
+ # A break from the preceding merging logic - Dependent hooks have to come
223
+ # first and a given named hook can only be run once. But seriously, you
224
+ # probably don't want to make a library that specifies hooks.
225
+ @hooks = ( other . hooks + @hooks ) . uniq
226
+
227
+ @expiration = ( @expiration + other . expiration ) . uniq { |h | h . fetch ( 'repository' ) }
228
+ end
229
+
230
+ def to_h
231
+ {
232
+ 'image_registries' => @image_registries . values . map ( &:to_h ) ,
233
+ 'default_flags' => @default_flags ,
234
+ 'artifacts' => @artifacts ,
235
+ 'flavors' => @flavors ,
236
+ 'hooks' => @hooks ,
237
+ 'expiration' => @expiration ,
238
+ }
239
+ end
240
+
241
+ def self . deep_merge ( h , other )
242
+
243
+ end
296
244
end
297
245
end
0 commit comments