cloudkeeper 1.7.1 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.circleci/config.yml +11 -9
- data/Dockerfile +2 -2
- data/README.md +30 -20
- data/cloudkeeper.gemspec +2 -2
- data/config/cloudkeeper.yml +2 -2
- data/lib/cloudkeeper/backend_connector.rb +39 -34
- data/lib/cloudkeeper/cli.rb +23 -19
- data/lib/cloudkeeper/entities/appliance.rb +20 -10
- data/lib/cloudkeeper/entities/conversions.rb +7 -7
- data/lib/cloudkeeper/entities/convertables/convertable.rb +1 -0
- data/lib/cloudkeeper/entities/image.rb +12 -3
- data/lib/cloudkeeper/entities/image_file.rb +2 -3
- data/lib/cloudkeeper/entities/image_formats/ova.rb +2 -2
- data/lib/cloudkeeper/errors/image.rb +1 -0
- data/lib/cloudkeeper/errors/image/checksum_error.rb +7 -0
- data/lib/cloudkeeper/errors/image_list.rb +1 -0
- data/lib/cloudkeeper/errors/image_list/image_list_error.rb +7 -0
- data/lib/cloudkeeper/managers/appliance_manager.rb +63 -34
- data/lib/cloudkeeper/managers/image_list_manager.rb +17 -22
- data/lib/cloudkeeper/managers/image_manager.rb +10 -2
- data/lib/cloudkeeper/utils/appliance.rb +6 -15
- data/lib/cloudkeeper/utils/filename.rb +1 -1
- data/lib/cloudkeeper/utils/url.rb +1 -1
- data/lib/cloudkeeper/version.rb +1 -1
- data/lib/cloudkeeper_grpc.rb +2 -0
- data/lib/cloudkeeper_grpc/.gitmodules +3 -3
- data/lib/cloudkeeper_grpc/README.md +6 -0
- data/lib/cloudkeeper_grpc/cloudkeeper_pb.rb +5 -2
- data/lib/cloudkeeper_grpc/cloudkeeper_services_pb.rb +2 -0
- data/lib/cloudkeeper_grpc/constants.rb +3 -5
- data/lib/cloudkeeper_grpc/protos/cloudkeeper.proto +7 -2
- data/lib/cloudkeeper_grpc/{metadata → status-codes}/CODE_OF_CONDUCT.md +0 -0
- data/lib/cloudkeeper_grpc/{metadata → status-codes}/LICENSE.txt +2 -6
- data/lib/cloudkeeper_grpc/status-codes/README.md +3 -0
- data/lib/cloudkeeper_grpc/status-codes/status-codes.yml +12 -0
- metadata +15 -19
- data/lib/cloudkeeper_grpc/metadata/README.md +0 -3
- data/lib/cloudkeeper_grpc/metadata/metadata.yml +0 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: e3e03cb31bad0ef3603a895b10422f1d62cc8abdcb495e4dd53833f51d6b6ebe
|
4
|
+
data.tar.gz: a317e14a4eaff09c65138778f9cd6b87e1e096b6bbcca4d23e6b088694d20be3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 90e5a73a5c8885cd26fe7b84cd1ead6bba25966dea3221e100ec366fff5a0b1a24ca91aebea5bd00acad59689fec7ec8efa9c7a91fcf9ba2bea086b611841210
|
7
|
+
data.tar.gz: ad0235434275038c0038fe0999c8de8e8c37376419b25fe88635b4cdaabd4882ac6c11becc77ded3d2429cdd627428809eb465249f5a6b4f142f51df5c5a4cab
|
data/.circleci/config.yml
CHANGED
@@ -1,9 +1,6 @@
|
|
1
1
|
version: 2
|
2
2
|
jobs:
|
3
|
-
|
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
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
-
|
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:
|
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
|
-
|
2
|
-
|
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
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
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
|
-
|
12
|
-
|
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
|
21
|
-
|
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 [
|
25
|
-
* [OpenStack](https://www.openstack.org/) - component [
|
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
|
59
|
-
Configuration file can be read by
|
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
|
-
|
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-
|
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
|
-
|
79
|
-
[--image-
|
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
|
data/cloudkeeper.gemspec
CHANGED
@@ -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', '
|
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', '~>
|
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'
|
data/config/cloudkeeper.yml
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
cloudkeeper:
|
2
|
-
image-
|
3
|
-
image-
|
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
|
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
|
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
|
41
|
-
CloudkeeperGrpc::ImageListIdentifier.new(image_list_identifier: image_list_identifier)
|
42
|
-
|
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
|
49
|
-
|
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
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
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(
|
82
|
-
|
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
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
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
|
-
|
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)
|
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
|
data/lib/cloudkeeper/cli.rb
CHANGED
@@ -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-
|
26
|
-
|
27
|
-
|
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: '
|
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
|
-
|
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
|
-
|
5
|
-
|
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
|
-
|
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 = '',
|
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
|
-
|
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
|
85
|
-
appliance_hash.
|
86
|
-
appliance.
|
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,
|
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
|
-
|
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).
|
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.
|
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)
|
@@ -1,14 +1,20 @@
|
|
1
|
+
require 'digest'
|
2
|
+
require 'json'
|
3
|
+
|
1
4
|
module Cloudkeeper
|
2
5
|
module Entities
|
3
6
|
class Image
|
4
|
-
|
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
|
-
|
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
|
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
|
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)) \
|
@@ -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
|
@@ -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'
|
@@ -15,60 +15,79 @@ module Cloudkeeper
|
|
15
15
|
logger.debug 'Running appliance synchronization...'
|
16
16
|
backend_connector.pre_action
|
17
17
|
|
18
|
-
|
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
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
40
|
-
logger.debug
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
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
|
-
|
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
|
56
|
-
logger.debug
|
57
|
-
|
58
|
-
|
59
|
-
|
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(
|
63
|
-
|
64
|
-
|
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
|
-
|
109
|
-
|
110
|
-
|
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 :
|
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
|
16
|
+
def download_image_list
|
19
17
|
logger.debug 'Downloading fresh image lists...'
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
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
|
29
|
-
|
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
|
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
|
-
|
78
|
-
|
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
|
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)
|
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
|
-
|
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
|
-
|
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.
|
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.
|
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 #{
|
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)
|
@@ -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
|
data/lib/cloudkeeper/version.rb
CHANGED
data/lib/cloudkeeper_grpc.rb
CHANGED
@@ -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 "
|
5
|
-
path =
|
6
|
-
url = https://github.com/the-cloudkeeper-project/cloudkeeper-
|
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 :
|
23
|
-
|
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
|
-
|
6
|
-
|
7
|
-
|
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
|
-
|
22
|
-
|
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
|
}
|
File without changes
|
@@ -1,14 +1,10 @@
|
|
1
|
-
|
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,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:
|
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-
|
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.
|
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.
|
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: '
|
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: '
|
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.
|
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,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
|