clc_machine 0.1.11

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b3a9cf7685e5942e8f5260b7a15ae6e0b71eb765
4
+ data.tar.gz: 600b9d4e20da9c498b72291550969c202939d21e
5
+ SHA512:
6
+ metadata.gz: a7959abe9f65cf4f527cd5ea3edef1466322c7456c8ab05097f08bae30086b49de42645b79159386136e1e39dcefb12e996af5adad41059e457545b239795212
7
+ data.tar.gz: 2b504629673ad5e507222770ef74d417398c255d379707d26bded1b5971e22dc7a6aaa7c1f670ccefcfc43d3bd3b4366e75ed618c9ced530ac20d4b05580b61a
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ *.bundle
10
+ *.so
11
+ *.o
12
+ *.a
13
+ mkmf.log
14
+ *.gem
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in clc_machine.gemspec
4
+ gemspec
@@ -0,0 +1,99 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ clc_machine (0.1.0)
5
+ chef-vault (~> 2.4.0)
6
+ clc-chef-metal-vsphere
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ akami (1.2.2)
12
+ gyoku (>= 0.4.0)
13
+ nokogiri
14
+ builder (3.2.2)
15
+ chef-vault (2.4.0)
16
+ chef-zero (3.2.1)
17
+ ffi-yajl (~> 1.1)
18
+ hashie (~> 2.0)
19
+ mixlib-log (~> 1.3)
20
+ rack
21
+ uuidtools (~> 2.1)
22
+ clc-chef-metal-vsphere (0.3.67)
23
+ clc-fork-chef-metal (= 0.14.alpha.8)
24
+ rbvmomi (~> 1.8.0, >= 1.8.2)
25
+ rubyzip (= 1.1.6)
26
+ clc-cheffish (0.8.3.clc.3)
27
+ chef-zero
28
+ clc-fork-chef-metal (0.14.alpha.8)
29
+ clc-cheffish (~> 0.8.3.clc.3)
30
+ inifile (~> 2.0)
31
+ net-scp (~> 1.0)
32
+ net-ssh (~> 2.0)
33
+ net-ssh-gateway (~> 1.2.0)
34
+ winrm (~> 1.2.0)
35
+ ffi (1.9.6)
36
+ ffi-yajl (1.3.1)
37
+ ffi (~> 1.5)
38
+ libyajl2 (~> 1.2)
39
+ gssapi (1.0.3)
40
+ ffi (>= 1.0.1)
41
+ gyoku (1.2.2)
42
+ builder (>= 2.1.2)
43
+ hashie (2.1.2)
44
+ httpclient (2.5.3.3)
45
+ httpi (0.9.7)
46
+ rack
47
+ inifile (2.0.2)
48
+ libyajl2 (1.2.0)
49
+ little-plugger (1.1.3)
50
+ logging (1.8.2)
51
+ little-plugger (>= 1.1.3)
52
+ multi_json (>= 1.8.4)
53
+ mini_portile (0.6.1)
54
+ mixlib-log (1.6.0)
55
+ multi_json (1.10.1)
56
+ net-scp (1.2.1)
57
+ net-ssh (>= 2.6.5)
58
+ net-ssh (2.9.1)
59
+ net-ssh-gateway (1.2.0)
60
+ net-ssh (>= 2.6.5)
61
+ nokogiri (1.6.5)
62
+ mini_portile (~> 0.6.0)
63
+ nori (1.1.5)
64
+ rack (1.5.2)
65
+ rake (10.4.2)
66
+ rbvmomi (1.8.2)
67
+ builder
68
+ nokogiri (>= 1.4.1)
69
+ trollop
70
+ rubyntlm (0.1.1)
71
+ rubyzip (1.1.6)
72
+ savon (0.9.5)
73
+ akami (~> 1.0)
74
+ builder (>= 2.1.2)
75
+ gyoku (>= 0.4.0)
76
+ httpi (~> 0.9)
77
+ nokogiri (>= 1.4.0)
78
+ nori (~> 1.0)
79
+ wasabi (~> 1.0)
80
+ trollop (2.0)
81
+ uuidtools (2.1.5)
82
+ wasabi (1.0.0)
83
+ nokogiri (>= 1.4.0)
84
+ winrm (1.2.0)
85
+ gssapi (~> 1.0.0)
86
+ httpclient (~> 2.2, >= 2.2.0.2)
87
+ logging (~> 1.6, >= 1.6.1)
88
+ nokogiri (~> 1.5)
89
+ rubyntlm (~> 0.1.1)
90
+ savon (= 0.9.5)
91
+ uuidtools (~> 2.1.2)
92
+
93
+ PLATFORMS
94
+ ruby
95
+
96
+ DEPENDENCIES
97
+ bundler (~> 1.7)
98
+ clc_machine!
99
+ rake (~> 10.0)
@@ -0,0 +1,43 @@
1
+ # clc_machine
2
+ clc_machine provides commands for inspecting, deleting and adding machines in the QA1 DLAB folder. This is really intended to be a quick and dirty tool for a rather narrow use case.
3
+
4
+ ## Installation
5
+
6
+ Add this line to your application's Gemfile:
7
+
8
+ ```ruby
9
+ gem 'clc_machine'
10
+ ```
11
+
12
+ And then execute:
13
+
14
+ $ bundle
15
+
16
+ Or install it yourself as:
17
+
18
+ $ gem install clc_machine
19
+
20
+ ## Usage
21
+
22
+ ```
23
+ clc_machine COMMAND [MACHINE NAME] [OPTIONS]
24
+ ```
25
+
26
+ ### Commands
27
+
28
+ - delete
29
+
30
+ Deletes machines specified by name or regex (see options). An attempt will be made to delete the machine from the chef server as well.
31
+
32
+ - get
33
+
34
+ Returns details about a machine.
35
+
36
+ - list
37
+
38
+ Lists all machines in the QA1 DLAB folder.
39
+
40
+ ### Options
41
+
42
+ - -n, --name: The name of the vm
43
+ - -r, --regex: A regular expression to match one or more VMs.
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,122 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'clc_machine'
4
+ require 'optparse'
5
+ require 'highline/import'
6
+
7
+ def parse_options()
8
+ options = {}
9
+
10
+ opt_parser = OptionParser.new do |opt|
11
+ opt.banner = "Usage: clc_machine COMMAND [MACHINE NAME] [OPTIONS]"
12
+ opt.separator ""
13
+ opt.separator "Commands"
14
+ opt.separator " delete: delete server"
15
+ opt.separator " get: gets a server"
16
+ opt.separator " list: lists servers"
17
+ opt.separator ""
18
+ opt.separator "Options"
19
+
20
+ opt.on("-n","--name NAME","name of vm to create") do |name|
21
+ options[:name] = name
22
+ end
23
+
24
+ opt.on("-r","--regex REGEX","regular expresion to match for deleting VMs") do |regex|
25
+ options[:regex] = Regexp.new regex
26
+ end
27
+
28
+ options[:stamp] = "QA1"
29
+ opt.on("-s","--stamp STAMP","name of stamp to target") do |stamp|
30
+ options[:stamp] = stamp
31
+ end
32
+
33
+ options[:folder] = "DLAB"
34
+ opt.on("-f","--folder FOLDER","vsphere folder to target") do |folder|
35
+ options[:folder] = folder
36
+ end
37
+
38
+ options[:hypervisor_type] = :standard
39
+ opt.on("-t","--hypervisor_type TYPE","standard(default) or hyperscale") do |hypervisor_type|
40
+ options[:hypervisor_type] = hypervisor_type.to_sym
41
+ end
42
+
43
+ opt.on("-c","--knife_config PATH","path to knife config") do |knife_config|
44
+ options[:knife_config] = knife_config
45
+ end
46
+
47
+ opt.on("-k","--key KEY","chef api client key") do |key|
48
+ options[:key] = key
49
+ end
50
+
51
+ opt.on("-u","--user USER","chef api client username") do |user|
52
+ options[:user] = user
53
+ end
54
+
55
+ opt.on("-h","--help","help") do
56
+ puts opt_parser
57
+ exit
58
+ end
59
+ end
60
+
61
+ command = ARGV[0]
62
+
63
+ # first token after command name defaults machine name if none was set
64
+ if command != 'list' && !options.has_key?(:name) && !options.has_key?(:regex) && ARGV.length > 1
65
+ options[:name] = ARGV[1]
66
+ end
67
+ options[:command] = command
68
+
69
+ if validate_input(options, opt_parser) == false
70
+ puts opt_parser
71
+ exit 1
72
+ end
73
+
74
+ options
75
+ end
76
+
77
+ def validate_input(options, opt_parser)
78
+ begin
79
+ opt_parser.parse!
80
+ mandatory = []
81
+ if options[:command] != "list"
82
+ if !options.has_key?(:name) && !options.has_key?(:regex)
83
+ puts "Either --name or --regex option is required"
84
+ return false
85
+ end
86
+ end
87
+ return false unless %w{list delete get}.include?(options[:command])
88
+ rescue OptionParser::InvalidOption, OptionParser::MissingArgument
89
+ puts $!.to_s
90
+ return false
91
+ end
92
+ return true
93
+ end
94
+
95
+ def run(options)
96
+ clc_machine = ClcMachine::Machine.new(options)
97
+ case options[:command]
98
+ when "get"
99
+ puts clc_machine.get(options[:name]).summary.pretty_inspect
100
+ when "list"
101
+ puts "listing vms in #{options[:folder]}..."
102
+ clc_machine.get.each do | vm |
103
+ puts vm['name']
104
+ end
105
+ when "delete"
106
+ if options[:regex]
107
+ clc_machine.delete(options[:regex])
108
+ else
109
+ confirm = ask("Delete machine #{options[:name]}? (Y/N) ") { |q| q.validate = /\A[yYnN]\Z/ }
110
+ if confirm.downcase == 'y'
111
+ clc_machine.delete(options[:name])
112
+ else
113
+ puts "Exiting."
114
+ exit 0
115
+ end
116
+ end
117
+ end
118
+
119
+ exit 0
120
+ end
121
+
122
+ run(parse_options)
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'clc_machine/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "clc_machine"
8
+ spec.version = ClcMachine::VERSION
9
+ spec.authors = ['CenturyLink Cloud']
10
+ spec.email = ["matt.wrock@ctl.io"]
11
+ spec.summary = 'Minimal cli for managing clc dlab machines'
12
+ spec.description = spec.summary
13
+ spec.homepage = ""
14
+
15
+ spec.files = `git ls-files -z`.split("\x0")
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_development_dependency "bundler", "~> 1.7"
21
+ spec.add_development_dependency "rake", "~> 10.0"
22
+ spec.add_runtime_dependency 'clc-chef-metal-vsphere'
23
+ spec.add_runtime_dependency 'chef-vault'
24
+ spec.add_runtime_dependency 'chef'
25
+ end
@@ -0,0 +1,141 @@
1
+ require 'chef_metal_vsphere/vsphere_driver'
2
+ require 'chef_metal/chef_machine_spec'
3
+ require 'clc_machine/data_bag'
4
+ require 'clc_machine/secret_data_bag'
5
+ require 'clc_machine/utils'
6
+ require 'rbvmomi'
7
+ require 'highline/import' # user interaction
8
+
9
+ module ClcMachine
10
+ class Machine
11
+ def initialize(options)
12
+ @options = options
13
+ set_chef(options)
14
+ @data_bag = DataBag.new(options[:stamp])
15
+ @secrets = SecretDataBag.new(options[:stamp])
16
+ end
17
+
18
+ attr_reader :options
19
+ attr_reader :data_bag
20
+ attr_reader :secrets
21
+
22
+ def connection
23
+ RbVmomi::VIM.connect vsphere_options
24
+ end
25
+
26
+ def get(name = nil)
27
+ stamp = data_bag.vsphere_datacenter(options[:hypervisor_type])
28
+ if name
29
+ find_vm(stamp, options[:folder], name)
30
+ else
31
+ folder = find_folder(stamp, options[:folder])
32
+ folder.childEntity.each
33
+ end
34
+ end
35
+
36
+ def delete(name)
37
+ if name.is_a?(String)
38
+ vm = get(name)
39
+ if vm
40
+ config = Chef::Config.merge!({ :driver_options => vsphere_options })
41
+ machine_spec = ChefMetal::ChefMachineSpec.new({'name' => name}, Cheffish.default_chef_server(config))
42
+ driver = ChefMetal.driver_for_url("vsphere://#{vsphere_options[:host]}", config)
43
+ action_handler = ChefMetal::ActionHandler.new
44
+ machine_spec.location = { 'driver_url' => driver.driver_url,
45
+ 'server_id' => vm.config.instanceUuid}
46
+
47
+ chef_server = Cheffish.default_chef_server(config)
48
+ begin
49
+ driver.destroy_machine(action_handler, machine_spec, :convergence_options => { :chef_server => chef_server })
50
+ rescue Chef::Exceptions::PrivateKeyMissing, Net::HTTPServerException, Errno::ETIMEDOUT
51
+ puts "Unable to delete node on the chef server. See previous message."
52
+ end
53
+
54
+ puts "succesfully deleted #{name}"
55
+ else
56
+ puts "VM #{name} does not exist. I shall destroy nothing."
57
+ end
58
+ else
59
+ to_be_deleted = []
60
+ print 'Looking for matching machines '
61
+ get.each do |v|
62
+ check = v['name']
63
+ print '.'
64
+ if name.match(check)
65
+ to_be_deleted.push(check)
66
+ end
67
+ end
68
+ if to_be_deleted.count > 0
69
+ puts "\nReady to delete #{to_be_deleted.inspect}"
70
+ confirm = ask("Continue? (Y/N) ") { |q| q.validate = /\A[yYnN]\Z/ }
71
+ if confirm.downcase == 'y'
72
+ to_be_deleted.each do |m|
73
+ delete m
74
+ end
75
+ else
76
+ puts 'No action taken. Exiting.'
77
+ exit 0
78
+ end
79
+ else
80
+ puts "\nNo matching machines found. Exiting."
81
+ exit 0
82
+ end
83
+ end
84
+ end
85
+
86
+ private
87
+
88
+ def set_chef(options)
89
+ Chef::Config.reset
90
+ if @options.has_key?(:knife_config)
91
+ Chef::Config.from_file(@options[:knife_config])
92
+ else
93
+ repo = Chef::Config.find_chef_repo_path(Dir.pwd)
94
+ Chef::Config.from_file(File.join(repo, '.chef', 'knife.rb'))
95
+ end
96
+
97
+ if @options.has_key?(:key)
98
+ Chef::Config[:client_key] = @options[:key]
99
+ end
100
+
101
+ if @options.has_key?(:user)
102
+ Chef::Config[:node_name] = @options[:user]
103
+ end
104
+
105
+ if !File::exist?(Chef::Config[:client_key])
106
+ raise "Cant locate #{Chef::Config[:client_key]}."
107
+ end
108
+ end
109
+
110
+ def vsphere_options
111
+ {
112
+ :host => data_bag.vsphere_server(options[:hypervisor_type]),
113
+ :user => secrets.vsphere_username(options[:hypervisor_type]),
114
+ :password => secrets.vsphere_password(options[:hypervisor_type]),
115
+ :insecure => true
116
+ }
117
+ end
118
+
119
+ def find_vm(dc_name, vm_folder, vm_name)
120
+ folder = find_folder(dc_name, vm_folder) or raise("vSphere Folder not found [#{vm_folder}] for vm #{vm_name}")
121
+ folder.find(vm_name, RbVmomi::VIM::VirtualMachine)
122
+ end
123
+
124
+ def find_folder(dc_name, folder_name)
125
+ baseEntity = dc(dc_name).vmFolder
126
+ if folder_name && folder_name.length > 0
127
+ entityArray = folder_name.split('/')
128
+ entityArray.each do |entityArrItem|
129
+ if entityArrItem != ''
130
+ baseEntity = baseEntity.childEntity.grep(RbVmomi::VIM::Folder).find { |f| f.name == entityArrItem }
131
+ end
132
+ end
133
+ end
134
+ baseEntity
135
+ end
136
+
137
+ def dc(dc_name)
138
+ connection.serviceInstance.find_datacenter(dc_name) or raise("vSphere Datacenter not found [#{dc_name}]")
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,42 @@
1
+ require 'json'
2
+
3
+ module ClcMachine
4
+ class DataBag
5
+ TMP_DIR = '/tmp/ClcMachine/data_bags'
6
+ def initialize(stamp)
7
+ @stamp = stamp
8
+ end
9
+
10
+ def vsphere_datacenter(hypervisor_type = :standard)
11
+ raw[:hypervisors][hypervisor_type][0]['datacenter']
12
+ end
13
+
14
+ def vsphere_server(hypervisor_type = :standard)
15
+ raw[:hypervisors][hypervisor_type][0]['vsphere_server']
16
+ end
17
+
18
+ def raw
19
+ @raw ||= load_databag
20
+ end
21
+
22
+ private
23
+
24
+ def load_databag
25
+ download_data_bag
26
+ Utils.symbolize_keys(JSON.parse(IO.read(File.join(TMP_DIR, 'stamps',"#{@stamp}.json"))))
27
+ end
28
+
29
+ def download_data_bag
30
+ FileUtils.mkdir_p(TMP_DIR) if !Dir.exist?(TMP_DIR)
31
+
32
+ fs_config = Chef::ChefFS::Config.new(Chef::Config, cwd = Dir.pwd)
33
+ pattern = Chef::ChefFS::FilePattern.new("/data_bags/*")
34
+ local = Chef::ChefFS::FileSystem::ChefRepositoryFileSystemRootDir.new(
35
+ {
36
+ 'data_bags' => [TMP_DIR],
37
+ }
38
+ )
39
+ Chef::ChefFS::FileSystem.copy_to(pattern, fs_config.chef_fs, local, 1, Chef::Config)
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,32 @@
1
+ require 'chef-vault'
2
+
3
+ module ClcMachine
4
+ class SecretDataBag
5
+ def initialize(stamp)
6
+ @stamp = stamp
7
+ end
8
+
9
+ def vsphere_username(hypervisor_type = :standard)
10
+ raw['hypervisors'][hypervisor_type.to_s]['vsphere_username']
11
+ end
12
+
13
+ def vsphere_password(hypervisor_type = :standard)
14
+ raw['hypervisors'][hypervisor_type.to_s]['vsphere_password']
15
+ end
16
+
17
+ def raw
18
+ @raw ||= load_secrets
19
+ end
20
+
21
+ private
22
+
23
+ def load_secrets
24
+ %w{secrets_qa secrets_prod}.each do |bag|
25
+ begin
26
+ return ChefVault::Item.load(bag, "#{@stamp}_secrets")
27
+ rescue ChefVault::Exceptions::KeysNotFound
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,12 @@
1
+ module ClcMachine
2
+ class Utils
3
+ def self.symbolize_keys(h)
4
+ Hash === h ?
5
+ Hash[
6
+ h.map do |k, v|
7
+ [k.respond_to?(:to_sym) ? k.to_sym : k, symbolize_keys(v)]
8
+ end
9
+ ] : h
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,3 @@
1
+ module ClcMachine
2
+ VERSION = "0.1.11"
3
+ end
metadata ADDED
@@ -0,0 +1,127 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: clc_machine
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.11
5
+ platform: ruby
6
+ authors:
7
+ - CenturyLink Cloud
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-03-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.7'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: clc-chef-metal-vsphere
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: chef-vault
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: chef
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: Minimal cli for managing clc dlab machines
84
+ email:
85
+ - matt.wrock@ctl.io
86
+ executables:
87
+ - clc_machine
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - ".gitignore"
92
+ - Gemfile
93
+ - Gemfile.lock
94
+ - README.md
95
+ - Rakefile
96
+ - bin/clc_machine
97
+ - clc_machine.gemspec
98
+ - lib/clc_machine.rb
99
+ - lib/clc_machine/data_bag.rb
100
+ - lib/clc_machine/secret_data_bag.rb
101
+ - lib/clc_machine/utils.rb
102
+ - lib/clc_machine/version.rb
103
+ homepage: ''
104
+ licenses: []
105
+ metadata: {}
106
+ post_install_message:
107
+ rdoc_options: []
108
+ require_paths:
109
+ - lib
110
+ required_ruby_version: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - ">="
113
+ - !ruby/object:Gem::Version
114
+ version: '0'
115
+ required_rubygems_version: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - ">="
118
+ - !ruby/object:Gem::Version
119
+ version: '0'
120
+ requirements: []
121
+ rubyforge_project:
122
+ rubygems_version: 2.4.1
123
+ signing_key:
124
+ specification_version: 4
125
+ summary: Minimal cli for managing clc dlab machines
126
+ test_files: []
127
+ has_rdoc: