cloudkeeper-aws 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.gitmodules +3 -0
- data/.rubocop.yml +48 -0
- data/.travis.yml +22 -0
- data/Gemfile +5 -0
- data/LICENSE +674 -0
- data/README.md +91 -0
- data/Rakefile +17 -0
- data/bin/cloudkeeper-aws +5 -0
- data/cloudkeeper-aws.gemspec +46 -0
- data/codecov.yml +4 -0
- data/config/cloudkeeper-aws.yml +17 -0
- data/lib/cloudkeeper/aws.rb +16 -0
- data/lib/cloudkeeper/aws/backend_executor.rb +76 -0
- data/lib/cloudkeeper/aws/cli.rb +159 -0
- data/lib/cloudkeeper/aws/cloud.rb +173 -0
- data/lib/cloudkeeper/aws/core_connector.rb +108 -0
- data/lib/cloudkeeper/aws/errors.rb +11 -0
- data/lib/cloudkeeper/aws/errors/backend.rb +14 -0
- data/lib/cloudkeeper/aws/errors/backend/appliance_not_found_error.rb +9 -0
- data/lib/cloudkeeper/aws/errors/backend/backend_error.rb +9 -0
- data/lib/cloudkeeper/aws/errors/backend/image_import_error.rb +9 -0
- data/lib/cloudkeeper/aws/errors/backend/multiple_appliances_found_error.rb +9 -0
- data/lib/cloudkeeper/aws/errors/backend/timeout_error.rb +9 -0
- data/lib/cloudkeeper/aws/errors/image_download_error.rb +7 -0
- data/lib/cloudkeeper/aws/errors/invalid_configuration_error.rb +7 -0
- data/lib/cloudkeeper/aws/errors/standard_error.rb +7 -0
- data/lib/cloudkeeper/aws/filter_helper.rb +33 -0
- data/lib/cloudkeeper/aws/image_downloader.rb +52 -0
- data/lib/cloudkeeper/aws/proto_helper.rb +70 -0
- data/lib/cloudkeeper/aws/settings.rb +18 -0
- data/lib/cloudkeeper/aws/version.rb +5 -0
- data/lib/cloudkeeper_grpc.rb +7 -0
- data/lib/cloudkeeper_grpc/.gitmodules +6 -0
- data/lib/cloudkeeper_grpc/CODE_OF_CONDUCT.md +49 -0
- data/lib/cloudkeeper_grpc/LICENSE.txt +17 -0
- data/lib/cloudkeeper_grpc/README.md +9 -0
- data/lib/cloudkeeper_grpc/cloudkeeper_pb.rb +59 -0
- data/lib/cloudkeeper_grpc/cloudkeeper_services_pb.rb +31 -0
- data/lib/cloudkeeper_grpc/constants.rb +10 -0
- data/lib/cloudkeeper_grpc/protos/CODE_OF_CONDUCT.md +49 -0
- data/lib/cloudkeeper_grpc/protos/LICENSE.txt +17 -0
- data/lib/cloudkeeper_grpc/protos/README.md +3 -0
- data/lib/cloudkeeper_grpc/protos/cloudkeeper.proto +64 -0
- data/lib/cloudkeeper_grpc/status-codes/CODE_OF_CONDUCT.md +49 -0
- data/lib/cloudkeeper_grpc/status-codes/LICENSE.txt +13 -0
- data/lib/cloudkeeper_grpc/status-codes/README.md +3 -0
- data/lib/cloudkeeper_grpc/status-codes/status-codes.yml +12 -0
- metadata +303 -0
@@ -0,0 +1,108 @@
|
|
1
|
+
module Cloudkeeper
|
2
|
+
module Aws
|
3
|
+
# Class implementing GRPC procedures
|
4
|
+
class CoreConnector < CloudkeeperGrpc::Communicator::Service
|
5
|
+
attr_accessor :cloud
|
6
|
+
|
7
|
+
include Cloudkeeper::Aws::BackendExecutor
|
8
|
+
|
9
|
+
ERRORS = {
|
10
|
+
Cloudkeeper::Aws::Errors::Backend::ApplianceNotFoundError \
|
11
|
+
=> CloudkeeperGrpc::Constants::STATUS_CODE_APPLIANCE_NOT_FOUND,
|
12
|
+
Cloudkeeper::Aws::Errors::Backend::MultipleAppliancesFoundError \
|
13
|
+
=> CloudkeeperGrpc::Constants::STATUS_CODE_INVALID_RESOURCE_STATE,
|
14
|
+
Cloudkeeper::Aws::Errors::Backend::ImageImportError \
|
15
|
+
=> CloudkeeperGrpc::Constants::STATUS_CODE_FAILED_APPLIANCE_TRANSFER,
|
16
|
+
Cloudkeeper::Aws::Errors::Backend::TimeoutError \
|
17
|
+
=> CloudkeeperGrpc::Constants::STATUS_CODE_FAILED_APPLIANCE_TRANSFER,
|
18
|
+
Cloudkeeper::Aws::Errors::Backend::BackendError \
|
19
|
+
=> CloudkeeperGrpc::Constants::STATUS_CODE_UNKNOWN,
|
20
|
+
Cloudkeeper::Aws::Errors::ImageDownloadError \
|
21
|
+
=> CloudkeeperGrpc::Constants::STATUS_CODE_UNKNOWN
|
22
|
+
}.freeze
|
23
|
+
|
24
|
+
def initialize(cloud)
|
25
|
+
@cloud = cloud
|
26
|
+
super()
|
27
|
+
end
|
28
|
+
|
29
|
+
def handle_error
|
30
|
+
yield
|
31
|
+
rescue Cloudkeeper::Aws::Errors::StandardError => e
|
32
|
+
logger.error { "Error #{e.class} with message #{e.message}" }
|
33
|
+
raise GRPC::BadStatus.new(ERRORS[e.class], e.message)
|
34
|
+
rescue ::StandardError => e
|
35
|
+
logger.error { "Standard error #{e.class} with message #{e.message}" }
|
36
|
+
raise GRPC::BadStatus.new(CloudkeeperGrpc::Constants::STATUS_CODE_UNKNOWN, e.message)
|
37
|
+
end
|
38
|
+
|
39
|
+
def pre_action(_empty, _call)
|
40
|
+
logger.debug { 'GRPC pre action' }
|
41
|
+
Google::Protobuf::Empty.new
|
42
|
+
end
|
43
|
+
|
44
|
+
def post_action(_empty, _call)
|
45
|
+
logger.debug { 'GRPC post action' }
|
46
|
+
Google::Protobuf::Empty.new
|
47
|
+
end
|
48
|
+
|
49
|
+
def add_appliance(appliance, _call)
|
50
|
+
logger.debug { "GRPC add appliance #{appliance.identifier}" }
|
51
|
+
handle_error do
|
52
|
+
register_appliance(appliance)
|
53
|
+
Google::Protobuf::Empty.new
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def update_appliance(appliance, _call)
|
58
|
+
logger.debug { "GRPC update appliance #{appliance.identifier}" }
|
59
|
+
handle_error do
|
60
|
+
modify_appliance(appliance)
|
61
|
+
Google::Protobuf::Empty.new
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def update_appliance_metadata(appliance, _call)
|
66
|
+
logger.debug { "GRPC update appliance metadata #{appliance.identifier}" }
|
67
|
+
handle_error do
|
68
|
+
change_tags(appliance)
|
69
|
+
Google::Protobuf::Empty.new
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def remove_appliance(appliance, _call)
|
74
|
+
logger.debug { "GRPC remove appliance #{appliance.identifier}" }
|
75
|
+
handle_error do
|
76
|
+
deregister_image(appliance)
|
77
|
+
Google::Protobuf::Empty.new
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def remove_image_list(image_list_identifier, _call)
|
82
|
+
logger.debug { "GRPC remove image list with id: #{image_list_identifier.image_list_identifier}" }
|
83
|
+
handle_error do
|
84
|
+
deregister_image_list(image_list_identifier)
|
85
|
+
Google::Protobuf::Empty.new
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def image_lists(_empty, _call)
|
90
|
+
logger.debug { 'GRPC image lists' }
|
91
|
+
handle_error { list_image_lists.each }
|
92
|
+
end
|
93
|
+
|
94
|
+
def appliances(image_list_identifier, _call)
|
95
|
+
logger.debug { "GRPC appliances for: #{image_list_identifier.image_list_identifier}" }
|
96
|
+
handle_error { fetch_appliances(image_list_identifier).each }
|
97
|
+
end
|
98
|
+
|
99
|
+
def remove_expired_appliances(_empty, _call)
|
100
|
+
logger.debug { 'GRPC remove expired appliances' }
|
101
|
+
handle_error do
|
102
|
+
deregister_expired_appliances
|
103
|
+
Google::Protobuf::Empty.new
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Cloudkeeper
|
2
|
+
module Aws
|
3
|
+
# Module containing error classes for Cloudkeeper-aws
|
4
|
+
module Errors
|
5
|
+
autoload :StandardError, 'cloudkeeper/aws/errors/standard_error'
|
6
|
+
autoload :ImageDownloadError, 'cloudkeeper/aws/errors/image_download_error'
|
7
|
+
autoload :InvalidConfigurationError, 'cloudkeeper/aws/errors/invalid_configuration_error'
|
8
|
+
autoload :Backend, 'cloudkeeper/aws/errors/backend'
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Cloudkeeper
|
2
|
+
module Aws
|
3
|
+
module Errors
|
4
|
+
# Module used for errors raised by AWS backend
|
5
|
+
module Backend
|
6
|
+
autoload :BackendError, 'cloudkeeper/aws/errors/backend/backend_error'
|
7
|
+
autoload :TimeoutError, 'cloudkeeper/aws/errors/backend/timeout_error'
|
8
|
+
autoload :ImageImportError, 'cloudkeeper/aws/errors/backend/image_import_error'
|
9
|
+
autoload :ApplianceNotFoundError, 'cloudkeeper/aws/errors/backend/appliance_not_found_error'
|
10
|
+
autoload :MultipleAppliancesFoundError, 'cloudkeeper/aws/errors/backend/multiple_appliances_found_error'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Cloudkeeper
|
2
|
+
module Aws
|
3
|
+
# Class used for generating filter structures used by AWS .describe_tags
|
4
|
+
class FilterHelper
|
5
|
+
TAG_APPLIANCE_IMAGE_LIST_IDENTIFIER = 'cloudkeeper_appliance_image_list_identifier'.freeze
|
6
|
+
TAG_APPLIANCE_IDENTIFIER = 'cloudkeeper_appliance_identifier'.freeze
|
7
|
+
TAG_CLOUDKEEPER_IDENTIFIER = 'cloudkeeper_identifier'.freeze
|
8
|
+
|
9
|
+
def self.all_image_lists
|
10
|
+
[{ name: 'tag-key', values: [TAG_APPLIANCE_IMAGE_LIST_IDENTIFIER] }] + cloudkeeper_instance
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.image_list(image_list_identifier)
|
14
|
+
[{ name: "tag:#{TAG_APPLIANCE_IMAGE_LIST_IDENTIFIER}",
|
15
|
+
values: [image_list_identifier] }] + cloudkeeper_instance
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.appliance(identifier)
|
19
|
+
[{ name: "tag:#{TAG_APPLIANCE_IDENTIFIER}",
|
20
|
+
values: [identifier] }] + cloudkeeper_instance
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.image(image_id)
|
24
|
+
[{ name: 'image-id', values: [image_id] }] + cloudkeeper_instance
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.cloudkeeper_instance
|
28
|
+
[{ name: "tag:#{TAG_CLOUDKEEPER_IDENTIFIER}",
|
29
|
+
values: [Cloudkeeper::Aws::Settings['identifier']] }]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'net/https'
|
2
|
+
|
3
|
+
module Cloudkeeper
|
4
|
+
module Aws
|
5
|
+
# Class used for downloading images for cloudkeeper
|
6
|
+
class ImageDownloader
|
7
|
+
# Downloads file from uri by segments
|
8
|
+
#
|
9
|
+
# @param image_uri [String] uri of the image to download
|
10
|
+
# @param username [String] authentication username
|
11
|
+
# @param password [String] authentication password
|
12
|
+
# @param limit [Number] redirect limit to handle redirect infinite loops
|
13
|
+
# @yield [segment] data segment
|
14
|
+
# @raise [Cloudkeeper::Aws:Errors::ImageDownloadError] if download failed
|
15
|
+
def self.download(image_uri, username = nil, password = nil, limit = 10, &block)
|
16
|
+
raise Cloudkeeper::Aws::Errors::ImageDownloadError, 'Too many redirects' \
|
17
|
+
if limit.zero?
|
18
|
+
|
19
|
+
uri = URI.parse(image_uri)
|
20
|
+
Net::HTTP.start(uri.host, uri.port) do |http|
|
21
|
+
request = Net::HTTP::Get.new(uri)
|
22
|
+
request.basic_auth username, password
|
23
|
+
http.request(request) do |resp|
|
24
|
+
handle_response(resp, username, password, limit, &block)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
rescue URI::InvalidURIError => e
|
28
|
+
raise Cloudkeeper::Aws::Errors::ImageDownloadError, e.message
|
29
|
+
end
|
30
|
+
|
31
|
+
# Method used for handeling responses from download requests.
|
32
|
+
# It handles redirects as well as failures.
|
33
|
+
#
|
34
|
+
# @param resp [HTTP::Response] response to handle
|
35
|
+
# @param username [String] authentication username
|
36
|
+
# @param password [String] authentication password
|
37
|
+
# @param limit [Number] redirect limit
|
38
|
+
# @yield [segment] data segment
|
39
|
+
def self.handle_response(resp, username, password, limit, &block)
|
40
|
+
case resp
|
41
|
+
when Net::HTTPRedirection then
|
42
|
+
download(resp['location'], username, password, limit - 1, &block)
|
43
|
+
when Net::HTTPSuccess then
|
44
|
+
resp.read_body(&block)
|
45
|
+
else
|
46
|
+
raise Cloudkeeper::Aws::Errors::ImageDownloadError,
|
47
|
+
'Failed to download image'
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module Cloudkeeper
|
2
|
+
module Aws
|
3
|
+
# Module refining basic GRPC structs with additional methods
|
4
|
+
# used for conversion from one format to another
|
5
|
+
class ProtoHelper
|
6
|
+
class << self
|
7
|
+
APPLIANCE_PREFIX = 'cloudkeeper_appliance_'.freeze
|
8
|
+
IMAGE_PREFIX = 'cloudkeeper_image_'.freeze
|
9
|
+
NAME_TAG_KEY = 'Name'.freeze
|
10
|
+
EXTRA_APPLIANCE_TAGS = %i[description title].freeze
|
11
|
+
|
12
|
+
def filter_tags(tags, prefix)
|
13
|
+
tags.select { |tag| tag[:key].include?(prefix) }
|
14
|
+
end
|
15
|
+
|
16
|
+
def remove_prefix(tags, prefix)
|
17
|
+
tags.map { |tag| { tag[:key].sub(prefix, '').to_sym => tag[:value] } }.reduce(&:merge)
|
18
|
+
end
|
19
|
+
|
20
|
+
def prepare_tags(tags, prefix)
|
21
|
+
remove_prefix(filter_tags(tags, prefix), prefix)
|
22
|
+
end
|
23
|
+
|
24
|
+
def shorten_extra_tags!(appliance_hash)
|
25
|
+
EXTRA_APPLIANCE_TAGS.each { |key| appliance_hash[key] = appliance_hash[key][0..254] }
|
26
|
+
end
|
27
|
+
|
28
|
+
def appliance_to_tags(appliance)
|
29
|
+
appliance_hash = appliance.to_hash
|
30
|
+
shorten_extra_tags!(appliance_hash)
|
31
|
+
image = appliance_hash.delete(:image)
|
32
|
+
tags = appliance_hash.map { |k, v| { key: "#{APPLIANCE_PREFIX}#{k}", value: v.to_s } }
|
33
|
+
tags += image_to_tags(image) if image
|
34
|
+
tags << { key: Cloudkeeper::Aws::FilterHelper::TAG_CLOUDKEEPER_IDENTIFIER,
|
35
|
+
value: Cloudkeeper::Aws::Settings['identifier'] }
|
36
|
+
tags << { key: NAME_TAG_KEY, value: appliance_hash[:title] }
|
37
|
+
end
|
38
|
+
|
39
|
+
def appliance_from_tags(tags)
|
40
|
+
appliance = appliance_prepare_values(prepare_tags(tags, APPLIANCE_PREFIX))
|
41
|
+
appliance[:image] = image_from_tags(tags)
|
42
|
+
CloudkeeperGrpc::Appliance.new(appliance)
|
43
|
+
end
|
44
|
+
|
45
|
+
def appliance_prepare_values(appliance)
|
46
|
+
appliance[:ram] = appliance[:ram].to_i
|
47
|
+
appliance[:core] = appliance[:core].to_i
|
48
|
+
appliance[:expiration_date] = appliance[:expiration_date].to_i
|
49
|
+
appliance
|
50
|
+
end
|
51
|
+
|
52
|
+
def image_to_tags(image)
|
53
|
+
image.to_hash.map { |k, v| { key: "#{IMAGE_PREFIX}#{k}", value: v.to_s } }
|
54
|
+
end
|
55
|
+
|
56
|
+
def image_from_tags(tags)
|
57
|
+
image = image_prepare_values(prepare_tags(tags, IMAGE_PREFIX))
|
58
|
+
CloudkeeperGrpc::Image.new(image)
|
59
|
+
end
|
60
|
+
|
61
|
+
def image_prepare_values(image)
|
62
|
+
image[:size] = image[:size].to_i
|
63
|
+
image[:mode] = image[:mode].upcase.to_sym
|
64
|
+
image[:format] = image[:format].upcase.to_sym
|
65
|
+
image
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'settingslogic'
|
2
|
+
|
3
|
+
module Cloudkeeper
|
4
|
+
module Aws
|
5
|
+
# Class handling settings logic of Cloudkeeper-aws
|
6
|
+
class Settings < Settingslogic
|
7
|
+
CONFIGURATION = 'cloudkeeper-aws.yml'.freeze
|
8
|
+
|
9
|
+
source "#{ENV['HOME']}/.cloudkeeper-aws/#{CONFIGURATION}" \
|
10
|
+
if File.exist?("#{ENV['HOME']}/.cloudkeeper-aws/#{CONFIGURATION}")
|
11
|
+
|
12
|
+
source "/etc/cloudkeeper-aws/#{CONFIGURATION}" \
|
13
|
+
if File.exist?("/etc/cloudkeeper-aws/#{CONFIGURATION}")
|
14
|
+
|
15
|
+
source "#{File.dirname(__FILE__)}/../../../config/#{CONFIGURATION}"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# Contributor Code of Conduct
|
2
|
+
|
3
|
+
As contributors and maintainers of this project, and in the interest of
|
4
|
+
fostering an open and welcoming community, we pledge to respect all people who
|
5
|
+
contribute through reporting issues, posting feature requests, updating
|
6
|
+
documentation, submitting pull requests or patches, and other activities.
|
7
|
+
|
8
|
+
We are committed to making participation in this project a harassment-free
|
9
|
+
experience for everyone, regardless of level of experience, gender, gender
|
10
|
+
identity and expression, sexual orientation, disability, personal appearance,
|
11
|
+
body size, race, ethnicity, age, religion, or nationality.
|
12
|
+
|
13
|
+
Examples of unacceptable behavior by participants include:
|
14
|
+
|
15
|
+
* The use of sexualized language or imagery
|
16
|
+
* Personal attacks
|
17
|
+
* Trolling or insulting/derogatory comments
|
18
|
+
* Public or private harassment
|
19
|
+
* Publishing other's private information, such as physical or electronic
|
20
|
+
addresses, without explicit permission
|
21
|
+
* Other unethical or unprofessional conduct
|
22
|
+
|
23
|
+
Project maintainers have the right and responsibility to remove, edit, or
|
24
|
+
reject comments, commits, code, wiki edits, issues, and other contributions
|
25
|
+
that are not aligned to this Code of Conduct, or to ban temporarily or
|
26
|
+
permanently any contributor for other behaviors that they deem inappropriate,
|
27
|
+
threatening, offensive, or harmful.
|
28
|
+
|
29
|
+
By adopting this Code of Conduct, project maintainers commit themselves to
|
30
|
+
fairly and consistently applying these principles to every aspect of managing
|
31
|
+
this project. Project maintainers who do not follow or enforce the Code of
|
32
|
+
Conduct may be permanently removed from the project team.
|
33
|
+
|
34
|
+
This code of conduct applies both within project spaces and in public spaces
|
35
|
+
when an individual is representing the project or its community.
|
36
|
+
|
37
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
38
|
+
reported by contacting a project maintainer at kimle.michal@gmail.com. All
|
39
|
+
complaints will be reviewed and investigated and will result in a response that
|
40
|
+
is deemed necessary and appropriate to the circumstances. Maintainers are
|
41
|
+
obligated to maintain confidentiality with regard to the reporter of an
|
42
|
+
incident.
|
43
|
+
|
44
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
45
|
+
version 1.3.0, available at
|
46
|
+
[http://contributor-covenant.org/version/1/3/0/][version]
|
47
|
+
|
48
|
+
[homepage]: http://contributor-covenant.org
|
49
|
+
[version]: http://contributor-covenant.org/version/1/3/0/
|