easy_manager 0.9.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a183ff2d7a5d75f1f6f75bb9aecae632bd834cf85e42f5eb54ccca4a5b1fc05c
4
+ data.tar.gz: 7329205dad75e774fdffe1c09d83969a953540062888ac392824e36add60e7fc
5
+ SHA512:
6
+ metadata.gz: 9875faf48edbdeac8d47bfdcd7f2d46980cfb78b6493b513fce019135ad100748551662c4a8b13b376639bd5bb25585db3d21e74d73a8c2dc08e1549f7cd085a
7
+ data.tar.gz: e47ef66b3b3bab4a3e4f76b09397c163483678d958fc2089333b7239607dcbde83c0b27307126089cf36aa14ed4dc5a277d5206e30cf4ccdd74536a2c89f350d
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'typhoeus'
4
+
5
+ require_relative 'easymanager/scaleway/main'
6
+ require_relative 'easymanager/ssh'
7
+ require_relative 'easymanager/utilities'
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ class EasyManager
4
+ class Scaleway
5
+ # Methods to retrieve information specific to Scaleway
6
+ class Config
7
+ def self.srv_infos(srv_type)
8
+ srv_infos = {
9
+ 'DEV1-S' => { volume: 20_000_000_000, volume_type: 'l_ssd' },
10
+ 'DEV1-M' => { volume: 40_000_000_000, volume_type: 'l_ssd' },
11
+ 'DEV1-L' => { volume: 80_000_000_000, volume_type: 'l_ssd' },
12
+ 'DEV1-XL' => { volume: 120_000_000_000, volume_type: 'l_ssd' }
13
+ }
14
+ srv_infos[srv_type]
15
+ end
16
+
17
+ def self.image_id(image)
18
+ image_id = {
19
+ 'ubuntu-jammy' => '2289fad9-2694-48ab-bb41-f19e4a9a8584',
20
+ 'debian-buster' => '6d124a42-de28-493f-933b-85a0df5552eb'
21
+ }
22
+ image_id[image]
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ class EasyManager
4
+ class Scaleway
5
+ # Specific method for ips management
6
+ # https://developers.scaleway.com/en/products/instance/api/#ips-268151
7
+ class Ips
8
+ def self.reserve(scw)
9
+ data = { project: scw.project }
10
+
11
+ response = Typhoeus.post(
12
+ File.join(scw.api_url, "instance/v1/zones/#{scw.zone}/ips"),
13
+ headers: scw.headers,
14
+ body: data.to_json
15
+ )
16
+ return unless response&.code == 201
17
+
18
+ Utilities.parse_json(response.body)
19
+ end
20
+
21
+ def self.delete(scw, ip_id)
22
+ Typhoeus.delete(
23
+ File.join(scw.api_url, "/instance/v1/zones/#{scw.zone}/ips/#{ip_id}"),
24
+ headers: scw.headers
25
+ )
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'date'
4
+ require_relative 'servers'
5
+ require_relative '../ssh'
6
+
7
+ class EasyManager
8
+ # Scaleway Class with global methods
9
+ class Scaleway
10
+ attr_reader :provider, :zone, :project, :api_url, :headers, :secret_token
11
+
12
+ def initialize(options)
13
+ @secret_token = options[:secret_token]
14
+ @zone = options[:zone]
15
+ @project = options[:project]
16
+ @api_url = 'https://api.scaleway.com/'
17
+ @headers = { 'X-Auth-Token' => @secret_token, 'Content-Type' => 'application/json' }
18
+ end
19
+
20
+ def list
21
+ Servers.list(self)
22
+ end
23
+
24
+ def create(options)
25
+ srv_type = options[:srv_type] || 'DEV1-S'
26
+ image = options[:image] || 'ubuntu-jammy'
27
+ name_pattern = options[:name_pattern] || 'scw-easymanager-__RANDOM__'
28
+ cloud_init = options[:cloud_init] || false
29
+
30
+ Servers.create(self, srv_type, image, name_pattern, cloud_init)
31
+ end
32
+
33
+ def delete(srv)
34
+ Servers.delete(self, srv['id'], srv['public_ip']['id'])
35
+ end
36
+
37
+ def delete_by_id(id)
38
+ servers = list
39
+
40
+ servers.each { |server| delete(server) if server['id'] == id }
41
+ end
42
+
43
+ def status(srv)
44
+ Servers.status(self, srv['id'])
45
+ end
46
+
47
+ def srv_ready?(srv, ssh)
48
+ Servers.ready?(self, srv, ssh, srv_ready_cmds)
49
+ end
50
+
51
+ def wait_until_ready!(srv, ssh, timeout = 300)
52
+ ready = false
53
+ start = Time.now
54
+ loop do
55
+ ready = srv_ready?(srv, ssh)
56
+ break if ready || Utilities.elapsed_times(Time.now.to_s, start.to_s) >= timeout
57
+
58
+ sleep(30)
59
+ end
60
+
61
+ ready
62
+ end
63
+
64
+ private
65
+
66
+ def srv_ready_cmds
67
+ check_cloud_init_cmd = "test -f '/var/log/cloud-init.log' && echo true"
68
+ cloud_init_ready_cmd = 'tail -1 /var/log/cloud-init-output.log'
69
+
70
+ [check_cloud_init_cmd, cloud_init_ready_cmd, 'hostname']
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'config'
4
+ require_relative 'ips'
5
+
6
+ class EasyManager
7
+ class Scaleway
8
+ # Specific method for server management
9
+ # https://developers.scaleway.com/en/products/instance/api/#servers-8bf7d7
10
+ class Servers
11
+ def self.list(scw)
12
+ response = Typhoeus.get(
13
+ File.join(scw.api_url, "instance/v1/zones/#{scw.zone}/servers"),
14
+ headers: scw.headers
15
+ )
16
+ return unless response&.code == 200
17
+
18
+ json_body = Utilities.parse_json(response.body)
19
+ return unless json_body
20
+
21
+ json_body['servers']
22
+ end
23
+
24
+ def self.srv_up?(scw, srv)
25
+ status(scw, srv['id']) == 'running' && Utilities.elapsed_times(Time.now.to_s, srv['creation_date']) > 90
26
+ end
27
+
28
+ def self.ready?(scw, srv, ssh, cmds)
29
+ return unless srv_up?(scw, srv)
30
+
31
+ cmd_values = SSH.cmd_exec(ssh, srv, cmds)
32
+ if cmd_values[cmds[0]].empty?
33
+ cmd_values['hostname'] == srv['hostname']
34
+ else
35
+ cmd_values[cmds[1]].include?('The system is finally up')
36
+ end
37
+ end
38
+
39
+ def self.status(scw, srv_id)
40
+ response = Typhoeus.get(
41
+ File.join(scw.api_url, "/instance/v1/zones/#{scw.zone}/servers/#{srv_id}"),
42
+ headers: scw.headers
43
+ )
44
+ return unless response&.code == 200
45
+
46
+ json_body = Utilities.parse_json(response.body)
47
+ return unless json_body
48
+
49
+ json_body['server']['state']
50
+ end
51
+
52
+ def self.create(scw, srv_type, image, name_pattern, cloud_init)
53
+ data = srv_data(scw, srv_type, image, name_pattern)
54
+ return if data.nil?
55
+
56
+ response = Typhoeus.post(File.join(scw.api_url, "/instance/v1/zones/#{scw.zone}/servers/"),
57
+ headers: scw.headers, body: data.to_json)
58
+ return unless response&.code == 201
59
+
60
+ body_json = Utilities.parse_json(response.body)
61
+ return unless body_json
62
+
63
+ srv_id = body_json['server']['id']
64
+ launch(scw, srv_id, cloud_init)
65
+
66
+ body_json['server']
67
+ end
68
+
69
+ def self.delete(scw, srv_id, srv_ip_id)
70
+ Ips.delete(scw, srv_ip_id)
71
+ action(scw, srv_id, 'terminate')
72
+ end
73
+
74
+ def self.launch(scw, srv_id, cloud_init)
75
+ add_cloud_init(scw, srv_id, cloud_init) if cloud_init
76
+ action(scw, srv_id, 'poweron')
77
+ end
78
+
79
+ def self.add_cloud_init(scw, srv_id, cloud_init)
80
+ data = Utilities.file_read(cloud_init)
81
+ return if data.nil?
82
+
83
+ Typhoeus.patch(
84
+ File.join(scw.api_url, "/instance/v1/zones/#{scw.zone}/servers/#{srv_id}/user_data/cloud-init"),
85
+ headers: { 'X-Auth-Token' => scw.secret_token, 'Content-Type' => 'text/plain' },
86
+ body: data
87
+ )
88
+ end
89
+
90
+ def self.action(scw, srv_id, action)
91
+ data = { action: action }
92
+ Typhoeus.post(
93
+ File.join(scw.api_url, "/instance/v1/zones/#{scw.zone}/servers/#{srv_id}/action"),
94
+ headers: scw.headers,
95
+ body: data.to_json
96
+ )
97
+ end
98
+
99
+ def self.srv_data(scw, srv_type, image, name_pattern)
100
+ srv_infos = Config.srv_infos(srv_type)
101
+ image_id = Config.image_id(image)
102
+ new_ip = Ips.reserve(scw)
103
+ return if image_id.nil? || srv_infos.nil? || new_ip.nil?
104
+
105
+ {
106
+ name: name_pattern.gsub('__RANDOM__', Utilities.random_string), commercial_type: srv_type,
107
+ public_ip: new_ip['ip']['id'], project: scw.project,
108
+ image: image_id,
109
+ volumes: { '0' => { size: srv_infos[:volume], volume_type: srv_infos[:volume_type] } }
110
+ }
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'net/ssh'
4
+ require 'net/scp'
5
+
6
+ class EasyManager
7
+ # SSH Class
8
+ class SSH
9
+ attr_reader :username, :ssh_key
10
+
11
+ def initialize(options = {})
12
+ @username = options[:username] || 'root'
13
+ @ssh_key = options[:ssh_key] || '~/.ssh/id_rsa'
14
+ end
15
+
16
+ def self.cmd_exec(ssh, srv, cmds)
17
+ cmd_values = {}
18
+
19
+ Net::SSH.start(srv['public_ip']['address'], ssh.username, keys: ssh.ssh_key) do |shell|
20
+ cmds.each do |cmd|
21
+ cmd_values[cmd] = shell.exec!(cmd).chomp
22
+ end
23
+ end
24
+
25
+ cmd_values
26
+ rescue Net::SSH::AuthenticationFailed
27
+ nil
28
+ end
29
+
30
+ def self.scp(ssh, srv, files)
31
+ Net::SCP.start(srv['public_ip']['address'], ssh.username, keys: ssh.ssh_key) do |shell|
32
+ files.each do |name, infos|
33
+ shell.upload! name, infos[:remote], recursive: infos[:recursive] || false
34
+ end
35
+ end
36
+ rescue Net::SSH::AuthenticationFailed
37
+ nil
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ # Utility method that can be reused in the code
6
+ class Utilities
7
+ def self.parse_json(content)
8
+ JSON.parse(content)
9
+ rescue JSON::ParseError
10
+ nil
11
+ end
12
+
13
+ def self.random_string
14
+ (0...8).map { rand(97..122).chr }.join
15
+ end
16
+
17
+ def self.file_read(file)
18
+ File.read(file)
19
+ rescue (Errno::ENOENT)
20
+ nil
21
+ end
22
+
23
+ def self.elapsed_times(first, second)
24
+ ((DateTime.parse(first) - DateTime.parse(second)) * 24 * 60 * 60).to_i
25
+ end
26
+ end
metadata ADDED
@@ -0,0 +1,152 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: easy_manager
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.9.1
5
+ platform: ruby
6
+ authors:
7
+ - Joshua MARTINELLE
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-08-30 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bcrypt_pbkdf
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.1'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 1.1.0
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '1.1'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 1.1.0
33
+ - !ruby/object:Gem::Dependency
34
+ name: ed25519
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '1.3'
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: 1.3.0
43
+ type: :runtime
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - "~>"
48
+ - !ruby/object:Gem::Version
49
+ version: '1.3'
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: 1.3.0
53
+ - !ruby/object:Gem::Dependency
54
+ name: net-scp
55
+ requirement: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - "~>"
58
+ - !ruby/object:Gem::Version
59
+ version: 4.0.0.rc1
60
+ type: :runtime
61
+ prerelease: false
62
+ version_requirements: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - "~>"
65
+ - !ruby/object:Gem::Version
66
+ version: 4.0.0.rc1
67
+ - !ruby/object:Gem::Dependency
68
+ name: net-ssh
69
+ requirement: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - "~>"
72
+ - !ruby/object:Gem::Version
73
+ version: 7.0.0beta1
74
+ type: :runtime
75
+ prerelease: false
76
+ version_requirements: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - "~>"
79
+ - !ruby/object:Gem::Version
80
+ version: 7.0.0beta1
81
+ - !ruby/object:Gem::Dependency
82
+ name: typhoeus
83
+ requirement: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - "~>"
86
+ - !ruby/object:Gem::Version
87
+ version: '1.4'
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: 1.4.0
91
+ type: :runtime
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - "~>"
96
+ - !ruby/object:Gem::Version
97
+ version: '1.4'
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ version: 1.4.0
101
+ - !ruby/object:Gem::Dependency
102
+ name: x25519
103
+ requirement: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - '='
106
+ - !ruby/object:Gem::Version
107
+ version: 1.0.9
108
+ type: :runtime
109
+ prerelease: false
110
+ version_requirements: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - '='
113
+ - !ruby/object:Gem::Version
114
+ version: 1.0.9
115
+ description:
116
+ email:
117
+ - contact@jomar.fr
118
+ executables: []
119
+ extensions: []
120
+ extra_rdoc_files: []
121
+ files:
122
+ - lib/easy_manager.rb
123
+ - lib/easymanager/scaleway/config.rb
124
+ - lib/easymanager/scaleway/ips.rb
125
+ - lib/easymanager/scaleway/main.rb
126
+ - lib/easymanager/scaleway/servers.rb
127
+ - lib/easymanager/ssh.rb
128
+ - lib/easymanager/utilities.rb
129
+ homepage: https://rubygems.org/gems/easymanager
130
+ licenses:
131
+ - MIT
132
+ metadata: {}
133
+ post_install_message:
134
+ rdoc_options: []
135
+ require_paths:
136
+ - lib
137
+ required_ruby_version: !ruby/object:Gem::Requirement
138
+ requirements:
139
+ - - ">="
140
+ - !ruby/object:Gem::Version
141
+ version: 2.7.1
142
+ required_rubygems_version: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - ">="
145
+ - !ruby/object:Gem::Version
146
+ version: '0'
147
+ requirements: []
148
+ rubygems_version: 3.1.2
149
+ signing_key:
150
+ specification_version: 4
151
+ summary: Cloud Server Manager Library
152
+ test_files: []