knife-maas 2.0.0 → 2.1.0

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: 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