knife-oneandone 1.0.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.
Files changed (37) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +12 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +26 -0
  5. data/.travis.yml +5 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE +201 -0
  8. data/README.md +278 -0
  9. data/Rakefile +6 -0
  10. data/knife-oneandone.gemspec +31 -0
  11. data/lib/1and1/helpers.rb +29 -0
  12. data/lib/chef/knife/oneandone_appliance_list.rb +39 -0
  13. data/lib/chef/knife/oneandone_base.rb +32 -0
  14. data/lib/chef/knife/oneandone_datacenter_list.rb +33 -0
  15. data/lib/chef/knife/oneandone_firewall_create.rb +97 -0
  16. data/lib/chef/knife/oneandone_firewall_delete.rb +59 -0
  17. data/lib/chef/knife/oneandone_firewall_list.rb +33 -0
  18. data/lib/chef/knife/oneandone_ip_list.rb +39 -0
  19. data/lib/chef/knife/oneandone_loadbalancer_create.rb +147 -0
  20. data/lib/chef/knife/oneandone_loadbalancer_delete.rb +59 -0
  21. data/lib/chef/knife/oneandone_loadbalancer_list.rb +39 -0
  22. data/lib/chef/knife/oneandone_mp_list.rb +37 -0
  23. data/lib/chef/knife/oneandone_server_create.rb +163 -0
  24. data/lib/chef/knife/oneandone_server_delete.rb +63 -0
  25. data/lib/chef/knife/oneandone_server_hdd_add.rb +60 -0
  26. data/lib/chef/knife/oneandone_server_hdd_delete.rb +55 -0
  27. data/lib/chef/knife/oneandone_server_hdd_list.rb +44 -0
  28. data/lib/chef/knife/oneandone_server_hdd_resize.rb +59 -0
  29. data/lib/chef/knife/oneandone_server_list.rb +35 -0
  30. data/lib/chef/knife/oneandone_server_modify.rb +80 -0
  31. data/lib/chef/knife/oneandone_server_reboot.rb +41 -0
  32. data/lib/chef/knife/oneandone_server_rename.rb +37 -0
  33. data/lib/chef/knife/oneandone_server_size_list.rb +39 -0
  34. data/lib/chef/knife/oneandone_server_start.rb +35 -0
  35. data/lib/chef/knife/oneandone_server_stop.rb +41 -0
  36. data/lib/knife-oneandone/version.rb +5 -0
  37. metadata +191 -0
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'knife-oneandone/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'knife-oneandone'
8
+ spec.version = Knife::Oneandone::VERSION
9
+ spec.authors = ['Nurfet Becirevic']
10
+ spec.email = ['nurfet@stackpointcloud.com']
11
+
12
+ spec.summary = 'Chef Knife plugin for 1&1 Cloud server'
13
+ spec.description = 'Official Chef Knife plugin for managing 1&1 Cloud servers'
14
+ spec.homepage = 'https://github.com/1and1/oneandone-cloudserver-chef'
15
+ spec.license = 'Apache-2.0'
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ spec.bindir = 'exe'
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ['lib']
21
+
22
+ spec.add_runtime_dependency '1and1', '~> 1.1'
23
+ spec.add_runtime_dependency 'chef', '~> 12'
24
+
25
+ spec.add_development_dependency 'bundler', '~> 1.12'
26
+ spec.add_development_dependency 'rake', '~> 10.0'
27
+ spec.add_development_dependency 'rspec', '~> 3.0'
28
+ spec.add_development_dependency 'rubocop', '~> 0.39'
29
+ spec.add_development_dependency 'webmock', '~> 1.24'
30
+ spec.add_development_dependency 'vcr', '~> 3.0'
31
+ end
@@ -0,0 +1,29 @@
1
+ module Oneandone
2
+ module Helpers
3
+ def split_delimited_input(input)
4
+ return [] if input.nil?
5
+
6
+ if input.is_a? String
7
+ input.strip!
8
+
9
+ delimiter = ','
10
+ # Knife is reading --option value1,value2 as "value1 value2"
11
+ # on Windows PowerShell interface
12
+ delimiter = ' ' if input.include? ' '
13
+
14
+ values = input.split(delimiter)
15
+ values
16
+ else
17
+ # return array of whatever
18
+ [input]
19
+ end
20
+ end
21
+
22
+ def validate(param, msg)
23
+ if param.nil? || param.empty?
24
+ ui.error("You must supply #{msg}!")
25
+ exit 1
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,39 @@
1
+ require 'chef/knife/oneandone_base'
2
+
3
+ class Chef
4
+ class Knife
5
+ class OneandoneApplianceList < Knife
6
+ include Knife::OneandoneBase
7
+
8
+ banner 'knife oneandone appliance list'
9
+
10
+ def run
11
+ $stdout.sync = true
12
+
13
+ init_client
14
+
15
+ response = OneAndOne::ServerAppliance.new.list
16
+ formated_output(response, true)
17
+
18
+ sizes = [
19
+ ui.color('ID', :bold),
20
+ ui.color('Name', :bold),
21
+ ui.color('Type', :bold),
22
+ ui.color('OS', :bold),
23
+ ui.color('Version', :bold),
24
+ ui.color('Architecture', :bold)
25
+ ]
26
+ response.each do |fs|
27
+ sizes << fs['id']
28
+ sizes << fs['name']
29
+ sizes << fs['type']
30
+ sizes << fs['os']
31
+ sizes << fs['version']
32
+ sizes << fs['os_architecture'].to_s
33
+ end
34
+
35
+ puts ui.list(sizes, :uneven_columns_across, 6)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,32 @@
1
+ require 'chef/knife'
2
+
3
+ class Chef
4
+ class Knife
5
+ module OneandoneBase
6
+ def self.included(includer)
7
+ includer.class_eval do
8
+ deps do
9
+ require 'oneandone'
10
+ end
11
+
12
+ option :oneandone_api_key,
13
+ short: '-A API_KEY',
14
+ long: '--oneandone-api-key API_KEY',
15
+ description: 'Your 1&1 API access key',
16
+ proc: proc { |api_token| Chef::Config[:knife][:oneandone_api_key] = api_token }
17
+ end
18
+ end
19
+
20
+ def init_client
21
+ OneAndOne.start(Chef::Config[:knife][:oneandone_api_key])
22
+ end
23
+
24
+ def formated_output(data, is_exit)
25
+ if config[:format] != default_config[:format]
26
+ ui.output(data)
27
+ exit 0 if is_exit
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,33 @@
1
+ require 'chef/knife/oneandone_base'
2
+
3
+ class Chef
4
+ class Knife
5
+ class OneandoneDatacenterList < Knife
6
+ include Knife::OneandoneBase
7
+
8
+ banner 'knife oneandone datacenter list'
9
+
10
+ def run
11
+ $stdout.sync = true
12
+
13
+ init_client
14
+
15
+ response = OneAndOne::Datacenter.new.list
16
+ formated_output(response, true)
17
+
18
+ datacenters = [
19
+ ui.color('ID', :bold),
20
+ ui.color('Location', :bold),
21
+ ui.color('Country Code', :bold)
22
+ ]
23
+ response.each do |dc|
24
+ datacenters << dc['id']
25
+ datacenters << dc['location']
26
+ datacenters << dc['country_code']
27
+ end
28
+
29
+ puts ui.list(datacenters, :uneven_columns_across, 3)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,97 @@
1
+ require 'chef/knife/oneandone_base'
2
+ require '1and1/helpers'
3
+
4
+ class Chef
5
+ class Knife
6
+ class OneandoneFirewallCreate < Knife
7
+ include Knife::OneandoneBase
8
+ include Oneandone::Helpers
9
+
10
+ banner 'knife oneandone firewall create (options)'
11
+
12
+ option :name,
13
+ short: '-n NAME',
14
+ long: '--name NAME',
15
+ description: 'Name of the firewall (required)'
16
+
17
+ option :description,
18
+ long: '--description DESCRIPTION',
19
+ description: 'Description of the firewall'
20
+
21
+ option :port_from,
22
+ long: '--port-from [PORT_FROM]',
23
+ description: 'A comma separated list of the first firewall ports in range (80,161,443)'
24
+
25
+ option :port_to,
26
+ long: '--port-to [PORT_TO]',
27
+ description: 'A comma separated list of the second firewall ports in range (80,162,443)'
28
+
29
+ option :protocol,
30
+ short: '-p [PROTOCOL]',
31
+ long: '--protocol [PROTOCOL]',
32
+ description: 'A comma separated list of the firewall protocols (TCP,UDP,TCP/UDP,ICMP,IPSEC,GRE)'
33
+
34
+ option :source,
35
+ short: '-S [SOURCE_IP]',
36
+ long: '--source [SOURCE_IP]',
37
+ description: 'A comma separated list of the source IPs allowed to access though the firewall'
38
+
39
+ option :wait,
40
+ short: '-W',
41
+ long: '--wait',
42
+ description: 'Wait for the operation to complete.'
43
+
44
+ def run
45
+ $stdout.sync = true
46
+
47
+ validate(config[:name], '-n NAME')
48
+ validate(config[:protocol], 'at least one value for --protocol [PROTOCOL]')
49
+
50
+ protocols = split_delimited_input(config[:protocol])
51
+ ports_from = split_delimited_input(config[:port_from])
52
+ ports_to = split_delimited_input(config[:port_to])
53
+ sources = split_delimited_input(config[:source])
54
+
55
+ validate_rules(ports_from, ports_to, protocols)
56
+
57
+ rules = []
58
+
59
+ for i in 0..(protocols.length - 1)
60
+ rule = {
61
+ 'protocol' => protocols[i].upcase,
62
+ 'port_from' => ports_from[i].nil? ? nil : ports_from[i].to_i,
63
+ 'port_to' => ports_to[i].nil? ? nil : ports_to[i].to_i,
64
+ 'source' => sources[i]
65
+ }
66
+ rules << rule
67
+ end
68
+
69
+ init_client
70
+
71
+ firewall = OneAndOne::Firewall.new
72
+ response = firewall.create(name: config[:name], description: config[:description], rules: rules)
73
+
74
+ if config[:wait]
75
+ firewall.wait_for
76
+ formated_output(firewall.get, true)
77
+ puts "Firewall policy #{response['id']} is #{ui.color('created', :bold)}"
78
+ else
79
+ formated_output(response, true)
80
+ puts "Firewall policy #{response['id']} is #{ui.color('being created', :bold)}"
81
+ end
82
+ end
83
+
84
+ def validate_rules(ports_from, ports_to, protocols)
85
+ if ports_from.length != ports_to.length
86
+ ui.error('You must supply equal number of --port-from and --port-to values!')
87
+ exit 1
88
+ end
89
+
90
+ if protocols.length < ports_from.length
91
+ ui.error('It is required that the value count of --protocol >= --port-from value count!')
92
+ exit 1
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,59 @@
1
+ require 'chef/knife/oneandone_base'
2
+
3
+ class Chef
4
+ class Knife
5
+ class OneandoneFirewallDelete < Knife
6
+ include Knife::OneandoneBase
7
+
8
+ banner 'knife oneandone firewall delete FIREWALL_ID [FIREWALL_ID] (options)'
9
+
10
+ option :wait,
11
+ short: '-W',
12
+ long: '--wait',
13
+ description: 'Wait for the operation to complete.'
14
+
15
+ def run
16
+ $stdout.sync = true
17
+
18
+ init_client
19
+
20
+ name_args.each do |firewall_id|
21
+ firewall = OneAndOne::Firewall.new
22
+
23
+ begin
24
+ firewall.get(firewall_id: firewall_id)
25
+ rescue StandardError => e
26
+ if e.message.include? 'NOT_FOUND'
27
+ ui.error("Firewall ID #{firewall_id} not found. Skipping.")
28
+ else
29
+ ui.error(e.message)
30
+ end
31
+ next
32
+ end
33
+
34
+ firewall_name = firewall.specs['name']
35
+
36
+ confirm("Do you really want to delete firewall policy '#{firewall_name}'")
37
+
38
+ firewall.delete
39
+
40
+ if config[:wait]
41
+ begin
42
+ puts ui.color('Deleting, wait for the operation to complete...', :cyan).to_s
43
+ firewall.wait_for
44
+ puts "Firewall policy '#{firewall_name}' is #{ui.color('deleted', :bold)}"
45
+ rescue StandardError => e
46
+ if e.message.include? 'NOT_FOUND'
47
+ puts "Firewall policy '#{firewall_name}' is #{ui.color('deleted', :bold)}"
48
+ else
49
+ ui.error(e.message)
50
+ end
51
+ end
52
+ else
53
+ puts "Firewall policy '#{firewall_name}' is #{ui.color('being deleted', :bold)}"
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,33 @@
1
+ require 'chef/knife/oneandone_base'
2
+
3
+ class Chef
4
+ class Knife
5
+ class OneandoneFirewallList < Knife
6
+ include Knife::OneandoneBase
7
+
8
+ banner 'knife oneandone firewall list'
9
+
10
+ def run
11
+ $stdout.sync = true
12
+
13
+ init_client
14
+
15
+ response = OneAndOne::Firewall.new.list
16
+ formated_output(response, true)
17
+
18
+ firewall_list = [
19
+ ui.color('ID', :bold),
20
+ ui.color('Name', :bold),
21
+ ui.color('State', :bold)
22
+ ]
23
+ response.each do |fw|
24
+ firewall_list << fw['id']
25
+ firewall_list << fw['name']
26
+ firewall_list << fw['state']
27
+ end
28
+
29
+ puts ui.list(firewall_list, :uneven_columns_across, 3)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,39 @@
1
+ require 'chef/knife/oneandone_base'
2
+
3
+ class Chef
4
+ class Knife
5
+ class OneandoneIpList < Knife
6
+ include Knife::OneandoneBase
7
+
8
+ banner 'knife oneandone ip list'
9
+
10
+ def run
11
+ $stdout.sync = true
12
+
13
+ init_client
14
+
15
+ response = OneAndOne::PublicIP.new.list
16
+ formated_output(response, true)
17
+
18
+ ip_list = [
19
+ ui.color('ID', :bold),
20
+ ui.color('IP Address', :bold),
21
+ ui.color('DHCP', :bold),
22
+ ui.color('State', :bold),
23
+ ui.color('Data Center', :bold),
24
+ ui.color('Assigned To', :bold)
25
+ ]
26
+ response.each do |ip|
27
+ ip_list << ip['id']
28
+ ip_list << ip['ip']
29
+ ip_list << ip['is_dhcp'].to_s
30
+ ip_list << ip['state']
31
+ ip_list << ip['datacenter']['country_code']
32
+ ip_list << (ip['assigned_to'].nil? ? '' : ip['assigned_to']['name'])
33
+ end
34
+
35
+ puts ui.list(ip_list, :uneven_columns_across, 6)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,147 @@
1
+ require 'chef/knife/oneandone_base'
2
+ require '1and1/helpers'
3
+
4
+ class Chef
5
+ class Knife
6
+ class OneandoneLoadbalancerCreate < Knife
7
+ include Knife::OneandoneBase
8
+ include Oneandone::Helpers
9
+
10
+ banner 'knife oneandone loadbalancer create (options)'
11
+
12
+ option :datacenter_id,
13
+ short: '-D DATACENTER_ID',
14
+ long: '--datacenter-id DATACENTER_ID',
15
+ description: 'ID of the virtual data center',
16
+ proc: proc { |datacenter_id| Chef::Config[:knife][:datacenter_id] = datacenter_id }
17
+
18
+ option :name,
19
+ short: '-n NAME',
20
+ long: '--name NAME',
21
+ description: 'Name of the load balancer (required)'
22
+
23
+ option :description,
24
+ long: '--description DESCRIPTION',
25
+ description: 'Description of the load balancer'
26
+
27
+ option :health_check,
28
+ long: '--health-check HEALTH_CHECK',
29
+ description: 'Health check test: TCP, ICMP or NONE. Default is TCP.',
30
+ default: 'TCP'
31
+
32
+ option :health_int,
33
+ long: '--health-int HEALTH_INTERVAL',
34
+ description: 'Health check interval (sec.). Default is 15.',
35
+ default: 15
36
+
37
+ option :health_path,
38
+ long: '--health-path HEALTH_PATH',
39
+ description: 'URL to call for the health checking'
40
+
41
+ option :health_regex,
42
+ long: '--health-regex HEALTH_REGEX',
43
+ description: 'Regular expression to check for the health checking'
44
+
45
+ option :method,
46
+ short: '-m METHOD',
47
+ long: '--method METHOD',
48
+ description: 'Balancing procedure: ROUND_ROBIN or LEAST_CONNECTIONS. Default is ROUND_ROBIN.',
49
+ default: 'ROUND_ROBIN'
50
+
51
+ option :persistence,
52
+ short: '-P',
53
+ long: '--persistence',
54
+ description: 'Enable load balancer persistence (true by default)',
55
+ boolean: true,
56
+ default: true
57
+
58
+ option :persistence_int,
59
+ long: '--persistence-int PERSISTENCE_INTERVAL',
60
+ description: 'Persistence interval (sec.). Default is 1200.',
61
+ default: 1200
62
+
63
+ option :port_balancer,
64
+ long: '--port-balancer [PORT_BALANCER]',
65
+ description: 'A comma separated list of the load balancer ports (80,161,443)'
66
+
67
+ option :port_server,
68
+ long: '--port-server [PORT_SERVER]',
69
+ description: 'A comma separated list of the servers ports (8080,161,443)'
70
+
71
+ option :protocol,
72
+ short: '-p [PROTOCOL]',
73
+ long: '--protocol [PROTOCOL]',
74
+ description: 'A comma separated list of the load balancer protocols (TCP,UDP)'
75
+
76
+ option :source,
77
+ short: '-S [SOURCE_IP]',
78
+ long: '--source [SOURCE_IP]',
79
+ description: 'A comma separated list of the source IPs from which the access is allowed'
80
+
81
+ option :wait,
82
+ short: '-W',
83
+ long: '--wait',
84
+ description: 'Wait for the operation to complete.'
85
+
86
+ def run
87
+ $stdout.sync = true
88
+
89
+ validate(config[:name], '-n NAME')
90
+ validate(config[:protocol], 'at least one value for --protocol [PROTOCOL]')
91
+
92
+ protocols = split_delimited_input(config[:protocol])
93
+ ports_balancer = split_delimited_input(config[:port_balancer])
94
+ ports_server = split_delimited_input(config[:port_server])
95
+ sources = split_delimited_input(config[:source])
96
+
97
+ validate_rules(protocols, ports_balancer, ports_server)
98
+
99
+ rules = []
100
+
101
+ for i in 0..(protocols.length - 1)
102
+ rule = {
103
+ 'protocol' => protocols[i].upcase,
104
+ 'port_balancer' => ports_balancer[i].to_i,
105
+ 'port_server' => ports_server[i].to_i,
106
+ 'source' => sources[i]
107
+ }
108
+ rules << rule
109
+ end
110
+
111
+ init_client
112
+
113
+ load_balancer = OneAndOne::LoadBalancer.new
114
+ response = load_balancer.create(
115
+ name: config[:name],
116
+ description: config[:description],
117
+ health_check_test: config[:health_check].to_s.upcase,
118
+ health_check_interval: config[:health_int].to_i,
119
+ persistence: config[:persistence],
120
+ persistence_time: config[:persistence_int].to_i,
121
+ method: config[:method].to_s.upcase,
122
+ rules: rules,
123
+ health_check_path: config[:health_path],
124
+ health_check_parse: config[:health_regex],
125
+ datacenter_id: config[:datacenter_id]
126
+ )
127
+
128
+ if config[:wait]
129
+ puts ui.color('Creating, wait for the operation to complete...', :cyan).to_s
130
+ load_balancer.wait_for
131
+ formated_output(load_balancer.get, true)
132
+ puts "Load balancer #{response['id']} is #{ui.color('created', :bold)}"
133
+ else
134
+ formated_output(response, true)
135
+ puts "Load balancer #{response['id']} is #{ui.color('being created', :bold)}"
136
+ end
137
+ end
138
+
139
+ def validate_rules(protocols, ports_balancer, ports_server)
140
+ if (ports_balancer.length != ports_server.length) || (ports_balancer.length != protocols.length)
141
+ ui.error('You must supply equal number of --protocol, --port-balancer and --port-server values!')
142
+ exit 1
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end