vanagon 0.9.3 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -79,7 +79,7 @@ class Vanagon
79
79
  # @raise [RuntimeError] an exception is raised if there is no known extraction method for @extension
80
80
  def extract(tar = "tar") # rubocop:disable Metrics/AbcSize
81
81
  # Extension does not appear to be an archive, so "extract" is a no-op
82
- return ':' unless archive_extensions.include?(extension)
82
+ return ': nothing to extract' unless archive_extensions.include?(extension)
83
83
 
84
84
  case decompressor
85
85
  when "7z"
@@ -0,0 +1,194 @@
1
+ require 'forwardable'
2
+
3
+ class Vanagon
4
+ # Environment is a validating wrapper around a delegated Hash,
5
+ # analogous to Ruby's built in accessor Env. It's intended to be
6
+ # used for defining multiple Environments, which can be used and
7
+ # manipulated the same way a bare Hash would be. We're delegating
8
+ # instead of subclassing because subclassing from Ruby Core is
9
+ # inviting calamity -- that stuff is written in C and may not
10
+ # correspond to assumptions you could safely make about Ruby.
11
+ class Environment
12
+ extend Forwardable
13
+ # @!method []
14
+ # @see Hash#[]
15
+ # @!method keys
16
+ # @see Hash#keys
17
+ # @!method values
18
+ # @see Hash#values
19
+ # @!method empty?
20
+ # @see Hash#empty?
21
+ def_delegators :@data, :[], :keys, :values, :empty?
22
+
23
+ # @!method each
24
+ # @see Hash#each
25
+ # @!method each_pair
26
+ # @see Hash#each_pair
27
+ def_delegators :@data, :each, :each_pair, :each_key, :each_value
28
+ def_delegators :@data, :each_with_index, :each_with_object
29
+ def_delegators :@data, :map, :flat_map
30
+
31
+ # @!method delete
32
+ # @see Hash#delete
33
+ # @!method delete_if
34
+ # @see Hash#delete_if
35
+ def_delegators :@data, :delete, :delete_if
36
+
37
+ # @!method to_h
38
+ # @see Hash#to_h
39
+ # @!method to_hash
40
+ # @see Hash#to_h
41
+ def_delegators :@data, :to_h, :to_hash
42
+
43
+ # Create a new Environment
44
+ # @return [Vanagon::Environment] a new Environment, with no defined env. vars.
45
+ def initialize
46
+ @data = {}
47
+ end
48
+
49
+ # Associates the value given by value with the key given by key. Keys must
50
+ # be strings, and should conform to the Open Group's guidelines for portable
51
+ # shell variable names:
52
+ # Environment variable names used by the utilities in the Shell and
53
+ # Utilities volume of IEEE Std 1003.1-2001 consist solely of uppercase
54
+ # letters, digits, and the '_' (underscore) from the characters defined
55
+ # in Portable Character Set and do not begin with a digit.
56
+ #
57
+ # Values must be Strings or Integers, and will be stored precisely as given,
58
+ # so any escaped characters, single or double quotes, or whitespace will be
59
+ # preserved exactly as passed during assignment.
60
+ #
61
+ # @param key [String]
62
+ # @param value [String, Integer]
63
+ # @raise [ArgumentError] if key is not a String, or if value is not a
64
+ # String or an Integer
65
+ def []=(key, value)
66
+ @data.update({ validate_key(key) => validate_value(value) })
67
+ end
68
+
69
+ # Returns a new Environment containing the contents of other_env and the
70
+ # contents of env.
71
+ # @param other_env [Environment]
72
+ # @example Merge two Environments
73
+ # >> local = Vanagon::Environment.new
74
+ # => #<Vanagon::Environment:0x007fc54d913f38 @data={}>
75
+ # >> global = Vanagon::Environment.new
76
+ # => #<Vanagon::Environment:0x007fc54b06da70 @data={}>
77
+ # >> local['PATH'] = '/usr/local/bin:/usr/bin:/bin'
78
+ # >> global['CC'] = 'ccache gcc'
79
+ # >> local.merge global
80
+ # => #<Vanagon::Environment:0x007fc54b0a72e8 @data={"PATH"=>"/usr/local/bin:/usr/bin:/bin", "CC"=>"ccache gcc"}>
81
+ def merge(other_env)
82
+ env_copy = self.dup
83
+ other_env.each_pair do |k, v|
84
+ env_copy[k] = v
85
+ end
86
+ env_copy
87
+ end
88
+ alias update merge
89
+
90
+ # Adds the contents of other_env to env.
91
+ # @param other_env [Environment]
92
+ # @example Merge two Environments
93
+ # >> local = Vanagon::Environment.new
94
+ # => #<Vanagon::Environment:0x007f8c68933b08 @data={}>
95
+ # >> global = Vanagon::Environment.new
96
+ # => #<Vanagon::Environment:0x007f8c644e5640 @data={}>
97
+ # >> local['PATH'] = '/usr/local/bin:/usr/bin:/bin'
98
+ # >> global['CC'] = 'ccache gcc'
99
+ # >> local.merge! global
100
+ # => #<Vanagon::Environment:0x007f8c68933b08 @data={"PATH"=>"/usr/local/bin:/usr/bin:/bin", "CC"=>"ccache gcc"}>
101
+ def merge!(other_env)
102
+ @data = merge(other_env).instance_variable_get(:@data)
103
+ end
104
+
105
+ # Converts env to an array of "#{key}=#{value}" strings, suitable for
106
+ # joining into a command.
107
+ # @example Convert to an Array
108
+ # >> local = Vanagon::Environment.new
109
+ # => #<Vanagon::Environment:0x007f8c68991258 @data={}>
110
+ # >> local['PATH'] = '/usr/local/bin:/usr/bin:/bin'
111
+ # => "/usr/local/bin:/usr/bin:/bin"
112
+ # >> local['CC'] = 'clang'
113
+ # => "clang"
114
+ # >> local.to_a
115
+ # => ["PATH=\"/usr/local/bin:/usr/bin:/bin\"", "CC=\"clang\""]
116
+ def to_a(delim = "=")
117
+ @data.map { |k, v| %(#{k}#{delim}#{v}) }
118
+ end
119
+ alias to_array to_a
120
+
121
+ # Converts env to a string by concatenating together all key-value pairs
122
+ # with a single space.
123
+ # @example Convert to an Array
124
+ # >> local = Vanagon::Environment.new
125
+ # => #<Vanagon::Environment:0x007f8c68014358 @data={}>
126
+ # >> local['PATH'] = '/usr/local/bin:/usr/bin:/bin'
127
+ # >> local['CC'] = 'clang'
128
+ # >> puts local
129
+ # PATH=/usr/local/bin:/usr/bin:/bin
130
+ # >>
131
+ def to_s
132
+ to_a.join("\s")
133
+ end
134
+ alias to_string to_s
135
+
136
+ def sanitize_value(str)
137
+ escaped_variables = str.scan(/\$\$([\w]+)/).flatten
138
+ return str if escaped_variables.empty?
139
+
140
+ warning = [%(Value "#{str}" looks like it's escaping one or more strings for shell interpolation.)]
141
+ escaped_variables.each { |v| warning.push "\t$$#{v} (will be coerced to $(#{v})" }
142
+ warning.push <<-eos.undent
143
+ All environment variables will be resolved by Make; these variables will
144
+ be unesecaped for now, but you should update your projects parameters.
145
+ eos
146
+
147
+ warn warning.join("\n")
148
+ str.gsub(/\$\$([\w]+)/, '$(\1)')
149
+ end
150
+ private :sanitize_value
151
+
152
+ # Validate that a key is a String, that it does not contain invalid
153
+ # characters, and that it does not begin with a digit
154
+ # @param key [String]
155
+ # @raise [ArgumentError] if key is not a String, if key contains invalid
156
+ # characters, or if key begins with a digit
157
+ def validate_key(str)
158
+ unless str.is_a?(String)
159
+ raise ArgumentError,
160
+ 'environment variable Name must be a String'
161
+ end
162
+
163
+ if str[0] =~ /\d/
164
+ raise ArgumentError,
165
+ 'environment variable Name cannot begin with a digit'
166
+ end
167
+
168
+ invalid_chars = str.scan(/[^\w]/).uniq
169
+ unless invalid_chars.empty?
170
+ raise ArgumentError,
171
+ 'environment variable Name contains invalid characters: ' +
172
+ invalid_chars.map { |char| %("#{char}") }.join(', ')
173
+ end
174
+ str
175
+ end
176
+ private :validate_key
177
+
178
+ # Validate that str is a String or an Integer, and that the value
179
+ # of str cannot be split into more than a single String by #shellsplit.
180
+ # @param value [String, Integer]
181
+ # @raise [ArgumentError] if key is not a String or an Integer
182
+ def validate_value(str)
183
+ unless str.is_a?(String) || str.is_a?(Integer)
184
+ raise ArgumentError,
185
+ 'Value must be a String or an Integer'
186
+ end
187
+
188
+ # sanitize the value, which should look for any Shell escaped
189
+ # variable names inside of the value.
190
+ str.is_a?(String) ? sanitize_value(str) : str
191
+ end
192
+ private :validate_value
193
+ end
194
+ end
@@ -1,3 +1,4 @@
1
+ require 'vanagon/environment'
1
2
  require 'vanagon/platform/dsl'
2
3
 
3
4
  class Vanagon
@@ -39,11 +40,18 @@ class Vanagon
39
40
 
40
41
  # Hold a string containing the values that a given platform
41
42
  # should use when a Makefile is run - resolves to the CFLAGS
42
- # and LDFLAGS variables. Should also be extended to support
43
- # CXXFLAGS and CPPFLAGS ASAP.
43
+ # and LDFLAGS variables. This should be changed to take advantage
44
+ # of the Environment, so that we can better leverage Make's
45
+ # Implicit Variables:
46
+ # https://www.gnu.org/software/make/manual/html_node/Implicit-Variables.html
47
+ # It should also be extended to support CXXFLAGS and CPPFLAGS ASAP.
44
48
  attr_accessor :cflags
45
49
  attr_accessor :ldflags
46
50
 
51
+ # The overall Environment that a given platform
52
+ # should pass to each component
53
+ attr_accessor :environment
54
+
47
55
  # Stores an Array of OpenStructs, each representing a complete
48
56
  # command to be run to install external the needed toolchains
49
57
  # and build dependencies for a given target platform.
@@ -191,6 +199,9 @@ class Vanagon
191
199
  @os_version = os_version
192
200
  @architecture = architecture
193
201
  @ssh_port = 22
202
+ # Environments are like Hashes but with specific constraints
203
+ # around their keys and values.
204
+ @environment = Vanagon::Environment.new
194
205
  @provisioning = []
195
206
  @install ||= "install"
196
207
  @target_user ||= "root"
@@ -18,16 +18,16 @@ class Vanagon
18
18
  #
19
19
  # @param name [String] name of the platform
20
20
  # @return [Vanagon::Platform::DSL] A DSL object to describe the {Vanagon::Platform}
21
- def initialize(name)
22
- @name = name
21
+ def initialize(platform_name)
22
+ @name = platform_name
23
23
  end
24
24
 
25
25
  # Primary way of interacting with the DSL. Also a simple factory to get the right platform object.
26
26
  #
27
27
  # @param name [String] name of the platform
28
28
  # @param block [Proc] DSL definition of the platform to call
29
- def platform(name, &block)
30
- @platform = case name
29
+ def platform(platform_name, &block) # rubocop:disable Metrics/AbcSize
30
+ @platform = case platform_name
31
31
  when /^aix-/
32
32
  Vanagon::Platform::RPM::AIX.new(@name)
33
33
  when /^(cisco-wrlinux|el|fedora)-/
@@ -51,6 +51,7 @@ class Vanagon
51
51
  end
52
52
 
53
53
  yield(self)
54
+ environment 'VANAGON_PLATFORM', platform_name.tr('-', '.')
54
55
  @platform
55
56
  end
56
57
 
@@ -83,6 +84,13 @@ class Vanagon
83
84
  method_name.to_s.start_with?('get_') || super
84
85
  end
85
86
 
87
+ # Adds an arbitrary environment variable to a Platform, which will be
88
+ # merged with any environment variables defined by the Project into the
89
+ # rendered Makefile
90
+ def environment(key, value)
91
+ @platform.environment[key] = value
92
+ end
93
+
86
94
  # Set the path to make for the platform
87
95
  #
88
96
  # @param make_cmd [String] Full path to the make command for the platform
@@ -127,9 +135,10 @@ class Vanagon
127
135
 
128
136
  # Set the cross_compiled flag for the platform
129
137
  #
130
- # @param cxx_flag [Boolean] True if this is a cross-compiled platform
131
- def cross_compiled(cxx_flag)
132
- @platform.cross_compiled = cxx_flag
138
+ # @param xcc [Boolean] True if this is a cross-compiled platform
139
+ def cross_compiled(xcc_flag)
140
+ @platform.cross_compiled = !!xcc_flag
141
+ environment 'VANAGON_PLATFORM_XCC', @platform.cross_compiled.to_s
133
142
  end
134
143
 
135
144
  # define an explicit Dist for the platform (most likely used for RPM platforms)
@@ -219,38 +228,38 @@ class Vanagon
219
228
  # Set the name of this platform as the vm pooler expects it
220
229
  #
221
230
  # @param name [String] name of the target template to use from the vmpooler
222
- def vmpooler_template(name)
223
- @platform.vmpooler_template = name
231
+ def vmpooler_template(template_name)
232
+ @platform.vmpooler_template = template_name
224
233
  end
225
234
 
226
235
  # Set the name of this platform as the vm pooler expects it
227
236
  #
228
237
  # @param name [String] name that the pooler uses for this platform
229
238
  # @deprecated Please use vmpooler_template instead, this will be removed in a future vanagon release.
230
- def vcloud_name(name)
239
+ def vcloud_name(cloud_name)
231
240
  warn "vcloud_name is a deprecated platform DSL method, and will be removed in a future vanagon release. Please use vmpooler_template instead."
232
- self.vmpooler_template(name)
241
+ self.vmpooler_template(cloud_name)
233
242
  end
234
243
 
235
244
  # Set the name of this platform as always-be-scheduling (ABS) expects it
236
245
  #
237
246
  # @param name [String] name of the platform to request from always-be-scheduling
238
- def abs_resource_name(name)
239
- @platform.abs_resource_name = name
247
+ def abs_resource_name(resource_name)
248
+ @platform.abs_resource_name = resource_name
240
249
  end
241
250
 
242
251
  # Set the name of the docker image to use
243
252
  #
244
253
  # @param name [String] name of the docker image to use
245
- def docker_image(name)
246
- @platform.docker_image = name
254
+ def docker_image(image_name)
255
+ @platform.docker_image = image_name
247
256
  end
248
257
 
249
258
  # Set the ami for the platform to use
250
259
  #
251
260
  # @param ami [String] the ami id used.
252
- def aws_ami(ami)
253
- @platform.aws_ami = ami
261
+ def aws_ami(ami_name)
262
+ @platform.aws_ami = ami_name
254
263
  end
255
264
 
256
265
  # Set the user data used in AWS to do setup. Like cloud-config
@@ -307,6 +316,7 @@ class Vanagon
307
316
  # @param user[String] a user string to login with.
308
317
  def target_user(user = "root")
309
318
  @platform.target_user = user
319
+ environment 'VANAGON_PLATFORM_USER', user
310
320
  end
311
321
 
312
322
  # Set the target ip address or hostname to start build
@@ -326,9 +336,10 @@ class Vanagon
326
336
 
327
337
  # Set any codename this platform may have (debian for example)
328
338
  #
329
- # @param name [String] codename for this platform (squeeze for example)
330
- def codename(name)
331
- @platform.codename = name
339
+ # @param codename [String] codename for this platform (squeeze for example)
340
+ def codename(codename)
341
+ @platform.codename = codename
342
+ environment 'VANAGON_PLATFORM_CODENAME', codename
332
343
  end
333
344
 
334
345
  def output_dir(directory)
@@ -1,4 +1,5 @@
1
1
  require 'vanagon/component'
2
+ require 'vanagon/environment'
2
3
  require 'vanagon/platform'
3
4
  require 'vanagon/project/dsl'
4
5
  require 'vanagon/utilities'
@@ -7,11 +8,70 @@ require 'ostruct'
7
8
  class Vanagon
8
9
  class Project
9
10
  include Vanagon::Utilities
10
- attr_accessor :components, :settings, :platform, :configdir, :name
11
- attr_accessor :version, :directories, :license, :description, :vendor
12
- attr_accessor :homepage, :requires, :user, :repo, :noarch, :identifier
13
- attr_accessor :cleanup, :version_file, :release, :replaces, :provides
14
- attr_accessor :conflicts, :bill_of_materials, :retry_count, :timeout
11
+
12
+ # Numerous attributes related to the artifact that a given
13
+ # Vanagon project will produce
14
+ attr_accessor :name
15
+ attr_accessor :version
16
+ attr_accessor :release
17
+ attr_accessor :license
18
+ attr_accessor :homepage
19
+ attr_accessor :vendor
20
+ attr_accessor :description
21
+ attr_accessor :components
22
+ attr_accessor :conflicts
23
+ attr_accessor :requires
24
+ attr_accessor :replaces
25
+ attr_accessor :provides
26
+
27
+ # Platform's abstraction is kind of backwards -- we should refactor
28
+ # how this works, and make it possible for Vanagon to default to all
29
+ # defined platforms if nothing is specified.
30
+ attr_accessor :platform
31
+ attr_accessor :configdir
32
+ attr_accessor :retry_count
33
+ attr_accessor :timeout
34
+
35
+ # Store any target directories that should be packed up into
36
+ # the resultant artifact produced by a given Vanagon project.
37
+ attr_accessor :directories
38
+
39
+ # This will define any new users that a project should create
40
+ attr_accessor :user
41
+
42
+ # This is entirely too Puppet centric, and should be refactored out
43
+ # !depreciate
44
+ # !refactor
45
+ attr_accessor :repo
46
+
47
+ # Mark a project as being architecture independent
48
+ attr_accessor :noarch
49
+
50
+ # This is macOS specific, and defines the Identifier that macOS should
51
+ # use when it builds a .pkg
52
+ attr_accessor :identifier
53
+
54
+ # Stores whether or not a project should cleanup as it builds
55
+ # because the target builder is space-constrained
56
+ attr_accessor :cleanup
57
+
58
+ # Stores whether or not Vanagon should write the project's version
59
+ # out into a file inside the package -- do we really need this?
60
+ # !depreciate
61
+ # !refactor
62
+ attr_accessor :version_file
63
+
64
+ # Stores the location for the bill-of-materials (a receipt of all
65
+ # files written during) project package assembly
66
+ attr_accessor :bill_of_materials
67
+
68
+ # Stores individual settings related to a given Vanagon project,
69
+ # not necessarily the artifact that the project produces
70
+ attr_accessor :settings
71
+
72
+ # The overall Environment that a given Vanagon
73
+ # project should pass to each platform
74
+ attr_accessor :environment
15
75
 
16
76
  # Loads a given project from the configdir
17
77
  #
@@ -46,6 +106,9 @@ class Vanagon
46
106
  @requires = []
47
107
  @directories = []
48
108
  @settings = {}
109
+ # Environments are like Hashes but with specific constraints
110
+ # around their keys and values.
111
+ @environment = Vanagon::Environment.new
49
112
  @platform = platform
50
113
  @release = "1"
51
114
  @replaces = []
@@ -65,6 +128,17 @@ class Vanagon
65
128
  @settings.key?(method_name) || super
66
129
  end
67
130
 
131
+ # Merge the platform's Environment into the project's Environment
132
+ # and return the result. This will produce the top-level Environment
133
+ # in the Makefile, that all components (and their Make targets)
134
+ # will inherit from.
135
+ #
136
+ # @return [Environment] a new Environment, constructed from merging
137
+ # @platform's Environment with the project's environment.
138
+ def merged_environment
139
+ environment.merge(@platform.environment)
140
+ end
141
+
68
142
  # Collects all sources and patches into the provided workdir
69
143
  #
70
144
  # @param workdir [String] directory to stage sources into
@@ -195,7 +269,7 @@ class Vanagon
195
269
  def get_preinstall_actions(pkg_state)
196
270
  scripts = @components.map(&:preinstall_actions).flatten.compact.select { |s| s.pkg_state.include? pkg_state }.map(&:scripts)
197
271
  if scripts.empty?
198
- return ':'
272
+ return ': no preinstall scripts provided'
199
273
  else
200
274
  return scripts.join("\n")
201
275
  end
@@ -212,7 +286,7 @@ class Vanagon
212
286
  def get_postinstall_actions(pkg_state)
213
287
  scripts = @components.map(&:postinstall_actions).flatten.compact.select { |s| s.pkg_state.include? pkg_state }.map(&:scripts)
214
288
  if scripts.empty?
215
- return ':'
289
+ return ': no postinstall scripts provided'
216
290
  else
217
291
  return scripts.join("\n")
218
292
  end
@@ -228,7 +302,7 @@ class Vanagon
228
302
  def get_preremove_actions(pkg_state)
229
303
  scripts = @components.map(&:preremove_actions).flatten.compact.select { |s| s.pkg_state.include? pkg_state }.map(&:scripts)
230
304
  if scripts.empty?
231
- return ':'
305
+ return ': no preremove scripts provided'
232
306
  else
233
307
  return scripts.join("\n")
234
308
  end
@@ -244,7 +318,7 @@ class Vanagon
244
318
  def get_postremove_actions(pkg_state)
245
319
  scripts = @components.map(&:postremove_actions).flatten.compact.select { |s| s.pkg_state.include? pkg_state }.map(&:scripts)
246
320
  if scripts.empty?
247
- return ':'
321
+ return ': no postremove scripts provided'
248
322
  else
249
323
  return scripts.join("\n")
250
324
  end
@@ -287,6 +361,14 @@ class Vanagon
287
361
  ret_dirs
288
362
  end
289
363
 
364
+ # This originally lived in the Makefile.erb template, but it's pretty
365
+ # domain-inspecific and we should try to minimize assignment
366
+ # inside an ERB template
367
+ # @return [Array] all of the paths produced by #get_directories
368
+ def dirnames
369
+ get_directories.map(&:path)
370
+ end
371
+
290
372
  # Get any services registered by components in the project
291
373
  #
292
374
  # @return [Array] the services provided by components in the project
@@ -380,6 +462,7 @@ class Vanagon
380
462
  #
381
463
  # @param workdir [String] workdir to put the packaging files into
382
464
  def generate_packaging_artifacts(workdir)
465
+ FileUtils.install File.join(VANAGON_ROOT, "resources", "metrics", "profiling_shell.sh"), File.join(workdir, "profiling_shell.sh"), :mode => 0775
383
466
  @platform.generate_packaging_artifacts(workdir, @name, binding)
384
467
  end
385
468
  end