jujube 0.5.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.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/.travis.yml +9 -0
  4. data/.yardopts +4 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE +22 -0
  7. data/README.md +180 -0
  8. data/Rakefile +15 -0
  9. data/acceptance/fixtures/endToEnd/endToEnd.job +43 -0
  10. data/acceptance/fixtures/endToEnd/expected.yml +80 -0
  11. data/acceptance/fixtures/multipleFiles/job1.job +1 -0
  12. data/acceptance/fixtures/multipleFiles/job2.job +1 -0
  13. data/acceptance/fixtures/multipleJobs/test.job +2 -0
  14. data/acceptance/fixtures/nestedDirectories/job1.job +1 -0
  15. data/acceptance/fixtures/nestedDirectories/nested/job2.job +1 -0
  16. data/acceptance/fixtures/singleJob/test.job +3 -0
  17. data/acceptance/jujube/acceptance_test.rb +47 -0
  18. data/acceptance/jujube/end_to_end_test.rb +13 -0
  19. data/acceptance/jujube/usage_test.rb +90 -0
  20. data/bin/jujube +5 -0
  21. data/jujube.gemspec +29 -0
  22. data/lib/jujube.rb +6 -0
  23. data/lib/jujube/components.rb +23 -0
  24. data/lib/jujube/components/builders.rb +19 -0
  25. data/lib/jujube/components/helpers.rb +40 -0
  26. data/lib/jujube/components/macros.rb +31 -0
  27. data/lib/jujube/components/notifications.rb +11 -0
  28. data/lib/jujube/components/parameters.rb +50 -0
  29. data/lib/jujube/components/publishers.rb +97 -0
  30. data/lib/jujube/components/scm.rb +19 -0
  31. data/lib/jujube/components/triggers.rb +105 -0
  32. data/lib/jujube/components/wrappers.rb +28 -0
  33. data/lib/jujube/driver.rb +78 -0
  34. data/lib/jujube/dsl.rb +43 -0
  35. data/lib/jujube/job.rb +226 -0
  36. data/lib/jujube/job_file_generator.rb +17 -0
  37. data/lib/jujube/job_loader.rb +51 -0
  38. data/lib/jujube/macros.rb +35 -0
  39. data/lib/jujube/utils.rb +27 -0
  40. data/lib/jujube/version.rb +5 -0
  41. data/test/components/builders_test.rb +10 -0
  42. data/test/components/parameters_test.rb +15 -0
  43. data/test/components/publishers_test.rb +51 -0
  44. data/test/components/scm_test.rb +11 -0
  45. data/test/components/triggers_test.rb +81 -0
  46. data/test/components/wrappers_test.rb +19 -0
  47. data/test/driver_test.rb +47 -0
  48. data/test/job_test.rb +79 -0
  49. data/test/test_helper.rb +4 -0
  50. metadata +186 -0
@@ -0,0 +1,105 @@
1
+ module Jujube
2
+ module Components
3
+
4
+ # Helper methods for creating trigger components.
5
+ module Triggers
6
+
7
+ # Specify a `pollscm` trigger for a job.
8
+ #
9
+ # See {http://ci.openstack.org/jenkins-job-builder/triggers.html#triggers.pollscm}.
10
+ #
11
+ # @param interval [String] The polling interval, using `cron` syntax.
12
+ # @return [Hash] The specification for the component.
13
+ def pollscm(interval)
14
+ {'pollscm' => interval}
15
+ end
16
+
17
+ # Specify a `pollurl` trigger for a job.
18
+ #
19
+ # This trigger requires support in jenkins-job-builder that has not yet been merged.
20
+ # See {https://review.openstack.org/#/c/83524/} for the patch.
21
+ #
22
+ # `pollurl` can poll several URLs. Each URL specification is added
23
+ # in a nested configuration block using the {#url} method.
24
+ #
25
+ # @example
26
+ # job "pollurl-example" do |j|
27
+ # j.triggers << pollurl(cron: "CRON") do |urls|
28
+ # urls << url("URL", check_date: true) do |content|
29
+ # content << json("JSON_PATH1", "JSON_PATH2")
30
+ # end
31
+ # end
32
+ # end
33
+ #
34
+ # @param options [Hash] Top-level options for configuring the component.
35
+ # @yieldparam urls [Array] An array to which nested URL specifications should be
36
+ # added by the block.
37
+ # @return [Hash] The specification for the component.
38
+ def pollurl(options = {}, &block)
39
+ to_config("pollurl", nested_options(:urls, options, &block))
40
+ end
41
+
42
+ # @!group pollurl Helpers
43
+
44
+ # Configure a URL to poll in a {#pollurl} component.
45
+ #
46
+ # If you want to check for changes to the actual content returned from the URL,
47
+ # (the `check-content` setting), pass a block that configures the content-type
48
+ # specifications using one or more of {#simple}, {#json}, {#xml}, or {#text}.
49
+ #
50
+ # @param the_url [String] The URL to monitor.
51
+ # @param options [Hash] Top-level options for this URL.
52
+ # @yieldparam content_types [Array] An array to which nested content-type
53
+ # specifications should be added by the block.
54
+ # @return [Hash] The specification for the URL.
55
+ def url(the_url, options = {}, &block)
56
+ options = {url: the_url}.merge!(options)
57
+ canonicalize_options(nested_options(:check_content, options, &block))
58
+ end
59
+
60
+ # @!endgroup
61
+
62
+ # @!group pollurl Content Types
63
+
64
+ # Configure a simple content check inside a {#url} specification of a
65
+ # {#pollurl} component.
66
+ # @return [Hash] The specification for the content type.
67
+ def simple
68
+ {"simple" => true}
69
+ end
70
+
71
+ # Configure a JSON content check inside a {#url} specification of a
72
+ # {#pollurl} component.
73
+ #
74
+ # @param paths [String...] Zero or more JSONPath expressions. Only changes to
75
+ # the parts of the JSON response that match one of the `paths` will trigger a build.
76
+ # @return [Hash] The specification for the content type.
77
+ def json(*paths)
78
+ {"json" => paths}
79
+ end
80
+
81
+ # Configure an XML content check inside a {#url} specification of a
82
+ # {#pollurl} component.
83
+ #
84
+ # @param xpaths [String...] Zero or more XPath expressions. Only changes to
85
+ # the parts of the XML response that match one of the `xpaths` will trigger a build.
86
+ # @return [Hash] The specification for the content type.
87
+ def xml(*xpaths)
88
+ {"xml" => xpaths}
89
+ end
90
+
91
+ # Configure a text content check inside a {#url} specification of a
92
+ # {#pollurl} component.
93
+ #
94
+ # @param regexes [String...] Zero or more regular expressions. Only changes to
95
+ # the parts of the text response that match one of the `regexes` will trigger a build.
96
+ # @return [Hash] The specification for the content type.
97
+ def text(*regexes)
98
+ {"text" => regexes}
99
+ end
100
+
101
+ # @!endgroup
102
+
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,28 @@
1
+ module Jujube
2
+ module Components
3
+
4
+ # Helper methods for creating wrapper components.
5
+ module Wrappers
6
+ extend Macros
7
+
8
+ # @!method timeout(options = {})
9
+ # Specify a `timeout` component for a job.
10
+ #
11
+ # See {http://ci.openstack.org/jenkins-job-builder/publishers.html#wrappers.timeout}.
12
+ #
13
+ # @param options [Hash] The configuration options for the component.
14
+ # @return [Hash] The specification for the component.
15
+ standard_component :timeout
16
+
17
+ # Specify a `timestamps` component for a job.
18
+ #
19
+ # See {http://ci.openstack.org/jenkins-job-builder/wrappers.html#wrappers.timestamps}.
20
+ #
21
+ # @return [Hash] The specification for the component.
22
+ def timestamps
23
+ 'timestamps'
24
+ end
25
+
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,78 @@
1
+ require_relative "job_loader"
2
+ require_relative "job_file_generator"
3
+ require 'optparse'
4
+ require 'ostruct'
5
+ require 'pathname'
6
+
7
+ module Jujube
8
+
9
+ # The command-line driver for the Jujube application.
10
+ class Driver
11
+
12
+ # Run the Jujube application.
13
+ def self.run
14
+ self.new.run
15
+ end
16
+
17
+ # Initialize the driver.
18
+ #
19
+ # @param loader [JobLoader] Loads jobs from user-specified files and/or directories.
20
+ # @param generator [JobFileGenerator] Generates the jenkins-job-builder YAML file from
21
+ # any jobs that have been loaded.
22
+ def initialize(loader = JobLoader.new, generator = JobFileGenerator.new)
23
+ @loader = loader
24
+ @generator = generator
25
+ end
26
+
27
+ # Run the Jujube application.
28
+ #
29
+ # @param argv [Array] The command-line arguments that control the application.
30
+ def run(argv = ARGV)
31
+ argv = adjusted_for_jruby(argv)
32
+ options = handle_options(argv)
33
+ jobs = @loader.load_jobs(*options.paths || Pathname.getwd)
34
+ @generator.generate(jobs, options.output || Pathname.new("jobs.yml"))
35
+ end
36
+
37
+ private
38
+
39
+ # Parse the command-line options.
40
+ #
41
+ # @param argv [Array] The command-line options.
42
+ # @return [OpenStruct] The options specified on the command-line. May include
43
+ # `paths`, a list of `Pathname`s to load and `output`, the `Pathname` of the
44
+ # output file to generate.
45
+ def handle_options(argv)
46
+ options = OpenStruct.new
47
+
48
+ OptionParser.new do |opts|
49
+ opts.banner = "Usage: #{$PROGRAM_NAME} [options] paths..."
50
+
51
+ opts.on('-o', '--output FILENAME', 'Specify output file (default = "jobs.yml")') do |file|
52
+ options.output = Pathname.new(file)
53
+ end
54
+
55
+ opts.on('-h', '--help', "Display this screen") do
56
+ puts opts
57
+ exit
58
+ end
59
+ end.parse!(argv)
60
+
61
+ options.paths = argv.map { |file| Pathname.new(file) } unless argv.empty?
62
+
63
+ options
64
+ end
65
+
66
+ # Adjust the command-line arguments for running acceptance tests under JRuby.
67
+ #
68
+ # When running the acceptance tests under JRuby, an extra '{}' argument
69
+ # is passed in ARGV. We remove it here so that it doesn't affect the
70
+ # rest of the program logic.
71
+ #
72
+ # See {https://github.com/jruby/jruby/issues/1290}.
73
+ def adjusted_for_jruby(argv)
74
+ argv.pop if argv.last == "{}"
75
+ argv
76
+ end
77
+ end
78
+ end
data/lib/jujube/dsl.rb ADDED
@@ -0,0 +1,43 @@
1
+ module Jujube
2
+ # The top-level DSL to be used when defining jobs.
3
+ module DSL
4
+ include Jujube::Components
5
+
6
+ # Define a new Jenkins job.
7
+ #
8
+ # Takes a configuration block that is used to configure the job by
9
+ # setting attribute values and adding components to the various sections
10
+ # of the job.
11
+ #
12
+ # @example
13
+ # job "my-job" do |j|
14
+ # j.description = "This is my job." # Specify attributes
15
+ # j.quiet_period = 5
16
+ #
17
+ # j.axes << slave(:arch, %w{i386 amd64}) # Add components to sections
18
+ #
19
+ # j.scm << git(url: "https://example.com/git/my-project", branches: %w{master dev})
20
+ #
21
+ # j.triggers << pollscm("@hourly")
22
+ #
23
+ # j.wrappers << timeout(type: 'elastic', elastic_percentage: 150, elastic_default_timeout: 5, fail: true)
24
+ # j.wrappers << timestamps
25
+ #
26
+ # j.builders << shell("bundle && bundle exec rake deploy")
27
+ #
28
+ # j.publishers << email_ext(recipients: %w{me@example.com you@example.com})
29
+ # end
30
+ #
31
+ # @param name [String] The name of the job as will be shown by Jenkins.
32
+ # @yieldparam job [Job] The job being created.
33
+ def job(name, &block)
34
+ Job.new(name, &block)
35
+ end
36
+
37
+ end
38
+ end
39
+
40
+ # Extend the main object with the DSL commands. This allows top-level
41
+ # calls to the DSL functions to work from a job file without polluting
42
+ # the object inheritance tree.
43
+ self.extend Jujube::DSL
data/lib/jujube/job.rb ADDED
@@ -0,0 +1,226 @@
1
+ require_relative "macros"
2
+ require 'yaml'
3
+
4
+ module Jujube
5
+ # Models a single Jenkins job.
6
+ class Job
7
+ extend Macros
8
+
9
+ # Initialize the job.
10
+ #
11
+ # Takes a configuration block for adding atributes and sections to the job.
12
+ # The configuration block is passed the new `Job` as a parameter.
13
+ #
14
+ # @param job_name [String] The name of the job. Will be the name as seen in Jenkins.
15
+ # @yieldparam self [Job] Passes itself to the configuration block.
16
+ def initialize(job_name)
17
+ @config = {}
18
+ self.name = job_name
19
+
20
+ yield(self) if block_given?
21
+
22
+ self.class.register(self)
23
+ end
24
+
25
+ # @!group Attributes
26
+
27
+ # @!attribute name
28
+ # The name of the job - will be the name as seen in Jenkins.
29
+ #
30
+ # See {http://ci.openstack.org/jenkins-job-builder/general.html}.
31
+ #
32
+ # @return [String]
33
+ attribute :name
34
+
35
+ # @!attribute project_type
36
+ # The type of job. This normally does not need to be specified, as it
37
+ # will be inferred as `matrix` if any `axes` are added.
38
+ #
39
+ # See {http://ci.openstack.org/jenkins-job-builder/general.html}.
40
+ #
41
+ # @return [String]
42
+ attribute :project_type
43
+
44
+ # @!attribute description
45
+ # The description of the job.
46
+ #
47
+ # See {http://ci.openstack.org/jenkins-job-builder/general.html}.
48
+ #
49
+ # @return [String]
50
+ attribute :description
51
+
52
+ # @!attribute node
53
+ # The Jenkins node or named group where the job should be run.
54
+ #
55
+ # See {http://ci.openstack.org/jenkins-job-builder/general.html}.
56
+ #
57
+ # @return [String]
58
+ attribute :node
59
+
60
+ # @!attribute block_upstream
61
+ # `true` if this job should block while upstream jobs are running.
62
+ #
63
+ # See {http://ci.openstack.org/jenkins-job-builder/general.html}.
64
+ #
65
+ # @return [Boolean]
66
+ attribute :block_upstream
67
+
68
+ # @!attribute block_downstream
69
+ # `true` if this job should block while downstream jobs are running.
70
+ #
71
+ # See {http://ci.openstack.org/jenkins-job-builder/general.html}.
72
+ #
73
+ # @return [Booelan]
74
+ attribute :block_downstream
75
+
76
+ # @!attribute quiet_period
77
+ # Number of seconds to wait between consecutive runs of the job.
78
+ #
79
+ # See {http://ci.openstack.org/jenkins-job-builder/general.html}.
80
+ #
81
+ # @return [Fixnum]
82
+ attribute :quiet_period
83
+
84
+ # @!endgroup
85
+
86
+ # @!group Sections
87
+
88
+ # @!method axes
89
+ # The matrix axes for the job.
90
+ #
91
+ # Add axes in the job's configuration block using helper methods defined in
92
+ # {Components::Parameters}.
93
+ #
94
+ # See {http://ci.openstack.org/jenkins-job-builder/project_matrix.html}.
95
+ #
96
+ # @return [Array]
97
+ section :axes
98
+
99
+ # @!method scm
100
+ # The SCMs for the job.
101
+ #
102
+ # Add SCMs in the job's configuration block using helper methods defined in
103
+ # {Components::Scm}.
104
+ #
105
+ # See {http://ci.openstack.org/jenkins-job-builder/scm.html}.
106
+ #
107
+ # @return [Array]
108
+ section :scm
109
+
110
+ # @!method triggers
111
+ # The triggers for the job.
112
+ #
113
+ # Add triggers in the job's configuration block using helper methods defined in
114
+ # {Components::Triggers}.
115
+ #
116
+ # See {http://ci.openstack.org/jenkins-job-builder/triggers.html}.
117
+ #
118
+ # @return [Array]
119
+ section :triggers
120
+
121
+ # @!method wrappers
122
+ # The wrappers for the job.
123
+ #
124
+ # Add wrappers in the job's configuration block using helper methods defined in
125
+ # {Components::Wrappers}.
126
+ #
127
+ # See {http://ci.openstack.org/jenkins-job-builder/wrappers.html}.
128
+ #
129
+ # @return [Array]
130
+ section :wrappers
131
+
132
+ # @!method builders
133
+ # The builders for the job.
134
+ #
135
+ # Add builders in the job's configuration block using helper methods defined in
136
+ # {Components::Builders}.
137
+ #
138
+ # See {http://ci.openstack.org/jenkins-job-builder/builders.html}.
139
+ #
140
+ # @return [Array]
141
+ section :builders
142
+
143
+ # @!method publishers
144
+ # The publishers for the job.
145
+ #
146
+ # Add publishers in the job's configuration block using helper methods defined in
147
+ # {Components::Publishers}.
148
+ #
149
+ # See {http://ci.openstack.org/jenkins-job-builder/publishers.html}.
150
+ #
151
+ # @return [Array]
152
+ section :publishers
153
+
154
+ # @!method notifications
155
+ # The notifications for the job.
156
+ #
157
+ # Add notifications in the job's configuration block using helper methods defined in
158
+ # {Components::Notifications}.
159
+ #
160
+ # See {http://ci.openstack.org/jenkins-job-builder/notifications.html}.
161
+ #
162
+ # @return [Array]
163
+ section :notifications
164
+
165
+ # @!endgroup
166
+
167
+ # Generate a YAML representation of the `Job`.
168
+ def to_yaml(*args)
169
+ to_h.to_yaml(*args)
170
+ end
171
+
172
+ # Generate a `Hash` repsentation of the `Job`.
173
+ def to_h
174
+ infer_project_type
175
+ {'job' => config}
176
+ end
177
+
178
+ # Keep track of all `Job`s defined during the execution of the passed block.
179
+ #
180
+ # This is used during job loading so that no extra syntax is required in the job
181
+ # definition files for registering jobs and so that it is possible to create jobs
182
+ # for testing purposes without having them registered anywhere.
183
+ #
184
+ # @return [Array<Job>] Returns a list of all `Job`s defined within the block.
185
+ def self.all_defined_during
186
+ @registry = []
187
+ yield
188
+ @registry
189
+ ensure
190
+ @registry = []
191
+ end
192
+
193
+ # Register a `Job` in a registry.
194
+ #
195
+ # If no registry is active, this is a no-op.
196
+ #
197
+ # @param job [Job] The job to register.
198
+ def self.register(job)
199
+ @registry << job if @registry
200
+ end
201
+
202
+ # The job configuration.
203
+ #
204
+ # All configuration is stored internally as a `Hash` with `String` keys in
205
+ # canonical jenkins-job-builder format. This format can readily be converted
206
+ # to the YAML needed by jenkins-job-builder.
207
+ attr_reader :config
208
+ private :config
209
+
210
+ private
211
+
212
+ # Set the {#project_type} to `matrix` if any `axes` have been specified.
213
+ def infer_project_type
214
+ self.project_type = "matrix" if has_axes?
215
+ end
216
+
217
+ # Have any `axes` been specified?
218
+ #
219
+ # Take care to not lazily-initialize an `"axes"` key when checking.
220
+ #
221
+ # @return [Boolean] `true` if any `axes` have been defined; `false` otherwise.
222
+ def has_axes?
223
+ config.has_key?("axes") && !axes.empty?
224
+ end
225
+ end
226
+ end