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:
Ryan Cragun 2025-01-31 13:48:38 -07:00 committed by GitHub
parent 6a87419ab0
commit cda9ad3491
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 1622 additions and 73 deletions

2
.gitignore vendored
View File

@ -133,4 +133,6 @@ website/components/node_modules
tools/godoctests/.bin
tools/gonilnilfunctions/.bin
tools/codechecker/.bin
tools/pipeline/.bin
tools/pipeline/pipeline
.ci-bootstrap

View File

@ -4,10 +4,11 @@ go 1.23.2
require (
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/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
)
@ -17,29 +18,30 @@ require (
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/fatih/color v1.16.0 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/analysis v0.23.0 // indirect
github.com/go-openapi/errors v0.22.0 // indirect
github.com/go-openapi/jsonpointer 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/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/strfmt v0.23.0 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/go-openapi/validate v0.24.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/hashicorp/go-hclog v1.6.3 // 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/jessevdk/go-flags v1.6.1 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mailru/easyjson v0.9.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mitchellh/go-wordwrap v1.0.1 // 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/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/zclconf/go-cty v1.15.0 // indirect
go.mongodb.org/mongo-driver v1.14.0 // indirect
go.opentelemetry.io/otel v1.20.0 // indirect
go.opentelemetry.io/otel/metric v1.20.0 // indirect
go.opentelemetry.io/otel/trace v1.20.0 // indirect
golang.org/x/mod v0.21.0 // indirect
golang.org/x/net v0.30.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.26.0 // indirect
golang.org/x/text v0.19.0 // indirect
golang.org/x/tools v0.26.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
github.com/zclconf/go-cty v1.16.2 // indirect
go.mongodb.org/mongo-driver v1.17.2 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/otel v1.34.0 // indirect
go.opentelemetry.io/otel/metric v1.34.0 // indirect
go.opentelemetry.io/otel/trace v1.34.0 // indirect
golang.org/x/mod v0.22.0 // indirect
golang.org/x/net v0.34.0 // indirect
golang.org/x/sync v0.10.0 // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/tools v0.29.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View File

@ -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/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ=
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.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
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.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
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/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
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/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/runtime v0.26.0 h1:HYOFtG00FM1UvqrcxbEJg/SwvDRvYLQKGhw2zaQjTcc=
github.com/go-openapi/runtime v0.26.0/go.mod h1:QgRGeZwrUcSHdeh4Ka9Glvo0ug1LC5WyE+EV88plZrQ=
github.com/go-openapi/runtime v0.28.0 h1:gpPPmWSNGo214l6n8hzdXYhPuJcGtziTOgUpvsFWGIQ=
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/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
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/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/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/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/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
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-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
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.22.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA=
github.com/hashicorp/hcl/v2 v2.23.0 h1:Fphj1/gCylPxHutVSEOf2fBOh1VE4AuLV7+kbJf3qos=
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/go.mod h1:RgXaKHrH02RjK2SMAVyHmDPrTrwR0iR6El3W93kh5Vc=
github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM=
github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
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/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
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/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
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.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
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.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
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.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/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
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.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
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.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
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/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=
@ -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/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.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
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/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/go.mod h1:E+qpdyiQs2YKRxFnX1JjpdFE1z3Ka94Kem2q9ZG6Jjo=
github.com/zclconf/go-cty v1.15.0 h1:tTCRWxsexYUmtt/wVxgDClUe+uQusuI443uL6e+5sXQ=
github.com/zclconf/go-cty v1.15.0/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
github.com/zclconf/go-cty v1.16.2 h1:LAJSwc3v81IRBZyUVQDUdZ7hs3SYs9jv0eZJDWHD/70=
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/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM=
go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80=
go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c=
go.opentelemetry.io/otel v1.20.0 h1:vsb/ggIY+hUjD/zCAQHpzTmndPqv/ml2ArbsbfBYTAc=
go.opentelemetry.io/otel v1.20.0/go.mod h1:oUIGj3D77RwJdM6PPZImDpSZGDvkD9fhesHny69JFrs=
go.opentelemetry.io/otel/metric v1.20.0 h1:ZlrO8Hu9+GAhnepmRGhSU7/VkpjrNowxRN9GyKR4wzA=
go.opentelemetry.io/otel/metric v1.20.0/go.mod h1:90DRw3nfK4D7Sm/75yQ00gTJxtkBxX+wu6YaNymbpVM=
go.opentelemetry.io/otel/sdk v1.14.0 h1:PDCppFRDq8A1jL9v6KMI6dYesaq+DFcDZvjsoGvxGzY=
go.opentelemetry.io/otel/sdk v1.14.0/go.mod h1:bwIC5TjrNG6QDCHNWvW4HLHtUQ4I+VQDsnjhvyZCALM=
go.opentelemetry.io/otel/trace v1.20.0 h1:+yxVAPZPbQhbC3OfAkeIVTky6iTFpcr4SiY9om7mXSQ=
go.opentelemetry.io/otel/trace v1.20.0/go.mod h1:HJSK7F/hA5RlzpZ0zKDCHCDHm556LCDtKaAo6JmBFUU=
go.mongodb.org/mongo-driver v1.17.2 h1:gvZyk8352qSfzyZ2UMWcpDpMSGEr1eqE4T793SqyhzM=
go.mongodb.org/mongo-driver v1.17.2/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw=
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/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
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-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-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-20220811171246-fbc7d0a398ab/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.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
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/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
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/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
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/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/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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View 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
}

View 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
}

View File

@ -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.AddCommand(newGenerateCmd())
rootCmd.AddCommand(newGithubCmd())
rootCmd.AddCommand(newReleasesCmd())
rootCmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {

View File

@ -10,8 +10,9 @@ import (
"slices"
"testing"
"github.com/hashicorp/vault/tools/pipeline/internal/pkg/releases"
"github.com/stretchr/testify/require"
"github.com/hashicorp/vault/tools/pipeline/internal/pkg/releases"
)
var testAPIVersions = []string{
@ -230,7 +231,7 @@ func Test_EnosDynamicConfigReq_Run(t *testing.T) {
AWSRegion: []string{"us-east-1", "us-west-2"},
DistroVersionAmzn: []string{"2023"},
DistroVersionLeap: []string{"15.6"},
DistroVersionRhel: []string{"8.10, 9.4"},
DistroVersionRhel: []string{"8.10", "9.4"},
DistroVersionSles: []string{"15.6"},
DistroVersionUbuntu: []string{"20.04", "24.04"},
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 {
sample_attributes = {
aws_region = ["us-east-1", "us-west-2"]
distro_version_amzn = ["2023"]
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_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"]

View 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
}

View File

@ -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
}
}

View 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{},
}
}

View 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 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
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 
2024-12-10T00:16:27.4739095Z  Error: exit status 1
2024-12-10T00:16:27.4739835Z 
2024-12-10T00:16:27.4740433Z  Error: Execution Error
2024-12-10T00:16:27.4741075Z 
2024-12-10T00:16:27.4742287Z  with module.verify_secrets_engines_create.enos_remote_exec.kv_put_secret_test["0"],
2024-12-10T00:16:27.4744486Z  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  106: resource "enos_remote_exec" "kv_put_secret_test" {
2024-12-10T00:16:27.4746974Z 
2024-12-10T00:16:27.4747719Z  failed to execute commands due to: running script:
2024-12-10T00:16:27.4749074Z  [/home/runner/work/vault/vault/enos/modules/verify_secrets_engines/scripts/kv-put.sh]
2024-12-10T00:16:27.4750274Z  failed, due to: 1 error occurred:
2024-12-10T00:16:27.4751566Z  * executing script: Process exited with status 2: Error writing data to
2024-12-10T00:16:27.4752733Z  secret/data/smoke-0: Error making API request.
2024-12-10T00:16:27.4753591Z 
2024-12-10T00:16:27.4754454Z  URL: PUT http://127.0.0.1:8200/v1/secret/data/smoke-0
2024-12-10T00:16:27.4755467Z  Code: 403. Errors:
2024-12-10T00:16:27.4756215Z 
2024-12-10T00:16:27.4756934Z  * 1 error occurred:
2024-12-10T00:16:27.4757726Z  * 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 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
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 
2024-12-10T00:16:46.3366178Z  Error: exit status 1
2024-12-10T00:16:46.3366866Z 
2024-12-10T00:16:46.3367513Z  Error: Execution Error
2024-12-10T00:16:46.3368230Z 
2024-12-10T00:16:46.3369253Z  with module.verify_secrets_engines_create.enos_remote_exec.kv_put_secret_test["1"],
2024-12-10T00:16:46.3370933Z  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  106: resource "enos_remote_exec" "kv_put_secret_test" {
2024-12-10T00:16:46.3373675Z 
2024-12-10T00:16:46.3374416Z  failed to execute commands due to: running script:
2024-12-10T00:16:46.3375692Z  [/home/runner/work/vault/vault/enos/modules/verify_secrets_engines/scripts/kv-put.sh]
2024-12-10T00:16:46.3376812Z  failed, due to: 1 error occurred:
2024-12-10T00:16:46.3377846Z  * executing script: Process exited with status 2: Error writing data to
2024-12-10T00:16:46.3379243Z  secret/data/smoke-1: Error making API request.
2024-12-10T00:16:46.3380011Z 
2024-12-10T00:16:46.3380747Z  URL: PUT http://127.0.0.1:8200/v1/secret/data/smoke-1
2024-12-10T00:16:46.3381808Z  Code: 403. Errors:
2024-12-10T00:16:46.3382441Z 
2024-12-10T00:16:46.3383036Z  * 1 error occurred:
2024-12-10T00:16:46.3383733Z  * permission denied
2024-12-10T00:16:46.3384376Z 
2024-12-10T00:16:46.3384902Z 
2024-12-10T00:16:46.3385425Z 
2024-12-10T00:16:46.3385951Z 
2024-12-10T00:16:46.3386514Z 
2024-12-10T00:16:46.3387094Z  output:
2024-12-10T00:16:46.3387970Z  Error writing data to secret/data/smoke-1: Error making API request.
2024-12-10T00:16:46.3388879Z 
2024-12-10T00:16:46.3389512Z  URL: PUT http://127.0.0.1:8200/v1/secret/data/smoke-1
2024-12-10T00:16:46.3390043Z  Code: 403. Errors:
2024-12-10T00:16:46.3390423Z 
2024-12-10T00:16:46.3390775Z  * 1 error occurred:
2024-12-10T00:16:46.3391187Z  * permission denied
2024-12-10T00:16:46.3391955Z 
2024-12-10T00:16:46.3392544Z 
2024-12-10T00:16:46.3393117Z 
2024-12-10T00:16:46.3393728Z  SSH Transport Config:
2024-12-10T00:16:46.3394448Z  user : ubuntu
2024-12-10T00:16:46.3395054Z  host : 23.22.214.188
2024-12-10T00:16:46.3395515Z  private_key : null
2024-12-10T00:16:46.3396144Z  private_key_path : /home/runner/work/vault/vault/enos/support/private_key.pem
2024-12-10T00:16:46.3396966Z  passphrase : null
2024-12-10T00:16:46.3397419Z  passphrase_path : null
2024-12-10T00:16:46.3397813Z 
2024-12-10T00:16:46.3398141Z 
2024-12-10T00:16:46.3398495Z  Application Logs:
2024-12-10T00:16:46.3398993Z  vault: /tmp/enos-debug-data/vault_23.22.214.188.log
2024-12-10T00:16:46.3399481Z 
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

View 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
})
}