ddsl 0.0.1

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.
@@ -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: []