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.



260
261
262
263
# File 'lib/apcera/stager/stager.rb', line 260

def complete
  upload
  done
end

#dependencies_add(type, name) ⇒ Object

Add dependencies to package.



174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/apcera/stager/stager.rb', line 174

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.



191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/apcera/stager/stager.rb', line 191

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.



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

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.



127
128
129
130
131
132
133
134
135
136
# File 'lib/apcera/stager/stager.rb', line 127

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.



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

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}.\n")
    end

    result
  end
rescue => e
  fail e
end

#execute_app(cmd) ⇒ Object

Execute a command in the app dir. Useful helper.



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

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

      result
    end
  end
rescue => e
  fail e
end

#exit0r(code) ⇒ Object

Exit, needed for tests to not quit.



296
297
298
# File 'lib/apcera/stager/stager.rb', line 296

def exit0r(code)
  exit code
end

#extract(location) ⇒ Object

Extract the package to a given location.



90
91
92
93
94
95
96
97
# File 'lib/apcera/stager/stager.rb', line 90

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

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

#fail(error = nil) ⇒ Object

Fail the stager, something went wrong.



286
287
288
289
290
291
292
293
# File 'lib/apcera/stager/stager.rb', line 286

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.



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

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



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

def output(text)
  $stdout.puts text
end

#output_error(text) ⇒ Object

Output to stderr



301
302
303
# File 'lib/apcera/stager/stager.rb', line 301

def output_error(text)
  $stderr.puts text
end

#provides_add(type, name) ⇒ Object

Add provides to package.



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

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.



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

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.



251
252
253
254
255
256
# File 'lib/apcera/stager/stager.rb', line 251

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



120
121
122
123
124
# File 'lib/apcera/stager/stager.rb', line 120

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

#start_commandObject

Returns the start command for the package.



266
267
268
# File 'lib/apcera/stager/stager.rb', line 266

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

#start_command=(val) ⇒ Object

Easily set the start command



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

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

#start_pathObject

Returns the start path for the package.



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

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

#start_path=(val) ⇒ Object

Easily set the start path



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

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

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

Add template to package.



208
209
210
211
212
213
214
215
216
217
218
# File 'lib/apcera/stager/stager.rb', line 208

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.



221
222
223
224
225
226
227
228
229
230
231
# File 'lib/apcera/stager/stager.rb', line 221

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 we just upload the unmodified app.



102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/apcera/stager/stager.rb', line 102

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

    upload_file(@pkg_path)
  else
    app_dir = Pathname.new(@app_path).relative_path_from(Pathname.new(@root_path)).to_s
    execute_app("cd #{app_path}/.. && tar czf #{@updated_pkg_path} #{app_dir}")

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