cloudkeeper-aws 2.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 (50) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.gitmodules +3 -0
  4. data/.rubocop.yml +48 -0
  5. data/.travis.yml +22 -0
  6. data/Gemfile +5 -0
  7. data/LICENSE +674 -0
  8. data/README.md +91 -0
  9. data/Rakefile +17 -0
  10. data/bin/cloudkeeper-aws +5 -0
  11. data/cloudkeeper-aws.gemspec +46 -0
  12. data/codecov.yml +4 -0
  13. data/config/cloudkeeper-aws.yml +17 -0
  14. data/lib/cloudkeeper/aws.rb +16 -0
  15. data/lib/cloudkeeper/aws/backend_executor.rb +76 -0
  16. data/lib/cloudkeeper/aws/cli.rb +159 -0
  17. data/lib/cloudkeeper/aws/cloud.rb +173 -0
  18. data/lib/cloudkeeper/aws/core_connector.rb +108 -0
  19. data/lib/cloudkeeper/aws/errors.rb +11 -0
  20. data/lib/cloudkeeper/aws/errors/backend.rb +14 -0
  21. data/lib/cloudkeeper/aws/errors/backend/appliance_not_found_error.rb +9 -0
  22. data/lib/cloudkeeper/aws/errors/backend/backend_error.rb +9 -0
  23. data/lib/cloudkeeper/aws/errors/backend/image_import_error.rb +9 -0
  24. data/lib/cloudkeeper/aws/errors/backend/multiple_appliances_found_error.rb +9 -0
  25. data/lib/cloudkeeper/aws/errors/backend/timeout_error.rb +9 -0
  26. data/lib/cloudkeeper/aws/errors/image_download_error.rb +7 -0
  27. data/lib/cloudkeeper/aws/errors/invalid_configuration_error.rb +7 -0
  28. data/lib/cloudkeeper/aws/errors/standard_error.rb +7 -0
  29. data/lib/cloudkeeper/aws/filter_helper.rb +33 -0
  30. data/lib/cloudkeeper/aws/image_downloader.rb +52 -0
  31. data/lib/cloudkeeper/aws/proto_helper.rb +70 -0
  32. data/lib/cloudkeeper/aws/settings.rb +18 -0
  33. data/lib/cloudkeeper/aws/version.rb +5 -0
  34. data/lib/cloudkeeper_grpc.rb +7 -0
  35. data/lib/cloudkeeper_grpc/.gitmodules +6 -0
  36. data/lib/cloudkeeper_grpc/CODE_OF_CONDUCT.md +49 -0
  37. data/lib/cloudkeeper_grpc/LICENSE.txt +17 -0
  38. data/lib/cloudkeeper_grpc/README.md +9 -0
  39. data/lib/cloudkeeper_grpc/cloudkeeper_pb.rb +59 -0
  40. data/lib/cloudkeeper_grpc/cloudkeeper_services_pb.rb +31 -0
  41. data/lib/cloudkeeper_grpc/constants.rb +10 -0
  42. data/lib/cloudkeeper_grpc/protos/CODE_OF_CONDUCT.md +49 -0
  43. data/lib/cloudkeeper_grpc/protos/LICENSE.txt +17 -0
  44. data/lib/cloudkeeper_grpc/protos/README.md +3 -0
  45. data/lib/cloudkeeper_grpc/protos/cloudkeeper.proto +64 -0
  46. data/lib/cloudkeeper_grpc/status-codes/CODE_OF_CONDUCT.md +49 -0
  47. data/lib/cloudkeeper_grpc/status-codes/LICENSE.txt +13 -0
  48. data/lib/cloudkeeper_grpc/status-codes/README.md +3 -0
  49. data/lib/cloudkeeper_grpc/status-codes/status-codes.yml +12 -0
  50. 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,9 @@
1
+ module Cloudkeeper
2
+ module Aws
3
+ module Errors
4
+ module Backend
5
+ class ApplianceNotFoundError < BackendError; end
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Cloudkeeper
2
+ module Aws
3
+ module Errors
4
+ module Backend
5
+ class BackendError < StandardError; end
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Cloudkeeper
2
+ module Aws
3
+ module Errors
4
+ module Backend
5
+ class ImageImportError < BackendError; end
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Cloudkeeper
2
+ module Aws
3
+ module Errors
4
+ module Backend
5
+ class MultipleAppliancesFoundError < BackendError; end
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Cloudkeeper
2
+ module Aws
3
+ module Errors
4
+ module Backend
5
+ class TimeoutError < BackendError; end
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,7 @@
1
+ module Cloudkeeper
2
+ module Aws
3
+ module Errors
4
+ class ImageDownloadError < StandardError; end
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module Cloudkeeper
2
+ module Aws
3
+ module Errors
4
+ class InvalidConfigurationError < StandardError; end
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module Cloudkeeper
2
+ module Aws
3
+ module Errors
4
+ class StandardError < ::StandardError; end
5
+ end
6
+ end
7
+ 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,5 @@
1
+ module Cloudkeeper
2
+ module Aws
3
+ VERSION = '2.0.0'.freeze
4
+ end
5
+ end
@@ -0,0 +1,7 @@
1
+ require 'active_support/core_ext/string'
2
+
3
+ module CloudkeeperGrpc
4
+ require 'cloudkeeper_grpc/cloudkeeper_pb'
5
+ require 'cloudkeeper_grpc/cloudkeeper_services_pb'
6
+ require 'cloudkeeper_grpc/constants'
7
+ end
@@ -0,0 +1,6 @@
1
+ [submodule "protos"]
2
+ path = protos
3
+ url = https://github.com/the-cloudkeeper-project/cloudkeeper-proto.git
4
+ [submodule "status-codes"]
5
+ path = status-codes
6
+ url = https://github.com/the-cloudkeeper-project/cloudkeeper-status-codes.git
@@ -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/