cloudkeeper-aws 2.0.0

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