knife-maas 2.0.0 → 2.1.0

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: bda6744898a63036dc18093aa32cd860b1aaf108
4
- data.tar.gz: 67a1b570ea3856377d599faf11e7debee3385831
3
+ metadata.gz: 6571f2695fd08992e7f747c0b5c4066490629e0a
4
+ data.tar.gz: 98e048add9f9c965a75b76f89b22709489e1444e
5
5
  SHA512:
6
- metadata.gz: 9c8202250766d95e264f4910f749746b31c4ca62d03dd294487f30888547fb1abc00bbfe339af81db20fe351f5429811ae92cd976bc1eca2a864672ccd586526
7
- data.tar.gz: fd9d00bcdf21d43e54e79176ec1af5c425554a27403acc3c51e104f38ca36bb06db5cc8c1b9f21a6a6fc134b27ceb1d1b11e7f345aadc47904056ea6f9179bc6
6
+ metadata.gz: 1f793b7d791b9bd31303f2963a9427511a85c9dab14cb1bc8323424db7f906f61f65734ea8b559337f17a00a1ef2298d1cd99a1029d7c3c5bbd53563a4463094
7
+ data.tar.gz: 6b4585118138025cc7144be9a2c0093516535a57b83fa81ccf41bdffd912df84d7ada5bf721134ca09daf244da2c5b2e9bc4ac15035ea630a5c52d83f4f19bad
data/.gitignore CHANGED
@@ -14,3 +14,7 @@
14
14
  mkmf.log
15
15
  *.gem
16
16
  .idea/
17
+ .chef/
18
+ .direnv/
19
+ .envrc
20
+ .ruby-version
@@ -0,0 +1,17 @@
1
+ Style/ClassAndModuleChildren:
2
+ Enabled: false
3
+ Style/GuardClause:
4
+ Enabled: false
5
+ Metrics/ClassLength:
6
+ Max: 200
7
+ Metrics/CyclomaticComplexity:
8
+ Max: 10
9
+ Metrics/PerceivedComplexity:
10
+ Max: 10
11
+ Metrics/MethodLength:
12
+ Max: 40
13
+ Metrics/AbcSize:
14
+ Max: 25
15
+ AllCops:
16
+ Exclude:
17
+ - 'Guardfile'
@@ -1,6 +1,6 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 2.1.1
3
+ - 2.3.0
4
4
  install:
5
5
  - bundle install --retry=3
6
6
  script:
@@ -1,7 +1,25 @@
1
1
  # Change Log
2
2
 
3
- ## [v2.0.0](https://github.com/chef-partners/knife-maas/tree/v2.0.0)
3
+ ## [v2.1.0](https://github.com/chef-partners/knife-maas/tree/v2.1.0)
4
4
 
5
+ [Full Changelog](https://github.com/chef-partners/knife-maas/compare/v2.0.0...v2.1.0)
6
+
7
+ **Implemented enhancements:**
8
+
9
+ - sleeps are race-condition prone [\#15](https://github.com/chef-partners/knife-maas/issues/15)
10
+
11
+ **Closed issues:**
12
+
13
+ - API 1.0 no longer supported in maas 2.0 [\#21](https://github.com/chef-partners/knife-maas/issues/21)
14
+ - if statement is not needed. [\#17](https://github.com/chef-partners/knife-maas/issues/17)
15
+ - Ability to select or change the OS from knife [\#7](https://github.com/chef-partners/knife-maas/issues/7)
16
+ - Give the option of either hostname or systemid [\#5](https://github.com/chef-partners/knife-maas/issues/5)
17
+
18
+ **Merged pull requests:**
19
+
20
+ - Merge of 2.0 update [\#24](https://github.com/chef-partners/knife-maas/pull/24) ([tpmullan](https://github.com/tpmullan))
21
+
22
+ ## [v2.0.0](https://github.com/chef-partners/knife-maas/tree/v2.0.0) (2017-03-16)
5
23
  [Full Changelog](https://github.com/chef-partners/knife-maas/compare/v1.2.0...v2.0.0)
6
24
 
7
25
  **Merged pull requests:**
data/README.md CHANGED
@@ -1,13 +1,13 @@
1
1
  # knife-maas
2
2
 
3
- [![Build Status](https://travis-ci.org/jjasghar/knife-maas.svg?branch=master)](https://travis-ci.org/jjasghar/knife-maas)
3
+ [![Build Status](https://travis-ci.org/chef-partners/knife-maas.svg?branch=master)](https://travis-ci.org/chef-partners/knife-maas)
4
4
 
5
5
  This is the knife plugin to talk to [MAAS](http://maas.ubuntu.com/). This also assumes you have MAAS
6
6
  configured with at least one user account. You'll need your API key as one of the
7
7
  configuration options. There is a [MAAS cookbook](https://supermarket.chef.io/cookbooks/maas) that
8
8
  this plugin has been tested against.
9
9
 
10
- This also assumes you have >= 1.7.1 of MAAS installed, and >= [Chef](http://chef.io) 12 installed.
10
+ This also assumes you have >= 2.0.0 of MAAS installed, and >= [Chef](http://chef.io) 12 installed.
11
11
 
12
12
  The version `2.0.0`+ of this knife plugin, requires version 2.0 of MAAS API.
13
13
 
@@ -1,25 +1,28 @@
1
1
  # coding: utf-8
2
2
  lib = File.expand_path('../lib', __FILE__)
3
3
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require 'chef/knife/maas/version'
4
+ require 'chef/maas/version'
5
5
 
6
6
  Gem::Specification.new do |spec|
7
- spec.name = "knife-maas"
8
- spec.version = Chef::Knife::Maas::VERSION
9
- spec.authors = ["JJ Asghar"]
10
- spec.email = ["jj@chef.io"]
7
+ spec.name = 'knife-maas'
8
+ spec.version = Chef::Maas::VERSION
9
+ spec.authors = ['Chef Partner Engineering']
10
+ spec.email = ['jj@chef.io']
11
11
  spec.summary = %q{A knife plugin to interact with MAAS}
12
12
  spec.description = %q{A knife plugin to interact with MAAS}
13
- spec.homepage = "http://github.com/jjasghar/knife-maas"
14
- spec.license = "Apache2"
13
+ spec.homepage = 'https://github.com/chef-partners/knife-maas'
14
+ spec.license = 'Apache2'
15
15
 
16
16
  spec.files = `git ls-files -z`.split("\x0")
17
17
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
- spec.require_paths = ["lib"]
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_dependency 'chef', '~> 12.0'
22
+ spec.add_dependency 'oauth', '~> 0.4'
23
+ spec.add_dependency 'bson', '~> 3.0'
24
+ spec.add_dependency 'xml-simple', '~> 1.1'
20
25
 
21
- spec.add_dependency "chef", "~> 12.0"
22
- spec.add_dependency "oauth", "~> 0.4"
23
26
 
24
27
  spec.add_development_dependency "bundler", "~> 1.7"
25
28
  spec.add_development_dependency "rake", "~> 10.0"
@@ -1,67 +1,94 @@
1
-
2
-
3
1
  class Chef
4
2
  class Knife
3
+ # Base Maas module with common methods
5
4
  module MaasBase
6
-
7
5
  def self.included(includer)
8
6
  includer.class_eval do
9
-
10
7
  deps do
11
- require 'oauth'
12
- require 'oauth/signature/plaintext'
13
- require 'chef/json_compat'
14
- require 'chef/knife'
15
- require 'readline'
8
+ require 'chef/maas/maas'
9
+ require 'chef/api_client'
10
+ require 'chef/node'
16
11
  Chef::Knife.load_deps
17
12
  end
18
- # add common knife option flags here
19
13
  end
20
14
  end
21
15
 
22
- def prepare_access_token(oauth_token, oauth_token_secret, consumer_key, consumer_secret)
16
+ def client
17
+ @client ||= begin
18
+ api_key = locate_config_value(:maas_api_key)
19
+ site_uri = locate_config_value(:maas_site)
20
+ ::Maas::Client.new(api_key, site_uri)
21
+ end
22
+ end
23
+
24
+ def destroy_item(klass, name, type_name)
25
+ object = klass.load(name)
26
+ object.destroy
27
+ ui.warn("Deleted #{type_name} #{name}")
28
+ rescue Net::HTTPServerException
29
+ ui.warn("Could not find a #{type_name} named #{name} to delete!")
30
+ end
23
31
 
24
- consumer = OAuth::Consumer.new( consumer_key,
25
- consumer_secret,
26
- {
27
- :site => locate_config_value(:maas_site)+"api/1.0/",
28
- :scheme => :header,
29
- :signature_method => "PLAINTEXT"
30
- })
32
+ def acquire_node(hostname: nil, zone: nil)
33
+ unless hostname || zone
34
+ ui.error 'Please specify either a zone or hostname'
35
+ exit 1
36
+ end
31
37
 
32
- access_token = OAuth::AccessToken.new( consumer,
33
- oauth_token,
34
- oauth_token_secret
35
- )
36
- return access_token
38
+ if !hostname.nil?
39
+ client.acquire_node(hostname: hostname)
40
+ elsif !zone.nil?
41
+ client.acquire_node(zone: zone)
42
+ end
37
43
  end
38
44
 
39
- def access_token
40
- maas_key = "#{locate_config_value(:maas_api_key)}"
41
- customer_key = maas_key.split(":")[0]
42
- customer_secret = ""
43
- token_key = maas_key.split(":")[1]
44
- token_secret = maas_key.split(":")[2]
45
- prepare_access_token(token_key, token_secret, customer_key, customer_secret)
45
+ def print_node_status(response_json)
46
+ if response_json.is_a?(Hash) && code = response_json['status']
47
+ ui.info(ui.color(::Maas::NODE_STATUS_MAP[code.to_s][:status],
48
+ ::Maas::NODE_STATUS_MAP[code.to_s][:color]))
49
+ end
50
+ response_json
46
51
  end
47
52
 
48
53
  def locate_config_value(key)
49
54
  key = key.to_sym
50
- Chef::Config[:knife][key] || config[key]
55
+ config[key] || Chef::Config[:knife][key]
51
56
  end
52
57
 
53
- def validate!(keys=[:hostname, :system_id])
54
- errors = []
55
-
56
- keys.each do |k|
57
- if locate_config_value(k).nil?
58
- errors << "You did not provide a valid '#{k}' value."
58
+ def with_timeout(max_time = nil, &block)
59
+ if max_time
60
+ Timeout.timeout(max_time) do
61
+ block.call
59
62
  end
63
+ else
64
+ block.call
65
+ end
66
+ rescue Timeout::Error
67
+ ui.error "Request took longer than #{max_time}"
68
+ exit 1
69
+ end
70
+
71
+ def wait_with_dots(sleep_seconds = 1)
72
+ print('.') && sleep(sleep_seconds)
73
+ end
74
+
75
+ def ensure_system_id!
76
+ system_id = locate_config_value(:system_id) || name_args[0]
77
+ unless system_id
78
+ ui.error('You must provide the system id of the node')
79
+ exit 1
60
80
  end
81
+ system_id
82
+ end
61
83
 
62
- if errors.each{|e| ui.error(e)}.any?
84
+ def ensure_chef_node_name!
85
+ hostname = locate_config_value(:hostname)
86
+ node_name = config[:chef_node_name] || hostname
87
+ unless node_name
88
+ ui.error ('You must provide the hostname or chef_node_name')
63
89
  exit 1
64
90
  end
91
+ node_name
65
92
  end
66
93
  end
67
94
  end
@@ -3,44 +3,25 @@ require 'chef/knife/maas_base'
3
3
  class Chef
4
4
  class Knife
5
5
  class MaasServerAcquire < Knife
6
-
7
6
  include Chef::Knife::MaasBase
8
7
 
9
- deps do
10
- end
11
-
12
- banner "knife maas server acquire (options)"
8
+ banner 'knife maas server acquire (options)'
13
9
 
14
10
  option :hostname,
15
- :short => "-h HOSTNAME",
16
- :long => "--hostname HOSTNAME",
17
- :description => "The HOSTNAME inside of MAAS"
11
+ short: '-h HOSTNAME',
12
+ long: '--hostname HOSTNAME',
13
+ description: 'The HOSTNAME inside of MAAS'
18
14
 
19
15
  option :zone,
20
- :short => "-Z ZONE",
21
- :long => "--zone ZONE",
22
- :description => "Bootstrap inside a ZONE inside of MAAS"
16
+ short: '-Z ZONE',
17
+ long: '--zone ZONE',
18
+ description: 'Bootstrap inside a ZONE inside of MAAS'
23
19
 
24
20
  def run
25
-
26
21
  hostname = locate_config_value(:hostname)
27
22
  zone = locate_config_value(:zone)
28
-
29
- if (!hostname.nil? && !zone.nil?)
30
- puts "\nPlease only use one of these options, zone or hostname"
31
- exit 1
32
- elsif !hostname.nil?
33
- response = access_token.request(:post, "/nodes/", {'op'=> 'acquire','name' => "#{hostname}"})
34
- elsif !zone.nil?
35
- response = access_token.request(:post, "/nodes/", {'op'=> 'acquire','zone' => "#{zone}"})
36
- else
37
- response = access_token.request(:post, "/nodes/?op=acquire")
38
- end
39
-
40
- hostname = JSON.parse(response.body)["hostname"]
41
- puts "Acquiring #{hostname} under your account now...."
23
+ print_node_status(acquire_node(hostname: hostname, zone: zone))
42
24
  end
43
-
44
25
  end
45
26
  end
46
27
  end
@@ -3,7 +3,6 @@ require 'chef/knife/maas_base'
3
3
  class Chef
4
4
  class Knife
5
5
  class MaasServerBootstrap < Knife
6
-
7
6
  include Chef::Knife::MaasBase
8
7
 
9
8
  deps do
@@ -11,100 +10,74 @@ class Chef
11
10
  Chef::Knife::Bootstrap.load_deps
12
11
  end
13
12
 
14
- banner "knife maas server bootstrap (options)"
13
+ banner 'knife maas server bootstrap (options)'
15
14
 
16
15
  option :hostname,
17
- :short => "-h HOSTNAME",
18
- :long => "--hostname HOSTNAME",
19
- :description => "The HOSTNAME inside of MAAS"
16
+ short: '-h HOSTNAME',
17
+ long: '--hostname HOSTNAME',
18
+ description: 'The HOSTNAME inside of MAAS'
20
19
 
21
20
  option :template_file,
22
- :long => "--template-file TEMPLATE",
23
- :description => "Full path to location of template to use",
24
- :proc => Proc.new { |t| Chef::Config[:knife][:template_file] = t },
25
- :default => false
21
+ long: '--template-file TEMPLATE',
22
+ description: 'Full path to location of template to use',
23
+ proc: proc { |t| Chef::Config[:knife][:template_file] = t },
24
+ default: false
26
25
 
27
26
  option :run_list,
28
- :short => "-r RUN_LIST",
29
- :long => "--run-list RUN_LIST",
30
- :description => "Comma separated list of roles/recipes to apply",
31
- :proc => lambda { |o| o.split(/[\s,]+/) },
32
- :default => []
27
+ short: '-r RUN_LIST',
28
+ long: '--run-list RUN_LIST',
29
+ description: 'Comma separated list of roles/recipes to apply',
30
+ proc: -> (o) { o.split(/[\s,]+/) },
31
+ default: []
33
32
 
34
33
  option :zone,
35
- :short => "-Z ZONE",
36
- :long => "--zone ZONE",
37
- :description => "Bootstrap inside a ZONE inside of MAAS"
34
+ short: '-Z ZONE',
35
+ long: '--zone ZONE',
36
+ description: 'Bootstrap inside a ZONE inside of MAAS'
37
+
38
+ option :bootstrap_timeout,
39
+ short: '-T TIMEOUT',
40
+ long: '--bootstrap-timeout',
41
+ description: 'How long to wait for the Server to initially boot',
42
+ default: 120
38
43
 
39
44
  def run
45
+ node = acquire_node(hostname: locate_config_value(:hostname),
46
+ zone: locate_config_value(:zone)
47
+ )
40
48
 
41
- hostname = locate_config_value(:hostname)
42
- zone = locate_config_value(:zone)
49
+ system_id = node['system_id']
43
50
 
44
- if (!hostname.nil? && !zone.nil?)
45
- puts "\nPlease only use one of these options, zone or hostname"
46
- exit 1
47
- elsif !hostname.nil?
48
- response = access_token.request(:post, "/nodes/", {'op'=> 'acquire','name' => "#{hostname}"})
49
- elsif !zone.nil?
50
- response = access_token.request(:post, "/nodes/", {'op'=> 'acquire','zone' => "#{zone}"})
51
- else
52
- response = access_token.request(:post, "/nodes/?op=acquire")
53
- end
51
+ puts "Acquiring #{node["hostname"]} under your account now.\n"
54
52
 
55
- hostname = JSON.parse(response.body)["hostname"]
56
- system_id = JSON.parse(response.body)["system_id"]
57
- system_info = access_token.request(:get, "/nodes/#{system_id}/")
58
- puts "Acquiring #{hostname} under your account now...."
59
-
60
- # hack to ensure the node have had time to spin up
61
- print(".")
62
- sleep 30
63
- print(".")
64
-
65
- response = access_token.request(:post, "/nodes/#{system_id}/?op=start")
66
- puts "\nStarting up #{hostname} now...."
67
-
68
- # hack to ensure the nodes have had time to spin up
69
- print(".")
70
- sleep 30
71
- print(".")
72
-
73
- server = JSON.parse(system_info.body)["hostname"]
74
- netboot = JSON.parse(system_info.body)["netboot"]
75
- power_state = JSON.parse(system_info.body)["power_state"]
76
-
77
- until ((netboot == false) && (power_state == "on") ) do
78
- print(".")
79
- sleep @initial_sleep_delay ||= 10
80
- system_info = access_token.request(:get, "/nodes/#{system_id}/")
81
- netboot = JSON.parse(system_info.body)["netboot"]
82
- power_state = JSON.parse(system_info.body)["power_state"]
53
+ with_timeout(60) do
54
+ # wait until node is in 'Allocated' state
55
+ wait_with_dots until client.list_node(system_id)['status'].to_s == '10'
83
56
  end
84
57
 
85
- bootstrap_ip_address = JSON.parse(system_info.body)["ip_addresses"][0]
58
+ puts "Deploying #{node["hostname"]}\n"
59
+ client.deploy_node(system_id)
86
60
 
87
- print(".")
88
- sleep 30
89
- print(".")
90
- sleep 30
61
+ with_timeout(60000) do
62
+ # wait until node is in 'Allocated' state
63
+ wait_with_dots until client.list_node(system_id)['status'].to_s == '6'
64
+ end
91
65
 
92
- print(".") until tcp_test_ssh(bootstrap_ip_address) {
93
- sleep @initial_sleep_delay ||= 10
94
- puts("connected and done")
95
- }
66
+ system_info = client.list_node(system_id)
67
+ bootstrap_ip_address = system_info['ip_addresses'][0]
96
68
 
97
- os_system = JSON.parse(system_info.body)["osystem"]
69
+ with_timeout(config[:bootstrap_timeout]) do
70
+ wait_with_dots(5) until tcp_test_ssh(bootstrap_ip_address)
71
+ end
98
72
 
99
- case os_system
100
- when "centos"
101
- user = "cloud-user"
73
+ case system_info['osystem']
74
+ when 'centos'
75
+ user = 'cloud-user'
102
76
  else
103
- user = "ubuntu"
77
+ user = 'ubuntu'
104
78
  end
105
79
 
106
80
  bootstrap_for_node(server, bootstrap_ip_address, user).run
107
-
108
81
  end
109
82
 
110
83
  def bootstrap_for_node(server, bootstrap_ip_address, user)
@@ -140,10 +113,8 @@ class Chef
140
113
  rescue Errno::EPERM
141
114
  false
142
115
  rescue Errno::ECONNREFUSED
143
- sleep 2
144
116
  false
145
117
  rescue Errno::EHOSTUNREACH
146
- sleep 2
147
118
  false
148
119
  rescue Errno::ENETUNREACH
149
120
  sleep 2
@@ -151,7 +122,6 @@ class Chef
151
122
  ensure
152
123
  tcp_socket && tcp_socket.close
153
124
  end
154
-
155
125
  end
156
126
  end
157
127
  end
@@ -0,0 +1,23 @@
1
+ require 'chef/knife/maas_base'
2
+
3
+ class Chef
4
+ class Knife
5
+ class MaasServerCommission < Knife
6
+ include Chef::Knife::MaasBase
7
+
8
+ banner 'knife maas server commission (options)'
9
+
10
+ option :system_id,
11
+ short: '-s SYSTEM_ID',
12
+ long: '--system-id SYSTEM_ID',
13
+ description: 'The System ID inside of MaaS'
14
+
15
+ def run
16
+ unless system_id = locate_config_value(:system_id) || name_args[0]
17
+ ui.error('You must provide the system id of the node')
18
+ end
19
+ print_node_status(client.commission_node(system_id))
20
+ end
21
+ end
22
+ end
23
+ end
@@ -1,59 +1,49 @@
1
1
  require 'chef/knife/maas_base'
2
- require 'chef/node'
3
- require 'chef/api_client'
4
2
 
5
3
  class Chef
6
4
  class Knife
7
5
  class MaasServerDelete < Knife
8
-
9
6
  include Chef::Knife::MaasBase
10
7
 
11
- banner "knife maas server delete (options)"
8
+ banner 'knife maas server delete (options)'
12
9
 
13
10
  option :hostname,
14
- :short => "-h HOSTNAME",
15
- :long => "--hostname HOSTNAME",
16
- :description => "The HOSTNAME inside of MaaS"
11
+ short: '-h HOSTNAME',
12
+ long: '--hostname HOSTNAME',
13
+ description: 'The HOSTNAME inside of MaaS'
17
14
 
18
15
  option :system_id,
19
- :short => "-s SYSTEM_ID",
20
- :long => "--system-id SYSTEM_ID",
21
- :description => "The System ID inside of MaaS"
16
+ short: '-s SYSTEM_ID',
17
+ long: '--system-id SYSTEM_ID',
18
+ description: 'The System ID inside of MaaS'
22
19
 
23
20
  option :purge,
24
- :short => "-P",
25
- :long => "--purge",
26
- :boolean => true,
27
- :default => false,
28
- :description => "Destroy corresponding node and client on the Chef Server, in addition to destroying the MaaS node itself. Assumes node and client have the same name as the server."
29
-
30
- def destroy_item(klass, name, type_name)
31
- begin
32
- object = klass.load(name)
33
- object.destroy
34
- ui.warn("Deleted #{type_name} #{name}")
35
- rescue Net::HTTPServerException
36
- ui.warn("Could not find a #{type_name} named #{name} to delete!")
37
- end
38
- end
21
+ short: '-P',
22
+ long: '--purge',
23
+ boolean: true,
24
+ default: false,
25
+ description: <<-EOS.gsub(/^ {15}/, '').gsub(/\n/, ' ')
26
+ Destroy corresponding node and client on the Chef Server, in
27
+ addition to destroying the MaaS node itself. Assumes node and
28
+ client have the same name as the server.
29
+ EOS
39
30
 
40
31
  def run
41
- system_id = locate_config_value(:system_id)
42
- hostname = locate_config_value(:hostname)
43
-
44
- if config[:purge]
45
- thing_to_delete = config[:chef_node_name] || hostname
46
- destroy_item(Chef::Node, thing_to_delete, "node")
47
- destroy_item(Chef::ApiClient, thing_to_delete, "client")
48
- else
49
- ui.warn("Corresponding node and client for the #{hostname} server were not deleted and remain registered with the Chef Server")
32
+ system_id = ensure_system_id!
33
+ node_name = ensure_chef_node_name! if config[:purge]
34
+
35
+ if print_node_status(client.delete_node(system_id))
36
+ if config[:purge]
37
+ destroy_item(Chef::Node, node_name, 'node')
38
+ destroy_item(Chef::ApiClient, node_name, 'client')
39
+ else
40
+ ui.warn <<-EOS.gsub(/^ {14}/, '').gsub(/\n/, ' ')
41
+ The corresponding node and client for #{node_name || system_id}
42
+ were not deleted and remain registered with the Chef Server
43
+ EOS
44
+ end
50
45
  end
51
-
52
- response = access_token.request(:post, "/nodes/#{system_id}/?op=delete")
53
- puts "Nuking #{system_id} From Orbit now...."
54
46
  end
55
-
56
-
57
47
  end
58
48
  end
59
49
  end
@@ -3,13 +3,11 @@ require 'chef/knife/maas_base'
3
3
  class Chef
4
4
  class Knife
5
5
  class MaasServerList < Knife
6
-
7
6
  include Chef::Knife::MaasBase
8
7
 
9
- banner "knife maas server list"
8
+ banner 'knife maas server list'
10
9
 
11
10
  def run
12
-
13
11
  server_list = [
14
12
  ui.color('System ID', :bold),
15
13
  ui.color('Status', :bold),
@@ -19,63 +17,38 @@ class Chef
19
17
  ui.color('IP Address', :bold)
20
18
  ]
21
19
 
22
- response = access_token.request(:get, "/nodes/?op=list")
23
- JSON.parse(response.body).sort_by { |h| h["system_id"] }.each do |server|
24
- Chef::Log.debug("Server: #{server.to_yaml}")
25
-
26
- server_list << server['system_id'].to_s
27
- server_list << begin
28
- status = server['substatus'].to_s
29
- case status
30
- when "2","3","8","11","13","15"
31
- ui.color("failed", :red)
32
- when "0","1","5","7","9","10","12","14"
33
- ui.color("deploying", :yellow)
34
- when "4"
35
- ui.color("ready", :green)
36
- when "6"
37
- ui.color("deployed", :green)
38
- else
39
- ui.color(status, :green)
40
- end
41
- end
42
- # NEW = 0
43
- # COMMISSIONING = 1
44
- # FAILED_COMMISSIONING = 2
45
- # MISSING = 3
46
- # READY = 4
47
- # RESERVED = 5
48
- # DEPLOYED = 6
49
- # RETIRED = 7
50
- # BROKEN = 8
51
- # DEPLOYING = 9
52
- # ALLOCATED = 10
53
- # FAILED_DEPLOYMENT = 11
54
- # RELEASING = 12
55
- # FAILED_RELEASING = 13
56
- # DISK_ERASING = 14
57
- # FAILED_DISK_ERASING = 15
58
- server_list << begin
59
- power_state = server['power_state'].to_s
60
- case power_state
61
- when "off"
62
- ui.color(power_state, :red)
63
- when "on"
64
- ui.color(power_state, :green)
65
- else
66
- ui.color(power_state, :yellow)
67
- end
68
- end
69
- server_list << server['hostname'].to_s
70
- server_list << server['owner'].to_s
71
- server_list << server['ip_addresses'].to_s
72
-
20
+ list = client.list_nodes
21
+
22
+ if list.is_a? Array
23
+ list.sort_by { |h| h['system_id'] }.each do |server|
24
+ #Chef::Log.debug("Server: #{server.to_yaml}")
25
+
26
+ server_list << server['system_id'].to_s
27
+
28
+ status = server['substatus'] || server['status'] || '-1'
29
+ server_list << ui.color(::Maas::NODE_STATUS_MAP[status.to_s][:status],
30
+ ::Maas::NODE_STATUS_MAP[status.to_s][:color])
31
+
32
+ server_list << begin
33
+ power_state = server['power_state'].to_s
34
+ case power_state
35
+ when 'off'
36
+ ui.color(power_state, :red)
37
+ when 'on'
38
+ ui.color(power_state, :green)
39
+ else
40
+ ui.color(power_state, :yellow)
41
+ end
42
+ end
43
+ server_list << server['hostname'].to_s
44
+ server_list << server['owner'].to_s
45
+ server_list << server['ip_addresses'].to_s
46
+ end
47
+ ui.info(ui.list(server_list, :uneven_columns_across, 6))
48
+ else
49
+ ui.info(list)
73
50
  end
74
- puts ui.list(server_list, :uneven_columns_across, 6)
75
-
76
51
  end
77
-
78
-
79
52
  end
80
53
  end
81
54
  end
@@ -1,62 +1,49 @@
1
1
  require 'chef/knife/maas_base'
2
- require 'chef/node'
3
- require 'chef/api_client'
4
2
 
5
3
  class Chef
6
4
  class Knife
7
5
  class MaasServerRelease < Knife
8
-
9
6
  include Chef::Knife::MaasBase
10
7
 
11
- banner "knife maas server release (options)"
8
+ banner 'knife maas server release (options)'
12
9
 
13
10
  option :hostname,
14
- :short => "-h HOSTNAME",
15
- :long => "--hostname HOSTNAME",
16
- :description => "The HOSTNAME inside of MAAS"
11
+ short: '-h HOSTNAME',
12
+ long: '--hostname HOSTNAME',
13
+ description: 'The HOSTNAME inside of MAAS'
17
14
 
18
15
  option :system_id,
19
- :short => "-s SYSTEM_ID",
20
- :long => "--system-id SYSTEM_ID",
21
- :description => "The System ID inside of MAAS"
16
+ short: '-s SYSTEM_ID',
17
+ long: '--system-id SYSTEM_ID',
18
+ description: 'The System ID inside of MAAS'
22
19
 
23
20
  option :purge,
24
- :short => "-P",
25
- :long => "--purge",
26
- :boolean => true,
27
- :default => false,
28
- :description => "Destroy corresponding node and client on the Chef Server, in addition to releasing the MAAS node itself. Assumes node and client have the same name as the server."
29
-
30
- def destroy_item(klass, name, type_name)
31
- begin
32
- object = klass.load(name)
33
- object.destroy
34
- ui.warn("Deleted #{type_name} #{name}")
35
- rescue Net::HTTPServerException
36
- ui.warn("Could not find a #{type_name} named #{name} to delete!")
37
- end
38
- end
21
+ short: '-P',
22
+ long: '--purge',
23
+ boolean: true,
24
+ default: false,
25
+ description: <<-EOS.gsub(/^ {15}/, '').gsub(/\n/, ' ')
26
+ Destroy corresponding node and client on the Chef Server, in
27
+ addition to releasing the MAAS node itself. Assumes node and
28
+ client have the same name as the server.
29
+ EOS
39
30
 
40
31
  def run
41
-
42
- validate!
43
-
44
- system_id = locate_config_value(:system_id)
45
- hostname = locate_config_value(:hostname)
46
-
47
- if config[:purge]
48
- thing_to_delete = config[:chef_node_name] || hostname
49
- destroy_item(Chef::Node, thing_to_delete, "node")
50
- destroy_item(Chef::ApiClient, thing_to_delete, "client")
51
- else
52
- ui.warn("Corresponding node and client for the #{hostname} server were not deleted and remain registered with the Chef Server")
32
+ system_id = ensure_system_id!
33
+ node_name = ensure_chef_node_name! if config[:purge]
34
+
35
+ if print_node_status(client.release_node(system_id))
36
+ if config[:purge]
37
+ destroy_item(Chef::Node, node_name, 'node')
38
+ destroy_item(Chef::ApiClient, node_name, 'client')
39
+ else
40
+ ui.warn <<-EOS.gsub(/^ {14}/, '').gsub(/\n/, ' ')
41
+ The corresponding node and client for #{node_name || system_id}
42
+ were not deleted and remain registered with the Chef Server
43
+ EOS
44
+ end
53
45
  end
54
-
55
- response = access_token.request(:post, "/nodes/#{system_id}/?op=release")
56
- puts "Releasing #{system_id} back to MAAS now...."
57
46
  end
58
-
59
-
60
47
  end
61
48
  end
62
49
  end
@@ -0,0 +1,23 @@
1
+ require 'chef/knife/maas_base'
2
+
3
+ class Chef
4
+ class Knife
5
+ class MaasServerShow < Knife
6
+ include Chef::Knife::MaasBase
7
+
8
+ banner 'knife maas server show $NODE (options)'
9
+
10
+ option :system_id,
11
+ short: '-s SYSTEM_ID',
12
+ long: '--system-id SYSTEM_ID',
13
+ description: 'The System ID inside of MaaS'
14
+
15
+ def run
16
+ unless system_id = locate_config_value(:system_id) || name_args[0]
17
+ ui.error('You must provide the system id of the node')
18
+ end
19
+ ui.info(Chef::JSONCompat.to_json_pretty(client.show_node(system_id)))
20
+ end
21
+ end
22
+ end
23
+ end
@@ -3,28 +3,21 @@ require 'chef/knife/maas_base'
3
3
  class Chef
4
4
  class Knife
5
5
  class MaasServerStart < Knife
6
-
7
6
  include Chef::Knife::MaasBase
8
7
 
9
- deps do
10
- require 'chef/knife/bootstrap'
11
- Chef::Knife::Bootstrap.load_deps
12
- end
13
-
14
- banner "knife maas server start (options)"
8
+ banner 'knife maas server start (options)'
15
9
 
16
10
  option :system_id,
17
- :short => "-s SYSTEM_ID",
18
- :long => "--system-id SYSTEM_ID",
19
- :description => "The System ID inside of MaaS"
11
+ short: '-s SYSTEM_ID',
12
+ long: '--system-id SYSTEM_ID',
13
+ description: 'The System ID inside of MaaS'
20
14
 
21
15
  def run
22
- system_id = locate_config_value(:system_id)
23
- response = access_token.request(:post, "/nodes/#{system_id}/?op=start")
24
- puts "Starting up #{system_id} now...."
16
+ unless system_id = locate_config_value(:system_id) || name_args[0]
17
+ ui.error('You must provide the system id of the node')
18
+ end
19
+ print_node_status(client.start_node(system_id))
25
20
  end
26
-
27
-
28
21
  end
29
22
  end
30
23
  end
@@ -3,23 +3,21 @@ require 'chef/knife/maas_base'
3
3
  class Chef
4
4
  class Knife
5
5
  class MaasServerStop < Knife
6
-
7
6
  include Chef::Knife::MaasBase
8
7
 
9
- banner "knife maas server stop (options)"
8
+ banner 'knife maas server stop (options)'
10
9
 
11
10
  option :system_id,
12
- :short => "-s SYSTEM_ID",
13
- :long => "--system-id SYSTEM_ID",
14
- :description => "The System ID inside of MaaS"
11
+ short: '-s SYSTEM_ID',
12
+ long: '--system-id SYSTEM_ID',
13
+ description: 'The System ID inside of MaaS'
15
14
 
16
15
  def run
17
- system_id = locate_config_value(:system_id)
18
- response = access_token.request(:post, "/nodes/#{system_id}/?op=stop")
19
- puts "Stopping #{system_id} now...."
16
+ unless system_id = locate_config_value(:system_id) || name_args[0]
17
+ ui.error('You must provide the system id of the node')
18
+ end
19
+ print_node_status(client.stop_node(system_id))
20
20
  end
21
-
22
-
23
21
  end
24
22
  end
25
23
  end
@@ -0,0 +1,143 @@
1
+ require 'oauth'
2
+ require 'oauth/signature/plaintext'
3
+ require 'chef/json_compat'
4
+ require 'chef/knife'
5
+ require 'readline'
6
+ require 'timeout'
7
+ require 'forwardable'
8
+ require 'bson'
9
+ require 'xmlsimple'
10
+
11
+ module Maas
12
+ class Client
13
+ extend Forwardable
14
+
15
+ attr_reader :api_key, :site_uri, :access_token, :ui
16
+
17
+ def initialize(api_key, site_uri)
18
+ @api_key = api_key
19
+ @site_uri = site_uri
20
+ @access_token = access_token
21
+ @ui = Chef::Knife.ui
22
+ end
23
+
24
+ def_delegators :@access_token, :get, :put, :post, :delete, :request
25
+
26
+ def acquire_node(hostname: nil, zone: nil)
27
+ if hostname && zone
28
+ ui.error 'Please specify either a zone or hostname'
29
+ elsif !hostname && !zone
30
+ ui.error 'Please specify a zone or hostname'
31
+ end
32
+
33
+ if hostname
34
+ response = post('/machines/', 'op' => 'allocate', 'name' => "#{hostname}")
35
+ elsif zone
36
+ response = post('/machines/', 'op' => 'allocate', 'zone' => "#{zone}")
37
+ end
38
+
39
+ parse_response(response)
40
+ end
41
+
42
+ # TODO: Maybe use method_missing to dynamically build these helpers
43
+
44
+ def delete_node(system_id)
45
+ parse_response(delete("/nodes/#{system_id}/"))
46
+ end
47
+
48
+ def show_node(system_id)
49
+ bson_parse(get("/nodes/#{system_id}/?op=details"))
50
+ end
51
+
52
+ def list_node(system_id)
53
+ list = parse_response(get("/nodes/?id=#{system_id}"))
54
+ if list.is_a? Array
55
+ return list.first
56
+ else
57
+ return Hash.new
58
+ end
59
+ end
60
+
61
+ def list_nodes
62
+ parse_response(get('/nodes/'))
63
+ end
64
+
65
+ def release_node(system_id)
66
+ parse_response(post("/machines/#{system_id}/?op=release"))
67
+ end
68
+
69
+ def start_node(system_id)
70
+ parse_response(post("/machines/#{system_id}/?op=power_on"))
71
+ end
72
+
73
+ def deploy_node(system_id)
74
+ parse_response(post("/machines/#{system_id}/?op=deploy"))
75
+ end
76
+
77
+
78
+ def commission_node(system_id)
79
+ parse_response(post("/machines/#{system_id}/?op=commission"))
80
+ end
81
+
82
+ def stop_node(system_id)
83
+ parse_response(post("/machines/#{system_id}/?op=power_off"))
84
+ end
85
+
86
+ private
87
+
88
+ def access_token
89
+ consumer_key, token_key, token_secret = api_key.split(':')
90
+ consumer = OAuth::Consumer.new(consumer_key,
91
+ '',
92
+ site: "#{site_uri}api/2.0",
93
+ scheme: :header,
94
+ signature_method: 'PLAINTEXT'
95
+ )
96
+ OAuth::AccessToken.new(consumer,
97
+ token_key,
98
+ token_secret
99
+ )
100
+ end
101
+
102
+ def parse_response(response)
103
+ case response
104
+ when Net::HTTPSuccess, Net::HTTPRedirection
105
+ parse_response_json(response)
106
+ else
107
+ ui.error(parse_response_json(response))
108
+ exit 1
109
+ end
110
+ end
111
+
112
+ def parse_response_json(response)
113
+ Chef::JSONCompat.parse(response.body)
114
+ rescue Chef::Exceptions::JSON::ParseError
115
+ response.body
116
+ end
117
+
118
+ def bson_parse(response)
119
+ decoded = {}
120
+ Hash.from_bson(StringIO.new(response.body)).each_pair do |type, binary|
121
+ decoded[type] = XmlSimple.xml_in(binary.data)
122
+ end
123
+ decoded
124
+ end
125
+
126
+ def with_timeout(max_time = nil, &block)
127
+ if max_time
128
+ Timeout.timeout(max_time) do
129
+ block.call
130
+ end
131
+ else
132
+ block.call
133
+ end
134
+ rescue Timeout::Error
135
+ ui.error "Request took longer than #{max_time}"
136
+ exit 1
137
+ end
138
+
139
+ def wait_with_dots(sleep_seconds = 1)
140
+ print('.') && sleep(sleep_seconds)
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,24 @@
1
+ require 'chef/maas/version'
2
+ require 'chef/maas/client'
3
+
4
+ module Maas
5
+ NODE_STATUS_MAP = {
6
+ '-1' => { color: :red, status: 'unknown' },
7
+ '0' => { color: :yellow, status: 'new' },
8
+ '1' => { color: :yellow, status: 'commissioning' },
9
+ '2' => { color: :red, status: 'failed_commissioning' },
10
+ '3' => { color: :red, status: 'missing' },
11
+ '4' => { color: :green, status: 'ready' },
12
+ '5' => { color: :yellow, status: 'reserved' },
13
+ '6' => { color: :green, status: 'deployed' },
14
+ '7' => { color: :yellow, status: 'retired' },
15
+ '8' => { color: :red, status: 'broken' },
16
+ '9' => { color: :yellow, status: 'deploying' },
17
+ '10' => { color: :yellow, status: 'allocated' },
18
+ '11' => { color: :red, status: 'failed_deployment' },
19
+ '12' => { color: :yellow, status: 'releasing' },
20
+ '13' => { color: :red, status: 'failed_releasing' },
21
+ '14' => { color: :yellow, status: 'disk_erasing' },
22
+ '15' => { color: :red, status: 'failed_disk_erasing' }
23
+ }
24
+ end
@@ -0,0 +1,5 @@
1
+ class Chef
2
+ module Maas
3
+ VERSION = '2.1.0'
4
+ end
5
+ end
@@ -1,8 +1,8 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Chef::Knife::Maas do
3
+ describe Chef::Maas do
4
4
  it 'has a version number' do
5
- expect(Chef::Knife::Maas::VERSION).not_to be nil
5
+ expect(Chef::Maas::VERSION).not_to be nil
6
6
  end
7
7
 
8
8
  it 'does nothing useful' do
@@ -1,2 +1,3 @@
1
1
  $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
- require 'chef/knife/maas'
2
+ require 'chef/maas/version'
3
+ require 'chef/maas/client'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: knife-maas
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
- - JJ Asghar
7
+ - Chef Partner Engineering
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-03-16 00:00:00.000000000 Z
11
+ date: 2017-04-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: chef
@@ -38,6 +38,34 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0.4'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bson
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: xml-simple
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.1'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.1'
41
69
  - !ruby/object:Gem::Dependency
42
70
  name: bundler
43
71
  requirement: !ruby/object:Gem::Requirement
@@ -103,6 +131,7 @@ extra_rdoc_files: []
103
131
  files:
104
132
  - ".gitignore"
105
133
  - ".rspec"
134
+ - ".rubocop.yml"
106
135
  - ".travis.yml"
107
136
  - CHANGELOG.md
108
137
  - Gemfile
@@ -110,19 +139,23 @@ files:
110
139
  - README.md
111
140
  - Rakefile
112
141
  - knife-maas.gemspec
113
- - lib/chef/knife/maas.rb
114
142
  - lib/chef/knife/maas/version.rb
115
143
  - lib/chef/knife/maas_base.rb
116
144
  - lib/chef/knife/maas_server_acquire.rb
117
145
  - lib/chef/knife/maas_server_bootstrap.rb
146
+ - lib/chef/knife/maas_server_commission.rb
118
147
  - lib/chef/knife/maas_server_delete.rb
119
148
  - lib/chef/knife/maas_server_list.rb
120
149
  - lib/chef/knife/maas_server_release.rb
150
+ - lib/chef/knife/maas_server_show.rb
121
151
  - lib/chef/knife/maas_server_start.rb
122
152
  - lib/chef/knife/maas_server_stop.rb
153
+ - lib/chef/maas/client.rb
154
+ - lib/chef/maas/maas.rb
155
+ - lib/chef/maas/version.rb
123
156
  - spec/knife/maas_spec.rb
124
157
  - spec/spec_helper.rb
125
- homepage: http://github.com/jjasghar/knife-maas
158
+ homepage: https://github.com/chef-partners/knife-maas
126
159
  licenses:
127
160
  - Apache2
128
161
  metadata: {}
@@ -1,9 +0,0 @@
1
- require "chef/knife/maas/version"
2
-
3
- class Chef
4
- class Knife
5
- class MaasServerList < Knife
6
-
7
- end
8
- end
9
- end