fa-harness-tools 1.0.5 → 1.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 +4 -4
- data/Gemfile.lock +7 -1
- data/README.md +2 -2
- data/examples/production-preflight-checks/production-preflight-checks.sh +2 -0
- data/exe/check-branch-protection +2 -8
- data/exe/check-forward-deploy +1 -7
- data/exe/check-recent-deploy +1 -7
- data/exe/check-schedule +1 -7
- data/fa-harness-tools.gemspec +1 -0
- data/lib/fa-harness-tools/check_branch_protection.rb +11 -2
- data/lib/fa-harness-tools/check_forward_deploy.rb +14 -3
- data/lib/fa-harness-tools/check_logger.rb +34 -0
- data/lib/fa-harness-tools/check_recent_deploy.rb +14 -3
- data/lib/fa-harness-tools/check_schedule.rb +13 -2
- data/lib/fa-harness-tools/github_client.rb +19 -12
- data/lib/fa-harness-tools/harness_context.rb +1 -1
- data/lib/fa-harness-tools/version.rb +1 -1
- data/lib/fa-harness-tools.rb +1 -0
- metadata +17 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 421353286e74cdc70badf595a611fc8d9a34a5f14e4341cacac88bbba11cee19
|
4
|
+
data.tar.gz: f80a236d16ea035736191b65c2138abec2cff6c126d36b930b3866e01d50489d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d610913dbd0979c1d6d5a19a04fe68b511042e333fbd4a616919855fb1d34dca130ea10e1e6a5d58d0579ad86fd796f9e2983a19ae222ddd2dea9b1015bbd229
|
7
|
+
data.tar.gz: 0fa44445386572aa266ef138f1bcc1bda3b84ea8b0fe6f98cfd86f17d58c0e5f045650c9b19bbc7309018f1cb9e0237f3e07f34f01e75b0110009d20a26035c3
|
data/Gemfile.lock
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
fa-harness-tools (1.0
|
4
|
+
fa-harness-tools (1.1.0)
|
5
5
|
octokit (~> 4.0)
|
6
|
+
pastel (~> 0.7)
|
6
7
|
tzinfo (~> 2.0)
|
7
8
|
tzinfo-data (~> 1.0)
|
8
9
|
|
@@ -13,12 +14,16 @@ GEM
|
|
13
14
|
public_suffix (>= 2.0.2, < 5.0)
|
14
15
|
concurrent-ruby (1.1.6)
|
15
16
|
diff-lcs (1.3)
|
17
|
+
equatable (0.6.1)
|
16
18
|
faraday (1.0.1)
|
17
19
|
multipart-post (>= 1.2, < 3)
|
18
20
|
multipart-post (2.1.1)
|
19
21
|
octokit (4.18.0)
|
20
22
|
faraday (>= 0.9)
|
21
23
|
sawyer (~> 0.8.0, >= 0.5.3)
|
24
|
+
pastel (0.7.4)
|
25
|
+
equatable (~> 0.6)
|
26
|
+
tty-color (~> 0.5)
|
22
27
|
public_suffix (4.0.5)
|
23
28
|
rake (13.0.1)
|
24
29
|
rspec (3.9.0)
|
@@ -38,6 +43,7 @@ GEM
|
|
38
43
|
addressable (>= 2.3.5)
|
39
44
|
faraday (> 0.8, < 2.0)
|
40
45
|
timecop (0.9.1)
|
46
|
+
tty-color (0.5.1)
|
41
47
|
tzinfo (2.0.2)
|
42
48
|
concurrent-ruby (~> 1.0)
|
43
49
|
tzinfo-data (1.2020.1)
|
data/README.md
CHANGED
@@ -24,9 +24,9 @@ Examples below use [variables defined by Harness](https://docs.harness.io/articl
|
|
24
24
|
|
25
25
|
Full scripts that can be used in Harness are available in the [examples/](examples/) directory.
|
26
26
|
|
27
|
-
###
|
27
|
+
### Optional environment variables
|
28
28
|
|
29
|
-
* `GITHUB_OAUTH_TOKEN` must be exported, containing a valid [personal access token](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line) for GitHub
|
29
|
+
* To access private repositories, `GITHUB_OAUTH_TOKEN` must be exported, containing a valid [personal access token](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line) for GitHub
|
30
30
|
|
31
31
|
### check-branch-brotection
|
32
32
|
|
@@ -9,11 +9,13 @@ export GITHUB_OAUTH_TOKEN="${secrets.getValue("github-oauth-token")}"
|
|
9
9
|
run() {
|
10
10
|
CMD=$1
|
11
11
|
shift
|
12
|
+
echo
|
12
13
|
$CMD \
|
13
14
|
--build-no "${artifact.buildNo}" \
|
14
15
|
--environment "${env.name}" \
|
15
16
|
--repository "${artifact.source.repositoryName}" \
|
16
17
|
"$@"
|
18
|
+
echo
|
17
19
|
}
|
18
20
|
|
19
21
|
# 1. Check we're within the daily deployment schedule
|
data/exe/check-branch-protection
CHANGED
@@ -32,7 +32,7 @@ OptionParser.new do |opts|
|
|
32
32
|
end.parse!
|
33
33
|
|
34
34
|
client = FaHarnessTools::GithubClient.new(
|
35
|
-
oauth_token: ENV.fetch("GITHUB_OAUTH_TOKEN"),
|
35
|
+
oauth_token: ENV.fetch("GITHUB_OAUTH_TOKEN", nil),
|
36
36
|
owner: options.fetch(:github_owner),
|
37
37
|
repo: options.fetch(:repo),
|
38
38
|
)
|
@@ -48,10 +48,4 @@ result = FaHarnessTools::CheckBranchProtection.new(
|
|
48
48
|
branch: options.fetch(:branch),
|
49
49
|
).verify?
|
50
50
|
|
51
|
-
|
52
|
-
puts result.last
|
53
|
-
exit 0
|
54
|
-
else
|
55
|
-
$stderr.puts result.last
|
56
|
-
exit 1
|
57
|
-
end
|
51
|
+
exit result ? 0 : 1
|
data/exe/check-forward-deploy
CHANGED
data/exe/check-recent-deploy
CHANGED
data/exe/check-schedule
CHANGED
data/fa-harness-tools.gemspec
CHANGED
@@ -27,6 +27,7 @@ Gem::Specification.new do |spec|
|
|
27
27
|
spec.require_paths = ["lib"]
|
28
28
|
|
29
29
|
spec.add_runtime_dependency "octokit", "~> 4.0"
|
30
|
+
spec.add_runtime_dependency "pastel", "~> 0.7"
|
30
31
|
spec.add_runtime_dependency "tzinfo", "~> 2.0"
|
31
32
|
spec.add_runtime_dependency "tzinfo-data", "~> 1.0"
|
32
33
|
|
@@ -5,14 +5,23 @@ module FaHarnessTools
|
|
5
5
|
@client = client
|
6
6
|
@context = context
|
7
7
|
@branch = branch
|
8
|
+
@logger = CheckLogger.new(
|
9
|
+
name: "Check branch protection",
|
10
|
+
description: "Only allow commits on the #{@branch} branch to be deployed",
|
11
|
+
)
|
8
12
|
end
|
9
13
|
|
10
14
|
def verify?
|
15
|
+
@logger.start
|
16
|
+
@logger.context_info(@client, @context)
|
17
|
+
|
11
18
|
new_sha = @context.new_commit_sha
|
19
|
+
|
20
|
+
@logger.info("checking if #{@branch} branch contains the commit")
|
12
21
|
if @client.branch_contains?(@branch, new_sha)
|
13
|
-
|
22
|
+
@logger.pass "#{@branch} contains #{new_sha}"
|
14
23
|
else
|
15
|
-
|
24
|
+
@logger.fail "#{@branch} does not contain #{new_sha}"
|
16
25
|
end
|
17
26
|
end
|
18
27
|
end
|
@@ -13,25 +13,36 @@ module FaHarnessTools
|
|
13
13
|
@client = client
|
14
14
|
@context = context
|
15
15
|
@tag_prefix = tag_prefix
|
16
|
+
@logger = CheckLogger.new(
|
17
|
+
name: "Check forward deploy",
|
18
|
+
description: "Only allow deployments that are newer than what's currently deployed",
|
19
|
+
)
|
16
20
|
end
|
17
21
|
|
18
22
|
def verify?
|
23
|
+
@logger.start
|
24
|
+
@logger.context_info(@client, @context)
|
25
|
+
|
19
26
|
current_tag = @client.last_deploy_tag(
|
20
27
|
prefix: @tag_prefix, environment: @context.environment)
|
21
28
|
|
22
29
|
if current_tag.nil?
|
23
30
|
# If no previous deploys we need to let it deploy otherwise it will
|
24
31
|
# never get past this check!
|
25
|
-
|
32
|
+
@logger.info "no #{@tag_prefix} tag was found, so this must be the first deployment"
|
33
|
+
return @logger.pass("this is the first recorded deployment so is permitted")
|
26
34
|
end
|
27
35
|
|
36
|
+
@logger.info("the most recent deployment is #{current_tag[:name]}")
|
37
|
+
|
28
38
|
current_deployed_rev = current_tag[:commit][:sha]
|
29
39
|
rev = @context.new_commit_sha
|
40
|
+
@logger.info("which means the currently deployed commit is #{current_deployed_rev}")
|
30
41
|
|
31
42
|
if @client.is_ancestor_of?(current_deployed_rev, rev)
|
32
|
-
|
43
|
+
@logger.pass "the commit being deployed is more recent than the currently deployed commit"
|
33
44
|
else
|
34
|
-
|
45
|
+
@logger.fail "the commit being deployed is before the currently deployed commit, so would revert changes"
|
35
46
|
end
|
36
47
|
end
|
37
48
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require "pastel"
|
2
|
+
|
3
|
+
module FaHarnessTools
|
4
|
+
class CheckLogger
|
5
|
+
def initialize(name:, description:)
|
6
|
+
@name = name
|
7
|
+
@description = description
|
8
|
+
@pastel = Pastel.new(enabled: true)
|
9
|
+
end
|
10
|
+
|
11
|
+
def start
|
12
|
+
puts @pastel.cyan(@pastel.bold(@name), %{ (#{@description})})
|
13
|
+
end
|
14
|
+
|
15
|
+
def info(message)
|
16
|
+
puts " ... #{message}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def context_info(client, context)
|
20
|
+
info("we're deploying repo #{client.owner_repo} into environment #{context.environment}")
|
21
|
+
info("we're trying to deploy commit #{context.new_commit_sha}")
|
22
|
+
end
|
23
|
+
|
24
|
+
def pass(message)
|
25
|
+
puts @pastel.green("PASS: #{message}")
|
26
|
+
true
|
27
|
+
end
|
28
|
+
|
29
|
+
def fail(message)
|
30
|
+
puts @pastel.red("FAIL: #{message}")
|
31
|
+
false
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -27,9 +27,16 @@ module FaHarnessTools
|
|
27
27
|
@context = context
|
28
28
|
@tag_prefix = tag_prefix
|
29
29
|
@allowed_rollback_count = allowed_rollback_count
|
30
|
+
@logger = CheckLogger.new(
|
31
|
+
name: "Check recent deploys",
|
32
|
+
description: "Only allow deployments of recent commits, up to #{@allowed_rollback_count} deployment rollbacks",
|
33
|
+
)
|
30
34
|
end
|
31
35
|
|
32
36
|
def verify?
|
37
|
+
@logger.start
|
38
|
+
@logger.context_info(@client, @context)
|
39
|
+
|
33
40
|
tags = @client.
|
34
41
|
all_deploy_tags(prefix: @tag_prefix, environment: @context.environment).
|
35
42
|
sort_by { |tag| tag[:name] }
|
@@ -39,16 +46,20 @@ module FaHarnessTools
|
|
39
46
|
if latest_allowed_tag.nil?
|
40
47
|
# If no previous deploys we need to let it deploy otherwise it will
|
41
48
|
# never get past this check!
|
42
|
-
|
49
|
+
@logger.info "no #{@tag_prefix} tag was found, so this must be the first deployment"
|
50
|
+
return @logger.pass("this is the first recorded deployment so is permitted")
|
43
51
|
end
|
44
52
|
|
53
|
+
@logger.info("the most recent tag allowed is #{latest_allowed_tag[:name]}")
|
54
|
+
|
45
55
|
latest_allowed_rev = @client.get_commit_sha_from_tag(latest_allowed_tag)
|
46
56
|
rev = @context.new_commit_sha
|
57
|
+
@logger.info("which means the most recent commit allowed is #{latest_allowed_rev}")
|
47
58
|
|
48
59
|
if @client.is_ancestor_of?(latest_allowed_rev, rev)
|
49
|
-
|
60
|
+
@logger.pass "the commit being deployed is more recent than the last permitted rollback commit"
|
50
61
|
else
|
51
|
-
|
62
|
+
@logger.fail "the commit being deployed is older than the last permitted rollback commit"
|
52
63
|
end
|
53
64
|
end
|
54
65
|
end
|
@@ -9,22 +9,33 @@ module FaHarnessTools
|
|
9
9
|
class CheckSchedule
|
10
10
|
def initialize(timezone: "Europe/London")
|
11
11
|
tz = TZInfo::Timezone.get(timezone)
|
12
|
+
@timezone = timezone
|
12
13
|
@now = tz.to_local(Time.now.utc)
|
14
|
+
@logger = CheckLogger.new(
|
15
|
+
name: "Check deployment schedule",
|
16
|
+
description: "Only allow deployments within certain times of the day",
|
17
|
+
)
|
13
18
|
end
|
14
19
|
|
15
20
|
def verify?
|
21
|
+
@logger.start
|
22
|
+
@logger.info("operating in the #{@timezone} timezone")
|
23
|
+
@logger.info("local time is #{@now}")
|
24
|
+
|
16
25
|
permitted = false
|
17
26
|
case @now.wday
|
18
27
|
when 1..4
|
28
|
+
@logger.info("deployments are allowed between 9am to 4pm today (Mon-Thu)")
|
19
29
|
permitted = true if @now.hour >= 9 && @now.hour < 16
|
20
30
|
when 5
|
31
|
+
@logger.info("deployments are allowed between 9am to 12pm today (Fri)")
|
21
32
|
permitted = true if @now.hour >= 9 && @now.hour < 12
|
22
33
|
end
|
23
34
|
|
24
35
|
if permitted
|
25
|
-
|
36
|
+
@logger.pass "inside the deployment schedule"
|
26
37
|
else
|
27
|
-
|
38
|
+
@logger.fail "outside the deployment schedule"
|
28
39
|
end
|
29
40
|
end
|
30
41
|
end
|
@@ -9,6 +9,8 @@ module FaHarnessTools
|
|
9
9
|
@octokit = Octokit::Client.new(access_token: oauth_token)
|
10
10
|
@owner = owner
|
11
11
|
@repo = repo
|
12
|
+
@oauth_token = oauth_token
|
13
|
+
validate_repo
|
12
14
|
end
|
13
15
|
|
14
16
|
def owner_repo
|
@@ -91,14 +93,20 @@ module FaHarnessTools
|
|
91
93
|
#
|
92
94
|
# @return [Bool] True is <ancestor> is ancestor of <commit>
|
93
95
|
def is_ancestor_of?(ancestor, commit)
|
94
|
-
|
96
|
+
# Compare returns the merge base, the common point in history between the
|
97
|
+
# two commits. If X is the ancestor of Y, then the merge base must be X.
|
98
|
+
# If not, it's a different branch.
|
99
|
+
@octokit.compare(owner_repo, ancestor, commit)[:merge_base_commit][:sha] == get_commit_sha(ancestor)
|
95
100
|
end
|
96
101
|
|
97
102
|
# Checks if <commit> is on branch <branch>
|
98
103
|
#
|
99
104
|
# @return [Bool] True is <commit> is on <branch>
|
100
105
|
def branch_contains?(branch, commit)
|
101
|
-
|
106
|
+
# The same implementation works for this question. We have both methods
|
107
|
+
# to make the intent clearer and also this one is guaranteed to resolve a
|
108
|
+
# branch name for the first argument.
|
109
|
+
is_ancestor_of?(commit, branch)
|
102
110
|
end
|
103
111
|
|
104
112
|
# Creates a Git tag
|
@@ -112,16 +120,15 @@ module FaHarnessTools
|
|
112
120
|
|
113
121
|
private
|
114
122
|
|
115
|
-
#
|
116
|
-
#
|
117
|
-
def
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
result
|
123
|
+
# Validates a repository exists.
|
124
|
+
# Raises a `LookupError` in the event a repository can't be found.
|
125
|
+
def validate_repo
|
126
|
+
@octokit.repo(owner_repo)
|
127
|
+
|
128
|
+
rescue Octokit::NotFound
|
129
|
+
message = "Unable to find repository #{owner_repo}"
|
130
|
+
message = "#{message}. If the repository is private, try setting GITHUB_OAUTH_TOKEN" unless @oauth_token
|
131
|
+
raise LookupError, message
|
125
132
|
end
|
126
133
|
end
|
127
134
|
end
|
data/lib/fa-harness-tools.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fa-harness-tools
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- FreeAgent
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-05-
|
11
|
+
date: 2020-05-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: octokit
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '4.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: pastel
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.7'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.7'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: tzinfo
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -146,6 +160,7 @@ files:
|
|
146
160
|
- lib/fa-harness-tools.rb
|
147
161
|
- lib/fa-harness-tools/check_branch_protection.rb
|
148
162
|
- lib/fa-harness-tools/check_forward_deploy.rb
|
163
|
+
- lib/fa-harness-tools/check_logger.rb
|
149
164
|
- lib/fa-harness-tools/check_recent_deploy.rb
|
150
165
|
- lib/fa-harness-tools/check_schedule.rb
|
151
166
|
- lib/fa-harness-tools/github_client.rb
|