rudder 0.1.0
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 +7 -0
- data/.gitignore +14 -0
- data/.rspec +3 -0
- data/.rubocop.yml +16 -0
- data/.travis.yml +5 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +53 -0
- data/LICENSE +21 -0
- data/README.md +101 -0
- data/Rakefile +24 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/docker-compose.yml +26 -0
- data/docs/Rudder.html +382 -0
- data/docs/Rudder/DSL.html +456 -0
- data/docs/Rudder/DSL/Component.html +645 -0
- data/docs/Rudder/DSL/Group.html +733 -0
- data/docs/Rudder/DSL/Job.html +487 -0
- data/docs/Rudder/DSL/Pipeline.html +1869 -0
- data/docs/Rudder/DSL/Resource.html +584 -0
- data/docs/Rudder/DSL/ResourceType.html +223 -0
- data/docs/Rudder/DSL/Util.html +324 -0
- data/docs/_index.html +211 -0
- data/docs/class_list.html +51 -0
- data/docs/css/common.css +1 -0
- data/docs/css/full_list.css +58 -0
- data/docs/css/style.css +496 -0
- data/docs/file.README.html +191 -0
- data/docs/file_list.html +56 -0
- data/docs/frames.html +17 -0
- data/docs/index.html +191 -0
- data/docs/js/app.js +303 -0
- data/docs/js/full_list.js +216 -0
- data/docs/js/jquery.js +4 -0
- data/docs/method_list.html +347 -0
- data/docs/top-level-namespace.html +110 -0
- data/examples/README.md +6 -0
- data/examples/groups/README.md +12 -0
- data/examples/groups/groups_pipeline.rb +34 -0
- data/examples/groups/jobs/bash_stuff/date.rb +16 -0
- data/examples/groups/jobs/bash_stuff/hello.rb +17 -0
- data/examples/groups/jobs/git_stuff/log.rb +22 -0
- data/examples/groups/jobs/git_stuff/ls.rb +21 -0
- data/examples/groups/resources/rudder_git.rb +6 -0
- data/examples/groups/resources/timer.rb +5 -0
- data/examples/hello_world_pipeline.rb +44 -0
- data/examples/images/groups/groups_all.png +0 -0
- data/examples/images/groups/groups_bash_stuff.png +0 -0
- data/examples/images/groups/groups_git_stuff.png +0 -0
- data/examples/images/hello_world.png +0 -0
- data/examples/images/includes/includes.png +0 -0
- data/examples/images/shared/borrows.png +0 -0
- data/examples/images/shared/common.png +0 -0
- data/examples/images/shared/wrapper.png +0 -0
- data/examples/includes/README.md +7 -0
- data/examples/includes/includes_pipeline.rb +6 -0
- data/examples/includes/jobs/log.rb +15 -0
- data/examples/includes/resources/rudder_git.rb +5 -0
- data/examples/shared/README.md +21 -0
- data/examples/shared/borrows_pipeline.rb +31 -0
- data/examples/shared/common_pipeline.rb +34 -0
- data/examples/shared/wrapper_pipeline.rb +51 -0
- data/exe/rudder +52 -0
- data/lib/rudder.rb +35 -0
- data/lib/rudder/dsl.rb +54 -0
- data/lib/rudder/dsl/component.rb +79 -0
- data/lib/rudder/dsl/group.rb +110 -0
- data/lib/rudder/dsl/job.rb +65 -0
- data/lib/rudder/dsl/pipeline.rb +374 -0
- data/lib/rudder/dsl/resource.rb +82 -0
- data/lib/rudder/dsl/resource_type.rb +45 -0
- data/lib/rudder/dsl/util.rb +48 -0
- data/lib/rudder/version.rb +5 -0
- data/lib/tasks/docker.rb +11 -0
- data/rudder.gemspec +29 -0
- metadata +189 -0
data/lib/rudder.rb
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'yaml'
|
|
4
|
+
|
|
5
|
+
require_relative 'rudder/dsl.rb'
|
|
6
|
+
require_relative 'rudder/version.rb'
|
|
7
|
+
|
|
8
|
+
##
|
|
9
|
+
# Methods to compile Rudder definitions
|
|
10
|
+
# to Concourse Pipeline definitions
|
|
11
|
+
#
|
|
12
|
+
module Rudder
|
|
13
|
+
##
|
|
14
|
+
# Compiles a {Rudder::DSL::Pipeline} definition from +path+
|
|
15
|
+
# to a {Hash}
|
|
16
|
+
#
|
|
17
|
+
# @param path [String] the path to the +Rudder+ definition
|
|
18
|
+
# @return [Hash] Concourse YAML friendly hash
|
|
19
|
+
#
|
|
20
|
+
def self.compile(path)
|
|
21
|
+
Rudder::DSL.eval_from_file(path).to_h
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
##
|
|
25
|
+
# Dumps a {Rudder::DSL::Pipeline} or Pipeline {Hash}
|
|
26
|
+
# to the provided file handle +output+
|
|
27
|
+
#
|
|
28
|
+
# @param pipeline {Rudder::DSL::Pipeline} definition. Assumed to be evaluated.
|
|
29
|
+
# @param output [File] handle to dump YAML to
|
|
30
|
+
# @return [nil]
|
|
31
|
+
#
|
|
32
|
+
def self.dump(pipeline, output)
|
|
33
|
+
output.puts(YAML.dump(pipeline.to_h))
|
|
34
|
+
end
|
|
35
|
+
end
|
data/lib/rudder/dsl.rb
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'dsl/pipeline.rb'
|
|
4
|
+
|
|
5
|
+
module Rudder
|
|
6
|
+
##
|
|
7
|
+
# DSL for configuring and manipulating Concourse Pipelines
|
|
8
|
+
#
|
|
9
|
+
# The building blocks of the DSL are:
|
|
10
|
+
# - {Rudder::DSL::Pipeline}, the top level definition containg all other definitions.
|
|
11
|
+
# See {https://concourse-ci.org/pipelines.html Concourse Pipeline}
|
|
12
|
+
# - {Rudder::DSL::Resource}, representing inputs and outputs of jobs.
|
|
13
|
+
# See {https://concourse-ci.org/resources.html Concourse Resource}
|
|
14
|
+
# - {Rudder::DSL::Job}, units of work.
|
|
15
|
+
# See {https://concourse-ci.org/jobs.html Concourse Job}
|
|
16
|
+
# - {Rudder::DSL::ResourceType}, defines how a {Rudder::DSL::Resource} operates.
|
|
17
|
+
# See {https://concourse-ci.org/resource-types.html Concourse Resource Type}
|
|
18
|
+
# - {Rudder::DSL::Group}, logically groups together Concourse Jobs in the UI.
|
|
19
|
+
# See {https://concourse-ci.org/pipeline-groups.html Concourse Grouping Jobs}
|
|
20
|
+
#
|
|
21
|
+
module DSL
|
|
22
|
+
##
|
|
23
|
+
# Entry to the DSL. Creates a new pipeline
|
|
24
|
+
# instance to evaluate user defined pipelines
|
|
25
|
+
#
|
|
26
|
+
# @return [Rudder::DSL::Pipeline] new, and unevaluated
|
|
27
|
+
#
|
|
28
|
+
def self.pipeline(*args, **kwargs)
|
|
29
|
+
Rudder::DSL::Pipeline.new(*args, **kwargs)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
##
|
|
33
|
+
# Load a pipeline from a definition file
|
|
34
|
+
# at the +path+
|
|
35
|
+
#
|
|
36
|
+
# @param path [String] to the {Rudder::DSL::Pipeline} definition
|
|
37
|
+
# @return [Rudder::DSL::Pipeline] from +path+, unevaluated
|
|
38
|
+
#
|
|
39
|
+
def self.from_file(path)
|
|
40
|
+
Rudder::DSL::Pipeline.new path
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
##
|
|
44
|
+
# Load and evaluate a pipeline from a definition file
|
|
45
|
+
# at the +path+
|
|
46
|
+
#
|
|
47
|
+
# @param path [String] to the {Rudder::DSL::Pipeline} definition
|
|
48
|
+
# @return [Rudder::DSL::Pipeline] from +path+, fully evaluated
|
|
49
|
+
#
|
|
50
|
+
def self.eval_from_file(path)
|
|
51
|
+
Rudder::DSL::Pipeline.new(path).eval
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'util'
|
|
4
|
+
|
|
5
|
+
module Rudder
|
|
6
|
+
module DSL
|
|
7
|
+
##
|
|
8
|
+
# Base class for other pipeline sub components to extend
|
|
9
|
+
#
|
|
10
|
+
# Not intended for public usage and subject to change.
|
|
11
|
+
#
|
|
12
|
+
class Component
|
|
13
|
+
include Rudder::DSL::Util
|
|
14
|
+
|
|
15
|
+
##
|
|
16
|
+
# Required method for all subclasses to implement.
|
|
17
|
+
#
|
|
18
|
+
# @raise [RuntimeError] if not implemented
|
|
19
|
+
def _inner_hash
|
|
20
|
+
raise 'Implement this in a subclass'
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def to_h
|
|
24
|
+
_deep_to_h(_inner_hash)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
##
|
|
28
|
+
# Populates the inner hash with missing method names and
|
|
29
|
+
# their arguments
|
|
30
|
+
#
|
|
31
|
+
# @param method [Symbol] top level key of this {Rudder::DSL::Component}
|
|
32
|
+
# Corresponds to the highest level key in a concourse
|
|
33
|
+
# component.
|
|
34
|
+
#
|
|
35
|
+
# @param *args entire arg collection is assigned to the value of the key
|
|
36
|
+
# +method+. Note: if only 1 argument is provided it is
|
|
37
|
+
# unwrapped from the +args+ {Array}.
|
|
38
|
+
# @param **kwargs treated as the last value of +args+ if provided
|
|
39
|
+
# @return the value related to +method+ if no arguments or keyword
|
|
40
|
+
# arguments are provided. Otherwise, +nil+.
|
|
41
|
+
#
|
|
42
|
+
# rubocop:disable Style/MethodMissingSuper
|
|
43
|
+
def method_missing(method, *args, **kwargs)
|
|
44
|
+
# Accessing inner hash as attribute
|
|
45
|
+
return _inner_hash[method] if args.empty? && kwargs.empty?
|
|
46
|
+
|
|
47
|
+
# Ruby treats dictionaries passed as the last argument as keyword dicts
|
|
48
|
+
# (specifically when they use symbols as keys). Just smashing
|
|
49
|
+
# these into args so we don't miss anything
|
|
50
|
+
args << kwargs unless kwargs.empty?
|
|
51
|
+
raise "Argument list missing from [#{method}]" if args.empty?
|
|
52
|
+
|
|
53
|
+
# If a single arg is given then assume this field is scalar,
|
|
54
|
+
# otherwise assume its a list that needs all args
|
|
55
|
+
formatted_args = args.size == 1 ? args[0] : args
|
|
56
|
+
_inner_hash[method] = formatted_args
|
|
57
|
+
end
|
|
58
|
+
# rubocop:enable Style/MethodMissingSuper
|
|
59
|
+
|
|
60
|
+
##
|
|
61
|
+
# Components respond to everything by default
|
|
62
|
+
#
|
|
63
|
+
# @return true
|
|
64
|
+
#
|
|
65
|
+
def respond_to?(_name, _include_all = true)
|
|
66
|
+
true
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
##
|
|
70
|
+
# Components respond to missing by default
|
|
71
|
+
#
|
|
72
|
+
# @return true
|
|
73
|
+
#
|
|
74
|
+
def respond_to_missing?(*_)
|
|
75
|
+
true
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'set'
|
|
4
|
+
|
|
5
|
+
module Rudder
|
|
6
|
+
module DSL
|
|
7
|
+
##
|
|
8
|
+
# Concourse group. Logically groups together Concourse
|
|
9
|
+
# jobs in the UI.
|
|
10
|
+
#
|
|
11
|
+
# == DSL Usage:
|
|
12
|
+
#
|
|
13
|
+
# {Rudder::DSL::Group}'s are the simplest element of any
|
|
14
|
+
# Concourse Pipeline, defined by only a name and a non-empty
|
|
15
|
+
# list of jobs.
|
|
16
|
+
#
|
|
17
|
+
#
|
|
18
|
+
# @example
|
|
19
|
+
# # Name's are typically set during initialization
|
|
20
|
+
# group :my_awesome_group do # => Name is set to :my_awesome_group
|
|
21
|
+
#
|
|
22
|
+
# # but the name may be changed post construction as well
|
|
23
|
+
# group :not_the_best_name do # => Name initialized to :not_the_best_name
|
|
24
|
+
# name :the_best_name
|
|
25
|
+
# end # => but is set to :the_best_name after the block is executed
|
|
26
|
+
#
|
|
27
|
+
# @example
|
|
28
|
+
# # Job's are always set post construction. They can be added
|
|
29
|
+
# # individually:
|
|
30
|
+
# group :my_awesome_group do
|
|
31
|
+
# job :some_prereq
|
|
32
|
+
# job :my_awesome_work
|
|
33
|
+
# end # => group.jobs = [:some_prereq, :my_awesome_work]
|
|
34
|
+
#
|
|
35
|
+
# # and they can be added in collections
|
|
36
|
+
# group :my_awesome_group do
|
|
37
|
+
# jobs :a_job, :and_another, :and_one_more
|
|
38
|
+
# end # => group.jobs = [:a_job, :and_another, :and_one_more]
|
|
39
|
+
#
|
|
40
|
+
#
|
|
41
|
+
class Group
|
|
42
|
+
##
|
|
43
|
+
# All {Rudder::DSL::Group}'s require
|
|
44
|
+
#
|
|
45
|
+
# - Name of the group
|
|
46
|
+
# - A list of jobs in the group
|
|
47
|
+
#
|
|
48
|
+
# Jobs are added after initilization.
|
|
49
|
+
#
|
|
50
|
+
# @param [String, Symbol] the non-+nil+ name of this group
|
|
51
|
+
# @raise [ArgumentError] if +name+ is +nil
|
|
52
|
+
#
|
|
53
|
+
def initialize(name)
|
|
54
|
+
raise super.ArgumentError 'Name cannot be nil' if name.nil?
|
|
55
|
+
|
|
56
|
+
@name = name
|
|
57
|
+
@jobs = Set.new
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
##
|
|
61
|
+
# Replace's this {Rudder::DSL::Group}'s name unless
|
|
62
|
+
# +name+ is nil.
|
|
63
|
+
#
|
|
64
|
+
# @param name [String, Symbol] the new name to use. Ignored if +nil+.
|
|
65
|
+
# @return [String, Symbol] the latest component name.
|
|
66
|
+
#
|
|
67
|
+
def name(name = nil)
|
|
68
|
+
@name = name unless name.nil?
|
|
69
|
+
@name
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
##
|
|
73
|
+
# Add a single job to the jobs list
|
|
74
|
+
#
|
|
75
|
+
# @param job_name [String, Symbol] to add to the jobs list
|
|
76
|
+
# @return [Set<String, Symbol>] the latest list of jobs
|
|
77
|
+
#
|
|
78
|
+
def job(job_name)
|
|
79
|
+
jobs job_name
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
##
|
|
83
|
+
# Adds all the jobs to the jobs list
|
|
84
|
+
#
|
|
85
|
+
# @param *args [*String, *Symbol] collection of jobs to add to this
|
|
86
|
+
# {Rudder::DSL::Group}
|
|
87
|
+
# @return [Set<String, Symbol>] the latest list of jobs
|
|
88
|
+
#
|
|
89
|
+
def jobs(*args)
|
|
90
|
+
args.each { |arg| @jobs << arg }
|
|
91
|
+
@jobs
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
##
|
|
95
|
+
# @return [Hash] YAML friendly +Hash+ representation of this resource
|
|
96
|
+
#
|
|
97
|
+
# @raise [RuntimeError] if +name+ is +nil+ or +jobs+ is empty
|
|
98
|
+
#
|
|
99
|
+
def to_h
|
|
100
|
+
raise 'Groups require a name' if @name.nil?
|
|
101
|
+
raise 'Groups require at least 1 job' if @jobs.empty?
|
|
102
|
+
|
|
103
|
+
{
|
|
104
|
+
'name' => @name.to_s,
|
|
105
|
+
'jobs' => @jobs.to_a.map(&:to_s)
|
|
106
|
+
}
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'component'
|
|
4
|
+
|
|
5
|
+
module Rudder
|
|
6
|
+
module DSL
|
|
7
|
+
##
|
|
8
|
+
# Concourse job
|
|
9
|
+
#
|
|
10
|
+
# Defines a plan of work that may share state
|
|
11
|
+
# in an explicit manner.
|
|
12
|
+
#
|
|
13
|
+
# == DSL Usage:
|
|
14
|
+
#
|
|
15
|
+
# {Rudder::DSL::Job} are defined by a +name+ and a +plan+ of work.
|
|
16
|
+
#
|
|
17
|
+
# @example
|
|
18
|
+
# # Name's are set during initializtion, and may not be nil
|
|
19
|
+
# job :awesome_job # => job.name = :awesome_job
|
|
20
|
+
#
|
|
21
|
+
# job nil # => Raises ArgumentError
|
|
22
|
+
#
|
|
23
|
+
# @example
|
|
24
|
+
# # The plan is set after construction
|
|
25
|
+
# job :awesome_job do
|
|
26
|
+
# plan << { get: :some_resource }
|
|
27
|
+
# plan << { get: :another_resource }
|
|
28
|
+
# end # => plan.source = [{get: :some_resource}, {get: :another_resource}]
|
|
29
|
+
#
|
|
30
|
+
class Job < Rudder::DSL::Component
|
|
31
|
+
##
|
|
32
|
+
# All Jobs require:
|
|
33
|
+
#
|
|
34
|
+
# - A name
|
|
35
|
+
# - A plan of work
|
|
36
|
+
#
|
|
37
|
+
# Plans are defined after initialization
|
|
38
|
+
#
|
|
39
|
+
# @param name [String, Symbol] name of this Concourse job. Must not be +nil+
|
|
40
|
+
# @raise [ArgumentError] when +name+ is nil
|
|
41
|
+
#
|
|
42
|
+
def initialize(name)
|
|
43
|
+
raise super.ArgumentError 'Name cannot be nil' if name.nil?
|
|
44
|
+
|
|
45
|
+
@job = { name: name, plan: [] }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def _inner_hash
|
|
49
|
+
@job
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
##
|
|
53
|
+
# @return [Hash] YAML friendly +Hash+ representation of this resource
|
|
54
|
+
#
|
|
55
|
+
# @raise [RuntimeError] if +name+ is +nil+ or +plan+ is empty
|
|
56
|
+
#
|
|
57
|
+
def to_h
|
|
58
|
+
raise 'Name must be set for Concourse Jobs' if @job[:name].nil?
|
|
59
|
+
raise 'Plan must be set for Concourse Jobs' if @job[:plan].empty?
|
|
60
|
+
|
|
61
|
+
super.to_h
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'group'
|
|
4
|
+
require_relative 'job'
|
|
5
|
+
require_relative 'resource'
|
|
6
|
+
require_relative 'resource_type'
|
|
7
|
+
require_relative 'util'
|
|
8
|
+
|
|
9
|
+
module Rudder
|
|
10
|
+
module DSL
|
|
11
|
+
##
|
|
12
|
+
# Concourse Pipeline. Main entry of the DSL. Evaluates
|
|
13
|
+
# user defined pipelines.
|
|
14
|
+
#
|
|
15
|
+
# == DSL Usage:
|
|
16
|
+
#
|
|
17
|
+
# {Rudder::DSL::Pipeline}'s are composed of various components:
|
|
18
|
+
#
|
|
19
|
+
# - {Rudder::DSL::Resource}: basic inputs and output of jobs.
|
|
20
|
+
# - {Rudder::DSL::Job}: basic computation unit of a pipeline
|
|
21
|
+
# - {Rudder::DSL::ResourceType}: custom resource definitions
|
|
22
|
+
# - {Rudder::DSL::Group}: logical grouping of jobs in the UI.
|
|
23
|
+
# Either every job is in a Group or no job is (hard Concourse
|
|
24
|
+
# requirement)
|
|
25
|
+
#
|
|
26
|
+
# === Adding Components
|
|
27
|
+
#
|
|
28
|
+
# Components are added to the Pipeline by component type, followed
|
|
29
|
+
# by name, optional arguments, then typically a block.
|
|
30
|
+
#
|
|
31
|
+
# @example Adding Components to Pipelines
|
|
32
|
+
# #
|
|
33
|
+
# # my_pipeline_definition.rb
|
|
34
|
+
# #
|
|
35
|
+
# resource :my_git_repo, :git do
|
|
36
|
+
# source[:uri] = 'https://github.com/my/repo.git'
|
|
37
|
+
# source[:branch] = :master
|
|
38
|
+
# end
|
|
39
|
+
#
|
|
40
|
+
# resource :daily, :time do
|
|
41
|
+
# source[:interval] = '24h'
|
|
42
|
+
#
|
|
43
|
+
# job :build_project do
|
|
44
|
+
# plan << [in_parallel: [{ get: :my_git_repo }, { get: :daily, trigger: true}]]
|
|
45
|
+
# build = { task: 'build my project', config: {
|
|
46
|
+
# platform: :linux,
|
|
47
|
+
# image_resource: { type: 'docker-image', source: { repository: 'busybox' } },
|
|
48
|
+
# run: { path: 'my_git_repo/build.sh' }
|
|
49
|
+
# }}
|
|
50
|
+
# plan << build
|
|
51
|
+
# end
|
|
52
|
+
#
|
|
53
|
+
#
|
|
54
|
+
# === Loading Other Pipelines
|
|
55
|
+
#
|
|
56
|
+
# {Rudder::DSL::Pipeline}'s can load other pipeline definitions
|
|
57
|
+
# using {Rudder::DSL::Pipeline#load}. This is a useful mechanism
|
|
58
|
+
# for abstracting out common subsections of pipelines, then
|
|
59
|
+
# merging them into larger pipelines.
|
|
60
|
+
#
|
|
61
|
+
# @example Loading / Importing Pipelines
|
|
62
|
+
# #
|
|
63
|
+
# # load_neighbor.rb
|
|
64
|
+
# #
|
|
65
|
+
# neighbor = load 'neighbor_pipeline.rb'
|
|
66
|
+
#
|
|
67
|
+
# # merge all the neighboring resources and jobs into this pipeline
|
|
68
|
+
# resources.merge! neighbor.resources
|
|
69
|
+
# jobs.merge! neighbor.jobs
|
|
70
|
+
#
|
|
71
|
+
# resource_type :slack_notification, 'docker-image' do
|
|
72
|
+
# source[:repository] = 'some/slack-docker-repo'
|
|
73
|
+
# end
|
|
74
|
+
#
|
|
75
|
+
# resource :our_slack_channel, :slack_notification do
|
|
76
|
+
# source[:url] = '((slack-team-webhook))'
|
|
77
|
+
# end
|
|
78
|
+
#
|
|
79
|
+
# # Add a slack notification task to the end
|
|
80
|
+
# # of every job
|
|
81
|
+
# jobs.values.each do |job|
|
|
82
|
+
# job.plan << {
|
|
83
|
+
# put: :our_slack_channel,
|
|
84
|
+
# params: { text: "Job #{job.name} complete!" }
|
|
85
|
+
# }
|
|
86
|
+
# end
|
|
87
|
+
#
|
|
88
|
+
# === Loading Individual Components
|
|
89
|
+
# Individual pipeline components can also be defined on a per-file
|
|
90
|
+
# basis and then loaded into a {Rudder::DSL::Pipeline} using
|
|
91
|
+
# {Rudder::DSL::Pipeline#load_component}. This is useful for factoring
|
|
92
|
+
# out common resources for multiple pipeline's to use.
|
|
93
|
+
#
|
|
94
|
+
# @example Loading / Importing Individual Components
|
|
95
|
+
# #
|
|
96
|
+
# # operations_scripts_resource.rb
|
|
97
|
+
# #
|
|
98
|
+
# type :git
|
|
99
|
+
# source[:uri] = 'https://github.com/<our org>/operations_scripts.git'
|
|
100
|
+
# source[:branch] = 'master'
|
|
101
|
+
#
|
|
102
|
+
#
|
|
103
|
+
# #
|
|
104
|
+
# # some_operations_pipeline.rb
|
|
105
|
+
# #
|
|
106
|
+
#
|
|
107
|
+
# # load the resource into the pipeline. Automatically includes
|
|
108
|
+
# # the resource into the resources list with the name :scripts
|
|
109
|
+
# load_component 'operations_scripts_resource.rb', :resource, :scripts
|
|
110
|
+
#
|
|
111
|
+
# job :audit do |pipeline|
|
|
112
|
+
# plan << {
|
|
113
|
+
# task: 'run the audit script', config: {
|
|
114
|
+
# platform: :linux,
|
|
115
|
+
# image_resource: {
|
|
116
|
+
# type: 'docker-image',
|
|
117
|
+
# source: { repository: 'alpine/git' }
|
|
118
|
+
# },
|
|
119
|
+
# run: {
|
|
120
|
+
# path: pipeline.scripts.sub_path('audit.rb')
|
|
121
|
+
# }
|
|
122
|
+
# }
|
|
123
|
+
# }
|
|
124
|
+
# end
|
|
125
|
+
class Pipeline
|
|
126
|
+
include Rudder::DSL::Util
|
|
127
|
+
# {Hash} of names to {Rudder::DSL::Resource}
|
|
128
|
+
# @return [Hash<(String, Symbol), Rudder::DSL::Resource>]
|
|
129
|
+
attr_accessor :resources
|
|
130
|
+
# {Hash} of names to {Rudder::DSL::Job}
|
|
131
|
+
# @return [Hash<(String, Symbol), Rudder::DSL::Job>]
|
|
132
|
+
attr_accessor :jobs
|
|
133
|
+
# {Hash} of names to {Rudder::DSL::ResourceType}
|
|
134
|
+
# @return [Hash<(String, Symbol), Rudder::DSL::ResourceType>]
|
|
135
|
+
attr_accessor :resource_types
|
|
136
|
+
# {Hash} of names to {Rudder::DSL::Group}
|
|
137
|
+
# @return [Hash<(String, Symbol), Rudder::DSL::Group>]
|
|
138
|
+
attr_accessor :groups
|
|
139
|
+
|
|
140
|
+
##
|
|
141
|
+
# All pipelines require:
|
|
142
|
+
# - Jobs
|
|
143
|
+
# - Resources
|
|
144
|
+
#
|
|
145
|
+
# Concourse Pipelines may optionally provide:
|
|
146
|
+
# - Resource Types
|
|
147
|
+
# - Groups
|
|
148
|
+
#
|
|
149
|
+
# +Rudder+ Pipelines may optionally include a +file_path+. This
|
|
150
|
+
# is required when loading resources from neighboring files.
|
|
151
|
+
#
|
|
152
|
+
# All pipeline requirements are only needed at the Pipeline
|
|
153
|
+
# render time (after evaluation), and need not be specified
|
|
154
|
+
# for initialization.
|
|
155
|
+
#
|
|
156
|
+
# @param file_path [String] path to this {Rudder::DSL::Pipeline} definition.
|
|
157
|
+
# @param resources [Hash<(String, Symbol), Rudder::DSL::Resource]
|
|
158
|
+
# map of Resource names to their definitions.
|
|
159
|
+
# @param jobs [Hash<(String, Symbol), Rudder::DSL::Job]
|
|
160
|
+
# map of Job names to their definitions.
|
|
161
|
+
# @param groups [Hash<(String, Symbol), Rudder::DSL::Group]
|
|
162
|
+
# map of Group names to their definitions.
|
|
163
|
+
# @param resources_types [Hash<(String, Symbol), Rudder::DSL::ResourceType]
|
|
164
|
+
# map of Resource Type names to their definitions.
|
|
165
|
+
#
|
|
166
|
+
def initialize(file_path = nil, resources: {}, jobs: {},
|
|
167
|
+
groups: {}, resource_types: {})
|
|
168
|
+
@resources = resources
|
|
169
|
+
@jobs = jobs
|
|
170
|
+
@groups = groups
|
|
171
|
+
@resource_types = resource_types
|
|
172
|
+
# rubocop:disable Layout/AlignHash, Layout/SpaceBeforeComma
|
|
173
|
+
@known_classes = {
|
|
174
|
+
resource: { clazz: Resource , pipeline_group: @resources },
|
|
175
|
+
job: { clazz: Job , pipeline_group: @jobs },
|
|
176
|
+
group: { clazz: Group , pipeline_group: @groups },
|
|
177
|
+
resource_type: { clazz: ResourceType, pipeline_group: @resource_types }
|
|
178
|
+
}
|
|
179
|
+
# rubocop:enable Layout/AlignHash, Layout/SpaceBeforeComma
|
|
180
|
+
@pipelines = {}
|
|
181
|
+
@file_path = file_path
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
##
|
|
185
|
+
# Renders all of this pipeline's components to their +Hash+
|
|
186
|
+
# representations.
|
|
187
|
+
#
|
|
188
|
+
# @return [Hash] YAML friendly +Hash+ representation of this +Pipeline+
|
|
189
|
+
# if either +groups+ or +resource_types+ is empty they will
|
|
190
|
+
# not be included in the rendering at all.
|
|
191
|
+
#
|
|
192
|
+
def to_h
|
|
193
|
+
h = {
|
|
194
|
+
'resources' => _convert_h_val(@resources.values),
|
|
195
|
+
'jobs' => _convert_h_val(@jobs.values)
|
|
196
|
+
}
|
|
197
|
+
h['groups'] = _convert_h_val(@groups.values) unless @groups.empty?
|
|
198
|
+
h['resource_types'] = _convert_h_val(@resource_types.values) unless @resource_types.empty?
|
|
199
|
+
h
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
##
|
|
203
|
+
# Populates this {Rudder::DSL::Pipeline} with components
|
|
204
|
+
# and optionally fetches defined components.
|
|
205
|
+
#
|
|
206
|
+
# Fetching
|
|
207
|
+
# --------------------------------------------------------------------
|
|
208
|
+
# When +method+ is called with no arguments it is treated
|
|
209
|
+
# as a {Rudder::DSL::Pipeline} getter method. +method+ is translated
|
|
210
|
+
# to the name of a {Rudder::DSL::Component} and the +Component+
|
|
211
|
+
# is returned if defined, otherwise nil is returned.
|
|
212
|
+
# --------------------------------------------------------------------
|
|
213
|
+
#
|
|
214
|
+
# Setting
|
|
215
|
+
# --------------------------------------------------------------------
|
|
216
|
+
# When +method+ is passed _any_ arguments (positional, placement, or block)
|
|
217
|
+
# then +method+ is treated as a setter.
|
|
218
|
+
#
|
|
219
|
+
# When setting, +method+ must be the name of a known {Rudder::DSL::Component}.
|
|
220
|
+
# The first argument is a required _name_ for the component. _All_ arguments
|
|
221
|
+
# and keyword arguments are then delegated to the {Rudder::DSL::Component}'s
|
|
222
|
+
# specific initializer.
|
|
223
|
+
#
|
|
224
|
+
# Finally, when a block is provided it is evaluated within the context
|
|
225
|
+
# of the newly constructed {Rudder::DSL::Component} with full priveleges
|
|
226
|
+
# to operate on it.
|
|
227
|
+
# --------------------------------------------------------------------
|
|
228
|
+
#
|
|
229
|
+
# @return [Rudder::DSL::Component, nil] when +method+ is called with no
|
|
230
|
+
# arguments, returns the {Rudder::DSL::Component} with name
|
|
231
|
+
# +method+, if any exists. Otherwise returns +nil+.
|
|
232
|
+
# @raise [RuntimeError] when attempting to define an unknown
|
|
233
|
+
# {Rudder::DSL::Component}
|
|
234
|
+
# @internal_todo
|
|
235
|
+
# TODO: Clean this up so these can be reenabled
|
|
236
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
237
|
+
def method_missing(method, *args, &component_block)
|
|
238
|
+
local_component = _get_local_component(method)
|
|
239
|
+
if !@known_classes.include?(method) && !local_component
|
|
240
|
+
return super.send(method, args, component_block)
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# Look up a previously defined component from the pipeline
|
|
244
|
+
return local_component if local_component && args.empty? && !block_given?
|
|
245
|
+
|
|
246
|
+
component_group = @known_classes[method][:pipeline_group]
|
|
247
|
+
name = args[0]
|
|
248
|
+
raise "Overlapping component name: #{method}" if component_group.include? name
|
|
249
|
+
|
|
250
|
+
component = @known_classes[method][:clazz].new(*args)
|
|
251
|
+
|
|
252
|
+
component.instance_exec self, &component_block if block_given?
|
|
253
|
+
component_group[name] = component
|
|
254
|
+
end
|
|
255
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
256
|
+
|
|
257
|
+
##
|
|
258
|
+
# {Rudder::DSL::Pipeline}'s respond to missing
|
|
259
|
+
#
|
|
260
|
+
# @return true
|
|
261
|
+
def respond_to_missing?(*_)
|
|
262
|
+
true
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def respond_to?(name, _include_all = true)
|
|
266
|
+
@known_classes.key? name
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
##
|
|
270
|
+
# Evaluates the given file path.
|
|
271
|
+
# If file_path nil, defaults to the one provided at construction time
|
|
272
|
+
# If both are nil, raises an exception
|
|
273
|
+
#
|
|
274
|
+
# @param file_path [String, nil] path to {Rudder::DSL::Pipeline} definition
|
|
275
|
+
# to evaluate. Uses the current +file_path+ if +nil+
|
|
276
|
+
# @raise [RuntimeError] if +file_path+ and {Rudder::DSL::Pipeline#file_path}
|
|
277
|
+
# are both +nil+
|
|
278
|
+
# @return [Rudder::DSL::Pipeline] the evaluated pipeline
|
|
279
|
+
def eval(file_path = nil)
|
|
280
|
+
@file_path = file_path || @file_path
|
|
281
|
+
if @file_path.nil?
|
|
282
|
+
raise 'File path must be provided at Pipeline initialization or eval call'
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
File.open(@file_path) do |f|
|
|
286
|
+
instance_eval f.read, @file_path
|
|
287
|
+
end
|
|
288
|
+
self
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
##
|
|
292
|
+
# Given a path relative to this pipeline, loads another
|
|
293
|
+
# pipeline and returns it
|
|
294
|
+
#
|
|
295
|
+
# Note that this includes _nothing_ from the relative pipeline in this
|
|
296
|
+
# one, instead just returning the pipeline to be manipulated
|
|
297
|
+
#
|
|
298
|
+
# May also optionally provides hashes for
|
|
299
|
+
# - resources
|
|
300
|
+
# - resource_types
|
|
301
|
+
# - jobs
|
|
302
|
+
# - groups
|
|
303
|
+
#
|
|
304
|
+
# @param other_pipeline_path [String] relative path to {Rudder::DSL::Pipeline}
|
|
305
|
+
# definition to load and evaluate.
|
|
306
|
+
# @param resources [Hash<(String, Symbol), Rudder::DSL::Resource]
|
|
307
|
+
# resources to initialize the other {Rudder::DSL::Pipeline} with
|
|
308
|
+
# @param resources_types [Hash<(String, Symbol), Rudder::DSL::ResourceType]
|
|
309
|
+
# resources_types to initialize the other {Rudder::DSL::Pipeline} with
|
|
310
|
+
# @param jobs [Hash<(String, Symbol), Rudder::DSL::Job]
|
|
311
|
+
# jobs to initialize the other {Rudder::DSL::Pipeline} with
|
|
312
|
+
# @param groups [Hash<(String, Symbol), Rudder::DSL::Group]
|
|
313
|
+
# groups to initialize the other {Rudder::DSL::Pipeline} with
|
|
314
|
+
def load(other_pipeline_path, resources: {}, resource_types: {},
|
|
315
|
+
jobs: {}, groups: {})
|
|
316
|
+
if @pipelines.key? other_pipeline_path
|
|
317
|
+
@pipelines[other_pipeline_path]
|
|
318
|
+
else
|
|
319
|
+
dir = File.dirname(@file_path)
|
|
320
|
+
full_path = File.join(dir, other_pipeline_path)
|
|
321
|
+
pipeline = Rudder::DSL::Pipeline.new(
|
|
322
|
+
full_path, resources: resources, resource_types: resource_types,
|
|
323
|
+
jobs: jobs, groups: groups
|
|
324
|
+
).eval
|
|
325
|
+
@pipelines[other_pipeline_path] = pipeline
|
|
326
|
+
pipeline
|
|
327
|
+
end
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
##
|
|
331
|
+
# Given a path to a component, its class, and
|
|
332
|
+
# any args required to construct it, creates
|
|
333
|
+
# a new component
|
|
334
|
+
#
|
|
335
|
+
# Note that this automatically includes the component into this pipeline
|
|
336
|
+
#
|
|
337
|
+
# @param component_path [String] path, relative to this pipeline, containing
|
|
338
|
+
# a {Rudder::DSL::Component} to load
|
|
339
|
+
# @param class_sym [Symbol] symbol of a {Rudder::DSL::Component}. May be one of:
|
|
340
|
+
# (+:job+, +:resource+, +:resource_type+, +:group+)
|
|
341
|
+
# @param name [String, Symbol] name to use for the loaded
|
|
342
|
+
# {Rudder::DSL::Component}. Must not be +nil+.
|
|
343
|
+
# @param *args any additional arguments to pass to the {Rudder::DSL::Component}
|
|
344
|
+
# constructor.
|
|
345
|
+
# @raise RuntimeError if +name+ is +nil+ or an uknown +class_sym+ is provided.
|
|
346
|
+
#
|
|
347
|
+
def load_component(component_path, class_sym, name, *args)
|
|
348
|
+
raise "Unable to load #{class_sym}" unless @known_classes.keys.include? class_sym
|
|
349
|
+
raise 'Name must not be nil' if name.nil?
|
|
350
|
+
|
|
351
|
+
full_path = File.join(File.dirname(@file_path), component_path)
|
|
352
|
+
component = @known_classes[class_sym][:clazz].new(name, *args)
|
|
353
|
+
component.instance_eval File.read(full_path), full_path
|
|
354
|
+
@known_classes[class_sym][:pipeline_group][name] = component
|
|
355
|
+
component
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
# Yikes! Seems like a bad idea - if someone uses the same name twice (say, 1 resource
|
|
359
|
+
# and 1 job), then this will return one pretty much at random.
|
|
360
|
+
# TODO: Make this not bad
|
|
361
|
+
# Oh well..
|
|
362
|
+
# TODO: This may be returning a non-nil/non-falsey type that causes some issues
|
|
363
|
+
def _get_local_component(component)
|
|
364
|
+
component = component.to_sym
|
|
365
|
+
locals = @known_classes.values.map do |m|
|
|
366
|
+
m[:pipeline_group][component]
|
|
367
|
+
end.compact
|
|
368
|
+
# TODO: Add a logger here..
|
|
369
|
+
puts "Found multiple bindings for: #{p}. Getting first found" unless locals.size <= 1
|
|
370
|
+
locals[0]
|
|
371
|
+
end
|
|
372
|
+
end
|
|
373
|
+
end
|
|
374
|
+
end
|