gearship 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 8aeb3e4ca5216f7eed6e9cb8816bce3012c914ac
4
+ data.tar.gz: eb9caa170948c310dbab24a250e139edc2885761
5
+ SHA512:
6
+ metadata.gz: bb2d7fab9e9c175ae7b07295723c317fda1fb878130f76efb2178851fcba6a7c9d2f2cb052673495d2f380d6219593da25d7425cc6fb52b9f1962a913b8d6b21
7
+ data.tar.gz: 47c33cf91042edf84b3b76f8400368e68f35c2ada3e2550213683d11308da1071bec810ebcfa64a82e55f71b49d2708bf26a57c584b11cc054d1ef05261919cf
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2017 Leonas.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,81 @@
1
+ ![](gearship.png)
2
+ --------
3
+ ## `gearship` deploys dockerized projects to virtual machines.
4
+
5
+ Created as a micro alternative to puppet, chef, capistrano, etc, for managing droplets on digital ocean.
6
+
7
+ ### Overview
8
+ - `gem install gearship`
9
+ - `gearship init`
10
+ - update `gearship.yml` with server configuration
11
+ - update `gearship.sh` with install configuration
12
+ - `gearship go setup_host`
13
+ - `gearship go deploy_app`
14
+ - your app is live!
15
+
16
+ ----------
17
+
18
+ ## Quickstart
19
+
20
+ Install Gem:
21
+
22
+ ```bash
23
+ $ gem install gearship
24
+ ```
25
+
26
+ `cd` into your project directory then create the `gearship` folder:
27
+
28
+ ```bash
29
+ $ cd my_project
30
+ $ gearship init
31
+ ```
32
+
33
+ `cd` into the `gearship` folder and edit `gearship.yml` with your server configuration
34
+
35
+ Run `gearship go setup_host` to install docker on a new server:
36
+
37
+ ```bash
38
+ $ cd gearship
39
+ $ gearship go setup_host
40
+ ```
41
+
42
+ Ship your dockerized project to the docker host:
43
+
44
+ ```bash
45
+ $ gearship go deploy_app
46
+ ```
47
+ ---------
48
+ ## gearship performs missions which are composed of actions.
49
+
50
+ - Shell scripts under the `missions` directory, such as `setup_host.sh` or `deploy_app.sh`, are automatically recognized as a mission.
51
+
52
+ - The actions folder has reusable actions that can be called from any `mission_name.sh` with `source actions/action_name.sh`.
53
+
54
+ - Actions can be retrieved remotely via HTTP. Put a URL in the actions section of `gearship.yml`, and gearship will automatically load the content and put it into the `compiled/actions` folder in the compile phase.
55
+
56
+ - `gearship go install_app` is equivalent to running `gearship.sh`, followed by `install_app.sh`.
57
+
58
+ ### What gearship does when `gearship go setup_app` is called
59
+
60
+ 1. Compile `gearship.yml` to generate attributes, retrieve remote actions, and copy files from `cargo` into the `compiled` directory. Append `setup_app.sh` file to a copy of `gearship.sh` in the `compiled` directory.
61
+ 2. SSH to the server specified in gearship.yml
62
+ 3. Transfer the content of the `compiled` directory to the remote server and extract in `$HOME/gearship`
63
+ 4. Run `gearship.sh` on the remote server.
64
+
65
+ ## Commands
66
+
67
+ ```bash
68
+ $ gearship # Show command help
69
+ $ gearship init # Install gearship into project
70
+ $ gearship go [mission] # go docker project
71
+ ```
72
+
73
+
74
+ ## Passing setup variables to scripts during compilation
75
+
76
+ #### Ruby
77
+ Attributes defined in `gearship.yml` are accessible from any file with `<%= @attributes.attribute_name %>` when `eval_erb: true` is set in `gearship.yml`
78
+
79
+ #### Bash
80
+ Attributes defined in `gearship.yml` are split into individual files in `compiled/attributes`.
81
+ Refer to them by `$(cat attributes/attribute_name)` in the script.
data/bin/gearship ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Abort beautifully with ctrl+c.
4
+ Signal.trap(:INT) { abort "\ngearship aborting." }
5
+
6
+ # Load the main lib and invoke CLI.
7
+ require 'gearship'
8
+ Gearship::Cli.start
data/gearship.gemspec ADDED
@@ -0,0 +1,21 @@
1
+ # coding: utf-8
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = 'gearship'
5
+ spec.version = '0.1.3' # retrieve this value by: Gem.loaded_specs['gearship'].version.to_s
6
+ spec.authors = ['Leonas']
7
+ spec.email = ['leonas@leonas.io']
8
+ spec.homepage = 'http://github.com/leonas/gearship'
9
+ spec.summary = %q{Deploy dockerized projects to virtual machines.}
10
+ spec.description = %q{Quickly set up a docker host and deploy your app with ease.}
11
+ spec.license = 'MIT'
12
+
13
+ spec.files = `git ls-files`.split($/)
14
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
15
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
16
+ spec.require_paths = ['lib']
17
+
18
+ spec.add_runtime_dependency 'thor'
19
+ spec.add_runtime_dependency 'rainbow'
20
+ spec.add_runtime_dependency 'net-ssh'
21
+ end
data/gearship.png ADDED
Binary file
@@ -0,0 +1,146 @@
1
+ require 'open3'
2
+ require 'ostruct'
3
+ require 'net/ssh'
4
+
5
+ module Gearship
6
+ class Cli < Thor
7
+ include Thor::Actions
8
+
9
+ desc 'init', 'Initialize gearship project'
10
+ def init(project = 'gearship')
11
+ do_init(project)
12
+ end
13
+
14
+ desc 'go [mission] [--sudo]', 'Send gearship on a mission'
15
+ method_options :sudo => false
16
+ def go(target, *args)
17
+ do_go(target, *args)
18
+ end
19
+
20
+ desc 'compile', 'Compile gearship project for debugging'
21
+ def compile(mission = nil)
22
+ do_compile(mission)
23
+ end
24
+
25
+ desc 'version', 'Show gearship version'
26
+ def version
27
+ puts Gem.loaded_specs['gearship'].version.to_s
28
+ end
29
+
30
+ no_tasks do
31
+ include Gearship::Utility
32
+
33
+ def self.source_root
34
+ File.expand_path('../../',__FILE__)
35
+ end
36
+
37
+ def do_init(project)
38
+ copy_file 'templates/.gitignore', "#{project}/.gitignore"
39
+ copy_file 'templates/gearship.yml', "#{project}/gearship.yml"
40
+ copy_file 'templates/gearship.sh', "#{project}/gearship.sh"
41
+
42
+ copy_file 'templates/actions/configure_firewall.sh', "#{project}/actions/configure_firewall.sh"
43
+ copy_file 'templates/actions/install_basics.sh', "#{project}/actions/install_basics.sh"
44
+ copy_file 'templates/actions/install_digitalocean_agent.sh', "#{project}/actions/install_digitalocean_agent.sh"
45
+ copy_file 'templates/actions/install_docker.sh', "#{project}/actions/install_docker.sh"
46
+ copy_file 'templates/actions/start_app.sh', "#{project}/actions/start_app.sh"
47
+
48
+ copy_file 'templates/missions/install_app.sh', "#{project}/missions/install_app.sh"
49
+ copy_file 'templates/missions/setup_host.sh', "#{project}/missions/setup_host.sh"
50
+ copy_file 'templates/missions/update_app.sh', "#{project}/missions/update_app.sh"
51
+
52
+ copy_file 'templates/cargo/sample.conf', "#{project}/cargo/sample.conf"
53
+ copy_file 'templates/notes/sample.md', "#{project}/notes/sample.md"
54
+ end
55
+
56
+ def do_go(*args)
57
+ mission = args[0]
58
+
59
+ do_compile(mission)
60
+
61
+ sudo = 'sudo ' if options.sudo?
62
+ user, host, port = parse_target(@config['attributes']['ssh_target'])
63
+ endpoint = "#{user}@#{host}"
64
+
65
+ # Remove server key from known hosts to avoid mismatch errors when VMs change.
66
+ `ssh-keygen -R #{host} 2> /dev/null`
67
+
68
+ remote_commands = <<-EOS
69
+ rm -rf ~/gearship &&
70
+ mkdir ~/gearship &&
71
+ cd ~/gearship &&
72
+ tar xz &&
73
+ #{sudo}bash gearship.sh
74
+ EOS
75
+
76
+ remote_commands.strip! << ' && rm -rf ~/gearship' if @config['preferences'] and @config['preferences']['erase_remote_folder']
77
+
78
+ local_commands = <<-EOS
79
+ cd compiled
80
+ tar cz . | ssh -o 'StrictHostKeyChecking no' #{endpoint} -p #{port} '#{remote_commands}'
81
+ EOS
82
+
83
+ Open3.popen3(local_commands) do |stdin, stdout, stderr|
84
+ stdin.close
85
+ t = Thread.new do
86
+ while (line = stderr.gets)
87
+ print line.color(:red)
88
+ end
89
+ end
90
+ while (line = stdout.gets)
91
+ print line.color(:green)
92
+ end
93
+ t.join
94
+ end
95
+ end
96
+
97
+ def do_compile(mission)
98
+ abort_with 'You must be in the gearship folder' unless File.exists?('gearship.yml')
99
+ abort_with "#{mission} doesn't exist!" if mission and !File.exists?("missions/#{mission}.sh")
100
+
101
+ @config = YAML.load(File.read('gearship.yml'))
102
+
103
+ @config['attributes'] ||= {}
104
+ @config['attributes'].update(Hash[@instance_attributes.map{|k,v| [k.to_s, v] }]) if @instance_attributes
105
+
106
+ (@config['attributes'] || {}).each {|key, value| create_file "compiled/attributes/#{key}", value }
107
+
108
+ cache_remote_actions = @config['preferences'] && @config['preferences']['cache_remote_actions']
109
+ (@config['actions'] || []).each do |key, value|
110
+ next if cache_remote_actions and File.exists?("compiled/actions/#{key}.sh")
111
+ get value, "compiled/actions/#{key}.sh"
112
+ end
113
+
114
+ copy_or_template = (@config['preferences'] && @config['preferences']['eval_erb']) ? :template : :copy_file
115
+ copy_local_files(@config, copy_or_template)
116
+
117
+ if mission
118
+ if copy_or_template == :template
119
+ template File.expand_path('gearship.sh'), 'compiled/_gearship.sh'
120
+ create_file 'compiled/gearship.sh', File.binread('compiled/_gearship.sh') << "\n" << File.binread("compiled/missions/#{mission}.sh")
121
+ else
122
+ create_file 'compiled/gearship.sh', File.binread('gearship.sh') << "\n" << File.binread("missions/#{mission}.sh")
123
+ end
124
+ else
125
+ send copy_or_template, File.expand_path('gearship.sh'), 'compiled/gearship.sh'
126
+ end
127
+ end
128
+
129
+ def parse_target(target)
130
+ target.match(/(.*@)?(.*?)(:.*)?$/)
131
+ config = Net::SSH::Config.for($2)
132
+ [ ($1 && $1.delete('@') || config[:user] || 'root'),
133
+ config[:host_name] || $2,
134
+ ($3 && $3.delete(':') || config[:port] && config[:port].to_s || '22') ]
135
+ end
136
+
137
+ def copy_local_files(config, copy_or_template)
138
+ @attributes = OpenStruct.new(config['attributes'])
139
+ files = Dir['{actions,missions,cargo}/**/*'].select { |file| File.file?(file) }
140
+ files.each { |file| send copy_or_template, File.expand_path(file), File.expand_path("compiled/#{file}") }
141
+
142
+ (config['files'] || []).each {|file| send copy_or_template, File.expand_path(file), "compiled/cargo/#{File.basename(file)}" }
143
+ end
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,17 @@
1
+ module Gearship
2
+ module Logger
3
+ class << self
4
+ def info(text)
5
+ puts text.bright
6
+ end
7
+
8
+ def success(text)
9
+ puts text.color(:green).bright
10
+ end
11
+
12
+ def error(text)
13
+ puts text.color(:red).bright
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ module Gearship
2
+ module Utility
3
+ def abort_with(text)
4
+ Logger.error text
5
+ abort
6
+ end
7
+
8
+ def exit_with(text)
9
+ Logger.success text
10
+ exit
11
+ end
12
+
13
+ def say(text)
14
+ Logger.info text
15
+ end
16
+ end
17
+ end
data/lib/gearship.rb ADDED
@@ -0,0 +1,12 @@
1
+ require 'thor'
2
+ require 'rainbow'
3
+ require 'yaml'
4
+
5
+ require 'rainbow/version'
6
+ require 'rainbow/ext/string' unless Rainbow::VERSION < '2.0.0'
7
+
8
+ module Gearship
9
+ autoload :Cli, 'gearship/cli'
10
+ autoload :Logger, 'gearship/logger'
11
+ autoload :Utility, 'gearship/utility'
12
+ end
@@ -0,0 +1 @@
1
+ /compiled
@@ -0,0 +1,19 @@
1
+ # install if not already present
2
+ if gearship.install "ufw"; then
3
+ apt-get -y install ufw
4
+ fi
5
+
6
+ # configure ufw
7
+ if ufw status | grep -q 'Status: active'; then
8
+ echo "ufw already configured, skipping."
9
+ else
10
+
11
+ # make sure ssh connection is not dropped
12
+ ufw allow ssh
13
+
14
+ # enable firewall
15
+ ufw --force enable
16
+ ufw allow ssh
17
+ ufw allow http
18
+ ufw allow https
19
+ fi
@@ -0,0 +1,6 @@
1
+ # Update apt catalog and upgrade installed packages
2
+ gearship.mute "apt-get update"
3
+ gearship.mute "apt-get -y upgrade"
4
+
5
+ # install most important packages
6
+ apt-get -y install git ntp curl
@@ -0,0 +1 @@
1
+ curl -sSL https://agent.digitalocean.com/install.sh | sh
@@ -0,0 +1,12 @@
1
+ if gearship.install "docker-ce"; then
2
+ sudo apt-get install -y linux-image-extra-$(uname -r) linux-image-extra-virtual
3
+
4
+ sudo apt-get install -y apt-transport-https ca-certificates curl software-properties-common
5
+
6
+ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
7
+
8
+ sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
9
+
10
+ sudo apt-get update
11
+ sudo apt-get install -y docker-ce
12
+ fi
@@ -0,0 +1,3 @@
1
+ docker login -u="<%= @attributes.docker_username %>" -p="<%= @attributes.docker_password %>"
2
+ docker pull <%= @attributes.docker_repo %>
3
+ docker run -d -p <%= @attributes.app_port_map %> --name=<%= @attributes.app_name %> --restart=always -d <%= @attributes.app_repo %>
@@ -0,0 +1 @@
1
+ # All files in this folder get transferred to remote server.
@@ -0,0 +1,52 @@
1
+ #!/bin/bash
2
+
3
+ # Main install script. mission scripts get appended to this when executing
4
+ # bundle exec gearship setup_host
5
+
6
+ # Exit immediately if a pipeline returns a non-zero status.
7
+ # (unless part of the test in an if statement)
8
+ set -e
9
+
10
+ export DEBIAN_FRONTEND=noninteractive
11
+
12
+ # Mute STDOUT and STDERR
13
+ function gearship.mute() {
14
+ echo "Running \"$@\""
15
+ `$@ >/dev/null 2>&1`
16
+ return $?
17
+ }
18
+
19
+ function gearship.installed() {
20
+ dpkg -s $@ >/dev/null 2>&1
21
+ return $?
22
+ }
23
+
24
+ function gearship.install() {
25
+ if gearship.installed "$@"; then
26
+ echo "$@ already installed"
27
+ return 1
28
+ else
29
+ echo "No packages found matching $@. Installing..."
30
+ gearship.mute "apt-get -y install $@"
31
+ return 0
32
+ fi
33
+ }
34
+
35
+ echo "
36
+ ___ _ _
37
+ / __| ___ __ _ _ _ ___| |_ (_) _ __
38
+ | (_ |/ -_)/ _\` || '_|(_-<| ' \ | || '_ \\
39
+ \___|\___|\__,_||_| /__/|_||_||_|| .__/
40
+ |_|
41
+
42
+ ___ _ _ __ _
43
+ / _ \| ' \ / _\` |
44
+ \___/|_||_| \__,_|
45
+
46
+ _ _ _
47
+ _ __ (_) ___ ___(_) ___ _ _ | |
48
+ | ' \ | |(_-<(_-<| |/ _ \| ' \ |_|
49
+ |_|_|_||_|/__//__/|_|\___/|_||_|(_)
50
+
51
+
52
+ "
@@ -0,0 +1,25 @@
1
+ ---
2
+ # variables here will be compiled to individual files in compiled/attributes.
3
+ attributes:
4
+ ssh_target: user@target:port
5
+ docker_username: docker_user
6
+ docker_password: docker_password
7
+ app_name: app_name
8
+ app_repo: username/repository
9
+ app_port_map: "4000:4000"
10
+
11
+ # Remote actions here will be downloaded to compiled/actions.
12
+ actions:
13
+ # install_newrelic: https://github.com/leonas/gearship-actions/install_newrelic.sh
14
+
15
+ # Listed files will be copied to compiled/files.
16
+ # files:
17
+ # - ~/.ssh/id_rsa.pub
18
+
19
+ preferences:
20
+ # Erase the generated folder on the server after deploy.
21
+ erase_remote_folder: true
22
+ cache_remote_actions: false
23
+
24
+ # Use variables like <%= @attributes.environment %> in actions, roles, files and install.sh.
25
+ eval_erb: true
@@ -0,0 +1 @@
1
+ source actions/start_app.sh
@@ -0,0 +1,10 @@
1
+ #!/bin/bash
2
+
3
+
4
+
5
+ source actions/install_basics.sh
6
+ source actions/install_docker.sh
7
+ source actions/configure_firewall.sh
8
+
9
+ # https://github.com/v2tec/watchtower
10
+ # Use watchtower to keep docker files auto updated
@@ -0,0 +1,2 @@
1
+ docker stop <%= @attributes.app_name %>
2
+ source actions/start_app.sh
@@ -0,0 +1,5 @@
1
+ Notes folder:
2
+ - unfinished action and mission scripts here so they don't cause errors when compiling.
3
+ - ideas and to do's for what to automate next using gearship
4
+
5
+ Feel free to delete this file and/or folder.
metadata ADDED
@@ -0,0 +1,111 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gearship
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.3
5
+ platform: ruby
6
+ authors:
7
+ - Leonas
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-06-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: thor
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rainbow
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: net-ssh
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: Quickly set up a docker host and deploy your app with ease.
56
+ email:
57
+ - leonas@leonas.io
58
+ executables:
59
+ - gearship
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - ".gitignore"
64
+ - Gemfile
65
+ - LICENSE
66
+ - README.md
67
+ - bin/gearship
68
+ - gearship.gemspec
69
+ - gearship.png
70
+ - lib/gearship.rb
71
+ - lib/gearship/cli.rb
72
+ - lib/gearship/logger.rb
73
+ - lib/gearship/utility.rb
74
+ - lib/templates/.gitignore
75
+ - lib/templates/actions/configure_firewall.sh
76
+ - lib/templates/actions/install_basics.sh
77
+ - lib/templates/actions/install_digitalocean_agent.sh
78
+ - lib/templates/actions/install_docker.sh
79
+ - lib/templates/actions/start_app.sh
80
+ - lib/templates/cargo/sample.conf
81
+ - lib/templates/gearship.sh
82
+ - lib/templates/gearship.yml
83
+ - lib/templates/missions/install_app.sh
84
+ - lib/templates/missions/setup_host.sh
85
+ - lib/templates/missions/update_app.sh
86
+ - lib/templates/notes/sample.md
87
+ homepage: http://github.com/leonas/gearship
88
+ licenses:
89
+ - MIT
90
+ metadata: {}
91
+ post_install_message:
92
+ rdoc_options: []
93
+ require_paths:
94
+ - lib
95
+ required_ruby_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ required_rubygems_version: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ requirements: []
106
+ rubyforge_project:
107
+ rubygems_version: 2.5.2
108
+ signing_key:
109
+ specification_version: 4
110
+ summary: Deploy dockerized projects to virtual machines.
111
+ test_files: []