berta 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rubocop.yml +26 -0
- data/.travis.yml +19 -0
- data/Gemfile +3 -0
- data/LICENSE +14 -0
- data/README.md +48 -0
- data/Rakefile +17 -0
- data/berta.gemspec +32 -0
- data/bin/berta +4 -0
- data/config/berta.yml +19 -0
- data/config/email.erb +18 -0
- data/lib/berta.rb +13 -0
- data/lib/berta/cli.rb +108 -0
- data/lib/berta/command_executor.rb +18 -0
- data/lib/berta/entities.rb +6 -0
- data/lib/berta/entities/expiration.rb +82 -0
- data/lib/berta/errors.rb +9 -0
- data/lib/berta/errors/backend_error.rb +5 -0
- data/lib/berta/errors/entities.rb +9 -0
- data/lib/berta/errors/entities/invalid_entity_xml_error.rb +7 -0
- data/lib/berta/errors/entities/no_user_email_error.rb +7 -0
- data/lib/berta/errors/opennebula.rb +13 -0
- data/lib/berta/errors/opennebula/authentication_error.rb +7 -0
- data/lib/berta/errors/opennebula/resource_not_found_error.rb +7 -0
- data/lib/berta/errors/opennebula/resource_retrieval_error.rb +7 -0
- data/lib/berta/errors/opennebula/resource_state_error.rb +7 -0
- data/lib/berta/errors/opennebula/stub_error.rb +7 -0
- data/lib/berta/errors/opennebula/user_not_authorized_error.rb +7 -0
- data/lib/berta/errors/standard_error.rb +5 -0
- data/lib/berta/expiration_manager.rb +42 -0
- data/lib/berta/notification_manager.rb +100 -0
- data/lib/berta/service.rb +118 -0
- data/lib/berta/settings.rb +37 -0
- data/lib/berta/utils.rb +6 -0
- data/lib/berta/utils/opennebula.rb +8 -0
- data/lib/berta/utils/opennebula/helper.rb +47 -0
- data/lib/berta/virtual_machine_handler.rb +120 -0
- metadata +304 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2c35df5b38eb1ec48f721aab12ab14ae6006f32d
|
4
|
+
data.tar.gz: 205b376e2b57fb9738880bdd47817b4933135f04
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9500f02e7755d6eab251cb337bf397cb9979f685fc13b736d433756289c3a5da0ba358cd4731f7e58eef955144f93aff8c8598f5e59cceba148a168cb472811a
|
7
|
+
data.tar.gz: 3581efd6664b2f26394e7e0bc752c22c01d7e983ea5d113e026044a3df7d65186e34dc305d992ad5accbe1720f75edd8ae110647b6fa795ffa0c77780b6ba466
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require: rubocop-rspec
|
2
|
+
|
3
|
+
AllCops:
|
4
|
+
TargetRubyVersion: 2.0
|
5
|
+
|
6
|
+
Metrics/LineLength:
|
7
|
+
Max: 135
|
8
|
+
|
9
|
+
Metrics/MethodLength:
|
10
|
+
Max: 15
|
11
|
+
|
12
|
+
Metrics/BlockLength:
|
13
|
+
Exclude:
|
14
|
+
- 'Rakefile'
|
15
|
+
- '**/*.rake'
|
16
|
+
- 'spec/**/*.rb'
|
17
|
+
- '*.gemspec'
|
18
|
+
Metrics/AbcSize:
|
19
|
+
Exclude:
|
20
|
+
- 'lib/berta/cli.rb'
|
21
|
+
|
22
|
+
RSpec/MultipleExpectations:
|
23
|
+
Enabled: false
|
24
|
+
|
25
|
+
RSpec/ExampleLength:
|
26
|
+
Enabled: false
|
data/.travis.yml
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
sudo: false
|
2
|
+
|
3
|
+
language: ruby
|
4
|
+
rvm:
|
5
|
+
- ruby-head
|
6
|
+
- 2.2.0
|
7
|
+
|
8
|
+
matrix:
|
9
|
+
allow_failures:
|
10
|
+
- rvm: ruby-head
|
11
|
+
fast_finish: true
|
12
|
+
|
13
|
+
branches:
|
14
|
+
only:
|
15
|
+
- master
|
16
|
+
|
17
|
+
before_install: 'gem install bundler -v 1.13.0'
|
18
|
+
|
19
|
+
script: 'bundle exec rake acceptance'
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
|
2
|
+
Copyright 2016 Dusan Baran
|
3
|
+
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
you may not use this file except in compliance with the License.
|
6
|
+
You may obtain a copy of the License at
|
7
|
+
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
See the License for the specific language governing permissions and
|
14
|
+
limitations under the License.
|
data/README.md
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# Berta
|
2
|
+
|
3
|
+
[![Build Status](https://travis-ci.org/dudoslav/berta.svg?branch=master)](https://travis-ci.org/dudoslav/berta)
|
4
|
+
[![Coverage Status](https://coveralls.io/repos/github/dudoslav/berta/badge.svg?branch=master)](https://coveralls.io/github/dudoslav/berta?branch=master)
|
5
|
+
[![Code Climate](https://codeclimate.com/github/dudoslav/berta/badges/gpa.svg)](https://codeclimate.com/github/dudoslav/berta)
|
6
|
+
[![Inline docs](http://inch-ci.org/github/dudoslav/berta.svg?branch=master)](http://inch-ci.org/github/dudoslav/berta)
|
7
|
+
[![Dependency Status](https://gemnasium.com/badges/github.com/dudoslav/berta.svg)](https://gemnasium.com/github.com/dudoslav/berta)
|
8
|
+
|
9
|
+
Berta cleans cloud from unused vms. She sets expiration to all virtual machines
|
10
|
+
and when expiration is close she will notify owners. Berta is developed as ruby gem.
|
11
|
+
|
12
|
+
## Getting started
|
13
|
+
|
14
|
+
### Installation
|
15
|
+
|
16
|
+
From rubygems:
|
17
|
+
|
18
|
+
```bash
|
19
|
+
gem install berta
|
20
|
+
```
|
21
|
+
|
22
|
+
From source:
|
23
|
+
|
24
|
+
```bash
|
25
|
+
git clone git://github.com/dudoslav/berta.git
|
26
|
+
cd cloudkeeper
|
27
|
+
gem install bundler
|
28
|
+
bundle install
|
29
|
+
```
|
30
|
+
|
31
|
+
### Configuration
|
32
|
+
|
33
|
+
Config files can be located in:
|
34
|
+
* `~/.berta/berta.yml`
|
35
|
+
* `/etc/berta/berta.yml`
|
36
|
+
* `PATH_TO_GEM_DIR/config/cloudkeeper.yml`
|
37
|
+
|
38
|
+
### Execution
|
39
|
+
|
40
|
+
Berta needs access to opennebula backend. To do that she needs to know opennebula
|
41
|
+
secret and endpoint. This can be specified in config file, as command line options or
|
42
|
+
by creating `one_auth` file in `~/.one`.
|
43
|
+
To run berta simply type:
|
44
|
+
```bash
|
45
|
+
berta
|
46
|
+
# or if backend and secret are not set
|
47
|
+
berta --opennebula-secret=<secret> --opennebula-endpoint=<endpoint>
|
48
|
+
```
|
data/Rakefile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'rspec/core/rake_task'
|
2
|
+
require 'rubocop/rake_task'
|
3
|
+
require 'bundler/gem_tasks'
|
4
|
+
|
5
|
+
RSpec::Core::RakeTask.new(:spec)
|
6
|
+
RuboCop::RakeTask.new
|
7
|
+
|
8
|
+
task default: :test
|
9
|
+
|
10
|
+
desc 'Run acceptance tests (RSpec + Rubocop)'
|
11
|
+
task test: 'acceptance'
|
12
|
+
|
13
|
+
desc 'Run acceptance tests (RSpec + Rubocop)'
|
14
|
+
task :acceptance do |_t|
|
15
|
+
Rake::Task['spec'].invoke
|
16
|
+
Rake::Task['rubocop'].invoke
|
17
|
+
end
|
data/berta.gemspec
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'berta'
|
3
|
+
s.version = '1.0.0'
|
4
|
+
s.summary = 'Berta VM expiration tool'
|
5
|
+
s.description = 'Berta will check all VMs on OpenNebula cloud for expiration date'
|
6
|
+
s.authors = ['Dusan Baran']
|
7
|
+
s.email = 'dbaran@hotmail.sk'
|
8
|
+
s.homepage = 'https://github.com/dudoslav/berta'
|
9
|
+
s.license = 'Apache License, Version 2.0'
|
10
|
+
|
11
|
+
s.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
12
|
+
s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
13
|
+
s.require_paths = ['lib']
|
14
|
+
|
15
|
+
s.add_development_dependency 'bundler', '~> 1.13'
|
16
|
+
s.add_development_dependency 'rake', '~> 11.2'
|
17
|
+
s.add_development_dependency 'rspec', '~> 3.5'
|
18
|
+
s.add_development_dependency 'rubocop', '~> 0.42'
|
19
|
+
s.add_development_dependency 'rubocop-rspec', '~> 1.7'
|
20
|
+
s.add_development_dependency 'pry', '~> 0.10'
|
21
|
+
s.add_development_dependency 'webmock', '~> 2.3'
|
22
|
+
s.add_development_dependency 'vcr', '~> 3.0'
|
23
|
+
s.add_development_dependency 'coveralls', '~> 0.8'
|
24
|
+
|
25
|
+
s.add_runtime_dependency 'thor', '~> 0.19'
|
26
|
+
s.add_runtime_dependency 'settingslogic', '~> 2.0'
|
27
|
+
s.add_runtime_dependency 'yell', '~> 2.0'
|
28
|
+
s.add_runtime_dependency 'opennebula', '~> 5.2'
|
29
|
+
s.add_runtime_dependency 'chronic_duration', '~> 0.10'
|
30
|
+
s.add_runtime_dependency 'mail', '~> 2.6'
|
31
|
+
s.add_runtime_dependency 'tilt', '~> 2.0'
|
32
|
+
end
|
data/bin/berta
ADDED
data/config/berta.yml
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
berta:
|
2
|
+
opennebula:
|
3
|
+
secret: # Secret for OpenNebula authentication
|
4
|
+
endpoint: # OpenNebula backend endpoint
|
5
|
+
expiration: # Action that will be set for execution
|
6
|
+
offset: 10 days # Time until action will be executed
|
7
|
+
action: terminate-hard # Type of action that will be executed
|
8
|
+
notification:
|
9
|
+
deadline: 1 day # Time when user should be notified before action will be executed
|
10
|
+
exclude: # Exclude VMs to ignore them
|
11
|
+
ids: # VMs with this IDs will be ignored
|
12
|
+
users: # VMs owned by this users will be ignored
|
13
|
+
groups: # VMs in this groups will be ignored
|
14
|
+
clusters: # VMs in this clusters will be ignored
|
15
|
+
logging:
|
16
|
+
file: /var/log/berta/berta.log # File ro write log to. To turn off file logging leave this field empty
|
17
|
+
level: error # Logging level
|
18
|
+
dry-run: false # Berta wont change anything on running instance of OpenNebula
|
19
|
+
debug: false # Debug mode
|
data/config/email.erb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
<%# Avaliable parametes: %>
|
2
|
+
<%# [String] user_name %>
|
3
|
+
<%# [String] user_email %>
|
4
|
+
<%# [Array<Hash>] vms, Array of Hashes with values: %>
|
5
|
+
<%# [String] id %>
|
6
|
+
<%# [String] name %>
|
7
|
+
<%# [String] expiration %>
|
8
|
+
From: cesnet@mail.muni.cz
|
9
|
+
To: <%= user_email %>
|
10
|
+
Subject: OpenNebula VM expiration
|
11
|
+
|
12
|
+
Hi <%= user_name %>,
|
13
|
+
your vms are about to expire..
|
14
|
+
|
15
|
+
VM Name | Expiration Date
|
16
|
+
<% vms.each do |vm| %>
|
17
|
+
<%= vm[:name] %> | <%= Time.at(vm[:expiration]) %>
|
18
|
+
<% end %>
|
data/lib/berta.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# Main Berta module
|
2
|
+
module Berta
|
3
|
+
autoload :Service, 'berta/service'
|
4
|
+
autoload :VirtualMachineHandler, 'berta/virtual_machine_handler'
|
5
|
+
autoload :ExpirationManager, 'berta/expiration_manager'
|
6
|
+
autoload :NotificationManager, 'berta/notification_manager'
|
7
|
+
autoload :CommandExecutor, 'berta/command_executor'
|
8
|
+
autoload :CLI, 'berta/cli'
|
9
|
+
autoload :Errors, 'berta/errors'
|
10
|
+
autoload :Entities, 'berta/entities'
|
11
|
+
autoload :Utils, 'berta/utils'
|
12
|
+
autoload :Settings, 'berta/settings'
|
13
|
+
end
|
data/lib/berta/cli.rb
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'yell'
|
3
|
+
|
4
|
+
module Berta
|
5
|
+
# CLI for berta
|
6
|
+
class CLI < Thor
|
7
|
+
def self.safe_fetch(keys)
|
8
|
+
current = Berta::Settings
|
9
|
+
keys.each do |key|
|
10
|
+
current = current[key]
|
11
|
+
break unless current
|
12
|
+
end
|
13
|
+
current
|
14
|
+
end
|
15
|
+
|
16
|
+
class_option :'opennebula-secret',
|
17
|
+
default: safe_fetch(%w(opennebula secret)),
|
18
|
+
type: :string
|
19
|
+
class_option :'opennebula-endpoint',
|
20
|
+
default: safe_fetch(%w(opennebula endpoint)),
|
21
|
+
type: :string
|
22
|
+
class_option :'expiration-offset',
|
23
|
+
required: true,
|
24
|
+
default: safe_fetch(%w(expiration offset)),
|
25
|
+
type: :string
|
26
|
+
class_option :'expiration-action',
|
27
|
+
required: true,
|
28
|
+
default: safe_fetch(%w(expiration action)),
|
29
|
+
type: :string
|
30
|
+
class_option :'notification-deadline',
|
31
|
+
required: true,
|
32
|
+
default: safe_fetch(%w(notification deadline)),
|
33
|
+
type: :string
|
34
|
+
class_option :'exclude-ids',
|
35
|
+
default: safe_fetch(%w(exclude ids)),
|
36
|
+
type: :array
|
37
|
+
class_option :'exclude-users',
|
38
|
+
default: safe_fetch(%w(exclude users)),
|
39
|
+
type: :array
|
40
|
+
class_option :'exclude-groups',
|
41
|
+
default: safe_fetch(%w(exclude groups)),
|
42
|
+
type: :array
|
43
|
+
class_option :'exclude-clusters',
|
44
|
+
default: safe_fetch(%w(exclude clusters)),
|
45
|
+
type: :array
|
46
|
+
class_option :'dry-run',
|
47
|
+
default: safe_fetch(%w(dry-run)),
|
48
|
+
type: :boolean
|
49
|
+
class_option :'logging-file',
|
50
|
+
default: safe_fetch(%w(logging file)),
|
51
|
+
type: :string
|
52
|
+
class_option :'logging-level',
|
53
|
+
required: true,
|
54
|
+
default: safe_fetch(%w(logging level)),
|
55
|
+
type: :string
|
56
|
+
class_option :debug,
|
57
|
+
default: safe_fetch(%w(debug)),
|
58
|
+
type: :boolean
|
59
|
+
|
60
|
+
desc 'cleanup', 'Task that sets all expiration to all vms and notifies users'
|
61
|
+
def cleanup
|
62
|
+
initialize_configuration(options)
|
63
|
+
initialize_logger(options)
|
64
|
+
Berta::CommandExecutor.cleanup
|
65
|
+
end
|
66
|
+
default_task :cleanup
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def initialize_configuration(options)
|
71
|
+
settings = Hash.new { |hash, key| hash[key] = {} }
|
72
|
+
settings['opennebula']['secret'] = options['opennebula-secret']
|
73
|
+
settings['opennebula']['endpoint'] = options['opennebula-endpoint']
|
74
|
+
settings['expiration']['offset'] = options['expiration-offset']
|
75
|
+
settings['expiration']['action'] = options['expiration-action']
|
76
|
+
settings['notification']['deadline'] = options['notification-deadline']
|
77
|
+
settings['exclude']['ids'] = options['exclude-ids']
|
78
|
+
settings['exclude']['users'] = options['exclude-users']
|
79
|
+
settings['exclude']['groups'] = options['exclude-groups']
|
80
|
+
settings['exclude']['clusters'] = options['exclude-clusters']
|
81
|
+
settings['dry-run'] = options['dry-run']
|
82
|
+
settings['debug'] = options['debug']
|
83
|
+
settings['logging']['file'] = options['logging-file']
|
84
|
+
settings['logging']['level'] = options['logging-level']
|
85
|
+
Berta::Settings.merge!(settings)
|
86
|
+
end
|
87
|
+
|
88
|
+
def initialize_logger(options)
|
89
|
+
logging_level = options['logging-level']
|
90
|
+
logging_level = 'debug' if options['debug'] || options['dry-run']
|
91
|
+
|
92
|
+
Yell.new :stdout, name: Object, level: logging_level, format: Yell::DefaultFormat
|
93
|
+
Object.send :include, Yell::Loggable
|
94
|
+
|
95
|
+
setup_file_logger(options['logging-file']) if options['logging-file']
|
96
|
+
|
97
|
+
logger.debug 'Running in debug mode...'
|
98
|
+
end
|
99
|
+
|
100
|
+
def setup_file_logger(logging_file)
|
101
|
+
unless (File.exist?(logging_file) && File.writable?(logging_file)) || File.writable?(File.dirname(logging_file))
|
102
|
+
logger.error "File #{logging_file} isn't writable"
|
103
|
+
return
|
104
|
+
end
|
105
|
+
logger.adapter :file, logging_file
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Berta
|
2
|
+
# Class for executing main berta commands
|
3
|
+
class CommandExecutor
|
4
|
+
# Function that performs clean up operation.
|
5
|
+
# Connects to opennebula database,
|
6
|
+
# runs expiration update process and
|
7
|
+
# notifies users about upcoming expirations.
|
8
|
+
def self.cleanup
|
9
|
+
service = Berta::Service.new(Berta::Settings['opennebula']['secret'],
|
10
|
+
Berta::Settings['opennebula']['endpoint'])
|
11
|
+
vms = service.running_vms
|
12
|
+
Berta::ExpirationManager.new.update_expirations(vms)
|
13
|
+
Berta::NotificationManager.new(service).notify_users(vms)
|
14
|
+
rescue Berta::Errors::BackendError => e
|
15
|
+
logger.error e.message
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module Berta
|
2
|
+
module Entities
|
3
|
+
# Class for storing expiration data, also can be created from xml
|
4
|
+
class Expiration
|
5
|
+
attr_reader :id, :time, :action
|
6
|
+
|
7
|
+
# Creates Expiration class instance from XMLElement.
|
8
|
+
#
|
9
|
+
# @param xml [XMLElement] XML from which to create instance
|
10
|
+
# @return [Berta::Entities::Expiration] Expiration from given xml
|
11
|
+
# @raise [Berta::Errors::Entities::InvalidEntityXMLError] If XMLElement was not in correct format
|
12
|
+
def self.from_xml(xml)
|
13
|
+
check_xml!(xml)
|
14
|
+
Berta::Entities::Expiration.new(xml['ID'], xml['TIME'], xml['ACTION'])
|
15
|
+
end
|
16
|
+
|
17
|
+
# Raises error if XML element is not in correct format.
|
18
|
+
#
|
19
|
+
# @param xml [XMLElement] XML to check for all required values
|
20
|
+
# @raise [Berta::Errors::Entities::InvalidEntityXMLError] If xml is not in correct format
|
21
|
+
def self.check_xml!(xml)
|
22
|
+
raise Berta::Errors::Entities::InvalidEntityXMLError, 'wrong enxpiration xml recieved' \
|
23
|
+
unless %w(ID
|
24
|
+
ACTION
|
25
|
+
TIME).all? { |path| xml.has_elements? path }
|
26
|
+
end
|
27
|
+
|
28
|
+
# Creates expiration class instance from given arguments.
|
29
|
+
#
|
30
|
+
# @param id [String] Schelude action id
|
31
|
+
# @param time [String] Schelude action execution time
|
32
|
+
# @param action [String] Schelude action
|
33
|
+
def initialize(id, time, action)
|
34
|
+
@id = id
|
35
|
+
@time = time
|
36
|
+
@action = action
|
37
|
+
end
|
38
|
+
|
39
|
+
# Generate schelude action template that can be used
|
40
|
+
# in VMS USER_TEMPLATE/SCHED_ACTION
|
41
|
+
#
|
42
|
+
# @return [String] Schelude action template
|
43
|
+
def template
|
44
|
+
<<-EOT
|
45
|
+
SCHED_ACTION = [
|
46
|
+
ID = "#{id}",
|
47
|
+
ACTION = "#{action}",
|
48
|
+
TIME = "#{time}"
|
49
|
+
]
|
50
|
+
EOT
|
51
|
+
end
|
52
|
+
|
53
|
+
# Determines if this schelude action is in notification interval.
|
54
|
+
# That means its expiration is closer to Time.now than notification
|
55
|
+
# deadline.
|
56
|
+
#
|
57
|
+
# @return [Boolean] Truthy if in notification interval else falsy
|
58
|
+
def in_notification_interval?
|
59
|
+
time_interval = time.to_i - Time.now.to_i
|
60
|
+
time_interval <= Berta::Settings.notification_deadline && time_interval >= 0
|
61
|
+
end
|
62
|
+
|
63
|
+
# Determines if this schelude action is in expiration interval.
|
64
|
+
# That means its expiration is closer to Time.now that expiration
|
65
|
+
# offset.
|
66
|
+
#
|
67
|
+
# @return [Boolean] Truthy if in expiration interval else falsy
|
68
|
+
def in_expiration_interval?
|
69
|
+
time_interval = time.to_i - Time.now.to_i
|
70
|
+
time_interval <= Berta::Settings.expiration_offset && time_interval >= 0
|
71
|
+
end
|
72
|
+
|
73
|
+
# Determines if this expiration has default expiration action set.
|
74
|
+
# Default expiration action is action defined in settings file.
|
75
|
+
#
|
76
|
+
# @return [Boolean] Truthy if has default action else falsy
|
77
|
+
def default_action?
|
78
|
+
action == Berta::Settings.expiration.action
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|