diff --git a/cmd/user-identity-mapper/Dockerfile b/cmd/user-identity-mapper/Dockerfile new file mode 100644 index 0000000..dde315d --- /dev/null +++ b/cmd/user-identity-mapper/Dockerfile @@ -0,0 +1,32 @@ +################################################################################################ +# Builder image +# See https://hub.docker.com/_/golang/ +################################################################################################ +FROM golang:1.20 as builder + +ARG OS=linux +ARG ARCH=amd64 + +WORKDIR /usr/src/app + +# pre-copy/cache parent go.mod for pre-downloading dependencies and only redownloading them in subsequent builds if they change +COPY go.mod go.sum ./ +RUN go mod download && go mod verify + +COPY pkg ./pkg +COPY cmd/user-identity-mapper ./cmd/user-identity-mapper + +RUN go build -v -o user-identity-mapper cmd/user-identity-mapper/*.go + +################################################################################################ +# user-identity-mapper image to be run by the job on OpenShift +################################################################################################ +FROM registry.access.redhat.com/ubi9/ubi-minimal:latest as user-identity-mapper + +# Copy the generated binary into the $PATH so it can be invoked +COPY --from=builder /usr/src/app/user-identity-mapper /usr/local/bin/ + +# Run as non-root user +USER 1001 + +CMD ["/usr/local/bin/user-identity-mapper"] \ No newline at end of file diff --git a/cmd/user-identity-mapper/main.go b/cmd/user-identity-mapper/main.go new file mode 100644 index 0000000..95ccaf0 --- /dev/null +++ b/cmd/user-identity-mapper/main.go @@ -0,0 +1,62 @@ +// 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" + "os" + + "github.com/charmbracelet/log" + userv1 "github.com/openshift/api/user/v1" + "github.com/spf13/cobra" + "k8s.io/apimachinery/pkg/runtime" + runtimeclient "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/config" +) + +func main() { + // cmd the command that maps an identity to its parent user + cmd := &cobra.Command{ + Use: "user-identity-mapper", + RunE: func(cmd *cobra.Command, args []string) error { + + logger := log.New(cmd.OutOrStderr()) + // Get a config to talk to the apiserver + cfg, err := config.GetConfig() + if err != nil { + logger.Error("unable to load config", "error", err) + os.Exit(1) + } + + // create client that will be used for retrieving the host operator secret & ToolchainCluster CRs + scheme := runtime.NewScheme() + if err := userv1.Install(scheme); err != nil { + logger.Error("unable to install scheme", "error", err) + os.Exit(1) + } + cl, err := runtimeclient.New(cfg, runtimeclient.Options{ + Scheme: scheme, + }) + if err != nil { + logger.Error("unable to create a client", "error", err) + os.Exit(1) + } + return CreateUserIdentityMappings(cmd.Context(), logger, cl) + }, + } + + if err := cmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(1) + } +} diff --git a/cmd/user-identity-mapper/user_identity_mapper.go b/cmd/user-identity-mapper/user_identity_mapper.go new file mode 100644 index 0000000..51b779a --- /dev/null +++ b/cmd/user-identity-mapper/user_identity_mapper.go @@ -0,0 +1,54 @@ +package main + +import ( + "context" + "fmt" + + "github.com/charmbracelet/log" + userv1 "github.com/openshift/api/user/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtimeclient "sigs.k8s.io/controller-runtime/pkg/client" +) + +func CreateUserIdentityMappings(ctx context.Context, logger *log.Logger, cl runtimeclient.Client) error { + logger.Info("listing users...") + users := &userv1.UserList{} + if err := cl.List(ctx, users, runtimeclient.MatchingLabels{ + "provider": "sandbox-sre", + }); err != nil { + return fmt.Errorf("unable to list users: %w", err) + } + for _, user := range users.Items { + logger.Info("listing identities", "username", user.Name) + identities := userv1.IdentityList{} + if err := cl.List(ctx, &identities, runtimeclient.MatchingLabels{ + "provider": "sandbox-sre", + "username": user.Name, + }); err != nil { + return fmt.Errorf("unable to list identities: %w", err) + } + if len(identities.Items) == 0 { + logger.Errorf("no identity associated with user %q", user.Name) + continue + } + for _, identity := range identities.Items { + logger.Info("creating/updating identity mapping", "user", user.Name, "identity", identity.Name) + if err := cl.Create(ctx, &userv1.UserIdentityMapping{ + ObjectMeta: metav1.ObjectMeta{ + Name: identity.Name, + }, + User: corev1.ObjectReference{ + Name: user.Name, + }, + Identity: corev1.ObjectReference{ + Name: identity.Name, + }, + }); err != nil && !errors.IsAlreadyExists(err) { + return fmt.Errorf("unable to create identity mapping for username %q and identity %q: %w", user.Name, identity.Name, err) + } + } + } + return nil +} diff --git a/cmd/user-identity-mapper/user_identity_mapper_test.go b/cmd/user-identity-mapper/user_identity_mapper_test.go new file mode 100644 index 0000000..6705b26 --- /dev/null +++ b/cmd/user-identity-mapper/user_identity_mapper_test.go @@ -0,0 +1,189 @@ +package main_test + +import ( + "bytes" + "context" + "fmt" + "testing" + + "github.com/codeready-toolchain/toolchain-common/pkg/test" + useridentitymapper "github.com/kubesaw/ksctl/cmd/user-identity-mapper" + + "github.com/charmbracelet/log" + userv1 "github.com/openshift/api/user/v1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes/scheme" + runtimeclient "sigs.k8s.io/controller-runtime/pkg/client" +) + +func TestUserIdentityMapper(t *testing.T) { + + // given + s := scheme.Scheme + err := userv1.Install(s) + require.NoError(t, err) + user1 := &userv1.User{ + ObjectMeta: metav1.ObjectMeta{ + Name: "user1", + Labels: map[string]string{ + "provider": "sandbox-sre", + }, + }, + } + identity1 := &userv1.Identity{ + ObjectMeta: metav1.ObjectMeta{ + Name: "identity1", + Labels: map[string]string{ + "provider": "sandbox-sre", + "username": "user1", + }, + }, + } + user2 := &userv1.User{ + ObjectMeta: metav1.ObjectMeta{ + Name: "user2", + Labels: map[string]string{ + "provider": "sandbox-sre", + }, + }, + } + identity2 := &userv1.Identity{ + ObjectMeta: metav1.ObjectMeta{ + Name: "identity2", + Labels: map[string]string{ + "provider": "sandbox-sre", + "username": "user2", + }, + }, + } + user3 := &userv1.User{ + ObjectMeta: metav1.ObjectMeta{ + Name: "user3", + // not managed by sandbox-sre + }, + } + identity3 := &userv1.Identity{ + ObjectMeta: metav1.ObjectMeta{ + Name: "identity3", + Labels: map[string]string{ + "provider": "sandbox-sre", + "username": "user3", + }, + }, + } + + t.Run("success", func(t *testing.T) { + // given + out := new(bytes.Buffer) + logger := log.New(out) + cl := test.NewFakeClient(t, user1, identity1, user2, identity2, user3, identity3) + + // when + err := useridentitymapper.CreateUserIdentityMappings(context.TODO(), logger, cl) + + // then + require.NoError(t, err) + assert.NotContains(t, out.String(), "unable to list identities") + uim := &userv1.UserIdentityMapping{} + // `user1` and `user2` are not managed by sandbox (ie, labelled with `provider: sandbox-sre`), hence the `UserIdentityMappings` exist + require.NoError(t, cl.Get(context.TODO(), types.NamespacedName{Name: identity1.Name}, uim)) + assert.Equal(t, identity1.Name, uim.Identity.Name) + assert.Equal(t, user1.Name, uim.User.Name) + require.NoError(t, cl.Get(context.TODO(), types.NamespacedName{Name: identity2.Name}, uim)) + assert.Equal(t, identity2.Name, uim.Identity.Name) + assert.Equal(t, user2.Name, uim.User.Name) + }) + + t.Run("failures", func(t *testing.T) { + + t.Run("user and identities not labelled", func(t *testing.T) { + // given + out := new(bytes.Buffer) + logger := log.New(out) + cl := test.NewFakeClient(t, user3, identity3) + + // when + err := useridentitymapper.CreateUserIdentityMappings(context.TODO(), logger, cl) + + // then + require.NoError(t, err) + assert.NotContains(t, out.String(), "unable to list identities") + // `user3` is not managed by sandbox (ie, not labelled with `provider: sandbox-sre`), , hence the `UserIdentityMappings` does not exist + require.EqualError(t, cl.Get(context.TODO(), types.NamespacedName{Name: identity3.Name}, &userv1.UserIdentityMapping{}), `useridentitymappings.user.openshift.io "identity3" not found`) + }) + + t.Run("missing identity", func(t *testing.T) { + // given + out := new(bytes.Buffer) + logger := log.New(out) + cl := test.NewFakeClient(t, user1) + + // when + err := useridentitymapper.CreateUserIdentityMappings(context.TODO(), logger, cl) + + // then + require.NoError(t, err) + assert.Contains(t, out.String(), `no identity associated with user "user1"`) + require.EqualError(t, cl.Get(context.TODO(), types.NamespacedName{Name: identity1.Name}, &userv1.UserIdentityMapping{}), `useridentitymappings.user.openshift.io "identity1" not found`) + }) + + t.Run("cannot list users", func(t *testing.T) { + // given + out := new(bytes.Buffer) + logger := log.New(out) + cl := test.NewFakeClient(t, user1, identity1) + cl.MockList = func(ctx context.Context, list runtimeclient.ObjectList, opts ...runtimeclient.ListOption) error { + if _, ok := list.(*userv1.UserList); ok { + return fmt.Errorf("mock error") + } + return cl.Client.List(ctx, list, opts...) + } + // when + err := useridentitymapper.CreateUserIdentityMappings(context.TODO(), logger, cl) + + // then + assert.EqualError(t, err, "unable to list users: mock error") + }) + + t.Run("cannot list identities", func(t *testing.T) { + // given + out := new(bytes.Buffer) + logger := log.New(out) + cl := test.NewFakeClient(t, user1, identity1, user2, identity2) + cl.MockList = func(ctx context.Context, list runtimeclient.ObjectList, opts ...runtimeclient.ListOption) error { + if _, ok := list.(*userv1.IdentityList); ok { + return fmt.Errorf("mock error") + } + return cl.Client.List(ctx, list, opts...) + } + + // when + err := useridentitymapper.CreateUserIdentityMappings(context.TODO(), logger, cl) + + // then + assert.EqualError(t, err, "unable to list identities: mock error") + }) + + t.Run("cannot create user-identity mapping", func(t *testing.T) { + // given + out := new(bytes.Buffer) + logger := log.New(out) + cl := test.NewFakeClient(t, user1, identity1, user2, identity2) + cl.MockCreate = func(ctx context.Context, obj runtimeclient.Object, opts ...runtimeclient.CreateOption) error { + if _, ok := obj.(*userv1.UserIdentityMapping); ok { + return fmt.Errorf("mock error") + } + return cl.Client.Create(ctx, obj, opts...) + } + + // when + err := useridentitymapper.CreateUserIdentityMappings(context.TODO(), logger, cl) + + // then + assert.EqualError(t, err, `unable to create identity mapping for username "user1" and identity "identity1": mock error`) + }) + }) +} diff --git a/go.mod b/go.mod index 625ecea..99052a4 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/pkg/errors v0.9.1 github.com/satori/go.uuid v1.2.0 github.com/spf13/cobra v1.8.0 - github.com/stretchr/testify v1.8.4 + github.com/stretchr/testify v1.9.0 golang.org/x/term v0.15.0 gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 @@ -27,7 +27,10 @@ require ( sigs.k8s.io/kustomize/api v0.12.1 ) -require github.com/h2non/gock v1.2.0 +require ( + github.com/charmbracelet/log v0.4.0 + github.com/h2non/gock v1.2.0 +) require ( github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect @@ -39,10 +42,12 @@ require ( github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 // indirect github.com/PuerkitoBio/purell v1.1.1 // indirect github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect + github.com/charmbracelet/lipgloss v0.10.0 // indirect github.com/cloudflare/circl v1.3.7 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/emicklei/go-restful/v3 v3.8.0 // indirect @@ -53,6 +58,7 @@ require ( github.com/fsnotify/fsnotify v1.5.4 // indirect github.com/fvbommel/sortorder v1.0.1 // indirect github.com/go-errors/errors v1.0.1 // indirect + github.com/go-logfmt/logfmt v0.6.0 // indirect github.com/go-logr/logr v1.2.3 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonreference v0.19.5 // indirect @@ -78,7 +84,10 @@ require ( 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/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mailru/easyjson v0.7.6 // indirect + github.com/mattn/go-isatty v0.0.18 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect github.com/migueleliasweb/go-github-mock v0.0.18 // indirect github.com/mitchellh/copystructure v1.0.0 // indirect @@ -89,6 +98,8 @@ require ( 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/muesli/reflow v0.3.0 // indirect + github.com/muesli/termenv v0.15.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/openshift/library-go v0.0.0-20230301092340-c13b89190a26 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect @@ -98,6 +109,7 @@ require ( github.com/prometheus/common v0.32.1 // indirect github.com/prometheus/procfs v0.7.3 // indirect github.com/redhat-cop/operator-utils v1.3.3-0.20220121120056-862ef22b8cdf // indirect + github.com/rivo/uniseg v0.4.7 // indirect github.com/russross/blackfriday v1.5.2 // indirect github.com/shopspring/decimal v1.2.0 // indirect github.com/sirupsen/logrus v1.8.1 // indirect @@ -106,6 +118,7 @@ require ( github.com/xlab/treeprint v1.1.0 // indirect go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect golang.org/x/crypto v0.17.0 // indirect + golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect golang.org/x/net v0.17.0 // indirect golang.org/x/oauth2 v0.7.0 // indirect golang.org/x/sys v0.15.0 // indirect diff --git a/go.sum b/go.sum index 3cfe4de..6725f3a 100644 --- a/go.sum +++ b/go.sum @@ -87,6 +87,8 @@ github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmV github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -111,6 +113,10 @@ github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw= 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/charmbracelet/lipgloss v0.10.0 h1:KWeXFSexGcfahHX+54URiZGkBFazf70JNMtwg/AFW3s= +github.com/charmbracelet/lipgloss v0.10.0/go.mod h1:Wig9DSfvANsxqkRsqj6x87irdy123SR4dOXlKa91ciE= +github.com/charmbracelet/log v0.4.0 h1:G9bQAcx8rWA2T3pWvx7YtPTPwgqpk7D68BX21IRW8ZM= +github.com/charmbracelet/log v0.4.0/go.mod h1:63bXt/djrizTec0l11H20t8FDSvA4CRZJ1KH22MdptM= 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= @@ -212,6 +218,8 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= +github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= @@ -430,6 +438,8 @@ github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgx 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/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= @@ -449,8 +459,13 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= +github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= @@ -489,6 +504,10 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G 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/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= +github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= +github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= +github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 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= @@ -575,6 +594,10 @@ github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1 github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/redhat-cop/operator-utils v1.3.3-0.20220121120056-862ef22b8cdf h1:fsZiv9XuFo8G7IyzFWjG02vqzJG7kSqFvD1Wiq3V/o8= github.com/redhat-cop/operator-utils v1.3.3-0.20220121120056-862ef22b8cdf/go.mod h1:FfTyeSCu+e2VLgeMh/1RFG8TSkVjKRPEyR6EmDt0RIw= +github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -630,15 +653,15 @@ github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= -github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= @@ -728,6 +751,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=