fa-harness-tools 1.0.2 → 1.2.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 +24 -10
- data/README.md +4 -2
- data/examples/create-deployment-tag/README.md +17 -0
- data/examples/create-deployment-tag/create-deployment-tag.sh +19 -0
- data/examples/production-preflight-checks/README.md +20 -0
- data/examples/production-preflight-checks/production-preflight-checks.sh +35 -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 +24 -9
- data/fa-harness-tools.gemspec +3 -1
- data/lib/fa-harness-tools.rb +3 -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 +16 -5
- data/lib/fa-harness-tools/check_schedule.rb +19 -12
- data/lib/fa-harness-tools/github_client.rb +66 -8
- data/lib/fa-harness-tools/harness_context.rb +1 -1
- data/lib/fa-harness-tools/schedule.rb +22 -0
- data/lib/fa-harness-tools/version.rb +1 -1
- metadata +38 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 51253c635049f814f42fcac831fd937ed713254a29547cf5915fc2861848a52e
|
4
|
+
data.tar.gz: ed786f440ef207a1f2d6b4c920d82f673c1726c81571f4d0cec692f02a2c42a4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3cb5bc18ffb6355a629de71c8220f851e12a85814e59549f8cfd7b259d4425d4422f81e0d85dc2e80e05e9fa76c5d5b3dfb12fe161fc61a9b87550dbd61300af
|
7
|
+
data.tar.gz: 73877cc9224058d573a41b3ab1b34e67d0fe8bbf06c17a408b37f646d552d7b627fab219d2b042654297b9ccb9dae09fbc4f1d833a6fae3e798b3603970fc320
|
data/Gemfile.lock
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
fa-harness-tools (1.0
|
4
|
+
fa-harness-tools (1.2.0)
|
5
|
+
fugit (~> 1.3)
|
5
6
|
octokit (~> 4.0)
|
7
|
+
pastel (~> 0.7)
|
6
8
|
tzinfo (~> 2.0)
|
7
9
|
tzinfo-data (~> 1.0)
|
8
10
|
|
@@ -11,15 +13,26 @@ GEM
|
|
11
13
|
specs:
|
12
14
|
addressable (2.7.0)
|
13
15
|
public_suffix (>= 2.0.2, < 5.0)
|
14
|
-
concurrent-ruby (1.1.
|
16
|
+
concurrent-ruby (1.1.6)
|
15
17
|
diff-lcs (1.3)
|
16
|
-
|
18
|
+
equatable (0.6.1)
|
19
|
+
et-orbi (1.2.4)
|
20
|
+
tzinfo
|
21
|
+
faraday (1.0.1)
|
17
22
|
multipart-post (>= 1.2, < 3)
|
23
|
+
fugit (1.3.5)
|
24
|
+
et-orbi (~> 1.1, >= 1.1.8)
|
25
|
+
raabro (~> 1.1)
|
18
26
|
multipart-post (2.1.1)
|
19
|
-
octokit (4.
|
27
|
+
octokit (4.18.0)
|
28
|
+
faraday (>= 0.9)
|
20
29
|
sawyer (~> 0.8.0, >= 0.5.3)
|
21
|
-
|
22
|
-
|
30
|
+
pastel (0.7.4)
|
31
|
+
equatable (~> 0.6)
|
32
|
+
tty-color (~> 0.5)
|
33
|
+
public_suffix (4.0.5)
|
34
|
+
raabro (1.3.1)
|
35
|
+
rake (13.0.1)
|
23
36
|
rspec (3.9.0)
|
24
37
|
rspec-core (~> 3.9.0)
|
25
38
|
rspec-expectations (~> 3.9.0)
|
@@ -37,9 +50,10 @@ GEM
|
|
37
50
|
addressable (>= 2.3.5)
|
38
51
|
faraday (> 0.8, < 2.0)
|
39
52
|
timecop (0.9.1)
|
40
|
-
|
53
|
+
tty-color (0.5.1)
|
54
|
+
tzinfo (2.0.2)
|
41
55
|
concurrent-ruby (~> 1.0)
|
42
|
-
tzinfo-data (1.
|
56
|
+
tzinfo-data (1.2020.1)
|
43
57
|
tzinfo (>= 1.0.0)
|
44
58
|
|
45
59
|
PLATFORMS
|
@@ -48,9 +62,9 @@ PLATFORMS
|
|
48
62
|
DEPENDENCIES
|
49
63
|
bundler (~> 1.0)
|
50
64
|
fa-harness-tools!
|
51
|
-
rake (~>
|
65
|
+
rake (~> 13.0)
|
52
66
|
rspec (~> 3.8)
|
53
67
|
timecop (~> 0.9)
|
54
68
|
|
55
69
|
BUNDLED WITH
|
56
|
-
1.
|
70
|
+
1.17.2
|
data/README.md
CHANGED
@@ -22,9 +22,11 @@ Or install it yourself as:
|
|
22
22
|
|
23
23
|
Examples below use [variables defined by Harness](https://docs.harness.io/article/9dvxcegm90-variables) so should be suitable to use directly in Harness scripts.
|
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
|
+
|
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
|
28
30
|
|
29
31
|
### check-branch-brotection
|
30
32
|
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# Create deployment Git tags
|
2
|
+
|
3
|
+
## Purpose
|
4
|
+
|
5
|
+
Can be added to a Harness pipeline to add a Git tag on every deployment. The tags can then be used by the other pre-flight checks.
|
6
|
+
|
7
|
+
## Requirements
|
8
|
+
|
9
|
+
1. Add a GitHub OAuth token to the Harness secrets manager, named `github-oauth-token`
|
10
|
+
2. Assumes the artifact build number is the commit ID
|
11
|
+
3. fa-harness-tools is installed on the Harness delegates (`gem install -v $VERSION fa-harness-tools`)
|
12
|
+
|
13
|
+
## Installation
|
14
|
+
|
15
|
+
Add the script to the Harness template library and then add to the last phase of the deployment workflow.
|
16
|
+
|
17
|
+
Define the `ONLY_ENVIRONMENT` variable input on the template, defaulting to `false`.
|
@@ -0,0 +1,19 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
#
|
3
|
+
# Creates a Git tag to mark successful deployment with fa-harness-tools
|
4
|
+
#
|
5
|
+
# Optionally set ONLY_ENVIRONMENT to only tag when running a deployment in
|
6
|
+
# that Harness environment.
|
7
|
+
|
8
|
+
set -e
|
9
|
+
|
10
|
+
export GITHUB_OAUTH_TOKEN="${secrets.getValue("github-oauth-token")}"
|
11
|
+
|
12
|
+
if [ -z "${ONLY_ENVIRONMENT}" -o "${ONLY_ENVIRONMENT}" = "${env.name}" ]; then
|
13
|
+
create-deployment-tag \
|
14
|
+
--build-no "${artifact.buildNo}" \
|
15
|
+
--environment "${env.name}" \
|
16
|
+
--repository "${artifact.source.repositoryName}" \
|
17
|
+
--tagger-email "noreply@example.com" \
|
18
|
+
--tagger-name "Harness"
|
19
|
+
fi
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# Production pre-flight checks
|
2
|
+
|
3
|
+
## Purpose
|
4
|
+
|
5
|
+
Can be added to a Harness pipeline to enforce a set of strict requirements for production deployments:
|
6
|
+
|
7
|
+
1. Only deploy within the daily deployment window/schedule
|
8
|
+
2. Only deploy builds from the master branch
|
9
|
+
3. Automated (triggered) deployments may only deploy forwards
|
10
|
+
4. Manual deployments may only deploy forwards or roll back three deployments
|
11
|
+
|
12
|
+
## Requirements
|
13
|
+
|
14
|
+
1. Add a GitHub OAuth token to the Harness secrets manager, named `github-oauth-token`
|
15
|
+
2. Assumes the artifact build number is the commit ID
|
16
|
+
3. fa-harness-tools is installed on the Harness delegates (`gem install -v $VERSION fa-harness-tools`)
|
17
|
+
|
18
|
+
## Installation
|
19
|
+
|
20
|
+
Add the script to the Harness template library and then add to an early phase of the deployment workflow.
|
@@ -0,0 +1,35 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
#
|
3
|
+
# Runs production deployment checks from fa-harness-tools
|
4
|
+
|
5
|
+
set -e
|
6
|
+
|
7
|
+
export GITHUB_OAUTH_TOKEN="${secrets.getValue("github-oauth-token")}"
|
8
|
+
|
9
|
+
run() {
|
10
|
+
CMD=$1
|
11
|
+
shift
|
12
|
+
echo
|
13
|
+
$CMD \
|
14
|
+
--build-no "${artifact.buildNo}" \
|
15
|
+
--environment "${env.name}" \
|
16
|
+
--repository "${artifact.source.repositoryName}" \
|
17
|
+
"$@"
|
18
|
+
echo
|
19
|
+
}
|
20
|
+
|
21
|
+
# 1. Check we're within the daily deployment schedule
|
22
|
+
check-schedule
|
23
|
+
|
24
|
+
# 2. Check the commit is on the master branch
|
25
|
+
run check-branch-protection
|
26
|
+
|
27
|
+
if [[ "${deploymentTriggeredBy}" =~ "Deployment Trigger" ]]; then
|
28
|
+
# 3. For automated deployments (trigger from CI), check deployment is fast-forward
|
29
|
+
run check-forward-deploy
|
30
|
+
else
|
31
|
+
# 3. For user deployments, check deployment is fast-forward or within last three deployments for rollbacks
|
32
|
+
run check-recent-deploy --allowed-rollback-count 3
|
33
|
+
fi
|
34
|
+
|
35
|
+
exit 0
|
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
@@ -3,17 +3,32 @@
|
|
3
3
|
require "fa-harness-tools"
|
4
4
|
require "optparse"
|
5
5
|
|
6
|
-
options = {
|
6
|
+
options = {
|
7
|
+
schedules: [],
|
8
|
+
timezone: "Europe/London",
|
9
|
+
}
|
7
10
|
OptionParser.new do |opts|
|
8
11
|
opts.banner = "Usage: check-schedule [options]"
|
9
|
-
end.parse!
|
10
12
|
|
11
|
-
|
13
|
+
opts.on("--schedule CRON", "Schedule defines a window in which a deployment can take place. Accepts cron syntax, e.g. '* 9-15 * * mon-thu'") do |v|
|
14
|
+
options[:schedules] << v
|
15
|
+
end
|
16
|
+
|
17
|
+
opts.on("--timezone TIMEZONE", "Specify the timezone which the schedule will be checked in, e.g. 'Europe/London'") do |v|
|
18
|
+
options[:timezone] = v
|
19
|
+
end
|
20
|
+
end.parse!
|
12
21
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
22
|
+
def schedules(options)
|
23
|
+
if options[:schedules].length == 0
|
24
|
+
options[:schedules] = [
|
25
|
+
"* 9-15 * * mon-thu",
|
26
|
+
"* 9-11 * * fri",
|
27
|
+
]
|
28
|
+
end
|
29
|
+
options[:schedules].map { |schedule| FaHarnessTools::Schedule.new(schedule: schedule.to_s) }
|
19
30
|
end
|
31
|
+
|
32
|
+
result = FaHarnessTools::CheckSchedule.new(timezone: options[:timezone], schedules: schedules(options)).verify?
|
33
|
+
|
34
|
+
exit result ? 0 : 1
|
data/fa-harness-tools.gemspec
CHANGED
@@ -27,11 +27,13 @@ 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"
|
33
|
+
spec.add_runtime_dependency "fugit", "~> 1.3"
|
32
34
|
|
33
35
|
spec.add_development_dependency "bundler", "~> 1.0"
|
34
|
-
spec.add_development_dependency "rake", "~>
|
36
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
35
37
|
spec.add_development_dependency "rspec", "~> 3.8"
|
36
38
|
spec.add_development_dependency "timecop", "~> 0.9"
|
37
39
|
end
|
data/lib/fa-harness-tools.rb
CHANGED
@@ -1,11 +1,14 @@
|
|
1
|
+
require "fa-harness-tools/check_logger"
|
1
2
|
require "fa-harness-tools/check_branch_protection"
|
2
3
|
require "fa-harness-tools/check_forward_deploy"
|
3
4
|
require "fa-harness-tools/check_recent_deploy"
|
4
5
|
require "fa-harness-tools/check_schedule"
|
6
|
+
require "fa-harness-tools/schedule"
|
5
7
|
require "fa-harness-tools/github_client"
|
6
8
|
require "fa-harness-tools/harness_context"
|
7
9
|
require "fa-harness-tools/version"
|
8
10
|
|
9
11
|
module FaHarnessTools
|
10
12
|
LookupError = Class.new(StandardError)
|
13
|
+
InvalidScheduleError = Class.new(StandardError)
|
11
14
|
end
|
@@ -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,28 +27,39 @@ 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] }
|
36
43
|
|
37
|
-
latest_allowed_tag = tags
|
44
|
+
latest_allowed_tag = tags.last(@allowed_rollback_count).first
|
38
45
|
|
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
|
|
45
|
-
|
53
|
+
@logger.info("the most recent tag allowed is #{latest_allowed_tag[:name]}")
|
54
|
+
|
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
|
@@ -3,28 +3,35 @@ require "tzinfo"
|
|
3
3
|
|
4
4
|
module FaHarnessTools
|
5
5
|
# Check against the time of day so you can restrict deploying to sensible
|
6
|
-
# hours.
|
7
|
-
#
|
8
|
-
# Restricts to Mon-Thu from 9am to 4pm, Fri from 9am to 12pm.
|
6
|
+
# hours.
|
9
7
|
class CheckSchedule
|
10
|
-
def initialize(timezone:
|
8
|
+
def initialize(timezone:, schedules:)
|
11
9
|
tz = TZInfo::Timezone.get(timezone)
|
10
|
+
@timezone = timezone
|
12
11
|
@now = tz.to_local(Time.now.utc)
|
12
|
+
@schedules = schedules
|
13
|
+
@logger = CheckLogger.new(
|
14
|
+
name: "Check deployment schedule",
|
15
|
+
description: "Only allow deployments within certain times of the day",
|
16
|
+
)
|
13
17
|
end
|
14
18
|
|
15
19
|
def verify?
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
20
|
+
@logger.start
|
21
|
+
@logger.info("operating in the #{@timezone} timezone")
|
22
|
+
@logger.info("local time is #{@now}")
|
23
|
+
|
24
|
+
permitted = @schedules.any? do |schedule|
|
25
|
+
can_run = schedule.can_run?(time: @now)
|
26
|
+
@logger.info("deployments are allowed due to the following schedule: #{schedule.to_s}") if can_run
|
27
|
+
can_run
|
22
28
|
end
|
23
29
|
|
24
30
|
if permitted
|
25
|
-
|
31
|
+
@logger.pass "inside the deployment schedule"
|
26
32
|
else
|
27
|
-
|
33
|
+
@logger.info "failed to match any schedule #{@schedules.map(&:to_s).join(", ")}"
|
34
|
+
@logger.fail "outside the deployment schedule"
|
28
35
|
end
|
29
36
|
end
|
30
37
|
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
|
@@ -17,14 +19,24 @@ module FaHarnessTools
|
|
17
19
|
|
18
20
|
# Return all tags starting "harness-deploy-ENV-"
|
19
21
|
#
|
20
|
-
# Used to find deployments in an environment.
|
21
|
-
#
|
22
|
+
# Used to find deployments in an environment. Provides only the tag name
|
23
|
+
# and object, though that may be an annotated tag or a commit.
|
24
|
+
#
|
25
|
+
# Use #get_commit_sha_from_tag to reliably find the commit that a tag
|
26
|
+
# points to.
|
22
27
|
#
|
23
28
|
# @return [Array[Hash]] Array of tag data hash, or [] if none
|
24
29
|
def all_deploy_tags(prefix:, environment:)
|
25
|
-
|
26
|
-
|
30
|
+
# #refs is a much quicker way than #tags to pull back all tag names, so
|
31
|
+
# we prefer this and then fetch commit information only when we need it
|
32
|
+
@octokit.refs(owner_repo, "tags/#{prefix}-#{environment}-").map do |ref|
|
33
|
+
{
|
34
|
+
name: ref[:ref][10..-1], # remove refs/tags/ prefix
|
35
|
+
object: ref[:object],
|
36
|
+
}
|
27
37
|
end
|
38
|
+
rescue Octokit::NotFound
|
39
|
+
[]
|
28
40
|
end
|
29
41
|
|
30
42
|
# Return the last (when sorted) tag starting "harness-deploy-ENV-"
|
@@ -34,8 +46,13 @@ module FaHarnessTools
|
|
34
46
|
#
|
35
47
|
# @return [Hash] Tag data hash, or nil if none
|
36
48
|
def last_deploy_tag(prefix:, environment:)
|
37
|
-
last_tag = all_deploy_tags
|
38
|
-
|
49
|
+
last_tag = all_deploy_tags(prefix: prefix, environment: environment).
|
50
|
+
sort_by { |tag| tag[:name] }.last
|
51
|
+
return nil unless last_tag
|
52
|
+
|
53
|
+
last_tag.merge(
|
54
|
+
commit: { sha: get_commit_sha_from_tag(last_tag) },
|
55
|
+
)
|
39
56
|
end
|
40
57
|
|
41
58
|
# Return a full commit SHA from a short SHA
|
@@ -48,20 +65,48 @@ module FaHarnessTools
|
|
48
65
|
commit[:sha]
|
49
66
|
end
|
50
67
|
|
68
|
+
# Return a full commit SHA from a tag
|
69
|
+
#
|
70
|
+
# The `tag` argument should be a Hash of tag data with an :object that can
|
71
|
+
# either be an annotated tag or a commit object.
|
72
|
+
#
|
73
|
+
# @return [String] Full commit SHA
|
74
|
+
# @raise [LookupError] If tag cannot be found
|
75
|
+
def get_commit_sha_from_tag(tag)
|
76
|
+
case tag[:object][:type]
|
77
|
+
when "commit"
|
78
|
+
tag[:object][:sha]
|
79
|
+
when "tag"
|
80
|
+
# When a tag points to a tag, recurse into it until we find a commit object
|
81
|
+
refed_tag = @octokit.tag(owner_repo, tag[:object][:sha])
|
82
|
+
get_commit_sha_from_tag(refed_tag.to_h.merge(tag.slice(:name)))
|
83
|
+
else
|
84
|
+
raise LookupError, "Tag #{tag[:name]} points to a non-commit object (#{tag[:object].inspect})"
|
85
|
+
end
|
86
|
+
rescue Octokit::NotFound
|
87
|
+
raise LookupError, "Unable to find tag #{tag.inspect} in Git repo"
|
88
|
+
end
|
89
|
+
|
51
90
|
# Checks if <ancestor> is an ancestor of <commit>
|
52
91
|
#
|
53
92
|
# i.e. commit and ancestor are directly related
|
54
93
|
#
|
55
94
|
# @return [Bool] True is <ancestor> is ancestor of <commit>
|
56
95
|
def is_ancestor_of?(ancestor, commit)
|
57
|
-
|
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)
|
58
100
|
end
|
59
101
|
|
60
102
|
# Checks if <commit> is on branch <branch>
|
61
103
|
#
|
62
104
|
# @return [Bool] True is <commit> is on <branch>
|
63
105
|
def branch_contains?(branch, commit)
|
64
|
-
|
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)
|
65
110
|
end
|
66
111
|
|
67
112
|
# Creates a Git tag
|
@@ -72,5 +117,18 @@ module FaHarnessTools
|
|
72
117
|
@octokit.create_ref(owner_repo, "tags/#{tag}", commit_sha)
|
73
118
|
@octokit.create_tag(owner_repo, tag, message, commit_sha, *args)
|
74
119
|
end
|
120
|
+
|
121
|
+
private
|
122
|
+
|
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
|
132
|
+
end
|
75
133
|
end
|
76
134
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'fugit'
|
2
|
+
|
3
|
+
module FaHarnessTools
|
4
|
+
# Creates a schedule which can be used to check if a change
|
5
|
+
# should be deployed.
|
6
|
+
class Schedule
|
7
|
+
def initialize(schedule:)
|
8
|
+
@schedule = schedule
|
9
|
+
@cron_schedule = Fugit.parse(schedule)
|
10
|
+
raise InvalidScheduleError, "'#{schedule}' can not be parsed" unless @cron_schedule
|
11
|
+
end
|
12
|
+
|
13
|
+
def can_run?(time:)
|
14
|
+
return false unless @cron_schedule.day_match?(time)
|
15
|
+
return @cron_schedule.hour_match?(time)
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_s
|
19
|
+
return @schedule
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
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.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- FreeAgent
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-05-26 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
|
@@ -52,6 +66,20 @@ dependencies:
|
|
52
66
|
- - "~>"
|
53
67
|
- !ruby/object:Gem::Version
|
54
68
|
version: '1.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: fugit
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '1.3'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '1.3'
|
55
83
|
- !ruby/object:Gem::Dependency
|
56
84
|
name: bundler
|
57
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -72,14 +100,14 @@ dependencies:
|
|
72
100
|
requirements:
|
73
101
|
- - "~>"
|
74
102
|
- !ruby/object:Gem::Version
|
75
|
-
version: '
|
103
|
+
version: '13.0'
|
76
104
|
type: :development
|
77
105
|
prerelease: false
|
78
106
|
version_requirements: !ruby/object:Gem::Requirement
|
79
107
|
requirements:
|
80
108
|
- - "~>"
|
81
109
|
- !ruby/object:Gem::Version
|
82
|
-
version: '
|
110
|
+
version: '13.0'
|
83
111
|
- !ruby/object:Gem::Dependency
|
84
112
|
name: rspec
|
85
113
|
requirement: !ruby/object:Gem::Requirement
|
@@ -133,6 +161,10 @@ files:
|
|
133
161
|
- Rakefile
|
134
162
|
- bin/console
|
135
163
|
- bin/setup
|
164
|
+
- examples/create-deployment-tag/README.md
|
165
|
+
- examples/create-deployment-tag/create-deployment-tag.sh
|
166
|
+
- examples/production-preflight-checks/README.md
|
167
|
+
- examples/production-preflight-checks/production-preflight-checks.sh
|
136
168
|
- exe/check-branch-protection
|
137
169
|
- exe/check-forward-deploy
|
138
170
|
- exe/check-recent-deploy
|
@@ -142,10 +174,12 @@ files:
|
|
142
174
|
- lib/fa-harness-tools.rb
|
143
175
|
- lib/fa-harness-tools/check_branch_protection.rb
|
144
176
|
- lib/fa-harness-tools/check_forward_deploy.rb
|
177
|
+
- lib/fa-harness-tools/check_logger.rb
|
145
178
|
- lib/fa-harness-tools/check_recent_deploy.rb
|
146
179
|
- lib/fa-harness-tools/check_schedule.rb
|
147
180
|
- lib/fa-harness-tools/github_client.rb
|
148
181
|
- lib/fa-harness-tools/harness_context.rb
|
182
|
+
- lib/fa-harness-tools/schedule.rb
|
149
183
|
- lib/fa-harness-tools/version.rb
|
150
184
|
homepage: https://github.com/fac/fa-harness-tools
|
151
185
|
licenses:
|