Class: Apcera::Stager

Inherits:
Object
  • Object
show all
Defined in:
lib/apcera/stager/stager.rb

Constant Summary collapse

PKG_NAME =
"pkg.tar.gz"
UPDATED_PKG_NAME =
"updated.tar.gz"

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ Stager

Returns a new instance of Stager.



8
9
10
11
12
13
14
15
# File 'lib/apcera/stager/stager.rb', line 8

def initialize(options = {})
  # Require stager url. Needed to talk to the Staging Coordinator.
  @stager_url = options[:stager_url] || ENV["STAGER_URL"]
  raise Apcera::Error::StagerURLRequired.new("stager_url required") unless @stager_url

  # Setup the environment, some test items here.
  setup_environment
end

Instance Attribute Details

#app_pathObject

Returns the value of attribute app_path.



3
4
5
# File 'lib/apcera/stager/stager.rb', line 3

def app_path
  @app_path
end

#pkg_pathObject

Returns the value of attribute pkg_path.



3
4
5
# File 'lib/apcera/stager/stager.rb', line 3

def pkg_path
  @pkg_path
end

#root_pathObject

Returns the value of attribute root_path.



3
4
5
# File 'lib/apcera/stager/stager.rb', line 3

def root_path
  @root_path
end

#stager_urlObject

Returns the value of attribute stager_url.



3
4
5
# File 'lib/apcera/stager/stager.rb', line 3

def stager_url
  @stager_url
end

#system_optionsObject

Returns the value of attribute system_options.



3
4
5
# File 'lib/apcera/stager/stager.rb', line 3

def system_options
  @system_options
end

#updated_pkg_pathObject

Returns the value of attribute updated_pkg_path.



3
4
5
# File 'lib/apcera/stager/stager.rb', line 3

def updated_pkg_path
  @updated_pkg_path
end

Instance Method Details

#completeObject

Finish staging, compress your app dir and send to the staging coordinator. Then tell the staging coordinator we are done.



270
271
272
273
# File 'lib/apcera/stager/stager.rb', line 270

def complete
  upload
  done
end

#dependencies_add(type, name) ⇒ Object

Add dependencies to package.



184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
# File 'lib/apcera/stager/stager.rb', line 184

def dependencies_add(type, name)
  exists = self.meta["dependencies"].detect { |dep| dep["type"] == type && dep["name"] == name }
  return false if exists

  response = RestClient.put(stager_meta_url, {
    :resource => "dependencies",
    :action => "add",
    :type => type,
    :name => name
  })

  true
rescue => e
  fail e
end

#dependencies_remove(type, name) ⇒ Object

Delete dependencies from package.



201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'lib/apcera/stager/stager.rb', line 201

def dependencies_remove(type, name)
  exists = self.meta["dependencies"].detect { |dep| dep["type"] == type && dep["name"] == name}
  return false if !exists

  response = RestClient.put(stager_meta_url, {
    :resource => "dependencies",
    :action => "remove",
    :type => type,
    :name => name
  })

  true
rescue => e
  fail e
end

#doneObject

Tell the staging coordinator you are done.



253
254
255
256
257
258
# File 'lib/apcera/stager/stager.rb', line 253

def done
  response = RestClient.post(@stager_url+"/done", {})
  exit0r 0
rescue => e
  fail e
end

#downloadObject

Download a package from the staging coordinator. We use Net::HTTP here because it supports streaming downloads.



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/apcera/stager/stager.rb', line 35

def download
  uri = URI(@stager_url + "/data")

  Net::HTTP.start(uri.host.to_s, uri.port.to_s) do |http|
    request = Net::HTTP::Get.new uri.request_uri

    http.request request do |response|
      if response.code.to_i == 200
        open @pkg_path, 'wb' do |io|
          response.read_body do |chunk|
            io.write chunk
          end
        end
      else
        raise Apcera::Error::DownloadError.new("package download failed.\n")
      end
    end
  end
rescue => e
  fail e
end

#environment_add(key, value) ⇒ Object

Add environment variable to package.



137
138
139
140
141
142
143
144
145
146
# File 'lib/apcera/stager/stager.rb', line 137

def environment_add(key, value)
  response = RestClient.put(stager_meta_url, {
    :resource => "environment",
    :action => "add",
    :key => key,
    :value => value
  })
rescue => e
  fail e
end

#environment_remove(key) ⇒ Object

Delete environment variable from package.



149
150
151
152
153
154
155
156
157
# File 'lib/apcera/stager/stager.rb', line 149

def environment_remove(key)
  response = RestClient.put(stager_meta_url, {
    :resource => "environment",
    :action => "remove",
    :key => key
  })
rescue => e
  fail e
end

#execute(*cmd) ⇒ Object

Execute a command in the shell. We don’t want real commands in tests.



59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/apcera/stager/stager.rb', line 59

def execute(*cmd)
  Bundler.with_clean_env do
    result = system(*cmd, @system_options)
    if !result
      raise Apcera::Error::ExecuteError.new("failed to execute: #{cmd.join(' ')}.\n")
    end

    result
  end
rescue => e
  fail e
end

#execute_app(*cmd) ⇒ Object

Execute a command in the directory your package was extracted to (or where you manually set @app_dir). Useful helper.



74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/apcera/stager/stager.rb', line 74

def execute_app(*cmd)
  raise_app_path_error if @run_path == nil
  Bundler.with_clean_env do
    Dir.chdir(@run_path) do |run_path|
      result = system(*cmd, @system_options)
      if !result
        raise Apcera::Error::ExecuteError.new("failed to execute: #{cmd.join(' ')}.\n")
      end

      result
    end
  end
rescue => e
  fail e
end

#exit0r(code) ⇒ Object

Exit, needed for tests to not quit.



306
307
308
# File 'lib/apcera/stager/stager.rb', line 306

def exit0r(code)
  exit code
end

#extract(location = "") ⇒ Object

Extract the package to a location within staging path, optionally creating the named folder first and extract into it. If a location parameter is given, when upload is run the folder will be uploaded up along with the files. In either case, execute_app will run commands in the location where files were extracted to.



95
96
97
98
99
100
101
102
103
104
105
# File 'lib/apcera/stager/stager.rb', line 95

def extract(location="")
  @app_path=File.join(@root_path, "staging")
  Dir.mkdir(@app_path) unless Dir.exists?(@app_path)

  @run_path = location.empty? ? @app_path : File.join(@app_path, location)
  Dir.mkdir(@run_path) unless Dir.exists?(@run_path)

  execute_app("tar -zxf #{@pkg_path}")
rescue => e
  fail e
end

#fail(error = nil) ⇒ Object

Fail the stager, something went wrong.



296
297
298
299
300
301
302
303
# File 'lib/apcera/stager/stager.rb', line 296

def fail(error = nil)
  output_error "Error: #{error.message}.\n" if error
  RestClient.post(@stager_url+"/failed", {})
rescue => e
  output_error "Error: #{e.message}.\n"
ensure
  exit0r 1
end

#metaObject

Get metadata for the package being staged.



244
245
246
247
248
249
250
# File 'lib/apcera/stager/stager.rb', line 244

def meta
  response = RestClient.get(stager_meta_url)
  return JSON.parse(response.to_s)
rescue => e
  output_error "Error: #{e.message}.\n"
  raise e
end

#output(text) ⇒ Object

Output to stdout



316
317
318
# File 'lib/apcera/stager/stager.rb', line 316

def output(text)
  $stdout.puts text
end

#output_error(text) ⇒ Object

Output to stderr.



311
312
313
# File 'lib/apcera/stager/stager.rb', line 311

def output_error(text)
  $stderr.puts text
end

#provides_add(type, name) ⇒ Object

Add provides to package.



160
161
162
163
164
165
166
167
168
169
# File 'lib/apcera/stager/stager.rb', line 160

def provides_add(type, name)
  response = RestClient.put(stager_meta_url, {
    :resource => "provides",
    :action => "add",
    :type => type,
    :name => name
  })
rescue => e
  fail e
end

#provides_remove(type, name) ⇒ Object

Delete provides from package.



172
173
174
175
176
177
178
179
180
181
# File 'lib/apcera/stager/stager.rb', line 172

def provides_remove(type, name)
  response = RestClient.put(stager_meta_url, {
    :resource => "provides",
    :action => "remove",
    :type => type,
    :name => name
  })
rescue => e
  fail e
end

#relaunchObject

Tell the staging coordinator you need to relaunch.



261
262
263
264
265
266
# File 'lib/apcera/stager/stager.rb', line 261

def relaunch
  response = RestClient.post(@stager_url+"/relaunch", {})
  exit0r 0
rescue => e
  fail e
end

#setup_chrootObject

Setup /stagerfs chroot environment so it is ready to run commands from pulled in dependencies. This does the following:

  • Setup working resolv.conf

  • Bind mounts /proc to /stagerfs/proc

  • Recursively bind mounts /dev to /stagerfs/dev



22
23
24
25
26
27
28
29
30
31
# File 'lib/apcera/stager/stager.rb', line 22

def setup_chroot
  execute("sudo mkdir -p /stagerfs/etc")
  execute("sudo cp /etc/resolv.conf /stagerfs/etc/resolv.conf")

  execute("sudo mkdir -p /stagerfs/proc")
  execute("sudo mount --bind /proc /stagerfs/proc")

  execute("sudo mkdir -p /stagerfs/dev")
  execute("sudo mount --rbind /dev /stagerfs/dev")
end

#snapshotObject

Snapshot the stager filesystem for app.



130
131
132
133
134
# File 'lib/apcera/stager/stager.rb', line 130

def snapshot
  response = RestClient.post(@stager_url+"/snapshot", {})
rescue => e
  fail e
end

#start_commandObject

Returns the start command for the package.



276
277
278
# File 'lib/apcera/stager/stager.rb', line 276

def start_command
  self.meta["environment"]["START_COMMAND"]
end

#start_command=(val) ⇒ Object

Easily set the start command.



281
282
283
# File 'lib/apcera/stager/stager.rb', line 281

def start_command=(val)
  self.environment_add("START_COMMAND", val)
end

#start_pathObject

Returns the start path for the package.



286
287
288
# File 'lib/apcera/stager/stager.rb', line 286

def start_path
  self.meta["environment"]["START_PATH"]
end

#start_path=(val) ⇒ Object

Easily set the start path.



291
292
293
# File 'lib/apcera/stager/stager.rb', line 291

def start_path=(val)
  self.environment_add("START_PATH", val)
end

#templates_add(path, left_delimiter = "{{", right_delimiter = "}}") ⇒ Object

Add template to package.



218
219
220
221
222
223
224
225
226
227
228
# File 'lib/apcera/stager/stager.rb', line 218

def templates_add(path, left_delimiter = "{{", right_delimiter = "}}")
  response = RestClient.put(stager_meta_url, {
    :resource => "templates",
    :action => "add",
    :path => path,
    :left_delimiter => left_delimiter,
    :right_delimiter => right_delimiter
  })
rescue => e
  fail e
end

#templates_remove(path, left_delimiter = "{{", right_delimiter = "}}") ⇒ Object

Delete template from package.



231
232
233
234
235
236
237
238
239
240
241
# File 'lib/apcera/stager/stager.rb', line 231

def templates_remove(path, left_delimiter = "{{", right_delimiter = "}}")
  response = RestClient.put(stager_meta_url, {
    :resource => "templates",
    :action => "remove",
    :path => path,
    :left_delimiter => left_delimiter,
    :right_delimiter => right_delimiter
  })
rescue => e
  fail e
end

#uploadObject

Upload the new package to the staging coordinator. If we have an app extracted we send that to the staging coordinator. If no app was ever extracted it is a noop.



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/apcera/stager/stager.rb', line 110

def upload
  if @app_path == nil
    unless File.exist?(@pkg_path)
      download
    end

    upload_file(@pkg_path)
  else
    # Use execute instead of execute_app so that if the user provided a dir
    # to extract into it results in the uploaded package being wrapped in
    # that directory.
    execute("cd #{@app_path} && tar czf #{@updated_pkg_path} ./*")

    upload_file(@updated_pkg_path)
  end
rescue => e
  fail e
end