knife-oneandone 1.0.0

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