djin 0.10.0 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5e44393cb2f47b9b3a68d2ce59ae997e5e1d6b252e68778293b5e62a7600374b
4
- data.tar.gz: 5c02ed7dc956357e31b07c6ade84c5f660cb26469f7434ea0128b42f0ebec74f
3
+ metadata.gz: 06daa5a16dcd5adf9e7ff60d976689fbe7e146cf58f708b97d3fea8de2113599
4
+ data.tar.gz: e36aad9524a35db424096893fc2f817306c25160b8afea4fb19bd7c1951053fc
5
5
  SHA512:
6
- metadata.gz: 1d8d9dbd993757586e35679c12e1d03b98c940e5bca58e6ab2b3b8955c84c78b2fad6ae2053a0430b85aa580217a8540b3196ab3489c0ae76c4426e712142822
7
- data.tar.gz: be25638d743e46c62a452891298003d3dd93905ea38b6bbe51420d44549ba001785f7b9a631cc4e4b83c61ffa541688420df9bd9339023b1cb86747ad2ae87c4
6
+ metadata.gz: 071abc594f4ee1ac16f3983550abba863fb22594afba6c2a0d1b54b92888feb8eda51f55445d6562f061f065b5da12b55e4a964dbe863e33227fb3e421e13cab
7
+ data.tar.gz: 118d467194cbcad067c942e692794ac48afbcd4295183971e9a3a3c3ef41aec4ec2d693da83e7f86686e70cdfe9369458ae0eee319258b3dd8d3eb54f0761893
@@ -1,25 +1,37 @@
1
1
  name: Ruby
2
2
 
3
- on: [push]
3
+ on: [push, workflow_dispatch]
4
4
 
5
5
  jobs:
6
6
  build:
7
7
  runs-on: ubuntu-16.04
8
8
  strategy:
9
9
  matrix:
10
- ruby: [ '2.4', '2.5', '2.6' ]
10
+ ruby: [ '2.5', '2.6', '2.7' ]
11
11
  name: Ruby ${{ matrix.ruby }}
12
12
  steps:
13
13
  - uses: actions/checkout@v2
14
- - uses: actions/setup-ruby@v1
15
- with:
16
- ruby-version: ${{ matrix.ruby }}
17
- - name: Install Gems
14
+
15
+ - name: Setup Code Climate test-reporter
18
16
  run: |
19
- gem install bundler
20
- bundle install --jobs 4 --retry 3
17
+ curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
18
+ chmod +x ./cc-test-reporter
19
+ ./cc-test-reporter before-build
20
+
21
+ - name: Build
22
+ run: |
23
+ docker-compose -p djin_ruby_${{ matrix.ruby }} build --build-arg ${{ matrix.ruby }} app
24
+
21
25
  - name: Run tests
22
- run: bundle exec rake
26
+ run: |
27
+ docker-compose -p djin_ruby_${{ matrix.ruby }} run --rm app rspec
28
+ env:
29
+ TMP_TEST_FILE_FOLDER: '/tmp'
30
+
31
+ - name: Publish code coverage
32
+ run: |
33
+ # TODO: Move to script
34
+ docker-compose -p djin_ruby_${{ matrix.ruby }} run --rm app 'export GIT_BRANCH="${GITHUB_REF/refs\/heads\//}" && ./cc-test-reporter after-build -r ${{secrets.CC_TEST_REPORTER_ID}}'
23
35
 
24
36
  lint:
25
37
  runs-on: ubuntu-16.04
data/.gitignore CHANGED
@@ -7,6 +7,7 @@
7
7
  /spec/reports/
8
8
  /tmp/
9
9
  .byebug_history
10
+ .irb_history
10
11
 
11
12
  # rspec failure tracking
12
13
  .rspec_status
data/.irbrc ADDED
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ IRB.conf[:SAVE_HISTORY] = 200
4
+ IRB.conf[:HISTORY_FILE] = '~/.irb_history'
@@ -3,6 +3,11 @@ inherit_from: .rubocop_todo.yml
3
3
  Style/Documentation:
4
4
  Enabled: false
5
5
 
6
+ Style/BlockDelimiters:
7
+ Exclude:
8
+ - spec/**/*
9
+
6
10
  Metrics/BlockLength:
7
11
  Exclude:
8
12
  - spec/**/*
13
+ - djin.gemspec
@@ -1,3 +1,7 @@
1
+ ## 0.11.0 - 14/01/2021
2
+ * [FEATURE] Remote Configs
3
+ * [TECH] Adds Test Coverage
4
+
1
5
  ## 0.10.0 - 08/10/2020
2
6
  * [FEATURE] -f command option
3
7
 
data/Dockerfile CHANGED
@@ -1,4 +1,5 @@
1
- FROM ruby:2.6.5-alpine AS builder
1
+ ARG RUBY_VERSION
2
+ FROM ruby:${RUBY_VERSION:-2.6.5}-alpine AS builder
2
3
 
3
4
  ENV BUILD_PACKAGES build-base git
4
5
 
@@ -13,17 +14,23 @@ COPY djin.gemspec Gemfile Gemfile.lock ./
13
14
 
14
15
  COPY lib/djin/version.rb lib/djin/version.rb
15
16
 
16
- RUN gem install bundler -v 2.0.2
17
+ RUN gem install bundler -v 2.1.4
17
18
 
18
19
  RUN bundle install
19
20
 
21
+ FROM builder AS dev
22
+
23
+ WORKDIR /usr/src/djin
24
+
25
+ COPY . .
26
+
20
27
  FROM ruby:2.6.5-alpine
21
28
 
22
29
  WORKDIR /usr/src/djin
23
30
 
24
31
  COPY --from=builder /usr/local/bundle/ /usr/local/bundle
25
32
 
26
- RUN gem install bundler -v 2.0.2
33
+ RUN gem install bundler -v 2.1.4
27
34
 
28
35
  ENV DEPENDENCIES git
29
36
 
data/Gemfile CHANGED
@@ -2,5 +2,9 @@
2
2
 
3
3
  source 'https://rubygems.org'
4
4
 
5
+ group :test do
6
+ gem 'factory_bot'
7
+ end
8
+
5
9
  # Specify your gem's dependencies in djin.gemspec
6
10
  gemspec
@@ -1,40 +1,46 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- djin (0.10.0)
4
+ djin (0.11.0)
5
5
  dry-cli (~> 0.6.0)
6
6
  dry-equalizer (~> 0.3.0)
7
7
  dry-struct (~> 1.3.0)
8
8
  dry-validation (~> 1.5.1)
9
+ git (~> 1.8.1)
9
10
  mustache (~> 1.1.1)
10
11
  vseries (~> 0.1.0)
11
12
 
12
13
  GEM
13
14
  remote: https://rubygems.org/
14
15
  specs:
16
+ activesupport (6.1.1)
17
+ concurrent-ruby (~> 1.0, >= 1.0.2)
18
+ i18n (>= 1.6, < 2)
19
+ minitest (>= 5.1)
20
+ tzinfo (~> 2.0)
21
+ zeitwerk (~> 2.3)
15
22
  ast (2.4.1)
16
23
  byebug (11.1.1)
17
24
  concurrent-ruby (1.1.7)
18
25
  diff-lcs (1.3)
26
+ docile (1.3.2)
19
27
  dry-cli (0.6.0)
20
28
  concurrent-ruby (~> 1.0)
21
- dry-configurable (0.11.6)
29
+ dry-configurable (0.12.0)
22
30
  concurrent-ruby (~> 1.0)
23
- dry-core (~> 0.4, >= 0.4.7)
24
- dry-equalizer (~> 0.2)
31
+ dry-core (~> 0.5, >= 0.5.0)
25
32
  dry-container (0.7.2)
26
33
  concurrent-ruby (~> 1.0)
27
34
  dry-configurable (~> 0.1, >= 0.1.3)
28
- dry-core (0.4.9)
35
+ dry-core (0.5.0)
29
36
  concurrent-ruby (~> 1.0)
30
37
  dry-equalizer (0.3.0)
31
38
  dry-inflector (0.2.0)
32
39
  dry-initializer (3.0.4)
33
- dry-logic (1.0.8)
40
+ dry-logic (1.1.0)
34
41
  concurrent-ruby (~> 1.0)
35
- dry-core (~> 0.2)
36
- dry-equalizer (~> 0.2)
37
- dry-schema (1.5.5)
42
+ dry-core (~> 0.5, >= 0.5)
43
+ dry-schema (1.5.6)
38
44
  concurrent-ruby (~> 1.0)
39
45
  dry-configurable (~> 0.8, >= 0.8.3)
40
46
  dry-core (~> 0.4)
@@ -61,13 +67,22 @@ GEM
61
67
  dry-equalizer (~> 0.2)
62
68
  dry-initializer (~> 3.0)
63
69
  dry-schema (~> 1.5, >= 1.5.2)
70
+ factory_bot (6.1.0)
71
+ activesupport (>= 5.0.0)
72
+ git (1.8.1)
73
+ rchardet (~> 1.8)
74
+ i18n (1.8.7)
75
+ concurrent-ruby (~> 1.0)
64
76
  ice_nine (0.11.2)
77
+ json (2.3.1)
78
+ minitest (5.14.3)
65
79
  mustache (1.1.1)
66
80
  parallel (1.19.2)
67
81
  parser (2.7.1.4)
68
82
  ast (~> 2.4.1)
69
83
  rainbow (3.0.0)
70
84
  rake (13.0.1)
85
+ rchardet (1.8.0)
71
86
  regexp_parser (1.7.1)
72
87
  rexml (3.2.4)
73
88
  rspec (3.9.0)
@@ -95,8 +110,16 @@ GEM
95
110
  rubocop-ast (0.0.3)
96
111
  parser (>= 2.7.0.1)
97
112
  ruby-progressbar (1.10.1)
113
+ simplecov (0.17.1)
114
+ docile (~> 1.1)
115
+ json (>= 1.8, < 3)
116
+ simplecov-html (~> 0.10.0)
117
+ simplecov-html (0.10.2)
118
+ tzinfo (2.0.4)
119
+ concurrent-ruby (~> 1.0)
98
120
  unicode-display_width (1.7.0)
99
121
  vseries (0.1.1)
122
+ zeitwerk (2.4.2)
100
123
 
101
124
  PLATFORMS
102
125
  ruby
@@ -105,9 +128,11 @@ DEPENDENCIES
105
128
  bundler (~> 2.0)
106
129
  byebug
107
130
  djin!
131
+ factory_bot
108
132
  rake (~> 13.0)
109
133
  rspec (~> 3.0)
110
134
  rubocop
135
+ simplecov (~> 0.17.0)
111
136
 
112
137
  BUNDLED WITH
113
138
  2.1.4
data/README.md CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  ![](https://github.com/catks/djin/workflows/Ruby/badge.svg?branch=master)
4
4
  [![Maintainability](https://api.codeclimate.com/v1/badges/824a2e78399813543212/maintainability)](https://codeclimate.com/github/catks/djin/maintainability)
5
+ [![Test Coverage](https://api.codeclimate.com/v1/badges/824a2e78399813543212/test_coverage)](https://codeclimate.com/github/catks/djin/test_coverage)
5
6
 
6
7
  Djin is a make-like utility for docker containers
7
8
 
@@ -13,7 +14,7 @@ Djin is distributed as a Ruby Gem, to install simple run:
13
14
 
14
15
  ### With Rbenv
15
16
 
16
- If you use Rbenv you can install djin only once and create a alias in your .basrc, .zshrc, etc:
17
+ If you use Rbenv you can install djin only once and create an alias in your .basrc, .zshrc, etc:
17
18
 
18
19
  #### ZSH
19
20
  $ RBENV_VERSION=$(rbenv global) gem install djin && echo "alias djin='RBENV_VERSION=$(rbenv global) djin'" >> ~/.zshrc
@@ -26,7 +27,7 @@ If you use Rbenv you can install djin only once and create a alias in your .basr
26
27
  To use djin first you need to create a djin.yml file:
27
28
 
28
29
  ```yaml
29
- djin_version: '0.10.0'
30
+ djin_version: '0.11.0'
30
31
 
31
32
  tasks:
32
33
  # With a docker image
@@ -53,12 +54,12 @@ You can also set task dependencies with depends_on option:
53
54
 
54
55
 
55
56
  ```yaml
56
- djin_version: '0.10.0'
57
+ djin_version: '0.11.0'
57
58
 
58
59
  _default_run_options: &default_run_options
59
60
  options: "--rm"
60
61
 
61
- tasks:
62
+ tasks:
62
63
  "db:create":
63
64
  docker-compose:
64
65
  service: app
@@ -82,7 +83,7 @@ tasks:
82
83
  Or mix local commands and docker/docker-compose commands:
83
84
 
84
85
  ```yaml
85
- djin_version: '0.10.0'
86
+ djin_version: '0.11.0'
86
87
 
87
88
  _default_run_options: &default_run_options
88
89
  options: "--rm"
@@ -121,7 +122,7 @@ After that you can run `djin {{task_name}}`, like `djin script` or `djin test`
121
122
  You can also use environment variables using the '{{YOUR_ENV_HERE}}' syntax, like so:
122
123
 
123
124
  ```yaml
124
- djin_version: '0.10.0'
125
+ djin_version: '0.11.0'
125
126
 
126
127
  _default_run_options: &default_run_options
127
128
  options: "--rm"
@@ -138,7 +139,7 @@ tasks:
138
139
 
139
140
  Or define some variables to use in multiple locations
140
141
  ```yaml
141
- djin_version: '0.10.0'
142
+ djin_version: '0.11.0'
142
143
 
143
144
  _default_run_options: &default_run_options
144
145
  options: "--rm"
@@ -162,7 +163,7 @@ tasks:
162
163
  It's also possible to pass custom arguments to the command, which means is possible to make a djin task act like the command itself:
163
164
 
164
165
  ```yaml
165
- djin_version: '0.10.0'
166
+ djin_version: '0.11.0'
166
167
 
167
168
  _default_run_options: &default_run_options
168
169
  options: "--rm"
@@ -179,16 +180,16 @@ tasks:
179
180
 
180
181
  ```
181
182
 
182
- With that you can pass custom args after `--`, eg: `djin rubocop -- --parallel`, which wil make djin runs `rubocop --parallel` inside the service `app`.
183
+ With that, you can pass custom args after `--`, eg: `djin rubocop -- --parallel`, which will make djin runs `rubocop --parallel` inside the service `app`.
183
184
 
184
185
  Under the hood djin uses [Mustache](https://mustache.github.io/), so you can use other features like conditionals: `{{#IS_ENABLE}} Enabled {{/IS_ENABLE}}` (for args use the `args?`, eg: `{{#args?}} {{args}} --and-other-thing{{/args?}}`), to see more more options you can access this [Link](https://mustache.github.io/mustache.5.html)
185
186
 
186
187
  ### Reusing tasks
187
188
 
188
- If you have multiple tasks with similar behaviour and with small differences you can use the `include` keyword, so this:
189
+ If you have multiple tasks with similar behavior and with small differences you can use the `include` keyword, so this:
189
190
 
190
191
  ```yaml
191
- djin_version: '0.10.0'
192
+ djin_version: '0.11.0'
192
193
 
193
194
  tasks:
194
195
  "host1:ssh":
@@ -227,7 +228,7 @@ can become this:
227
228
 
228
229
  ```yaml
229
230
  # djin.yml
230
- djin_version: '0.10.0'
231
+ djin_version: '0.11.0'
231
232
 
232
233
  include:
233
234
  - file: '.djin/server_tasks.yml'
@@ -249,7 +250,7 @@ include:
249
250
 
250
251
  ```yaml
251
252
  # .djin/server_tasks.yml
252
- djin_version: '0.10.0'
253
+ djin_version: '0.11.0'
253
254
 
254
255
  tasks:
255
256
  "{{namespace}}:ssh":
@@ -266,11 +267,30 @@ tasks:
266
267
  local:
267
268
  run:
268
269
  - ssh -t {{ssh_user}}@{{host}} tail -f /var/log/my_log
269
- ```
270
+ ```
271
+
272
+ You can also reuse tasks in some git repository, to do that you need to declare a git source and optionally a version:
273
+
274
+ ```yaml
275
+ djin_version: '0.11.0'
276
+
277
+ include:
278
+ - git: 'https://github.com/catks/djin.git'
279
+ version: 'master'
280
+ file: 'examples/djin_lib/test.yml'
281
+ context:
282
+ variables:
283
+ namespace: 'remote:'
284
+
285
+ ```
286
+
287
+ After that run `djin remote-config fetch` to fetch the repo and you can start using the tasks (All remote repos are cloned in `~/.djin/remote`)
288
+
289
+ See `djin remote-config` to learn more.
270
290
 
271
291
  ### Loading custom files
272
292
 
273
- You can also specify a file to be read by djin with `-f`, eg:
293
+ You can also specify a file to be read by djin with `-f`, eg:
274
294
 
275
295
  ```bash
276
296
  djin -f my_file.yml # Returns the help for all tasks in my_file
@@ -298,7 +318,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
298
318
  1. Enable multiple -f options to merge configuration between files
299
319
  2. Option to export tasks to Makefile
300
320
  3. djin-export docker image to create and sync makefiles
301
- 4. include key option to add tasks in git repositories files (maybe with a local cache)
321
+ 4. include a key option to add tasks in git repositories files (maybe with a local cache)
302
322
 
303
323
  ## Contributing
304
324
 
data/Vertofile CHANGED
@@ -1,4 +1,4 @@
1
- verto_version '0.8.0'
1
+ verto_version '0.10.0'
2
2
 
3
3
  config {
4
4
  version.prefix = 'v' # Adds a version_prefix
@@ -12,20 +12,10 @@ context(branch('master')) {
12
12
  }
13
13
 
14
14
  before_tag_creation {
15
- version_changes = sh(
16
- %q#git log --oneline --decorate | grep -B 100 -m 1 "tag:" | grep "pull request" | awk '{print $1}' | xargs git show --format='%b' | grep -v Approved | grep -v "^$" | grep -E "^[[:space:]]*\[.*\]" | sed 's/^[[:space:]]*\(.*\)/ * \1/'#, output: false
17
- ).output
18
-
19
- puts "---------------------------"
20
- version_changes = "## #{new_version} - #{Time.now.strftime('%d/%m/%Y')}\n#{version_changes}\n"
21
- exit unless confirm("Create new Realease?\n" \
22
- "---------------------------\n" \
23
- "#{version_changes}" \
24
- "---------------------------\n"
25
- )
26
-
27
- # CHANGELOG
28
- file('CHANGELOG.md').prepend(version_changes)
15
+ update_changelog(with: :merged_pull_requests_with_bracketed_labels,
16
+ confirmation: true,
17
+ filename: 'CHANGELOG.md')
18
+
29
19
  git!('add CHANGELOG.md')
30
20
 
31
21
  files_to_change_version_once = %w[lib/djin/version.rb djin.yml] + Dir['examples/**/*.yml'] + Dir['spec/support/fixtures/**/*.yml']
@@ -21,7 +21,7 @@ Gem::Specification.new do |spec|
21
21
  # Specify which files should be added to the gem when it is released.
22
22
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
23
23
  spec.files = Dir.chdir(File.expand_path(__dir__)) do
24
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
24
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|docker)/}) }
25
25
  end
26
26
  spec.bindir = 'exe'
27
27
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
@@ -31,6 +31,7 @@ Gem::Specification.new do |spec|
31
31
  spec.add_dependency 'dry-equalizer', '~> 0.3.0'
32
32
  spec.add_dependency 'dry-struct', '~> 1.3.0'
33
33
  spec.add_dependency 'dry-validation', '~> 1.5.1'
34
+ spec.add_dependency 'git', '~> 1.8.1'
34
35
  spec.add_dependency 'mustache', '~> 1.1.1'
35
36
  spec.add_dependency 'vseries', '~> 0.1.0'
36
37
  spec.add_development_dependency 'bundler', '~> 2.0'
@@ -38,4 +39,5 @@ Gem::Specification.new do |spec|
38
39
  spec.add_development_dependency 'rake', '~> 13.0'
39
40
  spec.add_development_dependency 'rspec', '~> 3.0'
40
41
  spec.add_development_dependency 'rubocop'
42
+ spec.add_development_dependency 'simplecov', '~> 0.17.0'
41
43
  end
data/djin.yml CHANGED
@@ -1,4 +1,4 @@
1
- djin_version: '0.10.0'
1
+ djin_version: '0.11.0'
2
2
 
3
3
  _default_run_options: &default_run_options
4
4
  options: "--rm --entrypoint=''"
@@ -9,11 +9,21 @@ tasks:
9
9
  docker-compose:
10
10
  service: app
11
11
  run:
12
- commands: "cd /usr/src/djin && rspec {{args}}"
12
+ commands: "rspec {{args}}"
13
13
  <<: *default_run_options
14
14
  aliases:
15
15
  - rspec
16
16
 
17
+ lint:
18
+ description: Lint
19
+ docker-compose:
20
+ service: app
21
+ run:
22
+ commands: "rubocop {{args}}"
23
+ <<: *default_run_options
24
+ aliases:
25
+ - rubocop
26
+
17
27
  sh:
18
28
  description: Enter app service shell
19
29
  docker-compose:
@@ -1,6 +1,20 @@
1
- version: '3'
1
+ version: "3.9"
2
+
2
3
  services:
3
4
  app:
4
- build: .
5
+ build:
6
+ context: .
7
+ target: dev
8
+ entrypoint: 'sh docker-entrypoint.sh'
9
+ command: 'djin'
10
+ tty: true
11
+ stdin_open: true
5
12
  volumes:
6
13
  - .:/usr/src/djin
14
+ depends_on:
15
+ - gitserver
16
+
17
+ gitserver:
18
+ image: catks/gitserver-http:0.1.0
19
+ volumes:
20
+ - ./docker/git_server/repos/:/var/lib/initial/
@@ -0,0 +1,7 @@
1
+ #!/bin/sh
2
+
3
+ set -e
4
+
5
+ bundle check || bundle install
6
+
7
+ exec bundle exec "$@"
@@ -1,5 +1,5 @@
1
1
  ---
2
- djin_version: '0.10.0'
2
+ djin_version: '0.11.0'
3
3
 
4
4
  include:
5
5
  - file: 'djin_lib/test.yml'
@@ -1,4 +1,4 @@
1
- djin_version: '0.10.0'
1
+ djin_version: '0.11.0'
2
2
 
3
3
  _default_run_options: &default_run_options
4
4
  options: "--rm --entrypoint=''"
@@ -1,4 +1,4 @@
1
- djin_version: '0.10.0'
1
+ djin_version: '0.11.0'
2
2
 
3
3
  include:
4
4
  - file: '.djin/server_tasks.yml'
@@ -0,0 +1,9 @@
1
+ djin_version: '0.11.0'
2
+
3
+ include:
4
+ - git: 'https://gitserver/myrepo.git'
5
+ version: 'master'
6
+ file: 'examples/djin_lib/test.yml'
7
+ context:
8
+ variables:
9
+ namespace: 'remote:'
@@ -9,22 +9,26 @@ require 'vseries'
9
9
  require 'dry/cli'
10
10
  require 'mustache'
11
11
  require 'optparse'
12
+ require 'git'
12
13
  require 'djin/extensions/hash_extensions'
13
14
  require 'djin/extensions/object_extensions'
14
15
  require 'djin/entities/types'
15
16
  require 'djin/entities/task'
16
- require 'djin/entities/file_config'
17
+ require 'djin/entities/include_config.rb'
18
+ require 'djin/entities/main_config'
17
19
  require 'djin/interpreter/base_command_builder'
18
20
  require 'djin/interpreter/docker_command_builder'
19
21
  require 'djin/interpreter/docker_compose_command_builder'
20
22
  require 'djin/interpreter/local_command_builder'
21
23
  require 'djin/interpreter'
24
+ require 'djin/include_resolver'
22
25
  require 'djin/config_loader'
23
26
  require 'djin/executor'
24
27
  require 'djin/root_cli_parser'
25
28
  require 'djin/cli'
26
29
  require 'djin/task_contract'
27
30
  require 'djin/repositories/task_repository'
31
+ require 'djin/repositories/remote_config_repository'
28
32
  require 'djin/memory_cache'
29
33
 
30
34
  module Djin
@@ -32,34 +36,66 @@ module Djin
32
36
 
33
37
  using Djin::ObjectExtensions
34
38
 
35
- def self.load_tasks!(*file_paths)
36
- files = file_paths.presence || RootCliParser.parse![:files] || ['djin.yml']
39
+ class << self
40
+ def load_tasks!(*file_paths)
41
+ files = file_paths.presence || RootCliParser.parse![:files] || ['djin.yml']
37
42
 
38
- file_config = ConfigLoader.load_files!(*files)
43
+ file_config = ConfigLoader.load_files!(*files)
39
44
 
40
- # TODO: Make all tasks be under 'tasks' key, passing only the tasks here
41
- tasks = Interpreter.load!(file_config)
45
+ # TODO: Make all tasks be under 'tasks' key, passing only the tasks here
46
+ tasks = Interpreter.load!(file_config)
42
47
 
43
- @task_repository = TaskRepository.new(tasks)
44
- CLI.load_tasks!(tasks)
45
- rescue Djin::Interpreter::InvalidConfigurationError => e
46
- error_name = e.class.name.split('::').last
47
- abort("[#{error_name}] #{e.message}")
48
- end
48
+ @task_repository = TaskRepository.new(tasks)
49
49
 
50
- def self.tasks
51
- task_repository.all
52
- end
50
+ remote_configs = file_config.include_configs.select { |f| f.type == :remote }
51
+ @remote_config_repository = RemoteConfigRepository.new(remote_configs)
53
52
 
54
- def self.task_repository
55
- @task_repository ||= TaskRepository.new
56
- end
53
+ CLI.load_tasks!(tasks)
54
+ rescue Djin::Interpreter::InvalidConfigurationError => e
55
+ error_name = e.class.name.split('::').last
56
+ abort("[#{error_name}] #{e.message}")
57
+ end
57
58
 
58
- def self.cache
59
- @cache ||= MemoryCache.new
60
- end
59
+ def tasks
60
+ task_repository.all
61
+ end
62
+
63
+ def task_repository
64
+ @task_repository ||= TaskRepository.new
65
+ end
66
+
67
+ def remote_config_repository
68
+ @remote_config_repository ||= RemoteConfigRepository.new
69
+ end
70
+
71
+ def cache
72
+ @cache ||= MemoryCache.new
73
+ end
74
+
75
+ def root_path
76
+ Pathname.new File.expand_path(__dir__ + '/..')
77
+ end
78
+
79
+ def warn(message, type: 'WARNING')
80
+ stderr.puts "[#{type}] #{message}"
81
+ end
82
+
83
+ def warn_once(message, type: 'WARNING')
84
+ return if warnings.include?(message)
85
+
86
+ warn(message, type: type)
87
+
88
+ warnings << message
89
+ end
90
+
91
+ def stderr
92
+ $stderr
93
+ end
94
+
95
+ private
61
96
 
62
- def self.root_path
63
- Pathname.new File.expand_path(File.dirname(__FILE__) + '/..')
97
+ def warnings
98
+ @warnings ||= []
99
+ end
64
100
  end
65
101
  end
@@ -37,7 +37,33 @@ module Djin
37
37
  end
38
38
  end
39
39
 
40
+ module RemoteConfig
41
+ class Fetch < Dry::CLI::Command
42
+ desc 'Fetchs missing remote configs'
43
+
44
+ def call(*)
45
+ Djin.remote_config_repository.fetch_all
46
+ end
47
+ end
48
+
49
+ class Clear < Dry::CLI::Command
50
+ desc 'clear downloaded remote configs'
51
+ option :all,
52
+ type: :boolean,
53
+ default: false,
54
+ desc: 'Remove all remote configs, not only the ones referenced in the current djin file'
55
+
56
+ def call(all:)
57
+ return Djin.remote_config_repository.clear_all if all
58
+
59
+ Djin.remote_config_repository.clear
60
+ end
61
+ end
62
+ end
63
+
40
64
  register '-f', File, aliases: ['--file']
41
65
  register '--version', Version, aliases: ['-v']
66
+ register 'remote-config fetch', RemoteConfig::Fetch
67
+ register 'remote-config clear', RemoteConfig::Clear
42
68
  end
43
69
  end
@@ -4,11 +4,13 @@ module Djin
4
4
  # TODO: Refactor this class to be the Interpreter
5
5
  # class and use the current interpreter as
6
6
  # a TaskLoader
7
+
8
+ # rubocop:disable Metrics/ClassLength
7
9
  class ConfigLoader
8
10
  using Djin::HashExtensions
9
11
  RESERVED_WORDS = %w[djin_version variables tasks include].freeze
10
12
 
11
- # CHange Base Error
13
+ # Change Base Error
12
14
  FileNotFoundError = Class.new(Interpreter::InvalidConfigurationError)
13
15
 
14
16
  def self.load_files!(*files, runtime_config: {}, base_directory: '.')
@@ -25,7 +27,7 @@ module Djin
25
27
  @base_directory = Pathname.new(base_directory)
26
28
  @template_file = @base_directory.join(template_file_path)
27
29
 
28
- raise FileNotFoundError, "File '#{@template_file}' not found" unless @template_file.exist?
30
+ file_not_found!(@template_file) unless @template_file.exist?
29
31
 
30
32
  @template_file_content = Djin.cache.fetch(@template_file.realpath.to_s) { @template_file.read }
31
33
  @runtime_config = runtime_config
@@ -33,6 +35,7 @@ module Djin
33
35
 
34
36
  def load!
35
37
  validate_version!
38
+ validate_missing_config!
36
39
 
37
40
  file_config
38
41
  end
@@ -40,11 +43,12 @@ module Djin
40
43
  private
41
44
 
42
45
  def file_config
43
- FileConfig.new(
46
+ MainConfig.new(
44
47
  djin_version: version,
45
48
  variables: variables,
46
49
  tasks: tasks,
47
- raw_tasks: raw_tasks
50
+ raw_tasks: raw_tasks,
51
+ include_configs: @include_configs || []
48
52
  )
49
53
  end
50
54
 
@@ -66,8 +70,11 @@ module Djin
66
70
  end
67
71
 
68
72
  def legacy_tasks
69
- warn '[DEPRECATED] Root tasks are deprecated and will be removed in Djin 1.0.0,' \
70
- ' put the tasks under \'tasks\' keyword'
73
+ Djin.warn_once(
74
+ 'Root tasks are deprecated and will be removed in Djin 1.0.0,' \
75
+ ' put the tasks under \'tasks\' keyword',
76
+ type: 'DEPRECATED'
77
+ )
71
78
 
72
79
  rendered_djin_config.except(*RESERVED_WORDS).reject { |task| task.start_with?('_') }
73
80
  end
@@ -94,11 +101,37 @@ module Djin
94
101
  included_config.raw_tasks
95
102
  end
96
103
 
104
+ # TODO: Rename method
97
105
  def included_config
98
- @included_config ||= raw_djin_config['include']&.map do |tasks_reference|
99
- ConfigLoader.load!(tasks_reference['file'], base_directory: @template_file.dirname,
100
- runtime_config: tasks_reference['context'] || {})
101
- end&.reduce(:deep_merge)
106
+ @included_config ||= begin
107
+ present_include_configs&.map do |present_include|
108
+ ConfigLoader.load!(present_include.file, base_directory: @template_file.dirname,
109
+ # TODO: Rename to context_config
110
+ runtime_config: present_include.context)
111
+ end&.reduce(:deep_merge)
112
+ end
113
+ end
114
+
115
+ def present_include_configs
116
+ include_configs&.select(&:present?)
117
+ end
118
+
119
+ def missing_include_configs
120
+ include_configs&.select(&:missing?)
121
+ end
122
+
123
+ # TODO: Refactor to move include methods to a specific IncludeConfigLoader, maybe rename IncludeResolver
124
+ def include_configs
125
+ @include_configs ||= begin
126
+ # TODO: Rename the resolved variables
127
+ resolver = IncludeResolver.new(base_directory: @template_file.dirname)
128
+
129
+ include_djin_config = raw_djin_config['include'] || []
130
+
131
+ include_djin_config.map do |include_config|
132
+ resolver.call(include_config)
133
+ end
134
+ end
102
135
  end
103
136
 
104
137
  def args
@@ -142,5 +175,26 @@ module Djin
142
175
 
143
176
  raise Interpreter::VersionNotSupportedError, "Version #{version} is not supported, use #{Djin::VERSION} or higher"
144
177
  end
178
+
179
+ def validate_missing_config!
180
+ missing_include_configs.each do |ic|
181
+ file_not_found!(ic.full_path) if ic.type == :local
182
+
183
+ missing_file_remote_error = "#{ic.git} exists but is missing %s," \
184
+ 'if the file exists in upstream run djin remote-config fetch to fix'
185
+
186
+ file_not_found!(ic.full_path, missing_file_remote_error) if ic.type == :remote && ic.repository_fetched?
187
+
188
+ if ic.type == :remote
189
+ Djin.warn_once "Missing #{ic.git} with version '#{ic.version}', " \
190
+ 'run `djin remote-config fetch` to fetch the config'
191
+ end
192
+ end
193
+ end
194
+
195
+ def file_not_found!(filename, message = "File '%s' not found")
196
+ raise FileNotFoundError, message % filename
197
+ end
145
198
  end
199
+ # rubocop:enable Metrics/ClassLength
146
200
  end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Djin
4
+ class IncludeConfig < Dry::Struct
5
+ attribute :file, Types::String
6
+ attribute :base_directory, Types::String
7
+ attribute :context, Types::Hash.default({}.freeze)
8
+ attribute :git, Types::String.optional.default(nil)
9
+ attribute :version, Types::String.default('master')
10
+ attribute :missing, Types::Bool.optional.default(nil)
11
+
12
+ using Djin::ObjectExtensions
13
+
14
+ include Dry::Equalizer(:git, :version, :file, :context)
15
+
16
+ def type
17
+ @type ||= git.present? ? :remote : :local
18
+ end
19
+
20
+ def present?
21
+ !missing?
22
+ end
23
+
24
+ def missing?
25
+ missing
26
+ end
27
+
28
+ def full_path
29
+ base_directory_pathname.join(file).expand_path
30
+ end
31
+
32
+ def repository_fetched?
33
+ @repository_fetched ||= base_directory_pathname.join(folder_name).exist?
34
+ end
35
+
36
+ # TODO: Rethink
37
+ def folder_name
38
+ @folder_name ||= "#{git.split('/').last.chomp('.git')}@#{version}"
39
+ end
40
+
41
+ private
42
+
43
+ def base_directory_pathname
44
+ @base_directory_pathname ||= Pathname.new(base_directory)
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Djin
4
+ module IncludeConfig
5
+ class Base < Dry::Struct
6
+ attribute :file, Types::String
7
+ attribute :context, Types::Hash.default({}.freeze)
8
+
9
+ include Dry::Equalizer(:file, :context)
10
+
11
+ def type
12
+ raise NotImplementedError
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Djin
4
+ module IncludeConfig
5
+ class Local < Dry::Struct
6
+ attribute :file, Types::String
7
+ attribute :context, Types::Hash.default({}.freeze)
8
+
9
+ include Dry::Equalizer(:file, :context)
10
+
11
+ def type
12
+ :local
13
+ end
14
+ end
15
+ end
16
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Djin
4
- class FileConfig < Dry::Struct
4
+ class MainConfig < Dry::Struct
5
5
  using HashExtensions
6
6
 
7
7
  attribute :djin_version, Types::String
@@ -9,6 +9,7 @@ module Djin
9
9
  attribute :tasks, Types::Hash
10
10
  attribute :raw_tasks, Types::Hash
11
11
  # TODO: Add env and args
12
+ attribute :include_configs, Types::Array.of(Djin::IncludeConfig).default([].freeze)
12
13
 
13
14
  include Dry::Equalizer(:djin_version, :variables, :tasks, :raw_tasks)
14
15
 
@@ -19,13 +20,13 @@ module Djin
19
20
  def merge(file_config)
20
21
  merged_hash = to_h.merge(file_config.to_h)
21
22
 
22
- FileConfig.new(merged_hash)
23
+ MainConfig.new(merged_hash)
23
24
  end
24
25
 
25
26
  def deep_merge(file_config)
26
27
  merged_hash = to_h.deep_merge(file_config.to_h)
27
28
 
28
- FileConfig.new(merged_hash)
29
+ MainConfig.new(merged_hash)
29
30
  end
30
31
  end
31
32
  end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Djin
4
+ class IncludeResolver
5
+ using ObjectExtensions
6
+ using HashExtensions
7
+
8
+ def initialize(base_directory: '.', remote_directory: '~/.djin/remote', entity_class: Djin::IncludeConfig)
9
+ # TODO: Use chain of responsability
10
+ @base_directory = Pathname.new(base_directory)
11
+ @remote_directory = Pathname.new(remote_directory)
12
+ @entity_class = entity_class
13
+ end
14
+
15
+ def call(params)
16
+ include_config_params = remote_handler(params)
17
+ include_config_params ||= local_handler(params)
18
+
19
+ build_entity(include_config_params)
20
+ end
21
+
22
+ private
23
+
24
+ def remote_handler(params)
25
+ return if params['git'].blank?
26
+
27
+ version = params['version'] || 'master'
28
+ # TODO: Extract RemoteConfig git_folder in IncludeConfig to another place and use it here
29
+ # Maybe create a optional git_folder attribute and fill it in here?
30
+ git_folder = "#{params['git'].split('/').last.chomp('.git')}@#{version}"
31
+
32
+ # TODO: Use RemoteConfigRepository
33
+ remote_file = @remote_directory.join(git_folder).join(params['file']).expand_path
34
+
35
+ missing = !remote_file.exist?
36
+
37
+ params.merge(missing: missing, file: remote_file.to_s, base_directory: @remote_directory.expand_path.to_s)
38
+ end
39
+
40
+ def local_handler(params)
41
+ # TODO: Mark not existing files as missing and handle all the missing files
42
+ missing = !@base_directory.join(params['file']).exist?
43
+ params.merge(missing: missing, base_directory: @base_directory.expand_path.to_s)
44
+ end
45
+
46
+ def build_entity(params)
47
+ @entity_class.new(**params.symbolize_keys)
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Djin
4
+ class RemoteConfigRepository
5
+ attr_accessor :base_path
6
+
7
+ def initialize(remote_configs, base_path: Pathname.new('~/.djin/remote'), stderr: Djin.stderr)
8
+ @remote_configs = remote_configs
9
+ @base_path = base_path
10
+ @stderr = stderr
11
+ end
12
+
13
+ def add(remote_config)
14
+ remote_configs << remote_config
15
+ end
16
+
17
+ def find(**filters)
18
+ remote_configs.select do |remote_config|
19
+ filters.reduce(true) do |memo, (filter_key, filter_value)|
20
+ memo && remote_config.public_send(filter_key) == filter_value
21
+ end
22
+ end
23
+ end
24
+
25
+ def fetch_all
26
+ remote_configs.each do |rc|
27
+ git_folder = base_path.join(rc.folder_name).expand_path
28
+
29
+ # TODO: Extract STDEER Output, maybe publishing events and subscribing a observer for the logs.
30
+ stderr.puts "Remote Path: #{base_path.expand_path}"
31
+ git_repo = rc.missing? ? clone_repo(git_folder, rc) : fetch_repo(git_folder, rc)
32
+
33
+ stderr.puts "Checking out to '#{rc.version}'"
34
+ git_repo.checkout(rc.version)
35
+ git_repo.pull
36
+ end
37
+ end
38
+
39
+ def clear
40
+ remote_configs.each do |rc|
41
+ git_folder = base_path.join(rc.folder_name)
42
+
43
+ stderr.puts "Removing #{rc.folder_name} repository..."
44
+ `rm -rf #{git_folder}`
45
+ end
46
+ end
47
+
48
+ def clear_all
49
+ remove_remote_folder
50
+ end
51
+
52
+ def remote_configs
53
+ @remote_configs ||= []
54
+ end
55
+
56
+ private
57
+
58
+ attr_accessor :stderr
59
+
60
+ def remove_remote_folder
61
+ stderr.puts "Removing #{base_path}..."
62
+ `rm -rf #{base_path}`
63
+ end
64
+
65
+ def clone_repo(git_folder, remote_config)
66
+ stderr.puts "Missing #{remote_config.folder_name} repository, cloning in #{git_folder}"
67
+ Git.clone(remote_config.git, git_folder, progress: true)
68
+ end
69
+
70
+ def fetch_repo(git_folder, remote_config)
71
+ stderr.puts "#{remote_config.git} repository already cloned, fetching..."
72
+ git_repo = Git.open(git_folder)
73
+ git_repo.fetch
74
+
75
+ git_repo
76
+ end
77
+ end
78
+ end
@@ -22,6 +22,10 @@ module Djin
22
22
  opts.on('-h', '--help') do
23
23
  throw :root_cli_exit
24
24
  end
25
+
26
+ opts.on('--all') do
27
+ throw :root_cli_exit
28
+ end
25
29
  end.parse(args)
26
30
  end
27
31
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Djin
4
- VERSION = '0.10.0'
4
+ VERSION = '0.11.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: djin
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.0
4
+ version: 0.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Carlos Atkinson
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-10-09 00:00:00.000000000 Z
11
+ date: 2021-01-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dry-cli
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: 1.5.1
69
+ - !ruby/object:Gem::Dependency
70
+ name: git
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 1.8.1
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 1.8.1
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: mustache
71
85
  requirement: !ruby/object:Gem::Requirement
@@ -164,6 +178,20 @@ dependencies:
164
178
  - - ">="
165
179
  - !ruby/object:Gem::Version
166
180
  version: '0'
181
+ - !ruby/object:Gem::Dependency
182
+ name: simplecov
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - "~>"
186
+ - !ruby/object:Gem::Version
187
+ version: 0.17.0
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - "~>"
193
+ - !ruby/object:Gem::Version
194
+ version: 0.17.0
167
195
  description:
168
196
  email:
169
197
  - carlos.atks@gmail.com
@@ -174,6 +202,7 @@ extra_rdoc_files: []
174
202
  files:
175
203
  - ".github/workflows/ruby.yml"
176
204
  - ".gitignore"
205
+ - ".irbrc"
177
206
  - ".rspec"
178
207
  - ".rubocop.yml"
179
208
  - ".rubocop_todo.yml"
@@ -191,26 +220,33 @@ files:
191
220
  - djin.gemspec
192
221
  - djin.yml
193
222
  - docker-compose.yml
223
+ - docker-entrypoint.sh
194
224
  - examples/djin.yml
195
225
  - examples/djin_lib/test.yml
196
226
  - examples/local_tasks/.djin/server_tasks.yml
197
227
  - examples/local_tasks/djin.yml
228
+ - examples/remote_tasks/djin.yml
198
229
  - exe/djin
199
230
  - lib/djin.rb
200
231
  - lib/djin/cli.rb
201
232
  - lib/djin/config_loader.rb
202
- - lib/djin/entities/file_config.rb
233
+ - lib/djin/entities/include_config.rb
234
+ - lib/djin/entities/include_config/base.rb
235
+ - lib/djin/entities/include_config/local.rb
236
+ - lib/djin/entities/main_config.rb
203
237
  - lib/djin/entities/task.rb
204
238
  - lib/djin/entities/types.rb
205
239
  - lib/djin/executor.rb
206
240
  - lib/djin/extensions/hash_extensions.rb
207
241
  - lib/djin/extensions/object_extensions.rb
242
+ - lib/djin/include_resolver.rb
208
243
  - lib/djin/interpreter.rb
209
244
  - lib/djin/interpreter/base_command_builder.rb
210
245
  - lib/djin/interpreter/docker_command_builder.rb
211
246
  - lib/djin/interpreter/docker_compose_command_builder.rb
212
247
  - lib/djin/interpreter/local_command_builder.rb
213
248
  - lib/djin/memory_cache.rb
249
+ - lib/djin/repositories/remote_config_repository.rb
214
250
  - lib/djin/repositories/task_repository.rb
215
251
  - lib/djin/root_cli_parser.rb
216
252
  - lib/djin/task_contract.rb
@@ -234,8 +270,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
234
270
  - !ruby/object:Gem::Version
235
271
  version: '0'
236
272
  requirements: []
237
- rubyforge_project:
238
- rubygems_version: 2.7.6
273
+ rubygems_version: 3.0.3
239
274
  signing_key:
240
275
  specification_version: 4
241
276
  summary: djin is a make-like utility for docker containers