ddsl 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../base'
4
+
5
+ module DDSL
6
+ module Command
7
+ module DockerCompose
8
+ class Run < Base
9
+ executable 'docker-compose'
10
+ command 'run'
11
+
12
+ executable_options do
13
+ accept_keys([
14
+ 'file'
15
+ ])
16
+
17
+ optionize_keys
18
+ expand_options
19
+ end
20
+
21
+ options do
22
+ accept_keys(%w[
23
+ rm envs volumes ports service_ports user workdir no_deps detach
24
+ ])
25
+
26
+ rename_keys(
27
+ 'envs' => 'e',
28
+ 'volumes' => 'volume',
29
+ 'ports' => 'publish'
30
+ )
31
+
32
+ optionize_keys
33
+ expand_options
34
+ end
35
+
36
+ arguments do
37
+ accept_keys(%w[service cmd])
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DDSL
4
+ module Command
5
+ module DSL
6
+ def self.included(base)
7
+ base.send :include, InstanceMethods
8
+ base.extend ClassMethods
9
+ end
10
+
11
+ module ClassMethods
12
+ def executable_options(&block)
13
+ singleton_class.class_eval do
14
+ @executable_options = Transproc::Transformer[Functions].define(&block)
15
+ end
16
+ end
17
+
18
+ def options(&block)
19
+ singleton_class.class_eval do
20
+ @options = Transproc::Transformer[Functions].define(&block)
21
+ end
22
+ end
23
+
24
+ def arguments(&block)
25
+ singleton_class.class_eval do
26
+ @arguments = Transproc::Transformer[Functions].define(&block)
27
+ end
28
+ end
29
+
30
+ def executable(name)
31
+ singleton_class.class_eval do
32
+ @executable = name
33
+ end
34
+ end
35
+
36
+ def command(name)
37
+ singleton_class.class_eval do
38
+ @command = name
39
+ end
40
+ end
41
+
42
+ def after(&block)
43
+ singleton_class.class_eval do
44
+ @after_block = block
45
+ end
46
+ end
47
+ end
48
+
49
+ module InstanceMethods
50
+ DEFAULT_TRANSFORMER = ->(_x) { [] }
51
+
52
+ def executable_options
53
+ search_ancestor_tree_variable(:@executable_options) || DEFAULT_TRANSFORMER
54
+ end
55
+
56
+ def options
57
+ search_ancestor_tree_variable(:@options) || DEFAULT_TRANSFORMER
58
+ end
59
+
60
+ def arguments
61
+ search_ancestor_tree_variable(:@arguments) || DEFAULT_TRANSFORMER
62
+ end
63
+
64
+ def after_block
65
+ search_ancestor_tree_variable(:@after_block)
66
+ end
67
+
68
+ def executable
69
+ search_ancestor_tree_variable(:@executable)
70
+ end
71
+
72
+ def command
73
+ search_ancestor_tree_variable(:@command)
74
+ end
75
+
76
+ private def search_ancestor_tree_variable(var_name)
77
+ self.class.ancestors.each do |clazz|
78
+ if clazz.singleton_class.instance_variable_defined?(var_name.to_sym)
79
+ return clazz.singleton_class.instance_variable_get(var_name.to_sym)
80
+ end
81
+ end
82
+
83
+ nil
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './command/docker/build'
4
+ require_relative './command/docker/push'
5
+ require_relative './command/docker/run'
6
+ require_relative './command/docker_compose/run'
7
+ require_relative './command/docker_compose/build'
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'transproc/all'
4
+
5
+ module DDSL
6
+ module Functions
7
+ extend Transproc::Registry
8
+
9
+ import Transproc::HashTransformations
10
+ import Transproc::ArrayTransformations
11
+
12
+ def self.idempotency(value)
13
+ value
14
+ end
15
+
16
+ def self.map_keys_matching(hash, regex, func)
17
+ hash.map do |key, value|
18
+ if regex&.match(key)
19
+ [func.call(key), value]
20
+ else
21
+ [key, value]
22
+ end
23
+ end.to_h
24
+ end
25
+
26
+ def self.optionize_keys(hash)
27
+ [
28
+ [:map_keys_matching, /\w+_\w+/, ->(x) { x.tr('_', '-') }],
29
+ [:map_keys_matching, /^.{2,}$/, ->(x) { '--' + x }],
30
+ [:map_keys_matching, /^.{1}$/, ->(x) { '-' + x }]
31
+ ].inject(Functions[:idempotency]) do |pipe, args|
32
+ pipe >> Functions[*args]
33
+ end.call(hash)
34
+ end
35
+
36
+ def self.expand_options(hash)
37
+ hash.map do |key, value|
38
+ case value
39
+ when String
40
+ [key, value]
41
+ when TrueClass, FalseClass
42
+ key
43
+ when Array
44
+ value.map { |v| [key, v] }
45
+ when Hash
46
+ value.map { |sub_key, v| [key, "#{sub_key}=#{v}"] }
47
+ end
48
+ end.flatten
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'yaml'
5
+ require_relative './schema_validator'
6
+
7
+ module DDSL
8
+ class Parser
9
+ class UnsupportedFileFormatError < StandardError; end
10
+
11
+ #
12
+ # Parse configuration file in given path
13
+ #
14
+ # @param [String] file_path
15
+ #
16
+ # @return [Hash]
17
+ #
18
+ def parse(file_path)
19
+ format = File.extname(file_path)
20
+ parsed_config = case format
21
+ when '.json'
22
+ JSON.parse(File.read(file_path))
23
+ when '.yml'
24
+ YAML.load_file(file_path)
25
+ else
26
+ raise UnsupportedFileFormatError, "format #{format} is unsupported"
27
+ end
28
+
29
+ DDSL::SchemaValidator.new.validate!(parsed_config)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,161 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rubocop:disable Metrics/ModuleLength
4
+ module DDSL
5
+ SCHEMA_VERSION = :draft4
6
+ SCHEMA = {
7
+ type: :object,
8
+ required: [:version],
9
+ additionalProperties: false,
10
+ properties: {
11
+ version: {
12
+ type: :integer,
13
+ enum: [1]
14
+ },
15
+ builds: {
16
+ type: :array,
17
+ items: { '$ref': '#/definitions/build' }
18
+ },
19
+ runs: {
20
+ type: :array,
21
+ items: { '$ref': '#/definitions/run' }
22
+ }
23
+ },
24
+ definitions: {
25
+ build: {
26
+ type: :object,
27
+ required: [:name],
28
+ oneOf: [
29
+ { "$ref": '#/definitions/build_docker_options' },
30
+ { "$ref": '#/definitions/build_docker_compose_options' }
31
+ ],
32
+ properties: {
33
+ name: {
34
+ type: :string
35
+ }
36
+ }
37
+ },
38
+ run: {
39
+ type: :object,
40
+ oneOf: [
41
+ { "$ref": '#/definitions/run_docker_options' },
42
+ { "$ref": '#/definitions/run_docker_compose_options' }
43
+ ]
44
+ },
45
+ build_docker_options: {
46
+ type: :object,
47
+ required: %i[type context file],
48
+ properties: {
49
+ type: {
50
+ type: :string,
51
+ enum: [:docker]
52
+ },
53
+ context: { type: :string },
54
+ file: { type: :string },
55
+ build_args: {
56
+ type: :object,
57
+ additionalProperties: {
58
+ type: :string
59
+ }
60
+ },
61
+ tags: {
62
+ type: :array,
63
+ items: { type: :string }
64
+ },
65
+ labels: {
66
+ type: :array,
67
+ items: { type: :string }
68
+ },
69
+ cache_from: {
70
+ type: :array,
71
+ items: { type: :string }
72
+ },
73
+ push: { type: :boolean }
74
+ }
75
+ },
76
+ build_docker_compose_options: {
77
+ type: :object,
78
+ required: %i[type],
79
+ properties: {
80
+ type: {
81
+ type: :string,
82
+ enum: [:'docker-compose']
83
+ },
84
+ file: { type: :string },
85
+ service: { type: :string },
86
+ no_cache: { type: :boolean },
87
+ pull: { type: :boolean },
88
+ force_rm: { type: :boolean },
89
+ compress: { type: :boolean },
90
+ parallel: { type: :boolean },
91
+ memory: { type: :string },
92
+ build_args: {
93
+ type: :object,
94
+ additionalProperties: {
95
+ type: :string
96
+ }
97
+ }
98
+ }
99
+ },
100
+ run_docker_options: {
101
+ type: :object,
102
+ required: %i[type image],
103
+ properties: {
104
+ type: {
105
+ type: :string,
106
+ enum: [:docker]
107
+ },
108
+ image: { type: :string },
109
+ cmd: { type: :string },
110
+ user: { type: :string },
111
+ workdir: { type: :string },
112
+ rm: { type: :boolean },
113
+ env: {
114
+ type: :object,
115
+ additionalProperties: {
116
+ type: :string
117
+ }
118
+ },
119
+ volumes: {
120
+ type: :object,
121
+ additionalProperties: {
122
+ type: :string
123
+ }
124
+ }
125
+ }
126
+ },
127
+ run_docker_compose_options: {
128
+ type: :object,
129
+ required: %i[type service],
130
+ properties: {
131
+ type: {
132
+ type: :string,
133
+ enum: [:'docker-compose']
134
+ },
135
+ service: { type: :string },
136
+ file: { type: :string },
137
+ cmd: { type: :string },
138
+ rm: { type: :boolean },
139
+ user: { type: :string },
140
+ workdir: { type: :string },
141
+ service_ports: { type: :string },
142
+ detach: { type: :boolean },
143
+ no_deps: { type: :boolean },
144
+ env: {
145
+ type: :object,
146
+ additionalProperties: {
147
+ type: :string
148
+ }
149
+ },
150
+ volumes: {
151
+ type: :object,
152
+ additionalProperties: {
153
+ type: :string
154
+ }
155
+ }
156
+ }
157
+ }
158
+ }
159
+ }.freeze
160
+ end
161
+ # rubocop:enable Metrics/ModuleLength
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json-schema'
4
+ require_relative './schema'
5
+
6
+ module DDSL
7
+ class SchemaValidator
8
+ class InvalidError < StandardError; end
9
+
10
+ #
11
+ # Validate given data against DDSL schema. If data is not compliant with the schema,
12
+ # InvalidError will be raised
13
+ #
14
+ # @param [Hash] data
15
+ #
16
+ # @return [Hash] same data that was given
17
+ #
18
+ def validate!(data)
19
+ errors = JSON::Validator.fully_validate(DDSL::SCHEMA, data, version: DDSL::SCHEMA_VERSION)
20
+
21
+ raise InvalidError, errors.join('\n') if errors.count.positive?
22
+
23
+ data
24
+ end
25
+ end
26
+ end
data/lib/ddsl/shell.rb ADDED
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DDSL
4
+ class Shell
5
+ class ExitStatusError < StandardError; end
6
+
7
+ #
8
+ # Run given list of arguments in a shell.
9
+ #
10
+ # Note: $stdin, $stout and $stderr will be forwarded
11
+ #
12
+ # @raise [ExitStatusError] if program fails
13
+ #
14
+ # @param [Array<String>] argv
15
+ #
16
+ # @return [void]
17
+ #
18
+ def call!(argv)
19
+ raise ExitStatusError unless invoke(argv)
20
+ end
21
+
22
+ #
23
+ # Invoke given argument list in a subshell
24
+ #
25
+ # @param [Array<String>] argv
26
+ #
27
+ # @return [Bool] true if exit status of the program is 0, no otherwise
28
+ #
29
+ def invoke(argv)
30
+ system(
31
+ argv.join(' ')
32
+ )
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DDSL
4
+ class VariableInjector
5
+ REGEX = /\$\w+/.freeze
6
+
7
+ attr_reader :var_map
8
+
9
+ def initialize(var_map)
10
+ @var_map = var_map
11
+ end
12
+
13
+ def inject(data)
14
+ traverse_hash(data)
15
+ end
16
+
17
+ private def traverse_hash(hash)
18
+ hash.map { |key, value| [key, process_value(value)] }.to_h
19
+ end
20
+
21
+ private def traverse_array(array)
22
+ array.map { |value| process_value(value) }
23
+ end
24
+
25
+ private def process_value(value)
26
+ case value
27
+ when Array
28
+ traverse_array(value)
29
+ when Hash
30
+ traverse_hash(value)
31
+ when String
32
+ process_content(value)
33
+ else
34
+ value
35
+ end
36
+ end
37
+
38
+ private def process_content(content)
39
+ content.scan(REGEX).inject(content) do |replaced, match|
40
+ replace_content(replaced, match)
41
+ end
42
+ end
43
+
44
+ private def replace_content(content, match)
45
+ key = var_map_key(match)
46
+
47
+ var_map.key?(key) ? content.gsub(match, var_map[key]) : content
48
+ end
49
+
50
+ private def var_map_key(match)
51
+ match.delete('$')
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DDSL
4
+ VERSION = '0.0.1'
5
+ end
data/lib/ddsl.rb ADDED
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './ddsl/functions'
4
+ require_relative './ddsl/cli'
5
+ require_relative './ddsl/parser'
6
+ require_relative './ddsl/command'
7
+
8
+ module DDSL
9
+ end
metadata ADDED
@@ -0,0 +1,160 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ddsl
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Martin Fernandez
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-11-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: clamp
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: json-schema
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2'
41
+ - !ruby/object:Gem::Dependency
42
+ name: transproc
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.60'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.60'
83
+ - !ruby/object:Gem::Dependency
84
+ name: simplecov
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.16'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.16'
97
+ description:
98
+ email:
99
+ - fmartin91@gmail.com
100
+ executables:
101
+ - ddsl
102
+ extensions: []
103
+ extra_rdoc_files: []
104
+ files:
105
+ - ".circleci/config.yml"
106
+ - ".ddsl.yml"
107
+ - ".gitignore"
108
+ - ".rspec"
109
+ - ".rubocop.yml"
110
+ - Gemfile
111
+ - Gemfile.lock
112
+ - README.md
113
+ - bin/ddsl
114
+ - bin/dev
115
+ - ddsl.gemspec
116
+ - docker/Dockerfile
117
+ - docker/Dockerfile.dev
118
+ - docker/docker-compose.yml
119
+ - lib/ddsl.rb
120
+ - lib/ddsl/cli.rb
121
+ - lib/ddsl/command.rb
122
+ - lib/ddsl/command/base.rb
123
+ - lib/ddsl/command/docker/build.rb
124
+ - lib/ddsl/command/docker/push.rb
125
+ - lib/ddsl/command/docker/run.rb
126
+ - lib/ddsl/command/docker_compose/build.rb
127
+ - lib/ddsl/command/docker_compose/run.rb
128
+ - lib/ddsl/command/dsl.rb
129
+ - lib/ddsl/functions.rb
130
+ - lib/ddsl/parser.rb
131
+ - lib/ddsl/schema.rb
132
+ - lib/ddsl/schema_validator.rb
133
+ - lib/ddsl/shell.rb
134
+ - lib/ddsl/variable_injector.rb
135
+ - lib/ddsl/version.rb
136
+ homepage: https://github.com/bilby91/ddsl
137
+ licenses:
138
+ - MIT
139
+ metadata: {}
140
+ post_install_message:
141
+ rdoc_options: []
142
+ require_paths:
143
+ - lib
144
+ required_ruby_version: !ruby/object:Gem::Requirement
145
+ requirements:
146
+ - - ">="
147
+ - !ruby/object:Gem::Version
148
+ version: '0'
149
+ required_rubygems_version: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - ">="
152
+ - !ruby/object:Gem::Version
153
+ version: '0'
154
+ requirements: []
155
+ rubyforge_project:
156
+ rubygems_version: 2.5.2.3
157
+ signing_key:
158
+ specification_version: 4
159
+ summary: Docker Declarative Specific Language
160
+ test_files: []