From efbb4fc42dc2ba94cb4b6d5b1788e19fb74b7ea4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 Feb 2024 10:19:45 -0500 Subject: [PATCH 01/10] Bump github.com/open-policy-agent/conftest from 0.48.0 to 0.49.1 (#148) Bumps [github.com/open-policy-agent/conftest](https://github.com/open-policy-agent/conftest) from 0.48.0 to 0.49.1. - [Release notes](https://github.com/open-policy-agent/conftest/releases) - [Changelog](https://github.com/open-policy-agent/conftest/blob/master/.goreleaser.yml) - [Commits](https://github.com/open-policy-agent/conftest/compare/v0.48.0...v0.49.1) --- updated-dependencies: - dependency-name: github.com/open-policy-agent/conftest dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index b4aa6357..76a21a40 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/labstack/echo/v4 v4.11.4 github.com/masterminds/semver v1.5.0 github.com/olekukonko/tablewriter v0.0.5 - github.com/open-policy-agent/conftest v0.48.0 + github.com/open-policy-agent/conftest v0.49.1 github.com/pkg/errors v0.9.1 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 github.com/prometheus/client_golang v1.18.0 @@ -134,7 +134,7 @@ require ( github.com/google/gofuzz v1.2.0 // indirect github.com/google/s2a-go v0.1.7 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect - github.com/google/uuid v1.5.0 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect github.com/googleapis/gax-go/v2 v2.12.0 // indirect github.com/gorilla/mux v1.8.1 // indirect @@ -186,7 +186,7 @@ require ( github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/open-policy-agent/opa v0.60.0 // indirect + github.com/open-policy-agent/opa v0.61.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0-rc5 // indirect github.com/patrickmn/go-cache v2.1.0+incompatible // indirect diff --git a/go.sum b/go.sum index 2bc745d5..864da4c3 100644 --- a/go.sum +++ b/go.sum @@ -573,8 +573,8 @@ github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaU github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= -github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +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/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= @@ -794,10 +794,10 @@ github.com/onsi/gomega v1.24.1/go.mod h1:3AOiACssS3/MajrniINInwbfOOtfZvplPzuRSmv github.com/onsi/gomega v1.25.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM= github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= -github.com/open-policy-agent/conftest v0.48.0 h1:v2BzUMz+b4rnimb4kRO7PV8BPHmcvx60agdzwspHYOg= -github.com/open-policy-agent/conftest v0.48.0/go.mod h1:lpJb5pK6ygns/l6jn+LB/6ek+894DPPGbnXyaHQTybo= -github.com/open-policy-agent/opa v0.60.0 h1:ZPoPt4yeNs5UXCpd/P/btpSyR8CR0wfhVoh9BOwgJNs= -github.com/open-policy-agent/opa v0.60.0/go.mod h1:aD5IK6AiLNYBjNXn7E02++yC8l4Z+bRDvgM6Ss0bBzA= +github.com/open-policy-agent/conftest v0.49.1 h1:4B1v8cE6Ew9FBQsUlYmCR/DqYYAmhkXji8Q1Ao93/pI= +github.com/open-policy-agent/conftest v0.49.1/go.mod h1:AFVxhsTq4g/ECFrMyPpyy14taPrkIYl63weNJvYts4Y= +github.com/open-policy-agent/opa v0.61.0 h1:nhncQ2CAYtQTV/SMBhDDPsCpCQsUW+zO/1j+T5V7oZg= +github.com/open-policy-agent/opa v0.61.0/go.mod h1:7OUuzJnsS9yHf8lw0ApfcbrnaRG1EkN3J2fuuqi4G/E= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI= From 409d52504aaed3dd55c8a753f2f37a7dd8a743dc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 Feb 2024 10:19:57 -0500 Subject: [PATCH 02/10] Bump go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc (#147) Bumps [go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc](https://github.com/open-telemetry/opentelemetry-go) from 1.21.0 to 1.24.0. - [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.21.0...v1.24.0) --- updated-dependencies: - dependency-name: go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 18 +++++++++--------- go.sum | 36 ++++++++++++++++++------------------ 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/go.mod b/go.mod index 76a21a40..285d9a6f 100644 --- a/go.mod +++ b/go.mod @@ -35,12 +35,12 @@ require ( github.com/yannh/kubeconform v0.6.4 github.com/ziflex/lecho/v3 v3.5.0 go.opentelemetry.io/contrib/instrumentation/runtime v0.46.1 - go.opentelemetry.io/otel v1.22.0 + go.opentelemetry.io/otel v1.24.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.45.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0 - go.opentelemetry.io/otel/sdk v1.22.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 + go.opentelemetry.io/otel/sdk v1.24.0 go.opentelemetry.io/otel/sdk/metric v1.22.0 - go.opentelemetry.io/otel/trace v1.22.0 + go.opentelemetry.io/otel/trace v1.24.0 golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 golang.org/x/net v0.20.0 golang.org/x/oauth2 v0.16.0 @@ -141,7 +141,7 @@ require ( github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.2 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-getter v1.7.3 // indirect github.com/hashicorp/go-retryablehttp v0.7.5 // indirect @@ -235,16 +235,16 @@ require ( go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 // indirect - go.opentelemetry.io/otel/metric v1.22.0 // indirect - go.opentelemetry.io/proto/otlp v1.0.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 // indirect + go.opentelemetry.io/otel/metric v1.24.0 // indirect + go.opentelemetry.io/proto/otlp v1.1.0 // indirect go.starlark.net v0.0.0-20231121155337-90ade8b19d09 // indirect go.uber.org/atomic v1.10.0 // indirect go.uber.org/multierr v1.9.0 // indirect golang.org/x/crypto v0.18.0 // indirect golang.org/x/mod v0.14.0 // indirect golang.org/x/sync v0.6.0 // indirect - golang.org/x/sys v0.16.0 // indirect + golang.org/x/sys v0.17.0 // indirect golang.org/x/term v0.16.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.5.0 // indirect diff --git a/go.sum b/go.sum index 864da4c3..bbb4d298 100644 --- a/go.sum +++ b/go.sum @@ -602,8 +602,8 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDa github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.2 h1:dygLcbEBA+t/P7ck6a8AkXv6juQ4cK0RHBoh32jxhHM= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.2/go.mod h1:Ap9RLCIJVtgQg1/BBgVEfypOAySvvlcpcVQkSzJCH4Y= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-getter v1.7.3 h1:bN2+Fw9XPFvOCjB0UOevFIMICZ7G2XSQHzfvLUyOM5E= @@ -1004,25 +1004,25 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 h1:aFJWCqJ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo= go.opentelemetry.io/contrib/instrumentation/runtime v0.46.1 h1:m9ReioVPIffxjJlGNRd0d5poy+9oTro3D+YbiEzUDOc= go.opentelemetry.io/contrib/instrumentation/runtime v0.46.1/go.mod h1:CANkrsXNzqOKXfOomu2zhOmc1/J5UZK9SGjrat6ZCG0= -go.opentelemetry.io/otel v1.22.0 h1:xS7Ku+7yTFvDfDraDIJVpw7XPyuHlB9MCiqqX5mcJ6Y= -go.opentelemetry.io/otel v1.22.0/go.mod h1:eoV4iAi3Ea8LkAEI9+GFT44O6T/D0GWAVFyZVCC6pMI= +go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= +go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.45.0 h1:tfil6di0PoNV7FZdsCS7A5izZoVVQ7AuXtyekbOpG/I= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.45.0/go.mod h1:AKFZIEPOnqB00P63bTjOiah4ZTaRzl1TKwUWpZdYUHI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 h1:cl5P5/GIfFh4t6xyruOgJP5QiA1pw4fYYdv6nc6CBWw= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0/go.mod h1:zgBdWWAu7oEEMC06MMKc5NLbA/1YDXV1sMpSqEeLQLg= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0 h1:tIqheXEFWAZ7O8A7m+J0aPTmpJN3YQ7qetUAdkkkKpk= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0/go.mod h1:nUeKExfxAQVbiVFn32YXpXZZHZ61Cc3s3Rn1pDBGAb0= -go.opentelemetry.io/otel/metric v1.22.0 h1:lypMQnGyJYeuYPhOM/bgjbFM6WE44W1/T45er4d8Hhg= -go.opentelemetry.io/otel/metric v1.22.0/go.mod h1:evJGjVpZv0mQ5QBRJoBF64yMuOf4xCWdXjK8pzFvliY= -go.opentelemetry.io/otel/sdk v1.22.0 h1:6coWHw9xw7EfClIC/+O31R8IY3/+EiRFHevmHafB2Gw= -go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 h1:t6wl9SPayj+c7lEIFgm4ooDBZVb01IhLB4InpomhRw8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0/go.mod h1:iSDOcsnSA5INXzZtwaBPrKp/lWu/V14Dd+llD0oI2EA= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 h1:Mw5xcxMwlqoJd97vwPxA8isEaIoxsta9/Q51+TTJLGE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0/go.mod h1:CQNu9bj7o7mC6U7+CA/schKEYakYXWr79ucDHTMGhCM= +go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= +go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= +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/sdk/metric v1.22.0 h1:ARrRetm1HCVxq0cbnaZQlfwODYJHo3gFL8Z3tSmHBcI= go.opentelemetry.io/otel/sdk/metric v1.22.0/go.mod h1:KjQGeMIDlBNEOo6HvjhxIec1p/69/kULDcp4gr0oLQQ= -go.opentelemetry.io/otel/trace v1.22.0 h1:Hg6pPujv0XG9QaVbGOBVHunyuLcCC3jN7WEhPx83XD0= -go.opentelemetry.io/otel/trace v1.22.0/go.mod h1:RbbHXVqKES9QhzZq/fE5UnOSILqRt40a21sPw2He1xo= +go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= +go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= -go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= +go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI= +go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY= go.starlark.net v0.0.0-20231121155337-90ade8b19d09 h1:hzy3LFnSN8kuQK8h9tHl4ndF6UruMj47OqwqsS+/Ai4= go.starlark.net v0.0.0-20231121155337-90ade8b19d09/go.mod h1:LcLNIzVOMp4oV+uusnpk+VU+SzXaJakUuBjoCSWH5dM= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= @@ -1301,8 +1301,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= From 97d3bba4bb015a626a2f7c6f725a6d6f8a683ee0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 Feb 2024 10:20:12 -0500 Subject: [PATCH 03/10] Bump github.com/argoproj/argo-cd/v2 from 2.9.5 to 2.10.1 (#145) Bumps [github.com/argoproj/argo-cd/v2](https://github.com/argoproj/argo-cd) from 2.9.5 to 2.10.1. - [Release notes](https://github.com/argoproj/argo-cd/releases) - [Changelog](https://github.com/argoproj/argo-cd/blob/master/CHANGELOG.md) - [Commits](https://github.com/argoproj/argo-cd/compare/v2.9.5...v2.10.1) --- updated-dependencies: - dependency-name: github.com/argoproj/argo-cd/v2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 10 +++++----- go.sum | 21 ++++++++++----------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/go.mod b/go.mod index 285d9a6f..ec0a3235 100644 --- a/go.mod +++ b/go.mod @@ -5,8 +5,8 @@ go 1.21 toolchain go1.21.6 require ( - github.com/argoproj/argo-cd/v2 v2.9.5 - github.com/argoproj/gitops-engine v0.7.1-0.20230906152414-b0fffe419a0f + github.com/argoproj/argo-cd/v2 v2.10.1 + github.com/argoproj/gitops-engine v0.7.1-0.20240122213038-792124280fcc github.com/cenkalti/backoff/v4 v4.2.1 github.com/creasty/defaults v1.7.0 github.com/ghodss/yaml v1.0.0 @@ -267,9 +267,9 @@ require ( k8s.io/cli-runtime v0.26.12 // indirect k8s.io/component-base v0.26.12 // indirect k8s.io/component-helpers v0.26.12 // indirect - k8s.io/klog/v2 v2.90.1 // indirect + k8s.io/klog/v2 v2.100.1 // indirect k8s.io/kube-aggregator v0.26.12 // indirect - k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect + k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect k8s.io/kubectl v0.26.12 // indirect k8s.io/kubernetes v1.26.12 // indirect k8s.io/utils v0.0.0-20240102154912-e7106e64919e // indirect @@ -280,7 +280,7 @@ require ( sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/kustomize/api v0.12.1 // indirect sigs.k8s.io/kustomize/kyaml v0.13.9 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.3.0 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/go.sum b/go.sum index bbb4d298..557bbfdb 100644 --- a/go.sum +++ b/go.sum @@ -244,10 +244,10 @@ github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6 github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= -github.com/argoproj/argo-cd/v2 v2.9.5 h1:PdR23x9HVuh4phv+WO/2vvYSMf60BDtbDZNz6+md5Pw= -github.com/argoproj/argo-cd/v2 v2.9.5/go.mod h1:3DQwsQvXosH/lpa7NYI9xBdDl5pdXQijO99ltKuSxQA= -github.com/argoproj/gitops-engine v0.7.1-0.20230906152414-b0fffe419a0f h1:cb2j6HxYJutMBvvQc/Y3EOSL7pcr5pcnP/4MNmYi4xc= -github.com/argoproj/gitops-engine v0.7.1-0.20230906152414-b0fffe419a0f/go.mod h1:/GMN0JuoJUUpnKlNLp2Wn/mfK8sglFsdPn+eoxSddmg= +github.com/argoproj/argo-cd/v2 v2.10.1 h1:VD06GPeoq14Bo7IfiW+EKim3T1C9xaMElVrEtw+zll0= +github.com/argoproj/argo-cd/v2 v2.10.1/go.mod h1:SK1uGZ9xWVzxuyg079MaO6+hz/Oz9wSDkGyT0gEkYSs= +github.com/argoproj/gitops-engine v0.7.1-0.20240122213038-792124280fcc h1:Fv94Mi2WvtvPkEH5WoWC3iy/VoQRLeSsE0hyg0n2UkY= +github.com/argoproj/gitops-engine v0.7.1-0.20240122213038-792124280fcc/go.mod h1:gWE8uROi7hIkWGNAVM+8FWkMfo0vZ03SLx/aFw/DBzg= github.com/argoproj/pkg v0.13.7-0.20230627120311-a4dd357b057e h1:kuLQvJqwwRMQTheT4MFyKVM8Txncu21CHT4yBWUl1Mk= github.com/argoproj/pkg v0.13.7-0.20230627120311-a4dd357b057e/go.mod h1:xBN5PLx2MoK63dmPfMo/PGBvd77K1Y0m/rzZOe4cs1s= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= @@ -1654,8 +1654,6 @@ gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -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/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= @@ -1702,12 +1700,13 @@ k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.5.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/klog/v2 v2.90.1 h1:m4bYOKall2MmOiRaR1J+We67Do7vm9KiQVlT96lnHUw= -k8s.io/klog/v2 v2.90.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= +k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-aggregator v0.26.12 h1:fbJN1K4kKeYylLrVXFse512pVmOYKXcw+LHpoQJTzJc= k8s.io/kube-aggregator v0.26.12/go.mod h1:exHB14iw5vzMqcm4kPGwsKmqow0BytE/xwQVxl2fhmI= -k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+OGxg8HsuBr/5f6tVAjDu6E= k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4= +k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f h1:2kWPakN3i/k81b0gvD5C5FJ2kxm1WrQFanWchyKuqGg= +k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f/go.mod h1:byini6yhqGC14c3ebc/QwanvYwhuMWF6yz2F8uwW8eg= k8s.io/kubectl v0.26.12 h1:wEPsNNHT4THWxoIYgJVhx6ok7lVnbJdJW0Pjl9QS6wc= k8s.io/kubectl v0.26.12/go.mod h1:0O8moTv0xcYVuimWIaxRJLQY6h+e7O+cANf5jhEpW1o= k8s.io/kubernetes v1.26.12 h1:Y6lGeOYCp40THMEZEUhECno31grjjEzTSCGzwDFgoX8= @@ -1735,8 +1734,8 @@ sigs.k8s.io/kustomize/api v0.12.1/go.mod h1:y3JUhimkZkR6sbLNwfJHxvo1TCLwuwm14sCY sigs.k8s.io/kustomize/kyaml v0.13.9 h1:Qz53EAaFFANyNgyOEJbT/yoIHygK40/ZcvU3rgry2Tk= sigs.k8s.io/kustomize/kyaml v0.13.9/go.mod h1:QsRbD0/KcU+wdk0/L0fIp2KLnohkVzs6fQ85/nOXac4= sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= -sigs.k8s.io/structured-merge-diff/v4 v4.3.0 h1:UZbZAZfX0wV2zr7YZorDz6GXROfDFj6LvqCRm4VUVKk= -sigs.k8s.io/structured-merge-diff/v4 v4.3.0/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= From 49fc58db846ac10232d2bd80d4d0990df2364fb4 Mon Sep 17 00:00:00 2001 From: Joseph Lombrozo Date: Mon, 18 Mar 2024 10:24:26 -0400 Subject: [PATCH 04/10] add support for diffing apps in external repositories (#150) --- cmd/container.go | 20 +- cmd/controller_cmd.go | 18 +- cmd/process.go | 20 +- cmd/processors.go | 45 +++ cmd/root.go | 1 + docs/usage.md | 1 + go.mod | 3 +- go.sum | 1 + pkg/affected_apps/argocd_matcher.go | 10 +- pkg/affected_apps/argocd_matcher_test.go | 9 +- pkg/aisummary/diff_summary.go | 4 +- pkg/aisummary/openai_client.go | 3 +- pkg/app_watcher/app_watcher.go | 5 +- pkg/app_watcher/app_watcher_test.go | 2 +- pkg/appdir/repoUrl.go | 40 -- pkg/appdir/repoUrl_test.go | 66 ---- pkg/appdir/vcstoargomap.go | 16 +- pkg/appdir/vcstoargumap_test.go | 4 +- pkg/argo_client/applications.go | 14 +- pkg/argo_client/manifests.go | 17 +- pkg/{ => checks}/diff/ai_summary.go | 10 +- pkg/checks/diff/check.go | 19 + pkg/{ => checks}/diff/diff.go | 27 +- pkg/checks/kubeconform/check.go | 15 + .../kubeconform}/validate.go | 62 ++- .../kubeconform}/validate_test.go | 12 +- pkg/checks/preupgrade/check.go | 12 + pkg/{kubepug => checks/preupgrade}/kubepug.go | 16 +- pkg/checks/rego/check.go | 44 +++ pkg/{conftest => checks/rego}/conftest.go | 33 +- pkg/checks/types.go | 38 ++ pkg/config/config.go | 2 + pkg/container/main.go | 11 +- pkg/events/check.go | 363 +++++++++--------- pkg/events/check_test.go | 14 +- pkg/events/metrics.go | 2 + pkg/events/runner.go | 74 ++-- pkg/git/manager.go | 57 +++ pkg/git/repo.go | 262 +++++++++++++ pkg/git/repo_test.go | 89 +++++ pkg/local/gitRepos.go | 97 ----- pkg/msg/message.go | 16 +- pkg/msg/message_test.go | 22 +- pkg/repoUrl.go | 43 +++ pkg/repoUrl_test.go | 72 ++++ pkg/server/hook_handler.go | 106 ++--- pkg/server/server.go | 10 +- pkg/server/server_test.go | 3 +- pkg/vcs/github_client/client.go | 61 +-- pkg/vcs/github_client/client_test.go | 42 ++ pkg/vcs/github_client/message.go | 41 +- pkg/vcs/gitlab_client/client.go | 29 +- pkg/vcs/gitlab_client/merge.go | 6 +- pkg/vcs/gitlab_client/message.go | 23 +- pkg/vcs/gitlab_client/project.go | 3 +- pkg/vcs/gitlab_client/status.go | 12 +- pkg/vcs/repo.go | 289 +------------- pkg/vcs/repo_test.go | 80 ---- pkg/vcs/types.go | 14 +- 59 files changed, 1297 insertions(+), 1133 deletions(-) create mode 100644 cmd/processors.go delete mode 100644 pkg/appdir/repoUrl.go delete mode 100644 pkg/appdir/repoUrl_test.go rename pkg/{ => checks}/diff/ai_summary.go (71%) create mode 100644 pkg/checks/diff/check.go rename pkg/{ => checks}/diff/diff.go (94%) create mode 100644 pkg/checks/kubeconform/check.go rename pkg/{validate => checks/kubeconform}/validate.go (63%) rename pkg/{validate => checks/kubeconform}/validate_test.go (80%) create mode 100644 pkg/checks/preupgrade/check.go rename pkg/{kubepug => checks/preupgrade}/kubepug.go (93%) create mode 100644 pkg/checks/rego/check.go rename pkg/{conftest => checks/rego}/conftest.go (85%) create mode 100644 pkg/checks/types.go create mode 100644 pkg/git/manager.go create mode 100644 pkg/git/repo.go create mode 100644 pkg/git/repo_test.go delete mode 100644 pkg/local/gitRepos.go create mode 100644 pkg/repoUrl.go create mode 100644 pkg/repoUrl_test.go create mode 100644 pkg/vcs/github_client/client_test.go delete mode 100644 pkg/vcs/repo_test.go diff --git a/cmd/container.go b/cmd/container.go index bf977b0f..bc45b9d9 100644 --- a/cmd/container.go +++ b/cmd/container.go @@ -15,13 +15,14 @@ import ( "github.com/zapier/kubechecks/pkg/vcs/gitlab_client" ) -func newContainer(ctx context.Context, cfg config.ServerConfig) (container.Container, error) { +func newContainer(ctx context.Context, cfg config.ServerConfig, watchApps bool) (container.Container, error) { var err error var ctr = container.Container{ Config: cfg, } + // create vcs client switch cfg.VcsType { case "gitlab": ctr.VcsClient, err = gitlab_client.CreateGitlabClient(cfg) @@ -34,24 +35,29 @@ func newContainer(ctx context.Context, cfg config.ServerConfig) (container.Conta return ctr, errors.Wrap(err, "failed to create vcs client") } + // create argo client if ctr.ArgoClient, err = argo_client.NewArgoClient(cfg); err != nil { return ctr, errors.Wrap(err, "failed to create argo client") } - vcsToArgoMap := appdir.NewVcsToArgoMap() + // create vcs to argo map + vcsToArgoMap := appdir.NewVcsToArgoMap(ctr.VcsClient.Username()) ctr.VcsToArgoMap = vcsToArgoMap + // watch app modifications, if necessary if cfg.MonitorAllApplications { if err = buildAppsMap(ctx, ctr.ArgoClient, ctr.VcsToArgoMap); err != nil { return ctr, errors.Wrap(err, "failed to build apps map") } - ctr.ApplicationWatcher, err = app_watcher.NewApplicationWatcher(vcsToArgoMap) - if err != nil { - return ctr, errors.Wrap(err, "failed to create watch applications") - } + if watchApps { + ctr.ApplicationWatcher, err = app_watcher.NewApplicationWatcher(vcsToArgoMap, cfg) + if err != nil { + return ctr, errors.Wrap(err, "failed to create watch applications") + } - go ctr.ApplicationWatcher.Run(ctx, 1) + go ctr.ApplicationWatcher.Run(ctx, 1) + } } return ctr, nil diff --git a/cmd/controller_cmd.go b/cmd/controller_cmd.go index 35e46582..72b0c6b1 100644 --- a/cmd/controller_cmd.go +++ b/cmd/controller_cmd.go @@ -13,11 +13,12 @@ import ( "github.com/spf13/viper" "github.com/zapier/kubechecks/pkg" + "github.com/zapier/kubechecks/pkg/checks" "github.com/zapier/kubechecks/pkg/config" "github.com/zapier/kubechecks/pkg/container" "github.com/zapier/kubechecks/pkg/events" + "github.com/zapier/kubechecks/pkg/git" "github.com/zapier/kubechecks/pkg/server" - "github.com/zapier/kubechecks/pkg/vcs" "github.com/zapier/kubechecks/telemetry" ) @@ -40,11 +41,16 @@ var ControllerCmd = &cobra.Command{ log.Fatal().Err(err).Msg("failed to parse configuration") } - ctr, err := newContainer(ctx, cfg) + ctr, err := newContainer(ctx, cfg, true) if err != nil { log.Fatal().Err(err).Msg("failed to create container") } + processors, err := getProcessors(ctr) + if err != nil { + log.Fatal().Err(err).Msg("failed to create processors") + } + t, err := initTelemetry(ctx, cfg) if err != nil { log.Panic().Err(err).Msg("Failed to initialize telemetry") @@ -57,7 +63,7 @@ var ControllerCmd = &cobra.Command{ } log.Info().Msgf("starting web server") - startWebserver(ctx, ctr) + startWebserver(ctx, ctr, processors) log.Info().Msgf("listening for requests") waitForShutdown() @@ -74,13 +80,13 @@ func initTelemetry(ctx context.Context, cfg config.ServerConfig) (*telemetry.Ope ) } -func startWebserver(ctx context.Context, ctr container.Container) { - srv := server.NewServer(ctr) +func startWebserver(ctx context.Context, ctr container.Container, processors []checks.ProcessorEntry) { + srv := server.NewServer(ctr, processors) go srv.Start(ctx) } func initializeGit(ctr container.Container) error { - if err := vcs.InitializeGitSettings(ctr.Config, ctr.VcsClient); err != nil { + if err := git.SetCredentials(ctr.Config, ctr.VcsClient); err != nil { return err } diff --git a/cmd/process.go b/cmd/process.go index ad7d6511..208ab847 100644 --- a/cmd/process.go +++ b/cmd/process.go @@ -15,23 +15,33 @@ var processCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { ctx := cmd.Context() - cfg := config.ServerConfig{ - UrlPrefix: "--unused--", - WebhookSecret: "--unused--", + cfg, err := config.New() + if err != nil { + log.Fatal().Err(err).Msg("failed to generate config") } - ctr, err := newContainer(ctx, cfg) + ctr, err := newContainer(ctx, cfg, false) if err != nil { log.Fatal().Err(err).Msg("failed to create container") } + log.Info().Msg("initializing git settings") + if err = initializeGit(ctr); err != nil { + log.Fatal().Err(err).Msg("failed to initialize git settings") + } + repo, err := ctr.VcsClient.LoadHook(ctx, args[0]) if err != nil { log.Fatal().Err(err).Msg("failed to load hook") return } - server.ProcessCheckEvent(ctx, repo, cfg, ctr) + processors, err := getProcessors(ctr) + if err != nil { + log.Fatal().Err(err).Msg("failed to create processors") + } + + server.ProcessCheckEvent(ctx, repo, ctr, processors) }, } diff --git a/cmd/processors.go b/cmd/processors.go new file mode 100644 index 00000000..d4da71cf --- /dev/null +++ b/cmd/processors.go @@ -0,0 +1,45 @@ +package cmd + +import ( + "github.com/pkg/errors" + + "github.com/zapier/kubechecks/pkg/checks" + "github.com/zapier/kubechecks/pkg/checks/diff" + "github.com/zapier/kubechecks/pkg/checks/kubeconform" + "github.com/zapier/kubechecks/pkg/checks/preupgrade" + "github.com/zapier/kubechecks/pkg/checks/rego" + "github.com/zapier/kubechecks/pkg/container" +) + +func getProcessors(ctr container.Container) ([]checks.ProcessorEntry, error) { + var procs []checks.ProcessorEntry + + procs = append(procs, checks.ProcessorEntry{ + Name: "validating app against schema", + Processor: kubeconform.Check, + }) + + procs = append(procs, checks.ProcessorEntry{ + Name: "generating diff for app", + Processor: diff.Check, + }) + + procs = append(procs, checks.ProcessorEntry{ + Name: "running pre-upgrade check", + Processor: preupgrade.Check, + }) + + if ctr.Config.EnableConfTest { + checker, err := rego.NewChecker(ctr.Config) + if err != nil { + return nil, errors.Wrap(err, "failed to create rego checker") + } + + procs = append(procs, checks.ProcessorEntry{ + Name: "validation policy", + Processor: checker.Check, + }) + } + + return procs, nil +} diff --git a/cmd/root.go b/cmd/root.go index 5ed55abf..fd0b6a39 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -58,6 +58,7 @@ func init() { stringFlag(flags, "argocd-api-token", "ArgoCD API token.") stringFlag(flags, "argocd-api-server-addr", "ArgoCD API Server Address.", newStringOpts().withDefault("argocd-server")) boolFlag(flags, "argocd-api-insecure", "Enable to use insecure connections to the ArgoCD API server.") + stringFlag(flags, "kubernetes-config", "Path to your kubernetes config file, used to monitor applications.") stringFlag(flags, "otel-collector-port", "The OpenTelemetry collector port.") stringFlag(flags, "otel-collector-host", "The OpenTelemetry collector host.") diff --git a/docs/usage.md b/docs/usage.md index ec819caa..aff7846a 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -42,6 +42,7 @@ The full list of supported environment variables is described below: |`KUBECHECKS_ENABLE_CONFTEST`|Set to true to enable conftest policy checking of manifests.|`false`| |`KUBECHECKS_ENSURE_WEBHOOKS`|Ensure that webhooks are created in repositories referenced by argo.|`false`| |`KUBECHECKS_FALLBACK_K8S_VERSION`|Fallback target Kubernetes version for schema / upgrade checks.|`1.23.0`| +|`KUBECHECKS_KUBERNETES_CONFIG`|Path to your kubernetes config file, used to monitor applications.|| |`KUBECHECKS_LABEL_FILTER`|(Optional) If set, The label that must be set on an MR (as "kubechecks:") for kubechecks to process the merge request webhook.|| |`KUBECHECKS_LOG_LEVEL`|Set the log output level. One of error, warn, info, debug, trace.|`info`| |`KUBECHECKS_MONITOR_ALL_APPLICATIONS`|Monitor all applications in argocd automatically.|`false`| diff --git a/go.mod b/go.mod index ec0a3235..57c0d70e 100644 --- a/go.mod +++ b/go.mod @@ -26,6 +26,7 @@ require ( github.com/rs/zerolog v1.31.0 github.com/sashabaranov/go-openai v1.19.3 github.com/shurcooL/githubv4 v0.0.0-20231126234147-1cffa1f02456 + github.com/sirupsen/logrus v1.9.3 github.com/spf13/cobra v1.8.0 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.18.2 @@ -47,6 +48,7 @@ require ( google.golang.org/grpc v1.61.1 gopkg.in/dealancer/validate.v2 v2.1.0 gopkg.in/yaml.v3 v3.0.1 + gotest.tools/v3 v3.0.3 k8s.io/apimachinery v0.26.12 k8s.io/client-go v0.26.12 ) @@ -209,7 +211,6 @@ require ( github.com/sergi/go-diff v1.3.1 // indirect github.com/shteou/go-ignore v0.3.1 // indirect github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466 // indirect - github.com/sirupsen/logrus v1.9.3 // indirect github.com/skeema/knownhosts v1.2.1 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spdx/tools-golang v0.5.3 // indirect diff --git a/go.sum b/go.sum index 557bbfdb..d794def1 100644 --- a/go.sum +++ b/go.sum @@ -1355,6 +1355,7 @@ golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= diff --git a/pkg/affected_apps/argocd_matcher.go b/pkg/affected_apps/argocd_matcher.go index 3a765002..679ab9a2 100644 --- a/pkg/affected_apps/argocd_matcher.go +++ b/pkg/affected_apps/argocd_matcher.go @@ -8,16 +8,16 @@ import ( "github.com/zapier/kubechecks/pkg/appdir" "github.com/zapier/kubechecks/pkg/container" - "github.com/zapier/kubechecks/pkg/vcs" + "github.com/zapier/kubechecks/pkg/git" ) type ArgocdMatcher struct { appsDirectory *appdir.AppDirectory } -func NewArgocdMatcher(vcsToArgoMap container.VcsToArgoMap, repo *vcs.Repo, repoPath string) (*ArgocdMatcher, error) { +func NewArgocdMatcher(vcsToArgoMap container.VcsToArgoMap, repo *git.Repo) (*ArgocdMatcher, error) { repoApps := getArgocdApps(vcsToArgoMap, repo) - kustomizeAppFiles := getKustomizeApps(vcsToArgoMap, repo, repoPath) + kustomizeAppFiles := getKustomizeApps(vcsToArgoMap, repo, repo.Directory) appDirectory := appdir.NewAppDirectory(). Union(repoApps). @@ -36,7 +36,7 @@ func logCounts(repoApps *appdir.AppDirectory) { } } -func getKustomizeApps(vcsToArgoMap container.VcsToArgoMap, repo *vcs.Repo, repoPath string) *appdir.AppDirectory { +func getKustomizeApps(vcsToArgoMap container.VcsToArgoMap, repo *git.Repo, repoPath string) *appdir.AppDirectory { log.Debug().Msgf("creating fs for %s", repoPath) fs := os.DirFS(repoPath) log.Debug().Msg("following kustomize apps") @@ -46,7 +46,7 @@ func getKustomizeApps(vcsToArgoMap container.VcsToArgoMap, repo *vcs.Repo, repoP return kustomizeAppFiles } -func getArgocdApps(vcsToArgoMap container.VcsToArgoMap, repo *vcs.Repo) *appdir.AppDirectory { +func getArgocdApps(vcsToArgoMap container.VcsToArgoMap, repo *git.Repo) *appdir.AppDirectory { log.Debug().Msgf("looking for %s repos", repo.CloneURL) repoApps := vcsToArgoMap.GetAppsInRepo(repo.CloneURL) diff --git a/pkg/affected_apps/argocd_matcher_test.go b/pkg/affected_apps/argocd_matcher_test.go index 97a64d5b..966db648 100644 --- a/pkg/affected_apps/argocd_matcher_test.go +++ b/pkg/affected_apps/argocd_matcher_test.go @@ -7,20 +7,19 @@ import ( "github.com/stretchr/testify/require" "github.com/zapier/kubechecks/pkg/appdir" - "github.com/zapier/kubechecks/pkg/vcs" + "github.com/zapier/kubechecks/pkg/git" ) func TestCreateNewMatcherWithNilVcsMap(t *testing.T) { // setup var ( - repo vcs.Repo - path string + repo git.Repo - vcsMap = appdir.NewVcsToArgoMap() + vcsMap = appdir.NewVcsToArgoMap("vcs-username") ) // run test - matcher, err := NewArgocdMatcher(vcsMap, &repo, path) + matcher, err := NewArgocdMatcher(vcsMap, &repo) require.NoError(t, err) // verify results diff --git a/pkg/aisummary/diff_summary.go b/pkg/aisummary/diff_summary.go index bcf70cc1..996927fc 100644 --- a/pkg/aisummary/diff_summary.go +++ b/pkg/aisummary/diff_summary.go @@ -10,9 +10,11 @@ import ( "github.com/zapier/kubechecks/telemetry" ) +var tracer = otel.Tracer("pkg/aisummary") + // SummarizeDiff uses ChatGPT to summarize changes to a Kubernetes application. func (c *OpenAiClient) SummarizeDiff(ctx context.Context, appName string, manifests []string, diff string) (string, error) { - ctx, span := otel.Tracer("Kubechecks").Start(ctx, "SummarizeDiff") + ctx, span := tracer.Start(ctx, "SummarizeDiff") defer span.End() model := openai.GPT4Turbo0125 diff --git a/pkg/aisummary/openai_client.go b/pkg/aisummary/openai_client.go index b4f5d64a..2ad5d58c 100644 --- a/pkg/aisummary/openai_client.go +++ b/pkg/aisummary/openai_client.go @@ -10,7 +10,6 @@ import ( "github.com/cenkalti/backoff/v4" "github.com/rs/zerolog/log" "github.com/sashabaranov/go-openai" - "go.opentelemetry.io/otel" ) type OpenAiClient struct { @@ -60,7 +59,7 @@ func createCompletionRequest(model, appName string, prompt string, content strin } func (c *OpenAiClient) makeCompletionRequestWithBackoff(ctx context.Context, req openai.ChatCompletionRequest) (openai.ChatCompletionResponse, error) { - ctx, span := otel.Tracer("Kubechecks").Start(ctx, "MakeCompletionRequestWithBackoff") + ctx, span := tracer.Start(ctx, "MakeCompletionRequestWithBackoff") defer span.End() // Lets setup backoff logic to retry this request for 1 minute bOff := backoff.NewExponentialBackOff() diff --git a/pkg/app_watcher/app_watcher.go b/pkg/app_watcher/app_watcher.go index 997a55d8..10c28d35 100644 --- a/pkg/app_watcher/app_watcher.go +++ b/pkg/app_watcher/app_watcher.go @@ -17,6 +17,7 @@ import ( "k8s.io/client-go/tools/cache" "github.com/zapier/kubechecks/pkg/appdir" + "github.com/zapier/kubechecks/pkg/config" ) // ApplicationWatcher is the controller that watches ArgoCD Application resources via the Kubernetes API @@ -29,9 +30,9 @@ type ApplicationWatcher struct { } // NewApplicationWatcher creates new instance of ApplicationWatcher. -func NewApplicationWatcher(vcsToArgoMap appdir.VcsToArgoMap) (*ApplicationWatcher, error) { +func NewApplicationWatcher(vcsToArgoMap appdir.VcsToArgoMap, cfg config.ServerConfig) (*ApplicationWatcher, error) { // this assumes kubechecks is running inside the cluster - kubeCfg, err := clientcmd.BuildConfigFromFlags("", "") + kubeCfg, err := clientcmd.BuildConfigFromFlags("", cfg.KubernetesConfig) if err != nil { log.Fatal().Msgf("Error building kubeconfig: %s", err.Error()) } diff --git a/pkg/app_watcher/app_watcher_test.go b/pkg/app_watcher/app_watcher_test.go index b46c8025..aea93851 100644 --- a/pkg/app_watcher/app_watcher_test.go +++ b/pkg/app_watcher/app_watcher_test.go @@ -31,7 +31,7 @@ func initTestObjects() *ApplicationWatcher { clientset := appclientsetfake.NewSimpleClientset(testApp1, testApp2) ctrl := &ApplicationWatcher{ applicationClientset: clientset, - vcsToArgoMap: appdir.NewVcsToArgoMap(), + vcsToArgoMap: appdir.NewVcsToArgoMap("vcs-username"), } appInformer, appLister := ctrl.newApplicationInformerAndLister(time.Second * 1) diff --git a/pkg/appdir/repoUrl.go b/pkg/appdir/repoUrl.go deleted file mode 100644 index 68b8182c..00000000 --- a/pkg/appdir/repoUrl.go +++ /dev/null @@ -1,40 +0,0 @@ -package appdir - -import ( - "fmt" - "net/url" - "strings" - - giturls "github.com/whilp/git-urls" -) - -type RepoURL struct { - Host, Path string -} - -func (r RepoURL) CloneURL() string { - return fmt.Sprintf("git@%s:%s", r.Host, r.Path) -} - -func buildNormalizedRepoUrl(host, path string) RepoURL { - path = strings.TrimPrefix(path, "/") - path = strings.TrimSuffix(path, ".git") - return RepoURL{host, path} -} - -func NormalizeRepoUrl(s string) (RepoURL, error) { - var parser func(string) (*url.URL, error) - - if strings.HasPrefix(s, "http") { - parser = url.Parse - } else { - parser = giturls.Parse - } - - r, err := parser(s) - if err != nil { - return RepoURL{}, err - } - - return buildNormalizedRepoUrl(r.Host, r.Path), nil -} diff --git a/pkg/appdir/repoUrl_test.go b/pkg/appdir/repoUrl_test.go deleted file mode 100644 index ece78042..00000000 --- a/pkg/appdir/repoUrl_test.go +++ /dev/null @@ -1,66 +0,0 @@ -package appdir - -import ( - "fmt" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/stretchr/testify/assert" -) - -func TestNormalizeStrings(t *testing.T) { - testCases := []struct { - input string - expected RepoURL - }{ - { - input: "git@github.com:one/two", - expected: RepoURL{"github.com", "one/two"}, - }, - { - input: "https://github.com/one/two", - expected: RepoURL{"github.com", "one/two"}, - }, - { - input: "git@gitlab.com:djeebus/helm-test.git", - expected: RepoURL{"gitlab.com", "djeebus/helm-test"}, - }, - { - input: "https://gitlab.com/djeebus/helm-test.git", - expected: RepoURL{"gitlab.com", "djeebus/helm-test"}, - }, - } - - for _, tc := range testCases { - t.Run(fmt.Sprintf("case %s", tc.input), func(t *testing.T) { - actual, err := NormalizeRepoUrl(tc.input) - require.NoError(t, err) - assert.Equal(t, tc.expected, actual) - }) - } -} - -// TestBuildNormalizedRepoURL tests the buildNormalizedRepoUrl function. -func TestBuildNormalizedRepoURL(t *testing.T) { - tests := []struct { - host string - path string - expected RepoURL - }{ - { - host: "example.com", - path: "/repository.git", - expected: RepoURL{ - Host: "example.com", - Path: "repository", - }, - }, - // ... additional test cases - } - - for _, tc := range tests { - result := buildNormalizedRepoUrl(tc.host, tc.path) - assert.Equal(t, tc.expected, result) - } -} diff --git a/pkg/appdir/vcstoargomap.go b/pkg/appdir/vcstoargomap.go index 4fd02046..3abad604 100644 --- a/pkg/appdir/vcstoargomap.go +++ b/pkg/appdir/vcstoargomap.go @@ -5,24 +5,28 @@ import ( "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" "github.com/rs/zerolog/log" + + "github.com/zapier/kubechecks/pkg" ) type VcsToArgoMap struct { - appDirByRepo map[RepoURL]*AppDirectory + username string + appDirByRepo map[pkg.RepoURL]*AppDirectory } -func NewVcsToArgoMap() VcsToArgoMap { +func NewVcsToArgoMap(vcsUsername string) VcsToArgoMap { return VcsToArgoMap{ - appDirByRepo: make(map[RepoURL]*AppDirectory), + username: vcsUsername, + appDirByRepo: make(map[pkg.RepoURL]*AppDirectory), } } -func (v2a VcsToArgoMap) GetMap() map[RepoURL]*AppDirectory { +func (v2a VcsToArgoMap) GetMap() map[pkg.RepoURL]*AppDirectory { return v2a.appDirByRepo } func (v2a VcsToArgoMap) GetAppsInRepo(repoCloneUrl string) *AppDirectory { - repoUrl, err := NormalizeRepoUrl(repoCloneUrl) + repoUrl, _, err := pkg.NormalizeRepoUrl(repoCloneUrl) if err != nil { log.Warn().Err(err).Msgf("failed to parse %s", repoCloneUrl) } @@ -92,7 +96,7 @@ func (v2a VcsToArgoMap) GetVcsRepos() []string { var repos []string for key := range v2a.appDirByRepo { - repos = append(repos, key.CloneURL()) + repos = append(repos, key.CloneURL(v2a.username)) } return repos diff --git a/pkg/appdir/vcstoargumap_test.go b/pkg/appdir/vcstoargumap_test.go index aacb21b0..611f8fa1 100644 --- a/pkg/appdir/vcstoargumap_test.go +++ b/pkg/appdir/vcstoargumap_test.go @@ -12,7 +12,7 @@ import ( func TestAddApp(t *testing.T) { // Setup your mocks and expected calls here. - v2a := NewVcsToArgoMap() // This would be mocked accordingly. + v2a := NewVcsToArgoMap("vcs-username") // This would be mocked accordingly. app1 := &v1alpha1.Application{ ObjectMeta: metav1.ObjectMeta{Name: "test-app-1", Namespace: "default"}, Spec: v1alpha1.ApplicationSpec{ @@ -48,7 +48,7 @@ func TestAddApp(t *testing.T) { func TestDeleteApp(t *testing.T) { // Setup your mocks and expected calls here. - v2a := NewVcsToArgoMap() // This would be mocked accordingly. + v2a := NewVcsToArgoMap("vcs-username") // This would be mocked accordingly. app1 := &v1alpha1.Application{ ObjectMeta: metav1.ObjectMeta{Name: "test-app-1", Namespace: "default"}, Spec: v1alpha1.ApplicationSpec{ diff --git a/pkg/argo_client/applications.go b/pkg/argo_client/applications.go index 4203b383..e047c678 100644 --- a/pkg/argo_client/applications.go +++ b/pkg/argo_client/applications.go @@ -7,21 +7,23 @@ import ( "github.com/argoproj/argo-cd/v2/pkg/apiclient/applicationset" "github.com/rs/zerolog/log" + "go.opentelemetry.io/otel" "github.com/argoproj/argo-cd/v2/pkg/apiclient/application" "github.com/argoproj/argo-cd/v2/pkg/apiclient/cluster" "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" "github.com/pkg/errors" - "go.opentelemetry.io/otel" "github.com/zapier/kubechecks/telemetry" ) +var tracer = otel.Tracer("pkg/argo_client") + // GetApplicationByName takes a context and a name, then queries the Argo Application client to retrieve the Application with the specified name. // It returns the found Application and any error encountered during the process. // If successful, the Application client connection is closed before returning. func (argo *ArgoClient) GetApplicationByName(ctx context.Context, name string) (*v1alpha1.Application, error) { - ctx, span := otel.Tracer("Kubechecks").Start(ctx, "GetApplicationByName") + ctx, span := tracer.Start(ctx, "GetApplicationByName") defer span.End() closer, appClient := argo.GetApplicationClient() @@ -40,7 +42,7 @@ func (argo *ArgoClient) GetApplicationByName(ctx context.Context, name string) ( // and returns the Kubernetes version of the destination cluster where the specified application is running. // It returns an error if the application or cluster information cannot be retrieved. func (argo *ArgoClient) GetKubernetesVersionByApplication(ctx context.Context, app v1alpha1.Application) (string, error) { - ctx, span := otel.Tracer("Kubechecks").Start(ctx, "GetKubernetesVersionByApplicationName") + ctx, span := tracer.Start(ctx, "GetKubernetesVersionByApplicationName") defer span.End() // Get destination cluster @@ -77,7 +79,7 @@ func (argo *ArgoClient) GetKubernetesVersionByApplication(ctx context.Context, a // It returns the found ApplicationList and any error encountered during the process. // If successful, the Application client connection is closed before returning. func (argo *ArgoClient) GetApplicationsByLabels(ctx context.Context, labels string) (*v1alpha1.ApplicationList, error) { - ctx, span := otel.Tracer("Kubechecks").Start(ctx, "GetApplicationsByLabels") + ctx, span := tracer.Start(ctx, "GetApplicationsByLabels") defer span.End() closer, appClient := argo.GetApplicationClient() @@ -100,7 +102,7 @@ func (argo *ArgoClient) GetApplicationsByAppset(ctx context.Context, name string } func (argo *ArgoClient) GetApplications(ctx context.Context) (*v1alpha1.ApplicationList, error) { - ctx, span := otel.Tracer("Kubechecks").Start(ctx, "GetApplications") + ctx, span := tracer.Start(ctx, "GetApplications") defer span.End() closer, appClient := argo.GetApplicationClient() @@ -115,7 +117,7 @@ func (argo *ArgoClient) GetApplications(ctx context.Context) (*v1alpha1.Applicat } func (argo *ArgoClient) GetApplicationSets(ctx context.Context) (*v1alpha1.ApplicationSetList, error) { - ctx, span := otel.Tracer("Kubechecks").Start(ctx, "GetApplications") + ctx, span := tracer.Start(ctx, "GetApplications") defer span.End() closer, appClient := argo.GetApplicationSetClient() diff --git a/pkg/argo_client/manifests.go b/pkg/argo_client/manifests.go index be0e9320..3ecd69a5 100644 --- a/pkg/argo_client/manifests.go +++ b/pkg/argo_client/manifests.go @@ -14,16 +14,15 @@ import ( "github.com/ghodss/yaml" "github.com/pkg/errors" "github.com/rs/zerolog/log" - "go.opentelemetry.io/otel" "k8s.io/apimachinery/pkg/api/resource" "github.com/zapier/kubechecks/telemetry" ) -func GetManifestsLocal(ctx context.Context, argoClient *ArgoClient, name string, tempRepoDir string, changedAppFilePath string, app argoappv1.Application) ([]string, error) { +func GetManifestsLocal(ctx context.Context, argoClient *ArgoClient, name, tempRepoDir, changedAppFilePath string, app argoappv1.Application) ([]string, error) { var err error - ctx, span := otel.Tracer("Kubechecks").Start(ctx, "GetManifestsLocal") + ctx, span := tracer.Start(ctx, "GetManifestsLocal") defer span.End() log.Debug().Str("name", name).Msg("GetManifestsLocal") @@ -58,18 +57,6 @@ func GetManifestsLocal(ctx context.Context, argoClient *ArgoClient, name string, return nil, errors.Wrap(err, "failed to get settings") } - // Code is commented out until Argo fixes the server side manifest generation - /* - localIncludes := []string{"*.yaml", "*.json", "*.yml"} - // sends files to argocd to generate a diff based on them. - - client, err := appClient.GetManifestsWithFiles(context.Background(), grpc_retry.Disable()) - errors.CheckError(err) - - err = manifeststream.SendApplicationManifestQueryWithFiles(context.Background(), client, appName, appNamespace, changedFilePath, localIncludes) - errors.CheckError(err) - */ - source := app.Spec.GetSource() log.Debug().Str("name", name).Msg("generating diff for application...") diff --git a/pkg/diff/ai_summary.go b/pkg/checks/diff/ai_summary.go similarity index 71% rename from pkg/diff/ai_summary.go rename to pkg/checks/diff/ai_summary.go index b0a123bb..1efa190d 100644 --- a/pkg/diff/ai_summary.go +++ b/pkg/checks/diff/ai_summary.go @@ -12,8 +12,10 @@ import ( "github.com/zapier/kubechecks/telemetry" ) -func AIDiffSummary(ctx context.Context, mrNote *msg.Message, cfg config.ServerConfig, name string, manifests []string, diff string) { - ctx, span := otel.Tracer("Kubechecks").Start(ctx, "AIDiffSummary") +var tracer = otel.Tracer("pkg/diff") + +func aiDiffSummary(ctx context.Context, mrNote *msg.Message, cfg config.ServerConfig, name string, manifests []string, diff string) { + ctx, span := tracer.Start(ctx, "aiDiffSummary") defer span.End() log.Debug().Str("name", name).Msg("generating ai diff summary for application...") @@ -25,13 +27,13 @@ func AIDiffSummary(ctx context.Context, mrNote *msg.Message, cfg config.ServerCo if err != nil { telemetry.SetError(span, err, "OpenAI SummarizeDiff") log.Error().Err(err).Msg("failed to summarize diff") - cr := msg.CheckResult{State: pkg.StateNone, Summary: "failed to summarize diff", Details: err.Error()} + cr := msg.Result{State: pkg.StateNone, Summary: "failed to summarize diff", Details: err.Error()} mrNote.AddToAppMessage(ctx, name, cr) return } if aiSummary != "" { - cr := msg.CheckResult{State: pkg.StateNone, Summary: "Show AI Summary Diff", Details: aiSummary} + cr := msg.Result{State: pkg.StateNone, Summary: "Show AI Summary Diff", Details: aiSummary} mrNote.AddToAppMessage(ctx, name, cr) } } diff --git a/pkg/checks/diff/check.go b/pkg/checks/diff/check.go new file mode 100644 index 00000000..0b7a1d50 --- /dev/null +++ b/pkg/checks/diff/check.go @@ -0,0 +1,19 @@ +package diff + +import ( + "context" + + "github.com/zapier/kubechecks/pkg/checks" + "github.com/zapier/kubechecks/pkg/msg" +) + +func Check(ctx context.Context, request checks.Request) (msg.Result, error) { + cr, rawDiff, err := getDiff(ctx, request.JsonManifests, request.App, request.Container, request.QueueApp, request.RemoveApp) + if err != nil { + return cr, err + } + + aiDiffSummary(ctx, request.Note, request.Container.Config, request.AppName, request.JsonManifests, rawDiff) + + return cr, nil +} diff --git a/pkg/diff/diff.go b/pkg/checks/diff/diff.go similarity index 94% rename from pkg/diff/diff.go rename to pkg/checks/diff/diff.go index af77e6d2..c81a1891 100644 --- a/pkg/diff/diff.go +++ b/pkg/checks/diff/diff.go @@ -21,7 +21,6 @@ import ( "github.com/go-logr/zerologr" "github.com/pmezard/go-difflib/difflib" "github.com/rs/zerolog/log" - "go.opentelemetry.io/otel" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" @@ -42,18 +41,18 @@ func isAppMissingErr(err error) bool { } /* -GetDiff takes cli output and return as a string or an array of strings instead of printing +getDiff takes cli output and return as a string or an array of strings instead of printing changedFilePath should be the root of the changed folder from https://github.com/argoproj/argo-cd/blob/d3ff9757c460ae1a6a11e1231251b5d27aadcdd1/cmd/argocd/commands/app.go#L879 */ -func GetDiff( +func getDiff( ctx context.Context, manifests []string, app argoappv1.Application, ctr container.Container, addApp, removeApp func(application2 argoappv1.Application), -) (msg.CheckResult, string, error) { - ctx, span := otel.Tracer("Kubechecks").Start(ctx, "GetDiff") +) (msg.Result, string, error) { + ctx, span := tracer.Start(ctx, "getDiff") defer span.End() argoClient := ctr.ArgoClient @@ -72,7 +71,7 @@ func GetDiff( if err != nil { if !isAppMissingErr(err) { telemetry.SetError(span, err, "Get Argo Managed Resources") - return msg.CheckResult{}, "", err + return msg.Result{}, "", err } resources = new(application.ManagedResourcesResponse) @@ -92,22 +91,22 @@ func GetDiff( argoSettings, err := settingsClient.Get(ctx, &settings.SettingsQuery{}) if err != nil { telemetry.SetError(span, err, "Get Argo Cluster Settings") - return msg.CheckResult{}, "", err + return msg.Result{}, "", err } liveObjs, err := cmdutil.LiveObjects(resources.Items) if err != nil { telemetry.SetError(span, err, "Get Argo Live Objects") - return msg.CheckResult{}, "", err + return msg.Result{}, "", err } groupedObjs, err := groupObjsByKey(unstructureds, liveObjs, app.Spec.Destination.Namespace) if err != nil { - return msg.CheckResult{}, "", err + return msg.Result{}, "", err } if items, err = groupObjsForDiff(resources, groupedObjs, items, argoSettings, app.Name); err != nil { - return msg.CheckResult{}, "", err + return msg.Result{}, "", err } diffBuffer := &strings.Builder{} @@ -135,13 +134,13 @@ func GetDiff( Build() if err != nil { telemetry.SetError(span, err, "Build Diff") - return msg.CheckResult{}, "failed to build diff", err + return msg.Result{}, "failed to build diff", err } diffRes, err := argodiff.StateDiff(item.live, item.target, diffConfig) if err != nil { telemetry.SetError(span, err, "State Diff") - return msg.CheckResult{}, "failed to state diff", err + return msg.Result{}, "failed to state diff", err } if diffRes.Modified || item.target == nil || item.live == nil { @@ -163,7 +162,7 @@ func GetDiff( err := PrintDiff(diffBuffer, live, target) if err != nil { telemetry.SetError(span, err, "Print Diff") - return msg.CheckResult{}, "", err + return msg.Result{}, "", err } switch { case item.target == nil: @@ -193,7 +192,7 @@ func GetDiff( diff := diffBuffer.String() - var cr msg.CheckResult + var cr msg.Result cr.Summary = summary cr.Details = fmt.Sprintf("```diff\n%s\n```", diff) diff --git a/pkg/checks/kubeconform/check.go b/pkg/checks/kubeconform/check.go new file mode 100644 index 00000000..2198470b --- /dev/null +++ b/pkg/checks/kubeconform/check.go @@ -0,0 +1,15 @@ +package kubeconform + +import ( + "context" + + "github.com/zapier/kubechecks/pkg/checks" + "github.com/zapier/kubechecks/pkg/msg" +) + +func Check(ctx context.Context, request checks.Request) (msg.Result, error) { + return argoCdAppValidate( + ctx, request.Container, request.AppName, request.KubernetesVersion, request.Repo.Directory, + request.YamlManifests, + ) +} diff --git a/pkg/validate/validate.go b/pkg/checks/kubeconform/validate.go similarity index 63% rename from pkg/validate/validate.go rename to pkg/checks/kubeconform/validate.go index 7d17a5c3..e70769fb 100644 --- a/pkg/validate/validate.go +++ b/pkg/checks/kubeconform/validate.go @@ -1,4 +1,4 @@ -package validate +package kubeconform import ( "context" @@ -8,19 +8,21 @@ import ( "path/filepath" "strings" + "github.com/pkg/errors" "github.com/rs/zerolog/log" "github.com/yannh/kubeconform/pkg/validator" "go.opentelemetry.io/otel" "github.com/zapier/kubechecks/pkg" - "github.com/zapier/kubechecks/pkg/config" - "github.com/zapier/kubechecks/pkg/local" + "github.com/zapier/kubechecks/pkg/container" "github.com/zapier/kubechecks/pkg/msg" ) -var reposCache = local.NewReposDirectory() +var tracer = otel.Tracer("pkg/validate") + +func getSchemaLocations(ctx context.Context, ctr container.Container, tempRepoPath string) []string { + cfg := ctr.Config -func getSchemaLocations(ctx context.Context, cfg config.ServerConfig, tempRepoPath string) []string { locations := []string{ // schemas included in kubechecks "default", @@ -28,16 +30,27 @@ func getSchemaLocations(ctx context.Context, cfg config.ServerConfig, tempRepoPa // schemas configured globally for _, schemasLocation := range cfg.SchemasLocations { - log.Debug().Str("schemas-location", schemasLocation).Msg("viper") - schemaPath := reposCache.EnsurePath(ctx, tempRepoPath, schemasLocation) - if schemaPath != "" { - locations = append(locations, schemaPath) + if strings.HasPrefix(schemasLocation, "http://") || strings.HasPrefix(schemasLocation, "https://") { + locations = append(locations, schemasLocation) + } else { + if !filepath.IsAbs(schemasLocation) { + schemasLocation = filepath.Join(tempRepoPath, schemasLocation) + } + + if _, err := os.Stat(schemasLocation); err != nil { + log.Warn(). + Err(err). + Str("path", schemasLocation). + Msg("schemas location is invalid, skipping") + } else { + locations = append(locations, schemasLocation) + } } } for index := range locations { location := locations[index] - if location == "default" { + if location == "default" || strings.Contains(location, "{{") { continue } @@ -52,15 +65,29 @@ func getSchemaLocations(ctx context.Context, cfg config.ServerConfig, tempRepoPa return locations } -func ArgoCdAppValidate(ctx context.Context, cfg config.ServerConfig, appName, targetKubernetesVersion, tempRepoPath string, appManifests []string) (msg.CheckResult, error) { - _, span := otel.Tracer("Kubechecks").Start(ctx, "ArgoCdAppValidate") +func wipeDir(dir string) { + if err := os.RemoveAll(dir); err != nil { + log.Error(). + Err(err). + Str("path", dir). + Msg("failed to wipe path") + } +} + +func argoCdAppValidate(ctx context.Context, ctr container.Container, appName, targetKubernetesVersion, tempRepoPath string, appManifests []string) (msg.Result, error) { + _, span := tracer.Start(ctx, "ArgoCdAppValidate") defer span.End() log.Debug().Str("app_name", appName).Str("k8s_version", targetKubernetesVersion).Msg("ArgoCDAppValidate") - cwd, _ := os.Getwd() + schemaCachePath, err := os.MkdirTemp("", "kubechecks-schema-cache-") + if err != nil { + return msg.Result{}, errors.Wrap(err, "failed to create schema cache") + } + defer wipeDir(schemaCachePath) + vOpts := validator.Opts{ - Cache: filepath.Join(cwd, "schemas/"), + Cache: schemaCachePath, SkipTLS: false, SkipKinds: map[string]struct{}{ "apiextensions.k8s.io/v1/CustomResourceDefinition": {}, @@ -74,7 +101,7 @@ func ArgoCdAppValidate(ctx context.Context, cfg config.ServerConfig, appName, ta var ( outputString []string - schemaLocations = getSchemaLocations(ctx, cfg, tempRepoPath) + schemaLocations = getSchemaLocations(ctx, ctr, tempRepoPath) ) log.Debug().Msgf("cache location: %s", vOpts.Cache) @@ -83,8 +110,7 @@ func ArgoCdAppValidate(ctx context.Context, cfg config.ServerConfig, appName, ta v, err := validator.New(schemaLocations, vOpts) if err != nil { - log.Error().Err(err).Msg("could not create kubeconform validator") - return msg.CheckResult{}, fmt.Errorf("could not create kubeconform validator: %v", err) + return msg.Result{}, fmt.Errorf("could not create kubeconform validator: %v", err) } result := v.Validate("-", io.NopCloser(strings.NewReader(strings.Join(appManifests, "\n")))) var invalid, failedValidation bool @@ -109,7 +135,7 @@ func ArgoCdAppValidate(ctx context.Context, cfg config.ServerConfig, appName, ta } } - var cr msg.CheckResult + var cr msg.Result if invalid { cr.State = pkg.StateWarning } else if failedValidation { diff --git a/pkg/validate/validate_test.go b/pkg/checks/kubeconform/validate_test.go similarity index 80% rename from pkg/validate/validate_test.go rename to pkg/checks/kubeconform/validate_test.go index 59672869..bd502b01 100644 --- a/pkg/validate/validate_test.go +++ b/pkg/checks/kubeconform/validate_test.go @@ -1,4 +1,4 @@ -package validate +package kubeconform import ( "context" @@ -12,13 +12,13 @@ import ( "github.com/stretchr/testify/assert" - "github.com/zapier/kubechecks/pkg/config" + "github.com/zapier/kubechecks/pkg/container" ) func TestDefaultGetSchemaLocations(t *testing.T) { ctx := context.TODO() - cfg := config.ServerConfig{} - schemaLocations := getSchemaLocations(ctx, cfg, "/some/other/path") + ctr := container.Container{} + schemaLocations := getSchemaLocations(ctx, ctr, "/some/other/path") // default schema location is "./schemas" assert.Len(t, schemaLocations, 1) @@ -27,7 +27,7 @@ func TestDefaultGetSchemaLocations(t *testing.T) { func TestGetRemoteSchemaLocations(t *testing.T) { ctx := context.TODO() - cfg := config.ServerConfig{} + ctr := container.Container{} if os.Getenv("CI") == "" { t.Skip("Skipping testing. Only for CI environments") @@ -39,7 +39,7 @@ func TestGetRemoteSchemaLocations(t *testing.T) { // t.Setenv("KUBECHECKS_SCHEMAS_LOCATION", fixture.URL) // doesn't work because viper needs to initialize from root, which doesn't happen viper.Set("schemas-location", []string{fixture.URL}) - schemaLocations := getSchemaLocations(ctx, cfg, "/some/other/path") + schemaLocations := getSchemaLocations(ctx, ctr, "/some/other/path") hasTmpDirPrefix := strings.HasPrefix(schemaLocations[0], "/tmp/schemas") assert.Equal(t, hasTmpDirPrefix, true, "invalid schemas location. Schema location should have prefix /tmp/schemas but has %s", schemaLocations[0]) } diff --git a/pkg/checks/preupgrade/check.go b/pkg/checks/preupgrade/check.go new file mode 100644 index 00000000..ee646fb2 --- /dev/null +++ b/pkg/checks/preupgrade/check.go @@ -0,0 +1,12 @@ +package preupgrade + +import ( + "context" + + "github.com/zapier/kubechecks/pkg/checks" + "github.com/zapier/kubechecks/pkg/msg" +) + +func Check(ctx context.Context, request checks.Request) (msg.Result, error) { + return checkApp(ctx, request.AppName, request.KubernetesVersion, request.YamlManifests) +} diff --git a/pkg/kubepug/kubepug.go b/pkg/checks/preupgrade/kubepug.go similarity index 93% rename from pkg/kubepug/kubepug.go rename to pkg/checks/preupgrade/kubepug.go index 90711c50..e3f40882 100644 --- a/pkg/kubepug/kubepug.go +++ b/pkg/checks/preupgrade/kubepug.go @@ -1,4 +1,4 @@ -package kubepug +package preupgrade import ( "bytes" @@ -20,8 +20,10 @@ import ( const docLinkFmt = "[%s Deprecation Notes](https://kubernetes.io/docs/reference/using-api/deprecation-guide/#%s-v%d%d)" -func CheckApp(ctx context.Context, appName, targetKubernetesVersion string, manifests []string) (msg.CheckResult, error) { - _, span := otel.Tracer("Kubechecks").Start(ctx, "KubePug") +var tracer = otel.Tracer("pkg/kubepug") + +func checkApp(ctx context.Context, appName, targetKubernetesVersion string, manifests []string) (msg.Result, error) { + _, span := tracer.Start(ctx, "KubePug") defer span.End() var outputString []string @@ -34,7 +36,7 @@ func CheckApp(ctx context.Context, appName, targetKubernetesVersion string, mani if err != nil { log.Error().Err(err).Msg("could not create temp directory to write manifests for kubepug check") //return "", err - return msg.CheckResult{}, err + return msg.Result{}, err } defer os.RemoveAll(tempDir) @@ -45,7 +47,7 @@ func CheckApp(ctx context.Context, appName, targetKubernetesVersion string, mani nextVersion, err := nextKubernetesVersion(targetKubernetesVersion) if err != nil { - return msg.CheckResult{}, err + return msg.Result{}, err } config := lib.Config{ K8sVersion: fmt.Sprintf("v%s", nextVersion.String()), @@ -58,7 +60,7 @@ func CheckApp(ctx context.Context, appName, targetKubernetesVersion string, mani result, err := kubepug.GetDeprecated() if err != nil { - return msg.CheckResult{}, err + return msg.Result{}, err } if len(result.DeprecatedAPIs) > 0 || len(result.DeletedAPIs) > 0 { @@ -113,7 +115,7 @@ func CheckApp(ctx context.Context, appName, targetKubernetesVersion string, mani outputString = append(outputString, "No Deprecated or Deleted APIs found.") } - return msg.CheckResult{ + return msg.Result{ State: checkStatus(result), Summary: "Show kubepug report:", Details: fmt.Sprintf( diff --git a/pkg/checks/rego/check.go b/pkg/checks/rego/check.go new file mode 100644 index 00000000..e40f0ec5 --- /dev/null +++ b/pkg/checks/rego/check.go @@ -0,0 +1,44 @@ +package rego + +import ( + "context" + + "github.com/pkg/errors" + + "github.com/zapier/kubechecks/pkg/checks" + "github.com/zapier/kubechecks/pkg/config" + "github.com/zapier/kubechecks/pkg/msg" +) + +type Checker struct { + locations []string +} + +var ErrNoLocationsConfigured = errors.New("no policy locations configured") + +func NewChecker(cfg config.ServerConfig) (*Checker, error) { + var c Checker + + var locations []string + if len(locations) == 0 { + return nil, ErrNoLocationsConfigured + } + + return &c, nil +} + +func (c *Checker) Check(ctx context.Context, request checks.Request) (msg.Result, error) { + argoApp, err := request.Container.ArgoClient.GetApplicationByName(ctx, request.AppName) + if err != nil { + return msg.Result{}, errors.Wrapf(err, "could not retrieve ArgoCD App data: %q", request.AppName) + } + + cr, err := conftest( + ctx, argoApp, request.Repo.Directory, c.locations, request.Container.VcsClient, + ) + if err != nil { + return msg.Result{}, err + } + + return cr, nil +} diff --git a/pkg/conftest/conftest.go b/pkg/checks/rego/conftest.go similarity index 85% rename from pkg/conftest/conftest.go rename to pkg/checks/rego/conftest.go index 88312e8e..c2aca03f 100644 --- a/pkg/conftest/conftest.go +++ b/pkg/checks/rego/conftest.go @@ -1,4 +1,4 @@ -package conftest +package rego import ( "bytes" @@ -16,14 +16,13 @@ import ( "go.opentelemetry.io/otel" "github.com/zapier/kubechecks/pkg" - "github.com/zapier/kubechecks/pkg/local" "github.com/zapier/kubechecks/pkg/msg" "github.com/zapier/kubechecks/telemetry" ) const passedMessage = "\nPassed all policy checks." -var reposCache = local.NewReposDirectory() +var tracer = otel.Tracer("pkg/conftest") type emojiable interface { ToEmoji(state pkg.CommitState) string @@ -34,38 +33,22 @@ type emojiable interface { // as a GitLab comment. The validation checks resources against Zapier policies and // provides feedback for warnings or errors as informational messages. Failure to // pass a policy check currently does not block deploy. -func Conftest( +func conftest( ctx context.Context, app *v1alpha1.Application, repoPath string, policiesLocations []string, vcs emojiable, -) (msg.CheckResult, error) { - _, span := otel.Tracer("Kubechecks").Start(ctx, "Conftest") +) (msg.Result, error) { + _, span := tracer.Start(ctx, "Conftest") defer span.End() confTestDir := filepath.Join(repoPath, app.Spec.Source.Path) log.Debug().Str("dir", confTestDir).Str("app", app.Name).Msg("running conftest in dir for application") - var locations []string - for _, policiesLocation := range policiesLocations { - log.Debug().Str("policies-location", policiesLocation).Msg("viper") - schemaPath := reposCache.EnsurePath(ctx, repoPath, policiesLocation) - if schemaPath != "" { - locations = append(locations, schemaPath) - } - } - - if len(locations) == 0 { - return msg.CheckResult{ - State: pkg.StateWarning, - Summary: "no policies locations configured", - }, nil - } - var r runner.TestRunner r.NoColor = true r.AllNamespaces = true // PATH To Rego Polices - r.Policy = locations + r.Policy = policiesLocations r.SuppressExceptions = false r.Trace = false @@ -77,7 +60,7 @@ func Conftest( results, err := r.Run(ctx, []string{confTestDir}) if err != nil { telemetry.SetError(span, err, "ConfTest Run") - return msg.CheckResult{}, err + return msg.Result{}, err } var b bytes.Buffer @@ -101,7 +84,7 @@ func Conftest( innerStrings = append(innerStrings, passedMessage) } - var cr msg.CheckResult + var cr msg.Result if failures { cr.State = pkg.StateFailure } else if warnings { diff --git a/pkg/checks/types.go b/pkg/checks/types.go new file mode 100644 index 00000000..ab981cb6 --- /dev/null +++ b/pkg/checks/types.go @@ -0,0 +1,38 @@ +package checks + +import ( + "context" + + "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" + "github.com/rs/zerolog" + + "github.com/zapier/kubechecks/pkg/container" + "github.com/zapier/kubechecks/pkg/git" + "github.com/zapier/kubechecks/pkg/msg" +) + +type ProcessorEntry struct { + Name string + Processor func(ctx context.Context, request Request) (msg.Result, error) +} + +type Processor interface { + Name() string + Command() +} + +type Request struct { + Log zerolog.Logger + Note *msg.Message + App v1alpha1.Application + Repo *git.Repo + Container container.Container + + QueueApp func(app v1alpha1.Application) + RemoveApp func(app v1alpha1.Application) + + AppName string + KubernetesVersion string + JsonManifests []string + YamlManifests []string +} diff --git a/pkg/config/config.go b/pkg/config/config.go index d0449773..2647acae 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -13,6 +13,7 @@ type ServerConfig struct { ArgoCDToken string ArgoCDPathPrefix string ArgoCDInsecure bool + KubernetesConfig string // otel EnableOtel bool @@ -60,6 +61,7 @@ func New() (ServerConfig, error) { EnableOtel: viper.GetBool("otel-enabled"), EnsureWebhooks: viper.GetBool("ensure-webhooks"), FallbackK8sVersion: viper.GetString("fallback-k8s-version"), + KubernetesConfig: viper.GetString("kubernetes-config"), LabelFilter: viper.GetString("label-filter"), LogLevel: logLevel, MonitorAllApplications: viper.GetBool("monitor-all-applications"), diff --git a/pkg/container/main.go b/pkg/container/main.go index 70c99b7c..02e208e3 100644 --- a/pkg/container/main.go +++ b/pkg/container/main.go @@ -1,10 +1,12 @@ package container import ( + "context" "io/fs" "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" + "github.com/zapier/kubechecks/pkg" "github.com/zapier/kubechecks/pkg/app_watcher" "github.com/zapier/kubechecks/pkg/appdir" "github.com/zapier/kubechecks/pkg/argo_client" @@ -18,7 +20,7 @@ type Container struct { Config config.ServerConfig - VcsClient vcs.VcsClient + VcsClient vcs.Client VcsToArgoMap VcsToArgoMap } @@ -28,6 +30,11 @@ type VcsToArgoMap interface { DeleteApp(*v1alpha1.Application) GetVcsRepos() []string GetAppsInRepo(string) *appdir.AppDirectory - GetMap() map[appdir.RepoURL]*appdir.AppDirectory + GetMap() map[pkg.RepoURL]*appdir.AppDirectory WalkKustomizeApps(cloneURL string, fs fs.FS) *appdir.AppDirectory } + +type ReposCache interface { + Clone(ctx context.Context, repoUrl string) (string, error) + CloneWithBranch(ctx context.Context, repoUrl, targetBranch string) (string, error) +} diff --git a/pkg/events/check.go b/pkg/events/check.go index db7da13e..f42c4804 100644 --- a/pkg/events/check.go +++ b/pkg/events/check.go @@ -3,9 +3,9 @@ package events import ( "context" "fmt" - "os" "reflect" "strings" + "sync" "sync/atomic" "time" @@ -20,136 +20,86 @@ import ( "github.com/zapier/kubechecks/pkg" "github.com/zapier/kubechecks/pkg/affected_apps" "github.com/zapier/kubechecks/pkg/argo_client" - "github.com/zapier/kubechecks/pkg/conftest" + "github.com/zapier/kubechecks/pkg/checks" "github.com/zapier/kubechecks/pkg/container" - "github.com/zapier/kubechecks/pkg/diff" - "github.com/zapier/kubechecks/pkg/kubepug" + "github.com/zapier/kubechecks/pkg/git" "github.com/zapier/kubechecks/pkg/msg" "github.com/zapier/kubechecks/pkg/repo_config" - "github.com/zapier/kubechecks/pkg/validate" "github.com/zapier/kubechecks/pkg/vcs" "github.com/zapier/kubechecks/telemetry" ) +var tracer = otel.Tracer("pkg/events") + type CheckEvent struct { - fileList []string // What files have changed in this PR/MR - TempWorkingDir string // Location of the local repo - repo *vcs.Repo - logger zerolog.Logger - workerLimits int - vcsNote *msg.Message + fileList []string // What files have changed in this PR/MR + pullRequest vcs.PullRequest + logger zerolog.Logger + workerLimits int + vcsNote *msg.Message affectedItems affected_apps.AffectedItems - ctr container.Container + ctr container.Container + repoManager repoManager + processors []checks.ProcessorEntry + repoLock sync.Mutex + clonedRepos map[string]*git.Repo addedAppsSet map[string]v1alpha1.Application appsSent int32 appChannel chan *v1alpha1.Application - doneChannel chan struct{} + wg sync.WaitGroup } -var inFlight int32 +type repoManager interface { + Clone(ctx context.Context, cloneURL, branchName string) (*git.Repo, error) +} -func NewCheckEvent(repo *vcs.Repo, ctr container.Container) *CheckEvent { +func NewCheckEvent(pullRequest vcs.PullRequest, ctr container.Container, repoManager repoManager, processors []checks.ProcessorEntry) *CheckEvent { ce := &CheckEvent{ - ctr: ctr, - repo: repo, + ctr: ctr, + clonedRepos: make(map[string]*git.Repo), + processors: processors, + pullRequest: pullRequest, + repoManager: repoManager, + logger: log.Logger.With(). + Str("repo", pullRequest.Name). + Int("event_id", pullRequest.CheckID). + Logger(), } - ce.logger = log.Logger.With().Str("repo", repo.Name).Int("event_id", repo.CheckID).Logger() return ce } -// getRepo gets the repo from a CheckEvent. In normal operations a CheckEvent can only be made by the VCSHookHandler -// As the Repo is built from a webhook payload via the VCSClient, it should always be present. If not, error -func (ce *CheckEvent) getRepo(ctx context.Context) (*vcs.Repo, error) { - _, span := otel.Tracer("Kubechecks").Start(ctx, "CheckEventGetRepo") +func (ce *CheckEvent) UpdateListOfChangedFiles(ctx context.Context, repo *git.Repo) error { + ctx, span := tracer.Start(ctx, "CheckEventGetListOfChangedFiles") defer span.End() - var err error - if ce.repo == nil { - ce.logger.Error().Err(err).Msg("Repo is nil, did you forget to create it?") - return nil, err - } - return ce.repo, nil -} - -func (ce *CheckEvent) CreateTempDir() error { - var err error - ce.TempWorkingDir, err = os.MkdirTemp("/tmp", "kubechecks-mr-clone") + files, err := repo.GetListOfChangedFiles(ctx) if err != nil { - ce.logger.Error().Err(err).Msg("Unable to make temp directory") return err } - return nil -} - -func (ce *CheckEvent) Cleanup(ctx context.Context) { - _, span := otel.Tracer("Kubechecks").Start(ctx, "Cleanup") - defer span.End() - if ce.TempWorkingDir != "" { - if err := os.RemoveAll(ce.TempWorkingDir); err != nil { - log.Warn().Err(err).Msgf("failed to remove %s", ce.TempWorkingDir) - } - } -} - -// CloneRepoLocal takes the repo inside the Check Event and try to clone it locally -func (ce *CheckEvent) CloneRepoLocal(ctx context.Context) error { - _, span := otel.Tracer("Kubechecks").Start(ctx, "CloneRepoLocal") - defer span.End() - - return ce.repo.CloneRepoLocal(ctx, ce.TempWorkingDir) -} - -// MergeIntoTarget merges the changes from the MR/PR into the base branch -func (ce *CheckEvent) MergeIntoTarget(ctx context.Context) error { - ctx, span := otel.Tracer("Kubechecks").Start(ctx, "MergeIntoTarget") - defer span.End() - gitRepo, err := ce.getRepo(ctx) - if err != nil { - return err - } - - return gitRepo.MergeIntoTarget(ctx) -} - -func (ce *CheckEvent) GetListOfChangedFiles(ctx context.Context) ([]string, error) { - ctx, span := otel.Tracer("Kubechecks").Start(ctx, "CheckEventGetListOfChangedFiles") - defer span.End() - - gitRepo, err := ce.getRepo(ctx) - if err != nil { - return nil, err - } - - if len(ce.fileList) == 0 { - ce.fileList, err = gitRepo.GetListOfChangedFiles(ctx) - } - - if err == nil { - ce.logger.Debug().Msgf("Changed files: %s", strings.Join(ce.fileList, ",")) - } - - return ce.fileList, err + ce.logger.Debug().Msgf("Changed files: %s", strings.Join(files, ",")) + ce.fileList = files + return nil } // GenerateListOfAffectedApps walks the repo to find any apps or appsets impacted by the changes in the MR/PR. -func (ce *CheckEvent) GenerateListOfAffectedApps(ctx context.Context, targetBranch string) error { - _, span := otel.Tracer("Kubechecks").Start(ctx, "GenerateListOfAffectedApps") +func (ce *CheckEvent) GenerateListOfAffectedApps(ctx context.Context, repo *git.Repo, targetBranch string) error { + _, span := tracer.Start(ctx, "GenerateListOfAffectedApps") defer span.End() var err error var matcher affected_apps.Matcher - cfg, _ := repo_config.LoadRepoConfig(ce.TempWorkingDir) + cfg, _ := repo_config.LoadRepoConfig(repo.Directory) if cfg != nil { log.Debug().Msg("using the config matcher") matcher = affected_apps.NewConfigMatcher(cfg, ce.ctr) } else { log.Debug().Msg("using an argocd matcher") - matcher, err = affected_apps.NewArgocdMatcher(ce.ctr.VcsToArgoMap, ce.repo, ce.TempWorkingDir) + matcher, err = affected_apps.NewArgocdMatcher(ce.ctr.VcsToArgoMap, repo) if err != nil { return errors.Wrap(err, "failed to create argocd matcher") } @@ -171,30 +121,83 @@ func (ce *CheckEvent) GenerateListOfAffectedApps(ctx context.Context, targetBran return err } -func (ce *CheckEvent) ProcessApps(ctx context.Context) { - ctx, span := otel.Tracer("Kubechecks").Start(ctx, "ProcessApps", - trace.WithAttributes( - attribute.String("affectedApps", fmt.Sprintf("%+v", ce.affectedItems.Applications)), - attribute.Int("workerLimits", ce.workerLimits), - attribute.Int("numAffectedApps", len(ce.affectedItems.Applications)), - )) +func canonicalize(cloneURL string) (pkg.RepoURL, error) { + parsed, _, err := pkg.NormalizeRepoUrl(cloneURL) + if err != nil { + return pkg.RepoURL{}, errors.Wrap(err, "failed to parse clone url") + } + + return parsed, nil +} + +func (ce *CheckEvent) getRepo(ctx context.Context, cloneURL, branchName string) (*git.Repo, error) { + var ( + err error + repo *git.Repo + ) + + ce.repoLock.Lock() + defer ce.repoLock.Unlock() + + parsed, err := canonicalize(cloneURL) + if err != nil { + return nil, errors.Wrap(err, "failed to parse clone url") + } + cloneURL = parsed.CloneURL(ce.ctr.VcsClient.Username()) + + branchName = strings.TrimSpace(branchName) + reposKey := fmt.Sprintf("%s|||%s", cloneURL, branchName) + + if repo, ok := ce.clonedRepos[reposKey]; ok { + return repo, nil + } + + repo, err = ce.repoManager.Clone(ctx, cloneURL, branchName) + if err != nil { + return nil, errors.Wrap(err, "failed to clone repo") + } + ce.clonedRepos[reposKey] = repo + return repo, nil +} + +func (ce *CheckEvent) Process(ctx context.Context) error { + _, span := tracer.Start(ctx, "GenerateListOfAffectedApps") defer span.End() - err := ce.ctr.VcsClient.TidyOutdatedComments(ctx, ce.repo) + // Clone the repo's BaseRef (main etc) locally into the temp dir we just made + repo, err := ce.getRepo(ctx, ce.pullRequest.CloneURL, ce.pullRequest.BaseRef) if err != nil { + return errors.Wrap(err, "failed to clone repo") + } + + // Merge the most recent changes into the branch we just cloned + if err = repo.MergeIntoTarget(ctx, ce.pullRequest.SHA); err != nil { + return errors.Wrap(err, "failed to merge into target") + } + + // Get the diff between the two branches, storing them within the CheckEvent (also returns but discarded here) + if err = ce.UpdateListOfChangedFiles(ctx, repo); err != nil { + return errors.Wrap(err, "failed to get list of changed files") + } + + // Generate a list of affected apps, storing them within the CheckEvent (also returns but discarded here) + if err = ce.GenerateListOfAffectedApps(ctx, repo, ce.pullRequest.BaseRef); err != nil { + return errors.Wrap(err, "failed to generate a list of affected apps") + } + + if err = ce.ctr.VcsClient.TidyOutdatedComments(ctx, ce.pullRequest); err != nil { ce.logger.Error().Err(err).Msg("Failed to tidy outdated comments") } if len(ce.affectedItems.Applications) <= 0 && len(ce.affectedItems.ApplicationSets) <= 0 { ce.logger.Info().Msg("No affected apps or appsets, skipping") - ce.ctr.VcsClient.PostMessage(ctx, ce.repo, ce.repo.CheckID, "No changes") - return + ce.ctr.VcsClient.PostMessage(ctx, ce.pullRequest, "No changes") + return nil } // Concurrently process all apps, with a corresponding error channel for reporting back failures ce.addedAppsSet = make(map[string]v1alpha1.Application) ce.appChannel = make(chan *v1alpha1.Application, len(ce.affectedItems.Applications)*2) - ce.doneChannel = make(chan struct{}, len(ce.affectedItems.Applications)*2) // If the number of affected apps that we have is less than our worker limit, lower the worker limit if ce.workerLimits > len(ce.affectedItems.Applications) { @@ -213,32 +216,30 @@ func (ce *CheckEvent) ProcessApps(ctx context.Context) { ce.queueApp(app) } - var returnCount int32 = 0 - for range ce.doneChannel { - ce.logger.Debug().Msg("finished an app") - - returnCount++ - ce.logger.Debug(). - Int32("done apps", returnCount). - Int("all apps", len(ce.addedAppsSet)). - Int32("sent apps", ce.appsSent). - Msg("completed apps") - - if returnCount == ce.appsSent { - ce.logger.Debug().Msg("Closing channels") - close(ce.appChannel) - close(ce.doneChannel) - } - } + ce.wg.Wait() + + close(ce.appChannel) + + ce.logger.Debug().Msg("finished an app") + + ce.logger.Debug(). + Int("all apps", len(ce.addedAppsSet)). + Int32("sent apps", ce.appsSent). + Msg("completed apps") + + ce.logger.Debug().Msg("Closing channels") + ce.logger.Info().Msg("Finished") comment := ce.vcsNote.BuildComment(ctx) if err = ce.ctr.VcsClient.UpdateMessage(ctx, ce.vcsNote, comment); err != nil { - ce.logger.Error().Err(err).Msg("failed to push comment") + return errors.Wrap(err, "failed to push comment") } worstStatus := ce.vcsNote.WorstState() ce.CommitStatus(ctx, worstStatus) + + return nil } func (ce *CheckEvent) removeApp(app v1alpha1.Application) { @@ -263,6 +264,7 @@ func (ce *CheckEvent) queueApp(app v1alpha1.Application) { Str("cluster-name", app.Spec.Destination.Name). Str("cluster-server", app.Spec.Destination.Server) + ce.wg.Add(1) atomic.AddInt32(&ce.appsSent, 1) logger.Msg("producing app on channel") @@ -273,10 +275,10 @@ func (ce *CheckEvent) queueApp(app v1alpha1.Application) { // CommitStatus sets the commit status on the MR // To set the PR/MR status func (ce *CheckEvent) CommitStatus(ctx context.Context, status pkg.CommitState) { - _, span := otel.Tracer("Kubechecks").Start(ctx, "CommitStatus") + _, span := tracer.Start(ctx, "CommitStatus") defer span.End() - if err := ce.ctr.VcsClient.CommitStatus(ctx, ce.repo, status); err != nil { + if err := ce.ctr.VcsClient.CommitStatus(ctx, ce.pullRequest, status); err != nil { log.Warn().Err(err).Msg("failed to update commit status") } } @@ -291,7 +293,7 @@ func (ce *CheckEvent) appWorkers(ctx context.Context, workerID int) { log.Warn().Msg("appWorkers received a nil app") } - ce.doneChannel <- struct{}{} + ce.wg.Done() } } @@ -301,12 +303,23 @@ func (ce *CheckEvent) appWorkers(ctx context.Context, workerID int) { // The processing is performed concurrently using Go routines and error groups. Any check results are sent through // the returnChan. The function also manages the inFlight atomic counter to track active processing routines. func (ce *CheckEvent) processApp(ctx context.Context, app v1alpha1.Application) { - appName := app.Name - dir := app.Spec.GetSource().Path + var ( + err error + + appName = app.Name + appSrc = app.Spec.Source + appPath = appSrc.Path + appRepoUrl = appSrc.RepoURL + + logger = ce.logger.With(). + Str("app_name", appName). + Str("app_path", appPath). + Logger() + ) - ctx, span := otel.Tracer("Kubechecks").Start(ctx, "processApp", trace.WithAttributes( + ctx, span := tracer.Start(ctx, "processApp", trace.WithAttributes( attribute.String("app", appName), - attribute.String("dir", dir), + attribute.String("dir", appPath), )) defer span.End() @@ -314,46 +327,59 @@ func (ce *CheckEvent) processApp(ctx context.Context, app v1alpha1.Application) defer atomic.AddInt32(&inFlight, -1) start := time.Now() - ce.logger.Info().Str("app", appName).Msg("Adding new app") + logger.Info().Str("app", appName).Msg("Adding new app") // Build a new section for this app in the parent comment ce.vcsNote.AddNewApp(ctx, appName) - ce.logger.Debug().Msgf("Getting manifests for app: %s with code at %s/%s", appName, ce.TempWorkingDir, dir) - jsonManifests, err := argo_client.GetManifestsLocal(ctx, ce.ctr.ArgoClient, appName, ce.TempWorkingDir, dir, app) + repo, err := ce.getRepo(ctx, appRepoUrl, appSrc.TargetRevision) if err != nil { - ce.logger.Error().Err(err).Msgf("Unable to get manifests for %s in %s", appName, dir) - cr := msg.CheckResult{State: pkg.StateError, Summary: "Unable to get manifests", Details: fmt.Sprintf("```\n%s\n```", ce.cleanupGetManifestsError(err))} - ce.vcsNote.AddToAppMessage(ctx, appName, cr) + logger.Error().Err(err).Msg("Unable to clone repository") + ce.vcsNote.AddToAppMessage(ctx, appName, msg.Result{ + State: pkg.StateError, + Summary: "failed to clone repo", + Details: fmt.Sprintf("Clone URL: `%s`\nTarget Revision: `%s`\n```\n%s\n```", appRepoUrl, appSrc.TargetRevision, err.Error()), + }) + return + } + repoPath := repo.Directory + + logger.Debug().Str("repo_path", repoPath).Msg("Getting manifests") + jsonManifests, err := argo_client.GetManifestsLocal(ctx, ce.ctr.ArgoClient, appName, repoPath, appPath, app) + if err != nil { + logger.Error().Err(err).Msg("Unable to get manifests") + ce.vcsNote.AddToAppMessage(ctx, appName, msg.Result{ + State: pkg.StateError, + Summary: "Unable to get manifests", + Details: fmt.Sprintf("```\n%s\n```", cleanupGetManifestsError(err, repo.Directory)), + }) return } // Argo diff logic wants unformatted manifests but everything else wants them as YAML, so we prepare both yamlManifests := argo_client.ConvertJsonToYamlManifests(jsonManifests) - ce.logger.Trace().Msgf("Manifests:\n%+v\n", yamlManifests) + logger.Trace().Msgf("Manifests:\n%+v\n", yamlManifests) k8sVersion, err := ce.ctr.ArgoClient.GetKubernetesVersionByApplication(ctx, app) if err != nil { - ce.logger.Error().Err(err).Msg("Error retrieving the Kubernetes version") + logger.Error().Err(err).Msg("Error retrieving the Kubernetes version") k8sVersion = ce.ctr.Config.FallbackK8sVersion } else { k8sVersion = fmt.Sprintf("%s.0", k8sVersion) - ce.logger.Info().Msgf("Kubernetes version: %s", k8sVersion) + logger.Info().Msgf("Kubernetes version: %s", k8sVersion) } - runner := newRunner(span, ctx, app, appName, k8sVersion, ce.TempWorkingDir, jsonManifests, yamlManifests, ce.logger, ce.vcsNote) - - runner.Run("validating app against schema", ce.validateSchemas) - runner.Run("generating diff for app", ce.generateDiff) + runner := newRunner( + ce.ctr, app, repo, appName, k8sVersion, jsonManifests, yamlManifests, logger, ce.vcsNote, + ce.queueApp, ce.removeApp, + ) - if ce.ctr.Config.EnableConfTest { - runner.Run("validation policy", ce.validatePolicy) + for _, processor := range ce.processors { + runner.Run(ctx, processor.Name, processor.Processor) } - runner.Run("running pre-upgrade check", ce.runPreupgradeCheck) - runner.Wait() - ce.vcsNote.SetFooter(start, ce.repo.SHA, ce.ctr.Config.LabelFilter, ce.ctr.Config.ShowDebugInfo) + ce.vcsNote.SetFooter(start, ce.pullRequest.SHA, ce.ctr.Config.LabelFilter, ce.ctr.Config.ShowDebugInfo) } const ( @@ -367,51 +393,6 @@ Check kubechecks application logs for more information. ` ) -var EmptyCheckResult msg.CheckResult - -func (ce *CheckEvent) runPreupgradeCheck(data CheckData) (msg.CheckResult, error) { - s, err := kubepug.CheckApp(data.ctx, data.appName, data.k8sVersion, data.yamlManifests) - if err != nil { - return EmptyCheckResult, err - } - - return s, nil -} - -func (ce *CheckEvent) validatePolicy(data CheckData) (msg.CheckResult, error) { - argoApp, err := ce.ctr.ArgoClient.GetApplicationByName(data.ctx, data.appName) - if err != nil { - return EmptyCheckResult, errors.Wrapf(err, "could not retrieve ArgoCD App data: %q", data.appName) - } - - cr, err := conftest.Conftest(data.ctx, argoApp, ce.TempWorkingDir, ce.ctr.Config.PoliciesLocation, ce.ctr.VcsClient) - if err != nil { - return EmptyCheckResult, err - } - - return cr, nil -} - -func (ce *CheckEvent) generateDiff(data CheckData) (msg.CheckResult, error) { - cr, rawDiff, err := diff.GetDiff(data.ctx, data.jsonManifests, data.app, ce.ctr, ce.queueApp, ce.removeApp) - if err != nil { - return EmptyCheckResult, err - } - - diff.AIDiffSummary(data.ctx, ce.vcsNote, ce.ctr.Config, data.appName, data.jsonManifests, rawDiff) - - return cr, nil -} - -func (ce *CheckEvent) validateSchemas(data CheckData) (msg.CheckResult, error) { - cr, err := validate.ArgoCdAppValidate(data.ctx, ce.ctr.Config, data.appName, data.k8sVersion, data.repoPath, data.yamlManifests) - if err != nil { - return EmptyCheckResult, err - } - - return cr, nil -} - // Creates a generic Note struct that we can write into across all worker threads func (ce *CheckEvent) createNote(ctx context.Context) *msg.Message { ctx, span := otel.Tracer("check").Start(ctx, "createNote") @@ -419,12 +400,12 @@ func (ce *CheckEvent) createNote(ctx context.Context) *msg.Message { ce.logger.Info().Msgf("Creating note") - return ce.ctr.VcsClient.PostMessage(ctx, ce.repo, ce.repo.CheckID, ":hourglass: kubechecks running ... ") + return ce.ctr.VcsClient.PostMessage(ctx, ce.pullRequest, ":hourglass: kubechecks running ... ") } // cleanupGetManifestsError takes an error as input and returns a simplified and more user-friendly error message. // It reformats Helm error messages by removing excess information, and makes file paths relative to the git repo root. -func (ce *CheckEvent) cleanupGetManifestsError(err error) string { +func cleanupGetManifestsError(err error, repoDirectory string) string { // cleanup the chonky helm error message for a better DX errStr := err.Error() if strings.Contains(errStr, "helm template") && strings.Contains(errStr, "failed exit status") { @@ -433,7 +414,7 @@ func (ce *CheckEvent) cleanupGetManifestsError(err error) string { } // strip the temp directory from any files mentioned to make file paths relative to git repo root - errStr = strings.ReplaceAll(errStr, ce.TempWorkingDir+"/", "") + errStr = strings.ReplaceAll(errStr, repoDirectory+"/", "") return errStr } diff --git a/pkg/events/check_test.go b/pkg/events/check_test.go index 8082e909..35947217 100644 --- a/pkg/events/check_test.go +++ b/pkg/events/check_test.go @@ -8,7 +8,7 @@ import ( // TestCleanupGetManifestsError tests the cleanupGetManifestsError function. func TestCleanupGetManifestsError(t *testing.T) { - checkEvent := &CheckEvent{TempWorkingDir: "/tmp/work"} + repoDirectory := "/some-dir" tests := []struct { name string @@ -22,24 +22,24 @@ func TestCleanupGetManifestsError(t *testing.T) { }, { name: "strip temp directory", - inputErr: fmt.Errorf("Error: %s/tmpfile.yaml not found", checkEvent.TempWorkingDir), - expectedError: "Error: tmpfile.yaml not found", + inputErr: fmt.Errorf("error: %s/tmpfile.yaml not found", repoDirectory), + expectedError: "error: tmpfile.yaml not found", }, { name: "strip temp directory and helm error", - inputErr: fmt.Errorf("`helm template . --name-template in-cluster-echo-server --namespace echo-server --kube-version 1.25 --values %s/apps/echo-server/in-cluster/values.yaml --values %s/apps/echo-server/in-cluster/notexist.yaml --api-versions admissionregistration.k8s.io/v1 --api-versions admissionregistration.k8s.io/v1/MutatingWebhookConfiguration --api-versions v1/Secret --api-versions v1/Service --api-versions v1/ServiceAccount --include-crds` failed exit status 1: Error: open %s/apps/echo-server/in-cluster/notexist.yaml: no such file or directory", checkEvent.TempWorkingDir, checkEvent.TempWorkingDir, checkEvent.TempWorkingDir), + inputErr: fmt.Errorf("`helm template . --name-template in-cluster-echo-server --namespace echo-server --kube-version 1.25 --values %s/apps/echo-server/in-cluster/values.yaml --values %s/apps/echo-server/in-cluster/notexist.yaml --api-versions admissionregistration.k8s.io/v1 --api-versions admissionregistration.k8s.io/v1/MutatingWebhookConfiguration --api-versions v1/Secret --api-versions v1/Service --api-versions v1/ServiceAccount --include-crds` failed exit status 1: Error: open %s/apps/echo-server/in-cluster/notexist.yaml: no such file or directory", repoDirectory, repoDirectory, repoDirectory), expectedError: "Helm Error: open apps/echo-server/in-cluster/notexist.yaml: no such file or directory", }, { name: "other error", - inputErr: errors.New("Error: unknown error"), - expectedError: "Error: unknown error", + inputErr: errors.New("error: unknown error"), + expectedError: "error: unknown error", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - cleanedError := checkEvent.cleanupGetManifestsError(tt.inputErr) + cleanedError := cleanupGetManifestsError(tt.inputErr, repoDirectory) if cleanedError != tt.expectedError { t.Errorf("Expected error: %s, \n Received: %s", tt.expectedError, cleanedError) } diff --git a/pkg/events/metrics.go b/pkg/events/metrics.go index 10000762..3d6cb8f4 100644 --- a/pkg/events/metrics.go +++ b/pkg/events/metrics.go @@ -1,5 +1,7 @@ package events +var inFlight int32 + func GetInFlight() int { return int(inFlight) } diff --git a/pkg/events/runner.go b/pkg/events/runner.go index 66c3cfb3..44f2feda 100644 --- a/pkg/events/runner.go +++ b/pkg/events/runner.go @@ -7,37 +7,25 @@ import ( "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" "github.com/rs/zerolog" - "go.opentelemetry.io/otel/trace" "github.com/zapier/kubechecks/pkg" + "github.com/zapier/kubechecks/pkg/checks" + "github.com/zapier/kubechecks/pkg/container" + "github.com/zapier/kubechecks/pkg/git" "github.com/zapier/kubechecks/pkg/msg" "github.com/zapier/kubechecks/telemetry" ) -type CheckData struct { - span trace.Span - ctx context.Context - logger zerolog.Logger - note *msg.Message - app v1alpha1.Application - - appName string - k8sVersion string - repoPath string - jsonManifests []string - yamlManifests []string -} - type Runner struct { - CheckData + checks.Request wg sync.WaitGroup } func newRunner( - span trace.Span, ctx context.Context, app v1alpha1.Application, - appName, k8sVersion, repoPath string, jsonManifests, yamlManifests []string, - logger zerolog.Logger, note *msg.Message, + ctr container.Container, app v1alpha1.Application, repo *git.Repo, + appName, k8sVersion string, jsonManifests, yamlManifests []string, + logger zerolog.Logger, note *msg.Message, queueApp, removeApp func(application v1alpha1.Application), ) *Runner { logger = logger. With(). @@ -45,29 +33,31 @@ func newRunner( Logger() return &Runner{ - CheckData: CheckData{ - app: app, - appName: appName, - k8sVersion: k8sVersion, - repoPath: repoPath, - jsonManifests: jsonManifests, - yamlManifests: yamlManifests, - - ctx: ctx, - logger: logger, - note: note, - span: span, + Request: checks.Request{ + App: app, + AppName: appName, + Container: ctr, + JsonManifests: jsonManifests, + KubernetesVersion: k8sVersion, + Log: logger, + Note: note, + QueueApp: queueApp, + RemoveApp: removeApp, + Repo: repo, + YamlManifests: yamlManifests, }, } } -type checkFunction func(data CheckData) (msg.CheckResult, error) +type checkFunction func(ctx context.Context, data checks.Request) (msg.Result, error) -func (r *Runner) Run(desc string, fn checkFunction) { +func (r *Runner) Run(ctx context.Context, desc string, fn checkFunction) { r.wg.Add(1) go func() { - logger := r.logger + logger := r.Log + + ctx, span := tracer.Start(ctx, desc) defer func() { r.wg.Done() @@ -75,13 +65,13 @@ func (r *Runner) Run(desc string, fn checkFunction) { if err := recover(); err != nil { logger.Error().Str("check", desc).Msgf("panic while running check") - telemetry.SetError(r.span, fmt.Errorf("%v", err), desc) - result := msg.CheckResult{ + telemetry.SetError(span, fmt.Errorf("%v", err), desc) + result := msg.Result{ State: pkg.StatePanic, Summary: desc, Details: fmt.Sprintf(errorCommentFormat, desc, err), } - r.note.AddToAppMessage(r.ctx, r.appName, result) + r.Note.AddToAppMessage(ctx, r.AppName, result) } }() @@ -90,20 +80,20 @@ func (r *Runner) Run(desc string, fn checkFunction) { Logger() logger.Info().Msgf("running check") - cr, err := fn(r.CheckData) + cr, err := fn(ctx, r.Request) logger.Info(). Err(err). Uint8("result", uint8(cr.State)). Msg("check result") if err != nil { - telemetry.SetError(r.span, err, desc) - result := msg.CheckResult{State: pkg.StateError, Summary: desc, Details: fmt.Sprintf(errorCommentFormat, desc, err)} - r.note.AddToAppMessage(r.ctx, r.appName, result) + telemetry.SetError(span, err, desc) + result := msg.Result{State: pkg.StateError, Summary: desc, Details: fmt.Sprintf(errorCommentFormat, desc, err)} + r.Note.AddToAppMessage(ctx, r.AppName, result) return } - r.note.AddToAppMessage(r.ctx, r.appName, cr) + r.Note.AddToAppMessage(ctx, r.AppName, cr) logger.Info(). Str("result", cr.State.BareString()). diff --git a/pkg/git/manager.go b/pkg/git/manager.go new file mode 100644 index 00000000..e3ca9d35 --- /dev/null +++ b/pkg/git/manager.go @@ -0,0 +1,57 @@ +package git + +import ( + "context" + "os" + "sync" + + "github.com/pkg/errors" + "github.com/rs/zerolog/log" + "go.opentelemetry.io/otel" + + "github.com/zapier/kubechecks/pkg/config" +) + +var tracer = otel.Tracer("pkg/git") + +type RepoManager struct { + lock sync.Mutex + repos []*Repo + cfg config.ServerConfig +} + +func NewRepoManager(cfg config.ServerConfig) *RepoManager { + return &RepoManager{cfg: cfg} +} + +func (rm *RepoManager) Clone(ctx context.Context, cloneUrl, branchName string) (*Repo, error) { + repo := New(rm.cfg, cloneUrl, branchName) + + if err := repo.Clone(ctx); err != nil { + return nil, errors.Wrap(err, "failed to clone repository") + } + + rm.lock.Lock() + defer rm.lock.Unlock() // just for safety's sake + rm.repos = append(rm.repos, repo) + + return repo, nil +} + +func wipeDir(dir string) { + if err := os.RemoveAll(dir); err != nil { + log.Error(). + Err(err). + Str("path", dir). + Msg("failed to wipe path") + } +} + +func (rm *RepoManager) Cleanup() { + rm.lock.Lock() + defer rm.lock.Unlock() + + for _, repo := range rm.repos { + wipeDir(repo.Directory) + } +} diff --git a/pkg/git/repo.go b/pkg/git/repo.go new file mode 100644 index 00000000..df7d24d4 --- /dev/null +++ b/pkg/git/repo.go @@ -0,0 +1,262 @@ +package git + +import ( + "bufio" + "context" + "fmt" + "io/fs" + "net/url" + "os" + "os/exec" + "path/filepath" + "strings" + "sync" + + "github.com/pkg/errors" + "github.com/rs/zerolog/log" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" + + "github.com/zapier/kubechecks/pkg/config" + "github.com/zapier/kubechecks/pkg/vcs" + "github.com/zapier/kubechecks/telemetry" +) + +type Repo struct { + // informational + BranchName string + Config config.ServerConfig + CloneURL string + + // exposed state + Directory string +} + +func New(cfg config.ServerConfig, cloneUrl, branchName string) *Repo { + if branchName == "" { + branchName = "HEAD" + } + + return &Repo{ + CloneURL: cloneUrl, + BranchName: branchName, + Config: cfg, + } +} + +func (r *Repo) Clone(ctx context.Context) error { + var err error + + r.Directory, err = os.MkdirTemp("/tmp", "kubechecks-repo-") + if err != nil { + return errors.Wrap(err, "failed to make temp dir") + } + + log.Info(). + Str("temp-dir", r.Directory). + Str("clone-url", r.CloneURL). + Str("branch", r.BranchName). + Msg("cloning git repo") + + // Attempt to locally clone the repo based on the provided information stored within + _, span := tracer.Start(ctx, "CloneRepo") + defer span.End() + + cmd := r.execCommand("git", "clone", r.CloneURL, r.Directory) + out, err := cmd.CombinedOutput() + if err != nil { + log.Error().Err(err).Msgf("unable to clone repository, %s", out) + return err + } + + if log.Trace().Enabled() { + if err = filepath.WalkDir(r.Directory, printFile); err != nil { + log.Warn().Err(err).Msg("failed to walk directory") + } + } + + return nil +} + +func printFile(s string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if !d.IsDir() { + println(s) + } + return nil +} + +func (r *Repo) MergeIntoTarget(ctx context.Context, sha string) error { + // Merge the last commit into a tmp branch off of the target branch + _, span := tracer.Start(ctx, "Repo - RepoMergeIntoTarget", + trace.WithAttributes( + attribute.String("branch_name", r.BranchName), + attribute.String("clone_url", r.CloneURL), + attribute.String("directory", r.Directory), + attribute.String("sha", sha), + )) + defer span.End() + + cmd := r.execCommand("git", "merge", sha) + out, err := cmd.CombinedOutput() + if err != nil { + telemetry.SetError(span, err, "merge commit into branch") + log.Error().Err(err).Msgf("unable to merge %s, %s", sha, out) + return err + } + + return nil +} + +func (r *Repo) Update(ctx context.Context) error { + cmd := r.execCommand("git", "pull") + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stdout + return cmd.Run() +} + +func (r *Repo) execCommand(name string, args ...string) *exec.Cmd { + argsToLog := r.censorVcsToken(args) + + log.Debug().Strs("args", argsToLog).Msg("building command") + cmd := exec.Command(name, args...) + if r.Directory != "" { + cmd.Dir = r.Directory + } + return cmd +} + +func (r *Repo) censorVcsToken(args []string) []string { + return censorVcsToken(r.Config, args) +} + +func execCommand(cfg config.ServerConfig, name string, args ...string) *exec.Cmd { + argsToLog := censorVcsToken(cfg, args) + + log.Debug().Strs("args", argsToLog).Msg("building command") + cmd := exec.Command(name, args...) + return cmd +} + +func censorVcsToken(cfg config.ServerConfig, args []string) []string { + vcsToken := cfg.VcsToken + if len(vcsToken) == 0 { + return args + } + + var argsToLog []string + for _, arg := range args { + argsToLog = append(argsToLog, strings.Replace(arg, vcsToken, "********", 10)) + } + return argsToLog +} + +// SetCredentials ensures Git auth is set up for cloning +func SetCredentials(cfg config.ServerConfig, vcsClient vcs.Client) error { + email := vcsClient.Email() + username := vcsClient.Username() + + cmd := execCommand(cfg, "git", "config", "--global", "user.email", email) + err := cmd.Run() + if err != nil { + return errors.Wrap(err, "failed to set git email address") + } + + cmd = execCommand(cfg, "git", "config", "--global", "user.name", username) + err = cmd.Run() + if err != nil { + return errors.Wrap(err, "failed to set git user name") + } + + cloneUrl, err := getCloneUrl(username, cfg) + if err != nil { + return errors.Wrap(err, "failed to get clone url") + } + + homedir, err := os.UserHomeDir() + if err != nil { + if err != nil { + return errors.Wrap(err, "unable to get home directory") + } + } + outfile, err := os.Create(fmt.Sprintf("%s/.git-credentials", homedir)) + if err != nil { + return errors.Wrap(err, "unable to create credentials file") + } + defer outfile.Close() + + cmd = execCommand(cfg, "echo", cloneUrl) + cmd.Stdout = outfile + err = cmd.Run() + if err != nil { + return errors.Wrap(err, "unable to set git credentials") + } + + cmd = execCommand(cfg, "git", "config", "--global", "credential.helper", "store") + err = cmd.Run() + if err != nil { + return errors.Wrap(err, "unable to set git credential usage") + } + log.Debug().Msg("git credentials set") + + return nil +} + +func getCloneUrl(user string, cfg config.ServerConfig) (string, error) { + vcsBaseUrl := cfg.VcsBaseUrl + vcsType := cfg.VcsType + vcsToken := cfg.VcsToken + + var hostname, scheme string + + if vcsBaseUrl == "" { + // hack: but it does happen to work for now + hostname = fmt.Sprintf("%s.com", vcsType) + scheme = "https" + } else { + parts, err := url.Parse(vcsBaseUrl) + if err != nil { + return "", errors.Wrapf(err, "failed to parse %q", vcsBaseUrl) + } + hostname = parts.Host + scheme = parts.Scheme + } + + return fmt.Sprintf("%s://%s:%s@%s", scheme, user, vcsToken, hostname), nil +} + +// GetListOfChangedFiles returns a list of files that have changed between the current branch and the target branch +func (r *Repo) GetListOfChangedFiles(ctx context.Context) ([]string, error) { + _, span := tracer.Start(ctx, "RepoGetListOfChangedFiles") + defer span.End() + + var fileList []string + + cmd := r.execCommand("git", "diff", "--name-only", fmt.Sprintf("%s/%s", "origin", r.BranchName)) + pipe, _ := cmd.StdoutPipe() + var wg sync.WaitGroup + scanner := bufio.NewScanner(pipe) + wg.Add(1) + go func() { + for scanner.Scan() { + line := scanner.Text() + fileList = append(fileList, line) + } + wg.Done() + }() + err := cmd.Start() + if err != nil { + log.Error().Err(err).Msg("unable to start diff command") + return nil, err + } + wg.Wait() + err = cmd.Wait() + if err != nil { + log.Error().Err(err).Msg("unable to diff branches") + return nil, err + } + + return fileList, nil +} diff --git a/pkg/git/repo_test.go b/pkg/git/repo_test.go new file mode 100644 index 00000000..7edcc26b --- /dev/null +++ b/pkg/git/repo_test.go @@ -0,0 +1,89 @@ +package git + +import ( + "context" + "os" + "os/exec" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/zapier/kubechecks/pkg/config" +) + +func wipe(t *testing.T, path string) { + err := os.RemoveAll(path) + require.NoError(t, err) +} + +func TestRepoRoundTrip(t *testing.T) { + originRepo, err := os.MkdirTemp("", "kubechecks-test-") + require.NoError(t, err) + defer wipe(t, originRepo) + + // initialize the test repo + cmd := exec.Command("/bin/sh", "-c", `#!/usr/bin/env bash +set -e +set -x + +# set up git repo +cd $TEMPDIR +git init +git config user.email "user@test.com" +git config user.name "Zap Zap" + +# set up main branch +git branch -m main + +echo "one" > abc.txt +git add abc.txt +git commit -m "commit one on main" + +# set up testing branch +git checkout -b testing +echo "three" > abc.txt +git add abc.txt +git commit -m "commit two on testing" + +# add commit back to main +git checkout main +echo "four" > def.txt +git add def.txt +git commit -m "commit two on main" + +# pull main into testing +git checkout testing +git merge main +echo "two" > ghi.txt +git add ghi.txt +git commit -m "commit three" +`) + cmd.Env = append(cmd.Env, "TEMPDIR="+originRepo) + cmd.Stderr = os.Stderr + cmd.Stdout = os.Stdout + err = cmd.Run() + require.NoError(t, err) + + cmd = exec.Command("git", "rev-parse", "HEAD") + cmd.Dir = originRepo + output, err := cmd.Output() + require.NoError(t, err) + sha := strings.TrimSpace(string(output)) + + var cfg config.ServerConfig + ctx := context.Background() + repo := New(cfg, originRepo, "main") + + err = repo.Clone(ctx) + require.NoError(t, err) + defer wipe(t, repo.Directory) + + err = repo.MergeIntoTarget(ctx, sha) + require.NoError(t, err) + + files, err := repo.GetListOfChangedFiles(ctx) + require.NoError(t, err) + assert.Equal(t, []string{"abc.txt", "ghi.txt"}, files) +} diff --git a/pkg/local/gitRepos.go b/pkg/local/gitRepos.go deleted file mode 100644 index 60b9549d..00000000 --- a/pkg/local/gitRepos.go +++ /dev/null @@ -1,97 +0,0 @@ -package local - -import ( - "context" - "os" - "os/exec" - "path/filepath" - "strings" - "sync" - - "github.com/rs/zerolog/log" - - "github.com/zapier/kubechecks/pkg/vcs" -) - -type ReposDirectory struct { - paths map[string]string - - mutex sync.Mutex -} - -func NewReposDirectory() *ReposDirectory { - rd := &ReposDirectory{ - paths: make(map[string]string), - } - - return rd -} - -func (rd *ReposDirectory) EnsurePath(ctx context.Context, tempRepoPath, location string) string { - if location == "" { - return "" - } - - if strings.HasPrefix(location, "https://") || strings.HasPrefix(location, "http://") || strings.HasPrefix(location, "git@") { - log.Debug().Str("location", location).Msg("registering remote repository") - localPath := rd.Register(ctx, location) - return localPath - } - - schemaPath := filepath.Join(tempRepoPath, location) - if stat, err := os.Stat(schemaPath); err == nil && stat.IsDir() { - log.Debug().Str("location", location).Msg("registering in-repo path") - return schemaPath - } else { - log.Warn().Str("location", location).Err(err).Msg("failed to find in-repo path") - } - - return "" -} - -func (rd *ReposDirectory) Register(ctx context.Context, cloneUrl string) string { - var ( - ok bool - repoDir string - ) - - rd.mutex.Lock() - defer rd.mutex.Unlock() - - repoDir, ok = rd.paths[cloneUrl] - if ok { - rd.fetchLatest(repoDir) - return repoDir - } - - return rd.clone(ctx, cloneUrl) -} - -func (rd *ReposDirectory) fetchLatest(repoDir string) { - cmd := exec.Command("git", "pull") - cmd.Dir = repoDir - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stdout - err := cmd.Run() - if err != nil { - log.Err(err).Msg("failed to pull latest") - } -} - -func (rd *ReposDirectory) clone(ctx context.Context, cloneUrl string) string { - repoDir, err := os.MkdirTemp("/tmp", "schemas") - if err != nil { - log.Err(err).Msg("failed to make temp dir") - return "" - } - - r := vcs.Repo{CloneURL: cloneUrl} - err = r.CloneRepoLocal(ctx, repoDir) - if err != nil { - log.Err(err).Str("clone-url", cloneUrl).Msg("failed to clone repository") - return "" - } - - rd.paths[cloneUrl] = repoDir - return repoDir -} diff --git a/pkg/msg/message.go b/pkg/msg/message.go index a4507096..c528ed0b 100644 --- a/pkg/msg/message.go +++ b/pkg/msg/message.go @@ -15,16 +15,18 @@ import ( "github.com/zapier/kubechecks/pkg" ) -type CheckResult struct { +var tracer = otel.Tracer("pkg/msg") + +type Result struct { State pkg.CommitState Summary, Details string } type AppResults struct { - results []CheckResult + results []Result } -func (ar *AppResults) AddCheckResult(result CheckResult) { +func (ar *AppResults) AddCheckResult(result Result) { ar.results = append(ar.results, result) } @@ -98,7 +100,7 @@ func (m *Message) AddNewApp(ctx context.Context, app string) { return } - _, span := otel.Tracer("Kubechecks").Start(ctx, "AddNewApp") + _, span := tracer.Start(ctx, "AddNewApp") defer span.End() m.lock.Lock() defer m.lock.Unlock() @@ -106,12 +108,12 @@ func (m *Message) AddNewApp(ctx context.Context, app string) { m.apps[app] = new(AppResults) } -func (m *Message) AddToAppMessage(ctx context.Context, app string, result CheckResult) { +func (m *Message) AddToAppMessage(ctx context.Context, app string, result Result) { if m.isDeleted(app) { return } - _, span := otel.Tracer("Kubechecks").Start(ctx, "AddToAppMessage") + _, span := tracer.Start(ctx, "AddToAppMessage") defer span.End() m.lock.Lock() defer m.lock.Unlock() @@ -146,7 +148,7 @@ func (m *Message) BuildComment(ctx context.Context) string { // Iterate the map of all apps in this message, building a final comment from their current state func (m *Message) buildComment(ctx context.Context) string { - _, span := otel.Tracer("Kubechecks").Start(ctx, "buildComment") + _, span := tracer.Start(ctx, "buildComment") defer span.End() names := getSortedKeys(m.apps) diff --git a/pkg/msg/message_test.go b/pkg/msg/message_test.go index faa68bb0..2f61c230 100644 --- a/pkg/msg/message_test.go +++ b/pkg/msg/message_test.go @@ -19,7 +19,7 @@ func (fe fakeEmojiable) ToEmoji(state pkg.CommitState) string { return fe.emoji func TestBuildComment(t *testing.T) { appResults := map[string]*AppResults{ "myapp": { - results: []CheckResult{ + results: []Result{ { State: pkg.StateError, Summary: "this failed bigly", @@ -60,24 +60,24 @@ func TestMessageIsSuccess(t *testing.T) { assert.Equal(t, pkg.StateNone, message.WorstState()) // one app, one success = success - message.AddToAppMessage(ctx, "some-app", CheckResult{State: pkg.StateSuccess}) + message.AddToAppMessage(ctx, "some-app", Result{State: pkg.StateSuccess}) assert.Equal(t, pkg.StateSuccess, message.WorstState()) // one app, one success, one failure = failure - message.AddToAppMessage(ctx, "some-app", CheckResult{State: pkg.StateFailure}) + message.AddToAppMessage(ctx, "some-app", Result{State: pkg.StateFailure}) assert.Equal(t, pkg.StateFailure, message.WorstState()) // one app, two successes, one failure = failure - message.AddToAppMessage(ctx, "some-app", CheckResult{State: pkg.StateSuccess}) + message.AddToAppMessage(ctx, "some-app", Result{State: pkg.StateSuccess}) assert.Equal(t, pkg.StateFailure, message.WorstState()) // one app, two successes, one failure = failure - message.AddToAppMessage(ctx, "some-app", CheckResult{State: pkg.StateSuccess}) + message.AddToAppMessage(ctx, "some-app", Result{State: pkg.StateSuccess}) assert.Equal(t, pkg.StateFailure, message.WorstState()) // two apps: second app's success does not override first app's failure message.AddNewApp(ctx, "some-other-app") - message.AddToAppMessage(ctx, "some-other-app", CheckResult{State: pkg.StateSuccess}) + message.AddToAppMessage(ctx, "some-other-app", Result{State: pkg.StateSuccess}) assert.Equal(t, pkg.StateFailure, message.WorstState()) }) @@ -98,7 +98,7 @@ func TestMessageIsSuccess(t *testing.T) { ctx = context.TODO() ) message.AddNewApp(ctx, "some-app") - message.AddToAppMessage(ctx, "some-app", CheckResult{State: state}) + message.AddToAppMessage(ctx, "some-app", Result{State: state}) assert.Equal(t, state, message.WorstState()) }) } @@ -110,23 +110,23 @@ func TestMultipleItemsWithNewlines(t *testing.T) { ctx = context.Background() ) message.AddNewApp(ctx, "first-app") - message.AddToAppMessage(ctx, "first-app", CheckResult{ + message.AddToAppMessage(ctx, "first-app", Result{ State: pkg.StateSuccess, Summary: "summary-1", Details: "detail-1", }) - message.AddToAppMessage(ctx, "first-app", CheckResult{ + message.AddToAppMessage(ctx, "first-app", Result{ State: pkg.StateSuccess, Summary: "summary-2", Details: "detail-2", }) message.AddNewApp(ctx, "second-app") - message.AddToAppMessage(ctx, "second-app", CheckResult{ + message.AddToAppMessage(ctx, "second-app", Result{ State: pkg.StateSuccess, Summary: "summary-1", Details: "detail-1", }) - message.AddToAppMessage(ctx, "second-app", CheckResult{ + message.AddToAppMessage(ctx, "second-app", Result{ State: pkg.StateSuccess, Summary: "summary-2", Details: "detail-2", diff --git a/pkg/repoUrl.go b/pkg/repoUrl.go new file mode 100644 index 00000000..bdba40a2 --- /dev/null +++ b/pkg/repoUrl.go @@ -0,0 +1,43 @@ +package pkg + +import ( + "fmt" + "net/url" + "strings" + + giturls "github.com/whilp/git-urls" +) + +type RepoURL struct { + Host, Path string +} + +func (r RepoURL) CloneURL(username string) string { + if username != "" { + return fmt.Sprintf("https://%s@%s/%s", username, r.Host, r.Path) + } + return fmt.Sprintf("https://%s/%s", r.Host, r.Path) +} + +func NormalizeRepoUrl(s string) (RepoURL, url.Values, error) { + var parser func(string) (*url.URL, error) + + if strings.HasPrefix(s, "http") { + parser = url.Parse + } else { + parser = giturls.Parse + } + + r, err := parser(s) + if err != nil { + return RepoURL{}, nil, err + } + + r.Path = strings.TrimPrefix(r.Path, "/") + r.Path = strings.TrimSuffix(r.Path, ".git") + + return RepoURL{ + Host: r.Host, + Path: r.Path, + }, r.Query(), nil +} diff --git a/pkg/repoUrl_test.go b/pkg/repoUrl_test.go new file mode 100644 index 00000000..a4bc786e --- /dev/null +++ b/pkg/repoUrl_test.go @@ -0,0 +1,72 @@ +package pkg + +import ( + "fmt" + "net/url" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/stretchr/testify/assert" +) + +func TestNormalizeStrings(t *testing.T) { + type expected struct { + RepoURL RepoURL + Query url.Values + } + testCases := []struct { + input, name string + expected expected + }{ + { + name: "simple github over ssh", + input: "git@github.com:one/two", + expected: expected{ + RepoURL: RepoURL{Host: "github.com", Path: "one/two"}, + Query: make(url.Values), + }, + }, + { + name: "simple github over https", + input: "https://github.com/one/two", + expected: expected{ + RepoURL: RepoURL{Host: "github.com", Path: "one/two"}, + Query: make(url.Values), + }, + }, + { + name: "simple gitlab over ssh", + input: "git@gitlab.com:djeebus/helm-test.git", + expected: expected{ + RepoURL: RepoURL{Host: "gitlab.com", Path: "djeebus/helm-test"}, + Query: make(url.Values), + }, + }, + { + name: "simple gitlab over https", + input: "https://gitlab.com/djeebus/helm-test.git", + expected: expected{ + RepoURL: RepoURL{Host: "gitlab.com", Path: "djeebus/helm-test"}, + Query: make(url.Values), + }, + }, + { + name: "simple gitlab over https with query", + input: "https://gitlab.com/djeebus/helm-test.git?subdir=/blah", + expected: expected{ + RepoURL: RepoURL{Host: "gitlab.com", Path: "djeebus/helm-test"}, + Query: url.Values{"subdir": []string{"/blah"}}, + }, + }, + } + + for _, tc := range testCases { + t.Run(fmt.Sprintf("case %s", tc.input), func(t *testing.T) { + repoURL, query, err := NormalizeRepoUrl(tc.input) + require.NoError(t, err) + assert.Equal(t, tc.expected.RepoURL, repoURL) + assert.Equal(t, tc.expected.Query, query) + }) + } +} diff --git a/pkg/server/hook_handler.go b/pkg/server/hook_handler.go index 601ccc27..2f8929e0 100644 --- a/pkg/server/hook_handler.go +++ b/pkg/server/hook_handler.go @@ -12,20 +12,24 @@ import ( "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" - "github.com/zapier/kubechecks/pkg/config" + "github.com/zapier/kubechecks/pkg/checks" "github.com/zapier/kubechecks/pkg/container" "github.com/zapier/kubechecks/pkg/events" + "github.com/zapier/kubechecks/pkg/git" "github.com/zapier/kubechecks/pkg/vcs" - "github.com/zapier/kubechecks/telemetry" ) +var tracer = otel.Tracer("pkg/server") + type VCSHookHandler struct { - ctr container.Container + ctr container.Container + processors []checks.ProcessorEntry } -func NewVCSHookHandler(ctr container.Container) *VCSHookHandler { +func NewVCSHookHandler(ctr container.Container, processors []checks.ProcessorEntry) *VCSHookHandler { return &VCSHookHandler{ - ctr: ctr, + ctr: ctr, + processors: processors, } } func (h *VCSHookHandler) AttachHandlers(grp *echo.Group) { @@ -43,7 +47,7 @@ func (h *VCSHookHandler) groupHandler(c echo.Context) error { return c.String(http.StatusUnauthorized, "Unauthorized") } - r, err := h.ctr.VcsClient.ParseHook(c.Request(), payload) + pr, err := h.ctr.VcsClient.ParseHook(c.Request(), payload) if err != nil { switch err { case vcs.ErrInvalidType: @@ -57,96 +61,54 @@ func (h *VCSHookHandler) groupHandler(c echo.Context) error { } // We now have a generic repo with all the info we need to start processing an event. Hand off to the event processor - go h.processCheckEvent(ctx, r) + go h.processCheckEvent(ctx, pr) return c.String(http.StatusAccepted, "Accepted") } // Takes a constructed Repo, and attempts to run the Kubechecks processing suite against it. // If the Repo is not yet populated, this will fail. -func (h *VCSHookHandler) processCheckEvent(ctx context.Context, repo *vcs.Repo) { - if !h.passesLabelFilter(repo) { +func (h *VCSHookHandler) processCheckEvent(ctx context.Context, pullRequest vcs.PullRequest) { + if !h.passesLabelFilter(pullRequest) { log.Warn().Str("label-filter", h.ctr.Config.LabelFilter).Msg("ignoring event, did not have matching label") return } - ProcessCheckEvent(ctx, repo, h.ctr.Config, h.ctr) + ProcessCheckEvent(ctx, pullRequest, h.ctr, h.processors) +} + +type RepoDirectory struct { } -func ProcessCheckEvent(ctx context.Context, r *vcs.Repo, cfg config.ServerConfig, ctr container.Container) { - var span trace.Span - ctx, span = otel.Tracer("Kubechecks").Start(ctx, "processCheckEvent", +func ProcessCheckEvent(ctx context.Context, pr vcs.PullRequest, ctr container.Container, processors []checks.ProcessorEntry) { + ctx, span := tracer.Start(ctx, "processCheckEvent", trace.WithAttributes( - attribute.Int("mr_id", r.CheckID), - attribute.String("project", r.Name), - attribute.String("sha", r.SHA), - attribute.String("source", r.HeadRef), - attribute.String("target", r.BaseRef), - attribute.String("default_branch", r.DefaultBranch), + attribute.Int("mr_id", pr.CheckID), + attribute.String("project", pr.Name), + attribute.String("sha", pr.SHA), + attribute.String("source", pr.HeadRef), + attribute.String("target", pr.BaseRef), + attribute.String("default_branch", pr.DefaultBranch), ), ) defer span.End() - // If we've gotten here, we can now begin running checks (or trying to) - cEvent := events.NewCheckEvent(r, ctr) - - err := cEvent.CreateTempDir() - if err != nil { - telemetry.SetError(span, err, "Create Temp Dir") - log.Error().Err(err).Msg("unable to create temp dir") - } - defer cEvent.Cleanup(ctx) - - err = vcs.InitializeGitSettings(ctr.Config, ctr.VcsClient) - if err != nil { - telemetry.SetError(span, err, "Initialize Git") - log.Error().Err(err).Msg("unable to initialize git") - return - } + // repo cache + repoMgr := git.NewRepoManager(ctr.Config) + defer repoMgr.Cleanup() - // Clone the repo's BaseRef (main etc) locally into the temp dir we just made - err = cEvent.CloneRepoLocal(ctx) - if err != nil { - // TODO: Cancel event if gitlab etc - telemetry.SetError(span, err, "Clone Repo Local") - log.Error().Err(err).Msg("unable to clone repo locally") - return - } - - // Merge the most recent changes into the branch we just cloned - err = cEvent.MergeIntoTarget(ctx) - if err != nil { - // TODO: Cancel if gitlab etc - log.Error().Err(err).Msg("failed to merge into target") - return - } - - // Get the diff between the two branches, storing them within the CheckEvent (also returns but discarded here) - _, err = cEvent.GetListOfChangedFiles(ctx) - if err != nil { - // TODO: Cancel if gitlab etc - log.Error().Err(err).Msg("failed to get list of changed files") - return - } - - // Generate a list of affected apps, storing them within the CheckEvent (also returns but discarded here) - err = cEvent.GenerateListOfAffectedApps(ctx, r.BaseRef) - if err != nil { - // TODO: Cancel if gitlab etc - //mEvent.CancelEvent(ctx, err, "Generate List of Affected Apps") - log.Error().Err(err).Msg("failed to generate a list of affected apps") - return + // If we've gotten here, we can now begin running checks (or trying to) + cEvent := events.NewCheckEvent(pr, ctr, repoMgr, processors) + if err := cEvent.Process(ctx); err != nil { + span.RecordError(err) + log.Error().Err(err).Msg("failed to process the request") } - - // At this stage, we've cloned the repo locally, merged the changes into a temp branch, and have calculated - // what apps/appsets and files have changed. We are now ready to run the Kubechecks suite! - cEvent.ProcessApps(ctx) } // passesLabelFilter checks if the given mergeEvent has a label that starts with "kubechecks:" // and matches the handler's labelFilter. Returns true if there's a matching label or no // "kubechecks:" labels are found, and false if a "kubechecks:" label is found but none match // the labelFilter. -func (h *VCSHookHandler) passesLabelFilter(repo *vcs.Repo) bool { +func (h *VCSHookHandler) passesLabelFilter(repo vcs.PullRequest) bool { foundKubechecksLabel := false for _, label := range repo.Labels { diff --git a/pkg/server/server.go b/pkg/server/server.go index 05ca3884..5a4a3448 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -14,6 +14,7 @@ import ( "github.com/rs/zerolog/log" "github.com/ziflex/lecho/v3" + "github.com/zapier/kubechecks/pkg/checks" "github.com/zapier/kubechecks/pkg/container" "github.com/zapier/kubechecks/pkg/vcs" ) @@ -21,11 +22,12 @@ import ( const KubeChecksHooksPathPrefix = "/hooks" type Server struct { - ctr container.Container + ctr container.Container + processors []checks.ProcessorEntry } -func NewServer(ctr container.Container) *Server { - return &Server{ctr: ctr} +func NewServer(ctr container.Container, processors []checks.ProcessorEntry) *Server { + return &Server{ctr: ctr, processors: processors} } func (s *Server) Start(ctx context.Context) { @@ -48,7 +50,7 @@ func (s *Server) Start(ctx context.Context) { hooksGroup := e.Group(s.hooksPrefix()) - ghHooks := NewVCSHookHandler(s.ctr) + ghHooks := NewVCSHookHandler(s.ctr, s.processors) ghHooks.AttachHandlers(hooksGroup) fmt.Println("Method\tPath") diff --git a/pkg/server/server_test.go b/pkg/server/server_test.go index 8550d230..43c9d38f 100644 --- a/pkg/server/server_test.go +++ b/pkg/server/server_test.go @@ -3,6 +3,7 @@ package server import ( "testing" + "github.com/zapier/kubechecks/pkg/checks" "github.com/zapier/kubechecks/pkg/config" "github.com/zapier/kubechecks/pkg/container" ) @@ -51,7 +52,7 @@ func TestHooksPrefix(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - s := NewServer(container.Container{Config: tt.cfg}) + s := NewServer(container.Container{Config: tt.cfg}, []checks.ProcessorEntry{}) if got := s.hooksPrefix(); got != tt.want { t.Errorf("hooksPrefix() = %v, want %v", got, tt.want) } diff --git a/pkg/vcs/github_client/client.go b/pkg/vcs/github_client/client.go index c9f60cdd..d5ea0518 100644 --- a/pkg/vcs/github_client/client.go +++ b/pkg/vcs/github_client/client.go @@ -2,6 +2,7 @@ package github_client import ( "context" + "fmt" "io" "net/http" "regexp" @@ -12,6 +13,8 @@ import ( "github.com/pkg/errors" "github.com/rs/zerolog/log" "github.com/shurcooL/githubv4" + giturls "github.com/whilp/git-urls" + "go.opentelemetry.io/otel" "golang.org/x/oauth2" "github.com/zapier/kubechecks/pkg" @@ -19,6 +22,8 @@ import ( "github.com/zapier/kubechecks/pkg/vcs" ) +var tracer = otel.Tracer("pkg/vcs/github_client") + type Client struct { shurcoolClient *githubv4.Client googleClient *github.Client @@ -107,10 +112,12 @@ func (c *Client) VerifyHook(r *http.Request, secret string) ([]byte, error) { } } -func (c *Client) ParseHook(r *http.Request, request []byte) (*vcs.Repo, error) { +var nilPr vcs.PullRequest + +func (c *Client) ParseHook(r *http.Request, request []byte) (vcs.PullRequest, error) { payload, err := github.ParseWebHook(github.WebHookType(r), request) if err != nil { - return nil, err + return nilPr, err } switch p := payload.(type) { @@ -121,21 +128,21 @@ func (c *Client) ParseHook(r *http.Request, request []byte) (*vcs.Repo, error) { return c.buildRepoFromEvent(p), nil default: log.Info().Str("action", p.GetAction()).Msg("ignoring Github pull request event due to non commit based action") - return nil, vcs.ErrInvalidType + return nilPr, vcs.ErrInvalidType } default: log.Error().Msg("invalid event provided to Github client") - return nil, vcs.ErrInvalidType + return nilPr, vcs.ErrInvalidType } } -func (c *Client) buildRepoFromEvent(event *github.PullRequestEvent) *vcs.Repo { +func (c *Client) buildRepoFromEvent(event *github.PullRequestEvent) vcs.PullRequest { var labels []string for _, label := range event.PullRequest.Labels { labels = append(labels, label.GetName()) } - return &vcs.Repo{ + return vcs.PullRequest{ BaseRef: *event.PullRequest.Base.Ref, HeadRef: *event.PullRequest.Head.Ref, DefaultBranch: *event.Repo.DefaultBranch, @@ -169,12 +176,12 @@ func toGithubCommitStatus(state pkg.CommitState) *string { return pkg.Pointer("failure") } -func (c *Client) CommitStatus(ctx context.Context, repo *vcs.Repo, status pkg.CommitState) error { - log.Info().Str("repo", repo.Name).Str("sha", repo.SHA).Str("status", status.BareString()).Msg("setting Github commit status") - repoStatus, _, err := c.googleClient.Repositories.CreateStatus(ctx, repo.Owner, repo.Name, repo.SHA, &github.RepoStatus{ +func (c *Client) CommitStatus(ctx context.Context, pr vcs.PullRequest, status pkg.CommitState) error { + log.Info().Str("repo", pr.Name).Str("sha", pr.SHA).Str("status", status.BareString()).Msg("setting Github commit status") + repoStatus, _, err := c.googleClient.Repositories.CreateStatus(ctx, pr.Owner, pr.Name, pr.SHA, &github.RepoStatus{ State: toGithubCommitStatus(status), Description: pkg.Pointer(status.BareString()), - ID: pkg.Pointer(int64(repo.CheckID)), + ID: pkg.Pointer(int64(pr.CheckID)), Context: pkg.Pointer("kubechecks"), }) if err != nil { @@ -186,16 +193,22 @@ func (c *Client) CommitStatus(ctx context.Context, repo *vcs.Repo, status pkg.Co } func parseRepo(cloneUrl string) (string, string) { - if strings.HasPrefix(cloneUrl, "git@") { - // parse ssh string - parts := strings.Split(cloneUrl, ":") - parts = strings.Split(parts[1], "/") - owner := parts[0] - repoName := strings.TrimSuffix(parts[1], ".git") - return owner, repoName + result, err := giturls.Parse(cloneUrl) + if err != nil { + panic(fmt.Errorf("%s: %s", cloneUrl, err.Error())) + } + + path := result.Path + path = strings.TrimPrefix(path, "/") + path = strings.TrimSuffix(path, ".git") + parts := strings.Split(path, "/") + if len(parts) != 2 { + panic(fmt.Errorf("%s: invalid path", cloneUrl)) } - panic(cloneUrl) + owner := parts[0] + repoName := strings.TrimSuffix(parts[1], ".git") + return owner, repoName } func (c *Client) GetHookByUrl(ctx context.Context, ownerAndRepoName, webhookUrl string) (*vcs.WebHookConfig, error) { @@ -241,27 +254,27 @@ func (c *Client) CreateHook(ctx context.Context, ownerAndRepoName, webhookUrl, w var rePullRequest = regexp.MustCompile(`(.*)/(.*)#(\d+)`) -func (c *Client) LoadHook(ctx context.Context, id string) (*vcs.Repo, error) { +func (c *Client) LoadHook(ctx context.Context, id string) (vcs.PullRequest, error) { m := rePullRequest.FindStringSubmatch(id) if len(m) != 4 { - return nil, errors.New("must be in format OWNER/REPO#PR") + return nilPr, errors.New("must be in format OWNER/REPO#PR") } ownerName := m[1] repoName := m[2] prNumber, err := strconv.ParseInt(m[3], 10, 32) if err != nil { - return nil, errors.Wrap(err, "failed to parse int") + return nilPr, errors.Wrap(err, "failed to parse int") } repoInfo, _, err := c.googleClient.Repositories.Get(ctx, ownerName, repoName) if err != nil { - return nil, errors.Wrap(err, "failed to get repo") + return nilPr, errors.Wrap(err, "failed to get repo") } pullRequest, _, err := c.googleClient.PullRequests.Get(ctx, ownerName, repoName, int(prNumber)) if err != nil { - return nil, errors.Wrap(err, "failed to get pull request") + return nilPr, errors.Wrap(err, "failed to get pull request") } var labels []string @@ -303,7 +316,7 @@ func (c *Client) LoadHook(ctx context.Context, id string) (*vcs.Repo, error) { userEmail = "kubechecks@github.com" } - return &vcs.Repo{ + return vcs.PullRequest{ BaseRef: baseRef, HeadRef: headRef, DefaultBranch: unPtr(repoInfo.DefaultBranch), diff --git a/pkg/vcs/github_client/client_test.go b/pkg/vcs/github_client/client_test.go new file mode 100644 index 00000000..5faf4d0b --- /dev/null +++ b/pkg/vcs/github_client/client_test.go @@ -0,0 +1,42 @@ +package github_client + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestParseRepo(t *testing.T) { + testcases := []struct { + name, input string + expectedOwner, expectedRepo string + }{ + { + name: "github.com over ssh", + input: "git@github.com:zapier/kubechecks.git", + expectedOwner: "zapier", + expectedRepo: "kubechecks", + }, + { + name: "github.com over https", + input: "https://github.com/zapier/kubechecks.git", + expectedOwner: "zapier", + expectedRepo: "kubechecks", + }, + { + name: "github.com with https with username without .git", + input: "https://djeebus@github.com/zapier/kubechecks", + expectedOwner: "zapier", + expectedRepo: "kubechecks", + }, + } + + for _, tc := range testcases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + owner, repo := parseRepo(tc.input) + assert.Equal(t, tc.expectedOwner, owner) + assert.Equal(t, tc.expectedRepo, repo) + }) + } +} diff --git a/pkg/vcs/github_client/message.go b/pkg/vcs/github_client/message.go index cb3f5c96..e9ff933a 100644 --- a/pkg/vcs/github_client/message.go +++ b/pkg/vcs/github_client/message.go @@ -8,7 +8,6 @@ import ( "github.com/google/go-github/v53/github" "github.com/rs/zerolog/log" "github.com/shurcooL/githubv4" - "go.opentelemetry.io/otel" "github.com/zapier/kubechecks/pkg" "github.com/zapier/kubechecks/pkg/msg" @@ -18,8 +17,8 @@ import ( const MaxCommentLength = 64 * 1024 -func (c *Client) PostMessage(ctx context.Context, repo *vcs.Repo, prID int, message string) *msg.Message { - _, span := otel.Tracer("Kubechecks").Start(ctx, "PostMessageToMergeRequest") +func (c *Client) PostMessage(ctx context.Context, pr vcs.PullRequest, message string) *msg.Message { + _, span := tracer.Start(ctx, "PostMessageToMergeRequest") defer span.End() if len(message) > MaxCommentLength { @@ -27,12 +26,12 @@ func (c *Client) PostMessage(ctx context.Context, repo *vcs.Repo, prID int, mess message = message[:MaxCommentLength] } - log.Debug().Msgf("Posting message to PR %d in repo %s", prID, repo.FullName) + log.Debug().Msgf("Posting message to PR %d in repo %s", pr.CheckID, pr.FullName) comment, _, err := c.googleClient.Issues.CreateComment( ctx, - repo.Owner, - repo.Name, - prID, + pr.Owner, + pr.Name, + pr.CheckID, &github.IssueComment{Body: &message}, ) @@ -41,11 +40,11 @@ func (c *Client) PostMessage(ctx context.Context, repo *vcs.Repo, prID int, mess log.Error().Err(err).Msg("could not post message to PR") } - return msg.NewMessage(repo.FullName, prID, int(*comment.ID), c) + return msg.NewMessage(pr.FullName, pr.CheckID, int(*comment.ID), c) } func (c *Client) UpdateMessage(ctx context.Context, m *msg.Message, msg string) error { - _, span := otel.Tracer("Kubechecks").Start(ctx, "UpdateMessage") + _, span := tracer.Start(ctx, "UpdateMessage") defer span.End() if len(msg) > MaxCommentLength { @@ -79,15 +78,15 @@ func (c *Client) UpdateMessage(ctx context.Context, m *msg.Message, msg string) // Pull all comments for the specified PR, and delete any comments that already exist from the bot // This is different from updating an existing message, as this will delete comments from previous runs of the bot // Whereas updates occur mid-execution -func (c *Client) pruneOldComments(ctx context.Context, repo *vcs.Repo, comments []*github.IssueComment) error { - _, span := otel.Tracer("Kubechecks").Start(ctx, "pruneOldComments") +func (c *Client) pruneOldComments(ctx context.Context, pr vcs.PullRequest, comments []*github.IssueComment) error { + _, span := tracer.Start(ctx, "pruneOldComments") defer span.End() - log.Debug().Msgf("Pruning messages from PR %d in repo %s", repo.CheckID, repo.FullName) + log.Debug().Msgf("Pruning messages from PR %d in repo %s", pr.CheckID, pr.FullName) for _, comment := range comments { if strings.EqualFold(comment.GetUser().GetLogin(), c.username) { - _, err := c.googleClient.Issues.DeleteComment(ctx, repo.Owner, repo.Name, *comment.ID) + _, err := c.googleClient.Issues.DeleteComment(ctx, pr.Owner, pr.Name, *comment.ID) if err != nil { return fmt.Errorf("failed to delete comment: %w", err) } @@ -97,11 +96,11 @@ func (c *Client) pruneOldComments(ctx context.Context, repo *vcs.Repo, comments return nil } -func (c *Client) hideOutdatedMessages(ctx context.Context, repo *vcs.Repo, comments []*github.IssueComment) error { - _, span := otel.Tracer("Kubechecks").Start(ctx, "hideOutdatedComments") +func (c *Client) hideOutdatedMessages(ctx context.Context, pr vcs.PullRequest, comments []*github.IssueComment) error { + _, span := tracer.Start(ctx, "hideOutdatedComments") defer span.End() - log.Debug().Msgf("Hiding kubecheck messages in PR %d in repo %s", repo.CheckID, repo.FullName) + log.Debug().Msgf("Hiding kubecheck messages in PR %d in repo %s", pr.CheckID, pr.FullName) for _, comment := range comments { if strings.EqualFold(comment.GetUser().GetLogin(), c.username) { @@ -130,15 +129,15 @@ func (c *Client) hideOutdatedMessages(ctx context.Context, repo *vcs.Repo, comme } -func (c *Client) TidyOutdatedComments(ctx context.Context, repo *vcs.Repo) error { - _, span := otel.Tracer("Kubechecks").Start(ctx, "TidyOutdatedComments") +func (c *Client) TidyOutdatedComments(ctx context.Context, pr vcs.PullRequest) error { + _, span := tracer.Start(ctx, "TidyOutdatedComments") defer span.End() var allComments []*github.IssueComment nextPage := 0 for { - comments, resp, err := c.googleClient.Issues.ListComments(ctx, repo.Owner, repo.Name, repo.CheckID, &github.IssueListCommentsOptions{ + comments, resp, err := c.googleClient.Issues.ListComments(ctx, pr.Owner, pr.Name, pr.CheckID, &github.IssueListCommentsOptions{ Sort: pkg.Pointer("created"), Direction: pkg.Pointer("asc"), ListOptions: github.ListOptions{Page: nextPage}, @@ -155,7 +154,7 @@ func (c *Client) TidyOutdatedComments(ctx context.Context, repo *vcs.Repo) error } if strings.ToLower(c.cfg.TidyOutdatedCommentsMode) == "delete" { - return c.pruneOldComments(ctx, repo, allComments) + return c.pruneOldComments(ctx, pr, allComments) } - return c.hideOutdatedMessages(ctx, repo, allComments) + return c.hideOutdatedMessages(ctx, pr, allComments) } diff --git a/pkg/vcs/gitlab_client/client.go b/pkg/vcs/gitlab_client/client.go index 21e45f58..af7f8e28 100644 --- a/pkg/vcs/gitlab_client/client.go +++ b/pkg/vcs/gitlab_client/client.go @@ -86,11 +86,13 @@ func (c *Client) VerifyHook(r *http.Request, secret string) ([]byte, error) { return io.ReadAll(r.Body) } +var nilPr vcs.PullRequest + // ParseHook parses and validates a webhook event; return an err if this isn't valid -func (c *Client) ParseHook(r *http.Request, request []byte) (*vcs.Repo, error) { +func (c *Client) ParseHook(r *http.Request, request []byte) (vcs.PullRequest, error) { eventRequest, err := gitlab.ParseHook(gitlab.HookEventType(r), request) if err != nil { - return nil, err + return nilPr, err } switch event := eventRequest.(type) { @@ -105,13 +107,13 @@ func (c *Client) ParseHook(r *http.Request, request []byte) (*vcs.Repo, error) { return c.buildRepoFromEvent(event), nil default: log.Trace().Msgf("Unhandled Action %s", event.ObjectAttributes.Action) - return nil, vcs.ErrInvalidType + return nilPr, vcs.ErrInvalidType } default: log.Trace().Msgf("Unhandled Event: %T", event) - return nil, vcs.ErrInvalidType + return nilPr, vcs.ErrInvalidType } - return nil, vcs.ErrInvalidType + return nilPr, vcs.ErrInvalidType } func parseRepoName(url string) (string, error) { @@ -174,33 +176,32 @@ func (c *Client) CreateHook(ctx context.Context, repoName, webhookUrl, webhookSe var reMergeRequest = regexp.MustCompile(`(.*)!(\d+)`) -func (c *Client) LoadHook(ctx context.Context, id string) (*vcs.Repo, error) { +func (c *Client) LoadHook(ctx context.Context, id string) (vcs.PullRequest, error) { m := reMergeRequest.FindStringSubmatch(id) if len(m) != 3 { - return nil, errors.New("must be in format REPOPATH!MR") + return nilPr, errors.New("must be in format REPOPATH!MR") } repoPath := m[1] mrNumber, err := strconv.ParseInt(m[2], 10, 32) if err != nil { - return nil, errors.Wrap(err, "failed to parse merge request number") + return nilPr, errors.Wrap(err, "failed to parse merge request number") } project, _, err := c.c.Projects.GetProject(repoPath, nil) if err != nil { - return nil, errors.Wrapf(err, "failed to get project '%s'", repoPath) + return nilPr, errors.Wrapf(err, "failed to get project '%s'", repoPath) } mergeRequest, _, err := c.c.MergeRequests.GetMergeRequest(repoPath, int(mrNumber), nil) if err != nil { - return nil, errors.Wrapf(err, "failed to get merge request '%d' in project '%s'", mrNumber, repoPath) + return nilPr, errors.Wrapf(err, "failed to get merge request '%d' in project '%s'", mrNumber, repoPath) } - return &vcs.Repo{ + return vcs.PullRequest{ BaseRef: mergeRequest.TargetBranch, HeadRef: mergeRequest.SourceBranch, DefaultBranch: project.DefaultBranch, - RepoDir: "", Remote: "", CloneURL: project.HTTPURLToRepo, Name: project.Name, @@ -216,14 +217,14 @@ func (c *Client) LoadHook(ctx context.Context, id string) (*vcs.Repo, error) { }, nil } -func (c *Client) buildRepoFromEvent(event *gitlab.MergeEvent) *vcs.Repo { +func (c *Client) buildRepoFromEvent(event *gitlab.MergeEvent) vcs.PullRequest { // Convert all labels from this MR to a string array of label names var labels []string for _, label := range event.Labels { labels = append(labels, label.Title) } - return &vcs.Repo{ + return vcs.PullRequest{ BaseRef: event.ObjectAttributes.TargetBranch, HeadRef: event.ObjectAttributes.SourceBranch, DefaultBranch: event.Project.DefaultBranch, diff --git a/pkg/vcs/gitlab_client/merge.go b/pkg/vcs/gitlab_client/merge.go index 087d08d0..62f63427 100644 --- a/pkg/vcs/gitlab_client/merge.go +++ b/pkg/vcs/gitlab_client/merge.go @@ -11,6 +11,8 @@ import ( "github.com/zapier/kubechecks/telemetry" ) +var tracer = otel.Tracer("pkg/vcs/gitlab_client") + type Changes struct { OldPath string `json:"old_path"` NewPath string `json:"new_path"` @@ -23,7 +25,7 @@ type Changes struct { } func (c *Client) GetMergeChanges(ctx context.Context, projectId int, mergeReqId int) ([]*Changes, error) { - _, span := otel.Tracer("Kubechecks").Start(ctx, "GetMergeChanges") + _, span := tracer.Start(ctx, "GetMergeChanges") defer span.End() var changes []*Changes @@ -50,7 +52,7 @@ func (c *Client) GetMergeChanges(ctx context.Context, projectId int, mergeReqId } func CheckForValidChanges(ctx context.Context, changes []*Changes, paths []string, fileTypes []string) bool { - _, span := otel.Tracer("Kubechecks").Start(ctx, "CheckForValidChanges") + _, span := tracer.Start(ctx, "CheckForValidChanges") defer span.End() for _, change := range changes { diff --git a/pkg/vcs/gitlab_client/message.go b/pkg/vcs/gitlab_client/message.go index 492a9625..38e38dc5 100644 --- a/pkg/vcs/gitlab_client/message.go +++ b/pkg/vcs/gitlab_client/message.go @@ -7,7 +7,6 @@ import ( "github.com/rs/zerolog/log" "github.com/xanzy/go-gitlab" - "go.opentelemetry.io/otel" "github.com/zapier/kubechecks/pkg" "github.com/zapier/kubechecks/pkg/msg" @@ -17,8 +16,8 @@ import ( const MaxCommentLength = 1_000_000 -func (c *Client) PostMessage(ctx context.Context, repo *vcs.Repo, mergeRequestID int, message string) *msg.Message { - _, span := otel.Tracer("Kubechecks").Start(ctx, "PostMessageToMergeRequest") +func (c *Client) PostMessage(ctx context.Context, pr vcs.PullRequest, message string) *msg.Message { + _, span := tracer.Start(ctx, "PostMessageToMergeRequest") defer span.End() if len(message) > MaxCommentLength { @@ -27,7 +26,7 @@ func (c *Client) PostMessage(ctx context.Context, repo *vcs.Repo, mergeRequestID } n, _, err := c.c.Notes.CreateMergeRequestNote( - repo.FullName, mergeRequestID, + pr.FullName, pr.CheckID, &gitlab.CreateMergeRequestNoteOptions{ Body: pkg.Pointer(message), }) @@ -36,11 +35,11 @@ func (c *Client) PostMessage(ctx context.Context, repo *vcs.Repo, mergeRequestID log.Error().Err(err).Msg("could not post message to MR") } - return msg.NewMessage(repo.FullName, mergeRequestID, n.ID, c) + return msg.NewMessage(pr.FullName, pr.CheckID, n.ID, c) } func (c *Client) hideOutdatedMessages(ctx context.Context, projectName string, mergeRequestID int, notes []*gitlab.Note) error { - _, span := otel.Tracer("Kubechecks").Start(ctx, "HideOutdatedMessages") + _, span := tracer.Start(ctx, "HideOutdatedMessages") defer span.End() log.Debug().Msg("hiding outdated comments") @@ -108,7 +107,7 @@ func (c *Client) UpdateMessage(ctx context.Context, m *msg.Message, message stri // Iterate over all comments for the Merge Request, deleting any from the authenticated user func (c *Client) pruneOldComments(ctx context.Context, projectName string, mrID int, notes []*gitlab.Note) error { - _, span := otel.Tracer("Kubechecks").Start(ctx, "pruneOldComments") + _, span := tracer.Start(ctx, "pruneOldComments") defer span.End() log.Debug().Msg("deleting outdated comments") @@ -126,8 +125,8 @@ func (c *Client) pruneOldComments(ctx context.Context, projectName string, mrID return nil } -func (c *Client) TidyOutdatedComments(ctx context.Context, repo *vcs.Repo) error { - _, span := otel.Tracer("Kubechecks").Start(ctx, "TidyOutdatedMessages") +func (c *Client) TidyOutdatedComments(ctx context.Context, pr vcs.PullRequest) error { + _, span := tracer.Start(ctx, "TidyOutdatedMessages") defer span.End() log.Debug().Msg("Tidying outdated comments") @@ -137,7 +136,7 @@ func (c *Client) TidyOutdatedComments(ctx context.Context, repo *vcs.Repo) error for { // list merge request notes - notes, resp, err := c.c.Notes.ListMergeRequestNotes(repo.FullName, repo.CheckID, &gitlab.ListMergeRequestNotesOptions{ + notes, resp, err := c.c.Notes.ListMergeRequestNotes(pr.FullName, pr.CheckID, &gitlab.ListMergeRequestNotesOptions{ Sort: pkg.Pointer("asc"), OrderBy: pkg.Pointer("created_at"), ListOptions: gitlab.ListOptions{ @@ -157,8 +156,8 @@ func (c *Client) TidyOutdatedComments(ctx context.Context, repo *vcs.Repo) error } if strings.ToLower(c.cfg.TidyOutdatedCommentsMode) == "delete" { - return c.pruneOldComments(ctx, repo.FullName, repo.CheckID, allNotes) + return c.pruneOldComments(ctx, pr.FullName, pr.CheckID, allNotes) } - return c.hideOutdatedMessages(ctx, repo.FullName, repo.CheckID, allNotes) + return c.hideOutdatedMessages(ctx, pr.FullName, pr.CheckID, allNotes) } diff --git a/pkg/vcs/gitlab_client/project.go b/pkg/vcs/gitlab_client/project.go index 930254fb..374884e0 100644 --- a/pkg/vcs/gitlab_client/project.go +++ b/pkg/vcs/gitlab_client/project.go @@ -6,7 +6,6 @@ import ( "github.com/cenkalti/backoff/v4" "github.com/xanzy/go-gitlab" - "go.opentelemetry.io/otel" "github.com/zapier/kubechecks/pkg" "github.com/zapier/kubechecks/pkg/repo_config" @@ -25,7 +24,7 @@ func (c *Client) GetProjectByID(project int) (*gitlab.Project, error) { } func (c *Client) GetRepoConfigFile(ctx context.Context, projectId int, mergeReqId int) ([]byte, error) { - _, span := otel.Tracer("Kubechecks").Start(ctx, "GetRepoConfigFile") + _, span := tracer.Start(ctx, "GetRepoConfigFile") defer span.End() // check MR branch diff --git a/pkg/vcs/gitlab_client/status.go b/pkg/vcs/gitlab_client/status.go index 7f6a3874..d17188bb 100644 --- a/pkg/vcs/gitlab_client/status.go +++ b/pkg/vcs/gitlab_client/status.go @@ -19,7 +19,7 @@ const GitlabCommitStatusContext = "kubechecks" var errNoPipelineStatus = errors.New("nil pipeline status") -func (c *Client) CommitStatus(ctx context.Context, repo *vcs.Repo, state pkg.CommitState) error { +func (c *Client) CommitStatus(ctx context.Context, pr vcs.PullRequest, state pkg.CommitState) error { description := fmt.Sprintf("%s %s", state.BareString(), c.ToEmoji(state)) status := &gitlab.SetCommitStatusOptions{ @@ -34,7 +34,7 @@ func (c *Client) CommitStatus(ctx context.Context, repo *vcs.Repo, state pkg.Com var pipelineStatus *gitlab.PipelineInfo getStatusFn := func() error { log.Debug().Msg("getting pipeline status") - pipelineStatus = c.GetLastPipelinesForCommit(repo.FullName, repo.SHA) + pipelineStatus = c.GetLastPipelinesForCommit(pr.FullName, pr.SHA) if pipelineStatus == nil { return errNoPipelineStatus } @@ -50,14 +50,14 @@ func (c *Client) CommitStatus(ctx context.Context, repo *vcs.Repo, state pkg.Com } log.Debug(). - Str("project", repo.FullName). - Str("commit_sha", repo.SHA). + Str("project", pr.FullName). + Str("commit_sha", pr.SHA). Str("kubechecks_status", description). Str("gitlab_status", string(status.State)). Msg("gitlab client: updating commit status") - _, err = c.setCommitStatus(repo.FullName, repo.SHA, status) + _, err = c.setCommitStatus(pr.FullName, pr.SHA, status) if err != nil { - log.Error().Err(err).Str("project", repo.FullName).Msg("gitlab client: could not set commit status") + log.Error().Err(err).Str("project", pr.FullName).Msg("gitlab client: could not set commit status") return err } return nil diff --git a/pkg/vcs/repo.go b/pkg/vcs/repo.go index fb31f530..4ec42bb7 100644 --- a/pkg/vcs/repo.go +++ b/pkg/vcs/repo.go @@ -1,33 +1,14 @@ package vcs import ( - "bufio" - "context" - "fmt" - "io/fs" - "net/url" - "os" - "os/exec" - "path/filepath" - "strings" - "sync" - - "github.com/pkg/errors" - "github.com/rs/zerolog/log" - "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/trace" - "github.com/zapier/kubechecks/pkg/config" - "github.com/zapier/kubechecks/telemetry" ) -// Repo represents a local Repostiory on disk, based off of a PR/MR -type Repo struct { +// PullRequest represents an PR/MR +type PullRequest struct { BaseRef string // base ref is the branch that the PR is being merged into HeadRef string // head ref is the branch that the PR is coming from DefaultBranch string // Some repos have default branches we need to capture - RepoDir string // The directory where the repo is cloned Remote string // Remote address CloneURL string // Where we clone the repo from Name string // Name of the repo @@ -41,269 +22,3 @@ type Repo struct { Config config.ServerConfig } - -func (r *Repo) CloneRepoLocal(ctx context.Context, repoDir string) error { - // Attempt to locally clone the repo based on the provided information stored within - _, span := otel.Tracer("Kubechecks").Start(ctx, "CloneRepo") - defer span.End() - - // TODO: Look if this is still needed - r.RepoDir = repoDir - - cmd := r.execCommand("git", "clone", r.CloneURL, repoDir) - out, err := cmd.CombinedOutput() - if err != nil { - log.Error().Err(err).Msgf("unable to clone repository, %s", out) - return err - } - - cmd = r.execCommand("git", "remote") - pipe, _ := cmd.StdoutPipe() - var wg sync.WaitGroup - scanner := bufio.NewScanner(pipe) - wg.Add(1) - go func() { - for scanner.Scan() { - line := scanner.Text() - r.Remote = line - // Just grab the first remote as it should be the default - break - } - wg.Done() - }() - err = cmd.Start() - if err != nil { - telemetry.SetError(span, err, "unable to get remote") - log.Error().Err(err).Msg("unable to get git remote") - return err - } - wg.Wait() - err = cmd.Wait() - if err != nil { - telemetry.SetError(span, err, "unable to get remote") - log.Error().Err(err).Msg("unable to get git remote") - return err - } - - if log.Trace().Enabled() { - // print contents of repo - //nolint - filepath.WalkDir(repoDir, walk) - } - - // Print the path to the cloned repository - log.Info().Str("project", r.Name).Str("ref", r.HeadRef).Msgf("Repository cloned to: %s", r.RepoDir) - - return nil -} - -func (r *Repo) MergeIntoTarget(ctx context.Context) error { - // Merge the last commit into a tmp branch off of the target branch - _, span := otel.Tracer("Kubechecks").Start(ctx, "Repo - RepoMergeIntoTarget", - trace.WithAttributes( - attribute.String("check_id", fmt.Sprintf("%d", r.CheckID)), - attribute.String("project", r.Name), - attribute.String("source_branch", r.HeadRef), - attribute.String("target_branch", r.BaseRef), - attribute.String("default_branch", r.DefaultBranch), - attribute.String("last_commit_id", r.SHA), - )) - defer span.End() - - log.Debug().Msgf("Merging MR commit %s into a tmp branch off of %s for manifest generation...", r.SHA, r.BaseRef) - cmd := r.execCommand("git", "fetch", r.Remote, r.BaseRef) - err := cmd.Run() - if err != nil { - telemetry.SetError(span, err, "git fetch remote into target branch") - log.Error().Err(err).Msgf("unable to fetch %s", r.BaseRef) - return err - } - - cmd = r.execCommand("git", "checkout", "-b", "tmp", fmt.Sprintf("%s/%s", r.Remote, r.BaseRef)) - _, err = cmd.Output() - if err != nil { - telemetry.SetError(span, err, "git checkout tmp branch") - log.Error().Err(err).Msgf("unable to checkout %s %s", r.Remote, r.BaseRef) - return err - } - - cmd = r.execCommand("git", "merge", r.SHA) - out, err := cmd.CombinedOutput() - if err != nil { - telemetry.SetError(span, err, "merge last commit id into tmp branch") - log.Error().Err(err).Msgf("unable to merge %s, %s", r.SHA, out) - return err - } - - return nil -} - -// GetListOfRepoFiles returns a list of all files in the local repository -func (r *Repo) GetListOfRepoFiles() ([]string, error) { - files := []string{} - walkFn := func(s string, d fs.DirEntry, err error) error { - if err != nil { - return err - } - if !d.IsDir() { - // make path relative to repo root - rel, _ := filepath.Rel(r.RepoDir, s) - if strings.HasPrefix(rel, ".git") { - // ignore files in .git subdir - return nil - } - - files = append(files, rel) - } - return nil - } - - err := filepath.WalkDir(r.RepoDir, walkFn) - - return files, err -} - -// GetListOfChangedFiles returns a list of files that have changed between the current branch and the target branch -func (r *Repo) GetListOfChangedFiles(ctx context.Context) ([]string, error) { - _, span := otel.Tracer("Kubechecks").Start(ctx, "RepoGetListOfChangedFiles") - defer span.End() - - var fileList = []string{} - - cmd := r.execCommand("git", "diff", "--name-only", fmt.Sprintf("%s/%s", r.Remote, r.BaseRef)) - pipe, _ := cmd.StdoutPipe() - var wg sync.WaitGroup - scanner := bufio.NewScanner(pipe) - wg.Add(1) - go func() { - for scanner.Scan() { - line := scanner.Text() - fileList = append(fileList, line) - } - wg.Done() - }() - err := cmd.Start() - if err != nil { - log.Error().Err(err).Msg("unable to start diff command") - return nil, err - } - wg.Wait() - err = cmd.Wait() - if err != nil { - log.Error().Err(err).Msg("unable to diff branches") - return nil, err - } - - return fileList, nil -} - -func walk(s string, d fs.DirEntry, err error) error { - if err != nil { - return err - } - if !d.IsDir() { - println(s) - } - return nil -} - -func (r *Repo) execCommand(name string, args ...string) *exec.Cmd { - cmd := execCommand(r.Config, name, args...) - cmd.Dir = r.RepoDir - return cmd -} - -func censorVcsToken(cfg config.ServerConfig, args []string) []string { - vcsToken := cfg.VcsToken - if len(vcsToken) == 0 { - return args - } - - var argsToLog []string - for _, arg := range args { - argsToLog = append(argsToLog, strings.Replace(arg, vcsToken, "********", 10)) - } - return argsToLog -} - -func execCommand(cfg config.ServerConfig, name string, args ...string) *exec.Cmd { - argsToLog := censorVcsToken(cfg, args) - - log.Debug().Strs("args", argsToLog).Msg("building command") - cmd := exec.Command(name, args...) - return cmd -} - -// InitializeGitSettings ensures Git auth is set up for cloning -func InitializeGitSettings(cfg config.ServerConfig, vcsClient VcsClient) error { - email := vcsClient.Email() - username := vcsClient.Username() - - cmd := execCommand(cfg, "git", "config", "--global", "user.email", email) - err := cmd.Run() - if err != nil { - return errors.Wrap(err, "failed to set git email address") - } - - cmd = execCommand(cfg, "git", "config", "--global", "user.name", username) - err = cmd.Run() - if err != nil { - return errors.Wrap(err, "failed to set git user name") - } - - cloneUrl, err := getCloneUrl(username, cfg) - if err != nil { - return errors.Wrap(err, "failed to get clone url") - } - - homedir, err := os.UserHomeDir() - if err != nil { - if err != nil { - return errors.Wrap(err, "unable to get home directory") - } - } - outfile, err := os.Create(fmt.Sprintf("%s/.git-credentials", homedir)) - if err != nil { - return errors.Wrap(err, "unable to create credentials file") - } - defer outfile.Close() - - cmd = execCommand(cfg, "echo", cloneUrl) - cmd.Stdout = outfile - err = cmd.Run() - if err != nil { - return errors.Wrap(err, "unable to set git credentials") - } - - cmd = execCommand(cfg, "git", "config", "--global", "credential.helper", "store") - err = cmd.Run() - if err != nil { - return errors.Wrap(err, "unable to set git credential usage") - } - log.Debug().Msg("git credentials set") - - return nil -} - -func getCloneUrl(user string, cfg config.ServerConfig) (string, error) { - vcsBaseUrl := cfg.VcsBaseUrl - vcsType := cfg.VcsType - vcsToken := cfg.VcsToken - - var hostname, scheme string - - if vcsBaseUrl == "" { - // hack: but it does happen to work for now - hostname = fmt.Sprintf("%s.com", vcsType) - scheme = "https" - } else { - parts, err := url.Parse(vcsBaseUrl) - if err != nil { - return "", errors.Wrapf(err, "failed to parse %q", vcsBaseUrl) - } - hostname = parts.Host - scheme = parts.Scheme - } - - return fmt.Sprintf("%s://%s:%s@%s", scheme, user, vcsToken, hostname), nil -} diff --git a/pkg/vcs/repo_test.go b/pkg/vcs/repo_test.go deleted file mode 100644 index 25f4fb50..00000000 --- a/pkg/vcs/repo_test.go +++ /dev/null @@ -1,80 +0,0 @@ -package vcs - -import ( - "fmt" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/zapier/kubechecks/pkg/config" -) - -func TestGetCloneUrl(t *testing.T) { - // common defaults - const ( - testToken = "test-token" - testUser = "test-user" - ) - - testcases := []struct { - name string - expected string - - vcsType string - vcsBaseUrl string - }{ - { - name: "gitlab default", - vcsType: "gitlab", - expected: "https://%s:%s@gitlab.com", - }, - { - name: "github default", - vcsType: "github", - expected: "https://%s:%s@github.com", - }, - { - name: "can override the host", - vcsType: "github", - vcsBaseUrl: "https://some.url.com/", - expected: "https://%s:%s@some.url.com", - }, - { - name: "can override the protocol", - vcsType: "github", - vcsBaseUrl: "http://some.url.com/", - expected: "http://%s:%s@some.url.com", - }, - } - - for _, tc := range testcases { - t.Run(tc.name, func(t *testing.T) { - assert.NotEqual(t, "", tc.vcsType) - - cfg := config.ServerConfig{ - VcsToken: testToken, - VcsType: tc.vcsType, - VcsBaseUrl: tc.vcsBaseUrl, - } - - actual, err := getCloneUrl(testUser, cfg) - require.NoError(t, err) - - expected := fmt.Sprintf(tc.expected, testUser, testToken) - require.Equal(t, expected, actual) - }) - } -} - -func TestCensorVcsToken(t *testing.T) { - cfg := config.ServerConfig{VcsToken: "hre"} - result := censorVcsToken(cfg, []string{"one", "two", "three"}) - assert.Equal(t, []string{"one", "two", "t********e"}, result) -} - -func TestCensorEmptyVcsToken(t *testing.T) { - cfg := config.ServerConfig{VcsToken: ""} - result := censorVcsToken(cfg, []string{"one", "two", "three"}) - assert.Equal(t, []string{"one", "two", "three"}, result) -} diff --git a/pkg/vcs/types.go b/pkg/vcs/types.go index 12e6b3ba..8b0cf652 100644 --- a/pkg/vcs/types.go +++ b/pkg/vcs/types.go @@ -14,18 +14,18 @@ type WebHookConfig struct { Events []string } -// VcsClient represents a VCS client -type VcsClient interface { +// Client represents a VCS client +type Client interface { // PostMessage takes in project name in form "owner/repo" (ie zapier/kubechecks), the PR/MR id, and the actual message - PostMessage(context.Context, *Repo, int, string) *msg.Message + PostMessage(context.Context, PullRequest, string) *msg.Message // UpdateMessage update a message with new content UpdateMessage(context.Context, *msg.Message, string) error // VerifyHook validates a webhook secret and return the body; must be called even if no secret VerifyHook(*http.Request, string) ([]byte, error) // ParseHook parses webook payload for valid events - ParseHook(*http.Request, []byte) (*Repo, error) + ParseHook(*http.Request, []byte) (PullRequest, error) // CommitStatus sets a status for a specific commit on the remote VCS - CommitStatus(context.Context, *Repo, pkg.CommitState) error + CommitStatus(context.Context, PullRequest, pkg.CommitState) error // GetHookByUrl gets a webhook by url GetHookByUrl(ctx context.Context, repoName, webhookUrl string) (*WebHookConfig, error) // CreateHook creates a webhook that points at kubechecks @@ -33,9 +33,9 @@ type VcsClient interface { // GetName returns the VCS client name (e.g. "github" or "gitlab") GetName() string // TidyOutdatedComments either by hiding or deleting them - TidyOutdatedComments(context.Context, *Repo) error + TidyOutdatedComments(context.Context, PullRequest) error // LoadHook creates an EventRequest from the ID of an actual request - LoadHook(ctx context.Context, repoAndId string) (*Repo, error) + LoadHook(ctx context.Context, repoAndId string) (PullRequest, error) Username() string Email() string From cb56452a325da6805b0e1a33f351a015c0029d04 Mon Sep 17 00:00:00 2001 From: Joseph Lombrozo Date: Thu, 21 Mar 2024 10:27:03 -0400 Subject: [PATCH 05/10] clone schema and policy locations, other improvements (#163) * clone schema and policy locations * actually use policy locations * fix clone url * can't do '--branch HEAD' * conftest has to work on the rendered manifets * update static repos every 5 minutes * use unmarshalling to get a config object * support disabling or diminishing certain checks --- .github/workflows/maintenance.yaml | 23 ---- cmd/container.go | 4 +- cmd/controller_cmd.go | 19 ++- cmd/flags.go | 4 + cmd/locations.go | 82 ++++++++++++ cmd/locations_test.go | 173 +++++++++++++++++++++++++ cmd/processors.go | 29 +++-- cmd/root.go | 20 ++- docs/usage.md | 5 + pkg/checks/diff/ai_summary.go | 2 +- pkg/checks/kubeconform/validate.go | 13 +- pkg/checks/preupgrade/kubepug.go | 2 +- pkg/checks/rego/check.go | 201 ++++++++++++++++++++++++++++- pkg/checks/rego/check_test.go | 184 ++++++++++++++++++++++++++ pkg/checks/rego/conftest.go | 144 --------------------- pkg/checks/types.go | 6 +- pkg/commitState.go | 29 +++++ pkg/config/config.go | 114 ++++++++-------- pkg/config/config_test.go | 25 ++++ pkg/container/main.go | 3 + pkg/events/check.go | 2 +- pkg/events/runner.go | 21 +-- pkg/git/repo.go | 7 +- pkg/utils.go | 15 +++ 24 files changed, 851 insertions(+), 276 deletions(-) delete mode 100644 .github/workflows/maintenance.yaml create mode 100644 cmd/locations.go create mode 100644 cmd/locations_test.go create mode 100644 pkg/checks/rego/check_test.go delete mode 100644 pkg/checks/rego/conftest.go create mode 100644 pkg/config/config_test.go diff --git a/.github/workflows/maintenance.yaml b/.github/workflows/maintenance.yaml deleted file mode 100644 index aa73a36f..00000000 --- a/.github/workflows/maintenance.yaml +++ /dev/null @@ -1,23 +0,0 @@ -# have to disable this, it's deleting untagged layers that are referenced by manifests - -#name: Maintenance -# -## Deleting untagged images requires a Personal Access Token, it cannot -## be done with a github action built in token. This is a known limitation. -# -#on: -# schedule: -# - cron: "0 0 * * SUN" -# workflow_dispatch: -# -#jobs: -# delete-untagged-images: -# name: delete untagged images -# runs-on: ubuntu-22.04 -# steps: -# - uses: Chizkiyahu/delete-untagged-ghcr-action@v3 -# with: -# owner_type: org -# package_name: kubechecks -# token: ${{ secrets.MAINTENANCE_TOKEN }} -# untagged_only: true diff --git a/cmd/container.go b/cmd/container.go index bc45b9d9..ec04433c 100644 --- a/cmd/container.go +++ b/cmd/container.go @@ -11,6 +11,7 @@ import ( "github.com/zapier/kubechecks/pkg/argo_client" "github.com/zapier/kubechecks/pkg/config" "github.com/zapier/kubechecks/pkg/container" + "github.com/zapier/kubechecks/pkg/git" "github.com/zapier/kubechecks/pkg/vcs/github_client" "github.com/zapier/kubechecks/pkg/vcs/gitlab_client" ) @@ -19,7 +20,8 @@ func newContainer(ctx context.Context, cfg config.ServerConfig, watchApps bool) var err error var ctr = container.Container{ - Config: cfg, + Config: cfg, + RepoManager: git.NewRepoManager(cfg), } // create vcs client diff --git a/cmd/controller_cmd.go b/cmd/controller_cmd.go index 72b0c6b1..ecf47124 100644 --- a/cmd/controller_cmd.go +++ b/cmd/controller_cmd.go @@ -46,6 +46,18 @@ var ControllerCmd = &cobra.Command{ log.Fatal().Err(err).Msg("failed to create container") } + log.Info().Msg("initializing git settings") + if err = initializeGit(ctr); err != nil { + log.Fatal().Err(err).Msg("failed to initialize git settings") + } + + if err = processLocations(ctx, ctr, cfg.PoliciesLocation); err != nil { + log.Fatal().Err(err).Msg("failed to process policy locations") + } + if err = processLocations(ctx, ctr, cfg.SchemasLocations); err != nil { + log.Fatal().Err(err).Msg("failed to process schema locations") + } + processors, err := getProcessors(ctr) if err != nil { log.Fatal().Err(err).Msg("failed to create processors") @@ -57,11 +69,6 @@ var ControllerCmd = &cobra.Command{ } defer t.Shutdown() - log.Info().Msg("initializing git settings") - if err = initializeGit(ctr); err != nil { - log.Fatal().Err(err).Msg("failed to initialize git settings") - } - log.Info().Msgf("starting web server") startWebserver(ctx, ctr, processors) @@ -129,7 +136,7 @@ func init() { stringFlag(flags, "fallback-k8s-version", "Fallback target Kubernetes version for schema / upgrade checks (KUBECHECKS_FALLBACK_K8S_VERSION).", newStringOpts().withDefault("1.23.0")) boolFlag(flags, "show-debug-info", "Set to true to print debug info to the footer of MR comments (KUBECHECKS_SHOW_DEBUG_INFO).") - boolFlag(flags, "enable-conftest", "Set to true to enable conftest policy checking of manifests (KUBECHECKS_ENABLE_CONFTEST).") + stringFlag(flags, "label-filter", `(Optional) If set, The label that must be set on an MR (as "kubechecks:") for kubechecks to process the merge request webhook (KUBECHECKS_LABEL_FILTER).`) stringFlag(flags, "openai-api-token", "OpenAI API Token.") stringFlag(flags, "webhook-url-base", "The endpoint to listen on for incoming PR/MR event webhooks. For example, 'https://checker.mycompany.com'.") diff --git a/cmd/flags.go b/cmd/flags.go index 9eadd9a2..13f67509 100644 --- a/cmd/flags.go +++ b/cmd/flags.go @@ -36,6 +36,10 @@ func boolFlag(flags *pflag.FlagSet, name, usage string, opts ...DocOpt[bool]) { addFlag(name, usage, opts, flags.Bool, flags.BoolP) } +func newBoolOpts() DocOpt[bool] { + return DocOpt[bool]{} +} + func newStringOpts() DocOpt[string] { return DocOpt[string]{} } diff --git a/cmd/locations.go b/cmd/locations.go new file mode 100644 index 00000000..13c805d4 --- /dev/null +++ b/cmd/locations.go @@ -0,0 +1,82 @@ +package cmd + +import ( + "context" + "path/filepath" + "strings" + "time" + + "github.com/docker/docker/builder/remotecontext/urlutil" + "github.com/pkg/errors" + "github.com/rs/zerolog/log" + + "github.com/zapier/kubechecks/pkg" + "github.com/zapier/kubechecks/pkg/container" + "github.com/zapier/kubechecks/pkg/git" +) + +func processLocations(ctx context.Context, ctr container.Container, locations []string) error { + for index, location := range locations { + if newLocation, err := maybeCloneGitUrl(ctx, ctr.RepoManager, location, ctr.VcsClient.Username()); err != nil { + return errors.Wrapf(err, "failed to clone %q", location) + } else if newLocation != "" { + locations[index] = newLocation + } + } + + return nil +} + +type cloner interface { + Clone(ctx context.Context, cloneUrl, branchName string) (*git.Repo, error) +} + +var ErrCannotUseQueryWithFilePath = errors.New("relative and absolute file paths cannot have query parameters") + +func maybeCloneGitUrl(ctx context.Context, repoManager cloner, location, vcsUsername string) (string, error) { + result := strings.SplitN(location, "?", 2) + if !urlutil.IsGitURL(result[0]) { + if len(result) > 1 { + return "", ErrCannotUseQueryWithFilePath + } + return result[0], nil + } + + repoUrl, query, err := pkg.NormalizeRepoUrl(location) + if err != nil { + return "", errors.Wrapf(err, "invalid git url: %q", location) + } + cloneUrl := repoUrl.CloneURL(vcsUsername) + + repo, err := repoManager.Clone(ctx, cloneUrl, query.Get("branch")) + if err != nil { + return "", errors.Wrap(err, "failed to clone") + } + + go func() { + tick := time.Tick(time.Minute * 5) + for { + select { + case <-ctx.Done(): + return + case <-tick: + } + + if err := repo.Update(ctx); err != nil { + log.Warn(). + Err(err). + Str("path", repo.Directory). + Str("url", repo.CloneURL). + Msg("failed to update repo") + } + } + }() + + path := repo.Directory + subdir := query.Get("subdir") + if subdir != "" { + path = filepath.Join(path, subdir) + } + + return path, nil +} diff --git a/cmd/locations_test.go b/cmd/locations_test.go new file mode 100644 index 00000000..5f3c9f0d --- /dev/null +++ b/cmd/locations_test.go @@ -0,0 +1,173 @@ +package cmd + +import ( + "context" + "fmt" + "testing" + + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/zapier/kubechecks/pkg/git" +) + +type fakeCloner struct { + cloneUrl, branchName string + result *git.Repo + err error +} + +func (f *fakeCloner) Clone(ctx context.Context, cloneUrl, branchName string) (*git.Repo, error) { + f.cloneUrl = cloneUrl + f.branchName = branchName + return f.result, f.err +} + +const testRoot = "/tmp/path" +const testUsername = "username" + +func TestMaybeCloneGitUrl_HappyPath(t *testing.T) { + var ( + ctx = context.TODO() + ) + + type expected struct { + path, cloneUrl, branch string + } + type testcase struct { + name, input string + expected expected + } + + testcases := []testcase{ + { + name: "ssh clone url", + input: "git@gitlab.com:org/team/project.git", + expected: expected{ + path: testRoot, + cloneUrl: fmt.Sprintf("https://%s@gitlab.com/org/team/project", testUsername), + }, + }, + { + name: "http clone url", + input: "https://gitlab.com/org/team/project.git", + expected: expected{ + path: testRoot, + cloneUrl: fmt.Sprintf("https://%s@gitlab.com/org/team/project", testUsername), + }, + }, + { + name: "ssh clone url with subdir", + input: "git@gitlab.com:org/team/project.git?subdir=/charts", + expected: expected{ + cloneUrl: fmt.Sprintf("https://%s@gitlab.com/org/team/project", testUsername), + path: fmt.Sprintf("%s/charts", testRoot), + }, + }, + { + name: "http clone url with subdir", + input: "https://gitlab.com/org/team/project.git?subdir=/charts", + expected: expected{ + cloneUrl: fmt.Sprintf("https://%s@gitlab.com/org/team/project", testUsername), + path: fmt.Sprintf("%s/charts", testRoot), + }, + }, + { + name: "ssh clone url with subdir without slash prefix", + input: "git@gitlab.com:org/team/project.git?subdir=charts", + expected: expected{ + cloneUrl: fmt.Sprintf("https://%s@gitlab.com/org/team/project", testUsername), + path: fmt.Sprintf("%s/charts", testRoot), + }, + }, + { + name: "http clone url with subdir without slash prefix", + input: "https://gitlab.com/org/team/project.git?subdir=charts", + expected: expected{ + cloneUrl: fmt.Sprintf("https://%s@gitlab.com/org/team/project", testUsername), + path: fmt.Sprintf("%s/charts", testRoot), + }, + }, + { + name: "local path with slash prefix", + input: "/tmp/output", + expected: expected{ + path: "/tmp/output", + }, + }, + { + name: "local path without slash prefix", + input: "tmp/output", + expected: expected{ + path: "tmp/output", + }, + }, + } + + for _, tc := range testcases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + fc := &fakeCloner{result: &git.Repo{Directory: testRoot}, err: nil} + actual, err := maybeCloneGitUrl(ctx, fc, tc.input, testUsername) + require.NoError(t, err) + assert.Equal(t, tc.expected.branch, fc.branchName) + assert.Equal(t, tc.expected.cloneUrl, fc.cloneUrl) + assert.Equal(t, tc.expected.path, actual) + }) + } +} + +func TestMaybeCloneGitUrl_URLError(t *testing.T) { + var ( + ctx = context.TODO() + ) + + testcases := []struct { + name, input, expected string + }{ + { + name: "cannot use query with file path", + input: "/blahblah?subdir=blah", + expected: "relative and absolute file paths cannot have query parameters", + }, + } + + for _, tc := range testcases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + fc := &fakeCloner{result: &git.Repo{Directory: testRoot}, err: nil} + result, err := maybeCloneGitUrl(ctx, fc, tc.input, testUsername) + require.ErrorContains(t, err, tc.expected) + require.Equal(t, "", result) + }) + } +} + +func TestMaybeCloneGitUrl_CloneError(t *testing.T) { + testcases := []struct { + name, input, expected string + cloneError error + }{ + { + name: "failed to clone", + input: "github.com/blah/blah", + cloneError: errors.New("blahblah"), + expected: "failed to clone: blahblah", + }, + } + + for _, tc := range testcases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + fc := &fakeCloner{result: &git.Repo{Directory: testRoot}, err: tc.cloneError} + result, err := maybeCloneGitUrl(ctx, fc, tc.input, testUsername) + require.ErrorContains(t, err, tc.expected) + require.Equal(t, "", result) + }) + } +} diff --git a/cmd/processors.go b/cmd/processors.go index d4da71cf..3228d691 100644 --- a/cmd/processors.go +++ b/cmd/processors.go @@ -14,20 +14,26 @@ import ( func getProcessors(ctr container.Container) ([]checks.ProcessorEntry, error) { var procs []checks.ProcessorEntry - procs = append(procs, checks.ProcessorEntry{ - Name: "validating app against schema", - Processor: kubeconform.Check, - }) - procs = append(procs, checks.ProcessorEntry{ Name: "generating diff for app", Processor: diff.Check, }) - procs = append(procs, checks.ProcessorEntry{ - Name: "running pre-upgrade check", - Processor: preupgrade.Check, - }) + if ctr.Config.EnableKubeConform { + procs = append(procs, checks.ProcessorEntry{ + Name: "validating app against schema", + Processor: kubeconform.Check, + WorstState: ctr.Config.WorstKubeConformState, + }) + } + + if ctr.Config.EnablePreupgrade { + procs = append(procs, checks.ProcessorEntry{ + Name: "running pre-upgrade check", + Processor: preupgrade.Check, + WorstState: ctr.Config.WorstPreupgradeState, + }) + } if ctr.Config.EnableConfTest { checker, err := rego.NewChecker(ctr.Config) @@ -36,8 +42,9 @@ func getProcessors(ctr container.Container) ([]checks.ProcessorEntry, error) { } procs = append(procs, checks.ProcessorEntry{ - Name: "validation policy", - Processor: checker.Check, + Name: "validation policy", + Processor: checker.Check, + WorstState: ctr.Config.WorstConfTestState, }) } diff --git a/cmd/root.go b/cmd/root.go index fd0b6a39..52fc3698 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -67,18 +67,32 @@ func init() { stringFlag(flags, "tidy-outdated-comments-mode", "Sets the mode to use when tidying outdated comments.", newStringOpts(). withChoices("hide", "delete"). - withDefault("hide"), - ) + withDefault("hide")) stringSliceFlag(flags, "schemas-location", "Sets schema locations to be used for every check request. Can be common paths inside the repos being checked or git urls in either git or http(s) format.", newStringSliceOpts(). withDefault([]string{"./schemas"})) + boolFlag(flags, "enable-conftest", "Set to true to enable conftest policy checking of manifests.") stringSliceFlag(flags, "policies-location", "Sets rego policy locations to be used for every check request. Can be common path inside the repos being checked or git urls in either git or http(s) format.", newStringSliceOpts(). withDefault([]string{"./policies"})) + stringFlag(flags, "worst-conftest-state", "The worst state that can be returned from conftest.", + newStringOpts(). + withDefault("panic")) + boolFlag(flags, "enable-kubeconform", "Enable kubeconform checks.", + newBoolOpts(). + withDefault(true)) + stringFlag(flags, "worst-kubeconform-state", "The worst state that can be returned from kubeconform.", + newStringOpts(). + withDefault("panic")) + boolFlag(flags, "enable-preupgrade", "Enable preupgrade checks.", + newBoolOpts(). + withDefault(true)) + stringFlag(flags, "worst-preupgrade-state", "The worst state that can be returned from preupgrade checks.", + newStringOpts(). + withDefault("panic")) panicIfError(viper.BindPFlags(flags)) - setupLogOutput() } diff --git a/docs/usage.md b/docs/usage.md index aff7846a..cfda7a43 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -40,6 +40,8 @@ The full list of supported environment variables is described below: |`KUBECHECKS_ARGOCD_API_SERVER_ADDR`|ArgoCD API Server Address.|`argocd-server`| |`KUBECHECKS_ARGOCD_API_TOKEN`|ArgoCD API token.|| |`KUBECHECKS_ENABLE_CONFTEST`|Set to true to enable conftest policy checking of manifests.|`false`| +|`KUBECHECKS_ENABLE_KUBECONFORM`|Enable kubeconform checks.|`true`| +|`KUBECHECKS_ENABLE_PREUPGRADE`|Enable preupgrade checks.|`true`| |`KUBECHECKS_ENSURE_WEBHOOKS`|Ensure that webhooks are created in repositories referenced by argo.|`false`| |`KUBECHECKS_FALLBACK_K8S_VERSION`|Fallback target Kubernetes version for schema / upgrade checks.|`1.23.0`| |`KUBECHECKS_KUBERNETES_CONFIG`|Path to your kubernetes config file, used to monitor applications.|| @@ -61,3 +63,6 @@ The full list of supported environment variables is described below: |`KUBECHECKS_WEBHOOK_SECRET`|Optional secret key for validating the source of incoming webhooks.|| |`KUBECHECKS_WEBHOOK_URL_BASE`|The endpoint to listen on for incoming PR/MR event webhooks. For example, 'https://checker.mycompany.com'.|| |`KUBECHECKS_WEBHOOK_URL_PREFIX`|If your application is running behind a proxy that uses path based routing, set this value to match the path prefix. For example, '/hello/world'.|| +|`KUBECHECKS_WORST_CONFTEST_STATE`|The worst state that can be returned from conftest.|`panic`| +|`KUBECHECKS_WORST_KUBECONFORM_STATE`|The worst state that can be returned from kubeconform.|`panic`| +|`KUBECHECKS_WORST_PREUPGRADE_STATE`|The worst state that can be returned from preupgrade checks.|`panic`| diff --git a/pkg/checks/diff/ai_summary.go b/pkg/checks/diff/ai_summary.go index 1efa190d..53969117 100644 --- a/pkg/checks/diff/ai_summary.go +++ b/pkg/checks/diff/ai_summary.go @@ -12,7 +12,7 @@ import ( "github.com/zapier/kubechecks/telemetry" ) -var tracer = otel.Tracer("pkg/diff") +var tracer = otel.Tracer("pkg/checks/diff") func aiDiffSummary(ctx context.Context, mrNote *msg.Message, cfg config.ServerConfig, name string, manifests []string, diff string) { ctx, span := tracer.Start(ctx, "aiDiffSummary") diff --git a/pkg/checks/kubeconform/validate.go b/pkg/checks/kubeconform/validate.go index e70769fb..71439ef3 100644 --- a/pkg/checks/kubeconform/validate.go +++ b/pkg/checks/kubeconform/validate.go @@ -18,7 +18,7 @@ import ( "github.com/zapier/kubechecks/pkg/msg" ) -var tracer = otel.Tracer("pkg/validate") +var tracer = otel.Tracer("pkg/checks/kubeconform") func getSchemaLocations(ctx context.Context, ctr container.Container, tempRepoPath string) []string { cfg := ctr.Config @@ -65,15 +65,6 @@ func getSchemaLocations(ctx context.Context, ctr container.Container, tempRepoPa return locations } -func wipeDir(dir string) { - if err := os.RemoveAll(dir); err != nil { - log.Error(). - Err(err). - Str("path", dir). - Msg("failed to wipe path") - } -} - func argoCdAppValidate(ctx context.Context, ctr container.Container, appName, targetKubernetesVersion, tempRepoPath string, appManifests []string) (msg.Result, error) { _, span := tracer.Start(ctx, "ArgoCdAppValidate") defer span.End() @@ -84,7 +75,7 @@ func argoCdAppValidate(ctx context.Context, ctr container.Container, appName, ta if err != nil { return msg.Result{}, errors.Wrap(err, "failed to create schema cache") } - defer wipeDir(schemaCachePath) + defer pkg.WipeDir(schemaCachePath) vOpts := validator.Opts{ Cache: schemaCachePath, diff --git a/pkg/checks/preupgrade/kubepug.go b/pkg/checks/preupgrade/kubepug.go index e3f40882..b1de363a 100644 --- a/pkg/checks/preupgrade/kubepug.go +++ b/pkg/checks/preupgrade/kubepug.go @@ -20,7 +20,7 @@ import ( const docLinkFmt = "[%s Deprecation Notes](https://kubernetes.io/docs/reference/using-api/deprecation-guide/#%s-v%d%d)" -var tracer = otel.Tracer("pkg/kubepug") +var tracer = otel.Tracer("pkg/checks/preupgrade") func checkApp(ctx context.Context, appName, targetKubernetesVersion string, manifests []string) (msg.Result, error) { _, span := tracer.Start(ctx, "KubePug") diff --git a/pkg/checks/rego/check.go b/pkg/checks/rego/check.go index e40f0ec5..53b02119 100644 --- a/pkg/checks/rego/check.go +++ b/pkg/checks/rego/check.go @@ -1,15 +1,36 @@ package rego import ( + "bytes" "context" + "fmt" + "io" + "os" + "path/filepath" + "strings" + "github.com/ghodss/yaml" + "github.com/olekukonko/tablewriter" + "github.com/open-policy-agent/conftest/output" + "github.com/open-policy-agent/conftest/parser" + "github.com/open-policy-agent/conftest/runner" "github.com/pkg/errors" + "github.com/rs/zerolog/log" + "go.opentelemetry.io/otel" + "github.com/zapier/kubechecks/pkg" "github.com/zapier/kubechecks/pkg/checks" "github.com/zapier/kubechecks/pkg/config" "github.com/zapier/kubechecks/pkg/msg" + "github.com/zapier/kubechecks/telemetry" ) +var tracer = otel.Tracer("pkg/checks/rego") + +type emojiable interface { + ToEmoji(state pkg.CommitState) string +} + type Checker struct { locations []string } @@ -19,26 +40,192 @@ var ErrNoLocationsConfigured = errors.New("no policy locations configured") func NewChecker(cfg config.ServerConfig) (*Checker, error) { var c Checker - var locations []string - if len(locations) == 0 { + c.locations = cfg.PoliciesLocation + if len(c.locations) == 0 { return nil, ErrNoLocationsConfigured } return &c, nil } +var ErrResourceMustHaveKind = errors.New("resource does not have kind") +var ErrResourceMustHaveMetadata = errors.New("resource does not have metadata") +var ErrResourceMustHaveName = errors.New("resource does not have name") + +func getFilenameFromRawManifest(manifest string) (string, error) { + resource := make(map[string]interface{}) + if err := yaml.Unmarshal([]byte(manifest), &resource); err != nil { + return "", errors.Wrap(err, "failed to unmarshal resource") + } + + kind, okKind := resource["kind"].(string) + if !okKind { + return "", ErrResourceMustHaveKind + } + + metadata, okMetadata := resource["metadata"].(map[string]interface{}) + if !okMetadata { + return "", ErrResourceMustHaveMetadata + } + + name, okName := metadata["name"].(string) + if !okName { + return "", ErrResourceMustHaveName + } + + namespace, okNamespace := metadata["namespace"].(string) + if !okNamespace { + return fmt.Sprintf("kind=%s,name=%s.yaml", kind, name), nil + } + + return fmt.Sprintf("namespace=%s,kind=%s,name=%s.yaml", namespace, kind, name), nil +} + +func dumpFiles(manifests []string) (string, error) { + result, err := os.MkdirTemp("", "kubechecks-manifests-") + if err != nil { + return "", errors.Wrap(err, "failed to create temp dir") + } + + log.Debug(). + Int("manifest_count", len(manifests)). + Msg("dumping manifests") + + for index, manifest := range manifests { + filename, err := getFilenameFromRawManifest(manifest) + if err != nil { + return result, errors.Wrap(err, "failed to get filename from manifest") + } + + fullPath := filepath.Join(result, filename) + manifestBytes := []byte(manifest) + log.Debug(). + Str("path", fullPath). + Int("index", index). + Int("size", len(manifestBytes)). + Msg("dumping manifest") + + if err = os.WriteFile(fullPath, manifestBytes, 0o666); err != nil { + return result, errors.Wrapf(err, "failed to write %s", filename) + } + } + + return result, nil +} + +// Check runs the conftest validation against an application in a given repository +// path. It generates a summary string with the results, which can later be posted +// as a GitLab comment. The validation checks resources against Zapier policies and +// provides feedback for warnings or errors as informational messages. Failure to +// pass a policy check currently does not block deploy. func (c *Checker) Check(ctx context.Context, request checks.Request) (msg.Result, error) { - argoApp, err := request.Container.ArgoClient.GetApplicationByName(ctx, request.AppName) + _, span := tracer.Start(ctx, "Conftest") + defer span.End() + + manifestsPath, err := dumpFiles(request.YamlManifests) + if manifestsPath != "" { + defer pkg.WipeDir(manifestsPath) + } if err != nil { - return msg.Result{}, errors.Wrapf(err, "could not retrieve ArgoCD App data: %q", request.AppName) + return msg.Result{}, errors.Wrap(err, "failed to write manifests to disk") } - cr, err := conftest( - ctx, argoApp, request.Repo.Directory, c.locations, request.Container.VcsClient, - ) + log.Debug(). + Strs("policiesPaths", c.locations). + Str("manifestsPath", manifestsPath). + Str("app", request.App.Name). + Msg("running conftest in dir for application") + + r := runner.TestRunner{ + AllNamespaces: true, + NoColor: true, + Policy: c.locations, + Parser: parser.YAML, + ShowBuiltinErrors: request.Container.Config.ShowDebugInfo, + SuppressExceptions: false, + Trace: request.Container.Config.ShowDebugInfo, + } + + results, err := r.Run(ctx, []string{manifestsPath}) if err != nil { + telemetry.SetError(span, err, "ConfTest Run") return msg.Result{}, err } + var b bytes.Buffer + formatConftestResults(&b, results, request.Container.VcsClient) + resultsMessage := b.String() + resultsMessage = strings.ReplaceAll(resultsMessage, fmt.Sprintf("%s/", manifestsPath), "") + + failures := false + warnings := false + for _, r := range results { + for _, f := range r.Warnings { + if !f.Passed() { + warnings = true + } + } + for _, f := range r.Failures { + if !f.Passed() { + failures = true + } + } + } + + if strings.TrimSpace(resultsMessage) == "" { + resultsMessage = "Passed all policy checks." + } + + var cr msg.Result + if failures { + cr.State = pkg.StateFailure + } else if warnings { + cr.State = pkg.StateWarning + } else { + cr.State = pkg.StateSuccess + } + + cr.Summary = "Show Conftest Validation result" + cr.Details = resultsMessage + return cr, nil } + +// formatConftestResults writes the check results from an array of output.CheckResult objects into a formatted table. +// The table omits success messages to reduce noise and includes file, message, and status (failed, warning, skipped). +// The formatted table is then rendered and written to the given io.Writer 'w'. +func formatConftestResults(w io.Writer, checkResults []output.CheckResult, vcs emojiable) { + table := tablewriter.NewWriter(w) + table.SetHeader([]string{" ", "file", "message"}) + table.SetBorders(tablewriter.Border{Left: true, Top: false, Right: true, Bottom: false}) + table.SetCenterSeparator("|") + table.SetAutoWrapText(false) + + var tableData [][]string + for _, checkResult := range checkResults { + for _, result := range checkResult.Exceptions { + tableData = append(tableData, []string{vcs.ToEmoji(pkg.StateError), code(checkResult.FileName), result.Message}) + } + + for _, result := range checkResult.Warnings { + tableData = append(tableData, []string{vcs.ToEmoji(pkg.StateWarning), code(checkResult.FileName), result.Message}) + } + + for _, result := range checkResult.Skipped { + tableData = append(tableData, []string{" :arrow_right: ", code(checkResult.FileName), result.Message}) + } + + for _, result := range checkResult.Failures { + tableData = append(tableData, []string{vcs.ToEmoji(pkg.StateFailure), code(checkResult.FileName), result.Message}) + } + } + + if len(tableData) > 0 { + table.AppendBulk(tableData) + table.Render() + } +} + +func code(s string) string { + return "`" + s + "`" +} diff --git a/pkg/checks/rego/check_test.go b/pkg/checks/rego/check_test.go new file mode 100644 index 00000000..a4797541 --- /dev/null +++ b/pkg/checks/rego/check_test.go @@ -0,0 +1,184 @@ +package rego + +import ( + "context" + "os" + "path/filepath" + "testing" + + "github.com/ghodss/yaml" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/zapier/kubechecks/pkg" + "github.com/zapier/kubechecks/pkg/checks" + "github.com/zapier/kubechecks/pkg/config" + "github.com/zapier/kubechecks/pkg/container" + "github.com/zapier/kubechecks/pkg/vcs/gitlab_client" +) + +func mustWrite(t *testing.T, filePath, content string) { + err := os.WriteFile(filePath, []byte(content), 0o666) + require.NoError(t, err) +} + +type yamlMap map[string]interface{} + +func TestHappyPath(t *testing.T) { + t.Parallel() + + testcases := []struct { + name, policy string + manifest yamlMap + expected pkg.CommitState + }{ + { + name: "good policy, good manifest", + policy: `package tests + +deny[msg] { + input.kind == "Deployment" + not input.spec.template.spec.securityContext.runAsNonRoot + + msg := "Containers must not run as root" +} +`, + manifest: yamlMap{ + "kind": "Deployment", + "metadata": yamlMap{ + "name": "test-deployment", + "namespace": "test-namespace", + }, + "spec": yamlMap{ + "template": yamlMap{ + "spec": yamlMap{ + "securityContext": yamlMap{ + "runAsNonRoot": true, + }, + }, + }, + }, + }, + expected: pkg.StateSuccess, + }, + { + name: "good policy, bad manifest", + policy: `package tests + +deny[msg] { + input.kind == "Deployment" + not input.spec.template.spec.securityContext.runAsNonRoot + + msg := "Containers must not run as root" +} +`, + manifest: yamlMap{ + "kind": "Deployment", + "metadata": yamlMap{ + "name": "test-deployment", + "namespace": "test-namespace", + }, + "spec": yamlMap{ + "template": yamlMap{ + "spec": yamlMap{ + "securityContext": yamlMap{ + "runAsNonRoot": false, + }, + }, + }, + }, + }, + expected: pkg.StateFailure, + }, + { + name: "good policy, missing key manifest", + policy: `package tests + +deny[msg] { + input.kind == "Deployment" + not input.spec.template.spec.securityContext.runAsNonRoot + + msg := "Containers must not run as root" +} +`, + manifest: yamlMap{ + "kind": "Deployment", + "metadata": yamlMap{ + "name": "test-deployment", + "namespace": "test-namespace", + }, + "spec": yamlMap{ + "template": yamlMap{ + "spec": yamlMap{ + "securityContext": yamlMap{}, + }, + }, + }, + }, + expected: pkg.StateFailure, + }, + { + name: "warn policy, bad manifest", + policy: `package tests + +warn[msg] { + input.kind == "Deployment" + not input.spec.template.spec.securityContext.runAsNonRoot + + msg := "Containers should not run as root" +} +`, + manifest: yamlMap{ + "kind": "Deployment", + "metadata": yamlMap{ + "name": "test-deployment", + "namespace": "test-namespace", + }, + "spec": yamlMap{ + "template": yamlMap{ + "spec": yamlMap{ + "securityContext": yamlMap{ + "runAsNonRoot": false, + }, + }, + }, + }, + }, + expected: pkg.StateWarning, + }, + } + + for _, tc := range testcases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + policiesPath, err := os.MkdirTemp("", "kubechecks-test-policies-") + require.NoError(t, err) + + mustWrite(t, filepath.Join(policiesPath, "policy.rego"), tc.policy) + + cfg := config.ServerConfig{ + ShowDebugInfo: true, + + PoliciesLocation: []string{policiesPath}, + } + c, err := NewChecker(cfg) + require.NoError(t, err) + + manifestBytes, err := yaml.Marshal(tc.manifest) + require.NoError(t, err) + + ctx := context.TODO() + request := checks.Request{ + Container: container.Container{ + Config: cfg, + VcsClient: new(gitlab_client.Client), + }, + YamlManifests: []string{string(manifestBytes)}, + } + cr, err := c.Check(ctx, request) + require.NoError(t, err) + + assert.Equal(t, tc.expected, cr.State, "%s\n\n%s", cr.Summary, cr.Details) + }) + } +} diff --git a/pkg/checks/rego/conftest.go b/pkg/checks/rego/conftest.go deleted file mode 100644 index c2aca03f..00000000 --- a/pkg/checks/rego/conftest.go +++ /dev/null @@ -1,144 +0,0 @@ -package rego - -import ( - "bytes" - "context" - "fmt" - "io" - "path/filepath" - "strings" - - "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" - "github.com/olekukonko/tablewriter" - "github.com/open-policy-agent/conftest/output" - "github.com/open-policy-agent/conftest/runner" - "github.com/rs/zerolog/log" - "go.opentelemetry.io/otel" - - "github.com/zapier/kubechecks/pkg" - "github.com/zapier/kubechecks/pkg/msg" - "github.com/zapier/kubechecks/telemetry" -) - -const passedMessage = "\nPassed all policy checks." - -var tracer = otel.Tracer("pkg/conftest") - -type emojiable interface { - ToEmoji(state pkg.CommitState) string -} - -// Conftest runs the conftest validation against an application in a given repository -// path. It generates a summary string with the results, which can later be posted -// as a GitLab comment. The validation checks resources against Zapier policies and -// provides feedback for warnings or errors as informational messages. Failure to -// pass a policy check currently does not block deploy. -func conftest( - ctx context.Context, app *v1alpha1.Application, repoPath string, policiesLocations []string, vcs emojiable, -) (msg.Result, error) { - _, span := tracer.Start(ctx, "Conftest") - defer span.End() - - confTestDir := filepath.Join(repoPath, app.Spec.Source.Path) - - log.Debug().Str("dir", confTestDir).Str("app", app.Name).Msg("running conftest in dir for application") - - var r runner.TestRunner - - r.NoColor = true - r.AllNamespaces = true - // PATH To Rego Polices - r.Policy = policiesLocations - r.SuppressExceptions = false - r.Trace = false - - //TODO only do the main values app yaml maybe >.> - innerStrings := []string{} - failures := false - warnings := false - - results, err := r.Run(ctx, []string{confTestDir}) - if err != nil { - telemetry.SetError(span, err, "ConfTest Run") - return msg.Result{}, err - } - - var b bytes.Buffer - formatConftestResults(&b, results, vcs) - innerStrings = append(innerStrings, fmt.Sprintf("\n\n**%s**\n", app.Spec.Source.Path)) - s := b.String() - s = strings.ReplaceAll(s, fmt.Sprintf("%s/", repoPath), "") - - for _, r := range results { - for _, f := range r.Warnings { - warnings = !f.Passed() - } - for _, f := range r.Failures { - failures = !f.Passed() - } - } - - if strings.TrimSpace(s) != "" { - innerStrings = append(innerStrings, s) - } else { - innerStrings = append(innerStrings, passedMessage) - } - - var cr msg.Result - if failures { - cr.State = pkg.StateFailure - } else if warnings { - cr.State = pkg.StateWarning - } else { - cr.State = pkg.StateSuccess - } - - cr.Summary = "Show Conftest Validation result" - cr.Details = strings.Join(innerStrings, "\n") - - return cr, nil -} - -// formatConftestResults writes the check results from an array of output.CheckResult objects into a formatted table. -// The table omits success messages to reduce noise and includes file, message, and status (failed, warning, skipped). -// The formatted table is then rendered and written to the given io.Writer 'w'. -func formatConftestResults(w io.Writer, checkResults []output.CheckResult, vcs emojiable) { - table := tablewriter.NewWriter(w) - table.SetHeader([]string{" ", "file", "message"}) - table.SetBorders(tablewriter.Border{Left: true, Top: false, Right: true, Bottom: false}) - table.SetCenterSeparator("|") - table.SetAutoWrapText(false) - - var tableData [][]string - for _, checkResult := range checkResults { - for r := 0; r < checkResult.Successes; r++ { - // don't include these to be less noisy - //tableData = append(tableData, []string{"success", checkResult.FileName, "SUCCESS"}) - } - - for _, result := range checkResult.Exceptions { - tableData = append(tableData, []string{vcs.ToEmoji(pkg.StateError), code(checkResult.FileName), result.Message}) - } - - for _, result := range checkResult.Warnings { - tableData = append(tableData, []string{vcs.ToEmoji(pkg.StateWarning), code(checkResult.FileName), result.Message}) - } - - for _, result := range checkResult.Skipped { - tableData = append(tableData, []string{" :arrow_right: ", code(checkResult.FileName), result.Message}) - } - - for _, result := range checkResult.Failures { - tableData = append(tableData, []string{vcs.ToEmoji(pkg.StateFailure), code(checkResult.FileName), result.Message}) - } - } - - if len(tableData) > 0 { - table.AppendBulk(tableData) - table.Render() - } -} - -func code(s string) string { - return "`" + s + "`" -} diff --git a/pkg/checks/types.go b/pkg/checks/types.go index ab981cb6..8d685a09 100644 --- a/pkg/checks/types.go +++ b/pkg/checks/types.go @@ -6,14 +6,16 @@ import ( "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" "github.com/rs/zerolog" + "github.com/zapier/kubechecks/pkg" "github.com/zapier/kubechecks/pkg/container" "github.com/zapier/kubechecks/pkg/git" "github.com/zapier/kubechecks/pkg/msg" ) type ProcessorEntry struct { - Name string - Processor func(ctx context.Context, request Request) (msg.Result, error) + Name string + Processor func(ctx context.Context, request Request) (msg.Result, error) + WorstState pkg.CommitState } type Processor interface { diff --git a/pkg/commitState.go b/pkg/commitState.go index 59d21412..87ae34c1 100644 --- a/pkg/commitState.go +++ b/pkg/commitState.go @@ -1,5 +1,10 @@ package pkg +import ( + "fmt" + "strings" +) + // CommitState is an enum for represnting the state of a commit for posting via CommitStatus type CommitState uint8 @@ -37,3 +42,27 @@ const defaultString = "Unknown" func WorstState(l1, l2 CommitState) CommitState { return max(l1, l2) } + +func BestState(l1, l2 CommitState) CommitState { + return min(l1, l2) +} + +func ParseCommitState(s string) (CommitState, error) { + switch strings.ToLower(s) { + case "success": + return StateSuccess, nil + case "running": + return StateRunning, nil + case "warning": + return StateWarning, nil + case "failure": + return StateFailure, nil + case "error": + return StateError, nil + case "panic": + return StatePanic, nil + default: + return StateNone, fmt.Errorf("unknown commit state: %s", s) + + } +} diff --git a/pkg/config/config.go b/pkg/config/config.go index 2647acae..4aa6e933 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -1,84 +1,86 @@ package config import ( + "reflect" + + "github.com/mitchellh/mapstructure" "github.com/pkg/errors" "github.com/rs/zerolog" "github.com/rs/zerolog/log" "github.com/spf13/viper" + + "github.com/zapier/kubechecks/pkg" ) type ServerConfig struct { // argocd - ArgoCDServerAddr string - ArgoCDToken string - ArgoCDPathPrefix string - ArgoCDInsecure bool - KubernetesConfig string + ArgoCDServerAddr string `mapstructure:"argocd-api-server-addr"` + ArgoCDToken string `mapstructure:"argocd-api-token"` + ArgoCDPathPrefix string `mapstructure:"argocd-api-path-prefix"` + ArgoCDInsecure bool `mapstructure:"argocd-api-insecure"` + KubernetesConfig string `mapstructure:"kubernetes-config"` // otel - EnableOtel bool - OtelCollectorHost string - OtelCollectorPort string + EnableOtel bool `mapstructure:"otel-enabled"` + OtelCollectorHost string `mapstructure:"otel-collector-host"` + OtelCollectorPort string `mapstructure:"otel-collector-port"` // vcs - VcsBaseUrl string - VcsToken string - VcsType string + VcsBaseUrl string `mapstructure:"vcs-base-url"` + VcsToken string `mapstructure:"vcs-token"` + VcsType string `mapstructure:"vcs-type"` // webhooks - EnsureWebhooks bool - WebhookSecret string - WebhookUrlBase string + EnsureWebhooks bool `mapstructure:"ensure-webhooks"` + WebhookSecret string `mapstructure:"webhook-secret"` + WebhookUrlBase string `mapstructure:"webhook-url-base"` + UrlPrefix string `mapstructure:"webhook-url-prefix"` + + // checks + // -- conftest + EnableConfTest bool `mapstructure:"enable-conftest"` + PoliciesLocation []string `mapstructure:"policies-location"` + WorstConfTestState pkg.CommitState `mapstructure:"worst-conftest-state"` + // -- kubeconform + EnableKubeConform bool `mapstructure:"enable-kubeconform"` + WorstKubeConformState pkg.CommitState `mapstructure:"worst-kubeconform-state"` + // -- preupgrade + EnablePreupgrade bool `mapstructure:"enable-preupgrade"` + WorstPreupgradeState pkg.CommitState `mapstructure:"worst-preupgrade-state"` // misc - EnableConfTest bool - FallbackK8sVersion string - LabelFilter string - LogLevel zerolog.Level - MonitorAllApplications bool - OpenAIAPIToken string - PoliciesLocation []string - SchemasLocations []string - ShowDebugInfo bool - TidyOutdatedCommentsMode string - UrlPrefix string + FallbackK8sVersion string `mapstructure:"fallback-k8s-version"` + LabelFilter string `mapstructure:"label-filter"` + LogLevel zerolog.Level `mapstructure:"log-level"` + MonitorAllApplications bool `mapstructure:"monitor-all-applications"` + OpenAIAPIToken string `mapstructure:"openai-api-token"` + SchemasLocations []string `mapstructure:"schemas-location"` + ShowDebugInfo bool `mapstructure:"show-debug-info"` + TidyOutdatedCommentsMode string `mapstructure:"tidy-outdated-comments-mode"` } func New() (ServerConfig, error) { - logLevelString := viper.GetString("log-level") - logLevel, err := zerolog.ParseLevel(logLevelString) - if err != nil { - return ServerConfig{}, errors.Wrap(err, "failed to parse log level") - } + return NewWithViper(viper.GetViper()) +} - cfg := ServerConfig{ - ArgoCDInsecure: viper.GetBool("argocd-api-insecure"), - ArgoCDToken: viper.GetString("argocd-api-token"), - ArgoCDPathPrefix: viper.GetString("argocd-api-path-prefix"), - ArgoCDServerAddr: viper.GetString("argocd-api-server-addr"), +func NewWithViper(v *viper.Viper) (ServerConfig, error) { + var cfg ServerConfig + if err := v.Unmarshal(&cfg, func(config *mapstructure.DecoderConfig) { + config.DecodeHook = func(in reflect.Type, out reflect.Type, value interface{}) (interface{}, error) { + if in.String() == "string" && out.String() == "zerolog.Level" { + input := value.(string) + return zerolog.ParseLevel(input) + } - EnableConfTest: viper.GetBool("enable-conftest"), - EnableOtel: viper.GetBool("otel-enabled"), - EnsureWebhooks: viper.GetBool("ensure-webhooks"), - FallbackK8sVersion: viper.GetString("fallback-k8s-version"), - KubernetesConfig: viper.GetString("kubernetes-config"), - LabelFilter: viper.GetString("label-filter"), - LogLevel: logLevel, - MonitorAllApplications: viper.GetBool("monitor-all-applications"), - OpenAIAPIToken: viper.GetString("openai-api-token"), - OtelCollectorHost: viper.GetString("otel-collector-host"), - OtelCollectorPort: viper.GetString("otel-collector-port"), - PoliciesLocation: viper.GetStringSlice("policies-location"), - SchemasLocations: viper.GetStringSlice("schemas-location"), - ShowDebugInfo: viper.GetBool("show-debug-info"), - TidyOutdatedCommentsMode: viper.GetString("tidy-outdated-comments-mode"), - UrlPrefix: viper.GetString("webhook-url-prefix"), - WebhookSecret: viper.GetString("webhook-secret"), - WebhookUrlBase: viper.GetString("webhook-url-base"), + if in.String() == "string" && out.String() == "pkg.CommitState" { + input := value.(string) + return pkg.ParseCommitState(input) + } - VcsBaseUrl: viper.GetString("vcs-base-url"), - VcsToken: viper.GetString("vcs-token"), - VcsType: viper.GetString("vcs-type"), + return value, nil + } + }); err != nil { + return cfg, errors.Wrap(err, "failed to read configuration") } log.Info().Msg("Server Configuration: ") diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go new file mode 100644 index 00000000..02b9b272 --- /dev/null +++ b/pkg/config/config_test.go @@ -0,0 +1,25 @@ +package config + +import ( + "testing" + + "github.com/rs/zerolog" + "github.com/spf13/viper" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/zapier/kubechecks/pkg" +) + +func TestNew(t *testing.T) { + v := viper.New() + v.Set("log-level", "info") + v.Set("argocd-api-insecure", "true") + v.Set("worst-conftest-state", "warning") + + cfg, err := NewWithViper(v) + require.NoError(t, err) + assert.Equal(t, zerolog.InfoLevel, cfg.LogLevel) + assert.Equal(t, true, cfg.ArgoCDInsecure) + assert.Equal(t, pkg.StateWarning, cfg.WorstConfTestState, "worst states can be overridden") +} diff --git a/pkg/container/main.go b/pkg/container/main.go index 02e208e3..12234447 100644 --- a/pkg/container/main.go +++ b/pkg/container/main.go @@ -11,6 +11,7 @@ import ( "github.com/zapier/kubechecks/pkg/appdir" "github.com/zapier/kubechecks/pkg/argo_client" "github.com/zapier/kubechecks/pkg/config" + "github.com/zapier/kubechecks/pkg/git" "github.com/zapier/kubechecks/pkg/vcs" ) @@ -20,6 +21,8 @@ type Container struct { Config config.ServerConfig + RepoManager *git.RepoManager + VcsClient vcs.Client VcsToArgoMap VcsToArgoMap } diff --git a/pkg/events/check.go b/pkg/events/check.go index f42c4804..c0263bcf 100644 --- a/pkg/events/check.go +++ b/pkg/events/check.go @@ -374,7 +374,7 @@ func (ce *CheckEvent) processApp(ctx context.Context, app v1alpha1.Application) ) for _, processor := range ce.processors { - runner.Run(ctx, processor.Name, processor.Processor) + runner.Run(ctx, processor.Name, processor.Processor, processor.WorstState) } runner.Wait() diff --git a/pkg/events/runner.go b/pkg/events/runner.go index 44f2feda..5fe70e7d 100644 --- a/pkg/events/runner.go +++ b/pkg/events/runner.go @@ -51,7 +51,7 @@ func newRunner( type checkFunction func(ctx context.Context, data checks.Request) (msg.Result, error) -func (r *Runner) Run(ctx context.Context, desc string, fn checkFunction) { +func (r *Runner) Run(ctx context.Context, desc string, fn checkFunction, worstState pkg.CommitState) { r.wg.Add(1) go func() { @@ -59,6 +59,11 @@ func (r *Runner) Run(ctx context.Context, desc string, fn checkFunction) { ctx, span := tracer.Start(ctx, desc) + addToAppMessage := func(result msg.Result) { + result.State = pkg.BestState(result.State, worstState) + r.Note.AddToAppMessage(ctx, r.AppName, result) + } + defer func() { r.wg.Done() @@ -71,7 +76,7 @@ func (r *Runner) Run(ctx context.Context, desc string, fn checkFunction) { Summary: desc, Details: fmt.Sprintf(errorCommentFormat, desc, err), } - r.Note.AddToAppMessage(ctx, r.AppName, result) + addToAppMessage(result) } }() @@ -80,23 +85,23 @@ func (r *Runner) Run(ctx context.Context, desc string, fn checkFunction) { Logger() logger.Info().Msgf("running check") - cr, err := fn(ctx, r.Request) + result, err := fn(ctx, r.Request) logger.Info(). Err(err). - Uint8("result", uint8(cr.State)). + Uint8("result", uint8(result.State)). Msg("check result") if err != nil { telemetry.SetError(span, err, desc) - result := msg.Result{State: pkg.StateError, Summary: desc, Details: fmt.Sprintf(errorCommentFormat, desc, err)} - r.Note.AddToAppMessage(ctx, r.AppName, result) + result = msg.Result{State: pkg.StateError, Summary: desc, Details: fmt.Sprintf(errorCommentFormat, desc, err)} + addToAppMessage(result) return } - r.Note.AddToAppMessage(ctx, r.AppName, cr) + addToAppMessage(result) logger.Info(). - Str("result", cr.State.BareString()). + Str("result", result.State.BareString()). Msgf("check done") }() } diff --git a/pkg/git/repo.go b/pkg/git/repo.go index df7d24d4..84140680 100644 --- a/pkg/git/repo.go +++ b/pkg/git/repo.go @@ -62,7 +62,12 @@ func (r *Repo) Clone(ctx context.Context) error { _, span := tracer.Start(ctx, "CloneRepo") defer span.End() - cmd := r.execCommand("git", "clone", r.CloneURL, r.Directory) + args := []string{"clone", r.CloneURL, r.Directory} + if r.BranchName != "" && r.BranchName != "HEAD" { + args = append(args, "--branch", r.BranchName) + } + + cmd := r.execCommand("git", args...) out, err := cmd.CombinedOutput() if err != nil { log.Error().Err(err).Msgf("unable to clone repository, %s", out) diff --git a/pkg/utils.go b/pkg/utils.go index fd77724e..fbc2c080 100644 --- a/pkg/utils.go +++ b/pkg/utils.go @@ -1,5 +1,11 @@ package pkg +import ( + "os" + + "github.com/rs/zerolog/log" +) + var ( GitTag = "" GitCommit = "" @@ -8,3 +14,12 @@ var ( func Pointer[T interface{}](item T) *T { return &item } + +func WipeDir(dir string) { + if err := os.RemoveAll(dir); err != nil { + log.Error(). + Err(err). + Str("path", dir). + Msg("failed to wipe path") + } +} From bb8552e95fde0d531eead2cb84ba16db950e7a0f Mon Sep 17 00:00:00 2001 From: Joseph Lombrozo Date: Thu, 21 Mar 2024 10:34:50 -0400 Subject: [PATCH 06/10] swap to non-deprecated echo prometheus package (#149) --- pkg/server/server.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/server/server.go b/pkg/server/server.go index 5a4a3448..0196d78f 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -7,7 +7,7 @@ import ( "strings" "github.com/heptiolabs/healthcheck" - "github.com/labstack/echo-contrib/prometheus" + "github.com/labstack/echo-contrib/echoprometheus" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" "github.com/pkg/errors" @@ -37,16 +37,16 @@ func (s *Server) Start(ctx context.Context) { e := echo.New() e.HideBanner = true - e.Use(middleware.Recover()) e.Logger = lecho.New(log.Logger) - // Enable metrics middleware - p := prometheus.NewPrometheus("kubechecks_echo", nil) - p.Use(e) + + e.Use(middleware.Recover()) + e.Use(echoprometheus.NewMiddleware("kubechecks_echo")) // add routes health := healthcheck.NewHandler() e.GET("/ready", echo.WrapHandler(health)) e.GET("/live", echo.WrapHandler(health)) + e.GET("/metrics", echoprometheus.NewHandler()) hooksGroup := e.Group(s.hooksPrefix()) From fb71326689349413a039c847932c122853fd0934 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Mar 2024 10:35:25 -0400 Subject: [PATCH 07/10] Bump github.com/argoproj/argo-cd/v2 from 2.10.1 to 2.10.4 (#162) Bumps [github.com/argoproj/argo-cd/v2](https://github.com/argoproj/argo-cd) from 2.10.1 to 2.10.4. - [Release notes](https://github.com/argoproj/argo-cd/releases) - [Changelog](https://github.com/argoproj/argo-cd/blob/master/CHANGELOG.md) - [Commits](https://github.com/argoproj/argo-cd/compare/v2.10.1...v2.10.4) --- updated-dependencies: - dependency-name: github.com/argoproj/argo-cd/v2 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 6 +++--- go.sum | 15 ++++++++++----- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index 57c0d70e..80ac1178 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.21 toolchain go1.21.6 require ( - github.com/argoproj/argo-cd/v2 v2.10.1 + github.com/argoproj/argo-cd/v2 v2.10.4 github.com/argoproj/gitops-engine v0.7.1-0.20240122213038-792124280fcc github.com/cenkalti/backoff/v4 v4.2.1 github.com/creasty/defaults v1.7.0 @@ -48,7 +48,6 @@ require ( google.golang.org/grpc v1.61.1 gopkg.in/dealancer/validate.v2 v2.1.0 gopkg.in/yaml.v3 v3.0.1 - gotest.tools/v3 v3.0.3 k8s.io/apimachinery v0.26.12 k8s.io/client-go v0.26.12 ) @@ -77,7 +76,7 @@ require ( github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 // indirect github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect github.com/argoproj/pkg v0.13.7-0.20230627120311-a4dd357b057e // indirect - github.com/aws/aws-sdk-go v1.49.6 // indirect + github.com/aws/aws-sdk-go v1.50.8 // indirect github.com/basgys/goxml2json v1.1.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect @@ -278,6 +277,7 @@ require ( muzzammil.xyz/jsonc v1.0.0 // indirect olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 // indirect oras.land/oras-go/v2 v2.3.1 // indirect + sigs.k8s.io/controller-runtime v0.14.7 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/kustomize/api v0.12.1 // indirect sigs.k8s.io/kustomize/kyaml v0.13.9 // indirect diff --git a/go.sum b/go.sum index d794def1..f5a2546c 100644 --- a/go.sum +++ b/go.sum @@ -244,8 +244,8 @@ github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6 github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= -github.com/argoproj/argo-cd/v2 v2.10.1 h1:VD06GPeoq14Bo7IfiW+EKim3T1C9xaMElVrEtw+zll0= -github.com/argoproj/argo-cd/v2 v2.10.1/go.mod h1:SK1uGZ9xWVzxuyg079MaO6+hz/Oz9wSDkGyT0gEkYSs= +github.com/argoproj/argo-cd/v2 v2.10.4 h1:iwI3IrjTnXSnepb0c/lftxjkzCa1DHt2YGjSw6DHP0Q= +github.com/argoproj/argo-cd/v2 v2.10.4/go.mod h1:nujAuswdQvB6yWI8HubQjfUiLdiIlKlG0ihx2Ht1D28= github.com/argoproj/gitops-engine v0.7.1-0.20240122213038-792124280fcc h1:Fv94Mi2WvtvPkEH5WoWC3iy/VoQRLeSsE0hyg0n2UkY= github.com/argoproj/gitops-engine v0.7.1-0.20240122213038-792124280fcc/go.mod h1:gWE8uROi7hIkWGNAVM+8FWkMfo0vZ03SLx/aFw/DBzg= github.com/argoproj/pkg v0.13.7-0.20230627120311-a4dd357b057e h1:kuLQvJqwwRMQTheT4MFyKVM8Txncu21CHT4yBWUl1Mk= @@ -256,8 +256,8 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkY github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/aws/aws-sdk-go v1.44.290/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= -github.com/aws/aws-sdk-go v1.49.6 h1:yNldzF5kzLBRvKlKz1S0bkvc2+04R1kt13KfBWQBfFA= -github.com/aws/aws-sdk-go v1.49.6/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go v1.50.8 h1:gY0WoOW+/Wz6XmYSgDH9ge3wnAevYDSQWPxxJvqAkP4= +github.com/aws/aws-sdk-go v1.50.8/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/basgys/goxml2json v1.1.0 h1:4ln5i4rseYfXNd86lGEB+Vi652IsIXIvggKM/BhUKVw= github.com/basgys/goxml2json v1.1.0/go.mod h1:wH7a5Np/Q4QoECFIU8zTQlZwZkrilY0itPfecMw41Dw= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= @@ -391,6 +391,8 @@ github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7 github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= +github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8= @@ -1355,7 +1357,6 @@ golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -1419,6 +1420,8 @@ golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNq golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= 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= +gomodules.xyz/jsonpatch/v2 v2.2.0 h1:4pT439QV83L+G9FkcCriY6EkpcK6r6bK+A5FBUMI7qY= +gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -1727,6 +1730,8 @@ oras.land/oras-go/v2 v2.3.1/go.mod h1:5AQXVEu1X/FKp1F9DMOb5ZItZBOa0y5dha0yCm4NR9 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/controller-runtime v0.14.7 h1:Vrnm2vk9ZFlRkXATHz0W0wXcqNl7kPat8q2JyxVy0Q8= +sigs.k8s.io/controller-runtime v0.14.7/go.mod h1:ErTs3SJCOujNUnTz4AS+uh8hp6DHMo1gj6fFndJT1X8= sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= From d3e7147468914aba75786d0f03a08cc2288a540e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Mar 2024 10:35:55 -0400 Subject: [PATCH 08/10] Bump github.com/rs/zerolog from 1.31.0 to 1.32.0 (#153) Bumps [github.com/rs/zerolog](https://github.com/rs/zerolog) from 1.31.0 to 1.32.0. - [Release notes](https://github.com/rs/zerolog/releases) - [Commits](https://github.com/rs/zerolog/compare/v1.31.0...v1.32.0) --- updated-dependencies: - dependency-name: github.com/rs/zerolog dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 80ac1178..c481f71a 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 github.com/prometheus/client_golang v1.18.0 github.com/rikatz/kubepug v1.4.0 - github.com/rs/zerolog v1.31.0 + github.com/rs/zerolog v1.32.0 github.com/sashabaranov/go-openai v1.19.3 github.com/shurcooL/githubv4 v0.0.0-20231126234147-1cffa1f02456 github.com/sirupsen/logrus v1.9.3 diff --git a/go.sum b/go.sum index f5a2546c..19aa0fce 100644 --- a/go.sum +++ b/go.sum @@ -853,8 +853,8 @@ github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/f github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= -github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A= -github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0= +github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= From b7a30cd837c6d1ab30c1513857163e7e2fc914bb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Mar 2024 10:37:01 -0400 Subject: [PATCH 09/10] Bump github.com/go-jose/go-jose/v3 from 3.0.1 to 3.0.3 (#155) Bumps [github.com/go-jose/go-jose/v3](https://github.com/go-jose/go-jose) from 3.0.1 to 3.0.3. - [Release notes](https://github.com/go-jose/go-jose/releases) - [Changelog](https://github.com/go-jose/go-jose/blob/v3.0.3/CHANGELOG.md) - [Commits](https://github.com/go-jose/go-jose/compare/v3.0.1...v3.0.3) --- updated-dependencies: - dependency-name: github.com/go-jose/go-jose/v3 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 6 +++--- go.sum | 13 ++++++------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/go.mod b/go.mod index c481f71a..66a9e857 100644 --- a/go.mod +++ b/go.mod @@ -114,7 +114,7 @@ require ( github.com/go-git/go-billy/v5 v5.5.0 // indirect github.com/go-git/go-git/v5 v5.11.0 // indirect github.com/go-ini/ini v1.67.0 // indirect - github.com/go-jose/go-jose/v3 v3.0.1 // indirect + github.com/go-jose/go-jose/v3 v3.0.3 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.20.2 // indirect @@ -241,11 +241,11 @@ require ( go.starlark.net v0.0.0-20231121155337-90ade8b19d09 // indirect go.uber.org/atomic v1.10.0 // indirect go.uber.org/multierr v1.9.0 // indirect - golang.org/x/crypto v0.18.0 // indirect + golang.org/x/crypto v0.19.0 // indirect golang.org/x/mod v0.14.0 // indirect golang.org/x/sync v0.6.0 // indirect golang.org/x/sys v0.17.0 // indirect - golang.org/x/term v0.16.0 // indirect + golang.org/x/term v0.17.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.17.0 // indirect diff --git a/go.sum b/go.sum index 19aa0fce..bcb285b4 100644 --- a/go.sum +++ b/go.sum @@ -437,8 +437,8 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= -github.com/go-jose/go-jose/v3 v3.0.1 h1:pWmKFVtt+Jl0vBZTIpz/eAKwsm6LkIxDVVbFHKkchhA= -github.com/go-jose/go-jose/v3 v3.0.1/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= +github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k= +github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= @@ -1041,7 +1041,6 @@ golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= @@ -1053,8 +1052,8 @@ golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= -golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= -golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1317,8 +1316,8 @@ golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= -golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= -golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= +golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= From b9d12cc2561f4d04abdc5e5783db073f1f3f47b6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Mar 2024 10:37:36 -0400 Subject: [PATCH 10/10] Bump go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc from 0.45.0 to 1.24.0 (#154) Bumps [go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc](https://github.com/open-telemetry/opentelemetry-go) from 0.45.0 to 1.24.0. - [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/bridge/opencensus/v0.45.0...v1.24.0) --- updated-dependencies: - dependency-name: go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 66a9e857..b2e320c8 100644 --- a/go.mod +++ b/go.mod @@ -37,10 +37,10 @@ require ( github.com/ziflex/lecho/v3 v3.5.0 go.opentelemetry.io/contrib/instrumentation/runtime v0.46.1 go.opentelemetry.io/otel v1.24.0 - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.45.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.24.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 go.opentelemetry.io/otel/sdk v1.24.0 - go.opentelemetry.io/otel/sdk/metric v1.22.0 + go.opentelemetry.io/otel/sdk/metric v1.24.0 go.opentelemetry.io/otel/trace v1.24.0 golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 golang.org/x/net v0.20.0 diff --git a/go.sum b/go.sum index bcb285b4..a937c9c2 100644 --- a/go.sum +++ b/go.sum @@ -1008,8 +1008,8 @@ go.opentelemetry.io/contrib/instrumentation/runtime v0.46.1 h1:m9ReioVPIffxjJlGN go.opentelemetry.io/contrib/instrumentation/runtime v0.46.1/go.mod h1:CANkrsXNzqOKXfOomu2zhOmc1/J5UZK9SGjrat6ZCG0= go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.45.0 h1:tfil6di0PoNV7FZdsCS7A5izZoVVQ7AuXtyekbOpG/I= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.45.0/go.mod h1:AKFZIEPOnqB00P63bTjOiah4ZTaRzl1TKwUWpZdYUHI= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.24.0 h1:f2jriWfOdldanBwS9jNBdeOKAQN7b4ugAMaNu1/1k9g= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.24.0/go.mod h1:B+bcQI1yTY+N0vqMpoZbEN7+XU4tNM0DmUiOwebFJWI= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 h1:t6wl9SPayj+c7lEIFgm4ooDBZVb01IhLB4InpomhRw8= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0/go.mod h1:iSDOcsnSA5INXzZtwaBPrKp/lWu/V14Dd+llD0oI2EA= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 h1:Mw5xcxMwlqoJd97vwPxA8isEaIoxsta9/Q51+TTJLGE= @@ -1018,8 +1018,8 @@ go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGX go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= 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/sdk/metric v1.22.0 h1:ARrRetm1HCVxq0cbnaZQlfwODYJHo3gFL8Z3tSmHBcI= -go.opentelemetry.io/otel/sdk/metric v1.22.0/go.mod h1:KjQGeMIDlBNEOo6HvjhxIec1p/69/kULDcp4gr0oLQQ= +go.opentelemetry.io/otel/sdk/metric v1.24.0 h1:yyMQrPzF+k88/DbH7o4FMAs80puqd+9osbiBrJrz/w8= +go.opentelemetry.io/otel/sdk/metric v1.24.0/go.mod h1:I6Y5FjH6rvEnTTAYQz3Mmv2kl6Ek5IIrmwTLqMrrOE0= go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=