diff --git a/hack/tools/go.mod b/hack/tools/go.mod index bb2d69db4f5a..5f933eb89a4c 100644 --- a/hack/tools/go.mod +++ b/hack/tools/go.mod @@ -20,7 +20,6 @@ require ( k8s.io/client-go v0.28.4 k8s.io/klog/v2 v2.100.1 k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 - k8s.io/kubectl v0.28.4 k8s.io/utils v0.0.0-20231127182322-b307cd553661 sigs.k8s.io/cluster-api v0.0.0-00010101000000-000000000000 sigs.k8s.io/cluster-api/test v0.0.0-00010101000000-000000000000 @@ -36,7 +35,6 @@ require ( cloud.google.com/go/compute v1.23.3 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect cloud.google.com/go/iam v1.1.5 // indirect - github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/MakeNowJust/heredoc v1.0.0 // indirect github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.2.0 // indirect @@ -48,10 +46,8 @@ require ( github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/chai2010/gettext-go v1.0.2 // indirect github.com/cloudflare/circl v1.3.3 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/daviddengcn/go-colortext v1.0.0 // indirect github.com/distribution/reference v0.5.0 // indirect github.com/docker/distribution v2.8.3+incompatible // indirect github.com/docker/docker v24.0.7+incompatible // indirect @@ -61,11 +57,8 @@ require ( github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/evanphx/json-patch v5.6.0+incompatible // indirect github.com/evanphx/json-patch/v5 v5.7.0 // indirect - github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect - github.com/fatih/camelcase v1.0.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect - github.com/fvbommel/sortorder v1.1.0 // indirect github.com/go-errors/errors v1.4.2 // indirect github.com/go-logr/logr v1.3.0 // indirect github.com/go-logr/stdr v1.2.2 // indirect @@ -76,7 +69,6 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect - github.com/google/btree v1.0.1 // indirect github.com/google/cel-go v0.16.1 // indirect github.com/google/gnostic-models v0.6.8 // indirect github.com/google/go-cmp v0.6.0 // indirect @@ -84,61 +76,45 @@ require ( github.com/google/go-querystring v1.1.0 // indirect 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.4.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect github.com/googleapis/gax-go/v2 v2.12.0 // indirect - github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/huandu/xstrings v1.3.3 // indirect github.com/imdario/mergo v0.3.13 // indirect - github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/jonboulle/clockwork v0.2.2 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect - github.com/lithammer/dedent v1.1.0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect - github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect - github.com/moby/spdystream v0.2.0 // indirect - github.com/moby/term v0.0.0-20221205130635-1aeaba878587 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0-rc2 // indirect github.com/pelletier/go-toml/v2 v2.1.0 // indirect - github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/prometheus/client_golang v1.17.0 // indirect github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // indirect github.com/prometheus/common v0.44.0 // indirect github.com/prometheus/procfs v0.11.1 // indirect - github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/shopspring/decimal v1.3.1 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect - github.com/spf13/cobra v1.8.0 // indirect github.com/spf13/viper v1.18.1 // indirect github.com/stoewer/go-strcase v1.2.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect - github.com/xlab/treeprint v1.2.0 // indirect 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 v1.21.0 // indirect go.opentelemetry.io/otel/metric v1.21.0 // indirect go.opentelemetry.io/otel/trace v1.21.0 // indirect - go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.16.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect @@ -164,13 +140,9 @@ require ( gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/apiserver v0.28.4 // indirect - k8s.io/cli-runtime v0.28.4 // indirect k8s.io/cluster-bootstrap v0.28.4 // indirect k8s.io/component-base v0.28.4 // indirect - k8s.io/component-helpers v0.28.4 // indirect - k8s.io/metrics v0.28.4 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect - sigs.k8s.io/kustomize/kustomize/v5 v5.0.4-0.20230601165947-6ce0bf390ce3 // indirect sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect ) diff --git a/hack/tools/go.sum b/hack/tools/go.sum index 04f8c4219b6d..8960321be5c4 100644 --- a/hack/tools/go.sum +++ b/hack/tools/go.sum @@ -10,7 +10,6 @@ cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXE cloud.google.com/go/storage v1.35.1 h1:B59ahL//eDfx2IIKFBeT5Atm9wnNmj3+8xG/W4WB//w= cloud.google.com/go/storage v1.35.1/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= @@ -28,7 +27,6 @@ github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls= github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E= github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df h1:7RFfzj4SSt6nnvCPbCqijJi1nWCd+TqAT3bYCStRC18= github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df/go.mod h1:pSwJ0fSY5KhvocuWSx4fz3BA8OrA1bQn+K1Eli3BRwM= -github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 h1:4daAzAu0S6Vi7/lbWECcX0j45yZReDZ56BQsrVBOEEY= github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -39,11 +37,6 @@ github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7N github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk= -github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs= @@ -52,15 +45,11 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k= github.com/coredns/caddy v1.1.0 h1:ezvsPrT/tA/7pYDBZxu0cT0VmWk75AfIaf6GSYCNMf0= github.com/coredns/corefile-migration v1.0.21 h1:W/DCETrHDiFo0Wj03EyMkaQ9fwsmSgqTCQDHpceaSsE= -github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/daviddengcn/go-colortext v1.0.0 h1:ANqDyC0ys6qCSvuEK7l3g5RaehL/Xck9EX8ATG8oKsE= -github.com/daviddengcn/go-colortext v1.0.0/go.mod h1:zDqEI5NVUop5QPpVJUxE9UO10hRnmkD5G4Pmri9+m4c= github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= @@ -84,17 +73,11 @@ github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCv github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.7.0 h1:nJqP7uwL84RJInrohHfW0Fx3awjbm8qZeFv0nW9SYGc= github.com/evanphx/json-patch/v5 v5.7.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= -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= -github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= -github.com/fvbommel/sortorder v1.1.0 h1:fUmoe+HLsBTctBDoaBwpQo5N+nrCp8g/BjKb/6ZQmYw= -github.com/fvbommel/sortorder v1.1.0/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -133,13 +116,6 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golangplus/bytes v0.0.0-20160111154220-45c989fe5450/go.mod h1:Bk6SMAONeMXrxql8uvOKuAZSu8aM5RUGv+1C6IJaEho= -github.com/golangplus/bytes v1.0.0/go.mod h1:AdRaCFwmc/00ZzELMWb01soso6W1R/++O1XL80yAn+A= -github.com/golangplus/fmt v1.0.0/go.mod h1:zpM0OfbMCjPtd2qkTD/jX2MgiFCqklhSUFyDW44gVQE= -github.com/golangplus/testing v1.0.0 h1:+ZeeiKZENNOMkTTELoSySazi+XaEhVO0mb+eanrSEUQ= -github.com/golangplus/testing v1.0.0/go.mod h1:ZDreixUV3YzhoVraIDyOzHrr76p6NUh6k/pPg/Q3gYA= -github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= -github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/google/cel-go v0.16.1 h1:3hZfSNiAU3KOiNtxuFXVp5WFy4hf/Ly3Sa4/7F8SXNo= github.com/google/cel-go v0.16.1/go.mod h1:HXZKzB0LXqer5lHHgfWAnlYwJaQBDKMjxjulNQzhwhY= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= @@ -149,7 +125,6 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -167,8 +142,6 @@ github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdf github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= -github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= -github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= @@ -177,9 +150,6 @@ github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfF github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM= -github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4= @@ -187,10 +157,6 @@ github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= -github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= -github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= -github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -203,10 +169,6 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= -github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= -github.com/lithammer/dedent v1.1.0 h1:VNzHMVCBNG1j0fh3OrsFRkVUwStdDArbgBWoPAffktY= -github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= @@ -216,29 +178,20 @@ github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfr github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= -github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= -github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= -github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/moby/term v0.0.0-20221205130635-1aeaba878587 h1:HfkjXDfhgVaN5rmueG8cL8KKeFNecRCXFhaJ2qZ5SKA= -github.com/moby/term v0.0.0-20221205130635-1aeaba878587/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= -github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= -github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo/v2 v2.13.2 h1:Bi2gGVkfn6gQcjNjZJVO8Gf0FHzMPf2phUei9tejVMs= @@ -250,8 +203,6 @@ github.com/opencontainers/image-spec v1.1.0-rc2 h1:2zx/Stx4Wc5pIPDvIxHXvXtQFW/7X github.com/opencontainers/image-spec v1.1.0-rc2/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ= github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= -github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= -github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -266,13 +217,10 @@ github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI= github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -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= github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= -github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= @@ -284,8 +232,6 @@ github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNo github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= -github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= -github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.18.1 h1:rmuU42rScKWlhhJDyXZRKJQHXFX02chSVW1IvkPGiVM= @@ -294,7 +240,6 @@ github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ai github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= @@ -309,8 +254,6 @@ github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8 github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ= github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY= -github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= -github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= @@ -326,8 +269,6 @@ go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ3 go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc= go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= -go.starlark.net v0.0.0-20230525235612-a134d8f9ddca h1:VdD38733bfYv5tUZwEIskMM93VanwNIi5bIKnDrJdEY= -go.starlark.net v0.0.0-20230525235612-a134d8f9ddca/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds= go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= @@ -385,18 +326,15 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.15.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.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= @@ -491,24 +429,16 @@ k8s.io/apimachinery v0.28.4 h1:zOSJe1mc+GxuMnFzD4Z/U1wst50X28ZNsn5bhgIIao8= k8s.io/apimachinery v0.28.4/go.mod h1:wI37ncBvfAoswfq626yPTe6Bz1c22L7uaJ8dho83mgg= k8s.io/apiserver v0.28.4 h1:BJXlaQbAU/RXYX2lRz+E1oPe3G3TKlozMMCZWu5GMgg= k8s.io/apiserver v0.28.4/go.mod h1:Idq71oXugKZoVGUUL2wgBCTHbUR+FYTWa4rq9j4n23w= -k8s.io/cli-runtime v0.28.4 h1:IW3aqSNFXiGDllJF4KVYM90YX4cXPGxuCxCVqCD8X+Q= -k8s.io/cli-runtime v0.28.4/go.mod h1:MLGRB7LWTIYyYR3d/DOgtUC8ihsAPA3P8K8FDNIqJ0k= k8s.io/client-go v0.28.4 h1:Np5ocjlZcTrkyRJ3+T3PkXDpe4UpatQxj85+xjaD2wY= k8s.io/client-go v0.28.4/go.mod h1:0VDZFpgoZfelyP5Wqu0/r/TRYcLYuJ2U1KEeoaPa1N4= k8s.io/cluster-bootstrap v0.28.4 h1:4MKNy1Qd9QY7pl47rSMGIORF+tm3CUaqC1M8U9bjn4Q= k8s.io/cluster-bootstrap v0.28.4/go.mod h1:/c4ro/R4yf4EtJgFgFtvnHkbDOHwubeKJXh5R1c89Bc= k8s.io/component-base v0.28.4 h1:c/iQLWPdUgI90O+T9TeECg8o7N3YJTiuz2sKxILYcYo= k8s.io/component-base v0.28.4/go.mod h1:m9hR0uvqXDybiGL2nf/3Lf0MerAfQXzkfWhUY58JUbU= -k8s.io/component-helpers v0.28.4 h1:+X9VXT5+jUsRdC26JyMZ8Fjfln7mSjgumafocE509C4= -k8s.io/component-helpers v0.28.4/go.mod h1:8LzMalOQ0K10tkBJWBWq8h0HTI9HDPx4WT3QvTFn9Ro= 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-openapi v0.0.0-20230717233707-2695361300d9 h1:LyMgNKD2P8Wn1iAwQU5OhxCKlKJy0sHc+PcDwFB24dQ= k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9/go.mod h1:wZK2AVp1uHCp4VamDVgBP2COHZjqD1T68Rf0CM3YjSM= -k8s.io/kubectl v0.28.4 h1:gWpUXW/T7aFne+rchYeHkyB8eVDl5UZce8G4X//kjUQ= -k8s.io/kubectl v0.28.4/go.mod h1:CKOccVx3l+3MmDbkXtIUtibq93nN2hkDR99XDCn7c/c= -k8s.io/metrics v0.28.4 h1:u36fom9+6c8jX2sk8z58H0hFaIUfrPWbXIxN7GT2blk= -k8s.io/metrics v0.28.4/go.mod h1:bBqAJxH20c7wAsTQxDXOlVqxGMdce49d7WNr1WeaLac= k8s.io/utils v0.0.0-20231127182322-b307cd553661 h1:FepOBzJ0GXm8t0su67ln2wAZjbQ6RxQGZDnzuLcrUTI= k8s.io/utils v0.0.0-20231127182322-b307cd553661/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/controller-runtime v0.16.3 h1:2TuvuokmfXvDUamSx1SuAOO3eTyye+47mJCigwG62c4= @@ -521,8 +451,6 @@ sigs.k8s.io/kubebuilder/docs/book/utils v0.0.0-20211028165026-57688c578b5d h1:KL sigs.k8s.io/kubebuilder/docs/book/utils v0.0.0-20211028165026-57688c578b5d/go.mod h1:NRdZafr4zSCseLQggdvIMXa7umxf+Q+PJzrj3wFwiGE= sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 h1:XX3Ajgzov2RKUdc5jW3t5jwY7Bo7dcRm+tFxT+NfgY0= sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3/go.mod h1:9n16EZKMhXBNSiUC5kSdFQJkdH3zbxS/JoO619G1VAY= -sigs.k8s.io/kustomize/kustomize/v5 v5.0.4-0.20230601165947-6ce0bf390ce3 h1:vq2TtoDcQomhy7OxXLUOzSbHMuMYq0Bjn93cDtJEdKw= -sigs.k8s.io/kustomize/kustomize/v5 v5.0.4-0.20230601165947-6ce0bf390ce3/go.mod h1:/d88dHCvoy7d0AKFT0yytezSGZKjsZBVs9YTkBHSGFk= sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3 h1:W6cLQc5pnqM7vh3b7HvGNfXrJ/xL6BDMS0v1V/HHg5U= sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3/go.mod h1:JWP1Fj0VWGHyw3YUPjXSQnRnrwezrZSrApfX5S0nIag= sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= diff --git a/hack/tools/release/notes/generator.go b/hack/tools/release/notes/generator.go new file mode 100644 index 000000000000..49e6e92e7e8b --- /dev/null +++ b/hack/tools/release/notes/generator.go @@ -0,0 +1,82 @@ +//go:build tools +// +build tools + +/* +Copyright 2023 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +// notesGenerator orchestrates the release notes generation. +// Lists the selected PRs for this collection of notes, +// process them to generate one entry per PR and then +// formats and prints the results. +type notesGenerator struct { + lister prLister + processor prProcessor + printer entriesPrinter +} + +func newNotesGenerator(lister prLister, processor prProcessor, printer entriesPrinter) *notesGenerator { + return ¬esGenerator{ + lister: lister, + processor: processor, + printer: printer, + } +} + +// PR is a GitHub PR. +type pr struct { + number uint64 + title string + labels []string +} + +// prLister returns a list of PRs. +type prLister interface { + listPRs() ([]pr, error) +} + +// notesEntry represents a line item for the release notes. +type notesEntry struct { + title string + section string + prNumber string +} + +// prProcessor generates notes entries for a list of PRs. +type prProcessor interface { + process([]pr) []notesEntry +} + +// entriesPrinter formats and outputs to stdout the notes +// based on a list of entries. +type entriesPrinter interface { + print([]notesEntry) +} + +// run generates and prints the notes. +func (g *notesGenerator) run() error { + prs, err := g.lister.listPRs() + if err != nil { + return err + } + + entries := g.processor.process(prs) + + g.printer.print(entries) + + return nil +} diff --git a/hack/tools/release/notes/github.go b/hack/tools/release/notes/github.go new file mode 100644 index 000000000000..8a93d3803706 --- /dev/null +++ b/hack/tools/release/notes/github.go @@ -0,0 +1,205 @@ +//go:build tools +// +build tools + +/* +Copyright 2023 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "encoding/json" + "fmt" + "log" + "math" + "os/exec" + "strings" + "time" +) + +// githubClient uses the gh CLI to make API request to GitHub. +type githubClient struct { + // repo is full [org]/[repo_name] + repo string +} + +// githubDiff is the API response for the "compare" endpoint. +type githubDiff struct { + // MergeBaseCommit points to most recent common ancestor between two references. + MergeBaseCommit githubCommitNode `json:"merge_base_commit"` + Commits []githubCommitNode `json:"commits"` + Total int `json:"total_commits"` +} + +type githubCommitNode struct { + Commit githubCommit `json:"commit"` +} + +type githubCommitter struct { + Date time.Time `json:"date"` +} + +// getDiffAllCommits calls the `compare` endpoint, iterating over all pages and aggregating results. +func (c githubClient) getDiffAllCommits(base, head string) (*githubDiff, error) { + pageSize := 250 + url := fmt.Sprintf("repos/%s/compare/%s...%s", c.repo, base, head) + + diff, commits, err := iterate(c, url, pageSize, nil, func(page *githubDiff) ([]githubCommitNode, int) { + return page.Commits, page.Total + }) + if err != nil { + return nil, err + } + + diff.Commits = commits + + return diff, nil +} + +// githubRef is the API response for the "ref" endpoint. +type githubRef struct { + Object githubObject `json:"object"` +} + +type objectType string + +const ( + commitType objectType = "commit" + tagType objectType = "tag" +) + +type githubObject struct { + ObjectType objectType `json:"type"` + SHA string `json:"sha"` +} + +// getRef calls the `git/ref` endpoint. +func (c githubClient) getRef(ref string) (githubRef, error) { + refResponse := githubRef{} + if err := c.runGHAPICommand(fmt.Sprintf("repos/%s/git/ref/%s", c.repo, ref), &refResponse); err != nil { + return githubRef{}, err + } + return refResponse, nil +} + +// githubTag is the API response for the "tags" endpoint. +type githubTag struct { + Object githubObject `json:"object"` +} + +// getTag calls the `tags` endpoint. +func (c githubClient) getTag(tagSHA string) (githubTag, error) { + tagResponse := githubTag{} + if err := c.runGHAPICommand(fmt.Sprintf("repos/%s/git/tags/%s", c.repo, tagSHA), &tagResponse); err != nil { + return githubTag{}, err + } + return tagResponse, nil +} + +// githubCommit is the API response for a "git/commits" request. +type githubCommit struct { + Message string `json:"message"` + Committer githubCommitter `json:"committer"` +} + +// getCommit calls the `commits` endpoint. +func (c githubClient) getCommit(sha string) (githubCommit, error) { + commit := githubCommit{} + if err := c.runGHAPICommand(fmt.Sprintf("repos/%s/git/commits/%s", c.repo, sha), &commit); err != nil { + return githubCommit{}, err + } + return commit, nil +} + +// githubPRList is the API response for the "search" endpoint. +type githubPRList struct { + Total int `json:"total_count"` + Items []githubPR `json:"items"` +} + +// githubPR is the API object included in a "search" query response when the +// return item is a PR. +type githubPR struct { + Number uint64 `json:"number"` + Title string `json:"title"` + Labels []githubLabel `json:"labels"` +} + +type githubLabel struct { + Name string `json:"name"` +} + +// listMergedPRs calls the `search` endpoint and queries for PRs. +func (c githubClient) listMergedPRs(after, before time.Time, baseBranches ...string) ([]githubPR, error) { + pageSize := 100 + searchQuery := fmt.Sprintf("repo:%s+is:pr+is:merged+merged:%s..%s", c.repo, after.Format(time.RFC3339), before.Format(time.RFC3339)) + if len(baseBranches) != 0 { + searchQuery += "+base:" + strings.Join(baseBranches, "+base:") + } + + _, prs, err := iterate(c, "search/issues", pageSize, []string{"q=" + searchQuery}, func(page *githubPRList) ([]githubPR, int) { + return page.Items, page.Total + }) + if err != nil { + return nil, err + } + + return prs, nil +} + +func (c githubClient) runGHAPICommand(url string, response any) error { + cmd := exec.Command("gh", "api", url) + + out, err := cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("%s: %v", string(out), err) + } + + return json.Unmarshal(out, response) +} + +type extractFunc[T, C any] func(page *T) (pageElements []C, totalElements int) + +func iterate[T, C any](client githubClient, url string, pageSize int, extraQueryArgs []string, extract extractFunc[T, C]) (*T, []C, error) { + page := 0 + totalElements := math.MaxInt + elementsRead := 0 + + var firstPage *T + var collection []C + + for elementsRead < totalElements { + page++ + requestURL := fmt.Sprintf("%s?per_page=%d&page=%d&%s", url, pageSize, page, strings.Join(extraQueryArgs, "&")) + log.Printf("Calling endpoint %s", requestURL) + pageResult := new(T) + if err := client.runGHAPICommand(requestURL, pageResult); err != nil { + return nil, nil, err + } + + pageRead, t := extract(pageResult) + collection = append(collection, pageRead...) + elementsRead += len(pageRead) + totalElements = t + + if firstPage == nil { + firstPage = pageResult + } + } + + log.Printf("Total of %d pages and %d elements read", page, len(collection)) + + return firstPage, collection, nil +} diff --git a/hack/tools/release/notes/list.go b/hack/tools/release/notes/list.go new file mode 100644 index 000000000000..2c0afa1b1ea6 --- /dev/null +++ b/hack/tools/release/notes/list.go @@ -0,0 +1,154 @@ +//go:build tools +// +build tools + +/* +Copyright 2023 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "fmt" + "log" + "regexp" + "time" + + "github.com/pkg/errors" +) + +// githubFromToPRLister lists PRs from GitHub contained between two refs. +type githubFromToPRLister struct { + client *githubClient + fromRef, toRef ref + // branch is optional. It helps optimize the PR query by restricting + // the results to PRs merged in the selected branch and in main + branch string +} + +func newGithubFromToPRLister(repo string, fromRef, toRef ref, branch string) *githubFromToPRLister { + return &githubFromToPRLister{ + client: &githubClient{repo: repo}, + fromRef: fromRef, + toRef: toRef, + branch: branch, + } +} + +// listPRs returns the PRs merged between `fromRef` and `toRef` (included). +// It lists all PRs merged in main in the configured branch in the date +// range between fromRef and toRef (we include main because minor releases +// include PRs both in main and the release branch). +// Then it crosschecks them with the PR numbers found in the commits +// between fromRef and toRef, discarding any PR not seeing in the commits list. +// This ensures we don't include any PR merged in the same date range that +// doesn't belong to our git timeline. +func (l *githubFromToPRLister) listPRs() ([]pr, error) { + log.Printf("Computing diff between %s and %s", l.fromRef, l.toRef) + diff, err := l.client.getDiffAllCommits(l.fromRef.value, l.toRef.value) + if err != nil { + return nil, err + } + + log.Printf("Reading ref %s for upper limit", l.toRef) + toRef, err := l.client.getRef(l.toRef.String()) + if err != nil { + return nil, err + } + + var toCommitSHA string + if toRef.Object.ObjectType == tagType { + log.Printf("Reading tag info %s for upper limit", toRef.Object.SHA) + toTag, err := l.client.getTag(toRef.Object.SHA) + if err != nil { + return nil, err + } + toCommitSHA = toTag.Object.SHA + } else { + toCommitSHA = toRef.Object.SHA + } + + log.Printf("Reading commit %s for upper limit", toCommitSHA) + toCommit, err := l.client.getCommit(toCommitSHA) + if err != nil { + return nil, err + } + + fromDate := diff.MergeBaseCommit.Commit.Committer.Date + // We add an extra minute to avoid errors by 1 (observed during testing) + // We cross check the list of PRs against the list of commits, so we will filter out + // any PRs entries not belonging to the computed diff + toDate := toCommit.Committer.Date.Add(1 * time.Minute) + + log.Printf("Listing PRs from %s to %s", fromDate, toDate) + // We include both the configured branch and `main` as the base branches because when + // cutting a new minor version, there will be PRs merged in both main and the release branch. + // This is just an optimization to avoid listing PRs over all branches, since we know we don't + // need the PRs from other release branches. + gPRs, err := l.client.listMergedPRs(fromDate, toDate, l.branch, "main") + if err != nil { + return nil, err + } + + log.Printf("Found %d PRs in github", len(gPRs)) + + selectedPRNumbers := buildSetOfPRNumbers(diff.Commits) + + prs := make([]pr, 0, len(gPRs)) + for _, p := range gPRs { + if _, ok := selectedPRNumbers[fmt.Sprintf("%d", p.Number)]; !ok { + continue + } + labels := make([]string, 0, len(p.Labels)) + for _, l := range p.Labels { + labels = append(labels, l.Name) + } + prs = append(prs, pr{ + number: p.Number, + title: p.Title, + labels: labels, + }) + } + + log.Printf("%d PRs match the commits from the git diff", len(prs)) + + if len(prs) != len(selectedPRNumbers) { + return nil, errors.Errorf("expected %d PRs from commit list but only found %d", len(selectedPRNumbers), len(prs)) + } + + return prs, nil +} + +var ( + mergeCommitMessage = regexp.MustCompile(`(?m)^Merge pull request #(\d+) .*$`) + tideSquashedCommitMessage = regexp.MustCompile(`(?m)^.+\(#(?P\d+)\)$`) +) + +func buildSetOfPRNumbers(commits []githubCommitNode) map[string]struct{} { + prNumbers := make(map[string]struct{}) + for _, commit := range commits { + match := mergeCommitMessage.FindStringSubmatch(commit.Commit.Message) + if len(match) == 2 { + prNumbers[match[1]] = struct{}{} + continue + } + + match = tideSquashedCommitMessage.FindStringSubmatch(commit.Commit.Message) + if len(match) == 2 { + prNumbers[match[1]] = struct{}{} + } + } + + return prNumbers +} diff --git a/hack/tools/release/notes/list_test.go b/hack/tools/release/notes/list_test.go new file mode 100644 index 000000000000..688abc9a1c0e --- /dev/null +++ b/hack/tools/release/notes/list_test.go @@ -0,0 +1,67 @@ +//go:build tools +// +build tools + +/* +Copyright 2023 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "testing" + + . "github.com/onsi/gomega" +) + +func Test_buildSetOfPRNumbers(t *testing.T) { + tests := []struct { + name string + commits []githubCommitNode + want map[string]struct{} + }{ + { + name: "merge commit", + commits: []githubCommitNode{ + { + Commit: githubCommit{ + Message: "Merge pull request #9072 from k8s-infra-cherrypick-robot/cherry-pick-9070-to-release-1.5\n\n[release-1.5] :bug: Change tilt debug base image to golang", + }, + }, + }, + want: map[string]struct{}{ + "9072": {}, + }, + }, + { + name: "squashed commit by tide", + commits: []githubCommitNode{ + { + Commit: githubCommit{ + Message: ":seedling: Add dependabot groups. Allow additional patch updates (#9263)\n\n* Allow patch updates on dependabot ignore list\n\nSigned-off-by: user \n\n* Add dependency groups for dependabot\n\nSigned-off-by: user \n\n---------\n\nSigned-off-by: user ", + }, + }, + }, + want: map[string]struct{}{ + "9263": {}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewWithT(t) + g.Expect(buildSetOfPRNumbers(tt.commits)).To(Equal(tt.want)) + }) + } +} diff --git a/hack/tools/release/notes/main.go b/hack/tools/release/notes/main.go index eeeed683e93d..2417667dc974 100644 --- a/hack/tools/release/notes/main.go +++ b/hack/tools/release/notes/main.go @@ -21,414 +21,100 @@ limitations under the License. package main import ( - "bytes" - "encoding/json" - "errors" "flag" "fmt" - "os" + "log" "os/exec" - "regexp" - "sort" - "strings" - "sync" - "time" - release "sigs.k8s.io/cluster-api/hack/tools/release/internal" + "github.com/blang/semver/v4" + "github.com/pkg/errors" ) /* -This tool prints all the titles of all PRs from previous release to HEAD. -This needs to be run *before* a tag is created. +This tool prints all the titles of all PRs in between to references. Use these as the base of your release notes. */ -var ( - outputOrder = []string{ - release.Proposals, - release.Warning, - release.Features, - release.Bugs, - release.Other, - release.Documentation, - release.Unknown, - } - - repo = flag.String("repository", "kubernetes-sigs/cluster-api", "The repo to run the tool from.") - - fromTag = flag.String("from", "", "The tag or commit to start from.") - - since = flag.String("since", "", "Include commits starting from and including this date. Accepts format: YYYY-MM-DD") - until = flag.String("until", "", "Include commits up to and including this date. Accepts format: YYYY-MM-DD") - numWorkers = flag.Int("workers", 10, "Number of concurrent routines to process PR entries. If running into GitHub rate limiting, use 1.") - - prefixAreaLabel = flag.Bool("prefix-area-label", true, "If enabled, will prefix the area label.") - - preReleaseVersion = flag.Bool("pre-release-version", false, "If enabled, will add a pre-release warning header. (default false)") - deprecation = flag.Bool("deprecation", true, "If enabled, will add a templated deprecation warning header.") - addKubernetesVersionSupport = flag.Bool("add-kubernetes-version-support", true, "If enabled, will add the Kubernetes version support header.") - - tagRegex = regexp.MustCompile(`^\[release-[\w-\.]*\]`) - - userFriendlyAreas = map[string]string{ - "e2e-testing": "e2e", - "provider/control-plane-kubeadm": "KCP", - "provider/infrastructure-docker": "CAPD", - "dependency": "Dependency", - "devtools": "Devtools", - "machine": "Machine", - "api": "API", - "machinepool": "MachinePool", - "clustercachetracker": "ClusterCacheTracker", - "clusterclass": "ClusterClass", - "testing": "Testing", - "release": "Release", - "machineset": "MachineSet", - "clusterresourceset": "ClusterResourceSet", - "machinedeployment": "MachineDeployment", - "ipam": "IPAM", - "provider/bootstrap-kubeadm": "CABPK", - "provider/infrastructure-in-memory": "CAPIM", - "provider/core": "Core", - "runtime-sdk": "Runtime SDK", - "ci": "CI", - "machinehealthcheck": "MachineHealthCheck", - "clusterctl": "clusterctl", // Preserve lowercase - "util": "util", // Preserve lowercase - "community-meeting": "Community meeting", - } - - releaseBackportMarker = regexp.MustCompile(`(?m)^\[release-\d\.\d\]\s*`) -) - func main() { - flag.Parse() - os.Exit(run()) -} - -func lastTag() string { - if fromTag != nil && *fromTag != "" { - return *fromTag - } - cmd := exec.Command("git", "describe", "--tags", "--abbrev=0") - out, err := cmd.Output() - if err != nil { - return firstCommit() + cmd := newNotesCmd() + if err := cmd.run(); err != nil { + log.Fatal(err) } - return string(bytes.TrimSpace(out)) } -func firstCommit() string { - cmd := exec.Command("git", "rev-list", "--max-parents=0", "HEAD") - out, err := cmd.Output() - if err != nil { - return "UNKNOWN" - } - return string(bytes.TrimSpace(out)) +type notesCmdConfig struct { + repo string + fromRef string + toRef string + newTag string + branch string + prefixAreaLabel bool + preReleaseVersion bool + deprecation bool + addKubernetesVersionSupport bool } -// Since git doesn't include the last day in rev-list we want to increase 1 day to include it in the interval. -func increaseDateByOneDay(date string) (string, error) { - layout := "2006-01-02" - datetime, err := time.Parse(layout, date) - if err != nil { - return "", err - } - datetime = datetime.Add(time.Hour * 24) - return datetime.Format(layout), nil -} - -const ( - missingAreaLabelPrefix = "MISSING_AREA" - areaLabelPrefix = "area/" - multipleAreaLabelsPrefix = "MULTIPLE_AREAS[" - documentationAreaLabel = "Documentation" -) - -type githubPullRequest struct { - Labels []githubLabel `json:"labels"` -} - -type githubLabel struct { - Name string `json:"name"` -} - -func getAreaLabel(merge string) (string, error) { - // Get pr id from merge commit - prID := strings.Replace(strings.TrimSpace(strings.Split(merge, " ")[3]), "#", "", -1) - - cmd := exec.Command("gh", "api", fmt.Sprintf("repos/%s/pulls/%s", *repo, prID)) //nolint:gosec - - out, err := cmd.CombinedOutput() - if err != nil { - return "", fmt.Errorf("%s: %v", string(out), err) - } +func readCmdConfig() *notesCmdConfig { + config := ¬esCmdConfig{} - pr := &githubPullRequest{} - if err := json.Unmarshal(out, pr); err != nil { - return "", err - } + flag.StringVar(&config.repo, "repository", "kubernetes-sigs/cluster-api", "The repo to run the tool from.") + flag.StringVar(&config.fromRef, "from", "", "The tag or commit to start from. It must be formatted as heads/ for branches and tags/ for tags. If not set, it will be calculated from release.") + flag.StringVar(&config.toRef, "to", "", "The ref (tag, branch or commit to stop at. It must be formatted as heads/ for branches and tags/ for tags. If not set, it will default to branch.") + flag.StringVar(&config.branch, "branch", "", "The branch to generate the notes from. If not set, it will be calculated from release.") + flag.StringVar(&config.newTag, "release", "", "The tag for the new release.") - var areaLabels []string - for _, label := range pr.Labels { - if area, ok := trimAreaLabel(label.Name); ok { - if userFriendlyArea, ok := userFriendlyAreas[area]; ok { - area = userFriendlyArea - } else { - area = capitalize(area) - } + flag.BoolVar(&config.prefixAreaLabel, "prefix-area-label", true, "If enabled, will prefix the area label.") + flag.BoolVar(&config.preReleaseVersion, "pre-release-version", false, "If enabled, will add a pre-release warning header. (default false)") + flag.BoolVar(&config.deprecation, "deprecation", true, "If enabled, will add a templated deprecation warning header.") + flag.BoolVar(&config.addKubernetesVersionSupport, "add-kubernetes-version-support", true, "If enabled, will add the Kubernetes version support header.") - areaLabels = append(areaLabels, area) - } - } + flag.Parse() - switch len(areaLabels) { - case 0: - return missingAreaLabelPrefix, nil - case 1: - return areaLabels[0], nil - default: - return multipleAreaLabelsPrefix + strings.Join(areaLabels, "/") + "]", nil - } + return config } -// trimAreaLabel removes the "area/" prefix from area labels and returns it. -// If the label is an area label, the second return value is true, otherwise false. -func trimAreaLabel(label string) (string, bool) { - trimmed := strings.TrimPrefix(label, areaLabelPrefix) - if len(trimmed) < len(label) { - return trimmed, true - } - - return label, false +type notesCmd struct { + config *notesCmdConfig } -func run() int { - if err := ensureInstalledDependencies(); err != nil { - fmt.Println(err) - return 1 - } - - var commitRange string - var cmd *exec.Cmd - - if *since != "" && *until != "" { - commitRange = fmt.Sprintf("%s - %s", *since, *until) - - lastDay, err := increaseDateByOneDay(*until) - if err != nil { - fmt.Println(err) - return 1 - } - cmd = exec.Command("git", "rev-list", "HEAD", "--since=\""+*since+"\"", "--until=\""+lastDay+"\"", "--merges", "--pretty=format:%B") //nolint:gosec - } else if *since != "" || *until != "" { - fmt.Println("--since and --until are required together or both unset") - return 1 - } else { - commitRange = lastTag() - cmd = exec.Command("git", "rev-list", commitRange+"..HEAD", "--merges", "--pretty=format:%B") //nolint:gosec - } - - merges := map[string][]string{ - release.Features: {}, - release.Bugs: {}, - release.Documentation: {}, - release.Warning: {}, - release.Other: {}, - release.Unknown: {}, - } - out, err := cmd.CombinedOutput() - if err != nil { - fmt.Println("Error") - fmt.Println(string(out)) - return 1 - } - - commits := []*commit{} - outLines := strings.Split(string(out), "\n") - for _, line := range outLines { - line = strings.TrimSpace(line) - last := len(commits) - 1 - switch { - case strings.HasPrefix(line, "commit"): - commits = append(commits, &commit{}) - case strings.HasPrefix(line, "Merge"): - commits[last].merge = line - continue - case line == "": - default: - commits[last].body = line - } - } - - results := make(chan releaseNoteEntryResult) - commitCh := make(chan *commit) - var wg sync.WaitGroup - - wg.Add(*numWorkers) - for i := 0; i < *numWorkers; i++ { - go func() { - for commit := range commitCh { - processed := releaseNoteEntryResult{} - processed.prEntry, processed.err = generateReleaseNoteEntry(commit) - results <- processed - } - wg.Done() - }() - } - - go func() { - for _, c := range commits { - commitCh <- c - } - close(commitCh) - }() - - go func() { - wg.Wait() - close(results) - }() - - for result := range results { - if result.err != nil { - fmt.Println(result.err) - return -1 - } - - if result.prEntry == nil || result.prEntry.title == "" { - continue - } - - if result.prEntry.section == release.Documentation { - merges[result.prEntry.section] = append(merges[result.prEntry.section], result.prEntry.prNumber) - } else { - merges[result.prEntry.section] = append(merges[result.prEntry.section], result.prEntry.title) - } - } - - if *preReleaseVersion { - fmt.Printf("🚨 This is a RELEASE CANDIDATE. Use it only for testing purposes. If you find any bugs, file an [issue](https://github.com/%s/issues/new).\n", *repo) - } - - if *addKubernetesVersionSupport { - fmt.Print(`## 👌 Kubernetes version support - -- Management Cluster: v1.**X**.x -> v1.**X**.x -- Workload Cluster: v1.**X**.x -> v1.**X**.x - -[More information about version support can be found here](https://cluster-api.sigs.k8s.io/reference/versions.html) - -`) +func newNotesCmd() *notesCmd { + config := readCmdConfig() + return ¬esCmd{ + config: config, } +} - fmt.Print(`## Highlights - -* REPLACE ME - -`) - - if *deprecation { - fmt.Print(`## Deprecation Warning - -REPLACE ME: A couple sentences describing the deprecation, including links to docs. - -* [GitHub issue #REPLACE ME](REPLACE ME) - -`) +func (cmd *notesCmd) run() error { + if err := validateConfig(cmd.config); err != nil { + return err } - fmt.Printf("## Changes since %v\n", commitRange) - - fmt.Printf("## :chart_with_upwards_trend: Overview\n") - if count := len(commits); count == 1 { - fmt.Println("- 1 new commit merged") - } else if count > 1 { - fmt.Printf("- %d new commits merged\n", count) - } - if count := len(merges[release.Warning]); count == 1 { - fmt.Println("- 1 breaking change :warning:") - } else if count > 1 { - fmt.Printf("- %d breaking changes :warning:\n", count) - } - if count := len(merges[release.Features]); count == 1 { - fmt.Println("- 1 feature addition ✨") - } else if count > 1 { - fmt.Printf("- %d feature additions ✨\n", count) + if err := computeConfigDefaults(cmd.config); err != nil { + return err } - if count := len(merges[release.Bugs]); count == 1 { - fmt.Println("- 1 bug fixed 🐛") - } else if count > 1 { - fmt.Printf("- %d bugs fixed 🐛\n", count) - } - fmt.Println() - - for _, key := range outputOrder { - mergeslice := merges[key] - if len(mergeslice) == 0 { - continue - } - switch key { - case release.Documentation: - sort.Strings(mergeslice) - if len(mergeslice) == 1 { - fmt.Printf( - ":book: Additionally, there has been 1 contribution to our documentation and book. (%s) \n\n", - mergeslice[0], - ) - } else { - fmt.Printf( - ":book: Additionally, there have been %d contributions to our documentation and book. (%s) \n\n", - len(mergeslice), - strings.Join(mergeslice, ", "), - ) - } - default: - fmt.Println("## " + key) - sort.Slice(mergeslice, func(i int, j int) bool { - str1 := strings.ToLower(mergeslice[i]) - str2 := strings.ToLower(mergeslice[j]) - return str1 < str2 - }) - - for _, merge := range mergeslice { - fmt.Println(merge) - } - fmt.Println() - } + if err := ensureInstalledDependencies(); err != nil { + return err } - fmt.Println("") - fmt.Println("_Thanks to all our contributors!_ 😊") - - return 0 -} + from, to := parseRef(cmd.config.fromRef), parseRef(cmd.config.toRef) -func trimTitle(title string) string { - // Remove a tag prefix if found. - title = tagRegex.ReplaceAllString(title, "") + printer := newReleaseNotesPrinter(cmd.config.repo, from.value) + printer.isPreRelease = cmd.config.preReleaseVersion + printer.printDeprecation = cmd.config.deprecation + printer.printKubernetesSupport = cmd.config.addKubernetesVersionSupport - return strings.TrimSpace(title) -} + generator := newNotesGenerator( + newGithubFromToPRLister(cmd.config.repo, from, to, cmd.config.branch), + newPREntryProcessor(cmd.config.prefixAreaLabel), + printer, + ) -type commit struct { - merge string - body string -} - -func formatMerge(line, prNumber string) string { - if prNumber == "" { - return line - } - return fmt.Sprintf("%s (%s)", line, prNumber) + return generator.run() } func ensureInstalledDependencies() error { - if !commandExists("git") { - return errors.New("git not available. Git is required to be present in the PATH") - } - if !commandExists("gh") { return errors.New("gh GitHub CLI not available. GitHub CLI is required to be present in the PATH. Refer to https://cli.github.com/ for installation") } @@ -441,121 +127,85 @@ func commandExists(cmd string) bool { return err == nil } -// releaseNoteEntryResult is the result of processing a PR to create a release note item. -// Used to aggregate the line item and error when processing concurrently. -type releaseNoteEntryResult struct { - prEntry *releaseNoteEntry - err error -} +func validateConfig(config *notesCmdConfig) error { + if config.fromRef == "" && config.newTag == "" { + return errors.New("at least one of --from or --release need to be set") + } -// releaseNoteEntry represents a line item in the release notes. -type releaseNoteEntry struct { - title string - section string - prNumber string -} + if config.branch == "" && config.newTag == "" { + return errors.New("at least one of --branch or --release need to be set") + } -// removePrefixes removes the specified prefixes from the title. -func removePrefixes(title string, prefixes []string) string { - entryWithoutTag := title - for _, prefix := range prefixes { - entryWithoutTag = strings.TrimLeft(strings.TrimPrefix(entryWithoutTag, prefix), " ") + if config.fromRef != "" { + if err := validateRef(config.fromRef); err != nil { + return err + } } - return entryWithoutTag -} + if config.toRef != "" { + if err := validateRef(config.toRef); err != nil { + return err + } + } -// trimAreaFromTitle removes the prefixed area from title to avoid duplication. -func trimAreaFromTitle(title, area string) string { - titleWithoutArea := title - pattern := `(?i)^` + regexp.QuoteMeta(area+":") - re := regexp.MustCompile(pattern) - titleWithoutArea = re.ReplaceAllString(titleWithoutArea, "") - titleWithoutArea = strings.TrimSpace(titleWithoutArea) - return titleWithoutArea + return nil } -func capitalize(str string) string { - return strings.ToUpper(string(str[0])) + str[1:] -} +// computeConfigDefaults calculates the value the non specified configuration fields +// based on the set fields. +func computeConfigDefaults(config *notesCmdConfig) error { + if config.fromRef != "" && config.branch != "" { + return nil + } -// generateReleaseNoteEntry processes a commit into a PR line item for the release notes. -func generateReleaseNoteEntry(c *commit) (*releaseNoteEntry, error) { - entry := &releaseNoteEntry{} - if c.body == "" { - c.body = "ERROR: BODY MISSING. FIX MANUALLY" + newTag, err := semver.ParseTolerant(config.newTag) + if err != nil { + return errors.Wrap(err, "invalid --release, is not a semver") } - entry.title = trimTitle(c.body) - var fork string - - var area string - if *prefixAreaLabel { - var err error - area, err = getAreaLabel(c.merge) - if err != nil { - return nil, err + + if config.fromRef == "" { + if newTag.Patch == 0 { + // If patch = 0, this a new minor release + // Hence we want to read commits from + config.fromRef = "tags/" + fmt.Sprintf("v%d.%d.0", newTag.Major, newTag.Minor-1) + } else { + // if not new minor release, this is a new patch, just decrease the patch + config.fromRef = "tags/" + fmt.Sprintf("v%d.%d.%d", newTag.Major, newTag.Minor, newTag.Patch-1) } } - switch { - case strings.HasPrefix(entry.title, ":sparkles:"), strings.HasPrefix(entry.title, "✨"): - entry.section = release.Features - entry.title = removePrefixes(entry.title, []string{":sparkles:", "✨"}) - case strings.HasPrefix(entry.title, ":bug:"), strings.HasPrefix(entry.title, "🐛"): - entry.section = release.Bugs - entry.title = removePrefixes(entry.title, []string{":bug:", "🐛"}) - case strings.HasPrefix(entry.title, ":book:"), strings.HasPrefix(entry.title, "📖"): - entry.section = release.Documentation - entry.title = removePrefixes(entry.title, []string{":book:", "📖"}) - if strings.Contains(entry.title, "CAEP") || strings.Contains(entry.title, "proposal") { - entry.section = release.Proposals - } - case strings.HasPrefix(entry.title, ":warning:"), strings.HasPrefix(entry.title, "⚠️"): - entry.section = release.Warning - entry.title = removePrefixes(entry.title, []string{":warning:", "⚠️"}) - case strings.HasPrefix(entry.title, "🚀"), strings.HasPrefix(entry.title, "🌱 Release v1."): - // TODO(g-gaston): remove the second condition using 🌱 prefix once 1.6 is released - // Release trigger PRs from previous releases are not included in the release notes - return nil, nil - case strings.HasPrefix(entry.title, ":seedling:"), strings.HasPrefix(entry.title, "🌱"): - entry.section = release.Other - entry.title = removePrefixes(entry.title, []string{":seedling:", "🌱"}) - default: - entry.section = release.Unknown + if config.branch == "" { + config.branch = defaultBranchForNewTag(newTag) } - // If the area label indicates documentation, use documentation as the section - // no matter what was the emoji used. This takes into account that the area label - // tends to be more accurate than the emoji (data point observed by the release team). - // We handle this after the switch statement to make sure we remove all emoji prefixes. - if area == documentationAreaLabel { - entry.section = release.Documentation + if config.toRef == "" { + config.toRef = "heads/" + config.branch } - entry.title = strings.TrimSpace(entry.title) - entry.title = trimReleaseBackportMarker(entry.title) + return nil +} - if entry.title == "" { - return entry, nil - } +// defaultBranchForNewTag calculates the branch to cut a release +// based on the new release tag. +func defaultBranchForNewTag(newTag semver.Version) string { + if newTag.Patch == 0 { + if len(newTag.Pre) == 0 { + // for new minor releases, use the release branch + return releaseBranchForVersion(newTag) + } else if len(newTag.Pre) == 2 && newTag.Pre[0].VersionStr == "rc" && newTag.Pre[1].VersionNum >= 1 { + // for the second or later RCs, we use the release branch since we cut this branch with the first RC + return releaseBranchForVersion(newTag) + } - if *prefixAreaLabel { - entry.title = trimAreaFromTitle(entry.title, area) - entry.title = capitalize(entry.title) - entry.title = fmt.Sprintf("- %s: %s", area, entry.title) - } else { - entry.title = capitalize(entry.title) - entry.title = fmt.Sprintf("- %s", entry.title) + // for any other pre release, we always cut from main + // this includes all beta releases and the first RC + return "main" } - _, _ = fmt.Sscanf(c.merge, "Merge pull request %s from %s", &entry.prNumber, &fork) - entry.title = formatMerge(entry.title, entry.prNumber) - - return entry, nil + // If it's a patch, we use the release branch + return releaseBranchForVersion(newTag) } -// trimReleaseBackportMarker removes the `[release-x.x]` prefix from a PR title if present. -// These are mostly used for back-ported PRs in release branches. -func trimReleaseBackportMarker(title string) string { - return releaseBackportMarker.ReplaceAllString(title, "${1}") +func releaseBranchForVersion(version semver.Version) string { + return fmt.Sprintf("release-%d.%d", version.Major, version.Minor) } diff --git a/hack/tools/release/notes/main_test.go b/hack/tools/release/notes/main_test.go index 83c7193f93f7..7bc1405720c9 100644 --- a/hack/tools/release/notes/main_test.go +++ b/hack/tools/release/notes/main_test.go @@ -19,7 +19,12 @@ limitations under the License. package main -import "testing" +import ( + "testing" + + "github.com/blang/semver/v4" + . "github.com/onsi/gomega" +) func Test_trimTitle(t *testing.T) { tests := []struct { @@ -103,3 +108,63 @@ func Test_trimAreaFromTitle(t *testing.T) { }) } } + +func Test_defaultBranchForNewTag(t *testing.T) { + tests := []struct { + name string + newVersion string + want string + }{ + { + name: "new minor", + newVersion: "v1.5.0", + want: "release-1.5", + }, + { + name: "new patch", + newVersion: "v1.6.1", + want: "release-1.6", + }, + { + name: "first RC", + newVersion: "v1.6.0-rc.0", + want: "main", + }, + { + name: "second RC", + newVersion: "v1.6.0-rc.1", + want: "release-1.6", + }, + { + name: "third RC", + newVersion: "v1.6.0-rc.2", + want: "release-1.6", + }, + { + name: "first Beta", + newVersion: "v1.6.0-beta.0", + want: "main", + }, + { + name: "second Beta", + newVersion: "v1.6.0-beta.1", + want: "main", + }, + { + name: "third Beta", + newVersion: "v1.6.0-beta.2", + want: "main", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewWithT(t) + + version, err := semver.ParseTolerant(tt.newVersion) + g.Expect(err).NotTo(HaveOccurred()) + + g.Expect(defaultBranchForNewTag(version)).To(Equal(tt.want)) + }) + } +} diff --git a/hack/tools/release/notes/print.go b/hack/tools/release/notes/print.go new file mode 100644 index 000000000000..4c2819577b7a --- /dev/null +++ b/hack/tools/release/notes/print.go @@ -0,0 +1,172 @@ +//go:build tools +// +build tools + +/* +Copyright 2023 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "fmt" + "sort" + "strings" + + release "sigs.k8s.io/cluster-api/hack/tools/release/internal" +) + +var defaultOutputOrder = []string{ + release.Proposals, + release.Warning, + release.Features, + release.Bugs, + release.Other, + release.Documentation, + release.Unknown, +} + +// releaseNotesPrinter outputs the PR entries following +// the right format for the release notes. +type releaseNotesPrinter struct { + outputOrder []string + isPreRelease bool + printKubernetesSupport bool + printDeprecation bool + fromTag string + repo string +} + +func newReleaseNotesPrinter(repo, fromTag string) *releaseNotesPrinter { + return &releaseNotesPrinter{ + repo: repo, + fromTag: fromTag, + outputOrder: defaultOutputOrder, + } +} + +// print outputs to stdout the release notes. +func (p *releaseNotesPrinter) print(entries []notesEntry) { + merges := map[string][]string{ + release.Features: {}, + release.Bugs: {}, + release.Documentation: {}, + release.Warning: {}, + release.Other: {}, + release.Unknown: {}, + } + + for _, entry := range entries { + if entry.section == release.Documentation { + merges[entry.section] = append(merges[entry.section], "#"+entry.prNumber) + } else { + merges[entry.section] = append(merges[entry.section], entry.title) + } + } + + if p.isPreRelease { + fmt.Printf("🚨 This is a RELEASE CANDIDATE. Use it only for testing purposes. If you find any bugs, file an [issue](https://github.com/%s/issues/new).\n", p.repo) + } + + if p.printKubernetesSupport { + fmt.Print(`## 👌 Kubernetes version support + +- Management Cluster: v1.**X**.x -> v1.**X**.x +- Workload Cluster: v1.**X**.x -> v1.**X**.x + +[More information about version support can be found here](https://cluster-api.sigs.k8s.io/reference/versions.html) + +`) + } + + fmt.Print(`## Highlights + +* REPLACE ME + +`) + + if p.printDeprecation { + fmt.Print(`## Deprecation Warning + +REPLACE ME: A couple sentences describing the deprecation, including links to docs. + +* [GitHub issue #REPLACE ME](REPLACE ME) + +`) + } + + fmt.Printf("## Changes since %s\n", p.fromTag) + + fmt.Printf("## :chart_with_upwards_trend: Overview\n") + if count := len(entries); count == 1 { + fmt.Println("- 1 new commit merged") + } else if count > 1 { + fmt.Printf("- %d new commits merged\n", count) + } + if count := len(merges[release.Warning]); count == 1 { + fmt.Println("- 1 breaking change :warning:") + } else if count > 1 { + fmt.Printf("- %d breaking changes :warning:\n", count) + } + if count := len(merges[release.Features]); count == 1 { + fmt.Println("- 1 feature addition ✨") + } else if count > 1 { + fmt.Printf("- %d feature additions ✨\n", count) + } + if count := len(merges[release.Bugs]); count == 1 { + fmt.Println("- 1 bug fixed 🐛") + } else if count > 1 { + fmt.Printf("- %d bugs fixed 🐛\n", count) + } + fmt.Println() + + for _, key := range p.outputOrder { + mergeslice := merges[key] + if len(mergeslice) == 0 { + continue + } + + switch key { + case release.Documentation: + sort.Strings(mergeslice) + if len(mergeslice) == 1 { + fmt.Printf( + ":book: Additionally, there has been 1 contribution to our documentation and book. (%s) \n\n", + mergeslice[0], + ) + } else { + fmt.Printf( + ":book: Additionally, there have been %d contributions to our documentation and book. (%s) \n\n", + len(mergeslice), + strings.Join(mergeslice, ", "), + ) + } + default: + fmt.Println("## " + key) + sort.Slice(mergeslice, func(i int, j int) bool { + str1 := strings.ToLower(mergeslice[i]) + str2 := strings.ToLower(mergeslice[j]) + return str1 < str2 + }) + + for _, merge := range mergeslice { + fmt.Println(merge) + } + fmt.Println() + } + } + + fmt.Println("") + fmt.Println("_Thanks to all our contributors!_ 😊") +} diff --git a/hack/tools/release/notes/process.go b/hack/tools/release/notes/process.go new file mode 100644 index 000000000000..7b5da18e6792 --- /dev/null +++ b/hack/tools/release/notes/process.go @@ -0,0 +1,253 @@ +//go:build tools +// +build tools + +/* +Copyright 2023 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "fmt" + "log" + "regexp" + "strings" + + release "sigs.k8s.io/cluster-api/hack/tools/release/internal" +) + +const ( + missingAreaLabelPrefix = "MISSING_AREA" + areaLabelPrefix = "area/" + multipleAreaLabelsPrefix = "MULTIPLE_AREAS[" + documentationArea = "Documentation" +) + +var ( + defaultUserFriendlyAreas = map[string]string{ + "e2e-testing": "e2e", + "provider/control-plane-kubeadm": "KCP", + "provider/infrastructure-docker": "CAPD", + "dependency": "Dependency", + "devtools": "Devtools", + "machine": "Machine", + "api": "API", + "machinepool": "MachinePool", + "clustercachetracker": "ClusterCacheTracker", + "clusterclass": "ClusterClass", + "testing": "Testing", + "release": "Release", + "machineset": "MachineSet", + "clusterresourceset": "ClusterResourceSet", + "machinedeployment": "MachineDeployment", + "ipam": "IPAM", + "provider/bootstrap-kubeadm": "CABPK", + "provider/infrastructure-in-memory": "CAPIM", + "provider/core": "Core", + "runtime-sdk": "Runtime SDK", + "ci": "CI", + "machinehealthcheck": "MachineHealthCheck", + "clusterctl": "clusterctl", // Preserve lowercase + "util": "util", // Preserve lowercase + "community-meeting": "Community meeting", + } + + tagRegex = regexp.MustCompile(`^\[release-[\w-\.]*\]`) + releaseBackportMarker = regexp.MustCompile(`(?m)^\[release-\d\.\d\]\s*`) +) + +type prEntriesProcessor struct { + userFriendlyAreas map[string]string + addAreaPrefix bool +} + +func newPREntryProcessor(addAreaPrefix bool) prEntriesProcessor { + return prEntriesProcessor{ + userFriendlyAreas: defaultUserFriendlyAreas, + addAreaPrefix: addAreaPrefix, + } +} + +// process generates a PR entry ready for printing per PR. It extracts the area +// from the PR labels and appends it as a prefix to the title. +// It might skip some PRs depending on the title. +func (g prEntriesProcessor) process(prs []pr) []notesEntry { + entries := make([]notesEntry, 0, len(prs)) + for i := range prs { + pr := &prs[i] + + entry := g.generateNoteEntry(pr) + + if entry == nil || entry.title == "" { + log.Printf("Ignoring PR [%s (#%d)]", pr.title, pr.number) + continue + } + + entries = append(entries, *entry) + } + + return entries +} + +func (g prEntriesProcessor) generateNoteEntry(p *pr) *notesEntry { + entry := ¬esEntry{} + + entry.title = trimTitle(p.title) + + var area string + if g.addAreaPrefix { + area = g.extractArea(p) + } + + switch { + case strings.HasPrefix(entry.title, ":sparkles:"), strings.HasPrefix(entry.title, "✨"): + entry.section = release.Features + entry.title = removePrefixes(entry.title, []string{":sparkles:", "✨"}) + case strings.HasPrefix(entry.title, ":bug:"), strings.HasPrefix(entry.title, "🐛"): + entry.section = release.Bugs + entry.title = removePrefixes(entry.title, []string{":bug:", "🐛"}) + case strings.HasPrefix(entry.title, ":book:"), strings.HasPrefix(entry.title, "📖"): + entry.section = release.Documentation + entry.title = removePrefixes(entry.title, []string{":book:", "📖"}) + if strings.Contains(entry.title, "CAEP") || strings.Contains(entry.title, "proposal") { + entry.section = release.Proposals + } + case strings.HasPrefix(entry.title, ":warning:"), strings.HasPrefix(entry.title, "⚠️"): + entry.section = release.Warning + entry.title = removePrefixes(entry.title, []string{":warning:", "⚠️"}) + case strings.HasPrefix(entry.title, "🚀"), strings.HasPrefix(entry.title, "🌱 Release v1."): + // TODO(g-gaston): remove the second condition using 🌱 prefix once 1.6 is released + // Release trigger PRs from previous releases are not included in the release notes + return nil + case strings.HasPrefix(entry.title, ":seedling:"), strings.HasPrefix(entry.title, "🌱"): + entry.section = release.Other + entry.title = removePrefixes(entry.title, []string{":seedling:", "🌱"}) + default: + entry.section = release.Unknown + } + + // If the area label indicates documentation, use documentation as the section + // no matter what was the emoji used. This takes into account that the area label + // tends to be more accurate than the emoji (data point observed by the release team). + // We handle this after the switch statement to make sure we remove all emoji prefixes. + if area == documentationArea { + entry.section = release.Documentation + } + + entry.title = strings.TrimSpace(entry.title) + entry.title = trimReleaseBackportMarker(entry.title) + + if entry.title == "" { + return nil + } + + if g.addAreaPrefix { + entry.title = trimAreaFromTitle(entry.title, area) + entry.title = capitalize(entry.title) + entry.title = fmt.Sprintf("- %s: %s", area, entry.title) + } else { + entry.title = capitalize(entry.title) + entry.title = fmt.Sprintf("- %s", entry.title) + } + + entry.prNumber = fmt.Sprintf("%d", p.number) + entry.title = formatPREntry(entry.title, entry.prNumber) + + return entry +} + +// extractArea processes the PR labels to extract the area. +func (g prEntriesProcessor) extractArea(pr *pr) string { + var areaLabels []string + for _, label := range pr.labels { + if area, ok := trimAreaLabel(label); ok { + if userFriendlyArea, ok := g.userFriendlyAreas[area]; ok { + area = userFriendlyArea + } else { + area = capitalize(area) + } + + areaLabels = append(areaLabels, area) + } + } + + switch len(areaLabels) { + case 0: + return missingAreaLabelPrefix + case 1: + return areaLabels[0] + default: + return multipleAreaLabelsPrefix + strings.Join(areaLabels, "/") + "]" + } +} + +// trimAreaLabel removes the "area/" prefix from area labels and returns it. +// If the label is an area label, the second return value is true, otherwise false. +func trimAreaLabel(label string) (string, bool) { + trimmed := strings.TrimPrefix(label, areaLabelPrefix) + if len(trimmed) < len(label) { + return trimmed, true + } + + return label, false +} + +// trimTitle removes release tags and white space from +// PR titles. +func trimTitle(title string) string { + // Remove a tag prefix if found. + title = tagRegex.ReplaceAllString(title, "") + + return strings.TrimSpace(title) +} + +// formatPREntry appends the PR number at the end of the title +// and makes it a valid link in GitHub release notes. +func formatPREntry(line, prNumber string) string { + if prNumber == "" { + return line + } + return fmt.Sprintf("%s (#%s)", line, prNumber) +} + +func capitalize(str string) string { + return strings.ToUpper(string(str[0])) + str[1:] +} + +// trimReleaseBackportMarker removes the `[release-x.x]` prefix from a PR title if present. +// These are mostly used for back-ported PRs in release branches. +func trimReleaseBackportMarker(title string) string { + return releaseBackportMarker.ReplaceAllString(title, "${1}") +} + +// removePrefixes removes the specified prefixes from the title. +func removePrefixes(title string, prefixes []string) string { + entryWithoutTag := title + for _, prefix := range prefixes { + entryWithoutTag = strings.TrimLeft(strings.TrimPrefix(entryWithoutTag, prefix), " ") + } + + return entryWithoutTag +} + +// trimAreaFromTitle removes the prefixed area from title to avoid duplication. +func trimAreaFromTitle(title, area string) string { + titleWithoutArea := title + pattern := `(?i)^` + regexp.QuoteMeta(area+":") + re := regexp.MustCompile(pattern) + titleWithoutArea = re.ReplaceAllString(titleWithoutArea, "") + titleWithoutArea = strings.TrimSpace(titleWithoutArea) + return titleWithoutArea +} diff --git a/hack/tools/release/notes/ref.go b/hack/tools/release/notes/ref.go new file mode 100644 index 000000000000..a0da62833315 --- /dev/null +++ b/hack/tools/release/notes/ref.go @@ -0,0 +1,54 @@ +//go:build tools +// +build tools + +/* +Copyright 2023 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "strings" + + "github.com/pkg/errors" +) + +// ref represents a git reference. +type ref struct { + // reType is the ref type: tags for a tag, head for a branch, commit for a commit. + reType string + value string +} + +func (r ref) String() string { + return r.reType + "/" + r.value +} + +func parseRef(r string) ref { + split := strings.SplitN(r, "/", 2) + return ref{ + reType: split[0], + value: split[1], + } +} + +func validateRef(r string) error { + split := strings.SplitN(r, "/", 2) + if len(split) != 2 { + return errors.Errorf("invalid ref %s: must follow [type]/[value]", r) + } + + return nil +} diff --git a/hack/tools/release/notes/release_notes_integration_test.go b/hack/tools/release/notes/release_notes_integration_test.go index 52f814737bc5..10ed87c888b3 100644 --- a/hack/tools/release/notes/release_notes_integration_test.go +++ b/hack/tools/release/notes/release_notes_integration_test.go @@ -27,44 +27,36 @@ import ( "testing" . "github.com/onsi/gomega" - "k8s.io/kubectl/pkg/cmd" ) func TestReleaseNotesIntegration(t *testing.T) { testCases := []struct { - name string - previousRelease string - releaseBranchForTest string - head string - expected string + name string + args []string + expected string }{ { // This tests a patch release computing the PR list from previous patch tag // to HEAD. Since v1.3 is out of support, we won't be backporting new PRs // so the branch should remain untouched and the test valid. - name: "new patch", - previousRelease: "v1.3.9", - releaseBranchForTest: "release-1.3", - head: "release-1.3", - expected: "test/golden/v1.3.10.md", + name: "new patch", + args: []string{"--release", "v1.3.10"}, + expected: "test/golden/v1.3.10.md", }, { - // The release notes command computes everything from last tag - // to HEAD. Hence if we use the head of release-1.5, this test will - // become invalid everytime we backport some PR to release branch release-1.5. - // Here we cheat a little by poiting to worktree to v1.5.0, which is the - // release that this test is simulating, so it should not exist yet. But - // it represents accurately the HEAD of release-1.5 when we released v1.5.0. - name: "new minor", - previousRelease: "v1.4.0", - releaseBranchForTest: "v1.5.0-integration-test", - head: "v1.5.0", - expected: "test/golden/v1.5.0.md", + // By default when using the `--release` options, notes command computes + // everything from last tag to HEAD. Hence if we use the head of release-1.5, + // this test will become invalid every time we backport some PR to release + // branch release-1.5. + // Instead, to simulate the v1.5.0 release, we manually set the `--to` flag, + // which sets the upper boundary for the PR search. + name: "new minor", + args: []string{"--from", "tags/v1.4.0", "--to", "tags/v1.5.0", "--branch", "release-1.5"}, + expected: "test/golden/v1.5.0.md", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - setupNotesTest(t, tc.previousRelease, tc.releaseBranchForTest, tc.head) g := NewWithT(t) expectedOutput, err := os.ReadFile(tc.expected) @@ -75,16 +67,20 @@ func TestReleaseNotesIntegration(t *testing.T) { t.Cleanup(func() { g.Expect(os.Chdir(orgCurrentDir)).To(Succeed()) }) - g.Expect(os.Chdir(tc.releaseBranchForTest)).To(Succeed()) // a two workers config is slow but it guarantees no rate limiting - os.Args = []string{os.Args[0], "--from", tc.previousRelease, "--workers", "2"} + os.Args = append([]string{os.Args[0]}, tc.args...) old := os.Stdout // keep backup of the real stdout to restore later r, w, err := os.Pipe() g.Expect(err).To(Succeed()) os.Stdout = w + t.Cleanup(func() { + // Reset defined flags so we can can call cmd.run() again + flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError) + }) + g.Expect(runReleaseNotesCmd()).To(Succeed()) w.Close() @@ -98,34 +94,14 @@ func TestReleaseNotesIntegration(t *testing.T) { } func runReleaseNotesCmd() error { - // we replicate the main function here so we don't get os.Exit - flag.Parse() - if code := run(); code != 0 { - return fmt.Errorf("release notes command exited with code %d", code) + cmd := newNotesCmd() + if err := cmd.run(); err != nil { + return fmt.Errorf("release notes command failed: %w", err) } return nil } -func setupNotesTest(tb testing.TB, previousRelease, releaseBranchForTest, head string) { - g := NewWithT(tb) - - _, err := os.Stat(releaseBranchForTest) - if os.IsNotExist(err) { - runCommand(tb, exec.Command("git", "worktree", "add", releaseBranchForTest)) - } else { - g.Expect(err).To(Succeed()) - } - - pull := exec.Command("git", "checkout", head) - pull.Dir = releaseBranchForTest - runCommand(tb, pull) - - tb.Cleanup(func() { - runCommand(tb, cmd.Command("git", "worktree", "remove", releaseBranchForTest)) - }) -} - func runCommand(tb testing.TB, cmd *exec.Cmd) { out, err := cmd.CombinedOutput() if err != nil { diff --git a/hack/tools/release/notes/test/golden/v1.5.0.md b/hack/tools/release/notes/test/golden/v1.5.0.md index 256b5a723471..01f35fdba2a8 100644 --- a/hack/tools/release/notes/test/golden/v1.5.0.md +++ b/hack/tools/release/notes/test/golden/v1.5.0.md @@ -17,7 +17,7 @@ REPLACE ME: A couple sentences describing the deprecation, including links to do ## Changes since v1.4.0 ## :chart_with_upwards_trend: Overview -- 335 new commits merged +- 339 new commits merged - 4 breaking changes :warning: - 19 feature additions ✨ - 67 bugs fixed 🐛 @@ -57,9 +57,9 @@ REPLACE ME: A couple sentences describing the deprecation, including links to do - CAPD: Add kind mapper (#8880) - CAPD: Change the haproxy entrypoint to prevent getting stopped immediately after start (#8685) - CAPD: Delegate CAPD port selection to the container runtime (#8642) +- CAPD: Fix fail-swap-on=false flag not being part of kind images anymore (#8767) - CAPD: Implement watch filter (#8789) - CAPD: Test/capd: fix kind mapper entry for v1.25.11 (#8914) -- CAPD: Test/e2e fix fail-swap-on=false flag not being part of kind images anymore (#8767) - CAPIM: Fix cluster deletion in the in-memory API server (#8818) - CAPIM: Fix inmemory provider docker build (#8822) - CAPIM: Test/e2e/in-memory: set providerID after VM is provisioned (#8879) @@ -150,6 +150,7 @@ REPLACE ME: A couple sentences describing the deprecation, including links to do - ClusterClass: Cluster/topology: use cached MD list in get current state (#8922) - ClusterClass: Deprecate rolloutAfter in cluster topology (#8324) - ClusterClass: Upgrading control plane should only be blocked if MD are upgrading (not just rolling out) (#8658) +- clusterctl: Add CABPOCNE and CACPOCNE Providers (#9068) - clusterctl: Add labels to OWNERS file (#8342) - clusterctl: Add move annotation on objects for cluster move operation (#8322) - clusterctl: Bump controller-tools to v0.12 (#8581) @@ -179,6 +180,7 @@ REPLACE ME: A couple sentences describing the deprecation, including links to do - Dependency: Bump github.com/onsi/ginkgo/v2 from 2.9.5 to 2.9.7 (#8792) - Dependency: Bump github.com/onsi/ginkgo/v2 from 2.9.7 to 2.10.0 (#8839) - Dependency: Bump github.com/onsi/gomega from 1.27.4 to 1.27.5 (#8390) +- Dependency: Bump github.com/onsi/gomega from 1.27.5 to 1.27.6 (#8460) - Dependency: Bump github.com/onsi/gomega from 1.27.6 to 1.27.7 (#8715) - Dependency: Bump github.com/onsi/gomega from 1.27.7 to 1.27.8 (#8841) - Dependency: Bump github.com/prometheus/client_golang from 1.14.0 to 1.15.0 (#8541) @@ -293,7 +295,7 @@ REPLACE ME: A couple sentences describing the deprecation, including links to do - util: Move `internal.labels` to `format` package for use by providers (#9006) - util: Rename internal/util/ssa util functions for better naming consistency (#8423) -:book: Additionally, there have been 72 contributions to our documentation and book. (#8252, #8279, #8284, #8288, #8293, #8307, #8308, #8309, #8319, #8327, #8351, #8355, #8363, #8375, #8383, #8397, #8416, #8419, #8439, #8446, #8447, #8454, #8508, #8509, #8510, #8511, #8520, #8521, #8552, #8554, #8559, #8580, #8587, #8593, #8596, #8597, #8612, #8613, #8630, #8632, #8651, #8661, #8673, #8686, #8699, #8701, #8712, #8719, #8729, #8740, #8753, #8760, #8762, #8763, #8775, #8779, #8781, #8782, #8787, #8798, #8802, #8805, #8812, #8843, #8854, #8901, #8924, #8932, #8955, #8956, #8958, #8960) +:book: Additionally, there have been 74 contributions to our documentation and book. (#8252, #8279, #8284, #8288, #8293, #8307, #8308, #8309, #8319, #8327, #8351, #8355, #8363, #8375, #8383, #8397, #8416, #8419, #8439, #8446, #8447, #8454, #8508, #8509, #8510, #8511, #8520, #8521, #8544, #8552, #8554, #8559, #8580, #8587, #8593, #8596, #8597, #8612, #8613, #8630, #8632, #8651, #8661, #8673, #8686, #8699, #8701, #8712, #8719, #8729, #8740, #8753, #8760, #8762, #8763, #8775, #8779, #8781, #8782, #8787, #8798, #8802, #8805, #8812, #8843, #8854, #8901, #8924, #8932, #8955, #8956, #8958, #8960, #8980) _Thanks to all our contributors!_ 😊