apcera 0.1.6.2 → 0.1.6.3

Sign up to get free protection for your applications and to get access to all the features.
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