full360-sequencer 0.0.6 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/release.yml +98 -0
- data/.github/workflows/test.yml +26 -0
- data/.gitignore +17 -0
- data/CHANGELOG.md +63 -0
- data/Dockerfile.jruby +23 -0
- data/Dockerfile.ruby +21 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +52 -0
- data/LICENSE +21 -0
- data/README.md +104 -0
- data/Rakefile +7 -0
- data/bin/console +10 -0
- data/bin/sequencer +15 -14
- data/entrypoint.sh +10 -0
- data/full360-sequencer.gemspec +34 -0
- data/lib/full360-sequencer.rb +1 -159
- data/lib/full360_sequencer/run_ecs_task.rb +103 -0
- data/lib/full360_sequencer/run_task_base.rb +12 -0
- data/lib/full360_sequencer/runner.rb +79 -0
- data/lib/full360_sequencer/version.rb +5 -0
- data/lib/full360_sequencer.rb +11 -0
- metadata +55 -23
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2847dd62aff2e2cd5540fd3abc9fdcf0b1087bb0e38c9adaeb501b04c4bddc8e
|
4
|
+
data.tar.gz: c0a54bc734e1e3c44aa2c3543e6a171839346c75b9e881028bd28580585f7c06
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f912e6cab2f0bed981e8a11d7fd9add676a1956c40fa6333b628f2ee24ad3e2ddb5e125a78ea0d22520841e39e76fc6c8d0b411e1510b998a04a2f99855a903b
|
7
|
+
data.tar.gz: e86a6e8eccc6296ddd7d58db583e4f9434c1fd8f82ff5f47011ccaa3412ebc9317b06e887c82ed53324bac4df82d9e33eb033a989a74aca1ccd54c49328d87fb
|
@@ -0,0 +1,98 @@
|
|
1
|
+
name: Release Gem
|
2
|
+
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
tags:
|
6
|
+
- "v*"
|
7
|
+
|
8
|
+
jobs:
|
9
|
+
release:
|
10
|
+
runs-on: ubuntu-latest
|
11
|
+
|
12
|
+
steps:
|
13
|
+
# Checkout code if release was created
|
14
|
+
- uses: actions/checkout@v2
|
15
|
+
|
16
|
+
# Setup ruby if a release was created
|
17
|
+
- uses: ruby/setup-ruby@v1
|
18
|
+
with:
|
19
|
+
ruby-version: 2.7
|
20
|
+
bundler-cache: true
|
21
|
+
|
22
|
+
# Publish
|
23
|
+
- name: publish gem
|
24
|
+
run: |
|
25
|
+
mkdir -p $HOME/.gem
|
26
|
+
touch $HOME/.gem/credentials
|
27
|
+
chmod 0600 $HOME/.gem/credentials
|
28
|
+
printf -- "---\n:rubygems_api_key: ${RUBYGEMS_API_KEY}\n" > $HOME/.gem/credentials
|
29
|
+
gem build *.gemspec
|
30
|
+
gem push *.gem
|
31
|
+
env:
|
32
|
+
RUBYGEMS_API_KEY: "${{secrets.RUBYGEMS_API_KEY}}"
|
33
|
+
|
34
|
+
docker-jruby:
|
35
|
+
runs-on: ubuntu-latest
|
36
|
+
needs: release
|
37
|
+
|
38
|
+
steps:
|
39
|
+
- name: Checkout
|
40
|
+
uses: actions/checkout@v2
|
41
|
+
|
42
|
+
- name: Set up Docker Buildx
|
43
|
+
uses: docker/setup-buildx-action@v1
|
44
|
+
|
45
|
+
- name: Login to DockerHub
|
46
|
+
uses: docker/login-action@v1
|
47
|
+
with:
|
48
|
+
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
49
|
+
password: ${{ secrets.DOCKER_HUB_TOKEN }}
|
50
|
+
|
51
|
+
- name: Extract tag name
|
52
|
+
id: tag
|
53
|
+
uses: actions/github-script@0.2.0
|
54
|
+
with:
|
55
|
+
script: return context.payload.ref.replace(/\/refs\/tags\//, '')
|
56
|
+
|
57
|
+
- name: Build JRuby image
|
58
|
+
uses: docker/build-push-action@v2
|
59
|
+
with:
|
60
|
+
file: ./Dockerfile.jruby
|
61
|
+
pull: true
|
62
|
+
push: true
|
63
|
+
tags: |
|
64
|
+
full360/sequencer:jruby-latest
|
65
|
+
full360/sequencer:${{ steps.tag.outputs.result }}-jruby-latest
|
66
|
+
|
67
|
+
docker-ruby:
|
68
|
+
runs-on: ubuntu-latest
|
69
|
+
needs: release
|
70
|
+
|
71
|
+
steps:
|
72
|
+
- name: Checkout
|
73
|
+
uses: actions/checkout@v2
|
74
|
+
|
75
|
+
- name: Set up Docker Buildx
|
76
|
+
uses: docker/setup-buildx-action@v1
|
77
|
+
|
78
|
+
- name: Login to DockerHub
|
79
|
+
uses: docker/login-action@v1
|
80
|
+
with:
|
81
|
+
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
82
|
+
password: ${{ secrets.DOCKER_HUB_TOKEN }}
|
83
|
+
|
84
|
+
- name: Extract tag name
|
85
|
+
id: tag
|
86
|
+
uses: actions/github-script@0.2.0
|
87
|
+
with:
|
88
|
+
script: return context.payload.ref.replace(/\/refs\/tags\//, '')
|
89
|
+
|
90
|
+
- name: Build Ruby image
|
91
|
+
uses: docker/build-push-action@v2
|
92
|
+
with:
|
93
|
+
file: ./Dockerfile.ruby
|
94
|
+
pull: true
|
95
|
+
push: true
|
96
|
+
tags: |
|
97
|
+
full360/sequencer:ruby-latest
|
98
|
+
full360/sequencer:${{ steps.tag.outputs.result }}-ruby-latest
|
@@ -0,0 +1,26 @@
|
|
1
|
+
name: CI Test
|
2
|
+
|
3
|
+
on: [push, pull_request]
|
4
|
+
|
5
|
+
jobs:
|
6
|
+
test:
|
7
|
+
strategy:
|
8
|
+
fail-fast: false
|
9
|
+
matrix:
|
10
|
+
os: [ubuntu-latest, macos-latest]
|
11
|
+
# Due to https://github.com/actions/runner/issues/849, we have to use quotes for '3.0'
|
12
|
+
ruby: ["2.7", "jruby-9.2.19.0", "jruby-9.3.0.0"]
|
13
|
+
runs-on: ${{ matrix.os }}
|
14
|
+
|
15
|
+
if: "!contains(github.event.head_commit.message, '[ci skip]')"
|
16
|
+
|
17
|
+
steps:
|
18
|
+
- name: Checkout
|
19
|
+
uses: actions/checkout@v2
|
20
|
+
|
21
|
+
- name: Test ${{ matrix.ruby }}
|
22
|
+
uses: ruby/setup-ruby@v1
|
23
|
+
with:
|
24
|
+
ruby-version: ${{ matrix.ruby }}
|
25
|
+
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
26
|
+
- run: bundle exec rake
|
data/.gitignore
ADDED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
# Changelog
|
2
|
+
All notable changes to this project will be documented in this file.
|
3
|
+
|
4
|
+
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
5
|
+
and this project adheres to [Semantic
|
6
|
+
Versioning](http://semver.org/spec/v2.0.0.html).
|
7
|
+
|
8
|
+
## [Unreleased]
|
9
|
+
### Added
|
10
|
+
### Changed
|
11
|
+
### Removed
|
12
|
+
|
13
|
+
## 0.2.5
|
14
|
+
### Added
|
15
|
+
### Changed
|
16
|
+
- Multiple workflow changese to make it work.
|
17
|
+
### Removed
|
18
|
+
|
19
|
+
## 0.2.0
|
20
|
+
### Added
|
21
|
+
- Add Docker image for Ruby and JRuby that will get triggered after the release.
|
22
|
+
### Changed
|
23
|
+
- Refactor the code to make it easier to read and modify.
|
24
|
+
- Update the AWS SDK for ECS to V3.
|
25
|
+
- Fix a missing logger dependency in the bin/sequencer code.
|
26
|
+
### Removed
|
27
|
+
- Tests for Ruby 3.0 as they were failing for an unknown reason and I couldn't
|
28
|
+
reproduce locally.
|
29
|
+
|
30
|
+
## 0.1.3
|
31
|
+
### Added
|
32
|
+
- Fix release workflow.
|
33
|
+
### Changed
|
34
|
+
### Removed
|
35
|
+
|
36
|
+
## 0.1.2
|
37
|
+
### Added
|
38
|
+
- Fix release workflow.
|
39
|
+
### Changed
|
40
|
+
### Removed
|
41
|
+
|
42
|
+
## 0.1.1
|
43
|
+
### Added
|
44
|
+
- Workflows dependable.
|
45
|
+
### Changed
|
46
|
+
### Removed
|
47
|
+
|
48
|
+
## 0.1.0
|
49
|
+
### Added
|
50
|
+
- Working GitHub Actions workflow for tests and releases.
|
51
|
+
- Add Gemfile to the Gem.
|
52
|
+
### Changed
|
53
|
+
- Update the .gemspec file and dependencies.
|
54
|
+
- Update the gem file format.
|
55
|
+
- Change from single quote to double quotes.
|
56
|
+
- Fix deprecation warnings in tests
|
57
|
+
### Removed
|
58
|
+
|
59
|
+
## 0.0.7
|
60
|
+
### Added
|
61
|
+
- Working version.
|
62
|
+
### Changed
|
63
|
+
### Removed
|
data/Dockerfile.jruby
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
FROM jruby:9.2-jre8
|
2
|
+
|
3
|
+
# Should be more than sufficient for transforms without large recordset
|
4
|
+
# operations.
|
5
|
+
ENV JRUBY_OPTS=-J-Xmx1024m
|
6
|
+
|
7
|
+
RUN apt-get update \
|
8
|
+
&& apt-get install -y --no-install-recommends \
|
9
|
+
unzip \
|
10
|
+
ca-certificates \
|
11
|
+
awscli \
|
12
|
+
&& update-ca-certificates \
|
13
|
+
&& rm -rf /var/lib/apt/lists/*
|
14
|
+
|
15
|
+
#set time zone
|
16
|
+
RUN mv /etc/localtime /etc/localtime.bak ; ln -s /usr/share/zoneinfo/UTC /etc/localtime
|
17
|
+
|
18
|
+
RUN gem install full360-sequencer
|
19
|
+
|
20
|
+
# this is the entry point
|
21
|
+
ADD entrypoint.sh /usr/sbin/runner.sh
|
22
|
+
RUN chmod 755 /usr/sbin/runner.sh
|
23
|
+
ENTRYPOINT ["/usr/sbin/runner.sh"]
|
data/Dockerfile.ruby
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
FROM ruby:2.7-alpine
|
2
|
+
|
3
|
+
# Install required packages
|
4
|
+
RUN apk add --no-cache \
|
5
|
+
unzip \
|
6
|
+
ca-certificates \
|
7
|
+
bash \
|
8
|
+
wget \
|
9
|
+
curl \
|
10
|
+
aws-cli \
|
11
|
+
&& update-ca-certificates \
|
12
|
+
|
13
|
+
#set time zone
|
14
|
+
RUN mv /etc/localtime /etc/localtime.bak ; ln -s /usr/share/zoneinfo/UTC /etc/localtime
|
15
|
+
|
16
|
+
RUN gem install full360-sequencer
|
17
|
+
|
18
|
+
# this is the entry point
|
19
|
+
ADD entrypoint.sh /usr/sbin/runner.sh
|
20
|
+
RUN chmod 755 /usr/sbin/runner.sh
|
21
|
+
ENTRYPOINT ["/usr/sbin/runner.sh"]
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
full360-sequencer (0.2.5)
|
5
|
+
aws-sdk-ecs (~> 1.85)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
aws-eventstream (1.2.0)
|
11
|
+
aws-partitions (1.510.0)
|
12
|
+
aws-sdk-core (3.121.1)
|
13
|
+
aws-eventstream (~> 1, >= 1.0.2)
|
14
|
+
aws-partitions (~> 1, >= 1.239.0)
|
15
|
+
aws-sigv4 (~> 1.1)
|
16
|
+
jmespath (~> 1.0)
|
17
|
+
aws-sdk-ecs (1.85.0)
|
18
|
+
aws-sdk-core (~> 3, >= 3.120.0)
|
19
|
+
aws-sigv4 (~> 1.1)
|
20
|
+
aws-sigv4 (1.4.0)
|
21
|
+
aws-eventstream (~> 1, >= 1.0.2)
|
22
|
+
coderay (1.1.3)
|
23
|
+
ffi (1.15.4-java)
|
24
|
+
jmespath (1.4.0)
|
25
|
+
method_source (1.0.0)
|
26
|
+
minitest (5.14.4)
|
27
|
+
pry (0.14.1)
|
28
|
+
coderay (~> 1.1)
|
29
|
+
method_source (~> 1.0)
|
30
|
+
pry (0.14.1-java)
|
31
|
+
coderay (~> 1.1)
|
32
|
+
method_source (~> 1.0)
|
33
|
+
spoon (~> 0.0)
|
34
|
+
rake (12.3.3)
|
35
|
+
spoon (0.0.6)
|
36
|
+
ffi
|
37
|
+
|
38
|
+
PLATFORMS
|
39
|
+
universal-java-11
|
40
|
+
universal-java-8
|
41
|
+
x86_64-darwin-18
|
42
|
+
x86_64-darwin-19
|
43
|
+
x86_64-linux
|
44
|
+
|
45
|
+
DEPENDENCIES
|
46
|
+
full360-sequencer!
|
47
|
+
minitest (~> 5.9)
|
48
|
+
pry (~> 0.14)
|
49
|
+
rake (~> 12)
|
50
|
+
|
51
|
+
BUNDLED WITH
|
52
|
+
2.2.28
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2017 Full 360 Inc
|
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 all
|
13
|
+
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 THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
# Full 360 Sequencer
|
2
|
+
|
3
|
+
Sequencer is a tool that supports the synchronous execution of docker containers
|
4
|
+
running in Amazon ECS for the purpose of batch processing.
|
5
|
+
|
6
|
+
While there are many solutions for container orchestration, most of them are
|
7
|
+
focused on managing and scaling long running microservices, where a group of
|
8
|
+
containers need to kept alive and able to talk to each other. This model is
|
9
|
+
quite different from the execution of a nightly ETL batch where a container
|
10
|
+
performs a series of actions such as:
|
11
|
+
|
12
|
+
* collect the data from source system
|
13
|
+
* transform the data into the data lake
|
14
|
+
* ingest the data into the database
|
15
|
+
* transform the data inside the database with SQL
|
16
|
+
* export data to downstream consumers such as third parties or BI tools
|
17
|
+
|
18
|
+
AWS Batch service could do the above but it is currently only available in a
|
19
|
+
single region.
|
20
|
+
|
21
|
+
At one end of the spectrum your other options fall into the realm of shell
|
22
|
+
scripts.. while the other end brings you powerful (and cool!) DAG based tools
|
23
|
+
such as Nomad and Airflow.
|
24
|
+
|
25
|
+
We consider the DAG based tools to be way overkill for a simple batch of
|
26
|
+
synchronous jobs, so we opted to expand on the idea of a shell script.
|
27
|
+
|
28
|
+
Putting a shell script into a container is easy enough, but it requires that you
|
29
|
+
build and deploy a new container every time you make a change.
|
30
|
+
|
31
|
+
Sequencer allows you to define a list of ECS tasks in a yaml file, the location
|
32
|
+
of which is passed into the container at run time. This yaml file can sit in an
|
33
|
+
S3 repository (git support forthcoming). Sequencer will synchronously run each
|
34
|
+
task, passing in your override parameters as specified.
|
35
|
+
|
36
|
+
## Installing Sequencer
|
37
|
+
|
38
|
+
Sequencer is installed as a ruby gem:
|
39
|
+
|
40
|
+
gem install full360-sequencer
|
41
|
+
|
42
|
+
Once installed... you can run the sequencer command from anywhere on your
|
43
|
+
system. Simply pass the path to your YAML file as a parameter. Note that you
|
44
|
+
will need to export some environment variables in order to use the AWS API.
|
45
|
+
|
46
|
+
$ export AWS_ACCESS_KEY_ID=aaaaaaaaayourkey
|
47
|
+
$ export AWS_SECRET_ACCESS_KEY=bbbbbbbbbbbbbbbbbbbbbbbbbbbbyoursecret
|
48
|
+
$ export AWS_REGION=us-west-2
|
49
|
+
$ sequencer mybatch.yml
|
50
|
+
|
51
|
+
Sequencer will utilize AWS instance or container roles if appropriate, though
|
52
|
+
you will have to provide `AWS_REGION` if you are not in us-east-1.
|
53
|
+
|
54
|
+
While using the command line sequencer is just fine, we suggest that you use the
|
55
|
+
full360/sequencer container available on Dockerhub.
|
56
|
+
|
57
|
+
## YAML File Specification
|
58
|
+
|
59
|
+
Sequencer requires a yaml file with the following structure:
|
60
|
+
|
61
|
+
```yaml
|
62
|
+
- first_task:
|
63
|
+
type: ecs_task
|
64
|
+
parameters:
|
65
|
+
cluster: yourcluster
|
66
|
+
task_definition: test-task-def:1
|
67
|
+
count: 1
|
68
|
+
- first_task:
|
69
|
+
type: ecs_task
|
70
|
+
parameters:
|
71
|
+
cluster: yourcluster2
|
72
|
+
task_definition: test-task-def:2
|
73
|
+
count: 1
|
74
|
+
```
|
75
|
+
|
76
|
+
The top level structure is an array of tasks to be executed in the order shown.
|
77
|
+
Each task can be provided a name. Below each task you must specify the task
|
78
|
+
`type`. At this time, only ecs_task is supported as a task type.
|
79
|
+
|
80
|
+
The `parameters` element correlates to the request provided to the ECS run_task
|
81
|
+
API call. All of the elements are supported as long as you provide a YAML
|
82
|
+
structure. Refer to the API [reference][ref] for more details.
|
83
|
+
|
84
|
+
## Releasing
|
85
|
+
|
86
|
+
Releasing a new version of the Gem requires a few steps:
|
87
|
+
- Update the `CHANGELOG.md` file
|
88
|
+
- Add a new section for the [UNRELEASED] code
|
89
|
+
- Check that all changes are reflected for the current release version
|
90
|
+
- Update the `version.rb` file
|
91
|
+
- Do a bundle install to ensure we are locking the latest version
|
92
|
+
- Commit all the changes
|
93
|
+
- Example: `git commit -m "Bump gem to version 1.0.0"`
|
94
|
+
- Create a Git tag that matches the version number in `version.rb`
|
95
|
+
- Example: `git tag -m "Version 1.0.0" v1.0.0`
|
96
|
+
|
97
|
+
## Running the Sequencer from Docker
|
98
|
+
|
99
|
+
Pass the following environment variables to the container at runtime:
|
100
|
+
|
101
|
+
* `SEQUENCER_YAML_S3_PATH` - s3:// path to the yaml configuration file.
|
102
|
+
* `AWS_REGION` - AWS region in which you are running the container.
|
103
|
+
|
104
|
+
[ref]: http://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_RunTask.html
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "pry"
|
5
|
+
require "full360-sequencer"
|
6
|
+
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
9
|
+
|
10
|
+
Pry.start
|
data/bin/sequencer
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
require
|
3
|
+
require "logger"
|
4
|
+
require "full360-sequencer"
|
4
5
|
|
5
6
|
config_file = ARGV[0]
|
6
7
|
|
@@ -14,29 +15,29 @@ def logger
|
|
14
15
|
end
|
15
16
|
|
16
17
|
def sequencer_version
|
17
|
-
|
18
|
+
Full360::Sequencer::VERSION
|
18
19
|
end
|
19
20
|
|
20
21
|
begin
|
21
22
|
logger.info("sequencer version #{sequencer_version}")
|
22
|
-
logger.level = ENV[
|
23
|
+
logger.level = ENV["SEQUENCER_LOG_DEBUG"] ? Logger::DEBUG : Logger::INFO
|
23
24
|
|
24
25
|
if config_file == nil
|
25
|
-
logger.error(
|
26
|
-
logger.error('YAML file not provided... exiting with error code 1')
|
26
|
+
logger.error("SEQUENCER_ERROR: YAML file not provided... exiting with error code 1")
|
27
27
|
exit 1
|
28
28
|
else
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
29
|
+
sleep_between_checks = ENV.fetch("SEQUENCER_SLEEP_BETWEEN_CHECKS", 5).to_i
|
30
|
+
|
31
|
+
Full360::Sequencer::Runner.new(sleep_between_checks, logger).tap do |runner|
|
32
|
+
runner.config_from_file(config_file)
|
33
|
+
runner.run
|
34
|
+
end
|
33
35
|
end
|
34
|
-
logger.info(
|
36
|
+
logger.info("all steps succeeded... exiting with code 0")
|
35
37
|
exit 0
|
36
38
|
rescue => e
|
37
|
-
logger.error(
|
38
|
-
logger.error(e.message)
|
39
|
+
logger.error("SEQUENCER_ERROR: #{e.message}")
|
39
40
|
e.backtrace.each { |r| logger.error(r) }
|
40
|
-
logger.error(
|
41
|
+
logger.error("failure... exiting with code 1")
|
41
42
|
exit 1
|
42
|
-
end
|
43
|
+
end
|
data/entrypoint.sh
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
lib = File.expand_path("../lib", __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require "full360_sequencer/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "full360-sequencer"
|
7
|
+
spec.version = Full360::Sequencer::VERSION
|
8
|
+
spec.date = "2017-10-17"
|
9
|
+
spec.summary = "Full 360 sequencer utility"
|
10
|
+
spec.description = "Automation for simple batch jobs run in AWS"
|
11
|
+
spec.authors = ["Full 360 Group"]
|
12
|
+
spec.email = "support@full360.com"
|
13
|
+
spec.files = ["lib/full360-sequencer.rb"]
|
14
|
+
spec.homepage = "https://full360.com"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
18
|
+
f.match(%r{^(test|spec|features)/})
|
19
|
+
end
|
20
|
+
|
21
|
+
spec.bindir = "bin"
|
22
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
23
|
+
spec.require_paths = ["lib"]
|
24
|
+
|
25
|
+
# Make it Ruby 2+ only
|
26
|
+
spec.required_ruby_version = ">= 2.0"
|
27
|
+
|
28
|
+
spec.add_runtime_dependency "aws-sdk-ecs", "~> 1.85"
|
29
|
+
|
30
|
+
# development dependencies
|
31
|
+
spec.add_development_dependency "minitest", "~> 5.9"
|
32
|
+
spec.add_development_dependency "rake", "~> 12"
|
33
|
+
spec.add_development_dependency "pry", "~> 0.14"
|
34
|
+
end
|
data/lib/full360-sequencer.rb
CHANGED
@@ -1,159 +1 @@
|
|
1
|
-
|
2
|
-
require 'aws-sdk'
|
3
|
-
require 'yaml'
|
4
|
-
require 'logger'
|
5
|
-
require 'pp'
|
6
|
-
|
7
|
-
module Full360
|
8
|
-
module Sequencer
|
9
|
-
class Runner
|
10
|
-
attr_accessor :sleep_between_checks
|
11
|
-
attr_accessor :config
|
12
|
-
|
13
|
-
def initialize(logger = nil)
|
14
|
-
@logger = logger ? logger : Logger.new(STDOUT)
|
15
|
-
|
16
|
-
# default 5 seconds between completed? checks
|
17
|
-
@sleep_between_checks = 5
|
18
|
-
end
|
19
|
-
|
20
|
-
def config_from_file(yaml_path)
|
21
|
-
@config = parse_config_file(yaml_path)
|
22
|
-
end
|
23
|
-
|
24
|
-
def run_task_class(task_type_string)
|
25
|
-
case task_type_string
|
26
|
-
when 'ecs_task' then Full360::Sequencer::RunECSTask
|
27
|
-
else nil
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
def run
|
32
|
-
@config.each do |params|
|
33
|
-
this_task_name = task_name(params)
|
34
|
-
this_task = run_task_class(params[this_task_name]['type']).new(
|
35
|
-
this_task_name,
|
36
|
-
params[this_task_name]
|
37
|
-
)
|
38
|
-
this_task.run_task
|
39
|
-
until this_task.completed?
|
40
|
-
sleep @sleep_between_checks
|
41
|
-
end
|
42
|
-
raise "task failed error" unless this_task.success
|
43
|
-
end
|
44
|
-
rescue => e
|
45
|
-
@logger.error('SEQUENCER_ERROR')
|
46
|
-
@logger.error(e.message)
|
47
|
-
e.backtrace.each { |r| @logger.error(r) }
|
48
|
-
raise e
|
49
|
-
end
|
50
|
-
|
51
|
-
def task_name(params)
|
52
|
-
params.keys.first
|
53
|
-
end
|
54
|
-
|
55
|
-
def parse_config_file(yaml_path)
|
56
|
-
YAML.load_file(yaml_path)
|
57
|
-
end
|
58
|
-
|
59
|
-
def config_valid?(config)
|
60
|
-
return false unless config.is_a? Array
|
61
|
-
true
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
class RunTaskBase
|
66
|
-
attr_reader :success
|
67
|
-
attr_reader :exit_code
|
68
|
-
|
69
|
-
def run_task; end
|
70
|
-
def completed?; end
|
71
|
-
def kill_task; end #will be used for timeout
|
72
|
-
end
|
73
|
-
|
74
|
-
class RunECSTask < RunTaskBase
|
75
|
-
def initialize(task_name, params, logger = nil)
|
76
|
-
@logger = logger ? logger : Logger.new(STDOUT)
|
77
|
-
@task_name = task_name
|
78
|
-
@params = params['parameters']
|
79
|
-
@params = keys_to_symbol(@params)
|
80
|
-
@cluster = @params[:cluster]
|
81
|
-
end
|
82
|
-
|
83
|
-
def keys_to_symbol(params)
|
84
|
-
# replaces string keys with symbol keys
|
85
|
-
# required by AWS SDK
|
86
|
-
if params.is_a?(Hash)
|
87
|
-
params.inject({}){ |memo,(k,v)| memo[k.to_sym] = v; memo }
|
88
|
-
else
|
89
|
-
nil
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
def run_task
|
94
|
-
@logger.info("starting ECS task #{@task_name}")
|
95
|
-
resp = ecs_run_task
|
96
|
-
@task_arn = resp.tasks[0].task_arn
|
97
|
-
@logger.info("#{@task_name} task created #{@task_arn} on cluster #{@cluster}")
|
98
|
-
end
|
99
|
-
|
100
|
-
def ecs_run_task
|
101
|
-
@logger.debug("creating AWS client for ECS task #{@task_name}...")
|
102
|
-
@ecs_client = ::Aws::ECS::Client.new
|
103
|
-
@logger.debug("running ECS task #{@task_name}...")
|
104
|
-
@start_time = Time.new
|
105
|
-
resp = @ecs_client.run_task(@params)
|
106
|
-
return resp
|
107
|
-
rescue => e
|
108
|
-
@logger.error('SEQUENCER_ERROR')
|
109
|
-
@logger.error("error creating ECS task...")
|
110
|
-
@logger.error("response from ECS: #{resp}")
|
111
|
-
raise e
|
112
|
-
end
|
113
|
-
|
114
|
-
def ecs_describe_tasks
|
115
|
-
@ecs_client.describe_tasks(
|
116
|
-
{
|
117
|
-
cluster: @cluster,
|
118
|
-
tasks: [@task_arn] # required
|
119
|
-
}
|
120
|
-
)
|
121
|
-
end
|
122
|
-
|
123
|
-
def completed?
|
124
|
-
resp = ecs_describe_tasks
|
125
|
-
status = last_task_status(resp)
|
126
|
-
@logger.info("#{@task_name} : #{@task_arn} current status: #{status}")
|
127
|
-
if status == 'STOPPED'
|
128
|
-
@logger.info("#{@task_name} completed in #{Time.new - @start_time} seconds")
|
129
|
-
# parse exit_code(s) and return completion
|
130
|
-
@success = determine_success(resp)
|
131
|
-
return true
|
132
|
-
end
|
133
|
-
false
|
134
|
-
rescue => e
|
135
|
-
@logger.error('SEQUENCER_ERROR')
|
136
|
-
@logger.error(e.message)
|
137
|
-
e.backtrace.each { |r| @logger.error(r) }
|
138
|
-
end
|
139
|
-
|
140
|
-
# parses last status from aws API response
|
141
|
-
def last_task_status(resp)
|
142
|
-
resp.tasks[0].last_status
|
143
|
-
end
|
144
|
-
|
145
|
-
# success is determined by all containers having zero exit code
|
146
|
-
def determine_success(resp)
|
147
|
-
success = true
|
148
|
-
resp.tasks[0].containers.each do |c|
|
149
|
-
@logger.info("#{@task_name} : container #{c.name} #{c.container_arn} completed with exit_code #{c.exit_code}")
|
150
|
-
if c.exit_code != 0
|
151
|
-
# we had a problem!
|
152
|
-
success = false
|
153
|
-
end
|
154
|
-
end
|
155
|
-
success
|
156
|
-
end
|
157
|
-
end
|
158
|
-
end
|
159
|
-
end
|
1
|
+
require "full360_sequencer"
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require "logger"
|
2
|
+
require "aws-sdk-ecs"
|
3
|
+
|
4
|
+
module Full360
|
5
|
+
module Sequencer
|
6
|
+
class RunECSTask < RunTaskBase
|
7
|
+
attr_accessor :task_name
|
8
|
+
attr_accessor :params
|
9
|
+
attr_accessor :ecs_client
|
10
|
+
attr_accessor :logger
|
11
|
+
|
12
|
+
attr_reader :cluster
|
13
|
+
attr_reader :task_arn
|
14
|
+
attr_reader :start_time
|
15
|
+
|
16
|
+
def initialize(task_name, params, ecs_client = nil, logger = nil)
|
17
|
+
@logger = logger ||= Logger.new(STDOUT)
|
18
|
+
@ecs_client = ecs_client ||= Aws::ECS::Client.new
|
19
|
+
@task_name = task_name
|
20
|
+
@params = params[:parameters]
|
21
|
+
@cluster = self.params[:cluster]
|
22
|
+
end
|
23
|
+
|
24
|
+
def run_task
|
25
|
+
logger.info("starting ECS task #{task_name}")
|
26
|
+
|
27
|
+
resp = ecs_run_task
|
28
|
+
@task_arn = resp.tasks.first.task_arn
|
29
|
+
|
30
|
+
logger.info("#{task_name} task created #{task_arn} on cluster #{cluster}")
|
31
|
+
end
|
32
|
+
|
33
|
+
def ecs_run_task
|
34
|
+
logger.debug("running ECS task #{task_name}...")
|
35
|
+
@start_time = Time.new.utc
|
36
|
+
|
37
|
+
resp = ecs_client.run_task(params)
|
38
|
+
resp
|
39
|
+
rescue => e
|
40
|
+
logger.error("SEQUENCER_ERROR: response from ECS #{resp}")
|
41
|
+
raise e
|
42
|
+
end
|
43
|
+
|
44
|
+
def ecs_describe_tasks
|
45
|
+
ecs_client.describe_tasks(
|
46
|
+
{
|
47
|
+
cluster: cluster,
|
48
|
+
tasks: [task_arn],
|
49
|
+
}
|
50
|
+
)
|
51
|
+
end
|
52
|
+
|
53
|
+
def completed?
|
54
|
+
retries ||= 0
|
55
|
+
|
56
|
+
resp = ecs_describe_tasks
|
57
|
+
status = last_task_status(resp)
|
58
|
+
|
59
|
+
logger.info("#{task_name}: #{task_arn} current status: #{status}")
|
60
|
+
|
61
|
+
completed = false
|
62
|
+
|
63
|
+
if status == "STOPPED"
|
64
|
+
logger.info("#{task_name} completed in #{Time.new.utc - start_time} seconds")
|
65
|
+
# parse exit_code(s) and return completion
|
66
|
+
@success = determine_success(resp)
|
67
|
+
completed = true
|
68
|
+
end
|
69
|
+
|
70
|
+
completed
|
71
|
+
rescue => e
|
72
|
+
logger.warn(e.message)
|
73
|
+
logger.warn("task completion check failed, trying again ##{retries}")
|
74
|
+
|
75
|
+
sleep 10*retries
|
76
|
+
|
77
|
+
retry if (retries += 1) < 3
|
78
|
+
|
79
|
+
logger.error("SEQUENCER_ERROR: #{e.message}")
|
80
|
+
e.backtrace.each { |r| logger.error(r) }
|
81
|
+
end
|
82
|
+
|
83
|
+
# parses last status from aws API response
|
84
|
+
def last_task_status(resp)
|
85
|
+
resp.tasks.first.last_status
|
86
|
+
end
|
87
|
+
|
88
|
+
# success is determined by all containers having zero exit code
|
89
|
+
def determine_success(resp)
|
90
|
+
success = true
|
91
|
+
|
92
|
+
resp.tasks.first.containers.each do |c|
|
93
|
+
logger.info("#{task_name}: container #{c.name} #{c.container_arn} completed with exit_code #{c.exit_code}")
|
94
|
+
|
95
|
+
# we had a problem!
|
96
|
+
success = false if c.exit_code != 0
|
97
|
+
end
|
98
|
+
|
99
|
+
success
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require "yaml"
|
2
|
+
require "logger"
|
3
|
+
require "full360_sequencer/run_ecs_task"
|
4
|
+
|
5
|
+
module Full360
|
6
|
+
module Sequencer
|
7
|
+
class Runner
|
8
|
+
attr_accessor :sleep_between_checks
|
9
|
+
attr_accessor :logger
|
10
|
+
|
11
|
+
attr_reader :config
|
12
|
+
|
13
|
+
# Initializes the class
|
14
|
+
# @params sleep_between_checks [Int]
|
15
|
+
# @params logger [Logger]
|
16
|
+
# @return [Full360::Sequencer::Runner]
|
17
|
+
def initialize(sleep_between_checks, logger = nil)
|
18
|
+
@sleep_between_checks = sleep_between_checks
|
19
|
+
@logger = logger ||= Logger.new(STDOUT)
|
20
|
+
end
|
21
|
+
|
22
|
+
def config_from_file(yaml_path)
|
23
|
+
@config = parse_config_file(yaml_path)
|
24
|
+
end
|
25
|
+
|
26
|
+
def run_task_class(task_type_string)
|
27
|
+
case task_type_string
|
28
|
+
when "ecs_task"
|
29
|
+
Full360::Sequencer::RunECSTask
|
30
|
+
else
|
31
|
+
nil
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def run
|
36
|
+
config.each do |params|
|
37
|
+
this_task_name = task_name(params)
|
38
|
+
|
39
|
+
this_task = run_task_class(params[this_task_name][:type]).new(
|
40
|
+
this_task_name,
|
41
|
+
params[this_task_name]
|
42
|
+
)
|
43
|
+
|
44
|
+
this_task.run_task
|
45
|
+
|
46
|
+
until this_task.completed?
|
47
|
+
sleep sleep_between_checks
|
48
|
+
end
|
49
|
+
|
50
|
+
raise "task failed error" unless this_task.success
|
51
|
+
end
|
52
|
+
rescue => e
|
53
|
+
logger.error("SEQUENCER_ERROR: #{e.message}")
|
54
|
+
|
55
|
+
e.backtrace.each { |r| logger.error(r) }
|
56
|
+
raise e
|
57
|
+
end
|
58
|
+
|
59
|
+
def task_name(params)
|
60
|
+
params.keys.first
|
61
|
+
end
|
62
|
+
|
63
|
+
def config_valid?(config)
|
64
|
+
return false unless config.is_a? Array
|
65
|
+
true
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
# parse_config_file reads and parses the configuration file and returns
|
71
|
+
# an array of hashes with symbolize keys ready to consume.
|
72
|
+
# @params yaml_path [String]
|
73
|
+
# @return [Array]
|
74
|
+
def parse_config_file(yaml_path)
|
75
|
+
YAML.safe_load(File.read(yaml_path), symbolize_names: true)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require "logger"
|
2
|
+
|
3
|
+
# Autoload all gem classes
|
4
|
+
module Full360
|
5
|
+
module Sequencer
|
6
|
+
autoload :VERSION, "full360_sequencer/version"
|
7
|
+
autoload :Runner, "full360_sequencer/runner"
|
8
|
+
autoload :RunECSTask, "full360_sequencer/run_ecs_task"
|
9
|
+
autoload :RunTaskBase, "full360_sequencer/run_task_base"
|
10
|
+
end
|
11
|
+
end
|
metadata
CHANGED
@@ -1,72 +1,105 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: full360-sequencer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
-
-
|
8
|
-
|
9
|
-
autorequire:
|
7
|
+
- Full 360 Group
|
8
|
+
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
11
|
date: 2017-10-17 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
14
13
|
- !ruby/object:Gem::Dependency
|
14
|
+
name: aws-sdk-ecs
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '1.
|
20
|
-
name: logger
|
21
|
-
prerelease: false
|
19
|
+
version: '1.85'
|
22
20
|
type: :runtime
|
21
|
+
prerelease: false
|
23
22
|
version_requirements: !ruby/object:Gem::Requirement
|
24
23
|
requirements:
|
25
24
|
- - "~>"
|
26
25
|
- !ruby/object:Gem::Version
|
27
|
-
version: '1.
|
26
|
+
version: '1.85'
|
28
27
|
- !ruby/object:Gem::Dependency
|
28
|
+
name: minitest
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
34
|
-
|
33
|
+
version: '5.9'
|
34
|
+
type: :development
|
35
35
|
prerelease: false
|
36
|
-
type: :runtime
|
37
36
|
version_requirements: !ruby/object:Gem::Requirement
|
38
37
|
requirements:
|
39
38
|
- - "~>"
|
40
39
|
- !ruby/object:Gem::Version
|
41
|
-
version: '
|
40
|
+
version: '5.9'
|
42
41
|
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
48
|
-
|
47
|
+
version: '12'
|
48
|
+
type: :development
|
49
49
|
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '12'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: pry
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0.14'
|
50
62
|
type: :development
|
63
|
+
prerelease: false
|
51
64
|
version_requirements: !ruby/object:Gem::Requirement
|
52
65
|
requirements:
|
53
66
|
- - "~>"
|
54
67
|
- !ruby/object:Gem::Version
|
55
|
-
version: '
|
56
|
-
description:
|
57
|
-
email:
|
68
|
+
version: '0.14'
|
69
|
+
description: Automation for simple batch jobs run in AWS
|
70
|
+
email: support@full360.com
|
58
71
|
executables:
|
72
|
+
- console
|
59
73
|
- sequencer
|
60
74
|
extensions: []
|
61
75
|
extra_rdoc_files: []
|
62
76
|
files:
|
77
|
+
- ".github/workflows/release.yml"
|
78
|
+
- ".github/workflows/test.yml"
|
79
|
+
- ".gitignore"
|
80
|
+
- CHANGELOG.md
|
81
|
+
- Dockerfile.jruby
|
82
|
+
- Dockerfile.ruby
|
83
|
+
- Gemfile
|
84
|
+
- Gemfile.lock
|
85
|
+
- LICENSE
|
86
|
+
- README.md
|
87
|
+
- Rakefile
|
88
|
+
- bin/console
|
63
89
|
- bin/sequencer
|
90
|
+
- entrypoint.sh
|
91
|
+
- full360-sequencer.gemspec
|
64
92
|
- lib/full360-sequencer.rb
|
65
|
-
|
93
|
+
- lib/full360_sequencer.rb
|
94
|
+
- lib/full360_sequencer/run_ecs_task.rb
|
95
|
+
- lib/full360_sequencer/run_task_base.rb
|
96
|
+
- lib/full360_sequencer/runner.rb
|
97
|
+
- lib/full360_sequencer/version.rb
|
98
|
+
homepage: https://full360.com
|
66
99
|
licenses:
|
67
100
|
- MIT
|
68
101
|
metadata: {}
|
69
|
-
post_install_message:
|
102
|
+
post_install_message:
|
70
103
|
rdoc_options: []
|
71
104
|
require_paths:
|
72
105
|
- lib
|
@@ -81,9 +114,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
81
114
|
- !ruby/object:Gem::Version
|
82
115
|
version: '0'
|
83
116
|
requirements: []
|
84
|
-
|
85
|
-
|
86
|
-
signing_key:
|
117
|
+
rubygems_version: 3.1.6
|
118
|
+
signing_key:
|
87
119
|
specification_version: 4
|
88
|
-
summary:
|
120
|
+
summary: Full 360 sequencer utility
|
89
121
|
test_files: []
|