gitlab-swat 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitlab-ci.yml +20 -0
- data/.rubocop.yml +30 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +52 -0
- data/README.md +101 -0
- data/cog-command +10 -0
- data/config.yaml +50 -0
- data/gitlab-swat.gemspec +27 -0
- data/lib/cog_cmd/swat/dryrun.rb +18 -0
- data/lib/cog_cmd/swat/reload.rb +25 -0
- data/lib/cog_cmd/swat/strike.rb +18 -0
- data/lib/rails_loader.rb +44 -0
- data/lib/swat.rb +131 -0
- data/lib/swat_git.rb +62 -0
- data/lib/swat_run.rb +20 -0
- data/scripts/invalid.rb +0 -0
- data/scripts/test.rb +23 -0
- data/spec/helpers/fail_stub.sh +6 -0
- data/spec/helpers/rails_stub.rb +8 -0
- data/spec/rails_loader_spec.rb +21 -0
- data/spec/spec_helper.rb +43 -0
- data/spec/swat_command_execution_spec.rb +64 -0
- data/spec/swat_command_spec.rb +13 -0
- data/spec/swat_config_spec.rb +8 -0
- data/spec/swat_dryrun_spec.rb +21 -0
- data/spec/swat_git_spec.rb +66 -0
- data/spec/swat_parameters_spec.rb +17 -0
- data/spec/swat_reload_spec.rb +61 -0
- data/spec/swat_script_spec.rb +24 -0
- data/spec/swat_strike_spec.rb +22 -0
- metadata +151 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: d7764e07cd7efc9efec68895fe07f7c8a2c3db9d
|
4
|
+
data.tar.gz: 679cc6ac1ae287113cb9fc3f70c6282c2eaaedb6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f323d1a8b248bad4c21b11a734a97697bb96961df59dee5b724dbe64bf4ecda27773a8120b40438349097c6f5511a30c60996412671fb40a49f8fe5e6b00deef
|
7
|
+
data.tar.gz: d5165ea4b0ebfb42e9ddc10d63fae6b8e2a5b1ea80e30f324ed0514b2ec99fc812b674684aebe3cb21f2e16bef0c0f40698f9bbe88dde91f0a57bc031d508a0a
|
data/.gitlab-ci.yml
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
image: ruby:2.3
|
2
|
+
before_script:
|
3
|
+
- ruby -v
|
4
|
+
- which ruby
|
5
|
+
- gem install bundler --no-ri --no-rdoc
|
6
|
+
- bundle install --jobs $(nproc) "${FLAGS[@]}" --path vendor
|
7
|
+
- git config --global user.email "you@example.com"
|
8
|
+
- git config --global user.name "Your Name"
|
9
|
+
|
10
|
+
cache:
|
11
|
+
paths:
|
12
|
+
- vendor
|
13
|
+
|
14
|
+
rspec:
|
15
|
+
script:
|
16
|
+
- bundle exec rspec -f d -c -b
|
17
|
+
|
18
|
+
rubocop:
|
19
|
+
script:
|
20
|
+
- bundle exec rubocop
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# Commonly used screens these days easily fit more than 80 characters.
|
2
|
+
Metrics/LineLength:
|
3
|
+
Max: 120
|
4
|
+
# Just use double quotes please
|
5
|
+
#
|
6
|
+
Style/StringLiterals:
|
7
|
+
EnforcedStyle: double_quotes
|
8
|
+
|
9
|
+
Style/FrozenStringLiteralComment:
|
10
|
+
Enabled: false
|
11
|
+
|
12
|
+
# Jim Weirich block style
|
13
|
+
Style/BlockDelimiters:
|
14
|
+
EnforcedStyle: semantic
|
15
|
+
|
16
|
+
Style/SignalException:
|
17
|
+
EnforcedStyle: semantic
|
18
|
+
|
19
|
+
Style/RaiseArgs:
|
20
|
+
EnforcedStyle: compact
|
21
|
+
|
22
|
+
Metrics/MethodLength:
|
23
|
+
Max: 15
|
24
|
+
|
25
|
+
Metrics/AbcSize:
|
26
|
+
Enabled: false
|
27
|
+
|
28
|
+
Metrics/BlockLength:
|
29
|
+
Exclude:
|
30
|
+
- 'spec/**/*.rb'
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
GEM
|
2
|
+
remote: https://rubygems.org/
|
3
|
+
specs:
|
4
|
+
ast (2.3.0)
|
5
|
+
cog-rb (0.4.4)
|
6
|
+
rake (~> 11.2)
|
7
|
+
diff-lcs (1.3)
|
8
|
+
docile (1.1.5)
|
9
|
+
json (2.0.3)
|
10
|
+
parser (2.4.0.0)
|
11
|
+
ast (~> 2.2)
|
12
|
+
powerpack (0.1.1)
|
13
|
+
rainbow (2.2.1)
|
14
|
+
rake (11.3.0)
|
15
|
+
rspec (3.5.0)
|
16
|
+
rspec-core (~> 3.5.0)
|
17
|
+
rspec-expectations (~> 3.5.0)
|
18
|
+
rspec-mocks (~> 3.5.0)
|
19
|
+
rspec-core (3.5.4)
|
20
|
+
rspec-support (~> 3.5.0)
|
21
|
+
rspec-expectations (3.5.0)
|
22
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
23
|
+
rspec-support (~> 3.5.0)
|
24
|
+
rspec-mocks (3.5.0)
|
25
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
26
|
+
rspec-support (~> 3.5.0)
|
27
|
+
rspec-support (3.5.0)
|
28
|
+
rubocop (0.48.0)
|
29
|
+
parser (>= 2.3.3.1, < 3.0)
|
30
|
+
powerpack (~> 0.1)
|
31
|
+
rainbow (>= 1.99.1, < 3.0)
|
32
|
+
ruby-progressbar (~> 1.7)
|
33
|
+
unicode-display_width (~> 1.0, >= 1.0.1)
|
34
|
+
ruby-progressbar (1.8.1)
|
35
|
+
simplecov (0.14.1)
|
36
|
+
docile (~> 1.1.0)
|
37
|
+
json (>= 1.8, < 3)
|
38
|
+
simplecov-html (~> 0.10.0)
|
39
|
+
simplecov-html (0.10.0)
|
40
|
+
unicode-display_width (1.1.3)
|
41
|
+
|
42
|
+
PLATFORMS
|
43
|
+
ruby
|
44
|
+
|
45
|
+
DEPENDENCIES
|
46
|
+
cog-rb (~> 0.4)
|
47
|
+
rspec (~> 3.5)
|
48
|
+
rubocop (~> 0.42)
|
49
|
+
simplecov (~> 0.13)
|
50
|
+
|
51
|
+
BUNDLED WITH
|
52
|
+
1.13.7
|
data/README.md
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
[![coverage report](https://gitlab.com/gitlab-cog/swat/badges/master/coverage.svg)](https://gitlab.com/gitlab-cog/swat/commits/master)
|
2
|
+
|
3
|
+
# GitLab SWAT
|
4
|
+
|
5
|
+
A Successful Deployment Ends Peacefully With No Bullets Fired.
|
6
|
+
If That’s Simply Not Possible, SWAT Uses Special Weapons and Tactics to Keep the Public Safe
|
7
|
+
|
8
|
+
## Defining ~~Weapons~~ Scripts
|
9
|
+
|
10
|
+
A script should only contain one class with the following structure
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
module Swat
|
14
|
+
#
|
15
|
+
# Command to use when calling the script file, it has to respect the module and class name
|
16
|
+
# because it will be imported from rails and called by name
|
17
|
+
#
|
18
|
+
class Command < BaseCommand
|
19
|
+
def prepare(context)
|
20
|
+
fail "I need at least 1 argument" if @args.empty?
|
21
|
+
context[:some_key] = "something"
|
22
|
+
"text to add to the prepare stage result"
|
23
|
+
end
|
24
|
+
|
25
|
+
def pre_check(context)
|
26
|
+
fail "something is not right" unless context[:some_key] == "something"
|
27
|
+
"text to add to the pre_check stage result"
|
28
|
+
end
|
29
|
+
|
30
|
+
def execute(context)
|
31
|
+
fail "execution failed!" if context.empty?
|
32
|
+
"Context so far is #{context}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
```
|
37
|
+
|
38
|
+
### Execution Stages
|
39
|
+
|
40
|
+
* prepare: initial stage, used to parse arguments, or whatever is necessary before getting into the pre_check stage.
|
41
|
+
* pre_check: stage used to validate that the command should continue to the execute stage if it is running in execute mode. Dryrun would only reach this stage.
|
42
|
+
* execute: the actual operation.
|
43
|
+
|
44
|
+
Any stage that raises an exception will stop the execution, and will force and early return with a failure state and the different messages from the executed phases.
|
45
|
+
|
46
|
+
### Available tools
|
47
|
+
|
48
|
+
* `@args` the arguments tha are sent from the execution and reach the command, simply a string array.
|
49
|
+
* `context` a hashmap that is created before the prepare stage and is sent to all methods, use this to accumulate state across stages.
|
50
|
+
|
51
|
+
# Configuring in cog
|
52
|
+
|
53
|
+
## Environment variables
|
54
|
+
|
55
|
+
* **SCRIPTS_REMOTE_URL** url pointing to the remote repository
|
56
|
+
* **SCRIPTS_LOCAL_PATH** folder where the remote repository will be downloaded to
|
57
|
+
* **RAILS_RUNNER_COMMAND** command used to run rails, for example: bundle exec rails runner ./scripts/lib/swat_run.rb
|
58
|
+
* **RAILS_WORKING_DIR** working dir in which to execute the rails runner command
|
59
|
+
|
60
|
+
## Cog Commands
|
61
|
+
|
62
|
+
* `dryrun <script> [args]` executes the given script with arguments in dryrun mode
|
63
|
+
* `strike <script> [args]` executes the given script with arguments in execute mode
|
64
|
+
* `reload [-f]` clones or pulls the scripts repo, use _-f_ to wipe the repo and clone it from scratch
|
65
|
+
|
66
|
+
# Development
|
67
|
+
|
68
|
+
## How to run integration tests
|
69
|
+
|
70
|
+
### Dry Run Mode
|
71
|
+
|
72
|
+
```sh
|
73
|
+
$ SCRIPTS_LOCAL_PATH=/home/user/src/gitlab.com/gitlab-cog/swat/scripts RAILS_RUNNER_COMMAND="rails r /home/user/src/gitlab.com/gitlab-cog/swat/lib/swat_run.rb" RAILS_WORKING_DIR=/home/user/src/gitlab.com/gitlab-cog/rails-project COG_COMMAND="dryrun" COG_ARGV_0="test" COG_ARGV_1="success" COG_ARGV_2="success" COG_ARGC=3 ./cog-command
|
74
|
+
COG_TEMPLATE: execution_result
|
75
|
+
{"execution_mode":"dryrun","prepare":{"successful":true,"output":"preparation is fine so far"},"pre_check":{"successful":true,"output":"all is gut"}}
|
76
|
+
```
|
77
|
+
|
78
|
+
### Strike Mode
|
79
|
+
|
80
|
+
```sh
|
81
|
+
$ SCRIPTS_LOCAL_PATH=/home/user/src/gitlab.com/gitlab-cog/swat/scripts RAILS_RUNNER_COMMAND="rails r /home/user/src/gitlab.com/gitlab-cog/swat/lib/swat_run.rb" RAILS_WORKING_DIR=/home/user/src/gitlab.com/gitlab-cog/rails-project COG_COMMAND="strike" COG_ARGV_0="test" COG_ARGV_1="success" COG_ARGV_2="success" COG_ARGC=3 ./cog-command
|
82
|
+
COG_TEMPLATE: execution_result
|
83
|
+
{"execution_mode":"execute","prepare":{"successful":true,"output":"preparation is fine so far"},"pre_check":{"successful":true,"output":"all is gut"},"execute":{"successful":true,"output":"Context so far is {:prepared=\u003e\"done\", :checks=\u003e\"done\"}"}}
|
84
|
+
```
|
85
|
+
|
86
|
+
### Reload command
|
87
|
+
|
88
|
+
```sh
|
89
|
+
$ SCRIPTS_LOCAL_PATH=/tmp/testing-cog/second SCRIPTS_REMOTE_URL=$(pwd) COG_COMMAND="reload" ./cog-command
|
90
|
+
{"source":"/home/user/src/gitlab.com/gitlab-cog/swat","target":"/tmp/testing-cog/scripts","action":"clone","wiped":false, "head":"1234 current commit"}
|
91
|
+
```
|
92
|
+
|
93
|
+
```sh
|
94
|
+
$ SCRIPTS_LOCAL_PATH=/tmp/testing-cog/second SCRIPTS_REMOTE_URL=$(pwd) COG_COMMAND="reload" ./cog-command
|
95
|
+
{"source":"/home/user/src/gitlab.com/gitlab-cog/swat","target":"/tmp/testing-cog/scripts","action":"pull","wiped":false, "head":"1234 current commit"}
|
96
|
+
```
|
97
|
+
|
98
|
+
```sh
|
99
|
+
$ SCRIPTS_LOCAL_PATH=/tmp/testing-cog/second SCRIPTS_REMOTE_URL=$(pwd) COG_COMMAND="reload" COG_OPTS=wipe COG_OPT_WIPE=true ./cog-command
|
100
|
+
{"source":"/home/user/src/gitlab.com/gitlab-cog/swat","target":"/tmp/testing-cog/scripts","action":"clone","wiped":true, "head":"1234 current commit"}
|
101
|
+
```
|
data/cog-command
ADDED
data/config.yaml
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
---
|
2
|
+
cog_bundle_version: 4
|
3
|
+
name: swat
|
4
|
+
version: 0.0.1
|
5
|
+
docker:
|
6
|
+
image: gitlab/swat
|
7
|
+
tag: 0.0.1
|
8
|
+
description: >
|
9
|
+
A Successful Deployment Ends Peacefully With No Bullets Fired.
|
10
|
+
If That’s Simply Not Possible, SWAT Uses Special Weapons and Tactics to Keep the Public Safe
|
11
|
+
config:
|
12
|
+
env:
|
13
|
+
- var: SCRIPT_REMOTE_URL
|
14
|
+
description: Required Url for a git repo where the scripts are stored
|
15
|
+
- var: SCRIPT_LOCAL_PATH
|
16
|
+
description: Required path to a local semi-persistent folder where to clone and update the scripts repo
|
17
|
+
- var: RAILS_EXECUTABLE
|
18
|
+
description: Required path in which to find the rails executable script with which load the script
|
19
|
+
homepage: https://gitlab.com/gitlab-cog/swat
|
20
|
+
author: Pablo Carranza <pablo@gitlab.com>
|
21
|
+
permissions:
|
22
|
+
- swat:reload
|
23
|
+
- swat:dryrun
|
24
|
+
- swat:strike
|
25
|
+
commands:
|
26
|
+
reload:
|
27
|
+
description: Clones or pulls the scripts repo
|
28
|
+
executable: /home/bundle/cog-command
|
29
|
+
options:
|
30
|
+
wipe:
|
31
|
+
type: bool
|
32
|
+
required: false
|
33
|
+
rules:
|
34
|
+
- must have swat:reload
|
35
|
+
dryrun:
|
36
|
+
description: Runs a script up to the precheck phase, used for training, showing or simply checking
|
37
|
+
executable: /home/bundle/cog-command
|
38
|
+
arguments: "<script> [args...]"
|
39
|
+
rules:
|
40
|
+
- must have swat:dryrun
|
41
|
+
strike:
|
42
|
+
description: Runs a script to the end, performing changes to the system.
|
43
|
+
executable: /home/bundle/cog-command
|
44
|
+
arguments: "<script> [args...]"
|
45
|
+
rules:
|
46
|
+
- must have swat:execute
|
47
|
+
templates:
|
48
|
+
execution_result:
|
49
|
+
body: |
|
50
|
+
~json var=$results~
|
data/gitlab-swat.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "gitlab-swat"
|
3
|
+
s.version = "0.1.0"
|
4
|
+
s.date = "2017-04-01"
|
5
|
+
s.summary = "ChatOps Cog Bundle that enables admins to remotely run predefined scripts in a rails console"
|
6
|
+
s.description = <<~eos
|
7
|
+
A Successful Deployment Ends Peacefully With No Bullets Fired.
|
8
|
+
If That’s Simply Not Possible, SWAT Uses Special Weapons and Tactics to Keep the Public Safe
|
9
|
+
|
10
|
+
GitLab-Swat allows admins to quickly deploy scripts that can be remotely executed through a rails console
|
11
|
+
|
12
|
+
Allowing fast action by using an external git repository as the scripts source, but keeping safety high by
|
13
|
+
enforcing a prepare-pre check-execute model that allows execution break at any stage if things are not going
|
14
|
+
as expected
|
15
|
+
eos
|
16
|
+
s.authors = ["Pablo Carranza"]
|
17
|
+
s.email = "pablo@gitlab.com"
|
18
|
+
s.files = `git ls-files -z`.split("\x0")
|
19
|
+
s.test_files = s.files.grep(%r{^(spec)/})
|
20
|
+
s.license = "MIT"
|
21
|
+
|
22
|
+
s.add_dependency "cog-rb", "~> 0.4"
|
23
|
+
|
24
|
+
s.add_development_dependency "rspec", "~> 3.5"
|
25
|
+
s.add_development_dependency "rubocop", "~> 0.42"
|
26
|
+
s.add_development_dependency "simplecov", "~> 0.13"
|
27
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require "cog"
|
2
|
+
require "swat"
|
3
|
+
require "rails_loader"
|
4
|
+
|
5
|
+
module CogCmd
|
6
|
+
module Swat
|
7
|
+
#
|
8
|
+
# Cog Command that loads and dryruns the given script
|
9
|
+
#
|
10
|
+
class Dryrun < Cog::Command
|
11
|
+
def run_command
|
12
|
+
rails = ::Swat::RailsLoader.new
|
13
|
+
response.template = "execution_result"
|
14
|
+
response.content = rails.run("dryrun #{request.args.join(' ')}")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require "cog"
|
2
|
+
require "json"
|
3
|
+
require "swat_git"
|
4
|
+
|
5
|
+
module CogCmd
|
6
|
+
module Swat
|
7
|
+
#
|
8
|
+
# Cog Command that [re]loads a local git repo for scripts
|
9
|
+
#
|
10
|
+
class Reload < Cog::Command
|
11
|
+
def run_command
|
12
|
+
git = ::Swat::Git.new
|
13
|
+
git.wipe if wipe?
|
14
|
+
result = { source: git.source,
|
15
|
+
target: git.target,
|
16
|
+
wiped: wipe? }.merge(git.update)
|
17
|
+
response.content = result.to_json
|
18
|
+
end
|
19
|
+
|
20
|
+
def wipe?
|
21
|
+
request.options["wipe"] == true || request.options["wipe"] == "true"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require "cog"
|
2
|
+
require "swat"
|
3
|
+
require "rails_loader"
|
4
|
+
|
5
|
+
module CogCmd
|
6
|
+
module Swat
|
7
|
+
#
|
8
|
+
# Cog Command that loads and executes the given script
|
9
|
+
#
|
10
|
+
class Strike < Cog::Command
|
11
|
+
def run_command
|
12
|
+
rails = ::Swat::RailsLoader.new
|
13
|
+
response.template = "execution_result"
|
14
|
+
response.content = rails.run("execute #{request.args.join(' ')}").to_s
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/rails_loader.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
require "open3"
|
2
|
+
|
3
|
+
module Swat
|
4
|
+
#
|
5
|
+
# Loads Rails command runner
|
6
|
+
#
|
7
|
+
# Loads RAILS_RUNNER_COMMAND from the environment to define
|
8
|
+
#
|
9
|
+
# Sample script:
|
10
|
+
#
|
11
|
+
# SCRIPTS_LOCAL_PATH=path_to/scripts bundle exec rails runner
|
12
|
+
# path_to/scripts/lib/swat_run.rb execute test success success 2&>/dev/null
|
13
|
+
#
|
14
|
+
# Can read the runner command and the working dir from environment variables
|
15
|
+
# such as:
|
16
|
+
# RAILS_RUNNER_COMMAND
|
17
|
+
# RAILS_WORKING_DIR
|
18
|
+
#
|
19
|
+
class RailsLoader
|
20
|
+
def initialize(command = ENV["RAILS_RUNNER_COMMAND"] || "",
|
21
|
+
working_dir = ENV["RAILS_WORKING_DIR"] || Dir.getwd)
|
22
|
+
fail "Invalid RAILS_RUNNER_COMMAND, please provide a rails command" if command.nil? || command.empty?
|
23
|
+
fail "Invalid RAILS_WORKING_DIR, '#{working_dir}' does not exists" unless Dir.exist?(working_dir)
|
24
|
+
@rails_command = command
|
25
|
+
@rails_working_dir = working_dir
|
26
|
+
end
|
27
|
+
|
28
|
+
def run(args)
|
29
|
+
command = "#{@rails_command} #{args}"
|
30
|
+
env = {
|
31
|
+
"PATH" => ENV["PATH"],
|
32
|
+
"SCRIPTS_REMOTE_URL" => ENV["SCRIPTS_REMOTE_URL"],
|
33
|
+
"SCRIPTS_LOCAL_PATH" => ENV["SCRIPTS_LOCAL_PATH"]
|
34
|
+
}
|
35
|
+
Open3.popen3(env, command, unsetenv_others: true, chdir: @rails_working_dir) do |_, stdout, stderr, wait_thr|
|
36
|
+
if wait_thr.value != 0
|
37
|
+
fail "Command #{args} failed with err: '#{stderr.read.strip}' " \
|
38
|
+
"out: '#{stdout.read.strip}'"
|
39
|
+
end
|
40
|
+
stdout.read.strip
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/swat.rb
ADDED
@@ -0,0 +1,131 @@
|
|
1
|
+
require "pathname"
|
2
|
+
#
|
3
|
+
# Swat module, where all the stuff lives
|
4
|
+
#
|
5
|
+
module Swat
|
6
|
+
#
|
7
|
+
# Defines the basic behavior of a command
|
8
|
+
#
|
9
|
+
# Inherith to reuse all this logic, only pre-check and execute are mandatory methods
|
10
|
+
# the rest can be as is
|
11
|
+
#
|
12
|
+
# Use the context object to send state from one stage to the next
|
13
|
+
class BaseCommand
|
14
|
+
def initialize(args)
|
15
|
+
@args = args
|
16
|
+
end
|
17
|
+
|
18
|
+
def prepare(_context)
|
19
|
+
# validate arguments here
|
20
|
+
end
|
21
|
+
|
22
|
+
def pre_check(_context)
|
23
|
+
fail "PreChecks are not defined"
|
24
|
+
end
|
25
|
+
|
26
|
+
def execute(_context)
|
27
|
+
fail "Execution is not defined"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
#
|
32
|
+
# Represents the whole execution pipeline of a command
|
33
|
+
#
|
34
|
+
# Returns a hashmap that contains the following keys
|
35
|
+
# execution_mode: self descriptive
|
36
|
+
# prepare, precheck, execute: the different stages of execution
|
37
|
+
# each stage will include a hash with:
|
38
|
+
# succesful: boolean, indicating if it was successful or not
|
39
|
+
# output: the stdout in case the stage was successfull, stderr otherwise
|
40
|
+
class CommandExecution
|
41
|
+
def initialize(command, execution_mode = "dryrun")
|
42
|
+
@command = command
|
43
|
+
@stages = stages(execution_mode)
|
44
|
+
@result = { execution_mode: execution_mode }
|
45
|
+
@context = {}
|
46
|
+
end
|
47
|
+
|
48
|
+
def run
|
49
|
+
@stages.each do |stage|
|
50
|
+
execute_stage(stage)
|
51
|
+
break unless @result[stage][:successful]
|
52
|
+
end
|
53
|
+
@result
|
54
|
+
end
|
55
|
+
|
56
|
+
def execute_stage(stage)
|
57
|
+
@result[stage] = { successful: true, output: @command.public_send(stage, @context) }
|
58
|
+
rescue => e
|
59
|
+
@result[stage] = { successful: false, output: e.to_s }
|
60
|
+
end
|
61
|
+
|
62
|
+
def stages(execution_mode)
|
63
|
+
case execution_mode
|
64
|
+
when "dryrun"
|
65
|
+
%i(prepare pre_check)
|
66
|
+
when "execute"
|
67
|
+
%i(prepare pre_check execute)
|
68
|
+
else
|
69
|
+
fail "Invalid execution mode '#{execution_mode}'"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
#
|
75
|
+
# Parameters parsing helper class
|
76
|
+
#
|
77
|
+
# Assumes the first parameter to be the execution mode (dryrun or execute)
|
78
|
+
# Assumes the second parameter to be the script name
|
79
|
+
# Captures the rest as arguments that are piped in to the script
|
80
|
+
class Parameters
|
81
|
+
attr_accessor :execution_mode, :command, :args
|
82
|
+
|
83
|
+
def initialize(args = ARGV.clone)
|
84
|
+
@args = args
|
85
|
+
@execution_mode = @args.shift
|
86
|
+
@command = @args.shift
|
87
|
+
fail "Execution mode is mandatory" if @execution_mode.nil?
|
88
|
+
fail "No command was specified" if @command.nil?
|
89
|
+
end
|
90
|
+
|
91
|
+
def to_s
|
92
|
+
"#{@execution_mode} #{@command} #{@args.inspect}"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
#
|
97
|
+
# An executable script
|
98
|
+
#
|
99
|
+
# Loads the script from file system using ruby's `require` command, then creates a
|
100
|
+
# ::Swat::Command and then calls `run` on it
|
101
|
+
#
|
102
|
+
# If a Swat::Command object cannot be found (NameError) the command is considered bogus.
|
103
|
+
class Script
|
104
|
+
def initialize(parameters, scripts_path = ENV["SCRIPTS_LOCAL_PATH"])
|
105
|
+
@parameters = parameters
|
106
|
+
@scripts_path = Pathname.new(scripts_path || "scripts")
|
107
|
+
end
|
108
|
+
|
109
|
+
def run
|
110
|
+
CommandExecution.new(create_command, @parameters.execution_mode).run
|
111
|
+
end
|
112
|
+
|
113
|
+
private
|
114
|
+
|
115
|
+
def command_file
|
116
|
+
@command_file ||= @scripts_path.join("#{@parameters.command}.rb")
|
117
|
+
end
|
118
|
+
|
119
|
+
def create_command
|
120
|
+
fail "Could not find command #{@parameters.command} in #{command_file.expand_path}" unless command_file.file?
|
121
|
+
require command_file.expand_path
|
122
|
+
::Swat::Command.new(@parameters.args)
|
123
|
+
rescue NameError
|
124
|
+
raise "#{@parameters.command} does not define a Swat::Command object"
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def self.run
|
129
|
+
Script.new(Parameters.new).run
|
130
|
+
end
|
131
|
+
end
|
data/lib/swat_git.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
require "fileutils"
|
2
|
+
|
3
|
+
module Swat
|
4
|
+
#
|
5
|
+
# Git handler
|
6
|
+
#
|
7
|
+
class Git
|
8
|
+
attr_accessor :source, :target
|
9
|
+
|
10
|
+
def initialize(source = ENV["SCRIPTS_REMOTE_URL"], target = ENV["SCRIPTS_LOCAL_PATH"])
|
11
|
+
@source = source
|
12
|
+
@target = target
|
13
|
+
end
|
14
|
+
|
15
|
+
def wipe
|
16
|
+
FileUtils.rm_rf(@target) if File.writable?(@target)
|
17
|
+
end
|
18
|
+
|
19
|
+
def update
|
20
|
+
if Dir.exist?(@target)
|
21
|
+
pull
|
22
|
+
else
|
23
|
+
clone
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def valid?
|
28
|
+
return false unless File.exist?(@target)
|
29
|
+
Dir.chdir(@target) {
|
30
|
+
`git config --get remote.origin.url`.strip == @source
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def clone
|
37
|
+
repo_path = Pathname.new(@target)
|
38
|
+
parent_folder = repo_path.parent
|
39
|
+
repo_name = repo_path.basename
|
40
|
+
fail "Invalid target path #{parent_folder}" unless File.writable?(parent_folder)
|
41
|
+
Dir.chdir(parent_folder) do
|
42
|
+
`git clone #{@source} #{repo_name} 2> /dev/null`
|
43
|
+
end
|
44
|
+
{ action: "clone", head: current_commit }
|
45
|
+
end
|
46
|
+
|
47
|
+
def pull
|
48
|
+
fail "Invalid target repo #{@target}" unless valid?
|
49
|
+
Dir.chdir(@target) do
|
50
|
+
`git pull origin 2> /dev/null`
|
51
|
+
end
|
52
|
+
{ action: "pull", head: current_commit }
|
53
|
+
end
|
54
|
+
|
55
|
+
def current_commit
|
56
|
+
fail "Invalid target repo #{@target}" unless valid?
|
57
|
+
Dir.chdir(@target) do
|
58
|
+
`git log --oneline -1 HEAD`.strip
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
data/lib/swat_run.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# Usage: through rails execute the following command:
|
2
|
+
# bundle exec rails runner <path to>/marvin_run.rb <command> [arguments]
|
3
|
+
#
|
4
|
+
# Such as:
|
5
|
+
# - <path to>/marvin.rb points to the place where this file is located
|
6
|
+
# - command is the name of a file existing inside the scripts folder of this repo
|
7
|
+
# - arguments are the required arguments for the given command
|
8
|
+
#
|
9
|
+
# Implementing new commands:
|
10
|
+
# - Create a file in the scripts folder such as scripts/<command>.rb
|
11
|
+
# - In this file create a Swat::Command class that inheriths Swat::BaseCommand
|
12
|
+
# - It is mandatory to override pre_check and execute, these methods should either execute or fail with an exception
|
13
|
+
# - Optionally the method `validate` can be implemented to validate the received arguments before execution
|
14
|
+
|
15
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
16
|
+
|
17
|
+
require "json"
|
18
|
+
require_relative "swat"
|
19
|
+
|
20
|
+
puts Swat.run.to_json
|
data/scripts/invalid.rb
ADDED
File without changes
|
data/scripts/test.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
module Swat
|
2
|
+
#
|
3
|
+
# Test command
|
4
|
+
#
|
5
|
+
class Command < BaseCommand
|
6
|
+
def prepare(context)
|
7
|
+
fail "I need at least 1 argument" if @args.empty?
|
8
|
+
context[:prepared] = "done"
|
9
|
+
"preparation is fine so far"
|
10
|
+
end
|
11
|
+
|
12
|
+
def pre_check(context)
|
13
|
+
fail "something is not right" unless @args[0].to_sym == :success
|
14
|
+
context[:checks] = "done"
|
15
|
+
"all is gut"
|
16
|
+
end
|
17
|
+
|
18
|
+
def execute(context)
|
19
|
+
fail "execution failed!" unless @args[1].to_sym == :success
|
20
|
+
"Context so far is #{context}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require_relative "spec_helper"
|
2
|
+
require "rails_loader"
|
3
|
+
|
4
|
+
describe Swat::RailsLoader do
|
5
|
+
it "can load and execute a command" do
|
6
|
+
expect(::Swat::RailsLoader
|
7
|
+
.new("./spec/helpers/rails_stub.rb lib/swat_run.rb")
|
8
|
+
.run("dryrun test success"))
|
9
|
+
.to eq("{\"execution_mode\":\"dryrun\",\"prepare\":{\"successful\":true," \
|
10
|
+
"\"output\":\"preparation is fine so far\"}," \
|
11
|
+
"\"pre_check\":{\"successful\":true,\"output\":\"all is gut\"}}")
|
12
|
+
end
|
13
|
+
|
14
|
+
it "can err out when the return code is not 0" do
|
15
|
+
expect {
|
16
|
+
::Swat::RailsLoader
|
17
|
+
.new("./spec/helpers/fail_stub.sh")
|
18
|
+
.run("dryrun")
|
19
|
+
}.to raise_error(/Command dryrun failed with err/)
|
20
|
+
end
|
21
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require "simplecov"
|
2
|
+
|
3
|
+
SimpleCov.start do
|
4
|
+
add_filter "vendor"
|
5
|
+
end
|
6
|
+
|
7
|
+
require "rspec"
|
8
|
+
require "swat"
|
9
|
+
require "tmpdir"
|
10
|
+
require "fileutils"
|
11
|
+
|
12
|
+
class GitSpecHelper
|
13
|
+
attr_accessor :source_repo, :target_dir
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@source_repo = Dir.mktmpdir
|
17
|
+
@target_dir = Dir.mktmpdir
|
18
|
+
Dir.chdir(@source_repo) do
|
19
|
+
`git init`
|
20
|
+
`echo "hi" > readme.md`
|
21
|
+
`git add readme.md`
|
22
|
+
`git commit -m "initial commit"`
|
23
|
+
end
|
24
|
+
clean_target
|
25
|
+
end
|
26
|
+
|
27
|
+
def destroy
|
28
|
+
FileUtils.rm_rf(@source_repo)
|
29
|
+
FileUtils.rm_rf(@target_dir) if File.exist?(@target_dir)
|
30
|
+
end
|
31
|
+
|
32
|
+
def clean_target
|
33
|
+
FileUtils.rm_rf(@target_dir)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def with_environment(args: [])
|
38
|
+
ENV["COG_ARGC"] = args.length.times { |n| ENV["COG_ARGV_#{n}"] = args[n] }.to_s
|
39
|
+
yield
|
40
|
+
ensure
|
41
|
+
ENV.delete("COG_ARGC")
|
42
|
+
args.length.times { |n| ENV.delete("COG_ARGV_#{n}") }
|
43
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require_relative "spec_helper"
|
2
|
+
require_relative "../scripts/test"
|
3
|
+
|
4
|
+
describe Swat::CommandExecution do
|
5
|
+
it "fails with an invalid execution mode" do
|
6
|
+
expect { Swat::CommandExecution.new(Swat::Command.new([]), "invalid") }
|
7
|
+
.to raise_error(/Invalid execution mode 'invalid'/)
|
8
|
+
end
|
9
|
+
|
10
|
+
it "fails prepare stage without arguments" do
|
11
|
+
executor = Swat::CommandExecution.new(Swat::Command.new([]))
|
12
|
+
expect(executor.run).to eq(
|
13
|
+
execution_mode: "dryrun",
|
14
|
+
prepare: { successful: false, output: "I need at least 1 argument" }
|
15
|
+
)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "stops execution on pre_checks when constraints are not met" do
|
19
|
+
executor = Swat::CommandExecution.new(Swat::Command.new([:failure]))
|
20
|
+
expect(executor.run).to eq(
|
21
|
+
execution_mode: "dryrun",
|
22
|
+
prepare: { successful: true, output: "preparation is fine so far" },
|
23
|
+
pre_check: { successful: false, output: "something is not right" }
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "it only goes as far as pre checks in dry run mode" do
|
28
|
+
executor = Swat::CommandExecution.new(Swat::Command.new([:success]))
|
29
|
+
expect(executor.run).to eq(
|
30
|
+
execution_mode: "dryrun",
|
31
|
+
prepare: { successful: true, output: "preparation is fine so far" },
|
32
|
+
pre_check: { successful: true, output: "all is gut" }
|
33
|
+
)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "stops execution on pre_checks when prechecks fail" do
|
37
|
+
executor = Swat::CommandExecution.new(Swat::Command.new([:failure]), "execute")
|
38
|
+
expect(executor.run).to eq(
|
39
|
+
execution_mode: "execute",
|
40
|
+
prepare: { successful: true, output: "preparation is fine so far" },
|
41
|
+
pre_check: { successful: false, output: "something is not right" }
|
42
|
+
)
|
43
|
+
end
|
44
|
+
|
45
|
+
it "execution can fail and report it" do
|
46
|
+
executor = Swat::CommandExecution.new(Swat::Command.new(%i(success failure)), "execute")
|
47
|
+
expect(executor.run).to eq(
|
48
|
+
execution_mode: "execute",
|
49
|
+
prepare: { successful: true, output: "preparation is fine so far" },
|
50
|
+
pre_check: { successful: true, output: "all is gut" },
|
51
|
+
execute: { successful: false, output: "execution failed!" }
|
52
|
+
)
|
53
|
+
end
|
54
|
+
|
55
|
+
it "execution can succeed" do
|
56
|
+
executor = Swat::CommandExecution.new(Swat::Command.new(%i(success success)), "execute")
|
57
|
+
expect(executor.run).to eq(
|
58
|
+
execution_mode: "execute",
|
59
|
+
prepare: { successful: true, output: "preparation is fine so far" },
|
60
|
+
pre_check: { successful: true, output: "all is gut" },
|
61
|
+
execute: { successful: true, output: "Context so far is {:prepared=>\"done\", :checks=>\"done\"}" }
|
62
|
+
)
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require_relative "spec_helper"
|
2
|
+
|
3
|
+
describe Swat::BaseCommand do
|
4
|
+
it "errs when calling prechecks" do
|
5
|
+
cmd = Swat::BaseCommand.new(:sentinel)
|
6
|
+
expect { cmd.pre_check({}) }.to raise_error(/PreChecks are not defined/)
|
7
|
+
end
|
8
|
+
|
9
|
+
it "errs when calling execute" do
|
10
|
+
cmd = Swat::BaseCommand.new(:sentinel)
|
11
|
+
expect { cmd.execute({}) }.to raise_error(/Execution is not defined/)
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "cog_cmd/swat/dryrun"
|
3
|
+
|
4
|
+
describe CogCmd::Swat::Dryrun do
|
5
|
+
before do allow(STDIN).to receive(:tty?) { true } end
|
6
|
+
before do
|
7
|
+
ENV["RAILS_RUNNER_COMMAND"] = "./spec/helpers/rails_stub.rb lib/swat_run.rb"
|
8
|
+
end
|
9
|
+
after do ENV.delete("RAILS_RUNNER_COMMAND") end
|
10
|
+
|
11
|
+
it "can be called" do
|
12
|
+
command = CogCmd::Swat::Dryrun.new
|
13
|
+
with_environment(args: ["test success"]) do
|
14
|
+
command.run_command
|
15
|
+
end
|
16
|
+
expect(command.response.content)
|
17
|
+
.to eq("{\"execution_mode\":\"dryrun\",\"prepare\":{\"successful\":true," \
|
18
|
+
"\"output\":\"preparation is fine so far\"}," \
|
19
|
+
"\"pre_check\":{\"successful\":true,\"output\":\"all is gut\"}}")
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "swat_git"
|
3
|
+
|
4
|
+
describe Swat::Git do
|
5
|
+
context "Given a valid URL" do
|
6
|
+
before(:all) do
|
7
|
+
@git = GitSpecHelper.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def add_commit
|
11
|
+
Dir.chdir(@git.source_repo) {
|
12
|
+
`echo "how you doing?" >> readme.md`
|
13
|
+
`git commit -a -m "Another line in"`
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
before(:each) do @git.clean_target end
|
18
|
+
|
19
|
+
it "can clone a repo" do
|
20
|
+
repo = Swat::Git.new(@git.source_repo, @git.target_dir)
|
21
|
+
repo.update
|
22
|
+
expect(repo.valid?).to be_truthy
|
23
|
+
end
|
24
|
+
|
25
|
+
it "can determine that a repo is invalid when it doesn't exists" do
|
26
|
+
expect(Swat::Git.new(@git.source_repo, @git.target_dir).valid?).to be_falsey
|
27
|
+
end
|
28
|
+
|
29
|
+
it "can clone and then update a repo" do
|
30
|
+
repo = Swat::Git.new(@git.source_repo, @git.target_dir)
|
31
|
+
repo.update
|
32
|
+
add_commit
|
33
|
+
repo.update
|
34
|
+
expect(repo.valid?).to be_truthy
|
35
|
+
end
|
36
|
+
|
37
|
+
it "can wipe a non existing repo" do
|
38
|
+
repo = Swat::Git.new(@git.source_repo, @git.target_dir)
|
39
|
+
repo.wipe
|
40
|
+
expect(File.exist?(@git.target_dir)).to be_falsey
|
41
|
+
end
|
42
|
+
|
43
|
+
it "can clone and then wipe a repo" do
|
44
|
+
repo = Swat::Git.new(@git.source_repo, @git.target_dir)
|
45
|
+
repo.update
|
46
|
+
repo.wipe
|
47
|
+
expect(File.exist?(@git.target_dir)).to be_falsey
|
48
|
+
end
|
49
|
+
|
50
|
+
it "can clone and then update a repo multiple times" do
|
51
|
+
repo = Swat::Git.new(@git.source_repo, @git.target_dir)
|
52
|
+
repo.update
|
53
|
+
3.times do
|
54
|
+
add_commit
|
55
|
+
repo.update
|
56
|
+
end
|
57
|
+
expect(Dir.chdir(@git.target_dir) {
|
58
|
+
`git log --oneline | wc -l`.strip
|
59
|
+
}).to eq("5")
|
60
|
+
end
|
61
|
+
|
62
|
+
after(:all) do
|
63
|
+
@git.destroy
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require_relative "spec_helper"
|
2
|
+
|
3
|
+
describe Swat::Parameters do
|
4
|
+
it "loads the command with arguments" do
|
5
|
+
allow(ARGV).to receive(:clone) { %w(dryrun command arg) }
|
6
|
+
params = Swat::Parameters.new
|
7
|
+
expect(params.execution_mode).to eq("dryrun")
|
8
|
+
expect(params.command).to eq("command")
|
9
|
+
expect(params.args).to eq(["arg"])
|
10
|
+
expect(params.to_s).to eq("dryrun command [\"arg\"]")
|
11
|
+
end
|
12
|
+
|
13
|
+
it "fails to load if there is no command" do
|
14
|
+
allow(ARGV).to receive(:clone) { [] }
|
15
|
+
expect { Swat::Parameters.new }.to raise_error(/Execution mode is mandatory/)
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "cog_cmd/swat/reload"
|
3
|
+
|
4
|
+
describe CogCmd::Swat::Reload do
|
5
|
+
before do allow(STDIN).to receive(:tty?) { true } end
|
6
|
+
|
7
|
+
let(:git) { GitSpecHelper.new }
|
8
|
+
|
9
|
+
before do
|
10
|
+
ENV["SCRIPTS_REMOTE_URL"] = git.source_repo
|
11
|
+
ENV["SCRIPTS_LOCAL_PATH"] = git.target_dir
|
12
|
+
end
|
13
|
+
|
14
|
+
it "reloads the repo" do
|
15
|
+
command = CogCmd::Swat::Reload.new
|
16
|
+
command.run_command
|
17
|
+
expect(JSON.parse(command.response.content)).to include(
|
18
|
+
"source" => git.source_repo,
|
19
|
+
"target" => git.target_dir,
|
20
|
+
"action" => "clone",
|
21
|
+
"wiped" => false
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "reloads the repo" do
|
26
|
+
begin
|
27
|
+
command = CogCmd::Swat::Reload.new
|
28
|
+
ENV["COG_OPTS"] = "wipe"
|
29
|
+
ENV["COG_OPT_WIPE"] = "true"
|
30
|
+
command.run_command
|
31
|
+
expect(JSON.parse(command.response.content)).to include(
|
32
|
+
"source" => git.source_repo,
|
33
|
+
"target" => git.target_dir,
|
34
|
+
"action" => "clone",
|
35
|
+
"wiped" => true
|
36
|
+
)
|
37
|
+
ensure
|
38
|
+
ENV.delete("COG_OPTS")
|
39
|
+
ENV.delete("COG_OPT_WIPE")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
it "reloads the repo 2 times" do
|
44
|
+
command = CogCmd::Swat::Reload.new
|
45
|
+
command.run_command
|
46
|
+
command.run_command
|
47
|
+
command.run_command
|
48
|
+
expect(JSON.parse(command.response.content)).to include(
|
49
|
+
"source" => git.source_repo,
|
50
|
+
"target" => git.target_dir,
|
51
|
+
"action" => "pull",
|
52
|
+
"wiped" => false
|
53
|
+
)
|
54
|
+
end
|
55
|
+
|
56
|
+
after do
|
57
|
+
ENV.delete("SCRIPTS_REMOTE_URL")
|
58
|
+
ENV.delete("SCRIPTS_LOCAL_PATH")
|
59
|
+
git.destroy
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require_relative "spec_helper"
|
2
|
+
|
3
|
+
describe Swat::Script do
|
4
|
+
context "with valid parameters" do
|
5
|
+
let(:params) {
|
6
|
+
Swat::Parameters.new(%w(dryrun test arg1 arg2))
|
7
|
+
}
|
8
|
+
|
9
|
+
it "can be created" do
|
10
|
+
script = Swat::Script.new(params)
|
11
|
+
expect(script).not_to be_nil
|
12
|
+
end
|
13
|
+
|
14
|
+
it "finds the test script" do
|
15
|
+
script = Swat::Script.new(params)
|
16
|
+
script.run
|
17
|
+
end
|
18
|
+
|
19
|
+
it "fils to find a valid script with a descriptive message" do
|
20
|
+
script = Swat::Script.new(params, "scr")
|
21
|
+
expect { script.run }.to raise_error(/Could not find command test in /)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "cog_cmd/swat/strike"
|
3
|
+
|
4
|
+
describe CogCmd::Swat::Strike do
|
5
|
+
before do allow(STDIN).to receive(:tty?) { true } end
|
6
|
+
before do
|
7
|
+
ENV["RAILS_RUNNER_COMMAND"] = "./spec/helpers/rails_stub.rb lib/swat_run.rb"
|
8
|
+
end
|
9
|
+
after do ENV.delete("RAILS_RUNNER_COMMAND") end
|
10
|
+
|
11
|
+
it "can be called" do
|
12
|
+
command = CogCmd::Swat::Strike.new
|
13
|
+
with_environment(args: ["test success success"]) do
|
14
|
+
command.run_command
|
15
|
+
end
|
16
|
+
expect(command.response.content)
|
17
|
+
.to eq("{\"execution_mode\":\"execute\",\"prepare\":{\"successful\":true," \
|
18
|
+
"\"output\":\"preparation is fine so far\"},\"pre_check\":{\"successful\":true," \
|
19
|
+
"\"output\":\"all is gut\"},\"execute\":{\"successful\":true," \
|
20
|
+
"\"output\":\"Context so far is {:prepared=>\\\"done\\\", :checks=>\\\"done\\\"}\"}}")
|
21
|
+
end
|
22
|
+
end
|
metadata
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gitlab-swat
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Pablo Carranza
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-04-01 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: cog-rb
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.4'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.4'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.5'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.5'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rubocop
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.42'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.42'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: simplecov
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0.13'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0.13'
|
69
|
+
description: |
|
70
|
+
A Successful Deployment Ends Peacefully With No Bullets Fired.
|
71
|
+
If That’s Simply Not Possible, SWAT Uses Special Weapons and Tactics to Keep the Public Safe
|
72
|
+
|
73
|
+
GitLab-Swat allows admins to quickly deploy scripts that can be remotely executed through a rails console
|
74
|
+
|
75
|
+
Allowing fast action by using an external git repository as the scripts source, but keeping safety high by
|
76
|
+
enforcing a prepare-pre check-execute model that allows execution break at any stage if things are not going
|
77
|
+
as expected
|
78
|
+
email: pablo@gitlab.com
|
79
|
+
executables: []
|
80
|
+
extensions: []
|
81
|
+
extra_rdoc_files: []
|
82
|
+
files:
|
83
|
+
- ".gitlab-ci.yml"
|
84
|
+
- ".rubocop.yml"
|
85
|
+
- Gemfile
|
86
|
+
- Gemfile.lock
|
87
|
+
- README.md
|
88
|
+
- cog-command
|
89
|
+
- config.yaml
|
90
|
+
- gitlab-swat.gemspec
|
91
|
+
- lib/cog_cmd/swat/dryrun.rb
|
92
|
+
- lib/cog_cmd/swat/reload.rb
|
93
|
+
- lib/cog_cmd/swat/strike.rb
|
94
|
+
- lib/rails_loader.rb
|
95
|
+
- lib/swat.rb
|
96
|
+
- lib/swat_git.rb
|
97
|
+
- lib/swat_run.rb
|
98
|
+
- scripts/invalid.rb
|
99
|
+
- scripts/test.rb
|
100
|
+
- spec/helpers/fail_stub.sh
|
101
|
+
- spec/helpers/rails_stub.rb
|
102
|
+
- spec/rails_loader_spec.rb
|
103
|
+
- spec/spec_helper.rb
|
104
|
+
- spec/swat_command_execution_spec.rb
|
105
|
+
- spec/swat_command_spec.rb
|
106
|
+
- spec/swat_config_spec.rb
|
107
|
+
- spec/swat_dryrun_spec.rb
|
108
|
+
- spec/swat_git_spec.rb
|
109
|
+
- spec/swat_parameters_spec.rb
|
110
|
+
- spec/swat_reload_spec.rb
|
111
|
+
- spec/swat_script_spec.rb
|
112
|
+
- spec/swat_strike_spec.rb
|
113
|
+
homepage:
|
114
|
+
licenses:
|
115
|
+
- MIT
|
116
|
+
metadata: {}
|
117
|
+
post_install_message:
|
118
|
+
rdoc_options: []
|
119
|
+
require_paths:
|
120
|
+
- lib
|
121
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
122
|
+
requirements:
|
123
|
+
- - ">="
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - ">="
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: '0'
|
131
|
+
requirements: []
|
132
|
+
rubyforge_project:
|
133
|
+
rubygems_version: 2.5.2
|
134
|
+
signing_key:
|
135
|
+
specification_version: 4
|
136
|
+
summary: ChatOps Cog Bundle that enables admins to remotely run predefined scripts
|
137
|
+
in a rails console
|
138
|
+
test_files:
|
139
|
+
- spec/helpers/fail_stub.sh
|
140
|
+
- spec/helpers/rails_stub.rb
|
141
|
+
- spec/rails_loader_spec.rb
|
142
|
+
- spec/spec_helper.rb
|
143
|
+
- spec/swat_command_execution_spec.rb
|
144
|
+
- spec/swat_command_spec.rb
|
145
|
+
- spec/swat_config_spec.rb
|
146
|
+
- spec/swat_dryrun_spec.rb
|
147
|
+
- spec/swat_git_spec.rb
|
148
|
+
- spec/swat_parameters_spec.rb
|
149
|
+
- spec/swat_reload_spec.rb
|
150
|
+
- spec/swat_script_spec.rb
|
151
|
+
- spec/swat_strike_spec.rb
|