apcera 0.1.6.2 → 0.1.6.3

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a10688de5010633265e3d0636240ce3491c842fd
4
- data.tar.gz: 272a13cdb43fb0e60e061acb60285fbb4b3f4dbd
3
+ metadata.gz: e504cd0ce27ad6fb761226d1d57a6b4a6e31c2a5
4
+ data.tar.gz: 61225f96f94e4a97366f74dd5952f02d4b30ccdd
5
5
  SHA512:
6
- metadata.gz: 736bd13a82d03755278bd61e056abe71659e0585e2d3d963f8e6dccbe80cc09bb9fb9b76d84bec9bbb40b773e2cfc86806c83604e603dd384dd8c2396f2c0d66
7
- data.tar.gz: 23135e53927acd53817ad3c23b3ad8dd5f50bddd33f9f7db9e72b3e03805b87d4f7519d2bc3543d5f63cf004a46b86ee5c85e4b1b747c0361f990ab267c4b315
6
+ metadata.gz: 52a6cd2d72bf0fcd07c8e18eaf9cf6248724e4076d9f4e8d3ddfab24a25da9ba4d50224cfac7cd540b3bcda4bad742f6885d1ff4214d8a4c8a99bd3e20b2a19b
7
+ data.tar.gz: a30bb99ab6c4ee76af2d12f631dec2d4ada84d41ef9dbcfb046685fb28fdbd73a2cfe48022443e46f84dc90fbfba001afa082c6223fb99f7c87e5e647116e756
@@ -1,3 +1,3 @@
1
1
  module Apcera
2
- VERSION = "0.1.6.2"
2
+ VERSION = "0.1.6.3"
3
3
  end
@@ -0,0 +1,1077 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'apcera'
4
+ require 'rubygems'
5
+ require 'json'
6
+ require 'rest-client'
7
+ require 'securerandom'
8
+ require 'open-uri'
9
+ require 'yaml'
10
+ require 'digest/sha1'
11
+ require 'getoptlong'
12
+ require 'base64'
13
+
14
+ class String
15
+ def blank?
16
+ nil? || empty?
17
+ end
18
+
19
+ def nonblank?
20
+ !nil? && !empty?
21
+ end
22
+ end
23
+
24
+ class ApceraAuth
25
+
26
+ # These are the bare minimum
27
+ #
28
+ attr_reader :target
29
+ attr_reader :bearer
30
+
31
+ # These are calculated from the above
32
+ #
33
+ attr_reader :token
34
+ attr_reader :token_prefix
35
+ attr_reader :domain
36
+ attr_reader :scheme
37
+ attr_reader :api_host
38
+ attr_reader :auth_host
39
+ attr_reader :base_api_url
40
+ attr_reader :base_auth_url
41
+
42
+ # This is situational
43
+ #
44
+ attr_reader :cache_file_path
45
+
46
+ def initialize(target = "", bearer = "")
47
+ # If a target or bearer is passed in, use that, otherwise, try to figure it out
48
+ #
49
+ cache_credentials = false
50
+
51
+ if (target.nonblank? || bearer.nonblank?)
52
+ @target = target
53
+ @bearer = bearer
54
+ else
55
+ cache_credentials = true
56
+
57
+ @cache_file_path = File.expand_path("~/.apcera_api")
58
+
59
+ if !File.writable?(File.expand_path("~/"))
60
+ puts "Cache File #{@cache_file_path} not writable, falling back to tmp"
61
+ @cache_file_path = "/tmp/.apcera_api"
62
+ end
63
+
64
+ cached_fields = {}
65
+ if (File.exists?(@cache_file_path))
66
+ cached_fields = JSON.parse(File.read(@cache_file_path))
67
+ end
68
+
69
+ # First, get the target. If it isn't in the cache or env, prompt for it
70
+ #
71
+ if (!cached_fields.has_key?("target") || cached_fields["target"] == "")
72
+
73
+ default = "http://demo.proveapcera.io"
74
+
75
+ # This can also be set in the environment
76
+ #
77
+ if ENV.key?("APC_TARGET")
78
+ puts "WARNING: Using APC_TARGET from ENV"
79
+ value = ENV["APC_TARGET"]
80
+ else
81
+ value = prompt_for_value("Enter the domain name for the cluster [#{default}] : ").delete!("\n")
82
+ end
83
+
84
+ if value == ""
85
+ value = default
86
+ end
87
+ cached_fields["target"] = value
88
+ end
89
+
90
+ # Just need to make sure that these are set when done
91
+ #
92
+ @target = cached_fields["target"]
93
+ end
94
+
95
+ # Build the paths
96
+ #
97
+ parts = @target.split(/[:\/]/)
98
+
99
+ @scheme = parts.first
100
+ @domain = parts.last
101
+
102
+ @api_host = "api.#{@domain}"
103
+ @auth_host = "auth.#{@domain}"
104
+ @base_api_url = "#{scheme}://#{@api_host}"
105
+ @base_auth_url = "#{scheme}://#{@auth_host}"
106
+
107
+ if (target.nonblank? || bearer.nonblank?)
108
+ @bearer = bearer
109
+ else
110
+ # We need to find it.
111
+ #
112
+ if (ENV.key?("APC_REFRESH_TOKEN"))
113
+ puts "WARNING: Using refresh token from environment"
114
+ cached_fields["refresh_token"] = ENV["APC_REFRESH_TOKEN"]
115
+ end
116
+
117
+ if (!cached_fields.has_key?("refresh_token") || cached_fields["refresh_token"] == "")
118
+ response = RestClient.get("#{@base_auth_url}/v1/oauth2/device/google/getcode")
119
+ code = JSON.parse(response)
120
+
121
+ ApceraApiHelper.notice "go to \n\n#{code['verification_url']}\n\n and enter code #{code['user_code']}\n\n"
122
+
123
+ # This stuff only works on the mac
124
+ #
125
+ system("echo #{code['user_code']} | pbcopy")
126
+ system("open #{code['verification_url']}")
127
+
128
+ value = prompt_for_value "Press Y when completed: "
129
+
130
+ if value.delete!("\n").casecmp("Y") != 0
131
+ ApceraApiHelper.notice "Error, giving up."
132
+ exit
133
+ end
134
+
135
+ device_code = code['device_code']
136
+
137
+ redeemed_url = "#{@base_auth_url}/v1/oauth2/device/google/redeemed"
138
+
139
+ obj = {:device_code => device_code}
140
+
141
+ refresh_token_wrapper = JSON.parse(RestClient.post(redeemed_url, obj.to_json, {:content_type => :json}))
142
+ cached_fields["refresh_token"] = refresh_token_wrapper["refresh_token"]
143
+ end
144
+
145
+ # If the token isn't there, or is expired, refresh it
146
+ #
147
+ if !cached_fields.has_key?("token") || cached_fields["token"] == "" || Time.parse(cached_fields["expires"]) < Time.now
148
+ refresh_url = "#{@base_auth_url}/v1/oauth2/device/google/refresh"
149
+
150
+ refresh_token_wrapper = {:refresh_token => cached_fields["refresh_token"], :token_type => "GOOGLE_REFRESH" }
151
+ refresh_resp = RestClient.post(refresh_url, refresh_token_wrapper.to_json, {:content_type => :json})
152
+ refreshed_token = JSON.parse(refresh_resp)
153
+
154
+ cached_fields["token"] = "Bearer #{refreshed_token['access_token']}"
155
+ cached_fields["expires"] = Time.now + refreshed_token["expires_in"].to_i
156
+ end
157
+ @bearer = cached_fields["token"]
158
+ end
159
+
160
+ @token_prefix = @bearer.split.first
161
+ @token = @bearer.split.last
162
+
163
+ if (cache_credentials)
164
+ File.write(cache_file_path, JSON.pretty_generate(cached_fields))
165
+ end
166
+
167
+ end
168
+
169
+ def prompt_for_value(*args)
170
+ print(*args)
171
+ gets
172
+ end
173
+
174
+ def to_s
175
+ body = "target [#{@target}]\n"
176
+ body += "bearer [#{@bearer}]\n"
177
+ body += "token [#{@token}]\n"
178
+ body += "token_prefix [#{@token_prefix}]\n"
179
+ body += "domain [#{@domain}]\n"
180
+ body += "scheme [#{@scheme}]\n"
181
+ body += "api_host [#{@api_host}]\n"
182
+ body += "auth_host [#{@auth_host}]\n"
183
+ body += "base_api_url [#{@base_api_url}]\n"
184
+ body += "base_auth_url [#{@base_auth_url}]\n"
185
+ body += "cache_file_path [#{@cache_file_path}]\n"
186
+ end
187
+ end
188
+
189
+
190
+ class ApceraApiHelper
191
+ attr_reader :docker_layer_hash
192
+ attr_reader :sandbox
193
+ attr_reader :target
194
+ attr_reader :api_host
195
+
196
+ # cache_file_path = File.expand_path("~/.apcera_api")
197
+
198
+ def initialize(enable_debugging, target = "", bearer = "")
199
+ self.debug_mode = enable_debugging
200
+
201
+ apc_env = ApceraAuth.new(target, bearer)
202
+ if (self.debug_mode)
203
+ puts apc_env.to_s
204
+ end
205
+
206
+ Apcera.configure do |config|
207
+ config.scheme = apc_env.scheme
208
+ config.api_key['authorization'] = apc_env.token
209
+ config.api_key_prefix['authorization'] = apc_env.token_prefix
210
+
211
+ config.host = apc_env.api_host
212
+ config.base_path = '/v1'
213
+
214
+ config.debugging = enable_debugging
215
+ config.verify_ssl = false
216
+ end
217
+
218
+ @_bindings_api = Apcera::ServicesAndBindingsApi.new
219
+ @_instances_api ||= Apcera::InstancesApi.new
220
+ @_jobs_api ||= Apcera::JobsApi.new
221
+ @_logs_api ||= Apcera::LogsApi.new
222
+ @_metrics_api ||= Apcera::MetricsApi.new
223
+ @_packages_api ||= Apcera::PackagesApi.new
224
+ @_stagingpipeline_api ||= Apcera::StagingPipelinesApi.new
225
+ @_stagingpipeline_api ||= Apcera::StagingPipelinesApi.new
226
+ @_utilities_api ||= Apcera::UtilitiesApi.new
227
+
228
+ sb = self.get_sandbox()
229
+ @sandbox = sb.namespace
230
+ end
231
+
232
+ def debug_mode=(mode)
233
+ @debug_mode = mode
234
+ Apcera::Configuration.debugging = mode
235
+ end
236
+
237
+ def debug_mode?()
238
+ @debug_mode
239
+ end
240
+
241
+ def debug_mode()
242
+ @debug_mode
243
+ end
244
+
245
+ def get_info
246
+ @_utilities_api.info_get()
247
+ end
248
+
249
+ def get_instance_managers()
250
+ @_instances_api.instance_managers_get()
251
+ end
252
+
253
+ # Runtime operations
254
+ #
255
+ def runtimes_get()
256
+ @_utilities_api.runtimes_get()
257
+ end
258
+
259
+ def get_sandbox()
260
+ @_utilities_api.namespace_default_get()
261
+ end
262
+
263
+ # Staging Pipeline operations
264
+ #
265
+ def stagingpipelines_get(opts = {})
266
+ @_stagingpipeline_api.stagingpipelines_get({:fqn => staging_pipeline})
267
+ end
268
+
269
+ def get_staging_pipeline_by_name(name)
270
+ ret = @_stagingpipeline_api.stagingpipelines_get({:fqn => name})
271
+ ret.length > 0 ? ret[0] : nil
272
+ end
273
+
274
+ def get_matching_staging_pipeline(name)
275
+ ApceraApiHelper.notice "get_matching_staging_pipeline for #{name}"
276
+ runtimes = self.runtimes_get()
277
+ staging_pipeline = ""
278
+
279
+ # First find the matching runtime
280
+ #
281
+ runtimes.each do | runtime |
282
+ runtime.patterns.each do | pattern |
283
+ # If the name is a star, we are doing the whole directory
284
+ #
285
+ if ((name == "*") && (!Dir.glob(pattern).empty?) ||
286
+ (name != "*") && (File.fnmatch(pattern, name)))
287
+ staging_pipeline = "stagpipe::/apcera::#{runtime.runtime}"
288
+ break
289
+ end
290
+ end
291
+ end
292
+
293
+ pipeline = self.get_staging_pipeline_by_name(staging_pipeline)
294
+ return pipeline
295
+ end
296
+
297
+ # Metrics operations
298
+ #
299
+ def get_job_metrics(opts = {})
300
+ @_metrics_api.metrics_jobs_get(opts)
301
+ end
302
+
303
+ def get_instance_manager_metrics(opts = {})
304
+ @_metrics_api.metrics_instance_managers_get(opts)
305
+ end
306
+
307
+ def get_cluster_metrics(opts = {})
308
+ @_metrics_api.metrics_cluster_get(opts)
309
+ end
310
+
311
+ def get_route_metrics(opts = {})
312
+ @_metrics_api.metrics_route_counters_get(opts)
313
+ end
314
+
315
+ def get_namespace_metrics(opts = {})
316
+ @_metrics_api.metrics_namespace_get(opts)
317
+ end
318
+
319
+ # Package operations
320
+ #
321
+ def find_packages(opt = {})
322
+ @_packages_api.packages_get(opt)
323
+ end
324
+
325
+ def package_exist?(fqn)
326
+ !get_package_by_name(fqn).nil?
327
+ end
328
+
329
+ def get_package_by_name(fqn)
330
+ ret = @_packages_api.packages_get({:fqn => fqn})
331
+ ret.length > 0 ? ret[0] : nil
332
+ end
333
+
334
+ def get_package_for_docker_layer(docker_namespace, layer_id)
335
+ # ret = @_packages_api.find_packages({:fqn => docker_namespace,
336
+ # :matchPartialFQN => true,
337
+ # :tag => "docker_layer_id,#{layer_id}" })
338
+ ret = @_packages_api.packages_get({:fqn => "package::#{docker_namespace}", :match_partial_fqn => "true", :tag => "docker_layer_id,#{layer_id}" })
339
+
340
+ ret.length > 0 ? ret[0] : nil
341
+ end
342
+
343
+ def get_package_uuid_for_docker_layer(docker_namespace, layer_id)
344
+ ret = self.get_package_for_docker_layer(docker_namespace, layer_id)
345
+ uuid = !ret.nil? ? ret.uuid : ""
346
+ uuid
347
+ end
348
+
349
+ def get_package_by_tag(tag)
350
+ ret = @_packages_api.find_packages({:tag => tag})
351
+ ret.length > 0 ? ret[0] : nil
352
+ end
353
+
354
+ def get_package_by_uuid(uuid)
355
+ ret = @_packages_api.packages_uuid_get(uuid)
356
+ ret.length > 0 ? ret[0] : nil
357
+ end
358
+
359
+ def create_package(package)
360
+ @_packages_api.packages_post(package)
361
+ end
362
+
363
+ def update_package_resources_by_uuid(uuid, payload)
364
+ @_packages_api.packages_resources_uuid_put(uuid, payload)
365
+ end
366
+
367
+ def update_package_by_uuid(uuid, package)
368
+ @_packages_api.packages_uuid_put(uuid, package)
369
+ end
370
+
371
+ def delete_package(package_name)
372
+ package = self.get_package_by_name(package_name)
373
+ ApceraApiHelper.notice("Trying to delete package uuid #{package.uuid}")
374
+ @_packages_api.packages_uuid_delete(package.uuid)
375
+ end
376
+
377
+ def wait_until_package_staged(package_name)
378
+ Apcera::Configuration.debugging = false
379
+ ApceraApiHelper.notice("Checking for #{package_name}...")
380
+
381
+ pkg = get_package_by_name(package_name)
382
+
383
+ if (['staging', 'uploading'].include?(pkg.state))
384
+ self.spin_until_package_staged(package_name)
385
+ end
386
+ end
387
+
388
+ def spin_until_package_staged(package_name)
389
+ save_debug = Apcera::Configuration.debugging
390
+ Apcera::Configuration.debugging = false
391
+
392
+ pkg = get_package_by_name(package_name)
393
+
394
+ show_wait_spinner{
395
+ while (['staging', 'uploading'].include?(pkg.state))
396
+ pkg = get_package_by_name(package_name)
397
+ sleep 5
398
+ end
399
+ }
400
+
401
+ Apcera::Configuration.debugging = save_debug
402
+
403
+ # return the package
404
+ #
405
+ pkg
406
+ end
407
+
408
+
409
+ def create_and_stage_git_repo(repo_name, clone_to, repo_subdir, namespace, job_name, change_to = "", pipeline = nil, tags = {})
410
+ save_dir = Dir.pwd
411
+ puts "save_dir is #{save_dir}"
412
+ tmp_dir = Dir.mktmpdir("git-")
413
+ puts "tmp_dir is #{tmp_dir}"
414
+
415
+ Dir.chdir(tmp_dir)
416
+ puts "now in #{Dir.pwd}"
417
+
418
+ system("git clone #{repo_name} #{clone_to}")
419
+ Dir.chdir("#{clone_to}/#{repo_subdir}")
420
+
421
+ create_and_stage_package("*", namespace, job_name, change_to, pipeline, tags)
422
+
423
+ Dir.chdir(save_dir)
424
+ FileUtils.rm_rf(tmp_dir)
425
+ end
426
+
427
+ # If filename is "*" then it does the whole directory
428
+ # Example:
429
+ # stage_package("*", "/sandbox/fred.flintstone", "my_new_job")
430
+ def create_and_stage_package(filename, namespace, job_name, change_to = "", pipeline = nil, tags = {})
431
+ file_list = filename
432
+
433
+ if ((filename != "*") && File.exist?("start.sh"))
434
+ file_list = "#{filename} start.sh"
435
+ end
436
+
437
+ if pipeline.nil?
438
+ pipeline = self.get_matching_staging_pipeline(filename)
439
+ end
440
+
441
+ if (pipeline.nil?)
442
+ ApceraApiHelper.notice "Fatal ERROR, could not determine staging pipeline for #{filename}"
443
+ exit
444
+ end
445
+
446
+ # when we stage, we only need the uuid
447
+ #
448
+ sp = Apcera::StagingPipeline.new({ :uuid => pipeline.uuid })
449
+
450
+ self.banner("Tarring up the files")
451
+ tarball_path = "/tmp/pkg-#{job_name.gsub("/", "-")}.tgz"
452
+ base_dir_opt = ""
453
+ if change_to.nonblank?
454
+ base_dir_opt = "-C #{change_to}"
455
+ end
456
+ tar_command = "COPYFILE_DISABLE=1 tar #{base_dir_opt} -czf #{tarball_path} #{file_list}"
457
+ system(tar_command)
458
+
459
+ contents = File.binread(tarball_path)
460
+ hex_digest = Digest::SHA1.hexdigest(contents.to_s)
461
+ sha256_digest = Digest::SHA256.hexdigest(contents.to_s)
462
+ length = File.size(tarball_path)
463
+
464
+ # We are now done with the file
465
+ #
466
+ File.delete(tarball_path)
467
+
468
+ # At some point will need to switch to the sha256 digest. Looks like that is now.
469
+ #
470
+ # resource = Apcera::PackageResource.new({:length => length, :digest => sha256_digest})
471
+ #
472
+ resource = Apcera::PackageResource.new()
473
+ package_fqn = "package::#{namespace}::#{job_name}"
474
+
475
+ new_pkg = Apcera::Package.new({
476
+ :name => job_name,
477
+ :fqn => package_fqn,
478
+ :staging_pipeline => sp
479
+ })
480
+
481
+ new_pkg.resources = [resource]
482
+
483
+ new_pkg.tags = tags
484
+
485
+ if self.debug_mode?
486
+ self.banner("Package Is")
487
+ ApceraApiHelper.notice new_pkg.to_yaml
488
+ end
489
+
490
+ ApceraApiHelper.notice("Creating a placeholder for the new package #{new_pkg.fqn}")
491
+
492
+ begin
493
+ created_pkg = self.create_package(new_pkg)
494
+ rescue
495
+ # Handle the race condition where the package has been created since this function started
496
+ #
497
+ ApceraApiHelper.notice "post failed, validate presense"
498
+ check_pkg = self.get_package_by_name(new_pkg.fqn)
499
+ if !check_pkg.nil?
500
+ ApceraApiHelper.notice "Package State is #{check_pkg.state}"
501
+ return ### SHOULD I REALLY??
502
+ end
503
+ end
504
+
505
+
506
+ ApceraApiHelper.notice("Uploading the new package... #{created_pkg.uuid} resource #{created_pkg.resource.uuid}" )
507
+
508
+ @_packages_api.packages_package_uuid_resources_resource_uuid_put(created_pkg.uuid, created_pkg.resource.uuid, contents, "sha256:#{sha256_digest}")
509
+
510
+ # bar = self.update_package_resources_by_uuid(created_pkg.uuid, contents)
511
+
512
+ # ApceraApiHelper.notice self.get_logs("staging.#{created_pkg.uuid}")
513
+
514
+ ApceraApiHelper.notice("Waiting for package #{job_name} to stage (might take some time)...")
515
+ pkg = self.spin_until_package_staged(package_fqn)
516
+
517
+ ApceraApiHelper.notice "Package (#{job_name}) staging status: #{pkg.state}"
518
+ pkg
519
+ end
520
+
521
+ def package_from_files(filename, namespace, job_name, port = 0)
522
+ # 1) Make sure that a job with that name isn't running
523
+ #
524
+ if self.job_exist?("job::#{namespace}::#{job_name}")
525
+ ApceraApiHelper.notice "Error, job #{namespace}::#{job_name} already exists"
526
+ return nil
527
+ end
528
+
529
+ # 2) Make sure that a package with that name isn't running
530
+ #
531
+ if self.package_exist?("package::#{namespace}::#{job_name}")
532
+ ApceraApiHelper.notice "Error, package #{namespace}::#{job_name} already exists"
533
+ return nil
534
+ end
535
+
536
+ create_and_stage_package(filename, namespace, job_name)
537
+ end
538
+
539
+ def job_from_package(namespace, job_name, allow_egress, port = 0, environment = {}, start_command = "")
540
+
541
+ fqn_base = "#{namespace}::#{job_name}"
542
+ package_fqn = "package::#{fqn_base}"
543
+ job_fqn = "job::#{fqn_base}"
544
+
545
+ if self.job_exist?(job_fqn)
546
+ ApceraApiHelper.notice "Job #{job_fqn} already exists, aborting"
547
+ return nil
548
+ end
549
+
550
+ self.banner("Getting package uuid for package #{package_fqn}")
551
+ pkg = self.get_package_by_name(package_fqn)
552
+
553
+ if pkg.nil?
554
+ ApceraApiHelper.notice "Error, package #{package_fqn} not found, aborting"
555
+ exit
556
+ end
557
+
558
+ # Need to build the route name
559
+ #
560
+ route = namespace.split("/").reverse.join(".").chomp(".")
561
+
562
+ port_route = Apcera::Port.new({
563
+ :optional => false,
564
+ :number => port,
565
+ :routes => [{:type => "http", :endpoint => "#{job_name}.#{route}.#{@domain}", :weight => 0}]
566
+ })
567
+
568
+ app = Apcera::ProcessObject.new({
569
+ # :start_command_raw => "",
570
+ :start_command => start_command,
571
+ :start_command_timeout => 30,
572
+ # :stop_command_raw => "",
573
+ :stop_command => "",
574
+ :stop_timeout => 5
575
+ })
576
+
577
+ app.environment = environment
578
+
579
+ resource = Apcera::Resource.new({
580
+ :cpu => 0,
581
+ :memory => 256 * 1024 * 1024,
582
+ :disk => 256 * 1024 * 1024,
583
+ :network => 5 * 1024 * 1024,
584
+ :netmax => 0
585
+ })
586
+ rollout = Apcera::RolloutConfig.new({
587
+ :force_stop_old_instances_after => 0,
588
+ :flapping_minimum_restarts => 0,
589
+ :flapping_percent => 0,
590
+ :flapping_window => 0,
591
+ :errored_state_window => 0
592
+ })
593
+
594
+ process = Apcera::Process.new({ :app => app})
595
+
596
+ restart = Apcera::RestartConfig.new({
597
+ :restart_mode => "always",
598
+ :maximum_attempts => 0
599
+ })
600
+
601
+ # Gather up all of the above
602
+ #
603
+ job_proto = Apcera::Job.new({
604
+ :uuid => "",
605
+ :ports => [port_route],
606
+ :name => job_name,
607
+ :fqn => job_fqn,
608
+ :num_instances => 1,
609
+ :packages => [{:uuid => pkg.uuid}],
610
+ :processes => process,
611
+ :resources => resource,
612
+ :rollout => rollout,
613
+ :restart => restart,
614
+ :state => "unknown"
615
+ })
616
+ # Ruby doesn't seem to like setting the tags in-line
617
+ #
618
+ job_proto.tags = {"app" => job_name}
619
+
620
+ if self.debug_mode?
621
+ self.banner("NEW JOB #{job_proto.to_yaml}")
622
+ end
623
+
624
+ new_job = self.create_job(job_proto)
625
+ self.banner("CREATED JOB #{job_name}")
626
+
627
+ tmp_pkg = self.get_package_by_name(package_fqn)
628
+
629
+ tmp_pkg.tags = {"linked-job" =>"#{new_job.uuid}"}
630
+
631
+ self.update_package_by_uuid(tmp_pkg.uuid, tmp_pkg)
632
+
633
+ # Then post the bindings
634
+ #
635
+ if allow_egress
636
+ self.banner("Posting the Allow-Egress binding for #{job_name}")
637
+ ename = fqn_base.gsub("/", "_").gsub("::", "_").gsub(".", "_")
638
+ binding = Apcera::Binding.new({
639
+ :name => "egress_for#{ename}",
640
+ :fqn => "binding::#{namespace}::#{job_name}",
641
+ :job_fqn => job_fqn,
642
+ :service_fqn => "service::/apcera::outside"
643
+ })
644
+ bound = self.create_binding(binding)
645
+ end
646
+
647
+ # return the new job
648
+ #
649
+ self.get_job_by_name(job_fqn)
650
+ end
651
+
652
+ # Job operations
653
+ #
654
+ def jobs_get(opt = {})
655
+ @_jobs_api.jobs_get(opt)
656
+ end
657
+
658
+ def job_exist?(name)
659
+ !self.get_job_by_name(name).nil?
660
+ end
661
+
662
+ def service_exist?(service)
663
+ !self.get_service_by_name(service).nil?
664
+ end
665
+
666
+ def create_docker_job_new_api(image_url, job_namespace, job_name, start_command, exposed_port = 0, environment = {}, allow_egress = false, ram_mb = 128, disk_mb = 16)
667
+
668
+ job_fqn = "job::#{job_namespace}::#{job_name}"
669
+
670
+ sc = start_command.split(" ")
671
+ puts "Trying to do job #{job_fqn}"
672
+
673
+ resource = Apcera::Resource.new({
674
+ :cpu => 0,
675
+ :memory => ram_mb * 1024 * 1024,
676
+ :disk => disk_mb * 1024 * 1024,
677
+ :network => 1 * 1024 * 1024 * 1024,
678
+ :netmax => 0
679
+ })
680
+
681
+ restart = Apcera::RestartConfig.new({
682
+ :restart_mode => "no",
683
+ :maximum_attempts => 0
684
+ })
685
+
686
+ docker_job = Apcera::CreateDockerJobRequest.new({
687
+ :'allow_egress' => allow_egress,
688
+ :'image_url' => image_url,
689
+ :'job_fqn' => job_fqn,
690
+ :'start_command' => sc,
691
+ :'resources' => resource,
692
+ :'restart' => restart
693
+ })
694
+
695
+ docker_job.env = environment
696
+
697
+ # Set up the port
698
+ #
699
+ if (!exposed_port.nil? && exposed_port != 0)
700
+ docker_job.exposed_ports = [exposed_port]
701
+
702
+ # need to also set the route also
703
+ #
704
+ sub_route = job_namespace.split("/").reverse.join(".").chomp(".")
705
+ route_name = "http://#{job_name}.#{sub_route}.#{self.get_base_domain}"
706
+ docker_job.routes = {route_name => exposed_port}
707
+ end
708
+
709
+ task = @_jobs_api.docker_jobs_post(docker_job)
710
+ task
711
+ end
712
+
713
+ def check_task_status(task_uuid)
714
+ @_jobs_api.tasks_uuid_get(task_uuid)
715
+ end
716
+
717
+ def create_job(job)
718
+ @_jobs_api.jobs_post(job)
719
+ end
720
+
721
+ def scale_job(job_fqn, number_of_instances)
722
+ job = get_job_by_name(job_fqn)
723
+ return if job.nil?
724
+
725
+ puts "Scaling #{job_fqn} from #{job.num_instances} to #{number_of_instances} "
726
+ job.num_instances = number_of_instances
727
+ self.update_job(job)
728
+ end
729
+
730
+ def update_job(job)
731
+ @_jobs_api.jobs_uuid_put(job.uuid, job)
732
+ end
733
+
734
+ def get_job_by_name(name)
735
+ ret = @_jobs_api.jobs_get({:fqn => name})
736
+ ret.length > 0 ? ret[0] : nil
737
+ end
738
+
739
+ def get_service_by_name(name)
740
+ ret = @_bindings_api.services_get({:fqn => name})
741
+ puts ret.to_yaml
742
+ ret.length > 0 ? ret[0] : nil
743
+ end
744
+
745
+ def get_job_state_by_name(name)
746
+ ret = @_jobs_api.jobs_get({:fqn => name})
747
+
748
+ ret.length > 0 ? ret[0].state : "JOB_NOT_FOUND"
749
+ end
750
+
751
+ def start_job(job_name, num_instances = 1)
752
+ job = self.get_job_by_name(job_name)
753
+
754
+ if self.debug_mode?
755
+ ApceraApiHelper.notice job.to_yaml
756
+ end
757
+
758
+ if job.nil?
759
+ ApceraApiHelper.notice "Error, job #{job_name} was not found"
760
+ return nil
761
+ elsif (job.state == "started" && job.num_instances == num_instances)
762
+ ApceraApiHelper.notice "#{job_name} is already running with requested number of instanes (#{num_instances})"
763
+ return job
764
+ end
765
+ job.state = "started"
766
+ job.num_instances = num_instances
767
+ job = self.update_job(job)
768
+
769
+ ApceraApiHelper.notice "#{job_name} state is now #{job.state} with #{job.num_instances}"
770
+ job
771
+ end
772
+
773
+ def delete_job(job_name)
774
+ job = self.get_job_by_name(job_name)
775
+ ApceraApiHelper.notice("Trying to delete job w/ uuid #{job.uuid}")
776
+ @_jobs_api.jobs_uuid_delete(job.uuid)
777
+ end
778
+
779
+ def stop_job(job_name)
780
+ job = self.get_job_by_name(job_name)
781
+
782
+ if self.debug_mode?
783
+ ApceraApiHelper.notice "JOB is\n\n"
784
+ ApceraApiHelper.notice job.to_yaml
785
+ end
786
+
787
+ if job.nil? || job.state == "stopped"
788
+ ApceraApiHelper.notice "Error, job #{job_name} is either already stopped or not found"
789
+ return job
790
+ end
791
+ job.state = "stopped"
792
+ self.update_job(job)
793
+
794
+ ApceraApiHelper.notice "Job #{job_name} state is now " + self.get_job_state_by_name(job_name)
795
+ end
796
+
797
+ def get_job_route(namespace, job_name)
798
+ job = self.get_job_by_name("job::#{namespace}::#{job_name}")
799
+
800
+ url = ""
801
+
802
+ if !job.ports.nil?
803
+ job.ports.each do | port |
804
+ if !port.routes.nil?
805
+ route = port.routes[0]
806
+ url = "#{route.type}://#{route.endpoint}"
807
+ break
808
+ end
809
+ end
810
+ end
811
+ url
812
+ end
813
+
814
+ # Binding operations
815
+ #
816
+ def create_binding(binding)
817
+ @_bindings_api.bindings_post(binding)
818
+ end
819
+
820
+ def link_jobs(source_job, target_job, name, port)
821
+ self.banner("Linking #{source_job} to #{target_job} via #{name} on port #{port}")
822
+ binding = Apcera::Binding.new({
823
+ :fqn => "binding::#{self.sandbox}::#{SecureRandom.uuid}",
824
+ :job_fqn => source_job,
825
+ :name => name,
826
+ :target_job_fqn => target_job,
827
+ :target_job_port => port
828
+ })
829
+
830
+ if self.debug_mode?
831
+ ApceraApiHelper.notice binding.to_yaml
832
+ end
833
+
834
+ self.create_binding(binding)
835
+ end
836
+
837
+ def add_service_to_job(job_fqn, service_fqn, name)
838
+ self.banner("Trying to bind service #{service_fqn} to #{job_fqn} as #{name}")
839
+
840
+ # first we need the provider for the service
841
+ #
842
+ provider = self.get_service_by_name(service_fqn)
843
+ puts ">>>>Provider is #{provider.to_yaml}"
844
+
845
+ binding = Apcera::Binding.new({
846
+ :fqn => "binding::#{self.sandbox}::#{SecureRandom.uuid}",
847
+ :job_fqn => job_fqn,
848
+ :name => name,
849
+ :provider_fqn => provider.provider_fqn,
850
+ :service_fqn => service_fqn
851
+ })
852
+
853
+ if self.debug_mode?
854
+ ApceraApiHelper.notice binding.to_yaml
855
+ end
856
+
857
+ self.create_binding(binding)
858
+ end
859
+
860
+ # get job logs
861
+ #
862
+ def get_job_logs(uuid, lines)
863
+
864
+ @_jobs_api.jobs_uuid_logs_get(uuid, opts = {:lines => lines})
865
+
866
+ end
867
+
868
+ # Log Operations
869
+ #
870
+ def get_logs(key)
871
+ @_logs_api.logs_channel_get(key).to_yaml
872
+ end
873
+
874
+ # Docker Operations
875
+ #
876
+ def initiate_docker_job(namespace, job_name, image_url, start_command = "" )
877
+ job_proto = self.build_docker_job_prototype(namespace, job_name, image_url, start_command)
878
+ @_jobs_api.docker_job_check(job_proto)
879
+
880
+ # return the prototype so we can use it later
881
+ #
882
+ job_proto
883
+ end
884
+
885
+ def job_from_docker(job_name, job_namespace, registry, image_name, image_tag, start_command = "", allow_egress = false, port = 0, environment = {}, user_name = "", password = "")
886
+
887
+ # Make sure that the job isn't running
888
+ #
889
+ base_fqn = "#{job_namespace}::#{job_name}"
890
+ job_fqn = "job::#{base_fqn}"
891
+
892
+ # begin
893
+ if self.job_exist?(job_fqn)
894
+ ApceraApiHelper.notice "error, job #{job_fqn} exists already, aborting"
895
+ exit
896
+ end
897
+ # rescue
898
+ # puts "crapped out checking #{job_name}"
899
+ # exit
900
+ # end
901
+
902
+ # This is the stuff from the scratchpad
903
+ #
904
+ parts = registry.split(/[:\/]/)
905
+ scheme = parts.first
906
+ domain = parts.last
907
+
908
+ userinfo = ""
909
+ if user_name.nonblank? && password.nonblank?
910
+ userinfo = URI::encode("#{user_name}:#{password}@")
911
+ end
912
+
913
+ image_suffix = ""
914
+ if image_tag.nonblank?
915
+ image_suffix = ":#{image_tag}"
916
+ end
917
+ endpoint_scheme = "#{scheme}://#{userinfo}"
918
+ registry_url = "#{endpoint_scheme}#{domain}"
919
+ image_url = "#{registry_url}/#{image_name}#{image_suffix}"
920
+
921
+ djt = self.create_docker_job_new_api(image_url, job_namespace, job_name, start_command, port, environment, allow_egress)
922
+ puts "docker job task location for #{job_name} is #{djt.location}"
923
+ location = djt.location
924
+
925
+ uuid = location.split("/").last
926
+ task = self.check_task_status(uuid)
927
+ if task.nil?
928
+ puts "YES, the task is nil for #{job_name}"
929
+ end
930
+ while task.state == "running"
931
+ # sleep a random time between 5 and 10 seconds
932
+ sleep rand(5..10)
933
+ task = self.check_task_status(uuid)
934
+ puts "Staging process (#{job_name}): *** #{task.state} ***"
935
+ end
936
+
937
+ return self.get_job_by_name(job_fqn)
938
+ end
939
+
940
+ def build_docker_job_prototype(namespace, job_name, image_url, start_command)
941
+
942
+ docker_origin = Apcera::DockerOrigin.new(
943
+ "ImageName" => "",
944
+ "ImageTag" => "",
945
+ "RegistryURL" => image_url,
946
+ "Volumes" => nil
947
+ )
948
+
949
+ port = Apcera::Port.new({
950
+ :optional => true,
951
+ :number => 222
952
+ })
953
+
954
+ app = Apcera::ProcessObject.new({
955
+ # :start_command_raw =>,
956
+ :start_command => start_command,
957
+ :start_command_timeout => 30,
958
+ :stop_command_raw => [],
959
+ :stop_command => "",
960
+ :stop_timeout => 5,
961
+ :user => "root"
962
+ })
963
+
964
+ process = Apcera::Process.new({ :app => app})
965
+
966
+ resource = Apcera::Resource.new({
967
+ :cpu => 0,
968
+ :memory => 768 * 1024 * 1024,
969
+ :disk => 256 * 1024 * 1024,
970
+ :network => 1 * 1024 * 1024 * 1024,
971
+ :netmax => 0
972
+ })
973
+
974
+ restart = Apcera::RestartConfig.new({
975
+ :restart_mode => "no",
976
+ :maximum_attempts => 0
977
+ })
978
+
979
+ rollout = Apcera::RolloutConfig.new({
980
+ :force_stop_old_instances_after => 0,
981
+ :flapping_minimum_restarts => 0,
982
+ :flapping_percent => 0,
983
+ :flapping_window => 0,
984
+ :errored_state_window => 0
985
+ })
986
+
987
+ job_proto = Apcera::Job.new({
988
+ :docker_origin => docker_origin,
989
+ :fqn => "job::#{namespace}::#{job_name}",
990
+ :name => job_name,
991
+ :num_instances => 1,
992
+ :packages => nil,
993
+ :ports => [port],
994
+ :processes => process,
995
+ :resources => resource,
996
+ :restart => restart,
997
+ :rollout => rollout,
998
+ :state => "unknown",
999
+ :uuid => "",
1000
+ :version_id => 0,
1001
+ :weight => 0
1002
+ })
1003
+
1004
+ job_proto.tags = {"app" => job_name, "docker" => job_name, "ssh" => "true"}
1005
+
1006
+ job_proto
1007
+ end
1008
+
1009
+ def _get_job_preferences_by_name(namespace, job_name)
1010
+ @_jobs_api.preferences_job_get("job::#{namespace}::#{job_name}")
1011
+ end
1012
+
1013
+ def get_docker_namespace(job_name)
1014
+ prefs = self._get_job_preferences_by_name(self.sandbox, job_name)
1015
+ prefs.docker_cache_namespace
1016
+ end
1017
+
1018
+ # Some route operations
1019
+ #
1020
+ def jobs_routes()
1021
+ @_jobs_api.jobs_routes_get()
1022
+ end
1023
+
1024
+ def jobs_routes_for_endpoint(endpoint_name)
1025
+ # @_jobs_api.jobs_routes_get()
1026
+ ApceraApiHelper.notice Base64.urlsafe_encode64(endpoint_name)
1027
+ @_jobs_api.jobs_routes_endpoint_get(Base64.urlsafe_encode64("zoom.jamie.smith.sandbox.demo.proveapcera.io"))
1028
+ end
1029
+
1030
+ # Utility Operations
1031
+ #
1032
+ def show_wait_spinner(fps=10)
1033
+ chars = %w[| / - \\]
1034
+ delay = 1.0/fps
1035
+ iter = 0
1036
+ spinner = Thread.new do
1037
+ while iter do # Keep spinning until told otherwise
1038
+ print chars[(iter+=1) % chars.length]
1039
+ sleep delay
1040
+ print "\b"
1041
+ end
1042
+ end
1043
+ yield.tap{ # After yielding to the block, save the return value
1044
+ iter = false # Tell the thread to exit, cleaning up after itself…
1045
+ spinner.join # …and wait for it to do so.
1046
+ } # Use the block's return value as the method's
1047
+ end
1048
+
1049
+ def get_base_domain()
1050
+ if @_base_domain.nil?
1051
+ foo = @_utilities_api.info_get()
1052
+ domain = foo.url.split(/[:\/]/).last
1053
+
1054
+ domain.gsub!("api.", "")
1055
+ @_base_domain = domain
1056
+ end
1057
+
1058
+ return @_base_domain
1059
+ end
1060
+
1061
+ def get_server_info()
1062
+ foo = @_utilities_api.info_get()
1063
+ return foo
1064
+ end
1065
+
1066
+ def banner(text)
1067
+ puts "\n\n"
1068
+ puts "################################################################################"
1069
+ puts "# #{text}"
1070
+ puts "################################################################################"
1071
+ end
1072
+
1073
+ def self.notice(text)
1074
+ puts text
1075
+ end
1076
+
1077
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: apcera
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.6.2
4
+ version: 0.1.6.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jamie Smith
@@ -273,6 +273,7 @@ files:
273
273
  - lib/apcera/models/virtual_network_endpoint_interface.rb
274
274
  - lib/apcera/version.rb
275
275
  - lib/apcera_api_gem.rb
276
+ - lib/apcera_api_helper.rb
276
277
  homepage: http://apcera.com
277
278
  licenses:
278
279
  - Apache-2.0