cloudkeeper 1.7.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +5 -5
  2. data/.circleci/config.yml +11 -9
  3. data/Dockerfile +2 -2
  4. data/README.md +30 -20
  5. data/cloudkeeper.gemspec +2 -2
  6. data/config/cloudkeeper.yml +2 -2
  7. data/lib/cloudkeeper/backend_connector.rb +39 -34
  8. data/lib/cloudkeeper/cli.rb +23 -19
  9. data/lib/cloudkeeper/entities/appliance.rb +20 -10
  10. data/lib/cloudkeeper/entities/conversions.rb +7 -7
  11. data/lib/cloudkeeper/entities/convertables/convertable.rb +1 -0
  12. data/lib/cloudkeeper/entities/image.rb +12 -3
  13. data/lib/cloudkeeper/entities/image_file.rb +2 -3
  14. data/lib/cloudkeeper/entities/image_formats/ova.rb +2 -2
  15. data/lib/cloudkeeper/errors/image.rb +1 -0
  16. data/lib/cloudkeeper/errors/image/checksum_error.rb +7 -0
  17. data/lib/cloudkeeper/errors/image_list.rb +1 -0
  18. data/lib/cloudkeeper/errors/image_list/image_list_error.rb +7 -0
  19. data/lib/cloudkeeper/managers/appliance_manager.rb +63 -34
  20. data/lib/cloudkeeper/managers/image_list_manager.rb +17 -22
  21. data/lib/cloudkeeper/managers/image_manager.rb +10 -2
  22. data/lib/cloudkeeper/utils/appliance.rb +6 -15
  23. data/lib/cloudkeeper/utils/filename.rb +1 -1
  24. data/lib/cloudkeeper/utils/url.rb +1 -1
  25. data/lib/cloudkeeper/version.rb +1 -1
  26. data/lib/cloudkeeper_grpc.rb +2 -0
  27. data/lib/cloudkeeper_grpc/.gitmodules +3 -3
  28. data/lib/cloudkeeper_grpc/README.md +6 -0
  29. data/lib/cloudkeeper_grpc/cloudkeeper_pb.rb +5 -2
  30. data/lib/cloudkeeper_grpc/cloudkeeper_services_pb.rb +2 -0
  31. data/lib/cloudkeeper_grpc/constants.rb +3 -5
  32. data/lib/cloudkeeper_grpc/protos/cloudkeeper.proto +7 -2
  33. data/lib/cloudkeeper_grpc/{metadata → status-codes}/CODE_OF_CONDUCT.md +0 -0
  34. data/lib/cloudkeeper_grpc/{metadata → status-codes}/LICENSE.txt +2 -6
  35. data/lib/cloudkeeper_grpc/status-codes/README.md +3 -0
  36. data/lib/cloudkeeper_grpc/status-codes/status-codes.yml +12 -0
  37. metadata +15 -19
  38. data/lib/cloudkeeper_grpc/metadata/README.md +0 -3
  39. data/lib/cloudkeeper_grpc/metadata/metadata.yml +0 -13
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 52cab17c0a8e141cd0f549626d82eafce0f55474
4
- data.tar.gz: d7f9b5015ac01d524761aa2079adfd82ece86be7
2
+ SHA256:
3
+ metadata.gz: e3e03cb31bad0ef3603a895b10422f1d62cc8abdcb495e4dd53833f51d6b6ebe
4
+ data.tar.gz: a317e14a4eaff09c65138778f9cd6b87e1e096b6bbcca4d23e6b088694d20be3
5
5
  SHA512:
6
- metadata.gz: e79c7b17353a91db63ed7a1d09fca50e7323c49af4664a80a4ace22d08160291f00542ae9ee269f4f3dbdb1025a69ad79469dad6594fd122f0b40309440d1a38
7
- data.tar.gz: 2b100d07bee607244b9facbce8549f0cad3649ba350b98e56c456ad15116150ebe3a6093eac91d7cbd2b8c3925817cbcdbf9b1ae3372180bab4d00039281f99c
6
+ metadata.gz: 90e5a73a5c8885cd26fe7b84cd1ead6bba25966dea3221e100ec366fff5a0b1a24ca91aebea5bd00acad59689fec7ec8efa9c7a91fcf9ba2bea086b611841210
7
+ data.tar.gz: ad0235434275038c0038fe0999c8de8e8c37376419b25fe88635b4cdaabd4882ac6c11becc77ded3d2429cdd627428809eb465249f5a6b4f142f51df5c5a4cab
@@ -1,9 +1,6 @@
1
1
  version: 2
2
2
  jobs:
3
- build:
4
- branches:
5
- ignore:
6
- - /.*/
3
+ build_upload:
7
4
  docker:
8
5
  - image: docker:stable
9
6
  working_directory: /root/working_directory
@@ -29,8 +26,13 @@ jobs:
29
26
  docker tag $DOCKERHUB_ORGANIZATION/$CIRCLE_PROJECT_REPONAME:$TAG $DOCKERHUB_ORGANIZATION/$CIRCLE_PROJECT_REPONAME:latest
30
27
  docker push $DOCKERHUB_ORGANIZATION/$CIRCLE_PROJECT_REPONAME:latest
31
28
  fi
32
- deployment:
33
- fake_deploy_for_cci2:
34
- tag: /v.*/
35
- commands:
36
- - echo "make tags run in 2.0"
29
+ workflows:
30
+ version: 2
31
+ release-docker-images:
32
+ jobs:
33
+ - build_upload:
34
+ filters:
35
+ tags:
36
+ only: /v.*/
37
+ branches:
38
+ ignore: /.*/
data/Dockerfile CHANGED
@@ -1,4 +1,4 @@
1
- FROM ubuntu:16.04
1
+ FROM ubuntu:18.04
2
2
 
3
3
  ARG branch=master
4
4
  ARG version
@@ -23,7 +23,7 @@ SHELL ["/bin/bash", "-c"]
23
23
  # update + dependencies
24
24
  RUN apt-get update && \
25
25
  apt-get --assume-yes upgrade && \
26
- apt-get --assume-yes install ruby qemu-utils curl nginx file
26
+ apt-get --assume-yes install ruby qemu-utils curl nginx file gnupg
27
27
 
28
28
  # EGI trust anchors
29
29
  RUN set -o pipefail && \
data/README.md CHANGED
@@ -1,15 +1,21 @@
1
- # cloudkeeper
2
- cloudkeeper is an AppDB <-> cloud synchronization utility
1
+ <h1 align="center">
2
+ <img src="https://i.imgur.com/dObI6KR.png" alt="Logo Cloudkeeper" title="Logo Cloudkeeper" width="256"/>
3
+ <p>Cloudkeeper</p>
4
+ </h1>
3
5
 
4
- [![Travis](https://img.shields.io/travis/the-cloudkeeper-project/cloudkeeper.svg?style=flat-square)](http://travis-ci.org/the-cloudkeeper-project/cloudkeeper)
5
- [![Gemnasium](https://img.shields.io/gemnasium/the-cloudkeeper-project/cloudkeeper.svg?style=flat-square)](https://gemnasium.com/the-cloudkeeper-project/cloudkeeper)
6
- [![Gem](https://img.shields.io/gem/v/cloudkeeper.svg?style=flat-square)](https://rubygems.org/gems/cloudkeeper)
7
- [![Code Climate](https://img.shields.io/codeclimate/maintainability/the-cloudkeeper-project/cloudkeeper.svg?style=flat-square)](https://codeclimate.com/github/the-cloudkeeper-project/cloudkeeper)
8
- [![DockerHub](https://img.shields.io/badge/docker-ready-blue.svg?style=flat-square)](https://hub.docker.com/r/cloudkeeper/cloudkeeper/)
9
- [![DOI](https://img.shields.io/badge/dynamic/json.svg?label=DOI&colorB=0D7EBE&prefix=&suffix=&query=$.doi&uri=https%3A%2F%2Fzenodo.org%2Fapi%2Frecords%2F891886&style=flat-square)](https://zenodo.org/record/891886)
6
+ <p align="center">
7
+ <a href="http://travis-ci.org/the-cloudkeeper-project/cloudkeeper"><img src="https://img.shields.io/travis/the-cloudkeeper-project/cloudkeeper.svg?style=flat-square" alt="Travis"></a>
8
+ <a href="https://depfu.com/repos/the-cloudkeeper-project/cloudkeeper"><img src="https://img.shields.io/depfu/the-cloudkeeper-project/cloudkeeper.svg?style=flat-square" alt="Depfu"></a>
9
+ <a href="https://rubygems.org/gems/cloudkeeper"><img src="https://img.shields.io/gem/v/cloudkeeper.svg?style=flat-square" alt="Gem"></a>
10
+ <a href="https://codeclimate.com/github/the-cloudkeeper-project/cloudkeeper"><img src="https://img.shields.io/codeclimate/maintainability/the-cloudkeeper-project/cloudkeeper.svg?style=flat-square" alt="Code Climate"></a>
11
+ <a href="https://hub.docker.com/r/cloudkeeper/cloudkeeper/"><img src="https://img.shields.io/badge/docker-ready-blue.svg?style=flat-square" alt="DockerHub"></a>
12
+ <a href="https://zenodo.org/record/891885"><img src="https://img.shields.io/badge/dynamic/json.svg?label=DOI&colorB=0D7EBE&prefix=&suffix=&query=$.doi&uri=https%3A%2F%2Fzenodo.org%2Fapi%2Frecords%2F891885&style=flat-square" alt="DOI"></a>
13
+ </p>
10
14
 
11
- ## What does cloudkeeper do?
12
- cloudkeeper is able to read image lists provided by EGI AppDB, parse their content and decide what cloud appliances should be added, updated or removed from managed cloud. During the addition and update cloudkeeper is able to download an appliance's image and convert it to the format supported by the managed cloud.
15
+ <h4 align="center">EGI AppDB <-> CMF synchronization utility</h4>
16
+
17
+ ## What does Cloudkeeper do?
18
+ Cloudkeeper is able to read image lists provided by EGI AppDB, parse their content and decide what cloud appliances should be added, updated or removed from managed cloud. During the addition and update Cloudkeeper is able to download an appliance's image and convert it to the format supported by the managed cloud.
13
19
 
14
20
  Currently supported image formats are:
15
21
  * QCOW2
@@ -17,12 +23,13 @@ Currently supported image formats are:
17
23
  * VMDK
18
24
  * OVA
19
25
 
20
- ## How does cloudkeeper work?
21
- cloudkeeper communicates with cloud specific components via [gRPC](http://www.grpc.io/) communication framework to manage individual clouds.
26
+ ## How does Cloudkeeper work?
27
+ Cloudkeeper communicates with cloud specific components via [gRPC](http://www.grpc.io/) communication framework to manage individual clouds.
22
28
 
23
29
  Currently supported clouds:
24
- * [OpenNebula](https://opennebula.org/) - component [cloudkeeper-one](https://github.com/the-cloudkeeper-project/cloudkeeper-one)
25
- * [OpenStack](https://www.openstack.org/) - component [cloudkeeper-os](https://github.com/the-cloudkeeper-project/cloudkeeper-os) (under development)
30
+ * [OpenNebula](https://opennebula.org/) - component [Cloudkeeper-ONE](https://github.com/the-cloudkeeper-project/cloudkeeper-one)
31
+ * [OpenStack](https://www.openstack.org/) - component [Cloudkeeper-OS](https://github.com/the-cloudkeeper-project/cloudkeeper-os)
32
+ * [Amazon Web Services](https://aws.amazon.com/) - component [Cloudkeeper-AWS](https://github.com/the-cloudkeeper-project/cloudkeeper-aws)
26
33
 
27
34
  ## Requirements
28
35
  * Ruby >= 2.2.0
@@ -55,8 +62,8 @@ bundle exec rake spec
55
62
  ```
56
63
 
57
64
  ## Configuration
58
- ### Create a configuration file for cloudkeeper
59
- Configuration file can be read by cloudkeeper from these
65
+ ### Create a configuration file for Cloudkeeper
66
+ Configuration file can be read by Cloudkeeper from these
60
67
  three locations:
61
68
 
62
69
  * `~/.cloudkeeper/cloudkeeper.yml`
@@ -67,16 +74,17 @@ The default configuration file can be found at the last location
67
74
  `PATH_TO_GEM_DIR/config/cloudkeeper.yml`.
68
75
 
69
76
  ## Usage
70
- cloudkeeper is run with executable `cloudkeeper`. For further assistance run `cloudkeeper help sync`:
77
+ Cloudkeeper is run with executable `cloudkeeper`. For further assistance run `cloudkeeper help sync`:
71
78
  ```bash
72
79
  $ cloudkeeper help sync
73
80
 
74
81
  Usage:
75
- cloudkeeper sync --backend-endpoint=BACKEND-ENDPOINT --external-tools-execution-timeout=N --formats=one two three --image-dir=IMAGE-DIR --image-lists=one two three --qemu-img-binary=QEMU-IMG-BINARY
82
+ cloudkeeper sync --backend-endpoint=BACKEND-ENDPOINT --external-tools-execution-timeout=N --formats=one two three --image-dir=IMAGE-DIR --image-list=IMAGE-LIST --qemu-img-binary=QEMU-IMG-BINARY
76
83
 
77
84
  Options:
78
- [--image-lists=one two three] # List of image lists to sync against
79
- [--image-lists-file=IMAGE-LISTS-FILE] # File containing list of image lists to sync against
85
+ --image-list=IMAGE-LIST # Image list to sync against
86
+ [--verify-image-list], [--no-verify-image-list] # Verify SMIME signature on image list
87
+ # Default: true
80
88
  [--ca-dir=CA-DIR] # CA directory
81
89
  # Default: /etc/grid-security/certificates/
82
90
  [--authentication], [--no-authentication] # Client <-> server authentication
@@ -93,6 +101,8 @@ Options:
93
101
  --external-tools-execution-timeout=N # Timeout for execution of external tools in seconds
94
102
  # Default: 600
95
103
  [--remote-mode], [--no-remote-mode] # Remote mode starts HTTP server (NGINX) and serves images to backend via HTTP
104
+ [--nginx-runtime-dir=NGINX-RUNTIME-DIR] # Runtime directory for NGINX
105
+ # Default: /var/run/cloudkeeper/
96
106
  [--nginx-error-log-file=NGINX-ERROR-LOG-FILE] # NGINX error log file
97
107
  # Default: /var/log/cloudkeeper/nginx-error.log
98
108
  [--nginx-access-log-file=NGINX-ACCESS-LOG-FILE] # NGINX access log file
@@ -31,7 +31,7 @@ Gem::Specification.new do |spec|
31
31
 
32
32
  spec.add_development_dependency 'bundler', '~> 1.13'
33
33
  spec.add_development_dependency 'diffy', '~> 3.1'
34
- spec.add_development_dependency 'grpc-tools', '>= 1.1', '<= 1.2.5'
34
+ spec.add_development_dependency 'grpc-tools', '~> 1.14'
35
35
  spec.add_development_dependency 'pry', '~> 0.10'
36
36
  spec.add_development_dependency 'rake', '~> 12.0'
37
37
  spec.add_development_dependency 'rspec', '~> 3.5'
@@ -39,7 +39,7 @@ Gem::Specification.new do |spec|
39
39
  spec.add_development_dependency 'rubocop', '~> 0.48'
40
40
  spec.add_development_dependency 'rubocop-rspec', '~> 1.15'
41
41
  spec.add_development_dependency 'simplecov', '~> 0.12'
42
- spec.add_development_dependency 'vcr', '~> 3.0'
42
+ spec.add_development_dependency 'vcr', '~> 4.0'
43
43
  spec.add_development_dependency 'webmock', '~> 3.0'
44
44
 
45
45
  spec.add_runtime_dependency 'activesupport', '>= 4.0', '< 6.0'
@@ -1,6 +1,6 @@
1
1
  cloudkeeper:
2
- image-lists: # List of image lists to sync against
3
- image-lists-file: # File containing list of image lists to sync against
2
+ image-list: # Image list to sync against
3
+ verify-image-list: true # Verify SMIME signature on image list
4
4
  ca-dir: /etc/grid-security/certificates/ # CA directory
5
5
  authentication: false # core (client) <-> backend (server) authentication (certificate, key and backend-certificate options)
6
6
  certificate: /etc/grid-security/hostcert.pem # Core's host certificate
@@ -2,21 +2,22 @@ module Cloudkeeper
2
2
  class BackendConnector
3
3
  include Cloudkeeper::Entities::Conversions
4
4
 
5
- attr_reader :grpc_client, :nginx
5
+ attr_reader :grpc_client, :nginx, :errors
6
6
 
7
7
  def initialize
8
8
  @grpc_client = CloudkeeperGrpc::Communicator::Stub.new(Cloudkeeper::Settings[:'backend-endpoint'], credentials)
9
9
  @nginx = Cloudkeeper::Nginx::HttpServer.new
10
+ @errors = false
10
11
  end
11
12
 
12
13
  def pre_action
13
14
  logger.debug "'pre_action' gRPC method call"
14
- handle_errors grpc_client.pre_action(Google::Protobuf::Empty.new, return_op: true), raise_exception: true
15
+ handle_errors(exception: true) { grpc_client.pre_action(Google::Protobuf::Empty.new) }
15
16
  end
16
17
 
17
18
  def post_action
18
19
  logger.debug "'post_action' gRPC method call"
19
- handle_errors grpc_client.post_action(Google::Protobuf::Empty.new, return_op: true)
20
+ handle_errors { grpc_client.post_action(Google::Protobuf::Empty.new) }
20
21
  end
21
22
 
22
23
  def add_appliance(appliance)
@@ -29,6 +30,12 @@ module Cloudkeeper
29
30
  manage_appliance appliance, :update_appliance
30
31
  end
31
32
 
33
+ def update_appliance_metadata(appliance)
34
+ logger.debug "'update_appliance_metadata' gRPC method call (appliance.identifier: #{appliance.identifier})"
35
+ appliance.image = nil
36
+ manage_appliance appliance, :update_appliance_metadata
37
+ end
38
+
32
39
  def remove_appliance(appliance)
33
40
  logger.debug "'remove_appliance' gRPC method call (appliance.identifier: #{appliance.identifier})"
34
41
  appliance.image = nil
@@ -37,35 +44,35 @@ module Cloudkeeper
37
44
 
38
45
  def remove_image_list(image_list_identifier)
39
46
  logger.debug "'remove_image_list' gRPC method call (image_list_identifier: #{image_list_identifier})"
40
- handle_errors grpc_client.remove_image_list(
41
- CloudkeeperGrpc::ImageListIdentifier.new(image_list_identifier: image_list_identifier),
42
- return_op: true
43
- )
47
+ handle_errors do
48
+ grpc_client.remove_image_list(CloudkeeperGrpc::ImageListIdentifier.new(image_list_identifier: image_list_identifier))
49
+ end
44
50
  end
45
51
 
46
52
  def image_lists
47
53
  logger.debug "'image_lists' gRPC method call"
48
- handle_errors(grpc_client.image_lists(Google::Protobuf::Empty.new, return_op: true)) do |response|
49
- response.map(&:image_list_identifier)
50
- end
54
+ response = handle_errors(exception: true) { grpc_client.image_lists(Google::Protobuf::Empty.new) }
55
+ response.map(&:image_list_identifier)
51
56
  end
52
57
 
53
58
  def appliances(image_list_identifier)
54
59
  logger.debug "'appliances' gRPC method call"
55
- handle_errors(
56
- grpc_client.appliances(
57
- CloudkeeperGrpc::ImageListIdentifier.new(image_list_identifier: image_list_identifier),
58
- return_op: true
59
- ), raise_exception: true
60
- ) do |response|
61
- response.inject({}) do |acc, elem|
62
- image = convert_image_proto(elem.image)
63
- appliance = convert_appliance_proto elem, image
64
- acc.merge appliance.identifier => appliance
65
- end
60
+ response = handle_errors(exception: true) do
61
+ grpc_client.appliances(CloudkeeperGrpc::ImageListIdentifier.new(image_list_identifier: image_list_identifier))
62
+ end
63
+
64
+ response.inject({}) do |acc, elem|
65
+ image = convert_image_proto(elem.image)
66
+ appliance = convert_appliance_proto elem, image
67
+ acc.merge appliance.identifier => appliance
66
68
  end
67
69
  end
68
70
 
71
+ def remove_expired_appliances
72
+ logger.debug "'remove_expired_appliances' gRPC method call"
73
+ handle_errors { grpc_client.remove_expired_appliances(Google::Protobuf::Empty.new) }
74
+ end
75
+
69
76
  private
70
77
 
71
78
  def credentials
@@ -78,20 +85,18 @@ module Cloudkeeper
78
85
  )
79
86
  end
80
87
 
81
- def handle_errors(operation, raise_exception: false)
82
- return_value = operation.execute
83
- return_value = yield(return_value) if block_given?
84
- check_status operation.trailing_metadata, raise_exception: raise_exception
88
+ def handle_errors(exception: false, default: Google::Protobuf::Empty.new)
89
+ raise Cloudkeeper::Errors::ArgumentError, 'Backend connector error-wrapper was called without a block!' unless block_given?
85
90
 
86
- return_value
87
- end
88
-
89
- def check_status(metadata, raise_exception: false)
90
- return if metadata[CloudkeeperGrpc::Constants::KEY_STATUS] == CloudkeeperGrpc::Constants::STATUS_SUCCESS
91
-
92
- message = "#{metadata[CloudkeeperGrpc::Constants::KEY_STATUS]}: #{metadata[CloudkeeperGrpc::Constants::KEY_MESSAGE]}"
91
+ yield
92
+ rescue GRPC::BadStatus => ex
93
+ errors = CloudkeeperGrpc::Constants.constants.reduce({}) { |acc, el| acc.merge(CloudkeeperGrpc::Constants.const_get(el) => el) }
94
+ message = "#{errors[ex.code]}: #{ex.details}"
93
95
  logger.error "Backend error: #{message}"
94
- raise Cloudkeeper::Errors::BackendError, message if raise_exception
96
+ @errors = true
97
+ raise Cloudkeeper::Errors::BackendError, message if exception
98
+
99
+ default
95
100
  end
96
101
 
97
102
  def set_remote_data(image_proto, access_data)
@@ -114,7 +119,7 @@ module Cloudkeeper
114
119
  set_remote_data image_proto, nginx.access_data
115
120
  end
116
121
 
117
- handle_errors grpc_client.send(call, convert_appliance(appliance, image_proto), return_op: true)
122
+ handle_errors { grpc_client.send(call, convert_appliance(appliance, image_proto)) }
118
123
 
119
124
  nginx.stop if Cloudkeeper::Settings[:'remote-mode'] && image
120
125
  rescue Cloudkeeper::Errors::NginxError, Cloudkeeper::Errors::Image::Format::NoRequiredFormatAvailableError => ex
@@ -3,6 +3,8 @@ require 'yell'
3
3
 
4
4
  module Cloudkeeper
5
5
  class CLI < Thor
6
+ BACKEND_ERROR_CODE = 12
7
+
6
8
  class_option :'logging-level',
7
9
  required: true,
8
10
  default: Cloudkeeper::Settings['logging']['level'],
@@ -22,14 +24,15 @@ module Cloudkeeper
22
24
  type: :boolean,
23
25
  desc: 'Runs cloudkeeper in debug mode'
24
26
 
25
- method_option :'image-lists',
26
- default: Cloudkeeper::Settings['image-lists'],
27
- type: :array,
28
- desc: 'List of image lists to sync against'
29
- method_option :'image-lists-file',
30
- default: Cloudkeeper::Settings['image-lists-file'],
27
+ method_option :'image-list',
28
+ required: true,
29
+ default: Cloudkeeper::Settings['image-list'],
31
30
  type: :string,
32
- desc: 'File containing list of image lists to sync against'
31
+ desc: 'Image list to sync against'
32
+ method_option :'verify-image-list',
33
+ default: Cloudkeeper::Settings['verify-image-list'],
34
+ type: :boolean,
35
+ desc: 'Verify SMIME signature on image list'
33
36
  method_option :'ca-dir',
34
37
  required: false,
35
38
  default: Cloudkeeper::Settings['ca-dir'],
@@ -129,7 +132,7 @@ module Cloudkeeper
129
132
  initialize_sync options
130
133
  File.open(Cloudkeeper::Settings[:'lock-file'], File::RDWR | File::CREAT, 0o644) do |file|
131
134
  lock = file.flock(File::LOCK_EX | File::LOCK_NB)
132
- Cloudkeeper::Managers::ApplianceManager.new.synchronize_appliances if lock
135
+ run_sync if lock
133
136
  abort 'cloudkeeper instance is already running, quitting' unless lock
134
137
  end
135
138
  rescue Cloudkeeper::Errors::InvalidConfigurationError => ex
@@ -148,6 +151,18 @@ module Cloudkeeper
148
151
 
149
152
  private
150
153
 
154
+ def run_sync
155
+ appliance_manager = Cloudkeeper::Managers::ApplianceManager.new
156
+ appliance_manager.synchronize_appliances
157
+
158
+ exit_with_message BACKEND_ERROR_CODE if appliance_manager.errors[:backend_errors]
159
+ end
160
+
161
+ def exit_with_message(code)
162
+ warn 'Some errors occured during the run. See logs for more info.'
163
+ exit code
164
+ end
165
+
151
166
  def initialize_sync(options)
152
167
  initialize_configuration options
153
168
  validate_configuration!
@@ -171,7 +186,6 @@ module Cloudkeeper
171
186
  validate_configuration_group! %i[remote-mode nginx-proxy-ip-address],
172
187
  %i[nginx-proxy-port],
173
188
  'NGINX proxy configuration missing'
174
- validate_contradictory_options! %i[image-lists image-lists-file], one_required: true
175
189
  end
176
190
 
177
191
  def validate_configuration_group!(flags, required_options, error_message)
@@ -180,16 +194,6 @@ module Cloudkeeper
180
194
  raise Cloudkeeper::Errors::InvalidConfigurationError, error_message unless all_options_available(required_options)
181
195
  end
182
196
 
183
- def validate_contradictory_options!(flags, options)
184
- filled = flags.select { |flag| Cloudkeeper::Settings[flag] }
185
- if filled.count > 1
186
- raise Cloudkeeper::Errors::InvalidConfigurationError, "Following options cannot be used together: #{filled.join(', ')}"
187
- end
188
-
189
- return unless options[:one_required]
190
- raise Cloudkeeper::Errors::InvalidConfigurationError, "One of the options #{flags.join(', ')} required" if filled.empty?
191
- end
192
-
193
197
  def all_options_available(required_options)
194
198
  required_options.reduce(true) { |acc, elem| Cloudkeeper::Settings[elem] && acc }
195
199
  end
@@ -1,13 +1,19 @@
1
+ require 'digest'
2
+ require 'json'
3
+
1
4
  module Cloudkeeper
2
5
  module Entities
3
6
  class Appliance
4
- attr_accessor :identifier, :description, :mpuri, :title, :group, :ram, :core, :version, :architecture
5
- attr_accessor :operating_system, :image, :attributes, :vo, :expiration_date, :image_list_identifier
7
+ IMAGE_LIST_APPLIANCE_ATTRIBUTES = %i[dc:identifier ad:mpuri dc:date:expires dc:title dc:description
8
+ ad:group hv:ram_minimum hv:core_minimum hv:version sl:arch
9
+ ad:base_mpuri ad:appid sl:os sl:osname sl:osversion].freeze
6
10
 
7
- REJECTED_ATTRIBUTES = %i[vo image_list_identifier].freeze
11
+ attr_accessor :identifier, :description, :mpuri, :title, :group, :ram, :core, :version, :architecture
12
+ attr_accessor :operating_system, :image, :vo, :expiration_date, :image_list_identifier, :base_mpuri, :appid, :digest
8
13
 
9
14
  def initialize(identifier, mpuri, vo, expiration_date, image_list_identifier, title = '', description = '', group = '',
10
- ram = 1024, core = 1, version = '', architecture = '', operating_system = '', image = nil, attributes = {})
15
+ ram = 1024, core = 1, version = '', architecture = '', base_mpuri = '', appid = '', digest = '',
16
+ operating_system = '', image = nil)
11
17
  if identifier.blank? || \
12
18
  mpuri.blank? || \
13
19
  vo.blank? || \
@@ -28,10 +34,12 @@ module Cloudkeeper
28
34
  @architecture = architecture
29
35
  @operating_system = operating_system
30
36
  @image = image
31
- @attributes = attributes
32
37
  @vo = vo
33
38
  @expiration_date = expiration_date
34
39
  @image_list_identifier = image_list_identifier
40
+ @base_mpuri = base_mpuri
41
+ @appid = appid
42
+ @digest = digest
35
43
  end
36
44
 
37
45
  def expired?
@@ -52,7 +60,7 @@ module Cloudkeeper
52
60
 
53
61
  appliance = construct_appliance(appliance_hash)
54
62
  construct_os_name!(appliance, appliance_hash)
55
- populate_attributes!(appliance, appliance_hash)
63
+ compute_digest!(appliance, appliance_hash)
56
64
 
57
65
  appliance
58
66
  rescue Cloudkeeper::Errors::ArgumentError => ex
@@ -72,7 +80,9 @@ module Cloudkeeper
72
80
  appliance_hash[:'hv:ram_minimum'],
73
81
  appliance_hash[:'hv:core_minimum'],
74
82
  appliance_hash[:'hv:version'],
75
- appliance_hash[:'sl:arch']
83
+ appliance_hash[:'sl:arch'],
84
+ appliance_hash[:'ad:base_mpuri'],
85
+ appliance_hash[:'ad:appid']
76
86
  end
77
87
 
78
88
  def construct_os_name!(appliance, appliance_hash)
@@ -81,9 +91,9 @@ module Cloudkeeper
81
91
  appliance.operating_system = "#{appliance.operating_system} #{appliance_hash[:'sl:osversion']}".strip
82
92
  end
83
93
 
84
- def populate_attributes!(appliance, appliance_hash)
85
- appliance_hash.reject! { |k, _v| REJECTED_ATTRIBUTES.include? k }
86
- appliance.attributes = appliance_hash.map { |k, v| [k.to_s, v.to_s] }.to_h
94
+ def compute_digest!(appliance, appliance_hash)
95
+ digest_hash = appliance_hash.select { |key| IMAGE_LIST_APPLIANCE_ATTRIBUTES.include? key }
96
+ appliance.digest = Digest::SHA512.hexdigest(digest_hash.to_json)
87
97
  end
88
98
  end
89
99
  end
@@ -7,7 +7,7 @@ module Cloudkeeper
7
7
  image_file = acceptable_image_file image
8
8
 
9
9
  CloudkeeperGrpc::Image.new mode: :LOCAL, location: image_file.file, format: image_file.format.upcase,
10
- checksum: image_file.checksum, size: image_file.size.to_i, uri: image.uri
10
+ checksum: image_file.checksum, size: image_file.size.to_i, uri: image.uri, digest: image.digest
11
11
  end
12
12
 
13
13
  def convert_appliance(appliance, image_proto)
@@ -16,22 +16,22 @@ module Cloudkeeper
16
16
  ram: appliance.ram.to_i, core: appliance.core.to_i, version: appliance.version.to_s,
17
17
  architecture: appliance.architecture.to_s, operating_system: appliance.operating_system.to_s,
18
18
  vo: appliance.vo.to_s, image: image_proto, expiration_date: appliance.expiration_date.to_i,
19
- image_list_identifier: appliance.image_list_identifier.to_s, attributes: appliance.attributes
19
+ image_list_identifier: appliance.image_list_identifier.to_s, appid: appliance.appid.to_s,
20
+ base_mpuri: appliance.base_mpuri.to_s, digest: appliance.digest.to_s
20
21
  end
21
22
 
22
23
  def convert_image_proto(image_proto)
23
- return nil unless image_proto
24
-
25
- Cloudkeeper::Entities::Image.new image_proto.uri, image_proto.checksum, image_proto.size
24
+ Cloudkeeper::Entities::Image.new image_proto.uri, image_proto.checksum, image_proto.size, image_proto.digest
26
25
  end
27
26
 
28
27
  def convert_appliance_proto(appliance_proto, image)
29
28
  Cloudkeeper::Entities::Appliance.new appliance_proto.identifier, appliance_proto.mpuri, appliance_proto.vo,
30
- Time.at(appliance_proto.expiration_date).to_datetime,
29
+ Time.at(appliance_proto.expiration_date).to_time,
31
30
  appliance_proto.image_list_identifier, appliance_proto.title,
32
31
  appliance_proto.description, appliance_proto.group, appliance_proto.ram,
33
32
  appliance_proto.core, appliance_proto.version, appliance_proto.architecture,
34
- appliance_proto.operating_system, image, appliance_proto.attributes.to_h
33
+ appliance_proto.base_mpuri, appliance_proto.appid, appliance_proto.digest,
34
+ appliance_proto.operating_system, image
35
35
  end
36
36
 
37
37
  def acceptable_image_file(image)
@@ -25,6 +25,7 @@ module Cloudkeeper
25
25
  result = method.to_s.match(format_regex)
26
26
  if result && result[:format]
27
27
  return self if format.to_sym == result[:format].to_sym
28
+
28
29
  return convert result[:format]
29
30
  end
30
31
 
@@ -1,14 +1,20 @@
1
+ require 'digest'
2
+ require 'json'
3
+
1
4
  module Cloudkeeper
2
5
  module Entities
3
6
  class Image
4
- attr_accessor :image_files, :size, :uri, :checksum
7
+ IMAGE_LIST_IMAGE_ATTRIBUTES = %i[hv:uri sl:checksum:sha512 hv:size hv:version].freeze
8
+
9
+ attr_accessor :image_files, :size, :uri, :checksum, :digest
5
10
 
6
- def initialize(uri, checksum, size = 0, image_files = [])
11
+ def initialize(uri, checksum, size = 0, digest = '', image_files = [])
7
12
  raise Cloudkeeper::Errors::ArgumentError, 'uri and checksum cannot be nil nor empty' if uri.blank? || checksum.blank?
8
13
 
9
14
  @uri = uri
10
15
  @checksum = checksum
11
16
  @size = size
17
+ @digest = digest
12
18
  @image_files = image_files
13
19
  end
14
20
 
@@ -30,7 +36,10 @@ module Cloudkeeper
30
36
  raise Cloudkeeper::Errors::Parsing::InvalidImageHashError, 'invalid image hash' if image_hash.blank?
31
37
 
32
38
  image_hash.deep_symbolize_keys!
33
- Image.new image_hash[:'hv:uri'], image_hash[:'sl:checksum:sha512'], image_hash[:'hv:size']
39
+ image_hash.keep_if { |key| IMAGE_LIST_IMAGE_ATTRIBUTES.include? key }
40
+
41
+ Image.new image_hash[:'hv:uri'], image_hash[:'sl:checksum:sha512'], image_hash[:'hv:size'],
42
+ Digest::SHA512.hexdigest(image_hash.to_json)
34
43
  rescue Cloudkeeper::Errors::ArgumentError => ex
35
44
  raise Cloudkeeper::Errors::Parsing::InvalidImageHashError, ex, "image hash #{image_hash.inspect} " \
36
45
  "doesn't contain all the necessary data"
@@ -1,11 +1,11 @@
1
1
  module Cloudkeeper
2
2
  module Entities
3
3
  class ImageFile
4
- attr_accessor :file, :format, :checksum, :size, :original
4
+ attr_accessor :file, :format, :checksum, :size
5
5
 
6
6
  include Cloudkeeper::Entities::Convertables::Convertable
7
7
 
8
- def initialize(file, format, checksum, size, original = false)
8
+ def initialize(file, format, checksum, size)
9
9
  raise Cloudkeeper::Errors::ArgumentError, 'file, format, checksum and size cannot be nil nor empty'\
10
10
  if file.blank? || format.blank? || checksum.blank? || size.blank?
11
11
 
@@ -13,7 +13,6 @@ module Cloudkeeper
13
13
  @format = format
14
14
  @checksum = checksum
15
15
  @size = size
16
- @original = original
17
16
 
18
17
  format_const_symbol = format.to_s.classify.to_sym
19
18
  extend(Cloudkeeper::Entities::Convertables.const_get(format_const_symbol)) \
@@ -2,8 +2,8 @@ module Cloudkeeper
2
2
  module Entities
3
3
  module ImageFormats
4
4
  module Ova
5
- OVF_REGEX = /^.+\.ovf$/i
6
- VMDK_REGEX = /^.+\.vmdk$/i
5
+ OVF_REGEX = /^.+\.ovf$/i.freeze
6
+ VMDK_REGEX = /^.+\.vmdk$/i.freeze
7
7
  ARCHIVE_MAX_FILES = 100
8
8
 
9
9
  def ova?(archive)
@@ -3,6 +3,7 @@ module Cloudkeeper
3
3
  module Image
4
4
  autoload :Format, 'cloudkeeper/errors/image/format'
5
5
  autoload :DownloadError, 'cloudkeeper/errors/image/download_error'
6
+ autoload :ChecksumError, 'cloudkeeper/errors/image/checksum_error'
6
7
  autoload :ConversionError, 'cloudkeeper/errors/image/conversion_error'
7
8
  end
8
9
  end
@@ -0,0 +1,7 @@
1
+ module Cloudkeeper
2
+ module Errors
3
+ module Image
4
+ class ChecksumError < StandardError; end
5
+ end
6
+ end
7
+ end
@@ -1,6 +1,7 @@
1
1
  module Cloudkeeper
2
2
  module Errors
3
3
  module ImageList
4
+ autoload :ImageListError, 'cloudkeeper/errors/image_list/image_list_error'
4
5
  autoload :VerificationError, 'cloudkeeper/errors/image_list/verification_error'
5
6
  autoload :RetrievalError, 'cloudkeeper/errors/image_list/retrieval_error'
6
7
  autoload :DownloadError, 'cloudkeeper/errors/image_list/download_error'
@@ -0,0 +1,7 @@
1
+ module Cloudkeeper
2
+ module Errors
3
+ module ImageList
4
+ class ImageListError < StandardError; end
5
+ end
6
+ end
7
+ end
@@ -15,60 +15,79 @@ module Cloudkeeper
15
15
  logger.debug 'Running appliance synchronization...'
16
16
  backend_connector.pre_action
17
17
 
18
- backend_image_lists = backend_connector.image_lists
19
- image_list_manager.download_image_lists
20
-
21
- sync_expired_image_lists
22
- sync_new_image_lists(backend_image_lists)
23
- sync_old_image_lists(backend_image_lists)
18
+ synchronize
24
19
 
25
20
  backend_connector.post_action
26
- rescue Cloudkeeper::Errors::BackendError => ex
21
+ rescue Cloudkeeper::Errors::BackendError, Cloudkeeper::Errors::ImageList::ImageListError => ex
27
22
  abort ex.message
28
23
  end
29
24
 
25
+ def errors
26
+ { backend_errors: backend_connector.errors }
27
+ end
28
+
30
29
  private
31
30
 
32
- def sync_expired_image_lists
33
- logger.debug 'Removing appliances from expired image lists...'
34
- image_list_manager.image_lists.each_value do |image_list|
35
- backend_connector.remove_image_list image_list if image_list.expired?
31
+ def synchronize
32
+ backend_connector.remove_expired_appliances
33
+
34
+ image_list_manager.download_image_list
35
+
36
+ image_list = image_list_manager.image_list
37
+ if backend_connector.image_lists.include? image_list.identifier
38
+ sync_old_image_list image_list
39
+ else
40
+ sync_new_image_list image_list
36
41
  end
37
42
  end
38
43
 
39
- def sync_new_image_lists(backend_image_lists)
40
- logger.debug 'Registering appliances from new image lists...'
41
- add_list = image_list_manager.image_lists.keys - backend_image_lists
42
- logger.debug "Image lists to register: #{add_list.inspect}"
43
- add_list.each do |image_list_identifier|
44
- image_list_manager.image_lists[image_list_identifier].appliances.each_value do |appliance|
45
- if appliance.expired?
46
- log_expired appliance, 'Skipping expired appliance'
47
- next
48
- end
44
+ def sync_new_image_list(image_list)
45
+ logger.debug "Registering appliances from new image list #{image_list.identifier.inspect}"
46
+
47
+ if image_list.expired?
48
+ log_expired image_list, 'Not registering expired image list'
49
+ return
50
+ end
51
+
52
+ add_new_appliances image_list
53
+ end
49
54
 
50
- add_appliance appliance
55
+ def add_new_appliances(image_list)
56
+ image_list.appliances.each_value do |appliance|
57
+ if appliance.expired?
58
+ log_expired appliance, 'Skipping expired appliance'
59
+ next
51
60
  end
61
+
62
+ add_appliance appliance
52
63
  end
53
64
  end
54
65
 
55
- def sync_old_image_lists(backend_image_lists)
56
- logger.debug 'Synchronizing registered appliances...'
57
- sync_list = image_list_manager.image_lists.keys & backend_image_lists
58
- logger.debug "Image lists to synchronize: #{sync_list.inspect}"
59
- sync_list.each { |image_list_identifier| sync_image_list image_list_identifier }
66
+ def sync_old_image_list(image_list)
67
+ logger.debug "Synchronizing registered appliances from image list #{image_list.identifier.inspect}"
68
+
69
+ if image_list.expired?
70
+ remove_expired_image_list image_list
71
+ return
72
+ end
73
+
74
+ sync_image_list image_list
60
75
  end
61
76
 
62
- def sync_image_list(image_list_identifier)
63
- logger.debug "Synchronizing appliances for image list with id #{image_list_identifier.inspect}"
64
- backend_appliances = backend_connector.appliances image_list_identifier
65
- image_list_appliances = image_list_manager.image_lists[image_list_identifier].appliances
77
+ def sync_image_list(image_list)
78
+ backend_appliances = backend_connector.appliances image_list.identifier
79
+ image_list_appliances = image_list.appliances
66
80
 
67
81
  remove_appliances backend_appliances, image_list_appliances
68
82
  add_appliances backend_appliances, image_list_appliances
69
83
  update_appliances backend_appliances, image_list_appliances
70
84
  end
71
85
 
86
+ def remove_expired_image_list(image_list)
87
+ logger.debug "Removing expired image list #{image_list.identifier.inspect}"
88
+ backend_connector.remove_image_list image_list.identifier
89
+ end
90
+
72
91
  def remove_appliances(backend_appliances, image_list_appliances)
73
92
  logger.debug 'Removing previously registered appliances...'
74
93
  remove_list = backend_appliances.keys - image_list_appliances.keys
@@ -105,12 +124,22 @@ module Cloudkeeper
105
124
  next
106
125
  end
107
126
 
108
- image_update = update_image?(image_list_appliance, backend_appliance)
109
- image_list_appliance.image = nil unless image_update
110
- update_appliance image_list_appliance if image_update || update_metadata?(image_list_appliance, backend_appliance)
127
+ method = :update_appliance_metadata if update_metadata?(image_list_appliance, backend_appliance)
128
+ method = :update_appliance if update_image?(image_list_appliance, backend_appliance)
129
+
130
+ send method, image_list_appliance if method
111
131
  end
112
132
  end
113
133
 
134
+ def update_appliance(appliance)
135
+ modify_appliance :update_appliance, appliance
136
+ end
137
+
138
+ def update_appliance_metadata(appliance)
139
+ appliance.image = nil
140
+ modify_appliance :update_appliance_metadata, appliance
141
+ end
142
+
114
143
  def add_appliance(appliance)
115
144
  modify_appliance :add_appliance, appliance
116
145
  end
@@ -6,39 +6,29 @@ require 'json'
6
6
  module Cloudkeeper
7
7
  module Managers
8
8
  class ImageListManager
9
- attr_reader :image_lists, :openssl_store
9
+ attr_reader :image_list, :openssl_store
10
10
 
11
11
  def initialize
12
- @image_lists = {}
13
-
14
12
  @openssl_store = OpenSSL::X509::Store.new
15
13
  @openssl_store.add_path Cloudkeeper::Settings[:'ca-dir'] if Cloudkeeper::Settings[:'ca-dir']
16
14
  end
17
15
 
18
- def download_image_lists
16
+ def download_image_list
19
17
  logger.debug 'Downloading fresh image lists...'
20
- Dir.mktmpdir('cloudkeeper') do |dir|
21
- urls = Cloudkeeper::Settings[:'image-lists'] || File.read(Cloudkeeper::Settings[:'image-lists-file']).split("\n")
22
- retrieve_image_lists urls, dir
23
- end
18
+ url = Cloudkeeper::Settings[:'image-list']
19
+ Dir.mktmpdir('cloudkeeper') { |dir| retrieve_image_list url, dir }
20
+ rescue Cloudkeeper::Errors::ImageList::DownloadError, Cloudkeeper::Errors::ImageList::VerificationError,
21
+ Cloudkeeper::Errors::Parsing::ParsingError, OpenSSL::PKCS7::PKCS7Error, JSON::ParserError => ex
22
+ raise Cloudkeeper::Errors::ImageList::ImageListError, "Image list #{url.inspect} couldn't be downloaded\n#{ex.message}"
24
23
  end
25
24
 
26
25
  private
27
26
 
28
- def retrieve_image_lists(urls, dir)
29
- urls.each do |url|
30
- begin
31
- image_list = convert_image_list(load_image_list(download_image_list(url, dir)))
32
- image_lists[image_list.identifier] = image_list
33
- rescue Cloudkeeper::Errors::ImageList::DownloadError, Cloudkeeper::Errors::ImageList::VerificationError,
34
- Cloudkeeper::Errors::Parsing::ParsingError => ex
35
- logger.warn "Image list #{url} couldn't be downloaded\n#{ex.message}"
36
- next
37
- end
38
- end
27
+ def retrieve_image_list(url, dir)
28
+ @image_list = convert_image_list(load_image_list(download_image_list_file(url, dir)))
39
29
  end
40
30
 
41
- def download_image_list(url, dir)
31
+ def download_image_list_file(url, dir)
42
32
  logger.debug "Downloading image list from #{url.inspect}"
43
33
  Cloudkeeper::Utils::URL.check!(url)
44
34
  uri = URI.parse url
@@ -74,10 +64,15 @@ module Cloudkeeper
74
64
  end
75
65
 
76
66
  def load_image_list(file)
77
- pkcs7 = OpenSSL::PKCS7.read_smime(File.read(file))
78
- verify_image_list!(pkcs7, file)
67
+ content = File.read(file)
68
+ pkcs7 = OpenSSL::PKCS7.read_smime(content)
69
+ verify_image_list!(pkcs7, file) if Cloudkeeper::Settings[:'verify-image-lists']
79
70
 
80
71
  JSON.parse pkcs7.data
72
+ rescue OpenSSL::PKCS7::PKCS7Error => ex
73
+ raise ex if Cloudkeeper::Settings[:'verify-image-lists']
74
+
75
+ JSON.parse content
81
76
  end
82
77
 
83
78
  def verify_image_list!(pkcs7, file)
@@ -1,4 +1,5 @@
1
1
  require 'net/https'
2
+ require 'digest'
2
3
 
3
4
  module Cloudkeeper
4
5
  module Managers
@@ -46,21 +47,28 @@ module Cloudkeeper
46
47
  raise Cloudkeeper::Errors::Image::Format::NoFormatRecognizedError, "No image format recognized for file #{file.inspect}"
47
48
  end
48
49
 
49
- def download_image(url)
50
+ def secure_download_image(url, checksum)
50
51
  logger.debug "Downloading image from #{url.inspect}"
51
52
  Cloudkeeper::Utils::URL.check!(url)
52
53
 
53
54
  uri = URI.parse url
54
55
  filename = generate_filename(uri)
55
56
  retrieve_image(uri, filename)
57
+ check_image_checksum!(filename, checksum)
56
58
 
57
59
  Cloudkeeper::Entities::ImageFile.new filename, format(filename), Cloudkeeper::Utils::Checksum.compute(filename),
58
- File.size(filename), true
60
+ File.size(filename)
59
61
  rescue Cloudkeeper::Errors::InvalidURLError, Cloudkeeper::Errors::Image::Format::RecognitionError,
60
62
  Cloudkeeper::Errors::ArgumentError, Cloudkeeper::Errors::NetworkConnectionError, ::IOError => ex
61
63
  raise Cloudkeeper::Errors::Image::DownloadError, "Image #{url.inspect} download error: #{ex.message}"
62
64
  end
63
65
 
66
+ def check_image_checksum!(filename, checksum)
67
+ computed = Digest::SHA512.file(filename).hexdigest
68
+ raise Cloudkeeper::Errors::Image::ChecksumError, "Checksum mismatch, expecting #{checksum.inspect} got #{computed.inspect}" \
69
+ unless checksum == computed
70
+ end
71
+
64
72
  def retrieve_image(uri, filename)
65
73
  Net::HTTP.start(uri.host, uri.port, connection_options(uri)) do |http|
66
74
  request = Net::HTTP::Get.new(uri)
@@ -1,10 +1,8 @@
1
1
  module Cloudkeeper
2
2
  module Utils
3
3
  module Appliance
4
- IMAGE_UPDATE_ATTRIBUTES = ['hv:version', 'sl:checksum:sha512', 'hv:size'].freeze
5
-
6
- def log_expired(appliance, message)
7
- logger.info "#{message} #{appliance.identifier.inspect}"
4
+ def log_expired(expirable, message)
5
+ logger.info "#{message} #{expirable.identifier.inspect}"
8
6
  end
9
7
 
10
8
  def clean_image_files(appliance)
@@ -21,22 +19,15 @@ module Cloudkeeper
21
19
  end
22
20
 
23
21
  def update_image?(image_list_appliance, backend_appliance)
24
- image_list_attributes = image_list_appliance.attributes
25
- backend_attributes = backend_appliance.attributes
26
-
27
- IMAGE_UPDATE_ATTRIBUTES.reduce(false) { |red, elem| red || (image_list_attributes[elem] != backend_attributes[elem]) }
22
+ image_list_appliance.image.digest != backend_appliance.image.digest
28
23
  end
29
24
 
30
25
  def update_metadata?(image_list_appliance, backend_appliance)
31
- image_list_appliance.attributes != backend_appliance.attributes
32
- end
33
-
34
- def update_appliance(appliance)
35
- modify_appliance :update_appliance, appliance
26
+ image_list_appliance.digest != backend_appliance.digest
36
27
  end
37
28
 
38
29
  def prepare_image!(appliance)
39
- image_file = Cloudkeeper::Managers::ImageManager.download_image(appliance.image.uri)
30
+ image_file = Cloudkeeper::Managers::ImageManager.secure_download_image(appliance.image.uri, appliance.image.checksum)
40
31
  appliance.image.add_image_file image_file
41
32
  return if acceptable_formats.include? image_file.format
42
33
 
@@ -47,7 +38,7 @@ module Cloudkeeper
47
38
  format = acceptable_formats.find { |acceptable_format| image_file.respond_to? "to_#{acceptable_format}".to_sym }
48
39
  unless format
49
40
  raise Cloudkeeper::Errors::Image::Format::NoRequiredFormatAvailableError,
50
- "image #{image.inspect} cannot be converted to any acceptable format"
41
+ "image #{image_file.inspect} cannot be converted to any acceptable format"
51
42
  end
52
43
 
53
44
  appliance.image.add_image_file image_file.send("to_#{format}".to_sym)
@@ -3,7 +3,7 @@ require 'zaru'
3
3
  module Cloudkeeper
4
4
  module Utils
5
5
  class Filename
6
- WHITESPACES_REGEXP = /\s+/
6
+ WHITESPACES_REGEXP = /\s+/.freeze
7
7
 
8
8
  def self.sanitize(name)
9
9
  Zaru.sanitize!(name).gsub(WHITESPACES_REGEXP, '_')
@@ -1,7 +1,7 @@
1
1
  module Cloudkeeper
2
2
  module Utils
3
3
  class URL
4
- URL_REGEXP = /\A#{URI.regexp(%w[http https])}\z/
4
+ URL_REGEXP = /\A#{URI.regexp(%w[http https])}\z/.freeze
5
5
 
6
6
  def self.check!(url)
7
7
  raise Cloudkeeper::Errors::InvalidURLError, "#{url.inspect} is not a valid URL" unless url =~ URL_REGEXP
@@ -1,3 +1,3 @@
1
1
  module Cloudkeeper
2
- VERSION = '1.7.1'.freeze
2
+ VERSION = '2.0.0'.freeze
3
3
  end
@@ -1,3 +1,5 @@
1
+ require 'active_support/all'
2
+
1
3
  module CloudkeeperGrpc
2
4
  require 'cloudkeeper_grpc/cloudkeeper_pb'
3
5
  require 'cloudkeeper_grpc/cloudkeeper_services_pb'
@@ -1,6 +1,6 @@
1
1
  [submodule "protos"]
2
2
  path = protos
3
3
  url = https://github.com/the-cloudkeeper-project/cloudkeeper-proto.git
4
- [submodule "metadata"]
5
- path = metadata
6
- url = https://github.com/the-cloudkeeper-project/cloudkeeper-metadata.git
4
+ [submodule "status-codes"]
5
+ path = status-codes
6
+ url = https://github.com/the-cloudkeeper-project/cloudkeeper-status-codes.git
@@ -1,3 +1,9 @@
1
1
  ## cloudkeeper gRPC generated classes for Ruby
2
2
 
3
3
  This repository contains Ruby classes generated for gRPC communication within the [cloudkeeper](https://github.com/the-cloudkeeper-project/cloudkeeper) project.
4
+
5
+ ### Important!
6
+ Code expects `String` class to contain method `underscore` which can be find within [Active Support](https://guides.rubyonrails.org/active_support_core_extensions.html) gem. At least extensions for `String` class must be required:
7
+ ```ruby
8
+ require 'active_support/core_ext/string'
9
+ ```
@@ -19,8 +19,10 @@ Google::Protobuf::DescriptorPool.generated_pool.build do
19
19
  optional :vo, :string, 11
20
20
  optional :expiration_date, :int64, 12
21
21
  optional :image_list_identifier, :string, 13
22
- optional :image, :message, 14, "cloudkeeper_grpc.Image"
23
- map :attributes, :string, :string, 15
22
+ optional :base_mpuri, :string, 14
23
+ optional :appid, :string, 15
24
+ optional :digest, :string, 16
25
+ optional :image, :message, 17, "cloudkeeper_grpc.Image"
24
26
  end
25
27
  add_message "cloudkeeper_grpc.Image" do
26
28
  optional :mode, :enum, 1, "cloudkeeper_grpc.Image.Mode"
@@ -31,6 +33,7 @@ Google::Protobuf::DescriptorPool.generated_pool.build do
31
33
  optional :size, :int64, 6
32
34
  optional :username, :string, 7
33
35
  optional :password, :string, 8
36
+ optional :digest, :string, 9
34
37
  end
35
38
  add_enum "cloudkeeper_grpc.Image.Mode" do
36
39
  value :LOCAL, 0
@@ -18,10 +18,12 @@ module CloudkeeperGrpc
18
18
  rpc :PostAction, Google::Protobuf::Empty, Google::Protobuf::Empty
19
19
  rpc :AddAppliance, Appliance, Google::Protobuf::Empty
20
20
  rpc :UpdateAppliance, Appliance, Google::Protobuf::Empty
21
+ rpc :UpdateApplianceMetadata, Appliance, Google::Protobuf::Empty
21
22
  rpc :RemoveAppliance, Appliance, Google::Protobuf::Empty
22
23
  rpc :RemoveImageList, ImageListIdentifier, Google::Protobuf::Empty
23
24
  rpc :ImageLists, Google::Protobuf::Empty, stream(ImageListIdentifier)
24
25
  rpc :Appliances, ImageListIdentifier, stream(Appliance)
26
+ rpc :RemoveExpiredAppliances, Google::Protobuf::Empty, Google::Protobuf::Empty
25
27
  end
26
28
 
27
29
  Stub = Service.rpc_stub_class
@@ -2,11 +2,9 @@ require 'yaml'
2
2
 
3
3
  module CloudkeeperGrpc
4
4
  class Constants
5
- metadata = YAML.load_file(File.join(File.dirname(__FILE__), 'metadata', 'metadata.yml'))
6
- metadata['keys'].each do |key|
7
- upcase_key = key.upcase
8
- const_set("KEY_#{upcase_key}", key)
9
- metadata[key].each { |value| const_set("#{upcase_key}_#{value.upcase}", value)} if metadata.has_key? key
5
+ status_codes = YAML.load_file(File.join(File.dirname(__FILE__), 'status-codes', 'status-codes.yml'))
6
+ status_codes.each do |key, value|
7
+ const_set("STATUS_CODE_#{key.underscore.upcase}", value)
10
8
  end
11
9
  end
12
10
  end
@@ -18,8 +18,10 @@ message Appliance {
18
18
  string vo = 11;
19
19
  int64 expiration_date = 12;
20
20
  string image_list_identifier = 13;
21
- Image image = 14;
22
- map<string, string> attributes = 15;
21
+ string base_mpuri = 14;
22
+ string appid = 15;
23
+ string digest = 16;
24
+ Image image = 17;
23
25
  }
24
26
 
25
27
  message Image {
@@ -41,6 +43,7 @@ message Image {
41
43
  int64 size = 6;
42
44
  string username = 7;
43
45
  string password = 8;
46
+ string digest = 9;
44
47
  }
45
48
 
46
49
  message ImageListIdentifier {
@@ -52,8 +55,10 @@ service Communicator {
52
55
  rpc PostAction(google.protobuf.Empty) returns (google.protobuf.Empty) {}
53
56
  rpc AddAppliance(Appliance) returns (google.protobuf.Empty) {}
54
57
  rpc UpdateAppliance(Appliance) returns (google.protobuf.Empty) {}
58
+ rpc UpdateApplianceMetadata(Appliance) returns (google.protobuf.Empty) {}
55
59
  rpc RemoveAppliance(Appliance) returns (google.protobuf.Empty) {}
56
60
  rpc RemoveImageList(ImageListIdentifier) returns (google.protobuf.Empty) {}
57
61
  rpc ImageLists(google.protobuf.Empty) returns (stream ImageListIdentifier) {}
58
62
  rpc Appliances(ImageListIdentifier) returns (stream Appliance) {}
63
+ rpc RemoveExpiredAppliances(google.protobuf.Empty) returns (google.protobuf.Empty) {}
59
64
  }
@@ -1,14 +1,10 @@
1
- The work represented by this source file was partially or entirely
2
- funded by the EGI-Engage project co-funded by the European Union (EU)
3
- Horizon 2020 program under Grant number 654142.
4
-
5
- Copyright (c) 2017 CESNET
1
+ Copyright 2018 Michal Kimle
6
2
 
7
3
  Licensed under the Apache License, Version 2.0 (the "License");
8
4
  you may not use this file except in compliance with the License.
9
5
  You may obtain a copy of the License at
10
6
 
11
- http://www.apache.org/licenses/LICENSE-2.0
7
+ http://www.apache.org/licenses/LICENSE-2.0
12
8
 
13
9
  Unless required by applicable law or agreed to in writing, software
14
10
  distributed under the License is distributed on an "AS IS" BASIS,
@@ -0,0 +1,3 @@
1
+ ## cloudkeeper status-codes file
2
+
3
+ This repository contains status codes for error handling of gRPC communication within [cloudkeeper](https://github.com/the-cloudkeeper-project/cloudkeeper) project.
@@ -0,0 +1,12 @@
1
+ ---
2
+ # Compatible with GRPC status codes
3
+ Ok: 0
4
+ Unknown: 2
5
+ PermissionDenied: 7
6
+ Unauthenticated: 16
7
+ # Custom status codes
8
+ ApplianceNotFound: 101
9
+ FailedApplianceTransfer: 102
10
+ ResourceNotFound: 103
11
+ FailedResourceRetrieval: 104
12
+ InvalidResourceState: 105
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cloudkeeper
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.1
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michal Kimle
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-04-09 00:00:00.000000000 Z
11
+ date: 2018-11-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -42,22 +42,16 @@ dependencies:
42
42
  name: grpc-tools
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - ">="
46
- - !ruby/object:Gem::Version
47
- version: '1.1'
48
- - - "<="
45
+ - - "~>"
49
46
  - !ruby/object:Gem::Version
50
- version: 1.2.5
47
+ version: '1.14'
51
48
  type: :development
52
49
  prerelease: false
53
50
  version_requirements: !ruby/object:Gem::Requirement
54
51
  requirements:
55
- - - ">="
56
- - !ruby/object:Gem::Version
57
- version: '1.1'
58
- - - "<="
52
+ - - "~>"
59
53
  - !ruby/object:Gem::Version
60
- version: 1.2.5
54
+ version: '1.14'
61
55
  - !ruby/object:Gem::Dependency
62
56
  name: pry
63
57
  requirement: !ruby/object:Gem::Requirement
@@ -162,14 +156,14 @@ dependencies:
162
156
  requirements:
163
157
  - - "~>"
164
158
  - !ruby/object:Gem::Version
165
- version: '3.0'
159
+ version: '4.0'
166
160
  type: :development
167
161
  prerelease: false
168
162
  version_requirements: !ruby/object:Gem::Requirement
169
163
  requirements:
170
164
  - - "~>"
171
165
  - !ruby/object:Gem::Version
172
- version: '3.0'
166
+ version: '4.0'
173
167
  - !ruby/object:Gem::Dependency
174
168
  name: webmock
175
169
  requirement: !ruby/object:Gem::Requirement
@@ -365,6 +359,7 @@ files:
365
359
  - lib/cloudkeeper/errors/convertables.rb
366
360
  - lib/cloudkeeper/errors/convertables/convertability_error.rb
367
361
  - lib/cloudkeeper/errors/image.rb
362
+ - lib/cloudkeeper/errors/image/checksum_error.rb
368
363
  - lib/cloudkeeper/errors/image/conversion_error.rb
369
364
  - lib/cloudkeeper/errors/image/download_error.rb
370
365
  - lib/cloudkeeper/errors/image/format.rb
@@ -376,6 +371,7 @@ files:
376
371
  - lib/cloudkeeper/errors/image/format/recognition_error.rb
377
372
  - lib/cloudkeeper/errors/image_list.rb
378
373
  - lib/cloudkeeper/errors/image_list/download_error.rb
374
+ - lib/cloudkeeper/errors/image_list/image_list_error.rb
379
375
  - lib/cloudkeeper/errors/image_list/retrieval_error.rb
380
376
  - lib/cloudkeeper/errors/image_list/verification_error.rb
381
377
  - lib/cloudkeeper/errors/invalid_configuration_error.rb
@@ -415,14 +411,14 @@ files:
415
411
  - lib/cloudkeeper_grpc/cloudkeeper_pb.rb
416
412
  - lib/cloudkeeper_grpc/cloudkeeper_services_pb.rb
417
413
  - lib/cloudkeeper_grpc/constants.rb
418
- - lib/cloudkeeper_grpc/metadata/CODE_OF_CONDUCT.md
419
- - lib/cloudkeeper_grpc/metadata/LICENSE.txt
420
- - lib/cloudkeeper_grpc/metadata/README.md
421
- - lib/cloudkeeper_grpc/metadata/metadata.yml
422
414
  - lib/cloudkeeper_grpc/protos/CODE_OF_CONDUCT.md
423
415
  - lib/cloudkeeper_grpc/protos/LICENSE.txt
424
416
  - lib/cloudkeeper_grpc/protos/README.md
425
417
  - lib/cloudkeeper_grpc/protos/cloudkeeper.proto
418
+ - lib/cloudkeeper_grpc/status-codes/CODE_OF_CONDUCT.md
419
+ - lib/cloudkeeper_grpc/status-codes/LICENSE.txt
420
+ - lib/cloudkeeper_grpc/status-codes/README.md
421
+ - lib/cloudkeeper_grpc/status-codes/status-codes.yml
426
422
  homepage: https://github.com/the-cloudkeeper-project/cloudkeeper
427
423
  licenses:
428
424
  - Apache License, Version 2.0
@@ -443,7 +439,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
443
439
  version: '0'
444
440
  requirements: []
445
441
  rubyforge_project:
446
- rubygems_version: 2.6.14
442
+ rubygems_version: 2.7.4
447
443
  signing_key:
448
444
  specification_version: 4
449
445
  summary: Synchronize cloud appliances between AppDB and cloud platforms
@@ -1,3 +0,0 @@
1
- ## cloudkeeper metadata file
2
-
3
- This repository contains potential metadata transfered during the gRPC communication within [cloudkeeper](https://github.com/the-cloudkeeper-project/cloudkeeper) project.
@@ -1,13 +0,0 @@
1
- keys:
2
- - status
3
- - message
4
- status:
5
- - SUCCESS
6
- - ERROR
7
- - ERROR_APPLIANCE_NOT_FOUND
8
- - ERROR_APPLIANCE_TRANSFER
9
- - ERROR_AUTHENTICATION
10
- - ERROR_USER_NOT_AUTHORIZED
11
- - ERROR_RESOURCE_NOT_FOUND
12
- - ERROR_RESOURCE_RETRIEVAL
13
- - ERROR_RESOURCE_STATE