easy_manager 0.9.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/easy_manager.rb +7 -0
- data/lib/easymanager/scaleway/config.rb +26 -0
- data/lib/easymanager/scaleway/ips.rb +29 -0
- data/lib/easymanager/scaleway/main.rb +73 -0
- data/lib/easymanager/scaleway/servers.rb +114 -0
- data/lib/easymanager/ssh.rb +40 -0
- data/lib/easymanager/utilities.rb +26 -0
- metadata +152 -0
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
|
data/lib/easy_manager.rb
ADDED
@@ -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: []
|