mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-31 18:48:08 +00:00 
			
		
		
		
	VAULT-33074: add github sub-command to pipeline (#29403)
				
					
				
			* VAULT-33074: add `github` sub-command to `pipeline` Investigating test workflow failures is common task that engineers on the sustaining rotation perform. This task often requires quite a bit of manual labor by manually inspecting all failed/cancelled workflows in the Github UI on per repo/branch/workflow basis and performing root cause analysis. As we work to improve our pipeline discoverability this PR adds a new `github` sub-command to the `pipeline` utility that allows querying for such workflows and returning either machine readable or human readable summaries in a single place. Eventually we plan to automate sending a summary of this data to an OTEL collector automatically but for now sustaining engineers can utilize it to query for workflows with lots of various criteria. A common pattern for investigating build/enos test failure workflows would be: ```shell export GITHUB_TOKEN="YOUR_TOKEN" go run -race ./tools/pipeline/... github list-workflow-runs -o hashicorp -r vault -d '2025-01-13..2025-01-23' --branch main --status failure build ``` This will list `build` workflow runs in `hashicorp/vault` repo for the `main` branch with the `status` or `conclusion` of `failure` within the date range of `2025-01-13..2025-01-23`. A sustaining engineer will likely do this for both `vault` and `vault-enterprise` repositories along with `enos-release-testing-oss` and `enos-release-testing-ent` workflows in addition to `build` in order to get a full picture of the last weeks failures. You can also use this utility to summarize workflows based on other statuses, branches, HEAD SHA's, event triggers, github actors, etc. For a full list of filter arguments you can pass `-h` to the sub-command. > [!CAUTION] > Be careful not to run this without setting strict filter arguments. > Failing to do so could result in trying to summarize way too many > workflows resulting in your API token being disabled for an hour. Signed-off-by: Ryan Cragun <me@ryan.ec>
This commit is contained in:
		
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -133,4 +133,6 @@ website/components/node_modules | |||||||
| tools/godoctests/.bin | tools/godoctests/.bin | ||||||
| tools/gonilnilfunctions/.bin | tools/gonilnilfunctions/.bin | ||||||
| tools/codechecker/.bin | tools/codechecker/.bin | ||||||
|  | tools/pipeline/.bin | ||||||
|  | tools/pipeline/pipeline | ||||||
| .ci-bootstrap | .ci-bootstrap | ||||||
| @@ -4,10 +4,11 @@ go 1.23.2 | |||||||
|  |  | ||||||
| require ( | require ( | ||||||
| 	github.com/Masterminds/semver v1.5.0 | 	github.com/Masterminds/semver v1.5.0 | ||||||
| 	github.com/hashicorp/hcl/v2 v2.22.0 | 	github.com/google/go-github/v68 v68.0.0 | ||||||
|  | 	github.com/hashicorp/hcl/v2 v2.23.0 | ||||||
| 	github.com/hashicorp/releases-api v0.1.23 | 	github.com/hashicorp/releases-api v0.1.23 | ||||||
| 	github.com/spf13/cobra v1.8.1 | 	github.com/spf13/cobra v1.8.1 | ||||||
| 	github.com/stretchr/testify v1.9.0 | 	github.com/stretchr/testify v1.10.0 | ||||||
| 	github.com/veqryn/slog-context v0.7.0 | 	github.com/veqryn/slog-context v0.7.0 | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -17,29 +18,30 @@ require ( | |||||||
| 	github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect | 	github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect | ||||||
| 	github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect | 	github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect | ||||||
| 	github.com/docker/go-units v0.5.0 // indirect | 	github.com/docker/go-units v0.5.0 // indirect | ||||||
| 	github.com/fatih/color v1.16.0 // indirect | 	github.com/fatih/color v1.18.0 // indirect | ||||||
| 	github.com/go-logr/logr v1.4.1 // indirect | 	github.com/go-logr/logr v1.4.2 // indirect | ||||||
| 	github.com/go-logr/stdr v1.2.2 // indirect | 	github.com/go-logr/stdr v1.2.2 // indirect | ||||||
| 	github.com/go-openapi/analysis v0.23.0 // indirect | 	github.com/go-openapi/analysis v0.23.0 // indirect | ||||||
| 	github.com/go-openapi/errors v0.22.0 // indirect | 	github.com/go-openapi/errors v0.22.0 // indirect | ||||||
| 	github.com/go-openapi/jsonpointer v0.21.0 // indirect | 	github.com/go-openapi/jsonpointer v0.21.0 // indirect | ||||||
| 	github.com/go-openapi/jsonreference v0.21.0 // indirect | 	github.com/go-openapi/jsonreference v0.21.0 // indirect | ||||||
| 	github.com/go-openapi/loads v0.22.0 // indirect | 	github.com/go-openapi/loads v0.22.0 // indirect | ||||||
| 	github.com/go-openapi/runtime v0.26.0 // indirect | 	github.com/go-openapi/runtime v0.28.0 // indirect | ||||||
| 	github.com/go-openapi/spec v0.21.0 // indirect | 	github.com/go-openapi/spec v0.21.0 // indirect | ||||||
| 	github.com/go-openapi/strfmt v0.23.0 // indirect | 	github.com/go-openapi/strfmt v0.23.0 // indirect | ||||||
| 	github.com/go-openapi/swag v0.23.0 // indirect | 	github.com/go-openapi/swag v0.23.0 // indirect | ||||||
| 	github.com/go-openapi/validate v0.24.0 // indirect | 	github.com/go-openapi/validate v0.24.0 // indirect | ||||||
| 	github.com/google/go-cmp v0.6.0 // indirect | 	github.com/google/go-cmp v0.6.0 // indirect | ||||||
|  | 	github.com/google/go-querystring v1.1.0 // indirect | ||||||
| 	github.com/google/uuid v1.6.0 // indirect | 	github.com/google/uuid v1.6.0 // indirect | ||||||
| 	github.com/hashicorp/go-hclog v1.6.3 // indirect | 	github.com/hashicorp/go-hclog v1.6.3 // indirect | ||||||
| 	github.com/hashicorp/go-version v1.7.0 // indirect | 	github.com/hashicorp/go-version v1.7.0 // indirect | ||||||
| 	github.com/imdario/mergo v0.3.15 // indirect | 	github.com/imdario/mergo v0.3.16 // indirect | ||||||
| 	github.com/inconshreveable/mousetrap v1.1.0 // indirect | 	github.com/inconshreveable/mousetrap v1.1.0 // indirect | ||||||
| 	github.com/jessevdk/go-flags v1.6.1 // indirect | 	github.com/jessevdk/go-flags v1.6.1 // indirect | ||||||
| 	github.com/josharian/intern v1.0.0 // indirect | 	github.com/josharian/intern v1.0.0 // indirect | ||||||
| 	github.com/mailru/easyjson v0.7.7 // indirect | 	github.com/mailru/easyjson v0.9.0 // indirect | ||||||
| 	github.com/mattn/go-colorable v0.1.13 // indirect | 	github.com/mattn/go-colorable v0.1.14 // indirect | ||||||
| 	github.com/mattn/go-isatty v0.0.20 // indirect | 	github.com/mattn/go-isatty v0.0.20 // indirect | ||||||
| 	github.com/mitchellh/go-wordwrap v1.0.1 // indirect | 	github.com/mitchellh/go-wordwrap v1.0.1 // indirect | ||||||
| 	github.com/mitchellh/hashstructure v1.1.0 // indirect | 	github.com/mitchellh/hashstructure v1.1.0 // indirect | ||||||
| @@ -49,17 +51,17 @@ require ( | |||||||
| 	github.com/opentracing/opentracing-go v1.2.0 // indirect | 	github.com/opentracing/opentracing-go v1.2.0 // indirect | ||||||
| 	github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect | 	github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect | ||||||
| 	github.com/spf13/pflag v1.0.5 // indirect | 	github.com/spf13/pflag v1.0.5 // indirect | ||||||
| 	github.com/zclconf/go-cty v1.15.0 // indirect | 	github.com/zclconf/go-cty v1.16.2 // indirect | ||||||
| 	go.mongodb.org/mongo-driver v1.14.0 // indirect | 	go.mongodb.org/mongo-driver v1.17.2 // indirect | ||||||
| 	go.opentelemetry.io/otel v1.20.0 // indirect | 	go.opentelemetry.io/auto/sdk v1.1.0 // indirect | ||||||
| 	go.opentelemetry.io/otel/metric v1.20.0 // indirect | 	go.opentelemetry.io/otel v1.34.0 // indirect | ||||||
| 	go.opentelemetry.io/otel/trace v1.20.0 // indirect | 	go.opentelemetry.io/otel/metric v1.34.0 // indirect | ||||||
| 	golang.org/x/mod v0.21.0 // indirect | 	go.opentelemetry.io/otel/trace v1.34.0 // indirect | ||||||
| 	golang.org/x/net v0.30.0 // indirect | 	golang.org/x/mod v0.22.0 // indirect | ||||||
| 	golang.org/x/sync v0.8.0 // indirect | 	golang.org/x/net v0.34.0 // indirect | ||||||
| 	golang.org/x/sys v0.26.0 // indirect | 	golang.org/x/sync v0.10.0 // indirect | ||||||
| 	golang.org/x/text v0.19.0 // indirect | 	golang.org/x/sys v0.29.0 // indirect | ||||||
| 	golang.org/x/tools v0.26.0 // indirect | 	golang.org/x/text v0.21.0 // indirect | ||||||
| 	gopkg.in/yaml.v2 v2.4.0 // indirect | 	golang.org/x/tools v0.29.0 // indirect | ||||||
| 	gopkg.in/yaml.v3 v3.0.1 // indirect | 	gopkg.in/yaml.v3 v3.0.1 // indirect | ||||||
| ) | ) | ||||||
|   | |||||||
| @@ -38,11 +38,11 @@ github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+m | |||||||
| github.com/ebitengine/purego v0.6.0-alpha.5 h1:EYID3JOAdmQ4SNZYJHu9V6IqOeRQDBYxqKAg9PyoHFY= | github.com/ebitengine/purego v0.6.0-alpha.5 h1:EYID3JOAdmQ4SNZYJHu9V6IqOeRQDBYxqKAg9PyoHFY= | ||||||
| github.com/ebitengine/purego v0.6.0-alpha.5/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ= | github.com/ebitengine/purego v0.6.0-alpha.5/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ= | ||||||
| github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= | github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= | ||||||
| github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= | github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= | ||||||
| github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= | github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= | ||||||
| github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= | github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= | ||||||
| github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= | github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= | ||||||
| github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= | github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= | ||||||
| github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= | ||||||
| github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= | ||||||
| github.com/go-openapi/analysis v0.23.0 h1:aGday7OWupfMs+LbmLZG4k0MYXIANxcuBTYUC03zFCU= | github.com/go-openapi/analysis v0.23.0 h1:aGday7OWupfMs+LbmLZG4k0MYXIANxcuBTYUC03zFCU= | ||||||
| @@ -55,8 +55,8 @@ github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF | |||||||
| github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= | github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= | ||||||
| github.com/go-openapi/loads v0.22.0 h1:ECPGd4jX1U6NApCGG1We+uEozOAvXvJSF4nnwHZ8Aco= | github.com/go-openapi/loads v0.22.0 h1:ECPGd4jX1U6NApCGG1We+uEozOAvXvJSF4nnwHZ8Aco= | ||||||
| github.com/go-openapi/loads v0.22.0/go.mod h1:yLsaTCS92mnSAZX5WWoxszLj0u+Ojl+Zs5Stn1oF+rs= | github.com/go-openapi/loads v0.22.0/go.mod h1:yLsaTCS92mnSAZX5WWoxszLj0u+Ojl+Zs5Stn1oF+rs= | ||||||
| github.com/go-openapi/runtime v0.26.0 h1:HYOFtG00FM1UvqrcxbEJg/SwvDRvYLQKGhw2zaQjTcc= | github.com/go-openapi/runtime v0.28.0 h1:gpPPmWSNGo214l6n8hzdXYhPuJcGtziTOgUpvsFWGIQ= | ||||||
| github.com/go-openapi/runtime v0.26.0/go.mod h1:QgRGeZwrUcSHdeh4Ka9Glvo0ug1LC5WyE+EV88plZrQ= | github.com/go-openapi/runtime v0.28.0/go.mod h1:QN7OzcS+XuYmkQLw05akXk0jRH/eZ3kb18+1KwW9gyc= | ||||||
| github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY= | github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY= | ||||||
| github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk= | github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk= | ||||||
| github.com/go-openapi/strfmt v0.23.0 h1:nlUS6BCqcnAk0pyhi9Y+kdDVZdZMHfEKQiS4HaMgO/c= | github.com/go-openapi/strfmt v0.23.0 h1:nlUS6BCqcnAk0pyhi9Y+kdDVZdZMHfEKQiS4HaMgO/c= | ||||||
| @@ -69,8 +69,13 @@ github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= | |||||||
| github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= | github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= | ||||||
| github.com/golang-migrate/migrate/v4 v4.14.1 h1:qmRd/rNGjM1r3Ve5gHd5ZplytrD02UcItYNxJ3iUHHE= | github.com/golang-migrate/migrate/v4 v4.14.1 h1:qmRd/rNGjM1r3Ve5gHd5ZplytrD02UcItYNxJ3iUHHE= | ||||||
| github.com/golang-migrate/migrate/v4 v4.14.1/go.mod h1:l7Ks0Au6fYHuUIxUhQ0rcVX1uLlJg54C/VvW7tvxSz0= | github.com/golang-migrate/migrate/v4 v4.14.1/go.mod h1:l7Ks0Au6fYHuUIxUhQ0rcVX1uLlJg54C/VvW7tvxSz0= | ||||||
|  | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||||||
| github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= | ||||||
| github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= | ||||||
|  | github.com/google/go-github/v68 v68.0.0 h1:ZW57zeNZiXTdQ16qrDiZ0k6XucrxZ2CGmoTvcCyQG6s= | ||||||
|  | github.com/google/go-github/v68 v68.0.0/go.mod h1:K9HAUBovM2sLwM408A18h+wd9vqdLOEqTUCbnRIcx68= | ||||||
|  | github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= | ||||||
|  | github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= | ||||||
| github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= | ||||||
| github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= | ||||||
| github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= | github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= | ||||||
| @@ -83,12 +88,12 @@ github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/C | |||||||
| github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= | github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= | ||||||
| github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= | github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= | ||||||
| github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= | github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= | ||||||
| github.com/hashicorp/hcl/v2 v2.22.0 h1:hkZ3nCtqeJsDhPRFz5EA9iwcG1hNWGePOTw6oyul12M= | github.com/hashicorp/hcl/v2 v2.23.0 h1:Fphj1/gCylPxHutVSEOf2fBOh1VE4AuLV7+kbJf3qos= | ||||||
| github.com/hashicorp/hcl/v2 v2.22.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA= | github.com/hashicorp/hcl/v2 v2.23.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA= | ||||||
| github.com/hashicorp/releases-api v0.1.23 h1:7cKuNZU5kBO+CsBvTkNDN2XdO0dQdXFtSC8f9IEXb+k= | github.com/hashicorp/releases-api v0.1.23 h1:7cKuNZU5kBO+CsBvTkNDN2XdO0dQdXFtSC8f9IEXb+k= | ||||||
| github.com/hashicorp/releases-api v0.1.23/go.mod h1:RgXaKHrH02RjK2SMAVyHmDPrTrwR0iR6El3W93kh5Vc= | github.com/hashicorp/releases-api v0.1.23/go.mod h1:RgXaKHrH02RjK2SMAVyHmDPrTrwR0iR6El3W93kh5Vc= | ||||||
| github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= | github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= | ||||||
| github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= | github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= | ||||||
| github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= | ||||||
| github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= | ||||||
| github.com/jackc/pgerrcode v0.0.0-20201024163028-a0d42d470451 h1:WAvSpGf7MsFuzAtK4Vk7R4EVe+liW4x83r4oWu0WHKw= | github.com/jackc/pgerrcode v0.0.0-20201024163028-a0d42d470451 h1:WAvSpGf7MsFuzAtK4Vk7R4EVe+liW4x83r4oWu0WHKw= | ||||||
| @@ -113,15 +118,14 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= | |||||||
| github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= | ||||||
| github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= | github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= | ||||||
| github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= | github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= | ||||||
| github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= | github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= | ||||||
| github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= | github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= | ||||||
| github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= | github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= | ||||||
| github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= | github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= | ||||||
| github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= | github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= | ||||||
| github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= | github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= | ||||||
| github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= | ||||||
| github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= | ||||||
| github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= |  | ||||||
| github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= | ||||||
| github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= | ||||||
| github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= | github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= | ||||||
| @@ -148,8 +152,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE | |||||||
| github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||||||
| github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= | ||||||
| github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||||||
| github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= | github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= | ||||||
| github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= | github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= | ||||||
| github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= | ||||||
| github.com/secure-systems-lab/go-securesystemslib v0.7.0 h1:OwvJ5jQf9LnIAS83waAjPbcMsODrTQUpJ02eNLUoxBg= | github.com/secure-systems-lab/go-securesystemslib v0.7.0 h1:OwvJ5jQf9LnIAS83waAjPbcMsODrTQUpJ02eNLUoxBg= | ||||||
| github.com/secure-systems-lab/go-securesystemslib v0.7.0/go.mod h1:/2gYnlnHVQ6xeGtfIqFy7Do03K4cdCY0A/GlJLDKLHI= | github.com/secure-systems-lab/go-securesystemslib v0.7.0/go.mod h1:/2gYnlnHVQ6xeGtfIqFy7Do03K4cdCY0A/GlJLDKLHI= | ||||||
| @@ -160,51 +164,53 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An | |||||||
| github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||||||
| github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= | ||||||
| github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= | github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= | ||||||
| github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= | ||||||
| github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= | ||||||
| github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0= | github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0= | ||||||
| github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw= | github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw= | ||||||
| github.com/veqryn/slog-context v0.7.0 h1:Ne7ajlR6Mjs2rQQtpg8k0eO6krR5wzpareh5VpV+V2s= | github.com/veqryn/slog-context v0.7.0 h1:Ne7ajlR6Mjs2rQQtpg8k0eO6krR5wzpareh5VpV+V2s= | ||||||
| github.com/veqryn/slog-context v0.7.0/go.mod h1:E+qpdyiQs2YKRxFnX1JjpdFE1z3Ka94Kem2q9ZG6Jjo= | github.com/veqryn/slog-context v0.7.0/go.mod h1:E+qpdyiQs2YKRxFnX1JjpdFE1z3Ka94Kem2q9ZG6Jjo= | ||||||
| github.com/zclconf/go-cty v1.15.0 h1:tTCRWxsexYUmtt/wVxgDClUe+uQusuI443uL6e+5sXQ= | github.com/zclconf/go-cty v1.16.2 h1:LAJSwc3v81IRBZyUVQDUdZ7hs3SYs9jv0eZJDWHD/70= | ||||||
| github.com/zclconf/go-cty v1.15.0/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= | github.com/zclconf/go-cty v1.16.2/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= | ||||||
| github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo= | github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo= | ||||||
| github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM= | github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM= | ||||||
| go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80= | go.mongodb.org/mongo-driver v1.17.2 h1:gvZyk8352qSfzyZ2UMWcpDpMSGEr1eqE4T793SqyhzM= | ||||||
| go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c= | go.mongodb.org/mongo-driver v1.17.2/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ= | ||||||
| go.opentelemetry.io/otel v1.20.0 h1:vsb/ggIY+hUjD/zCAQHpzTmndPqv/ml2ArbsbfBYTAc= | go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= | ||||||
| go.opentelemetry.io/otel v1.20.0/go.mod h1:oUIGj3D77RwJdM6PPZImDpSZGDvkD9fhesHny69JFrs= | go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= | ||||||
| go.opentelemetry.io/otel/metric v1.20.0 h1:ZlrO8Hu9+GAhnepmRGhSU7/VkpjrNowxRN9GyKR4wzA= | go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= | ||||||
| go.opentelemetry.io/otel/metric v1.20.0/go.mod h1:90DRw3nfK4D7Sm/75yQ00gTJxtkBxX+wu6YaNymbpVM= | go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= | ||||||
| go.opentelemetry.io/otel/sdk v1.14.0 h1:PDCppFRDq8A1jL9v6KMI6dYesaq+DFcDZvjsoGvxGzY= | go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= | ||||||
| go.opentelemetry.io/otel/sdk v1.14.0/go.mod h1:bwIC5TjrNG6QDCHNWvW4HLHtUQ4I+VQDsnjhvyZCALM= | go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= | ||||||
| go.opentelemetry.io/otel/trace v1.20.0 h1:+yxVAPZPbQhbC3OfAkeIVTky6iTFpcr4SiY9om7mXSQ= | go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw= | ||||||
| go.opentelemetry.io/otel/trace v1.20.0/go.mod h1:HJSK7F/hA5RlzpZ0zKDCHCDHm556LCDtKaAo6JmBFUU= | go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg= | ||||||
|  | go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= | ||||||
|  | go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= | ||||||
| go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= | go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= | ||||||
| go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= | go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= | ||||||
| golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= | golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= | ||||||
| golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= | golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= | ||||||
| golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= | golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= | ||||||
| golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= | golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= | ||||||
| golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= | golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= | ||||||
| golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= | golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= | ||||||
| golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= | golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= | ||||||
| golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= | golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= | ||||||
| golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
| golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
| golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
| golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
| golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
| golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |  | ||||||
| golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||||
| golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= | golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= | ||||||
| golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= | golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= | ||||||
| golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= | golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= | ||||||
| golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= | golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= | ||||||
| golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= | golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= | ||||||
| golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | ||||||
| golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= | golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= | ||||||
| golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= | golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= | ||||||
|  | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||||
| golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= | golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= | ||||||
| golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= | golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= | ||||||
| google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= | google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= | ||||||
| @@ -216,7 +222,5 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN | |||||||
| gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= | ||||||
| gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= | gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= | ||||||
| gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= | gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= | ||||||
| gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= |  | ||||||
| gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= |  | ||||||
| gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | ||||||
| gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||||||
|   | |||||||
							
								
								
									
										26
									
								
								tools/pipeline/internal/cmd/github.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								tools/pipeline/internal/cmd/github.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | |||||||
|  | // Copyright (c) HashiCorp, Inc. | ||||||
|  | // SPDX-License-Identifier: BUSL-1.1 | ||||||
|  |  | ||||||
|  | package cmd | ||||||
|  |  | ||||||
|  | import "github.com/spf13/cobra" | ||||||
|  |  | ||||||
|  | type githubCommandFlags struct { | ||||||
|  | 	Format string `json:"format,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var githubCmdFlags = &githubCommandFlags{} | ||||||
|  |  | ||||||
|  | func newGithubCmd() *cobra.Command { | ||||||
|  | 	github := &cobra.Command{ | ||||||
|  | 		Use:   "github", | ||||||
|  | 		Short: "Github commands", | ||||||
|  | 		Long:  "Github commands", | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	github.PersistentFlags().StringVarP(&githubCmdFlags.Format, "format", "f", "table", "The output format. Can be 'json' or 'table'") | ||||||
|  |  | ||||||
|  | 	github.AddCommand(newGithubListRunCmd()) | ||||||
|  |  | ||||||
|  | 	return github | ||||||
|  | } | ||||||
							
								
								
									
										89
									
								
								tools/pipeline/internal/cmd/github_list_runs.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								tools/pipeline/internal/cmd/github_list_runs.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,89 @@ | |||||||
|  | // Copyright (c) HashiCorp, Inc. | ||||||
|  | // SPDX-License-Identifier: BUSL-1.1 | ||||||
|  |  | ||||||
|  | package cmd | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | 	"os" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	ghclient "github.com/google/go-github/v68/github" | ||||||
|  | 	"github.com/spf13/cobra" | ||||||
|  |  | ||||||
|  | 	"github.com/hashicorp/vault/tools/pipeline/internal/pkg/github" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | var listGithubWorkflowRuns = &github.ListWorkflowRunsReq{} | ||||||
|  |  | ||||||
|  | func newGithubListRunCmd() *cobra.Command { | ||||||
|  | 	listRuns := &cobra.Command{ | ||||||
|  | 		Use:   "list-workflow-runs [WORKFLOW_NAME]", | ||||||
|  | 		Short: "List workflow runs", | ||||||
|  | 		Long:  "List Github Actions workflow runs for a given workflow. Be sure to use filter arguments to reduce the search, otherwise you'll likely hit your API limit.", | ||||||
|  | 		RunE:  runListGithubWorkflowsCmd, | ||||||
|  | 		Args: func(cmd *cobra.Command, args []string) error { | ||||||
|  | 			switch len(args) { | ||||||
|  | 			case 1: | ||||||
|  | 				listGithubWorkflowRuns.WorkflowName = args[0] | ||||||
|  | 				return nil | ||||||
|  | 			case 0: | ||||||
|  | 				return errors.New("no workflow name argument has been provided") | ||||||
|  | 			default: | ||||||
|  | 				return fmt.Errorf("expected a single workflow name as an argument, received (%d): %v", len(args), args) | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	listRuns.PersistentFlags().StringVarP(&listGithubWorkflowRuns.Actor, "actor", "a", "", "Filter using a specific Github actor") | ||||||
|  | 	listRuns.PersistentFlags().StringVarP(&listGithubWorkflowRuns.Branch, "branch", "b", "", "Filter using a specific Github branch") | ||||||
|  | 	listRuns.PersistentFlags().Int64VarP(&listGithubWorkflowRuns.CheckSuiteID, "check-suite-id", "c", 0, "Filter using a specific Github check suite") | ||||||
|  | 	listRuns.PersistentFlags().BoolVar(&listGithubWorkflowRuns.Compact, "compact", true, "When given a status filter, only fetch data for workflows, jobs, checks, and annotations that match our status and/or conclusion. Disabling compact mode with a large query range might result in Github throttling the requests.") | ||||||
|  | 	listRuns.PersistentFlags().StringVarP(&listGithubWorkflowRuns.DateQuery, "date-query", "d", fmt.Sprintf("%s..*", time.Now().Add(-168*time.Hour).Format(time.DateOnly)), "Filter using a date range query. It supports the Github ISO8601-ish date range query format. Default is newer than one week ago") | ||||||
|  | 	listRuns.PersistentFlags().StringVarP(&listGithubWorkflowRuns.Event, "event", "e", "", "Filter using a workflow triggered by an event type. E.g. push, pull_request, issue") | ||||||
|  | 	listRuns.PersistentFlags().BoolVarP(&listGithubWorkflowRuns.IncludePRs, "include-prs", "p", false, "Include workflow runs triggered via pull requests") | ||||||
|  | 	listRuns.PersistentFlags().StringVarP(&listGithubWorkflowRuns.Owner, "owner", "o", "hashicorp", "The Github organization") | ||||||
|  | 	listRuns.PersistentFlags().StringVarP(&listGithubWorkflowRuns.Repo, "repo", "r", "vault", "The Github repository. Private repositories require auth via a GITHUB_TOKEN env var") | ||||||
|  | 	listRuns.PersistentFlags().StringVar(&listGithubWorkflowRuns.Sha, "sha", "", "Filter based on the HEAD SHA associated with the workflow run") | ||||||
|  | 	listRuns.PersistentFlags().StringVar(&listGithubWorkflowRuns.Status, "status", "", "Filter by a given run status. For example: completed, cancelled, failure, skipped, success, in_progress") | ||||||
|  |  | ||||||
|  | 	return listRuns | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func runListGithubWorkflowsCmd(cmd *cobra.Command, args []string) error { | ||||||
|  | 	cmd.SilenceUsage = true // Don't spam the usage on failure | ||||||
|  |  | ||||||
|  | 	client := ghclient.NewClient(nil) | ||||||
|  | 	if token, set := os.LookupEnv("GITHUB_TOKEN"); set { | ||||||
|  | 		client = client.WithAuthToken(token) | ||||||
|  | 	} else { | ||||||
|  | 		fmt.Println("\x1b[1;33;49mWARNING\x1b[0m: GITHUB_TOKEN has not been set. While not required for public repositories you're likely to get throttled without it") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	res, err := listGithubWorkflowRuns.Run(context.TODO(), client) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("listing github workflow failures: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	switch githubCmdFlags.Format { | ||||||
|  | 	case "json": | ||||||
|  | 		b, err := json.Marshal(res) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return fmt.Errorf("marshaling response to JSON: %w", err) | ||||||
|  | 		} | ||||||
|  | 		fmt.Println(string(b)) | ||||||
|  | 	default: | ||||||
|  | 		for _, run := range res.Runs { | ||||||
|  | 			summary, err := run.Summary() | ||||||
|  | 			if err != nil { | ||||||
|  | 				return fmt.Errorf("generating workflow run response summary: %w", err) | ||||||
|  | 			} | ||||||
|  | 			fmt.Println(summary) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return err | ||||||
|  | } | ||||||
| @@ -28,6 +28,7 @@ func newRootCmd() *cobra.Command { | |||||||
| 	rootCmd.PersistentFlags().StringVar(&rootCfg.logLevel, "log", "warn", "Set the log level. One of 'debug', 'info', 'warn', 'error'") | 	rootCmd.PersistentFlags().StringVar(&rootCfg.logLevel, "log", "warn", "Set the log level. One of 'debug', 'info', 'warn', 'error'") | ||||||
|  |  | ||||||
| 	rootCmd.AddCommand(newGenerateCmd()) | 	rootCmd.AddCommand(newGenerateCmd()) | ||||||
|  | 	rootCmd.AddCommand(newGithubCmd()) | ||||||
| 	rootCmd.AddCommand(newReleasesCmd()) | 	rootCmd.AddCommand(newReleasesCmd()) | ||||||
|  |  | ||||||
| 	rootCmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error { | 	rootCmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error { | ||||||
|   | |||||||
| @@ -10,8 +10,9 @@ import ( | |||||||
| 	"slices" | 	"slices" | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"github.com/hashicorp/vault/tools/pipeline/internal/pkg/releases" |  | ||||||
| 	"github.com/stretchr/testify/require" | 	"github.com/stretchr/testify/require" | ||||||
|  |  | ||||||
|  | 	"github.com/hashicorp/vault/tools/pipeline/internal/pkg/releases" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var testAPIVersions = []string{ | var testAPIVersions = []string{ | ||||||
| @@ -230,7 +231,7 @@ func Test_EnosDynamicConfigReq_Run(t *testing.T) { | |||||||
| 							AWSRegion:             []string{"us-east-1", "us-west-2"}, | 							AWSRegion:             []string{"us-east-1", "us-west-2"}, | ||||||
| 							DistroVersionAmzn:     []string{"2023"}, | 							DistroVersionAmzn:     []string{"2023"}, | ||||||
| 							DistroVersionLeap:     []string{"15.6"}, | 							DistroVersionLeap:     []string{"15.6"}, | ||||||
| 							DistroVersionRhel:     []string{"8.10, 9.4"}, | 							DistroVersionRhel:     []string{"8.10", "9.4"}, | ||||||
| 							DistroVersionSles:     []string{"15.6"}, | 							DistroVersionSles:     []string{"15.6"}, | ||||||
| 							DistroVersionUbuntu:   []string{"20.04", "24.04"}, | 							DistroVersionUbuntu:   []string{"20.04", "24.04"}, | ||||||
| 							UpgradeInitialVersion: versions, | 							UpgradeInitialVersion: versions, | ||||||
| @@ -238,13 +239,21 @@ func Test_EnosDynamicConfigReq_Run(t *testing.T) { | |||||||
| 					}, | 					}, | ||||||
| 				} | 				} | ||||||
| 			}, | 			}, | ||||||
| 			hcl: []byte(` | 			hcl: []byte(`# Copyright (c) HashiCorp, Inc. | ||||||
|  | # SPDX-License-Identifier: BUSL-1.1 | ||||||
|  |  | ||||||
|  | # Code generated by pipeline generate enos-dynamic-config DO NOT EDIT. | ||||||
|  |  | ||||||
|  | # This file is overwritten in CI as it contains branch specific and sometimes ever-changing values. | ||||||
|  | # It's checked in here so that enos samples and scenarios can be performed, just be aware that this | ||||||
|  | # might change out from under you. | ||||||
|  |  | ||||||
| globals { | globals { | ||||||
|   sample_attributes = { |   sample_attributes = { | ||||||
|     aws_region              = ["us-east-1", "us-west-2"] |     aws_region              = ["us-east-1", "us-west-2"] | ||||||
|     distro_version_amzn     = ["2023"] |     distro_version_amzn     = ["2023"] | ||||||
|     distro_version_leap     = ["15.6"] |     distro_version_leap     = ["15.6"] | ||||||
|     distro_version_rhel     = ["8.10, 9.4"] |     distro_version_rhel     = ["8.10", "9.4"] | ||||||
|     distro_version_sles     = ["15.6"] |     distro_version_sles     = ["15.6"] | ||||||
|     distro_version_ubuntu   = ["20.04", "24.04"] |     distro_version_ubuntu   = ["20.04", "24.04"] | ||||||
|     upgrade_initial_version = ["1.16.6", "1.16.7", "1.16.8", "1.16.9", "1.16.10", "1.17.3", "1.17.4", "1.17.6", "1.18.0-rc1"] |     upgrade_initial_version = ["1.16.6", "1.16.7", "1.16.8", "1.16.9", "1.16.10", "1.17.3", "1.17.4", "1.17.6", "1.18.0-rc1"] | ||||||
|   | |||||||
							
								
								
									
										541
									
								
								tools/pipeline/internal/pkg/github/list_workflow_runs.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										541
									
								
								tools/pipeline/internal/pkg/github/list_workflow_runs.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,541 @@ | |||||||
|  | // Copyright (c) HashiCorp, Inc. | ||||||
|  | // SPDX-License-Identifier: BUSL-1.1 | ||||||
|  |  | ||||||
|  | package github | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | 	"net/http" | ||||||
|  | 	"sync" | ||||||
|  |  | ||||||
|  | 	gh "github.com/google/go-github/v68/github" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // PerPageMax is the maximum number of entities to request for enpoints that | ||||||
|  | // support pagination. 100 is usually the limit and we use it everywhere. | ||||||
|  | // We always request the maximum number of entities so that we we use the fewest | ||||||
|  | // possible API requests. There is a per-hour token limit after all. | ||||||
|  | const PerPageMax = 100 | ||||||
|  |  | ||||||
|  | // ListWorkflowRunsReq is a request to list workflows runs. The fields represent | ||||||
|  | // various criteria we can use to filter. | ||||||
|  | type ListWorkflowRunsReq struct { | ||||||
|  | 	Actor        string | ||||||
|  | 	Branch       string | ||||||
|  | 	CheckSuiteID int64 | ||||||
|  | 	Compact      bool | ||||||
|  | 	DateQuery    string | ||||||
|  | 	Event        string | ||||||
|  | 	IncludePRs   bool | ||||||
|  | 	Owner        string | ||||||
|  | 	Repo         string | ||||||
|  | 	Sha          string | ||||||
|  | 	Status       string | ||||||
|  | 	WorkflowName string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ListWorkflowRunsRes is a list workflows response. | ||||||
|  | type ListWorkflowRunsRes struct { | ||||||
|  | 	Workflow *gh.Workflow   `json:"workflow,omitempty"` | ||||||
|  | 	Runs     []*WorkflowRun `json:"runs,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // WorkflowRun represents a Github actions workflow run. We include the raw | ||||||
|  | // Github API run response, the workflows jobs, and the associated check suite. | ||||||
|  | type WorkflowRun struct { | ||||||
|  | 	Run       *gh.WorkflowRun `json:"run,omitempty"` | ||||||
|  | 	Jobs      []*WorkflowJob  `json:"jobs,omitempty"` | ||||||
|  | 	CheckRuns []*CheckRun     `json:"check_runs,omitempty"` | ||||||
|  | 	summary   string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // CheckRun represents the run of a check suite for a workflow. We include the | ||||||
|  | // check suite annotations. | ||||||
|  | type CheckRun struct { | ||||||
|  | 	Run         *gh.CheckRun             `json:"run,omitempty"` | ||||||
|  | 	Annotations []*gh.CheckRunAnnotation `json:"annotations,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // WorkflowJob represents a singular job of a workflow. We include the raw | ||||||
|  | // Job response from Github along with log entries for any failed steps. | ||||||
|  | type WorkflowJob struct { | ||||||
|  | 	Job        *gh.WorkflowJob `json:"job,omitempty"` | ||||||
|  | 	LogEntries []*LogEntry     `json:"log_entries,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Run runs the request to gather all instances of the workflow that match | ||||||
|  | // our filter criteria. | ||||||
|  | func (r *ListWorkflowRunsReq) Run(ctx context.Context, client *gh.Client) (*ListWorkflowRunsRes, error) { | ||||||
|  | 	var err error | ||||||
|  | 	res := &ListWorkflowRunsRes{} | ||||||
|  |  | ||||||
|  | 	if err = r.validate(); err != nil { | ||||||
|  | 		return nil, fmt.Errorf("validating request: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	res.Workflow, err = r.getWorkflow(ctx, client) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, fmt.Errorf("getting workflow: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	res.Runs, err = r.getWorkflowRuns(ctx, client, res.Workflow.GetID()) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, fmt.Errorf("getting workflow runs: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if len(res.Runs) < 1 { | ||||||
|  | 		return nil, fmt.Errorf("fetching workflow runs: no workflow runs match the given filters") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	err = r.getWorkflowCheckRuns(ctx, client, res.Runs) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, fmt.Errorf("fetching workflow check runs: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	err = r.getWorkflowCheckRunAnnotations(ctx, client, res.Runs) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, fmt.Errorf("fetching workflow check run annotations: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	err = r.getWorkflowJobs(ctx, client, res.Runs) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, fmt.Errorf("fetching workflow run jobs: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Logs have to be downloaded and parsed to get the relevant bits. Such | ||||||
|  | 	// an expensive operation is limited to only failures. | ||||||
|  | 	err = r.getUnsuccessfulWorkflowJobsLogs(ctx, client, res.Runs) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, fmt.Errorf("fetching failed workflow run job logs: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return res, r.summarizeWorkflowRuns(res.Runs) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // validate ensures that we've been given the minimum filter arguments necessary to complete a | ||||||
|  | // request. It is always recommended that additional fitlers be given to reduce the response size | ||||||
|  | // and not exhaust API limits. | ||||||
|  | func (r *ListWorkflowRunsReq) validate() error { | ||||||
|  | 	if r == nil { | ||||||
|  | 		return errors.New("failed to initialize request") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if r.Owner == "" { | ||||||
|  | 		return errors.New("no github organization has been provided") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if r.Repo == "" { | ||||||
|  | 		return errors.New("no github repository has been provided") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if r.DateQuery == "" { | ||||||
|  | 		return errors.New("no date range query has been provided") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if r.WorkflowName == "" { | ||||||
|  | 		return errors.New("no github actions workflow name has been provided") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // getWorkflow attempts to locate the workflow associated with our workflow name. | ||||||
|  | func (r *ListWorkflowRunsReq) getWorkflow(ctx context.Context, client *gh.Client) (*gh.Workflow, error) { | ||||||
|  | 	opts := &gh.ListOptions{PerPage: PerPageMax} | ||||||
|  | 	for { | ||||||
|  | 		wfs, res, err := client.Actions.ListWorkflows(ctx, r.Owner, r.Repo, opts) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		for _, wf := range wfs.Workflows { | ||||||
|  | 			if wf.GetName() == r.WorkflowName { | ||||||
|  | 				return wf, nil | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if res.NextPage == 0 { | ||||||
|  | 			return nil, fmt.Errorf("no workflow matching %s could be found", r.WorkflowName) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		opts.Page = res.NextPage | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // getWorkflowRuns gets teh workflow runs associated with a workflow ID. | ||||||
|  | func (r *ListWorkflowRunsReq) getWorkflowRuns(ctx context.Context, client *gh.Client, id int64) ([]*WorkflowRun, error) { | ||||||
|  | 	var runs []*WorkflowRun | ||||||
|  | 	opts := &gh.ListWorkflowRunsOptions{ | ||||||
|  | 		Actor:               r.Actor, | ||||||
|  | 		Branch:              r.Branch, | ||||||
|  | 		CheckSuiteID:        r.CheckSuiteID, | ||||||
|  | 		Created:             r.DateQuery, | ||||||
|  | 		ExcludePullRequests: !r.IncludePRs, | ||||||
|  | 		Event:               r.Event, | ||||||
|  | 		HeadSHA:             r.Sha, | ||||||
|  | 		ListOptions:         gh.ListOptions{PerPage: PerPageMax}, | ||||||
|  | 		Status:              r.Status, | ||||||
|  | 	} | ||||||
|  | 	if r.CheckSuiteID > 0 { | ||||||
|  | 		opts.CheckSuiteID = r.CheckSuiteID | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for { | ||||||
|  | 		wfrs, res, err := client.Actions.ListWorkflowRunsByID(ctx, r.Owner, r.Repo, id, opts) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		for _, r := range wfrs.WorkflowRuns { | ||||||
|  | 			runs = append(runs, &WorkflowRun{Run: r}) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if res.NextPage == 0 { | ||||||
|  | 			return runs, nil | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		opts.ListOptions.Page = res.NextPage | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // getWorkflowCheckRuns gets the check suite runs associated with the workflow runs. | ||||||
|  | func (r *ListWorkflowRunsReq) getWorkflowCheckRuns(ctx context.Context, client *gh.Client, wfrs []*WorkflowRun) error { | ||||||
|  | 	filter := "latest" | ||||||
|  | 	opts := &gh.ListCheckRunsOptions{ | ||||||
|  | 		Filter:      &filter, // "all" for all attemps | ||||||
|  | 		ListOptions: gh.ListOptions{PerPage: PerPageMax}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if len(wfrs) < 1 { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	wg := sync.WaitGroup{} | ||||||
|  | 	wg.Add(len(wfrs)) | ||||||
|  | 	errC, resC, cancel := r.startErrorCollector(ctx) | ||||||
|  |  | ||||||
|  | 	for _, wfr := range wfrs { | ||||||
|  | 		go func() { | ||||||
|  | 			defer wg.Done() | ||||||
|  |  | ||||||
|  | 			for { | ||||||
|  | 				chrs, res, err := client.Checks.ListCheckRunsCheckSuite(ctx, r.Owner, r.Repo, *wfr.Run.CheckSuiteID, opts) | ||||||
|  | 				if err != nil { | ||||||
|  | 					errC <- err | ||||||
|  | 					return | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				if r.Status == "" || !r.Compact { | ||||||
|  | 					for _, cr := range chrs.CheckRuns { | ||||||
|  | 						wfr.CheckRuns = append(wfr.CheckRuns, &CheckRun{Run: cr}) | ||||||
|  | 					} | ||||||
|  | 				} else { | ||||||
|  | 					for _, cr := range chrs.CheckRuns { | ||||||
|  | 						if cr.GetConclusion() == r.Status || cr.GetStatus() == r.Status { | ||||||
|  | 							wfr.CheckRuns = append(wfr.CheckRuns, &CheckRun{Run: cr}) | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				if res.NextPage == 0 { | ||||||
|  | 					break | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				opts.ListOptions.Page = res.NextPage | ||||||
|  | 			} | ||||||
|  | 		}() | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	wg.Wait() | ||||||
|  |  | ||||||
|  | 	return r.stopErrorCollector(errC, resC, cancel) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // getWorkflowCheckRunAnnotations gets the check suite annotations associated with the check suites | ||||||
|  | // that are associated with the runs. | ||||||
|  | func (r *ListWorkflowRunsReq) getWorkflowCheckRunAnnotations(ctx context.Context, client *gh.Client, wfrs []*WorkflowRun) error { | ||||||
|  | 	opts := &gh.ListOptions{PerPage: PerPageMax} | ||||||
|  |  | ||||||
|  | 	if len(wfrs) < 1 { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	wg := sync.WaitGroup{} | ||||||
|  | 	wg.Add(len(wfrs)) | ||||||
|  | 	errC, resC, cancel := r.startErrorCollector(ctx) | ||||||
|  |  | ||||||
|  | 	for _, wfr := range wfrs { | ||||||
|  | 		go func() { | ||||||
|  | 			defer wg.Done() | ||||||
|  |  | ||||||
|  | 			for _, cr := range wfr.CheckRuns { | ||||||
|  | 				for { | ||||||
|  | 					ans, res, err := client.Checks.ListCheckRunAnnotations(ctx, r.Owner, r.Repo, cr.Run.GetID(), opts) | ||||||
|  | 					if err != nil { | ||||||
|  | 						errC <- err | ||||||
|  | 						return | ||||||
|  | 					} | ||||||
|  | 					cr.Annotations = append(cr.Annotations, ans...) | ||||||
|  |  | ||||||
|  | 					if res.NextPage == 0 { | ||||||
|  | 						break | ||||||
|  | 					} | ||||||
|  |  | ||||||
|  | 					opts.Page = res.NextPage | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		}() | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	wg.Wait() | ||||||
|  |  | ||||||
|  | 	return r.stopErrorCollector(errC, resC, cancel) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // getWorkflowJobs gets the jobs associated with the workflow runs. | ||||||
|  | func (r *ListWorkflowRunsReq) getWorkflowJobs(ctx context.Context, client *gh.Client, wfrs []*WorkflowRun) error { | ||||||
|  | 	opts := &gh.ListWorkflowJobsOptions{ | ||||||
|  | 		Filter:      "latest", // "all" to include all attempts | ||||||
|  | 		ListOptions: gh.ListOptions{PerPage: PerPageMax}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if len(wfrs) < 1 { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	wg := sync.WaitGroup{} | ||||||
|  | 	wg.Add(len(wfrs)) | ||||||
|  | 	errC, resC, cancel := r.startErrorCollector(ctx) | ||||||
|  |  | ||||||
|  | 	for _, run := range wfrs { | ||||||
|  | 		go func() { | ||||||
|  | 			defer wg.Done() | ||||||
|  |  | ||||||
|  | 			for { | ||||||
|  | 				jobs, res, err := client.Actions.ListWorkflowJobs(ctx, r.Owner, r.Repo, *run.Run.ID, opts) | ||||||
|  | 				if err != nil { | ||||||
|  | 					errC <- err | ||||||
|  | 					return | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				if r.Status == "" || !r.Compact { | ||||||
|  | 					for _, job := range jobs.Jobs { | ||||||
|  | 						run.Jobs = append(run.Jobs, &WorkflowJob{Job: job}) | ||||||
|  | 					} | ||||||
|  | 				} else { | ||||||
|  | 					for _, job := range jobs.Jobs { | ||||||
|  | 						if job.GetConclusion() == r.Status || job.GetStatus() == r.Status { | ||||||
|  | 							run.Jobs = append(run.Jobs, &WorkflowJob{Job: job}) | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				if res.NextPage == 0 { | ||||||
|  | 					break | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				opts.ListOptions.Page = res.NextPage | ||||||
|  | 			} | ||||||
|  | 		}() | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	wg.Wait() | ||||||
|  |  | ||||||
|  | 	return r.stopErrorCollector(errC, resC, cancel) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // getUnsuccessfulWorkflowJobsLogs downloads the job log and parses out the | ||||||
|  | // out failed entries for any unsuccesful jobs. | ||||||
|  | func (r *ListWorkflowRunsReq) getUnsuccessfulWorkflowJobsLogs(ctx context.Context, client *gh.Client, wfrs []*WorkflowRun) error { | ||||||
|  | 	if len(wfrs) < 1 { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	wg := sync.WaitGroup{} | ||||||
|  | 	wg.Add(len(wfrs)) | ||||||
|  | 	errC, resC, cancel := r.startErrorCollector(ctx) | ||||||
|  |  | ||||||
|  | 	for _, run := range wfrs { | ||||||
|  | 		go func() { | ||||||
|  | 			defer wg.Done() | ||||||
|  |  | ||||||
|  | 			for _, job := range run.Jobs { | ||||||
|  | 				if job.Job == nil { | ||||||
|  | 					continue | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				if job.Job.GetStatus() == "completed" && job.Job.GetConclusion() == "successful" { | ||||||
|  | 					continue | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				url, _, err := client.Actions.GetWorkflowJobLogs(ctx, r.Owner, r.Repo, *job.Job.ID, 3) // last is max redirects | ||||||
|  | 				if err != nil { | ||||||
|  | 					errC <- err | ||||||
|  | 					return | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				req, err := http.NewRequestWithContext(ctx, http.MethodGet, url.String(), nil) | ||||||
|  | 				if err != nil { | ||||||
|  | 					errC <- err | ||||||
|  | 					return | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				res, err := http.DefaultClient.Do(req) | ||||||
|  | 				if err != nil { | ||||||
|  | 					errC <- err | ||||||
|  | 					return | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				defer res.Body.Close() | ||||||
|  | 				scanner := NewLogScaner( | ||||||
|  | 					WithLogScannerTruncate(), // truncate the body | ||||||
|  | 					// Our max size here is a magic number but seems to be a nice sweet | ||||||
|  | 					// spot where most of failure diagnostic will show up but you don't | ||||||
|  | 					// get walls of text. | ||||||
|  | 					WithLogScannerMaxSize(4000), | ||||||
|  | 					WithLogScannerOnlyUnsuccessful(), // only keep unsuccessful step logs | ||||||
|  | 				) | ||||||
|  |  | ||||||
|  | 				job.LogEntries, err = scanner.Scan(res.Body) | ||||||
|  | 				if err != nil { | ||||||
|  | 					errC <- err | ||||||
|  | 					return | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		}() | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	wg.Wait() | ||||||
|  |  | ||||||
|  | 	return r.stopErrorCollector(errC, resC, cancel) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // startErrorCollector starts a helper go routine that listens and aggregrates | ||||||
|  | // errors passed to it via it's returned channel. When the caller has completed | ||||||
|  | // all work that may write errors it can call the stopErrorCollector() method | ||||||
|  | // with the channel and cancel func to safely shut down the go routine and | ||||||
|  | // receive the aggregrate error. | ||||||
|  | func (r *ListWorkflowRunsReq) startErrorCollector(ctx context.Context) (chan error, chan error, context.CancelFunc) { | ||||||
|  | 	errC := make(chan error) | ||||||
|  | 	resC := make(chan error) | ||||||
|  | 	errCtx, cancelCollector := context.WithCancel(ctx) | ||||||
|  |  | ||||||
|  | 	go func() { | ||||||
|  | 		var err error | ||||||
|  |  | ||||||
|  | 	LOOP: | ||||||
|  | 		for { | ||||||
|  | 			select { | ||||||
|  | 			case err1 := <-errC: | ||||||
|  | 				err = errors.Join(err, err1) | ||||||
|  | 				continue | ||||||
|  | 			default: | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			select { | ||||||
|  | 			case err1 := <-errC: | ||||||
|  | 				err = errors.Join(err, err1) | ||||||
|  | 				continue | ||||||
|  | 			case <-errCtx.Done(): | ||||||
|  | 				// Don't bubble up the close signal, only bubble up outer context errors | ||||||
|  | 				if err1 := ctx.Err(); err1 != nil { | ||||||
|  | 					err = errors.Join(err, err1) | ||||||
|  | 				} | ||||||
|  | 				break LOOP | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 	DRAIN: | ||||||
|  | 		for { | ||||||
|  | 			select { | ||||||
|  | 			case err1 := <-errC: | ||||||
|  | 				err = errors.Join(err, err1) | ||||||
|  | 				continue | ||||||
|  | 			default: | ||||||
|  | 				break DRAIN | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		resC <- err | ||||||
|  | 	}() | ||||||
|  |  | ||||||
|  | 	return errC, resC, cancelCollector | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // stopErrorCollector stops the error collector and returns the aggregrated error. The given channel | ||||||
|  | // is closed. The caller must be sure that all work has concluded prior to calling. | ||||||
|  | func (r *ListWorkflowRunsReq) stopErrorCollector(errC chan error, resC chan error, cancel context.CancelFunc) error { | ||||||
|  | 	cancel() | ||||||
|  | 	err := <-resC | ||||||
|  | 	close(errC) | ||||||
|  | 	close(resC) | ||||||
|  |  | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // summarizeWorkflowRuns creates a human readable summary for all workflow runs. | ||||||
|  | func (r *ListWorkflowRunsReq) summarizeWorkflowRuns(wrfs []*WorkflowRun) error { | ||||||
|  | 	if len(wrfs) < 1 { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var err error | ||||||
|  | 	for _, run := range wrfs { | ||||||
|  | 		_, err = run.Summary() | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Summary returns the human readable summary of the workflow run. | ||||||
|  | func (r *WorkflowRun) Summary() (string, error) { | ||||||
|  | 	if r == nil { | ||||||
|  | 		return "", errors.New("uninitialized workflow run") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if r.summary != "" { | ||||||
|  | 		return r.summary, nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var err error | ||||||
|  | 	r.summary, err = summarizeWorkflowRun(r) | ||||||
|  | 	return r.summary, err | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // UnsuccessfulSteps returns any unsuccesful steps in the workflow job. | ||||||
|  | func (j *WorkflowJob) UnsuccessfulSteps() []*gh.TaskStep { | ||||||
|  | 	if j == nil || j.Job == nil || len(j.Job.Steps) < 1 { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	res := []*gh.TaskStep{} | ||||||
|  | 	for _, step := range j.Job.Steps { | ||||||
|  | 		if step.GetStatus() == "completed" && (step.GetConclusion() == "success" || step.GetConclusion() == "skipped") { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		res = append(res, step) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return res | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // UnsuccessfulSteps returns the names of any unsuccesful steps in the workflow job. | ||||||
|  | func (j *WorkflowJob) UnsuccessfulStepNames() []string { | ||||||
|  | 	steps := j.UnsuccessfulSteps() | ||||||
|  | 	if len(steps) < 1 { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	res := []string{} | ||||||
|  | 	for _, step := range steps { | ||||||
|  | 		res = append(res, step.GetName()) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return res | ||||||
|  | } | ||||||
| @@ -0,0 +1,35 @@ | |||||||
|  | // Copyright (c) HashiCorp, Inc. | ||||||
|  | // SPDX-License-Identifier: BUSL-1.1 | ||||||
|  |  | ||||||
|  | package github | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"io" | ||||||
|  | 	"os" | ||||||
|  | 	"path/filepath" | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"github.com/stretchr/testify/require" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // TestWorkflowRunSummaryTemplate verifies that we can correctly render a | ||||||
|  | // human readable summary from our response test fixture. We don't do strict | ||||||
|  | // value checking on rendered template. If you modify the template and/or | ||||||
|  | // response struct you'll probably need to update the test fixture. | ||||||
|  | func TestWorkflowRunSummaryTemplate(t *testing.T) { | ||||||
|  | 	f, err := os.Open(filepath.Join("./testfixtures/list_workflow_runs.json")) | ||||||
|  | 	require.NoError(t, err) | ||||||
|  | 	bytes, err := io.ReadAll(f) | ||||||
|  | 	require.NoError(t, err) | ||||||
|  | 	res := &ListWorkflowRunsRes{} | ||||||
|  | 	require.NoError(t, json.Unmarshal(bytes, res)) | ||||||
|  | 	for _, run := range res.Runs { | ||||||
|  | 		require.NotNil(t, run) | ||||||
|  | 		run.summary = "" | ||||||
|  | 		summary, err := run.Summary() | ||||||
|  | 		require.NoError(t, err) | ||||||
|  | 		require.NotEmpty(t, summary) | ||||||
|  | 		// t.Log(summary) // useful to see rendered output when modifying | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										253
									
								
								tools/pipeline/internal/pkg/github/log_scanner.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										253
									
								
								tools/pipeline/internal/pkg/github/log_scanner.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,253 @@ | |||||||
|  | // Copyright (c) HashiCorp, Inc. | ||||||
|  | // SPDX-License-Identifier: BUSL-1.1 | ||||||
|  |  | ||||||
|  | package github | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"bufio" | ||||||
|  | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | 	"io" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // LogScanner it a Github Actions workflow job log scanner and decoder. | ||||||
|  | type LogScanner struct { | ||||||
|  | 	Truncate         bool | ||||||
|  | 	MaxSizeBytes     int | ||||||
|  | 	OnlySteps        []string | ||||||
|  | 	OnlyUnsuccessful bool | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // LogScanOpts are scanner options. | ||||||
|  | type LogScanOpts func(*LogScanner) | ||||||
|  |  | ||||||
|  | // LogEntry is a job step log entry. | ||||||
|  | type LogEntry struct { | ||||||
|  | 	StepName string `json:"step_name,omitempty"` | ||||||
|  | 	SetupLog []byte `json:"setup_log,omitempty"` | ||||||
|  | 	BodyLog  []byte `json:"body_log,omitempty"` | ||||||
|  | 	ErrorLog []byte `json:"error_log,omitempty"` | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // logSection is a section token in the job log. | ||||||
|  | type logSection int | ||||||
|  |  | ||||||
|  | const ( | ||||||
|  | 	labelGroup    = "##[group]" | ||||||
|  | 	labelEndGroup = "##[endgroup]" | ||||||
|  | 	labelError    = "##[error]" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | const ( | ||||||
|  | 	logSectionNone logSection = iota | ||||||
|  | 	logSectionSetup | ||||||
|  | 	logSectionBody | ||||||
|  | 	logSectionError | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // NewLogScaner takes none-or-many LogScanOpts and returns a new instance of LogScanner. | ||||||
|  | func NewLogScaner(opts ...LogScanOpts) *LogScanner { | ||||||
|  | 	scanner := &LogScanner{ | ||||||
|  | 		Truncate:         false, | ||||||
|  | 		OnlySteps:        []string{}, | ||||||
|  | 		OnlyUnsuccessful: false, | ||||||
|  | 		MaxSizeBytes:     (1 << 20), // 1MiB | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, opt := range opts { | ||||||
|  | 		opt(scanner) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return scanner | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // WithLogScannerTruncate enables body log truncation. | ||||||
|  | func WithLogScannerTruncate() LogScanOpts { | ||||||
|  | 	return func(scanner *LogScanner) { | ||||||
|  | 		scanner.Truncate = true | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // WithLogScannerMaxSize configures the max body log size if truncation is enabled. | ||||||
|  | func WithLogScannerMaxSize(max int) LogScanOpts { | ||||||
|  | 	return func(scanner *LogScanner) { | ||||||
|  | 		scanner.MaxSizeBytes = max | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // WithLogScannerOnlySteps takes step names and only returns entries for matching steps. | ||||||
|  | func WithLogScannerOnlySteps(steps []string) LogScanOpts { | ||||||
|  | 	return func(scanner *LogScanner) { | ||||||
|  | 		if scanner.OnlySteps == nil { | ||||||
|  | 			scanner.OnlySteps = []string{} | ||||||
|  | 		} | ||||||
|  | 		for _, g := range steps { | ||||||
|  | 			scanner.OnlySteps = append(scanner.OnlySteps, strings.TrimSpace(g)) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // WithLogScannerOnlyUnsuccessful filters any successful log entries. | ||||||
|  | func WithLogScannerOnlyUnsuccessful() LogScanOpts { | ||||||
|  | 	return func(scanner *LogScanner) { | ||||||
|  | 		scanner.OnlyUnsuccessful = true | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Scan scans a Github Actions job raw log file and parses it into individual | ||||||
|  | // entries for each step that is run. | ||||||
|  | func (s *LogScanner) Scan(in io.Reader) ([]*LogEntry, error) { | ||||||
|  | 	if s == nil { | ||||||
|  | 		return nil, errors.New("uninitialized scanner") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	scanner := bufio.NewScanner(in) | ||||||
|  | 	logBuffer := newLogBuffer(s.Truncate, s.MaxSizeBytes) | ||||||
|  | 	res := []*LogEntry{} | ||||||
|  | 	logSec := logSectionNone | ||||||
|  |  | ||||||
|  | 	for scanner.Scan() { | ||||||
|  | 		// ##[group] | ||||||
|  | 		if strings.Contains(scanner.Text(), labelGroup) { | ||||||
|  | 			logSec = logSectionSetup | ||||||
|  | 			// Start parsing a new log group. | ||||||
|  |  | ||||||
|  | 			// Before we begin, persist our last log entry and reset our buffer for | ||||||
|  | 			// our new group. | ||||||
|  | 			if logBuffer.stepName != "" { | ||||||
|  | 				res = append(res, logBuffer.entry()) | ||||||
|  | 				logBuffer.reset() | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			parts := strings.SplitN(scanner.Text(), labelGroup, 2) | ||||||
|  | 			if len(parts) != 2 { | ||||||
|  | 				return nil, fmt.Errorf("malformed log group line expected %s followed by step name, got: %s", labelGroup, scanner.Text()) | ||||||
|  | 			} | ||||||
|  | 			logBuffer.stepName = strings.TrimSpace(parts[1]) | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// ##[error] | ||||||
|  | 		if strings.Contains(scanner.Text(), labelError) { | ||||||
|  | 			logSec = logSectionError | ||||||
|  | 			// The error label often preceeds the actual first line of the error log. | ||||||
|  | 			// Make sure we extract it and write it to the log. | ||||||
|  | 			parts := strings.SplitN(scanner.Text(), labelError, 2) | ||||||
|  | 			if len(parts) == 2 { | ||||||
|  | 				logBuffer.write(logSec, parts[0]+parts[1]+"\n") | ||||||
|  | 			} | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// ##[endgroup] | ||||||
|  | 		if strings.Contains(scanner.Text(), labelEndGroup) { | ||||||
|  | 			logSec = logSectionBody | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Write the line to the buffer | ||||||
|  | 		logBuffer.write(logSec, scanner.Text()+"\n") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err := scanner.Err(); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Write our last entry | ||||||
|  | 	res = append(res, logBuffer.entry()) | ||||||
|  |  | ||||||
|  | 	if len(s.OnlySteps) == 0 && !s.OnlyUnsuccessful { | ||||||
|  | 		return res, nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Filter our respons if necessary | ||||||
|  | 	var entries []*LogEntry | ||||||
|  | 	if s.OnlyUnsuccessful { | ||||||
|  | 		for _, entry := range res { | ||||||
|  | 			if len(entry.ErrorLog) > 0 { | ||||||
|  | 				entries = append(entries, entry) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		entries = res | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if len(s.OnlySteps) < 1 { | ||||||
|  | 		return entries, nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	var filtered []*LogEntry | ||||||
|  | 	for _, entry := range entries { | ||||||
|  | 		for _, os := range s.OnlySteps { | ||||||
|  | 			if strings.Contains(strings.TrimSpace(entry.StepName), os) { | ||||||
|  | 				filtered = append(filtered, entry) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return filtered, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // logBuffer is a buffer used by the scanner when parsing a raw job log. | ||||||
|  | type logBuffer struct { | ||||||
|  | 	truncate bool | ||||||
|  | 	maxSize  int | ||||||
|  | 	stepName string | ||||||
|  | 	setup    *strings.Builder | ||||||
|  | 	body     *strings.Builder | ||||||
|  | 	error    *strings.Builder | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *logBuffer) write(lc logSection, in string) { | ||||||
|  | 	switch lc { | ||||||
|  | 	case logSectionNone: | ||||||
|  | 	case logSectionSetup: | ||||||
|  | 		b.setup.WriteString(in) | ||||||
|  | 	case logSectionBody: | ||||||
|  | 		b.body.WriteString(in) | ||||||
|  | 	case logSectionError: | ||||||
|  | 		b.error.WriteString(in) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *logBuffer) entry() *LogEntry { | ||||||
|  | 	var groupBody string | ||||||
|  | 	if b.truncate && b.body.Len() > b.maxSize { | ||||||
|  | 		groupBody = b.body.String()[b.body.Len()-b.maxSize:] | ||||||
|  | 	} else { | ||||||
|  | 		groupBody = b.body.String() | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	e := &LogEntry{ | ||||||
|  | 		StepName: b.stepName, | ||||||
|  | 	} | ||||||
|  | 	if sl := []byte(strings.TrimSpace(b.setup.String())); len(sl) > 0 { | ||||||
|  | 		e.SetupLog = sl | ||||||
|  | 	} | ||||||
|  | 	if bl := []byte(strings.TrimSpace(groupBody)); len(bl) > 0 { | ||||||
|  | 		e.BodyLog = bl | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if el := []byte(strings.TrimSpace(b.error.String())); len(el) > 0 { | ||||||
|  | 		e.ErrorLog = el | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return e | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (b *logBuffer) reset() { | ||||||
|  | 	b.setup.Reset() | ||||||
|  | 	b.body.Reset() | ||||||
|  | 	b.error.Reset() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func newLogBuffer(truncate bool, maxSize int) *logBuffer { | ||||||
|  | 	return &logBuffer{ | ||||||
|  | 		truncate: truncate, | ||||||
|  | 		maxSize:  maxSize, | ||||||
|  | 		body:     &strings.Builder{}, | ||||||
|  | 		setup:    &strings.Builder{}, | ||||||
|  | 		error:    &strings.Builder{}, | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										397
									
								
								tools/pipeline/internal/pkg/github/log_scanner_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										397
									
								
								tools/pipeline/internal/pkg/github/log_scanner_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,397 @@ | |||||||
|  | // Copyright (c) HashiCorp, Inc. | ||||||
|  | // SPDX-License-Identifier: BUSL-1.1 | ||||||
|  |  | ||||||
|  | package github | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"os" | ||||||
|  | 	"path/filepath" | ||||||
|  | 	"testing" | ||||||
|  |  | ||||||
|  | 	"github.com/stretchr/testify/require" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestLogScannerScan_groups(t *testing.T) { | ||||||
|  | 	for name, test := range map[string]struct { | ||||||
|  | 		onlySteps []string | ||||||
|  | 		expected  []*LogEntry | ||||||
|  | 	}{ | ||||||
|  | 		"all": { | ||||||
|  | 			expected: []*LogEntry{ | ||||||
|  | 				{ | ||||||
|  | 					StepName: "Operating System", | ||||||
|  | 					SetupLog: []byte(`2024-12-10T00:12:39.4701675Z Ubuntu | ||||||
|  | 2024-12-10T00:12:39.4702661Z 24.04.1 | ||||||
|  | 2024-12-10T00:12:39.4784102Z LTS`), | ||||||
|  | 				}, | ||||||
|  |  | ||||||
|  | 				{ | ||||||
|  | 					StepName: "Runner Image", | ||||||
|  | 					SetupLog: []byte(`2024-12-10T00:12:39.4788307Z Image: ubuntu-24.04 | ||||||
|  | 2024-12-10T00:12:39.4789692Z Version: 20241201.1.0 | ||||||
|  | 2024-12-10T00:12:39.4792093Z Included Software: https://github.com/actions/runner-images/blob/ubuntu24/20241201.1/images/ubuntu/Ubuntu2404-Readme.md | ||||||
|  | 2024-12-10T00:12:39.4793925Z Image Release: https://github.com/actions/runner-images/releases/tag/ubuntu24%2F20241201.1`), | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					StepName: "Runner Image Provisioner", | ||||||
|  | 					SetupLog: []byte(`2024-12-10T00:12:39.4796788Z 2.0.385.1`), | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					StepName: "GITHUB_TOKEN Permissions", | ||||||
|  | 					SetupLog: []byte(`2024-12-10T00:12:39.4801616Z Contents: read | ||||||
|  | 2024-12-10T00:12:39.4802428Z Metadata: read`), | ||||||
|  | 					BodyLog: []byte(`2024-12-10T00:12:39.4806392Z Secret source: Actions | ||||||
|  | 2024-12-10T00:12:39.4807540Z Prepare workflow directory | ||||||
|  | 2024-12-10T00:12:39.5129473Z Prepare all required actions | ||||||
|  | 2024-12-10T00:12:39.5167065Z Getting action download info | ||||||
|  | 2024-12-10T00:12:39.9037777Z Download action repository 'actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683' (SHA:11bd71901bbe5b1630ceea73d27597364c9af683) | ||||||
|  | 2024-12-10T00:12:40.0290605Z Download action repository 'hashicorp/vault-action@d1720f055e0635fd932a1d2a48f87a666a57906c' (SHA:d1720f055e0635fd932a1d2a48f87a666a57906c) | ||||||
|  | 2024-12-10T00:12:40.3303861Z Download action repository 'hashicorp/setup-terraform@v3' (SHA:b9cd54a3c349d3f38e8881555d616ced269862dd) | ||||||
|  | 2024-12-10T00:12:40.6598264Z Download action repository 'aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502' (SHA:e3dd6a429d7300a6a4c196c26e071d42e0343502) | ||||||
|  | 2024-12-10T00:12:40.9874063Z Download action repository 'hashicorp/action-setup-enos@v1' (SHA:b9fa53484a1e8fdcc7b02a118bcf01d65b9414c9) | ||||||
|  | 2024-12-10T00:12:41.3078672Z Download action repository 'actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16' (SHA:fa0a91b85d4f404e444e00e005971372dc801d16) | ||||||
|  | 2024-12-10T00:12:41.6838043Z Download action repository 'actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882' (SHA:b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882) | ||||||
|  | 2024-12-10T00:12:41.7759930Z Download action repository 'hashicorp/actions-slack-status@v2.0.1' (SHA:1a3f63b30bd476aee1f3bd6f9d8f2aacc4f14d81) | ||||||
|  | 2024-12-10T00:12:42.1628380Z Getting action download info | ||||||
|  | 2024-12-10T00:12:42.3588968Z Download action repository 'actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea' (SHA:60a0d83039c74a4aee543508d2ffcb1c3799cdea) | ||||||
|  | 2024-12-10T00:12:42.6849336Z Download action repository 'slackapi/slack-github-action@70cd7be8e40a46e8b0eced40b0de447bdb42f68e' (SHA:70cd7be8e40a46e8b0eced40b0de447bdb42f68e) | ||||||
|  | 2024-12-10T00:12:43.0076240Z Getting action download info | ||||||
|  | 2024-12-10T00:12:43.1908900Z Getting action download info | ||||||
|  | 2024-12-10T00:12:43.4507571Z Getting action download info | ||||||
|  | 2024-12-10T00:12:43.6489981Z Uses: hashicorp/vault/.github/workflows/test-run-enos-scenario-matrix.yml@refs/heads/main (59489a88821ff0431b1ec1b22208a92ecccce183)`), | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					StepName: "Inputs", | ||||||
|  | 					SetupLog: []byte(`2024-12-10T00:12:43.6492960Z   build-artifact-name: vault_1.18.3-1_amd64.deb | ||||||
|  | 2024-12-10T00:12:43.6493938Z   sample-max: 2 | ||||||
|  | 2024-12-10T00:12:43.6494344Z   sample-name: release_ce_linux_amd64_deb | ||||||
|  | 2024-12-10T00:12:43.6494796Z   runs-on: "ubuntu-latest" | ||||||
|  | 2024-12-10T00:12:43.6495195Z   ssh-key-name: vault-ci-ssh-key | ||||||
|  | 2024-12-10T00:12:43.6495583Z   vault-edition: ce | ||||||
|  | 2024-12-10T00:12:43.6495989Z   vault-revision: 2767f8ee6214d03498b32d776173e1f336281bc5 | ||||||
|  | 2024-12-10T00:12:43.6496467Z   vault-version: 1.18.3`), | ||||||
|  | 					BodyLog: []byte(`2024-12-10T00:12:43.6498105Z Complete job name: Test vault_1.18.3-1_amd64.deb / run proxy arch:amd64 artifact_source:artifactory artifact_type:package backend:raft config_mode:file consul_edition:ent consul_version:1.14.11 distro:ubuntu edition:ce ip_version:4 seal:shamir`), | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					StepName: "Run actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683", | ||||||
|  | 					SetupLog: []byte(`2024-12-10T00:12:43.7201109Z with: | ||||||
|  | 2024-12-10T00:12:43.7201732Z   ref: 2767f8ee6214d03498b32d776173e1f336281bc5 | ||||||
|  | 2024-12-10T00:12:43.7202218Z   repository: hashicorp/vault | ||||||
|  | 2024-12-10T00:12:43.7202777Z   token: *** | ||||||
|  | 2024-12-10T00:12:43.7203128Z   ssh-strict: true | ||||||
|  | 2024-12-10T00:12:43.7203468Z   ssh-user: git | ||||||
|  | 2024-12-10T00:12:43.7203808Z   persist-credentials: true | ||||||
|  | 2024-12-10T00:12:43.7204183Z   clean: true | ||||||
|  | 2024-12-10T00:12:43.7204535Z   sparse-checkout-cone-mode: true | ||||||
|  | 2024-12-10T00:12:43.7204940Z   fetch-depth: 1 | ||||||
|  | 2024-12-10T00:12:43.7205270Z   fetch-tags: false | ||||||
|  | 2024-12-10T00:12:43.7205608Z   show-progress: true | ||||||
|  | 2024-12-10T00:12:43.7205950Z   lfs: false | ||||||
|  | 2024-12-10T00:12:43.7206267Z   submodules: false | ||||||
|  | 2024-12-10T00:12:43.7206604Z   set-safe-directory: true`), | ||||||
|  | 					BodyLog: []byte(`2024-12-10T00:12:43.9369921Z Syncing repository: hashicorp/vault`), | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					StepName: "Getting Git version info", | ||||||
|  | 					SetupLog: []byte(`2024-12-10T00:12:43.9372499Z Working directory is '/home/runner/work/vault/vault' | ||||||
|  | 2024-12-10T00:12:43.9373351Z [command]/usr/bin/git version | ||||||
|  | 2024-12-10T00:12:43.9487550Z git version 2.47.1`), | ||||||
|  | 					BodyLog: []byte(`2024-12-10T00:12:43.9533901Z Temporarily overriding HOME='/home/runner/work/_temp/115b40a3-18c7-458a-9579-a62a03b06fec' before making global git config changes | ||||||
|  | 2024-12-10T00:12:43.9535633Z Adding repository directory to the temporary git global config as a safe directory | ||||||
|  | 2024-12-10T00:12:43.9546389Z [command]/usr/bin/git config --global --add safe.directory /home/runner/work/vault/vault | ||||||
|  | 2024-12-10T00:12:43.9583806Z Deleting the contents of '/home/runner/work/vault/vault'`), | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					StepName: "Initializing the repository", | ||||||
|  | 					SetupLog: []byte(`2024-12-10T00:12:43.9592246Z [command]/usr/bin/git init /home/runner/work/vault/vault | ||||||
|  | 2024-12-10T00:12:43.9681668Z hint: Using 'master' as the name for the initial branch. This default branch name | ||||||
|  | 2024-12-10T00:12:43.9682921Z hint: is subject to change. To configure the initial branch name to use in all | ||||||
|  | 2024-12-10T00:12:43.9684045Z hint: of your new repositories, which will suppress this warning, call: | ||||||
|  | 2024-12-10T00:12:43.9684886Z hint: | ||||||
|  | 2024-12-10T00:12:43.9685567Z hint: 	git config --global init.defaultBranch <name> | ||||||
|  | 2024-12-10T00:12:43.9686287Z hint: | ||||||
|  | 2024-12-10T00:12:43.9686783Z hint: Names commonly chosen instead of 'master' are 'main', 'trunk' and | ||||||
|  | 2024-12-10T00:12:43.9687500Z hint: 'development'. The just-created branch can be renamed via this command: | ||||||
|  | 2024-12-10T00:12:43.9688022Z hint: | ||||||
|  | 2024-12-10T00:12:43.9688363Z hint: 	git branch -m <name> | ||||||
|  | 2024-12-10T00:12:43.9692962Z Initialized empty Git repository in /home/runner/work/vault/vault/.git/ | ||||||
|  | 2024-12-10T00:12:43.9704240Z [command]/usr/bin/git remote add origin https://github.com/hashicorp/vault`), | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					StepName: "Disabling automatic garbage collection", | ||||||
|  | 					SetupLog: []byte(`2024-12-10T00:12:43.9750441Z [command]/usr/bin/git config --local gc.auto 0`), | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					StepName: "Setting up auth", | ||||||
|  | 					SetupLog: []byte(`2024-12-10T00:12:43.9786405Z [command]/usr/bin/git config --local --name-only --get-regexp core\.sshCommand | ||||||
|  | 2024-12-10T00:12:43.9816121Z [command]/usr/bin/git submodule foreach --recursive sh -c "git config --local --name-only --get-regexp 'core\.sshCommand' && git config --local --unset-all 'core.sshCommand' || :" | ||||||
|  | 2024-12-10T00:12:44.0274103Z [command]/usr/bin/git config --local --name-only --get-regexp http\.https\:\/\/github\.com\/\.extraheader | ||||||
|  | 2024-12-10T00:12:44.0302372Z [command]/usr/bin/git submodule foreach --recursive sh -c "git config --local --name-only --get-regexp 'http\.https\:\/\/github\.com\/\.extraheader' && git config --local --unset-all 'http.https://github.com/.extraheader' || :" | ||||||
|  | 2024-12-10T00:12:44.0522906Z [command]/usr/bin/git config --local http.https://github.com/.extraheader AUTHORIZATION: basic ***`), | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					StepName: "Fetching the repository", | ||||||
|  | 					SetupLog: []byte(`2024-12-10T00:12:44.0572949Z [command]/usr/bin/git -c protocol.version=2 fetch --no-tags --prune --no-recurse-submodules --depth=1 origin 2767f8ee6214d03498b32d776173e1f336281bc5 | ||||||
|  | 2024-12-10T00:12:45.5127876Z From https://github.com/hashicorp/vault | ||||||
|  | 2024-12-10T00:12:45.5130722Z  * branch            2767f8ee6214d03498b32d776173e1f336281bc5 -> FETCH_HEAD`), | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					StepName: "Determining the checkout info", | ||||||
|  | 					BodyLog: []byte(`2024-12-10T00:12:45.5169128Z [command]/usr/bin/git sparse-checkout disable | ||||||
|  | 2024-12-10T00:12:45.5214350Z [command]/usr/bin/git config --local --unset-all extensions.worktreeConfig`), | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					StepName: "Checking out the ref", | ||||||
|  | 					SetupLog: []byte(`2024-12-10T00:12:45.5246868Z [command]/usr/bin/git checkout --progress --force 2767f8ee6214d03498b32d776173e1f336281bc5 | ||||||
|  | 2024-12-10T00:12:46.1483364Z Note: switching to '2767f8ee6214d03498b32d776173e1f336281bc5'. | ||||||
|  | 2024-12-10T00:12:46.1484000Z | ||||||
|  | 2024-12-10T00:12:46.1484523Z You are in 'detached HEAD' state. You can look around, make experimental | ||||||
|  | 2024-12-10T00:12:46.1485553Z changes and commit them, and you can discard any commits you make in this | ||||||
|  | 2024-12-10T00:12:46.1486616Z state without impacting any branches by switching back to a branch. | ||||||
|  | 2024-12-10T00:12:46.1487236Z | ||||||
|  | 2024-12-10T00:12:46.1487698Z If you want to create a new branch to retain commits you create, you may | ||||||
|  | 2024-12-10T00:12:46.1488691Z do so (now or later) by using -c with the switch command. Example: | ||||||
|  | 2024-12-10T00:12:46.1489249Z | ||||||
|  | 2024-12-10T00:12:46.1489526Z   git switch -c <new-branch-name> | ||||||
|  | 2024-12-10T00:12:46.1489940Z | ||||||
|  | 2024-12-10T00:12:46.1490209Z Or undo this operation with: | ||||||
|  | 2024-12-10T00:12:46.1490632Z | ||||||
|  | 2024-12-10T00:12:46.1490894Z   git switch - | ||||||
|  | 2024-12-10T00:12:46.1491230Z | ||||||
|  | 2024-12-10T00:12:46.1492016Z Turn off this advice by setting config variable advice.detachedHead to false | ||||||
|  | 2024-12-10T00:12:46.1492672Z | ||||||
|  | 2024-12-10T00:12:46.1493261Z HEAD is now at 2767f8e Update vault-plugin-secrets-openldap to v0.14.4 (#29131) (#29133)`), | ||||||
|  | 					BodyLog: []byte(`2024-12-10T00:12:46.1556639Z [command]/usr/bin/git log -1 --format=%H | ||||||
|  | 2024-12-10T00:12:46.1578665Z 2767f8ee6214d03498b32d776173e1f336281bc5`), | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					StepName: "Run enos scenario launch --timeout 45m0s --chdir ./enos proxy arch:amd64 artifact_source:artifactory artifact_type:package backend:raft config_mode:file consul_edition:ent consul_version:1.14.11 distro:ubuntu edition:ce ip_version:4 seal:shamir", | ||||||
|  | 					SetupLog: []byte(`2024-12-10T00:12:50.2289674Z [36;1menos scenario launch --timeout 45m0s --chdir ./enos proxy arch:amd64 artifact_source:artifactory artifact_type:package backend:raft config_mode:file consul_edition:ent consul_version:1.14.11 distro:ubuntu edition:ce ip_version:4 seal:shamir[0m | ||||||
|  | 2024-12-10T00:12:50.2314107Z shell: /usr/bin/bash -e {0} | ||||||
|  | 2024-12-10T00:12:50.2314496Z env: | ||||||
|  | 2024-12-10T00:12:50.2314951Z   GITHUB_TOKEN: *** | ||||||
|  | 2024-12-10T00:12:50.2315358Z   ENOS_DEBUG_DATA_ROOT_DIR: /tmp/enos-debug-data | ||||||
|  | 2024-12-10T00:12:50.2331925Z   DYNAMIC_CONFIG_KEY: 1.18.3-2024-12-49 | ||||||
|  | 2024-12-10T00:12:50.2332387Z   DYNAMIC_CONFIG_PATH: enos/enos-dynamic-config.hcl`), | ||||||
|  | 					BodyLog: []byte(`2024-12-10T00:12:50.7389391Z proxy [arch:amd64 artifact_source:artifactory artifact_type:package backend:raft config_mode:file consul_edition:ent consul_version:1.14.11 distro:ubuntu edition:ce ip_version:4 seal:shamir] generate: running | ||||||
|  | 2024-12-10T00:12:51.2323596Z proxy [arch:amd64 artifact_source:artifactory artifact_type:package backend:raft config_mode:file consul_edition:ent consul_version:1.14.11 distro:ubuntu edition:ce ip_version:4 seal:shamir] generate: success! | ||||||
|  | 2024-12-10T00:12:51.2325463Z proxy [arch:amd64 artifact_source:artifactory artifact_type:package backend:raft config_mode:file consul_edition:ent consul_version:1.14.11 distro:ubuntu edition:ce ip_version:4 seal:shamir] init: running | ||||||
|  | 2024-12-10T00:12:56.2324410Z proxy [arch:amd64 artifact_source:artifactory artifact_type:package backend:raft config_mode:file consul_edition:ent consul_version:1.14.11 distro:ubuntu edition:ce ip_version:4 seal:shamir] init: running | ||||||
|  | 2024-12-10T00:12:57.8598120Z proxy [arch:amd64 artifact_source:artifactory artifact_type:package backend:raft config_mode:file consul_edition:ent consul_version:1.14.11 distro:ubuntu edition:ce ip_version:4 seal:shamir] init: success! | ||||||
|  | 2024-12-10T00:12:57.8602036Z proxy [arch:amd64 artifact_source:artifactory artifact_type:package backend:raft config_mode:file consul_edition:ent consul_version:1.14.11 distro:ubuntu edition:ce ip_version:4 seal:shamir] validate: running | ||||||
|  | 2024-12-10T00:13:00.2734861Z proxy [arch:amd64 artifact_source:artifactory artifact_type:package backend:raft config_mode:file consul_edition:ent consul_version:1.14.11 distro:ubuntu edition:ce ip_version:4 seal:shamir] validate: success! | ||||||
|  | 2024-12-10T00:13:00.2737223Z proxy [arch:amd64 artifact_source:artifactory artifact_type:package backend:raft config_mode:file consul_edition:ent consul_version:1.14.11 distro:ubuntu edition:ce ip_version:4 seal:shamir] plan: running | ||||||
|  | 2024-12-10T00:13:04.6929951Z proxy [arch:amd64 artifact_source:artifactory artifact_type:package backend:raft config_mode:file consul_edition:ent consul_version:1.14.11 distro:ubuntu edition:ce ip_version:4 seal:shamir] plan: success! | ||||||
|  | 2024-12-10T00:13:04.6933016Z proxy [arch:amd64 artifact_source:artifactory artifact_type:package backend:raft config_mode:file consul_edition:ent consul_version:1.14.11 distro:ubuntu edition:ce ip_version:4 seal:shamir] apply: running | ||||||
|  | 2024-12-10T00:13:09.6937929Z proxy [arch:amd64 artifact_source:artifactory artifact_type:package backend:raft config_mode:file consul_edition:ent consul_version:1.14.11 distro:ubuntu edition:ce ip_version:4 seal:shamir] apply: running | ||||||
|  | 2024-12-10T00:16:27.4736392Z proxy [arch:amd64 artifact_source:artifactory artifact_type:package backend:raft config_mode:file consul_edition:ent consul_version:1.14.11 distro:ubuntu edition:ce ip_version:4 seal:shamir] apply: failed! | ||||||
|  | 2024-12-10T00:16:27.4738430Z [31m╷[0m | ||||||
|  | 2024-12-10T00:16:27.4739095Z [31m│[0m [1m[31mError: [0m[1mexit status 1 | ||||||
|  | 2024-12-10T00:16:27.4739835Z [31m│[0m | ||||||
|  | 2024-12-10T00:16:27.4740433Z [31m│[0m Error: Execution Error | ||||||
|  | 2024-12-10T00:16:27.4741075Z [31m│[0m | ||||||
|  | 2024-12-10T00:16:27.4742287Z [31m│[0m   with module.verify_secrets_engines_create.enos_remote_exec.kv_put_secret_test["0"], | ||||||
|  | 2024-12-10T00:16:27.4744486Z [31m│[0m   on ../../modules/verify_secrets_engines/modules/create/kv.tf line 106, in resource "enos_remote_exec" "kv_put_secret_test": | ||||||
|  | 2024-12-10T00:16:27.4746078Z [31m│[0m  106: resource "enos_remote_exec" "kv_put_secret_test" { | ||||||
|  | 2024-12-10T00:16:27.4746974Z [31m│[0m | ||||||
|  | 2024-12-10T00:16:27.4747719Z [31m│[0m failed to execute commands due to: running script: | ||||||
|  | 2024-12-10T00:16:27.4749074Z [31m│[0m [/home/runner/work/vault/vault/enos/modules/verify_secrets_engines/scripts/kv-put.sh] | ||||||
|  | 2024-12-10T00:16:27.4750274Z [31m│[0m failed, due to: 1 error occurred: | ||||||
|  | 2024-12-10T00:16:27.4751566Z [31m│[0m 	* executing script: Process exited with status 2: Error writing data to | ||||||
|  | 2024-12-10T00:16:27.4752733Z [31m│[0m secret/data/smoke-0: Error making API request. | ||||||
|  | 2024-12-10T00:16:27.4753591Z [31m│[0m | ||||||
|  | 2024-12-10T00:16:27.4754454Z [31m│[0m URL: PUT http://127.0.0.1:8200/v1/secret/data/smoke-0 | ||||||
|  | 2024-12-10T00:16:27.4755467Z [31m│[0m Code: 403. Errors: | ||||||
|  | 2024-12-10T00:16:27.4756215Z [31m│[0m | ||||||
|  | 2024-12-10T00:16:27.4756934Z [31m│[0m * 1 error occurred: | ||||||
|  | 2024-12-10T00:16:27.4757726Z [31m│[0m 	* permission denied | ||||||
|  | 2024-12-10T00:16:27.4951718Z Scenario: proxy [arch:amd64 artifact_source:artifactory artifact_type:package backend:raft config_mode:file consul_edition:ent consul_version:1.14.11 distro:ubuntu edition:ce ip_version:4 seal:shamir] failed! | ||||||
|  | 2024-12-10T00:16:27.4952646Z   Init: success! | ||||||
|  | 2024-12-10T00:16:27.4952980Z   Validate: success! | ||||||
|  | 2024-12-10T00:16:27.4953315Z   Plan: success!`), | ||||||
|  | 					ErrorLog: []byte(`2024-12-10T00:16:27.4962854Z Process completed with exit code 1.`), | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					StepName: "Run enos scenario launch --timeout 45m0s --chdir ./enos proxy arch:amd64 artifact_source:artifactory artifact_type:package backend:raft config_mode:file consul_edition:ent consul_version:1.14.11 distro:ubuntu edition:ce ip_version:4 seal:shamir", | ||||||
|  | 					SetupLog: []byte(`2024-12-10T00:16:27.5028497Z [36;1menos scenario launch --timeout 45m0s --chdir ./enos proxy arch:amd64 artifact_source:artifactory artifact_type:package backend:raft config_mode:file consul_edition:ent consul_version:1.14.11 distro:ubuntu edition:ce ip_version:4 seal:shamir[0m | ||||||
|  | 2024-12-10T00:16:27.5056211Z shell: /usr/bin/bash -e {0} | ||||||
|  | 2024-12-10T00:16:27.5056606Z env: | ||||||
|  | 2024-12-10T00:16:27.5057108Z   GITHUB_TOKEN: *** | ||||||
|  | 2024-12-10T00:16:27.5074106Z   DYNAMIC_CONFIG_KEY: 1.18.3-2024-12-49 | ||||||
|  | 2024-12-10T00:16:27.5074569Z   DYNAMIC_CONFIG_PATH: enos/enos-dynamic-config.hcl`), | ||||||
|  | 					BodyLog: []byte(`2024-12-10T00:16:28.0207793Z proxy [arch:amd64 artifact_source:artifactory artifact_type:package backend:raft config_mode:file consul_edition:ent consul_version:1.14.11 distro:ubuntu edition:ce ip_version:4 seal:shamir] generate: running | ||||||
|  | 2024-12-10T00:16:28.5089608Z proxy [arch:amd64 artifact_source:artifactory artifact_type:package backend:raft config_mode:file consul_edition:ent consul_version:1.14.11 distro:ubuntu edition:ce ip_version:4 seal:shamir] generate: success! | ||||||
|  | 2024-12-10T00:16:28.5093852Z proxy [arch:amd64 artifact_source:artifactory artifact_type:package backend:raft config_mode:file consul_edition:ent consul_version:1.14.11 distro:ubuntu edition:ce ip_version:4 seal:shamir] init: running | ||||||
|  | 2024-12-10T00:16:29.8095857Z proxy [arch:amd64 artifact_source:artifactory artifact_type:package backend:raft config_mode:file consul_edition:ent consul_version:1.14.11 distro:ubuntu edition:ce ip_version:4 seal:shamir] init: success! | ||||||
|  | 2024-12-10T00:16:29.8097587Z proxy [arch:amd64 artifact_source:artifactory artifact_type:package backend:raft config_mode:file consul_edition:ent consul_version:1.14.11 distro:ubuntu edition:ce ip_version:4 seal:shamir] validate: running | ||||||
|  | 2024-12-10T00:16:32.1198366Z proxy [arch:amd64 artifact_source:artifactory artifact_type:package backend:raft config_mode:file consul_edition:ent consul_version:1.14.11 distro:ubuntu edition:ce ip_version:4 seal:shamir] validate: success! | ||||||
|  | 2024-12-10T00:16:32.1199975Z proxy [arch:amd64 artifact_source:artifactory artifact_type:package backend:raft config_mode:file consul_edition:ent consul_version:1.14.11 distro:ubuntu edition:ce ip_version:4 seal:shamir] plan: running | ||||||
|  | 2024-12-10T00:16:37.0846706Z proxy [arch:amd64 artifact_source:artifactory artifact_type:package backend:raft config_mode:file consul_edition:ent consul_version:1.14.11 distro:ubuntu edition:ce ip_version:4 seal:shamir] plan: success! | ||||||
|  | 2024-12-10T00:16:37.0848336Z proxy [arch:amd64 artifact_source:artifactory artifact_type:package backend:raft config_mode:file consul_edition:ent consul_version:1.14.11 distro:ubuntu edition:ce ip_version:4 seal:shamir] apply: running | ||||||
|  | 2024-12-10T00:16:42.0856135Z proxy [arch:amd64 artifact_source:artifactory artifact_type:package backend:raft config_mode:file consul_edition:ent consul_version:1.14.11 distro:ubuntu edition:ce ip_version:4 seal:shamir] apply: running | ||||||
|  | 2024-12-10T00:16:46.3363724Z proxy [arch:amd64 artifact_source:artifactory artifact_type:package backend:raft config_mode:file consul_edition:ent consul_version:1.14.11 distro:ubuntu edition:ce ip_version:4 seal:shamir] apply: failed! | ||||||
|  | 2024-12-10T00:16:46.3365518Z [31m╷[0m | ||||||
|  | 2024-12-10T00:16:46.3366178Z [31m│[0m [1m[31mError: [0m[1mexit status 1 | ||||||
|  | 2024-12-10T00:16:46.3366866Z [31m│[0m | ||||||
|  | 2024-12-10T00:16:46.3367513Z [31m│[0m Error: Execution Error | ||||||
|  | 2024-12-10T00:16:46.3368230Z [31m│[0m | ||||||
|  | 2024-12-10T00:16:46.3369253Z [31m│[0m   with module.verify_secrets_engines_create.enos_remote_exec.kv_put_secret_test["1"], | ||||||
|  | 2024-12-10T00:16:46.3370933Z [31m│[0m   on ../../modules/verify_secrets_engines/modules/create/kv.tf line 106, in resource "enos_remote_exec" "kv_put_secret_test": | ||||||
|  | 2024-12-10T00:16:46.3372804Z [31m│[0m  106: resource "enos_remote_exec" "kv_put_secret_test" { | ||||||
|  | 2024-12-10T00:16:46.3373675Z [31m│[0m | ||||||
|  | 2024-12-10T00:16:46.3374416Z [31m│[0m failed to execute commands due to: running script: | ||||||
|  | 2024-12-10T00:16:46.3375692Z [31m│[0m [/home/runner/work/vault/vault/enos/modules/verify_secrets_engines/scripts/kv-put.sh] | ||||||
|  | 2024-12-10T00:16:46.3376812Z [31m│[0m failed, due to: 1 error occurred: | ||||||
|  | 2024-12-10T00:16:46.3377846Z [31m│[0m 	* executing script: Process exited with status 2: Error writing data to | ||||||
|  | 2024-12-10T00:16:46.3379243Z [31m│[0m secret/data/smoke-1: Error making API request. | ||||||
|  | 2024-12-10T00:16:46.3380011Z [31m│[0m | ||||||
|  | 2024-12-10T00:16:46.3380747Z [31m│[0m URL: PUT http://127.0.0.1:8200/v1/secret/data/smoke-1 | ||||||
|  | 2024-12-10T00:16:46.3381808Z [31m│[0m Code: 403. Errors: | ||||||
|  | 2024-12-10T00:16:46.3382441Z [31m│[0m | ||||||
|  | 2024-12-10T00:16:46.3383036Z [31m│[0m * 1 error occurred: | ||||||
|  | 2024-12-10T00:16:46.3383733Z [31m│[0m 	* permission denied | ||||||
|  | 2024-12-10T00:16:46.3384376Z [31m│[0m | ||||||
|  | 2024-12-10T00:16:46.3384902Z [31m│[0m | ||||||
|  | 2024-12-10T00:16:46.3385425Z [31m│[0m | ||||||
|  | 2024-12-10T00:16:46.3385951Z [31m│[0m | ||||||
|  | 2024-12-10T00:16:46.3386514Z [31m│[0m | ||||||
|  | 2024-12-10T00:16:46.3387094Z [31m│[0m output: | ||||||
|  | 2024-12-10T00:16:46.3387970Z [31m│[0m Error writing data to secret/data/smoke-1: Error making API request. | ||||||
|  | 2024-12-10T00:16:46.3388879Z [31m│[0m | ||||||
|  | 2024-12-10T00:16:46.3389512Z [31m│[0m URL: PUT http://127.0.0.1:8200/v1/secret/data/smoke-1 | ||||||
|  | 2024-12-10T00:16:46.3390043Z [31m│[0m Code: 403. Errors: | ||||||
|  | 2024-12-10T00:16:46.3390423Z [31m│[0m | ||||||
|  | 2024-12-10T00:16:46.3390775Z [31m│[0m * 1 error occurred: | ||||||
|  | 2024-12-10T00:16:46.3391187Z [31m│[0m 	* permission denied | ||||||
|  | 2024-12-10T00:16:46.3391955Z [31m│[0m | ||||||
|  | 2024-12-10T00:16:46.3392544Z [31m│[0m | ||||||
|  | 2024-12-10T00:16:46.3393117Z [31m│[0m | ||||||
|  | 2024-12-10T00:16:46.3393728Z [31m│[0m SSH Transport Config: | ||||||
|  | 2024-12-10T00:16:46.3394448Z [31m│[0m             user : ubuntu | ||||||
|  | 2024-12-10T00:16:46.3395054Z [31m│[0m             host : 23.22.214.188 | ||||||
|  | 2024-12-10T00:16:46.3395515Z [31m│[0m      private_key : null | ||||||
|  | 2024-12-10T00:16:46.3396144Z [31m│[0m private_key_path : /home/runner/work/vault/vault/enos/support/private_key.pem | ||||||
|  | 2024-12-10T00:16:46.3396966Z [31m│[0m       passphrase : null | ||||||
|  | 2024-12-10T00:16:46.3397419Z [31m│[0m  passphrase_path : null | ||||||
|  | 2024-12-10T00:16:46.3397813Z [31m│[0m | ||||||
|  | 2024-12-10T00:16:46.3398141Z [31m│[0m | ||||||
|  | 2024-12-10T00:16:46.3398495Z [31m│[0m Application Logs: | ||||||
|  | 2024-12-10T00:16:46.3398993Z [31m│[0m   vault: /tmp/enos-debug-data/vault_23.22.214.188.log | ||||||
|  | 2024-12-10T00:16:46.3399481Z [31m│[0m | ||||||
|  | 2024-12-10T00:16:46.3571781Z Scenario: proxy [arch:amd64 artifact_source:artifactory artifact_type:package backend:raft config_mode:file consul_edition:ent consul_version:1.14.11 distro:ubuntu edition:ce ip_version:4 seal:shamir] failed! | ||||||
|  | 2024-12-10T00:16:46.3572720Z   Init: success! | ||||||
|  | 2024-12-10T00:16:46.3573082Z   Validate: success! | ||||||
|  | 2024-12-10T00:16:46.3573432Z   Plan: success!`), | ||||||
|  | 					ErrorLog: []byte(`2024-12-10T00:16:46.3575376Z Process completed with exit code 1.`), | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					StepName: "Run hashicorp/actions-slack-status@v2.0.1", | ||||||
|  | 					SetupLog: []byte(`2024-12-10T00:18:21.7000653Z with: | ||||||
|  | 2024-12-10T00:18:21.7002008Z   failure-message: enos scenario launch proxy arch:amd64 artifact_source:artifactory artifact_type:package backend:raft config_mode:file consul_edition:ent consul_version:1.14.11 distro:ubuntu edition:ce ip_version:4 seal:shamir failed. | ||||||
|  | Triggering event: "repository_dispatch" | ||||||
|  | Actor: "crt-orchestrator[bot]" | ||||||
|  | 2024-12-10T00:18:21.7003270Z   status: failure | ||||||
|  | 2024-12-10T00:18:21.7003900Z   slack-webhook-url: *** | ||||||
|  | 2024-12-10T00:18:21.7004273Z env: | ||||||
|  | 2024-12-10T00:18:21.7004715Z   GITHUB_TOKEN: *** | ||||||
|  | 2024-12-10T00:18:21.7021520Z   DYNAMIC_CONFIG_KEY: 1.18.3-2024-12-49 | ||||||
|  | 2024-12-10T00:18:21.7021989Z   DYNAMIC_CONFIG_PATH: enos/enos-dynamic-config.hcl`), | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					StepName: "Run actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea", | ||||||
|  | 					SetupLog: []byte(`2024-12-10T00:18:21.7064074Z with: | ||||||
|  | 2024-12-10T00:18:21.7064579Z   script: return require(process.env.GITHUB_ACTION_PATH + '/main.js')({context, core}) | ||||||
|  |  | ||||||
|  | 2024-12-10T00:18:21.7065286Z   github-token: *** | ||||||
|  | 2024-12-10T00:18:21.7065816Z   debug: false | ||||||
|  | 2024-12-10T00:18:21.7066175Z   user-agent: actions/github-script | ||||||
|  | 2024-12-10T00:18:21.7066603Z   result-encoding: json | ||||||
|  | 2024-12-10T00:18:21.7066955Z   retries: 0 | ||||||
|  | 2024-12-10T00:18:21.7067326Z   retry-exempt-status-codes: 400,401,403,404,422 | ||||||
|  | 2024-12-10T00:18:21.7067754Z env: | ||||||
|  | 2024-12-10T00:18:21.7068163Z   GITHUB_TOKEN: *** | ||||||
|  | 2024-12-10T00:18:21.7085096Z   DYNAMIC_CONFIG_KEY: 1.18.3-2024-12-49 | ||||||
|  | 2024-12-10T00:18:21.7085559Z   DYNAMIC_CONFIG_PATH: enos/enos-dynamic-config.hcl | ||||||
|  | 2024-12-10T00:18:21.7086012Z   INPUT_STATUS: failure | ||||||
|  | 2024-12-10T00:18:21.7086382Z   INPUT_SUCCESS-MESSAGE: | ||||||
|  | 2024-12-10T00:18:21.7087631Z   INPUT_FAILURE-MESSAGE: enos scenario launch proxy arch:amd64 artifact_source:artifactory artifact_type:package backend:raft config_mode:file consul_edition:ent consul_version:1.14.11 distro:ubuntu edition:ce ip_version:4 seal:shamir failed. | ||||||
|  | Triggering event: "repository_dispatch" | ||||||
|  | Actor: "crt-orchestrator[bot]" | ||||||
|  | 2024-12-10T00:18:21.7088915Z   INPUT_CANCELLED-MESSAGE: | ||||||
|  | 2024-12-10T00:18:21.7089296Z   INPUT_SKIPPED-MESSAGE:`), | ||||||
|  | 					BodyLog: []byte(`2024-12-10T00:18:22.6819662Z Post job cleanup. | ||||||
|  | 2024-12-10T00:18:22.6892801Z Post job cleanup. | ||||||
|  | 2024-12-10T00:18:22.8076185Z Post job cleanup. | ||||||
|  | 2024-12-10T00:18:22.9022071Z [command]/usr/bin/git version | ||||||
|  | 2024-12-10T00:18:22.9062573Z git version 2.47.1 | ||||||
|  | 2024-12-10T00:18:22.9107683Z Temporarily overriding HOME='/home/runner/work/_temp/dbe6a6bb-4627-4ebf-86bc-d7e567dc0df4' before making global git config changes | ||||||
|  | 2024-12-10T00:18:22.9109480Z Adding repository directory to the temporary git global config as a safe directory | ||||||
|  | 2024-12-10T00:18:22.9122431Z [command]/usr/bin/git config --global --add safe.directory /home/runner/work/vault/vault | ||||||
|  | 2024-12-10T00:18:22.9159445Z [command]/usr/bin/git config --local --name-only --get-regexp core\.sshCommand | ||||||
|  | 2024-12-10T00:18:22.9193356Z [command]/usr/bin/git submodule foreach --recursive sh -c "git config --local --name-only --get-regexp 'core\.sshCommand' && git config --local --unset-all 'core.sshCommand' || :" | ||||||
|  | 2024-12-10T00:18:22.9439897Z [command]/usr/bin/git config --local --name-only --get-regexp http\.https\:\/\/github\.com\/\.extraheader | ||||||
|  | 2024-12-10T00:18:22.9461015Z http.https://github.com/.extraheader | ||||||
|  | 2024-12-10T00:18:22.9474504Z [command]/usr/bin/git config --local --unset-all http.https://github.com/.extraheader | ||||||
|  | 2024-12-10T00:18:22.9505151Z [command]/usr/bin/git submodule foreach --recursive sh -c "git config --local --name-only --get-regexp 'http\.https\:\/\/github\.com\/\.extraheader' && git config --local --unset-all 'http.https://github.com/.extraheader' || :" | ||||||
|  | 2024-12-10T00:18:22.9846381Z Cleaning up orphan processes`), | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		"only_steps": { | ||||||
|  | 			onlySteps: []string{ | ||||||
|  | 				"Operating System", | ||||||
|  | 				"Runner Image Provisioner", | ||||||
|  | 				"Disabling automatic garbage collection", | ||||||
|  | 				"Setting up auth", | ||||||
|  | 				"Run hashicorp/actions-slack-status@v2.0.1", | ||||||
|  | 			}, | ||||||
|  | 			expected: []*LogEntry{ | ||||||
|  | 				{ | ||||||
|  | 					StepName: "Operating System", | ||||||
|  | 					SetupLog: []byte(`2024-12-10T00:12:39.4701675Z Ubuntu | ||||||
|  | 2024-12-10T00:12:39.4702661Z 24.04.1 | ||||||
|  | 2024-12-10T00:12:39.4784102Z LTS`), | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					StepName: "Runner Image Provisioner", | ||||||
|  | 					SetupLog: []byte(`2024-12-10T00:12:39.4796788Z 2.0.385.1`), | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					StepName: "Disabling automatic garbage collection", | ||||||
|  | 					SetupLog: []byte(`2024-12-10T00:12:43.9750441Z [command]/usr/bin/git config --local gc.auto 0`), | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					StepName: "Setting up auth", | ||||||
|  | 					SetupLog: []byte(`2024-12-10T00:12:43.9786405Z [command]/usr/bin/git config --local --name-only --get-regexp core\.sshCommand | ||||||
|  | 2024-12-10T00:12:43.9816121Z [command]/usr/bin/git submodule foreach --recursive sh -c "git config --local --name-only --get-regexp 'core\.sshCommand' && git config --local --unset-all 'core.sshCommand' || :" | ||||||
|  | 2024-12-10T00:12:44.0274103Z [command]/usr/bin/git config --local --name-only --get-regexp http\.https\:\/\/github\.com\/\.extraheader | ||||||
|  | 2024-12-10T00:12:44.0302372Z [command]/usr/bin/git submodule foreach --recursive sh -c "git config --local --name-only --get-regexp 'http\.https\:\/\/github\.com\/\.extraheader' && git config --local --unset-all 'http.https://github.com/.extraheader' || :" | ||||||
|  | 2024-12-10T00:12:44.0522906Z [command]/usr/bin/git config --local http.https://github.com/.extraheader AUTHORIZATION: basic ***`), | ||||||
|  | 				}, | ||||||
|  | 				{ | ||||||
|  | 					StepName: "Run hashicorp/actions-slack-status@v2.0.1", | ||||||
|  | 					SetupLog: []byte(`2024-12-10T00:18:21.7000653Z with: | ||||||
|  | 2024-12-10T00:18:21.7002008Z   failure-message: enos scenario launch proxy arch:amd64 artifact_source:artifactory artifact_type:package backend:raft config_mode:file consul_edition:ent consul_version:1.14.11 distro:ubuntu edition:ce ip_version:4 seal:shamir failed. | ||||||
|  | Triggering event: "repository_dispatch" | ||||||
|  | Actor: "crt-orchestrator[bot]" | ||||||
|  | 2024-12-10T00:18:21.7003270Z   status: failure | ||||||
|  | 2024-12-10T00:18:21.7003900Z   slack-webhook-url: *** | ||||||
|  | 2024-12-10T00:18:21.7004273Z env: | ||||||
|  | 2024-12-10T00:18:21.7004715Z   GITHUB_TOKEN: *** | ||||||
|  | 2024-12-10T00:18:21.7021520Z   DYNAMIC_CONFIG_KEY: 1.18.3-2024-12-49 | ||||||
|  | 2024-12-10T00:18:21.7021989Z   DYNAMIC_CONFIG_PATH: enos/enos-dynamic-config.hcl`), | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	} { | ||||||
|  | 		t.Run(name, func(t *testing.T) { | ||||||
|  | 			f, err := os.Open(filepath.Join("./testfixtures/actions.log")) | ||||||
|  | 			require.NoError(t, err) | ||||||
|  | 			scanner := NewLogScaner(WithLogScannerOnlySteps(test.onlySteps)) | ||||||
|  | 			res, err := scanner.Scan(f) | ||||||
|  | 			require.NoError(t, err) | ||||||
|  | 			require.Len(t, res, len(test.expected)) | ||||||
|  | 			require.Equal(t, test.expected, res) | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										189
									
								
								tools/pipeline/internal/pkg/github/workflow_run_summary.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										189
									
								
								tools/pipeline/internal/pkg/github/workflow_run_summary.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,189 @@ | |||||||
|  | // Copyright (c) HashiCorp, Inc. | ||||||
|  | // SPDX-License-Identifier: BUSL-1.1 | ||||||
|  |  | ||||||
|  | package github | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"errors" | ||||||
|  | 	"slices" | ||||||
|  | 	"strings" | ||||||
|  | 	"text/template" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // workflowRunTemplate is our template for rendering workflow runs in human | ||||||
|  | // readable text. | ||||||
|  | var workflowRunTemplate = template.Must(template.New("workflow_run").Funcs(template.FuncMap{ | ||||||
|  | 	"boldify":              boldify, | ||||||
|  | 	"format_log_lines":     formatLogLines, | ||||||
|  | 	"intensify_status":     intensifyStatus, | ||||||
|  | 	"intensify_annotation": intensifyAnnotationLevel, | ||||||
|  | 	"redify":               redify, | ||||||
|  | 	"splitlines":           splitLines, | ||||||
|  | }).Parse(workflowRunTextTemplate)) | ||||||
|  |  | ||||||
|  | // workflowRunTextTemplate is the actual template text of our human readable | ||||||
|  | // workflow run output. | ||||||
|  | const workflowRunTextTemplate = ` | ||||||
|  | {{ .Run.Name }} (ID: {{ .Run.ID }}) | ||||||
|  |   Title: {{ boldify .Run.DisplayTitle }} | ||||||
|  |   URL: {{ .Run.HTMLURL }} | ||||||
|  |   HEAD Branch: {{ .Run.HeadBranch }} | ||||||
|  |   HEAD SHA: {{ .Run.HeadSHA }} | ||||||
|  |   Author: {{ .Run.HeadCommit.Author.Name }} | ||||||
|  |   Actor: {{ .Run.Actor.Login }} | ||||||
|  |   Attempt: {{ .Run.RunAttempt }} | ||||||
|  |   {{- if .CheckRuns }} | ||||||
|  |   {{ boldify "Annotations" }} | ||||||
|  |     {{- range $cr := .CheckRuns }} | ||||||
|  |     {{- range $a := $cr.Annotations }} | ||||||
|  |     {{ intensify_annotation $a.AnnotationLevel }}{{- if $a.Title }} {{ boldify $a.Title }}{{- end -}} | ||||||
|  |     {{- if $a.Message }} | ||||||
|  |     {{- range $am := splitlines $a.Message }} | ||||||
|  |       {{ boldify $am }} | ||||||
|  |     {{- end -}} | ||||||
|  |     {{- end -}} | ||||||
|  |     {{- if $a.RawDetails }} | ||||||
|  |     {{- range $ad := splitlines $a.RawDetails }} | ||||||
|  |       {{ boldify $ad }} | ||||||
|  |     {{- end -}} | ||||||
|  |     {{- end -}} | ||||||
|  |     {{- end -}} | ||||||
|  |     {{- end -}} | ||||||
|  |   {{- end }} | ||||||
|  |   Status: {{ intensify_status .Run.Status }} | ||||||
|  |   Conclusion: {{ intensify_status .Run.Conclusion }} | ||||||
|  |   {{- if .Jobs -}} | ||||||
|  |   {{- range $j := .Jobs }} | ||||||
|  |     Job: {{ boldify $j.Job.Name }} | ||||||
|  |       URL: {{ $j.Job.HTMLURL }} | ||||||
|  |       Status: {{ $j.Job.Status }} | ||||||
|  |       Conclusion: {{ $j.Job.Conclusion }} | ||||||
|  |       CreatedAt: {{ $j.Job.CreatedAt }} | ||||||
|  |       StartedAt: {{ $j.Job.StartedAt }} | ||||||
|  |       CompletedAt: {{ $j.Job.CompletedAt }} | ||||||
|  |       {{- if .UnsuccessfulSteps }} | ||||||
|  |       {{ boldify "Unsuccessful Steps:" }} | ||||||
|  |       {{- range $s := .UnsuccessfulSteps }} | ||||||
|  |         Step: {{ boldify $s.Name }} | ||||||
|  |           Status: {{ intensify_status $s.Status }} | ||||||
|  |           Conclusion: {{ intensify_status $s.Conclusion }} | ||||||
|  |       {{- end -}} | ||||||
|  |       {{- end -}} | ||||||
|  |       {{- if .LogEntries }} | ||||||
|  |       {{ boldify "Unsuccessful Step Log Summaries:" }} | ||||||
|  |       {{- range $l := .LogEntries }} | ||||||
|  |         Step: {{ boldify $l.StepName }} | ||||||
|  |         {{- range $sl := format_log_lines $l.SetupLog }} | ||||||
|  |           {{ $sl }} | ||||||
|  |         {{- end -}} | ||||||
|  |         {{- range $bl := format_log_lines $l.BodyLog }} | ||||||
|  |           {{ $bl }} | ||||||
|  |         {{- end -}} | ||||||
|  |         {{- range $el := format_log_lines $l.ErrorLog }} | ||||||
|  |           {{ redify $el }} | ||||||
|  |         {{- end -}} | ||||||
|  |       {{- end -}} | ||||||
|  |       {{- end -}} | ||||||
|  |   {{- end -}} | ||||||
|  |   {{- end -}}` | ||||||
|  |  | ||||||
|  | func summarizeWorkflowRun(r *WorkflowRun) (string, error) { | ||||||
|  | 	if r == nil { | ||||||
|  | 		return "", errors.New("uninitialized workflow run") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if r.summary != "" { | ||||||
|  | 		return r.summary, nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	b := &bytes.Buffer{} | ||||||
|  | 	err := workflowRunTemplate.Execute(b, r) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return "", err | ||||||
|  | 	} | ||||||
|  | 	r.summary = b.String() | ||||||
|  |  | ||||||
|  | 	return r.summary, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func formatLogLines(log []byte) []string { | ||||||
|  | 	if len(log) == 0 { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	lines := []string{} | ||||||
|  | 	lastLine := "" | ||||||
|  | 	for _, line := range strings.Split(string(log), "\n") { | ||||||
|  | 		// Strip out duplicate lines and blank lines so the summaries | ||||||
|  | 		// are more clear. Log lines include timestamps with microseconds | ||||||
|  | 		// so we compare for duplicates by comparing line values without | ||||||
|  | 		// the timestamp. | ||||||
|  | 		newLineNoTimestamp := "" | ||||||
|  | 		newLineParts := strings.SplitN(line, " ", 2) | ||||||
|  | 		if len(newLineParts) < 2 { | ||||||
|  | 			// blank lines may only have a timestamp | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		newLineNoTimestamp = newLineParts[1] | ||||||
|  | 		if strings.TrimSpace(newLineNoTimestamp) == "" { | ||||||
|  | 			// Don't write otherwise blank lines | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		if lastLine == newLineNoTimestamp { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		lines = append(lines, line) | ||||||
|  | 		lastLine = newLineNoTimestamp | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return lines | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func intensifyStatus(in string) string { | ||||||
|  | 	switch in { | ||||||
|  | 	case "completed", "success": | ||||||
|  | 		return "\x1b[1;32;49m" + in + "\x1b[0m" | ||||||
|  | 	case "cancelled": | ||||||
|  | 		return "\x1b[1;33;49m" + in + "\x1b[0m" | ||||||
|  | 	case "failure": | ||||||
|  | 		return "\x1b[1;31;49m" + in + "\x1b[0m" | ||||||
|  | 	case "skipped": | ||||||
|  | 		return "\x1b[1;37;49m" + in + "\x1b[0m" | ||||||
|  | 	case "in_progress": | ||||||
|  | 		return "\x1b[1;37;49m" + in + "\x1b[0m" | ||||||
|  | 	case "warning": | ||||||
|  | 		return "\x1b[1;33;49m" + in + "\x1b[0m" | ||||||
|  | 	default: | ||||||
|  | 		return in | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func intensifyAnnotationLevel(in string) string { | ||||||
|  | 	switch in { | ||||||
|  | 	case "failure": | ||||||
|  | 		return "\x1b[1;31;49m" + in + "\x1b[0m" | ||||||
|  | 	case "warning": | ||||||
|  | 		return "\x1b[1;33;49m" + in + "\x1b[0m" | ||||||
|  | 	default: | ||||||
|  | 		return in | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func boldify(in string) string { | ||||||
|  | 	return "\x1b[1;39m" + in + "\x1b[0m" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func redify(in string) string { | ||||||
|  | 	return "\x1b[1;31m" + in + "\x1b[0m" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func splitLines(in string) []string { | ||||||
|  | 	return slices.DeleteFunc(strings.Split(in, "\n"), func(s string) bool { | ||||||
|  | 		if s == "\n" || s == "" { | ||||||
|  | 			return true | ||||||
|  | 		} | ||||||
|  | 		return false | ||||||
|  | 	}) | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user
	 Ryan Cragun
					Ryan Cragun