released 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +16 -0
- data/Gemfile.lock +130 -0
- data/README.md +163 -0
- data/Rakefile +13 -0
- data/lib/released/goal.rb +38 -0
- data/lib/released/goals/file_exists.rb +30 -0
- data/lib/released/goals/gem_built.rb +46 -0
- data/lib/released/goals/gem_pushed.rb +60 -0
- data/lib/released/goals/git_ref_pushed.rb +39 -0
- data/lib/released/goals/github_release_exists.rb +33 -0
- data/lib/released/goals/shell.rb +40 -0
- data/lib/released/goals.rb +11 -0
- data/lib/released/pipeline_reader.rb +73 -0
- data/lib/released/runner.rb +171 -0
- data/lib/released/version.rb +3 -0
- data/lib/released.rb +17 -0
- data/released.gemspec +38 -0
- data/spec/released/goals/gem_built_spec.rb +64 -0
- data/spec/released/goals/gem_pushed_spec.rb +149 -0
- data/spec/released/goals/git_ref_pushed_spec.rb +74 -0
- data/spec/released/pipeline_reader_spec.rb +49 -0
- data/spec/spec_helper.rb +33 -0
- metadata +185 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 16ad85ccdafa69e73909d4f175e292aea0b13fd3
|
4
|
+
data.tar.gz: 768b7e7e8bc22e4e0d6e406e0b89df6573ea2046
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 15ddba448b7ba2e02991a4e2721e35032074bbe648d527e48da881745e62f1f1809b2dfa1f95f2b756d2dde3c1b7c315af88fb5bd96859e97b606f17e2f8637a
|
7
|
+
data.tar.gz: f4b2de9c067cc3d0a2ff08522a2c95967d50b4905a55d9efa2ec1d091085c7c121f9d070a8df765b27af71f553d12e0c8527680981f4f0922cae2be9c1210607
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
released (0.0.1)
|
5
|
+
ddplugin (~> 1.0)
|
6
|
+
gems
|
7
|
+
git
|
8
|
+
nanoc (~> 4.4)
|
9
|
+
netrc
|
10
|
+
octokit
|
11
|
+
twitter
|
12
|
+
|
13
|
+
GEM
|
14
|
+
remote: https://rubygems.org/
|
15
|
+
specs:
|
16
|
+
addressable (2.5.0)
|
17
|
+
public_suffix (~> 2.0, >= 2.0.2)
|
18
|
+
ast (2.3.0)
|
19
|
+
builder (3.2.2)
|
20
|
+
colored (1.2)
|
21
|
+
concurrent-ruby (1.0.2)
|
22
|
+
crack (0.4.3)
|
23
|
+
safe_yaml (~> 1.0.0)
|
24
|
+
cri (2.7.0)
|
25
|
+
colored (~> 1.2)
|
26
|
+
ddplugin (1.0.0)
|
27
|
+
diff-lcs (1.2.5)
|
28
|
+
faraday (0.10.0)
|
29
|
+
multipart-post (>= 1.2, < 3)
|
30
|
+
fuubar (2.2.0)
|
31
|
+
rspec-core (~> 3.0)
|
32
|
+
ruby-progressbar (~> 1.4)
|
33
|
+
geminabox (0.13.4)
|
34
|
+
builder
|
35
|
+
faraday
|
36
|
+
httpclient (>= 2.2.7)
|
37
|
+
nesty
|
38
|
+
sinatra (>= 1.2.7)
|
39
|
+
gems (0.8.3)
|
40
|
+
git (1.3.0)
|
41
|
+
hamster (3.0.0)
|
42
|
+
concurrent-ruby (~> 1.0)
|
43
|
+
hashdiff (0.3.1)
|
44
|
+
httpclient (2.8.2.4)
|
45
|
+
json (2.0.2)
|
46
|
+
multi_json (1.12.1)
|
47
|
+
multipart-post (2.0.0)
|
48
|
+
nanoc (4.4.2)
|
49
|
+
cri (~> 2.3)
|
50
|
+
hamster (~> 3.0)
|
51
|
+
parallel (~> 1.9)
|
52
|
+
ref (~> 2.0)
|
53
|
+
nesty (1.0.2)
|
54
|
+
netrc (0.11.0)
|
55
|
+
octokit (4.6.2)
|
56
|
+
sawyer (~> 0.8.0, >= 0.5.3)
|
57
|
+
parallel (1.10.0)
|
58
|
+
parser (2.3.2.0)
|
59
|
+
ast (~> 2.2)
|
60
|
+
powerpack (0.1.1)
|
61
|
+
public_suffix (2.0.4)
|
62
|
+
rack (1.6.5)
|
63
|
+
rack-protection (1.5.3)
|
64
|
+
rack
|
65
|
+
rainbow (2.1.0)
|
66
|
+
rake (11.3.0)
|
67
|
+
ref (2.0.0)
|
68
|
+
rspec (3.5.0)
|
69
|
+
rspec-core (~> 3.5.0)
|
70
|
+
rspec-expectations (~> 3.5.0)
|
71
|
+
rspec-mocks (~> 3.5.0)
|
72
|
+
rspec-core (3.5.4)
|
73
|
+
rspec-support (~> 3.5.0)
|
74
|
+
rspec-expectations (3.5.0)
|
75
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
76
|
+
rspec-support (~> 3.5.0)
|
77
|
+
rspec-its (1.2.0)
|
78
|
+
rspec-core (>= 3.0.0)
|
79
|
+
rspec-expectations (>= 3.0.0)
|
80
|
+
rspec-mocks (3.5.0)
|
81
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
82
|
+
rspec-support (~> 3.5.0)
|
83
|
+
rspec-support (3.5.0)
|
84
|
+
rubocop (0.45.0)
|
85
|
+
parser (>= 2.3.1.1, < 3.0)
|
86
|
+
powerpack (~> 0.1)
|
87
|
+
rainbow (>= 1.99.1, < 3.0)
|
88
|
+
ruby-progressbar (~> 1.7)
|
89
|
+
unicode-display_width (~> 1.0, >= 1.0.1)
|
90
|
+
ruby-progressbar (1.8.1)
|
91
|
+
safe_yaml (1.0.4)
|
92
|
+
sawyer (0.8.1)
|
93
|
+
addressable (>= 2.3.5, < 2.6)
|
94
|
+
faraday (~> 0.8, < 1.0)
|
95
|
+
simple_oauth (0.3.1)
|
96
|
+
sinatra (1.4.7)
|
97
|
+
rack (~> 1.5)
|
98
|
+
rack-protection (~> 1.4)
|
99
|
+
tilt (>= 1.3, < 3)
|
100
|
+
tilt (2.0.5)
|
101
|
+
twitter (4.4.2)
|
102
|
+
faraday (~> 0.8)
|
103
|
+
multi_json (~> 1.3)
|
104
|
+
simple_oauth (~> 0.2)
|
105
|
+
unicode-display_width (1.1.1)
|
106
|
+
vcr (3.0.3)
|
107
|
+
webmock (2.1.0)
|
108
|
+
addressable (>= 2.3.6)
|
109
|
+
crack (>= 0.3.2)
|
110
|
+
hashdiff
|
111
|
+
|
112
|
+
PLATFORMS
|
113
|
+
ruby
|
114
|
+
|
115
|
+
DEPENDENCIES
|
116
|
+
bundler (>= 1.7.10, < 2.0)
|
117
|
+
fuubar
|
118
|
+
geminabox
|
119
|
+
json (~> 2.0)
|
120
|
+
rake
|
121
|
+
released!
|
122
|
+
rspec
|
123
|
+
rspec-its
|
124
|
+
rspec-mocks
|
125
|
+
rubocop
|
126
|
+
vcr
|
127
|
+
webmock
|
128
|
+
|
129
|
+
BUNDLED WITH
|
130
|
+
1.13.6
|
data/README.md
ADDED
@@ -0,0 +1,163 @@
|
|
1
|
+
# Released
|
2
|
+
|
3
|
+
Released is a release pipeline tool. It is a possible implementation of [Nanoc RFC 8](https://github.com/nanoc/rfcs/pull/8).
|
4
|
+
|
5
|
+
## Status
|
6
|
+
|
7
|
+
_Released_ is experimental. It lacks features and is not stable. To track development, take a look at the [GitHub projects for Released](https://github.com/ddfreyne/released/projects).
|
8
|
+
|
9
|
+
## Example
|
10
|
+
|
11
|
+
Example pipeline:
|
12
|
+
|
13
|
+
```yaml
|
14
|
+
goals:
|
15
|
+
- shell:
|
16
|
+
command: bundle exec rake spec
|
17
|
+
- shell:
|
18
|
+
command: bundle exec rake rubocop
|
19
|
+
- gem_built:
|
20
|
+
name: released
|
21
|
+
version: env!VERSION
|
22
|
+
- gem_pushed:
|
23
|
+
name: released
|
24
|
+
version: env!VERSION
|
25
|
+
authorization: |
|
26
|
+
-----BEGIN PGP MESSAGE-----
|
27
|
+
Comment: GPGTools - http://gpgtools.org
|
28
|
+
|
29
|
+
hQIMA2z0x64EwZScAQ/9E4WVht6M/KHgJ0JwGUn77/s7zexsHtc6jUhd0PGHJtTp
|
30
|
+
KI/0pnFuKketcoZ2MVGhoKO4zMj7oUgcMk9ajKYe+CtxntWoCVqSKFtuAPD7Sa59
|
31
|
+
vcDLMnznNHpF3X6lRoCaRZ9uJYKaxR+HkrKjs8IquX2fr1rmTUy2POQvqZiz8kur
|
32
|
+
uYJNT2rV83dVxnnf5Xxi+39XGpHznDYC7Jz0cETTjj69xq8HqLaXG2DMaYGfZQMX
|
33
|
+
laL/vABwy63jzpDyp3IUNYEhol9GNJT02kvZLf851LtG7WUif5mH/Yh7jYbDMMbE
|
34
|
+
L0bwZ6rwF483QLnrcBQmjVVokRZmgwVZ2aj4FrE9rPPcRP5En4xqnyAoNECqJEIw
|
35
|
+
a95f92xOoKhuANo5pFWLkV4sOw5wbY35yY3+URAaXc0xSsfpMi7zDkcMGBI0heZn
|
36
|
+
pzFb9rGBOA0k+nLwAS8cIEqVWPmUMUkKaKR2vnJdqMS3we9q8wEwaDY6LyrXZOv9
|
37
|
+
4CgqmGAJAGlODzEzJ3HiW3eP24/8A/s9dpA665/gL497Mt+y2M998hZg6KOVHCVV
|
38
|
+
j3JF1h+hY8f/aE/Z1A8rHwh2fSrbBkoJlG+YqqFgstcgVeHPToI+Cqnv4z9MZLxR
|
39
|
+
9USzTsS5aIagiNfKIuugaieGpwphIwy5P3GNRSFpew3yldm47rPAqJi9kgscHrDS
|
40
|
+
WwHkjnLwffytKqAeUyQjXdZMvWtd3KN/bdc4n/mSeu+C7MdQzP9SJI9psTlFkpFk
|
41
|
+
4kQZyb0WGIdRH5Rs3KFQ2UaNl+feNi18QFz6vLKamUGuX58OwkvX5TzvnFQ=
|
42
|
+
=RMIv
|
43
|
+
-----END PGP MESSAGE-----
|
44
|
+
```
|
45
|
+
|
46
|
+
Example output:
|
47
|
+
|
48
|
+
```
|
49
|
+
% released nanoc.yaml
|
50
|
+
```
|
51
|
+
|
52
|
+
```
|
53
|
+
*** Assessing goals…
|
54
|
+
|
55
|
+
gem pushed (released)… ok
|
56
|
+
|
57
|
+
*** Achieving goals…
|
58
|
+
|
59
|
+
shell (bundle exec rake spec)… ok (newly achieved)
|
60
|
+
shell (bundle exec rake rubocop)… ok (newly achieved)
|
61
|
+
gem built (released)… ok (newly achieved)
|
62
|
+
gem pushed (released)… ok (already achieved)
|
63
|
+
|
64
|
+
Finished! :)
|
65
|
+
```
|
66
|
+
|
67
|
+
## Philosophy
|
68
|
+
|
69
|
+
_Released_’s philosophy is threefold:
|
70
|
+
|
71
|
+
* **idempotent**: The tool should be able to be run multiple times for the same version, and steps that have already executed should not cause additional effects. For example, if publishing the gem succeeds, but pushing to GitHub fails, the tool should be able to be run again and pushing to GitHub should succeed without failing on the gem push step.
|
72
|
+
|
73
|
+
* **safe**: If an erroneous condition arises, and continuing could lead to a broken release, the tool should abort. For example, if any of the pre-release verifications fail, the release should not continue.
|
74
|
+
|
75
|
+
* **resilient**: If a release cannot be made properly due to a dependent service being unavailable, the tool should be able to retry the step, or skip it if the step is deemed to be optional.
|
76
|
+
|
77
|
+
## Concepts
|
78
|
+
|
79
|
+
_Released_ has the following concepts:
|
80
|
+
|
81
|
+
* pipeline
|
82
|
+
* goal
|
83
|
+
|
84
|
+
The sections below elaborate on these concepts.
|
85
|
+
|
86
|
+
### Goal
|
87
|
+
|
88
|
+
A goal expresses an individual desired end state. For example:
|
89
|
+
|
90
|
+
* `tests_passing`: the tests are passing
|
91
|
+
* `style_checked`: the style checks are passing
|
92
|
+
* `pkg_built`: the Debian package is built
|
93
|
+
* `pkg_published`: the Debian package is published
|
94
|
+
* `tweet_sent`: the tweet about the release is sent
|
95
|
+
|
96
|
+
Goals are described in a passive voice (e.g. `gem_built`; “the gem has been built”) rather than in the imperative mood (e.g. `build_gem`; “build the gem”). This approach allows expressing what the end state is, rather than how to achieve it. This way, the release process is idempotent: re-running the release process when it has already succeeded before will leave everything as-is, as all goals have already been achieved.
|
97
|
+
|
98
|
+
When attempting to achieve a goal, _Released_ will first _assess_ the goal in order to determine whether the goal can realistically be obtained given the current circumstances. For example, when the goal is to have a Debian package published, the assessment would fail if no working credentials are available.
|
99
|
+
|
100
|
+
_Released_ will take the necessary steps to achieve a given goal, but no more than that. If a goal is already achieved, no steps will be taken. It is therefore quite acceptable try to achieve a goal multiple times.
|
101
|
+
|
102
|
+
TODO: figure out difference between temporary/retriable failures (GitHub is down) and permanent/non-retriable ones (tests are failing)
|
103
|
+
|
104
|
+
### Pipeline
|
105
|
+
|
106
|
+
A pipeline is a sequence of goals. Each goal will be executed in sequence, and a goal will only be executed if no previous goals have failed.
|
107
|
+
|
108
|
+
## Pipeline file
|
109
|
+
|
110
|
+
Strings in `pipeline.yaml` will be replaced according the following rules:
|
111
|
+
|
112
|
+
* Strings starting with `env!` will be replaced with the value of the environment variable whose name is everything after the exclamation mark. For example, `version: env!VERSION` will become `version: 0.1.2` if the `VERSION` environment variable is set to `0.1.2`.
|
113
|
+
|
114
|
+
* Strings starting with `sh!` will be replaced with the output of the shell command following the exclamation mark. For example, `version: sh!echo -n 0.1.2` will become `version: 0.1.2`.
|
115
|
+
|
116
|
+
* Strings starting with `-----BEGIN PGP MESSAGE-----` will be replaced with their content passed through `gpg --decrypt`.
|
117
|
+
|
118
|
+
## Defining custom goal types
|
119
|
+
|
120
|
+
To define a custom goal type, subclass `Released::Goal` and give it an identifier:
|
121
|
+
|
122
|
+
```ruby
|
123
|
+
class FileExists < Released::Goal
|
124
|
+
identifier :file_exists
|
125
|
+
|
126
|
+
def initialize(config)
|
127
|
+
@filename = config.fetch('filename')
|
128
|
+
@contents = config.fetch('contents')
|
129
|
+
end
|
130
|
+
|
131
|
+
def try_achieve
|
132
|
+
File.write(@filename, @contents)
|
133
|
+
end
|
134
|
+
|
135
|
+
def achieved?
|
136
|
+
File.file?(@filename) && File.read(@filename) == @contents
|
137
|
+
end
|
138
|
+
|
139
|
+
def failure_reason
|
140
|
+
if !File.file?(@filename)
|
141
|
+
"file `#{@filename}` does not exist"
|
142
|
+
elsif File.read(@filename) != @contents
|
143
|
+
"file `#{@filename}` does not have the expected contents"
|
144
|
+
else
|
145
|
+
"unknown reason"
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
```
|
150
|
+
|
151
|
+
Define the following methods:
|
152
|
+
|
153
|
+
* `initialize(config)` — Initialize the goal with the given configuration. The configuration is a hash whose keys are strings (not symbols).
|
154
|
+
|
155
|
+
* `try_achieve` — Perform any steps necessary to achieve the goal.
|
156
|
+
|
157
|
+
* `achieved?` — Return `true` if the goal has been achieved, `false` otherwise. This method should not mutate state.
|
158
|
+
|
159
|
+
* `failure_reason` — Return a string containing the reason why the goal was not achieved. This method should not mutate state.
|
160
|
+
|
161
|
+
You might want to implement the following methods as well:
|
162
|
+
|
163
|
+
* `effectful?` — Return `true` if achieving the goal is effectful, i.e. is expected to cause a state change, `false` otherwise. For example, a `gem_built` goal is effectful, while a `test_passed` goal is not. The default is `true`.
|
data/Rakefile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'rubocop/rake_task'
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
|
4
|
+
RuboCop::RakeTask.new(:rubocop) do |task|
|
5
|
+
task.options = %w(--display-cop-names --format simple)
|
6
|
+
task.patterns = ['bin/*', 'lib/**/*.rb', 'spec/**/*.rb']
|
7
|
+
end
|
8
|
+
|
9
|
+
RSpec::Core::RakeTask.new(:spec) do |t|
|
10
|
+
t.verbose = false
|
11
|
+
end
|
12
|
+
|
13
|
+
task default: [:spec, :rubocop]
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Released
|
2
|
+
class Goal
|
3
|
+
extend DDPlugin::Plugin
|
4
|
+
|
5
|
+
# @abstract
|
6
|
+
def initialize(_config = {})
|
7
|
+
raise NotImplementedError
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_s
|
11
|
+
self.class.identifier.to_s
|
12
|
+
end
|
13
|
+
|
14
|
+
def assessable?
|
15
|
+
respond_to?(:assess)
|
16
|
+
end
|
17
|
+
|
18
|
+
# @abstract
|
19
|
+
def effectful?
|
20
|
+
true
|
21
|
+
end
|
22
|
+
|
23
|
+
# @abstract
|
24
|
+
def try_achieve
|
25
|
+
raise NotImplementedError
|
26
|
+
end
|
27
|
+
|
28
|
+
# @abstract
|
29
|
+
def achieved?
|
30
|
+
raise NotImplementedError
|
31
|
+
end
|
32
|
+
|
33
|
+
# @abstract
|
34
|
+
def failure_reason
|
35
|
+
raise NotImplementedError
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Released
|
2
|
+
module Goals
|
3
|
+
class FileExists < Released::Goal
|
4
|
+
identifier :file_exists
|
5
|
+
|
6
|
+
def initialize(config)
|
7
|
+
@filename = config.fetch('filename')
|
8
|
+
@contents = config.fetch('contents')
|
9
|
+
end
|
10
|
+
|
11
|
+
def try_achieve
|
12
|
+
File.write(@filename, @contents)
|
13
|
+
end
|
14
|
+
|
15
|
+
def achieved?
|
16
|
+
File.file?(@filename) && File.read(@filename) == @contents
|
17
|
+
end
|
18
|
+
|
19
|
+
def failure_reason
|
20
|
+
if !File.file?(@filename)
|
21
|
+
"file `#{@filename}` does not exist"
|
22
|
+
elsif File.read(@filename) != @contents
|
23
|
+
"file `#{@filename}` does not have the expected contents"
|
24
|
+
else
|
25
|
+
'unknown reason'
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Released
|
2
|
+
module Goals
|
3
|
+
class GemBuilt < Released::Goal
|
4
|
+
identifier :gem_built
|
5
|
+
|
6
|
+
def initialize(config = {})
|
7
|
+
@name = config.fetch('name')
|
8
|
+
@version = config.fetch('version')
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_s
|
12
|
+
"gem built (#{@name})"
|
13
|
+
end
|
14
|
+
|
15
|
+
def try_achieve
|
16
|
+
# TODO: remove
|
17
|
+
Dir['*.gem'].each { |f| FileUtils.rm_f(f) }
|
18
|
+
|
19
|
+
stdout = ''
|
20
|
+
stderr = ''
|
21
|
+
piper = Nanoc::Extra::Piper.new(stdout: stdout, stderr: stderr)
|
22
|
+
|
23
|
+
begin
|
24
|
+
gemspec_file_path = "#{@name}.gemspec"
|
25
|
+
piper.run(['gem', 'build', gemspec_file_path], [])
|
26
|
+
rescue
|
27
|
+
raise "Failed to build gem: #{stderr}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def achieved?
|
32
|
+
File.file?(expected_name)
|
33
|
+
end
|
34
|
+
|
35
|
+
def failure_reason
|
36
|
+
"expected the file #{expected_name} to exist"
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def expected_name
|
42
|
+
@_expected_name ||= @name + '-' + @version + '.gem'
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'gems'
|
2
|
+
|
3
|
+
module Released
|
4
|
+
module Goals
|
5
|
+
class GemPushed < Released::Goal
|
6
|
+
identifier :gem_pushed
|
7
|
+
|
8
|
+
BASE_URL = 'https://rubygems.org'.freeze
|
9
|
+
|
10
|
+
def initialize(config = {})
|
11
|
+
@name = config.fetch('name')
|
12
|
+
@version = config.fetch('version')
|
13
|
+
|
14
|
+
@rubygems_repo = Gems::Client.new(
|
15
|
+
key: config.fetch('authorization'),
|
16
|
+
host: config.fetch('rubygems_base_url', BASE_URL),
|
17
|
+
)
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_s
|
21
|
+
"gem pushed (#{@name})"
|
22
|
+
end
|
23
|
+
|
24
|
+
def assess
|
25
|
+
gems = @rubygems_repo.gems
|
26
|
+
if gems =~ /Access Denied/
|
27
|
+
raise 'Authorization failed'
|
28
|
+
end
|
29
|
+
|
30
|
+
unless gems.any? { |g| g['name'] == @name }
|
31
|
+
raise 'List of owned gems does not include request gem'
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def try_achieve
|
36
|
+
filename = @name + '-' + @version + '.gem'
|
37
|
+
unless File.file?(filename)
|
38
|
+
raise "no such gem file: #{filename}"
|
39
|
+
end
|
40
|
+
|
41
|
+
File.open(filename, 'r') do |io|
|
42
|
+
@rubygems_repo.push(io)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def achieved?
|
47
|
+
gems = @rubygems_repo.gems
|
48
|
+
if gems =~ /Access Denied/
|
49
|
+
raise 'Authorization failed'
|
50
|
+
end
|
51
|
+
|
52
|
+
gems.any? { |g| g['name'] == @name && g['version'] == @version }
|
53
|
+
end
|
54
|
+
|
55
|
+
def failure_reason
|
56
|
+
"expected list of gems to contain “#{@name}”, version #{@version}"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'git'
|
2
|
+
|
3
|
+
module Released
|
4
|
+
module Goals
|
5
|
+
class GitRefPushed < Released::Goal
|
6
|
+
identifier :git_ref_pushed
|
7
|
+
|
8
|
+
def initialize(config)
|
9
|
+
@working_dir = config.fetch('working_dir')
|
10
|
+
@remote = config.fetch('remote')
|
11
|
+
@branch = config.fetch('branch')
|
12
|
+
end
|
13
|
+
|
14
|
+
def try_achieve
|
15
|
+
g.push(@remote, @branch)
|
16
|
+
end
|
17
|
+
|
18
|
+
def achieved?
|
19
|
+
there_branch = g.branches["#{@remote}/#{@branch}"]
|
20
|
+
return false if there_branch.nil?
|
21
|
+
there = there_branch.gcommit.sha
|
22
|
+
|
23
|
+
here = g.object('HEAD').sha
|
24
|
+
|
25
|
+
here == there
|
26
|
+
end
|
27
|
+
|
28
|
+
def failure_reason
|
29
|
+
"HEAD does not exist on #{@remote}/#{@branch}"
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def g
|
35
|
+
@_g ||= Git.open(@working_dir)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'octokit'
|
2
|
+
|
3
|
+
module Released
|
4
|
+
module Goals
|
5
|
+
class GitHubReleaseExists < Released::Goal
|
6
|
+
identifier :github_release_exists
|
7
|
+
|
8
|
+
def initialize(config)
|
9
|
+
@repository_name = config.fetch('repository_name') # e.g. nanoc/nanoc
|
10
|
+
@tag = config.fetch('tag')
|
11
|
+
@release_notes = config.fetch('release_notes')
|
12
|
+
end
|
13
|
+
|
14
|
+
def try_achieve
|
15
|
+
client = Octokit::Client.new(netrc: true)
|
16
|
+
client.create_release(
|
17
|
+
@repository_name, @tag,
|
18
|
+
body: @release_notes
|
19
|
+
)
|
20
|
+
end
|
21
|
+
|
22
|
+
def achieved?
|
23
|
+
client = Octokit::Client.new(netrc: true)
|
24
|
+
releases = client.releases(@repository_name)
|
25
|
+
releases.any? { |r| r.tag_name == @tag }
|
26
|
+
end
|
27
|
+
|
28
|
+
def failure_reason
|
29
|
+
"no release exists in repository #{@repository_name} for tag #{@tag}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Released
|
2
|
+
module Goals
|
3
|
+
# TODO: rename
|
4
|
+
class Shell < Released::Goal
|
5
|
+
identifier :shell
|
6
|
+
|
7
|
+
def initialize(config = {})
|
8
|
+
@command = config.fetch('command')
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_s
|
12
|
+
"shell (#{@command})"
|
13
|
+
end
|
14
|
+
|
15
|
+
def effectful?
|
16
|
+
false
|
17
|
+
end
|
18
|
+
|
19
|
+
def assess
|
20
|
+
sleep 1
|
21
|
+
end
|
22
|
+
|
23
|
+
def try_achieve
|
24
|
+
stdout = ''
|
25
|
+
stderr = ''
|
26
|
+
piper = Nanoc::Extra::Piper.new(stdout: stdout, stderr: stderr)
|
27
|
+
|
28
|
+
begin
|
29
|
+
piper.run(@command, [])
|
30
|
+
rescue
|
31
|
+
raise "Failed execute command!\n\nstderr:\n#{stderr}\n\nstdout:\n#{stdout}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def achieved?
|
36
|
+
false
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Released
|
2
|
+
module Goals
|
3
|
+
end
|
4
|
+
end
|
5
|
+
|
6
|
+
require_relative 'goals/file_exists'
|
7
|
+
require_relative 'goals/gem_built'
|
8
|
+
require_relative 'goals/gem_pushed'
|
9
|
+
require_relative 'goals/git_ref_pushed'
|
10
|
+
require_relative 'goals/github_release_exists'
|
11
|
+
require_relative 'goals/shell'
|