vidar 1.16.0 → 1.17.1
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/.bundler-version +1 -1
- data/.github/dependabot.yml +40 -1
- data/.github/workflows/auto-approve.yml +8 -6
- data/.github/workflows/auto-merge.yml +62 -15
- data/.github/workflows/ci.yml +2 -2
- data/.gitignore +2 -2
- data/.standard.yml +2 -0
- data/CHANGELOG.md +18 -0
- data/Gemfile +10 -12
- data/Gemfile.lock +39 -30
- data/Rakefile +5 -6
- data/exe/vidar +1 -1
- data/lib/vidar/cli.rb +15 -13
- data/lib/vidar/config.rb +57 -12
- data/lib/vidar/deploy_status.rb +22 -21
- data/lib/vidar/honeycomb_notification.rb +16 -10
- data/lib/vidar/interpolation.rb +1 -1
- data/lib/vidar/k8s/container.rb +30 -3
- data/lib/vidar/k8s/pod_set.rb +27 -8
- data/lib/vidar/log.rb +1 -1
- data/lib/vidar/run.rb +2 -2
- data/lib/vidar/sentry_notification.rb +4 -1
- data/lib/vidar/slack_notification.rb +10 -7
- data/lib/vidar/version.rb +1 -1
- data/lib/vidar.rb +20 -20
- data/vidar.gemspec +18 -18
- metadata +11 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c9f70e2aea16ca7648801c10d594785cf71cc4059e723075a7e0a5ac2df28e7a
|
|
4
|
+
data.tar.gz: e02d0cfe17b044eb62d11fee018e15956c02835ab7bb947a224b5a3fcfdf48bc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 88bb3c398a191d7b02a6d2591653b5ddc8ed88a1d2432dd84e2d0188fdcbb3873ea5f20eb7ca00c3b2f3bc49b03f030f3c12dcb4799d306741fa4d0f668489f4
|
|
7
|
+
data.tar.gz: afe4490c85deca103d2b07f720b36710fae66fc7acc60ffb91fbf4c235ac4fa027ecb01067a56710a3bafb9e0d357eeeafb3aa41d729b0c0a43b607ca4c3f3b1
|
data/.bundler-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
4.0.
|
|
1
|
+
4.0.10
|
data/.github/dependabot.yml
CHANGED
|
@@ -6,10 +6,49 @@ updates:
|
|
|
6
6
|
schedule:
|
|
7
7
|
interval: "weekly"
|
|
8
8
|
day: "monday"
|
|
9
|
-
time: "
|
|
9
|
+
time: "11:00"
|
|
10
10
|
timezone: "UTC"
|
|
11
|
+
cooldown:
|
|
12
|
+
default-days: 2
|
|
13
|
+
include:
|
|
14
|
+
- "*"
|
|
11
15
|
commit-message:
|
|
12
16
|
prefix: "[dependabot]"
|
|
17
|
+
groups:
|
|
18
|
+
ruby-minor-and-patch:
|
|
19
|
+
applies-to: version-updates
|
|
20
|
+
update-types:
|
|
21
|
+
- "minor"
|
|
22
|
+
- "patch"
|
|
13
23
|
labels:
|
|
24
|
+
- "dependencies"
|
|
14
25
|
- "automerge"
|
|
26
|
+
ignore:
|
|
27
|
+
- dependency-name: "*"
|
|
28
|
+
update-types:
|
|
29
|
+
- "version-update:semver-major"
|
|
30
|
+
|
|
31
|
+
- package-ecosystem: "github-actions"
|
|
32
|
+
directory: "/"
|
|
33
|
+
schedule:
|
|
34
|
+
interval: "weekly"
|
|
35
|
+
day: "monday"
|
|
36
|
+
time: "11:00"
|
|
37
|
+
timezone: "UTC"
|
|
38
|
+
cooldown:
|
|
39
|
+
default-days: 2
|
|
40
|
+
commit-message:
|
|
41
|
+
prefix: "[dependabot]"
|
|
42
|
+
groups:
|
|
43
|
+
actions-minor-and-patch:
|
|
44
|
+
applies-to: version-updates
|
|
45
|
+
update-types:
|
|
46
|
+
- "minor"
|
|
47
|
+
- "patch"
|
|
48
|
+
labels:
|
|
15
49
|
- "dependencies"
|
|
50
|
+
- "automerge"
|
|
51
|
+
ignore:
|
|
52
|
+
- dependency-name: "*"
|
|
53
|
+
update-types:
|
|
54
|
+
- "version-update:semver-major"
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
name: Auto approve
|
|
1
|
+
name: Auto approve hot-fix PRs
|
|
2
2
|
on:
|
|
3
3
|
pull_request_target:
|
|
4
|
-
types:
|
|
5
|
-
|
|
6
|
-
- ready_for_review
|
|
4
|
+
types: [opened, labeled, ready_for_review]
|
|
5
|
+
|
|
7
6
|
jobs:
|
|
8
7
|
auto-approve:
|
|
8
|
+
if: >
|
|
9
|
+
github.actor == 'renofidev' ||
|
|
10
|
+
contains(github.event.pull_request.labels.*.name, 'HOTFIX-AUTO-APPROVE') ||
|
|
11
|
+
contains(github.event.pull_request.labels.*.name, 'self-approve')
|
|
9
12
|
runs-on: ubuntu-latest
|
|
10
13
|
steps:
|
|
11
|
-
- uses: hmarr/auto-approve-action@
|
|
12
|
-
if: github.actor == 'dependabot[bot]' || github.actor == 'dependabot-preview[bot]' || github.actor == 'renofidev' || contains(github.event.pull_request.labels.*.name, 'HOTFIX-AUTO-APPROVE') || contains(github.event.pull_request.labels.*.name, 'self-approve') || contains(github.event.pull_request.labels.*.name, 'dependencies')
|
|
14
|
+
- uses: hmarr/auto-approve-action@f0939ea97e9205ef24d872e76833fa908a770363 # v4.0.0
|
|
13
15
|
with:
|
|
14
16
|
github-token: "${{ secrets.GITHUB_TOKEN }}"
|
|
@@ -2,6 +2,9 @@ name: automerge
|
|
|
2
2
|
on:
|
|
3
3
|
pull_request_target:
|
|
4
4
|
types:
|
|
5
|
+
- opened
|
|
6
|
+
- synchronize
|
|
7
|
+
- reopened
|
|
5
8
|
- labeled
|
|
6
9
|
pull_request_review:
|
|
7
10
|
types:
|
|
@@ -9,23 +12,67 @@ on:
|
|
|
9
12
|
check_suite:
|
|
10
13
|
types:
|
|
11
14
|
- completed
|
|
12
|
-
|
|
15
|
+
|
|
16
|
+
permissions:
|
|
17
|
+
contents: write
|
|
18
|
+
pull-requests: write
|
|
19
|
+
|
|
13
20
|
jobs:
|
|
14
|
-
automerge:
|
|
21
|
+
automerge-labeled:
|
|
22
|
+
if: >
|
|
23
|
+
github.event.pull_request.user.login != 'dependabot[bot]' &&
|
|
24
|
+
contains(github.event.pull_request.labels.*.name, 'automerge') &&
|
|
25
|
+
!contains(github.event.pull_request.labels.*.name, 'automerge blocked')
|
|
15
26
|
runs-on: ubuntu-latest
|
|
16
27
|
steps:
|
|
17
|
-
- name:
|
|
18
|
-
|
|
28
|
+
- name: Enable auto-merge
|
|
29
|
+
run: gh pr merge --auto --squash --delete-branch "$PR_URL"
|
|
19
30
|
env:
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
31
|
+
PR_URL: ${{ github.event.pull_request.html_url }}
|
|
32
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
33
|
+
|
|
34
|
+
automerge-dependabot:
|
|
35
|
+
if: >
|
|
36
|
+
github.event.pull_request.user.login == 'dependabot[bot]' &&
|
|
37
|
+
!contains(github.event.pull_request.labels.*.name, 'automerge blocked')
|
|
38
|
+
runs-on: ubuntu-latest
|
|
39
|
+
steps:
|
|
40
|
+
- name: Fetch Dependabot metadata
|
|
41
|
+
id: metadata
|
|
42
|
+
uses: dependabot/fetch-metadata@25dd0e34f4fe68f24cc83900b1fe3fe149efef98 # v3.1.0
|
|
43
|
+
with:
|
|
44
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
45
|
+
|
|
46
|
+
- name: Approve minor and patch
|
|
47
|
+
if: >
|
|
48
|
+
steps.metadata.outputs.update-type == 'version-update:semver-patch' ||
|
|
49
|
+
steps.metadata.outputs.update-type == 'version-update:semver-minor'
|
|
50
|
+
uses: hmarr/auto-approve-action@f0939ea97e9205ef24d872e76833fa908a770363 # v4.0.0
|
|
51
|
+
with:
|
|
52
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
53
|
+
|
|
54
|
+
- name: Enable auto-merge for minor and patch
|
|
55
|
+
if: >
|
|
56
|
+
steps.metadata.outputs.update-type == 'version-update:semver-patch' ||
|
|
57
|
+
steps.metadata.outputs.update-type == 'version-update:semver-minor'
|
|
58
|
+
run: gh pr merge --auto --squash --delete-branch "$PR_URL"
|
|
59
|
+
env:
|
|
60
|
+
PR_URL: ${{ github.event.pull_request.html_url }}
|
|
61
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
62
|
+
|
|
63
|
+
automerge-update-branch:
|
|
64
|
+
if: github.event_name == 'check_suite'
|
|
65
|
+
runs-on: ubuntu-latest
|
|
66
|
+
steps:
|
|
67
|
+
- name: Update branches behind base
|
|
26
68
|
env:
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
69
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
70
|
+
run: |
|
|
71
|
+
gh pr list --repo "$GITHUB_REPOSITORY" \
|
|
72
|
+
--label automerge \
|
|
73
|
+
--limit 100 \
|
|
74
|
+
--json number,labels \
|
|
75
|
+
--jq '.[] | select(.labels | map(.name) | contains(["automerge blocked"]) | not) | .number' \
|
|
76
|
+
| while read pr_number; do
|
|
77
|
+
gh pr update-branch "$pr_number" --repo "$GITHUB_REPOSITORY" 2>/dev/null || true
|
|
78
|
+
done
|
data/.github/workflows/ci.yml
CHANGED
|
@@ -14,9 +14,9 @@ jobs:
|
|
|
14
14
|
ruby-version: [3.4, 4.0]
|
|
15
15
|
|
|
16
16
|
steps:
|
|
17
|
-
- uses: actions/checkout@
|
|
17
|
+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
18
18
|
- name: Set up Ruby
|
|
19
|
-
uses: ruby/setup-ruby@v1
|
|
19
|
+
uses: ruby/setup-ruby@c4e5b1316158f92e3d49443a9d58b31d25ac0f8f # v1.306.0
|
|
20
20
|
with:
|
|
21
21
|
ruby-version: ${{ matrix.ruby-version }}
|
|
22
22
|
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
data/.gitignore
CHANGED
data/.standard.yml
ADDED
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
## 1.17.1 - 2026-05-19
|
|
4
|
+
|
|
5
|
+
- Fix `monitor_deploy_status` failing the deploy promotion when `--max_tries` is passed: Thor's `invoke :notify_sentry` was forwarding the parent task's ARGV as positional args. Pass explicit empty args/options to scope the sub-invocation.
|
|
6
|
+
|
|
7
|
+
## 1.17.0 - 2026-03-24
|
|
8
|
+
|
|
9
|
+
- Replace backtick shell calls with `Open3.capture3` for safer command execution
|
|
10
|
+
- Add `rescue JSON::ParserError` in `K8s::PodSet` to handle malformed kubectl output gracefully
|
|
11
|
+
- Fix `terminated_error?` returning truthy for exit code 0
|
|
12
|
+
- Add `Unknown` state label for containers with unrecognized state
|
|
13
|
+
- Add `sidecar?` method and `sidecar_container_names` config key for configurable sidecar filtering (replaces hardcoded istio-proxy)
|
|
14
|
+
- Add `rescue Faraday::Error` to all notification classes to prevent network errors from crashing deploys
|
|
15
|
+
- Simplify `DeployStatus` polling loops and remove off-by-one in max_tries
|
|
16
|
+
- Add `vidar.yml` schema validation on load (requires `image`, `namespace`, `github`)
|
|
17
|
+
- Pin faraday dependency to `>= 2.0, < 3`
|
|
18
|
+
- Add YARD documentation to all public classes and methods
|
|
19
|
+
- Expand test coverage from 18 to 148 examples
|
|
20
|
+
|
|
3
21
|
## 1.16.0 - 2026-01-27
|
|
4
22
|
|
|
5
23
|
Ruby 4.0 support. Drop ruby 3.3 support.
|
data/Gemfile
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
|
-
source
|
|
1
|
+
source "https://rubygems.org"
|
|
2
2
|
|
|
3
3
|
gemspec
|
|
4
4
|
|
|
5
|
-
gem
|
|
6
|
-
gem
|
|
7
|
-
gem
|
|
8
|
-
gem
|
|
9
|
-
gem
|
|
10
|
-
gem
|
|
11
|
-
gem
|
|
12
|
-
gem
|
|
13
|
-
gem 'rubocop-rspec'
|
|
14
|
-
gem 'webmock'
|
|
5
|
+
gem "awesome_print"
|
|
6
|
+
gem "bundler"
|
|
7
|
+
gem "irb"
|
|
8
|
+
gem "pry"
|
|
9
|
+
gem "rake"
|
|
10
|
+
gem "rspec"
|
|
11
|
+
gem "standard"
|
|
12
|
+
gem "webmock"
|
|
15
13
|
|
|
16
|
-
gem
|
|
14
|
+
gem "openssl"
|
data/Gemfile.lock
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
vidar (1.
|
|
4
|
+
vidar (1.17.1)
|
|
5
5
|
colorize
|
|
6
|
-
faraday
|
|
6
|
+
faraday (>= 2.0, < 3)
|
|
7
7
|
thor (~> 1.0)
|
|
8
8
|
|
|
9
9
|
GEM
|
|
10
10
|
remote: https://rubygems.org/
|
|
11
11
|
specs:
|
|
12
|
-
addressable (2.
|
|
12
|
+
addressable (2.9.0)
|
|
13
13
|
public_suffix (>= 2.0.2, < 8.0)
|
|
14
14
|
ast (2.4.3)
|
|
15
15
|
awesome_print (1.9.2)
|
|
16
|
-
bigdecimal (4.
|
|
16
|
+
bigdecimal (4.1.1)
|
|
17
17
|
coderay (1.1.3)
|
|
18
18
|
colorize (1.1.0)
|
|
19
19
|
crack (1.0.1)
|
|
@@ -21,8 +21,8 @@ GEM
|
|
|
21
21
|
rexml
|
|
22
22
|
date (3.5.1)
|
|
23
23
|
diff-lcs (1.6.2)
|
|
24
|
-
erb (6.0.
|
|
25
|
-
faraday (2.14.
|
|
24
|
+
erb (6.0.4)
|
|
25
|
+
faraday (2.14.2)
|
|
26
26
|
faraday-net_http (>= 2.0, < 3.5)
|
|
27
27
|
json
|
|
28
28
|
logger
|
|
@@ -30,26 +30,27 @@ GEM
|
|
|
30
30
|
net-http (~> 0.5)
|
|
31
31
|
hashdiff (1.2.1)
|
|
32
32
|
io-console (0.8.2)
|
|
33
|
-
irb (1.
|
|
33
|
+
irb (1.18.0)
|
|
34
34
|
pp (>= 0.6.0)
|
|
35
|
+
prism (>= 1.3.0)
|
|
35
36
|
rdoc (>= 4.0.0)
|
|
36
37
|
reline (>= 0.4.2)
|
|
37
|
-
json (2.
|
|
38
|
+
json (2.19.5)
|
|
38
39
|
language_server-protocol (3.17.0.5)
|
|
39
40
|
lint_roller (1.1.0)
|
|
40
41
|
logger (1.7.0)
|
|
41
42
|
method_source (1.1.0)
|
|
42
43
|
net-http (0.9.1)
|
|
43
44
|
uri (>= 0.11.1)
|
|
44
|
-
openssl (4.0.
|
|
45
|
-
parallel (1.
|
|
46
|
-
parser (3.3.
|
|
45
|
+
openssl (4.0.2)
|
|
46
|
+
parallel (1.28.0)
|
|
47
|
+
parser (3.3.11.1)
|
|
47
48
|
ast (~> 2.4.1)
|
|
48
49
|
racc
|
|
49
50
|
pp (0.6.3)
|
|
50
51
|
prettyprint
|
|
51
52
|
prettyprint (0.2.0)
|
|
52
|
-
prism (1.
|
|
53
|
+
prism (1.9.0)
|
|
53
54
|
pry (0.16.0)
|
|
54
55
|
coderay (~> 1.1)
|
|
55
56
|
method_source (~> 1.0)
|
|
@@ -57,15 +58,15 @@ GEM
|
|
|
57
58
|
psych (5.3.1)
|
|
58
59
|
date
|
|
59
60
|
stringio
|
|
60
|
-
public_suffix (7.0.
|
|
61
|
+
public_suffix (7.0.5)
|
|
61
62
|
racc (1.8.1)
|
|
62
63
|
rainbow (3.1.1)
|
|
63
|
-
rake (13.
|
|
64
|
-
rdoc (7.
|
|
64
|
+
rake (13.4.2)
|
|
65
|
+
rdoc (7.2.0)
|
|
65
66
|
erb
|
|
66
67
|
psych (>= 4.0.0)
|
|
67
68
|
tsort
|
|
68
|
-
regexp_parser (2.
|
|
69
|
+
regexp_parser (2.12.0)
|
|
69
70
|
reline (0.6.3)
|
|
70
71
|
io-console (~> 0.5)
|
|
71
72
|
rexml (3.4.4)
|
|
@@ -78,11 +79,11 @@ GEM
|
|
|
78
79
|
rspec-expectations (3.13.5)
|
|
79
80
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
80
81
|
rspec-support (~> 3.13.0)
|
|
81
|
-
rspec-mocks (3.13.
|
|
82
|
+
rspec-mocks (3.13.8)
|
|
82
83
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
83
84
|
rspec-support (~> 3.13.0)
|
|
84
|
-
rspec-support (3.13.
|
|
85
|
-
rubocop (1.
|
|
85
|
+
rspec-support (3.13.7)
|
|
86
|
+
rubocop (1.84.2)
|
|
86
87
|
json (~> 2.3)
|
|
87
88
|
language_server-protocol (~> 3.17.0.2)
|
|
88
89
|
lint_roller (~> 1.1.0)
|
|
@@ -90,19 +91,29 @@ GEM
|
|
|
90
91
|
parser (>= 3.3.0.2)
|
|
91
92
|
rainbow (>= 2.2.2, < 4.0)
|
|
92
93
|
regexp_parser (>= 2.9.3, < 3.0)
|
|
93
|
-
rubocop-ast (>= 1.
|
|
94
|
+
rubocop-ast (>= 1.49.0, < 2.0)
|
|
94
95
|
ruby-progressbar (~> 1.7)
|
|
95
96
|
unicode-display_width (>= 2.4.0, < 4.0)
|
|
96
|
-
rubocop-ast (1.49.
|
|
97
|
+
rubocop-ast (1.49.1)
|
|
97
98
|
parser (>= 3.3.7.2)
|
|
98
99
|
prism (~> 1.7)
|
|
99
|
-
rubocop-
|
|
100
|
+
rubocop-performance (1.26.1)
|
|
100
101
|
lint_roller (~> 1.1)
|
|
101
|
-
rubocop (>= 1.
|
|
102
|
-
|
|
103
|
-
lint_roller (~> 1.1)
|
|
104
|
-
rubocop (~> 1.81)
|
|
102
|
+
rubocop (>= 1.75.0, < 2.0)
|
|
103
|
+
rubocop-ast (>= 1.47.1, < 2.0)
|
|
105
104
|
ruby-progressbar (1.13.0)
|
|
105
|
+
standard (1.54.0)
|
|
106
|
+
language_server-protocol (~> 3.17.0.2)
|
|
107
|
+
lint_roller (~> 1.0)
|
|
108
|
+
rubocop (~> 1.84.0)
|
|
109
|
+
standard-custom (~> 1.0.0)
|
|
110
|
+
standard-performance (~> 1.8)
|
|
111
|
+
standard-custom (1.0.2)
|
|
112
|
+
lint_roller (~> 1.0)
|
|
113
|
+
rubocop (~> 1.50)
|
|
114
|
+
standard-performance (1.9.0)
|
|
115
|
+
lint_roller (~> 1.1)
|
|
116
|
+
rubocop-performance (~> 1.26.0)
|
|
106
117
|
stringio (3.2.0)
|
|
107
118
|
thor (1.5.0)
|
|
108
119
|
tsort (0.2.0)
|
|
@@ -110,7 +121,7 @@ GEM
|
|
|
110
121
|
unicode-emoji (~> 4.1)
|
|
111
122
|
unicode-emoji (4.2.0)
|
|
112
123
|
uri (1.1.1)
|
|
113
|
-
webmock (3.26.
|
|
124
|
+
webmock (3.26.2)
|
|
114
125
|
addressable (>= 2.8.0)
|
|
115
126
|
crack (>= 0.3.2)
|
|
116
127
|
hashdiff (>= 0.4.0, < 2.0.0)
|
|
@@ -126,9 +137,7 @@ DEPENDENCIES
|
|
|
126
137
|
pry
|
|
127
138
|
rake
|
|
128
139
|
rspec
|
|
129
|
-
|
|
130
|
-
rubocop-rake
|
|
131
|
-
rubocop-rspec
|
|
140
|
+
standard
|
|
132
141
|
vidar!
|
|
133
142
|
webmock
|
|
134
143
|
|
data/Rakefile
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
require
|
|
2
|
-
require
|
|
3
|
-
require
|
|
1
|
+
require "bundler/gem_tasks"
|
|
2
|
+
require "rspec/core/rake_task"
|
|
3
|
+
require "standard/rake"
|
|
4
4
|
|
|
5
5
|
RSpec::Core::RakeTask.new(:spec)
|
|
6
|
-
RuboCop::RakeTask.new
|
|
7
6
|
|
|
8
|
-
task ci: %i[spec
|
|
9
|
-
task default: %i[spec
|
|
7
|
+
task ci: %i[spec standard]
|
|
8
|
+
task default: %i[spec standard:fix]
|
data/exe/vidar
CHANGED
data/lib/vidar/cli.rb
CHANGED
|
@@ -24,7 +24,8 @@ module Vidar
|
|
|
24
24
|
Log.info "Pulling #{Config.get!(:image)} tags"
|
|
25
25
|
Run.docker "pull #{Config.get!(:image)}:#{Config.get!(:base_stage_name)}-#{Config.get!(:current_branch)} 2> /dev/null || true"
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
image_names_stdout, _stderr, _status = Open3.capture3('docker images --format "{{.Repository}}:{{.Tag}}"')
|
|
28
|
+
image_names = image_names_stdout.split("\n")
|
|
28
29
|
base_image = "#{Config.get!(:image)}:#{Config.get!(:base_stage_name)}-#{Config.get!(:default_branch)}"
|
|
29
30
|
Run.docker "pull #{base_image} 2> /dev/null || true" unless image_names.include?(base_image)
|
|
30
31
|
|
|
@@ -128,8 +129,8 @@ module Vidar
|
|
|
128
129
|
destination = options[:destination]
|
|
129
130
|
container = options[:container]
|
|
130
131
|
all = options[:all]
|
|
131
|
-
Log.info "Set kubectl image for #{
|
|
132
|
-
Run.kubectl "set image #{destination} #{container}=#{Config.get!(:image)}:#{revision} #{
|
|
132
|
+
Log.info "Set kubectl image for #{"all " if all}#{destination} container=#{container}..."
|
|
133
|
+
Run.kubectl "set image #{destination} #{container}=#{Config.get!(:image)}:#{revision} #{"--all" if all}"
|
|
133
134
|
end
|
|
134
135
|
|
|
135
136
|
desc "set_image", "Set image for k8s deployment"
|
|
@@ -146,8 +147,8 @@ module Vidar
|
|
|
146
147
|
destination = options[:destination]
|
|
147
148
|
container = options[:container]
|
|
148
149
|
all = options[:all]
|
|
149
|
-
Log.info "Set kubectl image for #{
|
|
150
|
-
Run.kubectl "set image #{destination} #{container}=#{Config.get!(:image)}:#{revision} #{
|
|
150
|
+
Log.info "Set kubectl image for #{"all " if all}#{destination} container=#{container}..."
|
|
151
|
+
Run.kubectl "set image #{destination} #{container}=#{Config.get!(:image)}:#{revision} #{"--all" if all}"
|
|
151
152
|
end
|
|
152
153
|
|
|
153
154
|
desc "release", "Build and publish docker images"
|
|
@@ -178,7 +179,7 @@ module Vidar
|
|
|
178
179
|
Log.info "OK: All containers are ready"
|
|
179
180
|
slack_notification.success if slack_notification.configured?
|
|
180
181
|
honeycomb_notification.success
|
|
181
|
-
invoke :notify_sentry
|
|
182
|
+
invoke :notify_sentry, [], {}
|
|
182
183
|
else
|
|
183
184
|
Log.error "ERROR: Some of containers are errored or not ready"
|
|
184
185
|
slack_notification.failure if slack_notification.configured?
|
|
@@ -198,16 +199,17 @@ module Vidar
|
|
|
198
199
|
Log.error "ERROR: could not find deployment config for #{Config.get!(:kubectl_context)} context" unless deploy_config
|
|
199
200
|
|
|
200
201
|
pod_set = K8s::PodSet.new(namespace: Config.get!(:namespace), filter: options[:name])
|
|
201
|
-
|
|
202
|
+
sidecar_names = Array(Config.get(:sidecar_container_names))
|
|
203
|
+
containers = pod_set.containers.select(&:ready_and_running?).reject { |c| c.sidecar?(sidecar_names) }
|
|
202
204
|
|
|
203
205
|
if containers.empty?
|
|
204
|
-
name = options[:name] ||
|
|
206
|
+
name = options[:name] || "any"
|
|
205
207
|
Log.error "No running containers found with *#{name}* name"
|
|
206
208
|
exit(1)
|
|
207
209
|
else
|
|
208
210
|
Log.info "Available containers:"
|
|
209
211
|
containers.each(&:print)
|
|
210
|
-
container = containers.detect { |c| c.name ==
|
|
212
|
+
container = containers.detect { |c| c.name == "console" } || containers.last
|
|
211
213
|
|
|
212
214
|
Log.info "Running #{options[:command]} in #{container.pod_name}"
|
|
213
215
|
Run.kubectl("exec -it #{container.pod_name} -- #{options[:command]}")
|
|
@@ -232,7 +234,7 @@ module Vidar
|
|
|
232
234
|
desc "notify_sentry", "Notify sentry about current release"
|
|
233
235
|
def notify_sentry
|
|
234
236
|
sentry_notification = SentryNotification.new(
|
|
235
|
-
revision:
|
|
237
|
+
revision: Config.get!(:revision),
|
|
236
238
|
deploy_config: Config.deploy_config
|
|
237
239
|
)
|
|
238
240
|
|
|
@@ -248,10 +250,10 @@ module Vidar
|
|
|
248
250
|
desc "notify_slack", "Send custom slack notification"
|
|
249
251
|
def notify_slack
|
|
250
252
|
slack_notification = SlackNotification.new(
|
|
251
|
-
github:
|
|
252
|
-
revision:
|
|
253
|
+
github: Config.get!(:github),
|
|
254
|
+
revision: Config.get!(:revision),
|
|
253
255
|
revision_name: Config.get!(:revision_name),
|
|
254
|
-
build_url:
|
|
256
|
+
build_url: Config.build_url,
|
|
255
257
|
deploy_config: Config.deploy_config
|
|
256
258
|
)
|
|
257
259
|
|
data/lib/vidar/config.rb
CHANGED
|
@@ -1,34 +1,44 @@
|
|
|
1
1
|
module Vidar
|
|
2
|
+
# Loads and provides access to the vidar.yml manifest configuration.
|
|
3
|
+
# Values fall back to DEFAULT_OPTIONS when not defined in the manifest.
|
|
2
4
|
class Config
|
|
3
5
|
DEFAULT_MANIFEST_FILE = "vidar.yml".freeze
|
|
4
6
|
DEFAULT_BRANCHES = %w[main master].freeze
|
|
7
|
+
REQUIRED_KEYS = %w[image namespace github].freeze
|
|
5
8
|
|
|
6
9
|
DEFAULT_OPTIONS = {
|
|
7
|
-
compose_file:
|
|
8
|
-
compose_cmd:
|
|
9
|
-
default_branch:
|
|
10
|
-
current_branch:
|
|
11
|
-
revision:
|
|
12
|
-
revision_name:
|
|
13
|
-
kubectl_context:
|
|
14
|
-
shell_command:
|
|
15
|
-
console_command:
|
|
16
|
-
base_stage_name:
|
|
10
|
+
compose_file: -> { "docker-compose.ci.yml" },
|
|
11
|
+
compose_cmd: -> { "docker compose" },
|
|
12
|
+
default_branch: -> { (DEFAULT_BRANCHES & branches).first || DEFAULT_BRANCHES.first },
|
|
13
|
+
current_branch: -> { (ENV["SEMAPHORE_GIT_WORKING_BRANCH"] || shell_capture("git rev-parse --abbrev-ref HEAD")).tr("/", "-") },
|
|
14
|
+
revision: -> { shell_capture("git rev-parse HEAD") },
|
|
15
|
+
revision_name: -> { shell_capture('git show --pretty=format:"%s (%h)" -s HEAD') },
|
|
16
|
+
kubectl_context: -> { shell_capture("kubectl config current-context") },
|
|
17
|
+
shell_command: -> { "/bin/sh" },
|
|
18
|
+
console_command: -> { "bin/console" },
|
|
19
|
+
base_stage_name: -> { "base" },
|
|
17
20
|
release_stage_name: -> { "release" },
|
|
18
|
-
honeycomb_api_key:
|
|
21
|
+
honeycomb_api_key: -> { ENV["HONEYCOMB_API_KEY"] },
|
|
22
|
+
sidecar_container_names: -> { ["istio-proxy"] }
|
|
19
23
|
}.freeze
|
|
20
24
|
|
|
21
25
|
class << self
|
|
22
26
|
attr_reader :data
|
|
23
27
|
attr_writer :manifest_file
|
|
24
28
|
|
|
29
|
+
# Loads the manifest file and validates required keys.
|
|
30
|
+
# @param file_path [String] path to vidar.yml
|
|
31
|
+
# @raise [MissingManifestFileError] if the file does not exist
|
|
32
|
+
# @raise [Error] if required keys are missing or schema is invalid
|
|
25
33
|
def load(file_path = manifest_file)
|
|
26
34
|
ensure_file_exist!(file_path)
|
|
27
35
|
|
|
28
36
|
@data = YAML.load_file(file_path)
|
|
37
|
+
validate_schema!
|
|
29
38
|
@loaded = true
|
|
30
39
|
end
|
|
31
40
|
|
|
41
|
+
# @return [String] path to the manifest file
|
|
32
42
|
def manifest_file
|
|
33
43
|
@manifest_file || DEFAULT_MANIFEST_FILE
|
|
34
44
|
end
|
|
@@ -37,10 +47,13 @@ module Vidar
|
|
|
37
47
|
fail(MissingManifestFileError, file_path) unless File.exist?(file_path)
|
|
38
48
|
end
|
|
39
49
|
|
|
50
|
+
# @return [Boolean] true if the manifest has been loaded
|
|
40
51
|
def loaded?
|
|
41
52
|
@loaded
|
|
42
53
|
end
|
|
43
54
|
|
|
55
|
+
# @param key [Symbol, String] config key
|
|
56
|
+
# @return [Object, nil] value from manifest or default
|
|
44
57
|
def get(key)
|
|
45
58
|
load unless loaded?
|
|
46
59
|
|
|
@@ -51,19 +64,26 @@ module Vidar
|
|
|
51
64
|
Vidar::Interpolation.call(value, self)
|
|
52
65
|
end
|
|
53
66
|
|
|
67
|
+
# @param key [Symbol, String] config key
|
|
68
|
+
# @return [Object] value from manifest or default
|
|
69
|
+
# @raise [MissingConfigError] if the key is not found
|
|
54
70
|
def get!(key)
|
|
55
71
|
get(key) || fail(MissingConfigError, key)
|
|
56
72
|
end
|
|
57
73
|
|
|
74
|
+
# @return [String, nil] CI build URL resolved from env or manifest
|
|
58
75
|
def build_url
|
|
59
76
|
value = ENV[get(:build_env).to_s] || get(:build_url)
|
|
60
77
|
value&.empty? ? nil : value
|
|
61
78
|
end
|
|
62
79
|
|
|
80
|
+
# @param env [String] environment name (maps to HONEYCOMB_API_KEY_<ENV>)
|
|
81
|
+
# @return [String, nil] Honeycomb API key for the given environment
|
|
63
82
|
def honeycomb_env_api_key(env)
|
|
64
83
|
ENV["HONEYCOMB_API_KEY_#{env.upcase}"]
|
|
65
84
|
end
|
|
66
85
|
|
|
86
|
+
# @return [DeployConfig] deployment config for the current kubectl context
|
|
67
87
|
def deploy_config
|
|
68
88
|
deploy_configs[get!(:kubectl_context)] ||= build_deploy_config(get!(:kubectl_context))
|
|
69
89
|
end
|
|
@@ -88,13 +108,38 @@ module Vidar
|
|
|
88
108
|
@deploy_configs ||= {}
|
|
89
109
|
end
|
|
90
110
|
|
|
111
|
+
# @return [Array<String>] local git branch names
|
|
91
112
|
def branches
|
|
92
|
-
|
|
113
|
+
stdout, _stderr, _status = Open3.capture3("git for-each-ref --format='%(refname:short)' refs/heads/*")
|
|
114
|
+
stdout.split("\n")
|
|
93
115
|
end
|
|
94
116
|
|
|
117
|
+
# @return [Boolean] true if current branch is the default branch
|
|
95
118
|
def default_branch?
|
|
96
119
|
get!(:current_branch) == get!(:default_branch)
|
|
97
120
|
end
|
|
121
|
+
|
|
122
|
+
private
|
|
123
|
+
|
|
124
|
+
def validate_schema!
|
|
125
|
+
missing = REQUIRED_KEYS.reject { |k| @data.key?(k) }
|
|
126
|
+
fail(Error, "vidar.yml is missing required keys: #{missing.join(", ")}") if missing.any?
|
|
127
|
+
|
|
128
|
+
deployments = @data["deployments"]
|
|
129
|
+
fail(Error, "vidar.yml: 'deployments' must be a Hash, got #{deployments.class}") if deployments && !deployments.is_a?(Hash)
|
|
130
|
+
|
|
131
|
+
return unless deployments
|
|
132
|
+
|
|
133
|
+
deployments.each do |context, config|
|
|
134
|
+
fail(Error, "vidar.yml: deployment '#{context}' must be a Hash") unless config.is_a?(Hash)
|
|
135
|
+
fail(Error, "vidar.yml: deployment '#{context}' is missing required key 'name'") unless config.key?("name")
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def shell_capture(command)
|
|
140
|
+
stdout, _stderr, _status = Open3.capture3(command)
|
|
141
|
+
stdout.strip
|
|
142
|
+
end
|
|
98
143
|
end
|
|
99
144
|
end
|
|
100
145
|
end
|
data/lib/vidar/deploy_status.rb
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
module Vidar
|
|
2
|
+
# Polls Kubernetes pod status until deployment completes or times out.
|
|
2
3
|
class DeployStatus
|
|
3
4
|
INITIAL_SLEEP = 2
|
|
4
5
|
SLEEP = 10
|
|
@@ -6,52 +7,52 @@ module Vidar
|
|
|
6
7
|
|
|
7
8
|
attr_reader :namespace, :filter, :max_tries
|
|
8
9
|
|
|
10
|
+
# @param namespace [String] Kubernetes namespace, or "all"
|
|
11
|
+
# @param filter [String, nil] optional substring filter on container names
|
|
12
|
+
# @param max_tries [Integer] maximum poll iterations before giving up
|
|
9
13
|
def initialize(namespace:, filter: nil, max_tries: MAX_TRIES)
|
|
10
14
|
@namespace = namespace
|
|
11
15
|
@filter = filter
|
|
12
16
|
@max_tries = max_tries
|
|
13
17
|
end
|
|
14
18
|
|
|
19
|
+
# Waits until at least one pod exists in the namespace.
|
|
20
|
+
# @return [void]
|
|
15
21
|
def wait_until_up
|
|
16
|
-
tries = 0
|
|
17
|
-
|
|
18
22
|
sleep(INITIAL_SLEEP)
|
|
19
23
|
|
|
20
|
-
|
|
21
|
-
|
|
24
|
+
max_tries.times do
|
|
25
|
+
ps = current_pod_set
|
|
26
|
+
break if ps.any?
|
|
27
|
+
|
|
22
28
|
sleep(SLEEP)
|
|
23
|
-
if tries > max_tries
|
|
24
|
-
break
|
|
25
|
-
end
|
|
26
29
|
end
|
|
27
30
|
end
|
|
28
31
|
|
|
32
|
+
# Waits until all pods are deployed and none are still initializing.
|
|
33
|
+
# @return [void]
|
|
29
34
|
def wait_until_completed
|
|
30
|
-
tries = 0
|
|
31
|
-
|
|
32
35
|
sleep(INITIAL_SLEEP)
|
|
33
36
|
|
|
34
|
-
|
|
35
|
-
|
|
37
|
+
max_tries.times do
|
|
38
|
+
ps = current_pod_set
|
|
39
|
+
break if ps.deployed?
|
|
40
|
+
|
|
36
41
|
sleep(SLEEP)
|
|
37
|
-
if tries > max_tries && !pod_set.waiting?
|
|
38
|
-
break
|
|
39
|
-
end
|
|
40
42
|
end
|
|
41
43
|
end
|
|
42
44
|
|
|
45
|
+
# @return [Boolean] true if the last observed pod set reported success
|
|
43
46
|
def success?
|
|
44
|
-
return false unless last_pod_set
|
|
47
|
+
return false unless @last_pod_set
|
|
45
48
|
|
|
46
|
-
last_pod_set.success?
|
|
49
|
+
@last_pod_set.success?
|
|
47
50
|
end
|
|
48
51
|
|
|
49
|
-
|
|
50
|
-
@pod_set
|
|
51
|
-
end
|
|
52
|
+
private
|
|
52
53
|
|
|
53
|
-
def
|
|
54
|
-
@
|
|
54
|
+
def current_pod_set
|
|
55
|
+
@last_pod_set = K8s::PodSet.new(namespace:, filter:)
|
|
55
56
|
end
|
|
56
57
|
end
|
|
57
58
|
end
|
|
@@ -2,12 +2,12 @@ module Vidar
|
|
|
2
2
|
class HoneycombNotification
|
|
3
3
|
def self.get
|
|
4
4
|
new(
|
|
5
|
-
github:
|
|
6
|
-
revision:
|
|
5
|
+
github: Config.get!(:github),
|
|
6
|
+
revision: Config.get!(:revision),
|
|
7
7
|
revision_name: Config.get!(:revision_name),
|
|
8
|
-
build_url:
|
|
8
|
+
build_url: Config.build_url,
|
|
9
9
|
deploy_config: Config.deploy_config,
|
|
10
|
-
api_key: Config.get(:honeycomb_api_key)
|
|
10
|
+
api_key: Config.get(:honeycomb_api_key)
|
|
11
11
|
)
|
|
12
12
|
end
|
|
13
13
|
|
|
@@ -68,8 +68,8 @@ module Vidar
|
|
|
68
68
|
|
|
69
69
|
response = connection.post do |req|
|
|
70
70
|
req.url "https://api.honeycomb.io/1/markers/#{dataset}"
|
|
71
|
-
req.headers[
|
|
72
|
-
req.headers[
|
|
71
|
+
req.headers["Content-Type"] = "application/json"
|
|
72
|
+
req.headers["X-Honeycomb-Team"] = api_key.to_s
|
|
73
73
|
req.body = data.to_json
|
|
74
74
|
end
|
|
75
75
|
|
|
@@ -77,6 +77,9 @@ module Vidar
|
|
|
77
77
|
|
|
78
78
|
warn "Honeycomb marker not created: status: #{response.status} response: #{response.body}"
|
|
79
79
|
false
|
|
80
|
+
rescue Faraday::Error => e
|
|
81
|
+
warn "Honeycomb legacy marker request failed: #{e.message}"
|
|
82
|
+
false
|
|
80
83
|
end
|
|
81
84
|
|
|
82
85
|
def create_marker
|
|
@@ -84,8 +87,8 @@ module Vidar
|
|
|
84
87
|
|
|
85
88
|
response = connection.post do |req|
|
|
86
89
|
req.url "https://api.honeycomb.io/1/markers/__all__"
|
|
87
|
-
req.headers[
|
|
88
|
-
req.headers[
|
|
90
|
+
req.headers["Content-Type"] = "application/json"
|
|
91
|
+
req.headers["X-Honeycomb-Team"] = Config.honeycomb_env_api_key(dataset).to_s
|
|
89
92
|
req.body = data.to_json
|
|
90
93
|
end
|
|
91
94
|
|
|
@@ -93,15 +96,18 @@ module Vidar
|
|
|
93
96
|
|
|
94
97
|
warn "Honeycomb marker not created: status: #{response.status} response: #{response.body}"
|
|
95
98
|
false
|
|
99
|
+
rescue Faraday::Error => e
|
|
100
|
+
warn "Honeycomb env marker request failed: #{e.message}"
|
|
101
|
+
false
|
|
96
102
|
end
|
|
97
103
|
|
|
98
104
|
def data
|
|
99
105
|
{
|
|
100
|
-
message: "#{success? ?
|
|
106
|
+
message: "#{success? ? "Successful" : "Failed"} deploy of #{github} revision #{revision} - #{revision_name}",
|
|
101
107
|
type: success? ? "deploy" : "failed_deploy",
|
|
102
108
|
start_time: start_time.to_i,
|
|
103
109
|
end_time: end_time.to_i,
|
|
104
|
-
url: build_url
|
|
110
|
+
url: build_url
|
|
105
111
|
}
|
|
106
112
|
end
|
|
107
113
|
end
|
data/lib/vidar/interpolation.rb
CHANGED
|
@@ -8,7 +8,7 @@ module Vidar
|
|
|
8
8
|
fail ArgumentError, "getter must respond_to get." unless getter.respond_to?(:get)
|
|
9
9
|
|
|
10
10
|
string.gsub(INTERPOLATION_PATTERN) do |match|
|
|
11
|
-
getter.get($1) || ENV[$1] || match
|
|
11
|
+
getter.get($1) || ENV[$1] || match
|
|
12
12
|
end
|
|
13
13
|
end
|
|
14
14
|
end
|
data/lib/vidar/k8s/container.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
module Vidar
|
|
2
2
|
module K8s
|
|
3
|
+
# Represents a single Kubernetes container and its current state.
|
|
3
4
|
class Container
|
|
4
5
|
JOB_KIND = "Job".freeze
|
|
5
6
|
|
|
@@ -17,22 +18,26 @@ module Vidar
|
|
|
17
18
|
@message = data["message"]
|
|
18
19
|
end
|
|
19
20
|
|
|
21
|
+
# @return [String] container name, falling back to pod name
|
|
20
22
|
def name
|
|
21
23
|
data["name"] || pod_name
|
|
22
24
|
end
|
|
23
25
|
|
|
26
|
+
# @return [Boolean] true if the container is considered successfully deployed
|
|
24
27
|
def deployed?
|
|
25
28
|
return terminated? if job?
|
|
26
29
|
|
|
27
30
|
ready? && running?
|
|
28
31
|
end
|
|
29
32
|
|
|
33
|
+
# @return [Boolean] true if the container completed successfully
|
|
30
34
|
def success?
|
|
31
35
|
return terminated_completed? if job?
|
|
32
36
|
|
|
33
37
|
ready_and_running?
|
|
34
38
|
end
|
|
35
39
|
|
|
40
|
+
# @return [Boolean] true if the container is ready and running
|
|
36
41
|
def ready_and_running?
|
|
37
42
|
ready? && running?
|
|
38
43
|
end
|
|
@@ -46,9 +51,10 @@ module Vidar
|
|
|
46
51
|
parts << namespace.to_s.ljust(20, " ")
|
|
47
52
|
parts << name.to_s.ljust(35, " ")
|
|
48
53
|
parts += text_statuses.map { |s| s.ljust(45, " ") }
|
|
49
|
-
"| #{parts.join(
|
|
54
|
+
"| #{parts.join(" | ")} |"
|
|
50
55
|
end
|
|
51
56
|
|
|
57
|
+
# @return [Array<String>] two-element array with status label and detail
|
|
52
58
|
def text_statuses
|
|
53
59
|
if unschedulable?
|
|
54
60
|
[ColorizedString["Unschedulable"].light_red, ColorizedString[message].light_red]
|
|
@@ -67,18 +73,21 @@ module Vidar
|
|
|
67
73
|
elsif waiting?
|
|
68
74
|
[ColorizedString["Waiting"].light_yellow, ""]
|
|
69
75
|
else
|
|
70
|
-
[ColorizedString[
|
|
76
|
+
[ColorizedString["Unknown"].light_yellow, state.empty? ? "" : state.inspect]
|
|
71
77
|
end
|
|
72
78
|
end
|
|
73
79
|
|
|
80
|
+
# @return [Boolean] true if container state is "waiting"
|
|
74
81
|
def waiting?
|
|
75
82
|
state["waiting"]
|
|
76
83
|
end
|
|
77
84
|
|
|
85
|
+
# @return [Boolean] true if container is ready
|
|
78
86
|
def ready?
|
|
79
87
|
data["ready"]
|
|
80
88
|
end
|
|
81
89
|
|
|
90
|
+
# @return [Boolean] true if container is running
|
|
82
91
|
def running?
|
|
83
92
|
!running_started_at.nil?
|
|
84
93
|
end
|
|
@@ -87,10 +96,12 @@ module Vidar
|
|
|
87
96
|
state.dig("running", "startedAt")
|
|
88
97
|
end
|
|
89
98
|
|
|
99
|
+
# @return [Boolean] true if container has terminated (any reason)
|
|
90
100
|
def terminated?
|
|
91
101
|
!state["terminated"].nil?
|
|
92
102
|
end
|
|
93
103
|
|
|
104
|
+
# @return [Boolean] true if container terminated successfully
|
|
94
105
|
def terminated_completed?
|
|
95
106
|
state.dig("terminated", "reason") == "Completed" || state.dig("terminated", "exitCode") == 0
|
|
96
107
|
end
|
|
@@ -99,18 +110,34 @@ module Vidar
|
|
|
99
110
|
state.dig("terminated", "finishedAt")
|
|
100
111
|
end
|
|
101
112
|
|
|
113
|
+
# @return [Boolean] true if container terminated with an error exit code
|
|
102
114
|
def terminated_error?
|
|
103
|
-
|
|
115
|
+
exit_code = state.dig("terminated", "exitCode")
|
|
116
|
+
state.dig("terminated", "reason") == "Error" || (!exit_code.nil? && exit_code != 0)
|
|
104
117
|
end
|
|
105
118
|
|
|
119
|
+
# @return [Boolean] true if container state is unknown
|
|
120
|
+
def unknown?
|
|
121
|
+
!unschedulable? && !running? && !terminated? && !waiting?
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# @return [Boolean] true if container reason is Unschedulable
|
|
106
125
|
def unschedulable?
|
|
107
126
|
reason == "Unschedulable"
|
|
108
127
|
end
|
|
109
128
|
|
|
129
|
+
# @return [Boolean] true if this container belongs to a Job
|
|
110
130
|
def job?
|
|
111
131
|
kind == JOB_KIND
|
|
112
132
|
end
|
|
113
133
|
|
|
134
|
+
# @param sidecar_names [Array<String>] list of sidecar container names to match
|
|
135
|
+
# @return [Boolean] true if this container is a known sidecar
|
|
136
|
+
def sidecar?(sidecar_names = ["istio-proxy"])
|
|
137
|
+
sidecar_names.include?(name.to_s)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# @return [Boolean] true if this is an istio-proxy sidecar container
|
|
114
141
|
def istio?
|
|
115
142
|
name == "istio-proxy"
|
|
116
143
|
end
|
data/lib/vidar/k8s/pod_set.rb
CHANGED
|
@@ -1,19 +1,27 @@
|
|
|
1
1
|
module Vidar
|
|
2
2
|
module K8s
|
|
3
|
+
# Represents a collection of Kubernetes pods and their containers
|
|
4
|
+
# fetched via `kubectl get pods` for a given namespace.
|
|
3
5
|
class PodSet
|
|
6
|
+
# @param namespace [String] Kubernetes namespace, or "all" for all namespaces
|
|
7
|
+
# @param filter [String, nil] optional substring filter on container names
|
|
4
8
|
def initialize(namespace:, filter: nil)
|
|
5
9
|
@namespace = namespace
|
|
6
10
|
@filter = filter
|
|
7
11
|
end
|
|
8
12
|
|
|
13
|
+
# @return [Boolean] true if any containers exist in the pod set
|
|
9
14
|
def any?
|
|
10
15
|
containers.any?
|
|
11
16
|
end
|
|
12
17
|
|
|
18
|
+
# @return [Boolean] true if any containers are in waiting state
|
|
13
19
|
def waiting?
|
|
14
20
|
containers.any?(&:waiting?)
|
|
15
21
|
end
|
|
16
22
|
|
|
23
|
+
# Logs current container states and returns whether all are deployed.
|
|
24
|
+
# @return [Boolean]
|
|
17
25
|
def deployed?
|
|
18
26
|
if items.empty?
|
|
19
27
|
Log.error "Could not fetch pod list"
|
|
@@ -29,12 +37,14 @@ module Vidar
|
|
|
29
37
|
containers.all?(&:deployed?)
|
|
30
38
|
end
|
|
31
39
|
|
|
40
|
+
# @return [Boolean] true if all containers report success
|
|
32
41
|
def success?
|
|
33
42
|
return false if containers.empty?
|
|
34
43
|
|
|
35
44
|
containers.all?(&:success?)
|
|
36
45
|
end
|
|
37
46
|
|
|
47
|
+
# @return [Array<Container>] filtered container list
|
|
38
48
|
def containers
|
|
39
49
|
if filter
|
|
40
50
|
all_containers.select { |cs| cs.name.to_s.include?(filter) }
|
|
@@ -49,21 +59,30 @@ module Vidar
|
|
|
49
59
|
|
|
50
60
|
def items
|
|
51
61
|
@items ||= begin
|
|
52
|
-
|
|
53
|
-
|
|
62
|
+
output = kubectl_get.strip
|
|
63
|
+
return [] if output.empty?
|
|
64
|
+
|
|
65
|
+
JSON.parse(output)["items"] || []
|
|
66
|
+
rescue JSON::ParserError => e
|
|
67
|
+
Log.error "Failed to parse kubectl JSON output: #{e.message}"
|
|
68
|
+
[]
|
|
54
69
|
end
|
|
55
70
|
end
|
|
56
71
|
|
|
57
72
|
def kubectl_get
|
|
58
|
-
|
|
59
|
-
|
|
73
|
+
envs = Run.kubectl_envs_hash
|
|
74
|
+
stdout, stderr, status = if namespace == "all"
|
|
75
|
+
Open3.capture3(envs, "kubectl", "get", "pods", "--all-namespaces", "-o", "json")
|
|
60
76
|
else
|
|
61
|
-
|
|
77
|
+
Open3.capture3(envs, "kubectl", "get", "pods", "-n", namespace, "-o", "json")
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
unless status.success?
|
|
81
|
+
Log.error "kubectl get pods failed: #{stderr.strip}"
|
|
82
|
+
return ""
|
|
62
83
|
end
|
|
63
|
-
end
|
|
64
84
|
|
|
65
|
-
|
|
66
|
-
containers.select(&:ready_and_running?)
|
|
85
|
+
stdout
|
|
67
86
|
end
|
|
68
87
|
|
|
69
88
|
def all_containers
|
data/lib/vidar/log.rb
CHANGED
data/lib/vidar/run.rb
CHANGED
|
@@ -7,7 +7,7 @@ module Vidar
|
|
|
7
7
|
|
|
8
8
|
def docker_compose(command)
|
|
9
9
|
args = %w[revision current_branch].map { |arg| "#{arg.upcase}=#{Config.get!(arg.to_sym)}" }
|
|
10
|
-
system("#{args.join(
|
|
10
|
+
system("#{args.join(" ")} #{Config.get!(:compose_cmd)} -f #{Config.get!(:compose_file)} #{command}") || exit(1)
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def kubectl(command, namespace: Config.get!(:namespace))
|
|
@@ -24,7 +24,7 @@ module Vidar
|
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
def kubectl_envs_hash
|
|
27
|
-
{
|
|
27
|
+
{"HTTPS_PROXY" => Config.deploy_config.https_proxy}.compact
|
|
28
28
|
end
|
|
29
29
|
end
|
|
30
30
|
end
|
|
@@ -13,9 +13,12 @@ module Vidar
|
|
|
13
13
|
def call
|
|
14
14
|
connection.post do |req|
|
|
15
15
|
req.url webhook_url
|
|
16
|
-
req.headers[
|
|
16
|
+
req.headers["Content-Type"] = "application/json"
|
|
17
17
|
req.body = data.to_json
|
|
18
18
|
end
|
|
19
|
+
rescue Faraday::Error => e
|
|
20
|
+
warn "Sentry notification request failed: #{e.message}"
|
|
21
|
+
nil
|
|
19
22
|
end
|
|
20
23
|
|
|
21
24
|
private
|
|
@@ -2,10 +2,10 @@ module Vidar
|
|
|
2
2
|
class SlackNotification
|
|
3
3
|
def self.get
|
|
4
4
|
new(
|
|
5
|
-
github:
|
|
6
|
-
revision:
|
|
5
|
+
github: Config.get!(:github),
|
|
6
|
+
revision: Config.get!(:revision),
|
|
7
7
|
revision_name: Config.get!(:revision_name),
|
|
8
|
-
build_url:
|
|
8
|
+
build_url: Config.build_url,
|
|
9
9
|
deploy_config: Config.deploy_config
|
|
10
10
|
)
|
|
11
11
|
end
|
|
@@ -31,7 +31,7 @@ module Vidar
|
|
|
31
31
|
def failure
|
|
32
32
|
message = [
|
|
33
33
|
"<!channel> Failed deploy of #{github_link} to #{deploy_link}.",
|
|
34
|
-
build_link
|
|
34
|
+
build_link
|
|
35
35
|
]
|
|
36
36
|
perform_with data(message:, color: failure_color)
|
|
37
37
|
end
|
|
@@ -39,7 +39,7 @@ module Vidar
|
|
|
39
39
|
def success
|
|
40
40
|
message = [
|
|
41
41
|
"Successful deploy of #{github_link} to #{deploy_link}.",
|
|
42
|
-
build_link
|
|
42
|
+
build_link
|
|
43
43
|
]
|
|
44
44
|
perform_with data(message:, color: success_color)
|
|
45
45
|
end
|
|
@@ -51,9 +51,12 @@ module Vidar
|
|
|
51
51
|
def perform_with(data)
|
|
52
52
|
connection.post do |req|
|
|
53
53
|
req.url webhook_url
|
|
54
|
-
req.headers[
|
|
54
|
+
req.headers["Content-Type"] = "application/json"
|
|
55
55
|
req.body = data.to_json
|
|
56
56
|
end
|
|
57
|
+
rescue Faraday::Error => e
|
|
58
|
+
warn "Slack notification request failed: #{e.message}"
|
|
59
|
+
nil
|
|
57
60
|
end
|
|
58
61
|
|
|
59
62
|
private
|
|
@@ -72,7 +75,7 @@ module Vidar
|
|
|
72
75
|
title_link: github_url,
|
|
73
76
|
color:,
|
|
74
77
|
text:,
|
|
75
|
-
fallback: text
|
|
78
|
+
fallback: text
|
|
76
79
|
}
|
|
77
80
|
]
|
|
78
81
|
}
|
data/lib/vidar/version.rb
CHANGED
data/lib/vidar.rb
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
require
|
|
2
|
-
require
|
|
3
|
-
require
|
|
4
|
-
require
|
|
1
|
+
require "json"
|
|
2
|
+
require "open3"
|
|
3
|
+
require "uri"
|
|
4
|
+
require "yaml"
|
|
5
5
|
|
|
6
|
-
require
|
|
7
|
-
require
|
|
8
|
-
require
|
|
6
|
+
require "colorized_string"
|
|
7
|
+
require "faraday"
|
|
8
|
+
require "thor"
|
|
9
9
|
|
|
10
|
-
require
|
|
11
|
-
require
|
|
12
|
-
require
|
|
13
|
-
require
|
|
14
|
-
require
|
|
15
|
-
require
|
|
16
|
-
require
|
|
17
|
-
require
|
|
18
|
-
require
|
|
19
|
-
require
|
|
20
|
-
require
|
|
21
|
-
require
|
|
22
|
-
require
|
|
10
|
+
require "vidar/version"
|
|
11
|
+
require "vidar/config"
|
|
12
|
+
require "vidar/honeycomb_notification"
|
|
13
|
+
require "vidar/interpolation"
|
|
14
|
+
require "vidar/log"
|
|
15
|
+
require "vidar/run"
|
|
16
|
+
require "vidar/sentry_notification"
|
|
17
|
+
require "vidar/slack_notification"
|
|
18
|
+
require "vidar/k8s/container"
|
|
19
|
+
require "vidar/k8s/pod_set"
|
|
20
|
+
require "vidar/deploy_config"
|
|
21
|
+
require "vidar/deploy_status"
|
|
22
|
+
require "vidar/cli"
|
|
23
23
|
|
|
24
24
|
module Vidar
|
|
25
25
|
Error = Class.new(StandardError)
|
data/vidar.gemspec
CHANGED
|
@@ -1,32 +1,32 @@
|
|
|
1
|
-
lib = File.expand_path(
|
|
1
|
+
lib = File.expand_path("lib", __dir__)
|
|
2
2
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
3
|
-
require
|
|
3
|
+
require "vidar/version"
|
|
4
4
|
|
|
5
5
|
Gem::Specification.new do |spec|
|
|
6
|
-
spec.name =
|
|
6
|
+
spec.name = "vidar"
|
|
7
7
|
spec.version = Vidar::VERSION
|
|
8
|
-
spec.authors = [
|
|
9
|
-
spec.email = [
|
|
8
|
+
spec.authors = ["Krzysztof Knapik", "RenoFi Engineering Team"]
|
|
9
|
+
spec.email = ["knapo@knapo.net", "engineering@renofi.com"]
|
|
10
10
|
|
|
11
|
-
spec.summary =
|
|
12
|
-
spec.homepage =
|
|
13
|
-
spec.license =
|
|
11
|
+
spec.summary = "K8s deployment tools based on thor"
|
|
12
|
+
spec.homepage = "https://github.com/RenoFi/vidar"
|
|
13
|
+
spec.license = "MIT"
|
|
14
14
|
|
|
15
|
-
spec.metadata[
|
|
16
|
-
spec.metadata[
|
|
17
|
-
spec.metadata[
|
|
18
|
-
spec.metadata[
|
|
15
|
+
spec.metadata["homepage_uri"] = "https://github.com/RenoFi/vidar"
|
|
16
|
+
spec.metadata["source_code_uri"] = "https://github.com/RenoFi/vidar"
|
|
17
|
+
spec.metadata["changelog_uri"] = "https://github.com/RenoFi/vidar/blob/master/CHANGELOG.md"
|
|
18
|
+
spec.metadata["rubygems_mfa_required"] = "true"
|
|
19
19
|
|
|
20
|
-
spec.required_ruby_version = Gem::Requirement.new(
|
|
20
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 3.4.0")
|
|
21
21
|
|
|
22
22
|
spec.files = Dir.chdir(__dir__) do
|
|
23
23
|
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(bin/|spec/|\.rub)}) }
|
|
24
24
|
end
|
|
25
|
-
spec.bindir =
|
|
25
|
+
spec.bindir = "exe"
|
|
26
26
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
27
|
-
spec.require_paths = [
|
|
27
|
+
spec.require_paths = ["lib"]
|
|
28
28
|
|
|
29
|
-
spec.add_dependency
|
|
30
|
-
spec.add_dependency
|
|
31
|
-
spec.add_dependency
|
|
29
|
+
spec.add_dependency "colorize"
|
|
30
|
+
spec.add_dependency "faraday", ">= 2.0", "< 3"
|
|
31
|
+
spec.add_dependency "thor", "~> 1.0"
|
|
32
32
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: vidar
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.17.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Krzysztof Knapik
|
|
@@ -30,14 +30,20 @@ dependencies:
|
|
|
30
30
|
requirements:
|
|
31
31
|
- - ">="
|
|
32
32
|
- !ruby/object:Gem::Version
|
|
33
|
-
version: '0'
|
|
33
|
+
version: '2.0'
|
|
34
|
+
- - "<"
|
|
35
|
+
- !ruby/object:Gem::Version
|
|
36
|
+
version: '3'
|
|
34
37
|
type: :runtime
|
|
35
38
|
prerelease: false
|
|
36
39
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
40
|
requirements:
|
|
38
41
|
- - ">="
|
|
39
42
|
- !ruby/object:Gem::Version
|
|
40
|
-
version: '0'
|
|
43
|
+
version: '2.0'
|
|
44
|
+
- - "<"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '3'
|
|
41
47
|
- !ruby/object:Gem::Dependency
|
|
42
48
|
name: thor
|
|
43
49
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -72,6 +78,7 @@ files:
|
|
|
72
78
|
- ".github/workflows/ci.yml"
|
|
73
79
|
- ".gitignore"
|
|
74
80
|
- ".rspec"
|
|
81
|
+
- ".standard.yml"
|
|
75
82
|
- CHANGELOG.md
|
|
76
83
|
- Gemfile
|
|
77
84
|
- Gemfile.lock
|
|
@@ -116,7 +123,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
116
123
|
- !ruby/object:Gem::Version
|
|
117
124
|
version: '0'
|
|
118
125
|
requirements: []
|
|
119
|
-
rubygems_version: 4.0.
|
|
126
|
+
rubygems_version: 4.0.10
|
|
120
127
|
specification_version: 4
|
|
121
128
|
summary: K8s deployment tools based on thor
|
|
122
129
|
test_files: []
|