vanagon 0.9.3 → 0.10.0

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