From 9a1b9f1a24ed1336fafd90b077ba32f11266f29a Mon Sep 17 00:00:00 2001 From: Shawn Hsu Date: Thu, 10 Sep 2026 18:21:35 +0800 Subject: [PATCH 1/4] feat(keycloak): add Helper.GetUsersByClientRoleName for bulk role lookups HasClientRole only checks one user at a time -- a consumer that needs to know which users out of a whole list hold a given Client Role would have to call it once per user. Wraps gocloak's existing GetUsersByClientRoleName instead, so the check becomes one call for the whole role membership. Paginates via the same First/Max loop as paginateGroups/ paginateGroupMembers, reusing groupsPageSize -- Keycloak's list endpoints silently truncate to a server-side default page size when Max is unset, same footgun those two already guard against. Signed-off-by: Shawn Hsu Co-Authored-By: Claude Sonnet 5 --- pkg/keycloak/keycloak.go | 34 ++++++++++ pkg/keycloak/keycloak_test.go | 108 +++++++++++++++++++++++++++++++ pkg/keycloak/mock_Client_test.go | 92 ++++++++++++++++++++++++++ 3 files changed, 234 insertions(+) diff --git a/pkg/keycloak/keycloak.go b/pkg/keycloak/keycloak.go index 7a5b923..ce049c8 100644 --- a/pkg/keycloak/keycloak.go +++ b/pkg/keycloak/keycloak.go @@ -56,6 +56,7 @@ type Client interface { AddUserToGroup(ctx context.Context, token string, realm string, userID string, groupID string) error DeleteUserFromGroup(ctx context.Context, token string, realm string, userID string, groupID string) error GetClientRolesByUserID(ctx context.Context, token string, realm string, idOfClient string, userID string) ([]*gocloak.Role, error) + GetUsersByClientRoleName(ctx context.Context, token string, realm string, idOfClient string, roleName string, params gocloak.GetUsersByRoleParams) ([]*gocloak.User, error) GetRealmRole(ctx context.Context, token string, realm string, roleName string) (*gocloak.Role, error) AddRealmRoleToUser(ctx context.Context, token string, realm string, userID string, roles []gocloak.Role) error LogoutUserSession(context.Context, string, string, string) error @@ -567,3 +568,36 @@ func (h *Helper) HasClientRole(realm, clientID, userID, roleName string) (bool, return false, nil } + +func paginateClientRoleUsers(fetch func(gocloak.GetUsersByRoleParams) ([]*gocloak.User, error), params gocloak.GetUsersByRoleParams) ([]*gocloak.User, error) { + if params.Max != nil { + return fetch(params) + } + + var all []*gocloak.User + first := 0 + for { + page := params + page.First = gocloak.IntP(first) + page.Max = gocloak.IntP(groupsPageSize) + + got, err := fetch(page) + if err != nil { + return nil, err + } + + all = append(all, got...) + if len(got) < groupsPageSize { + return all, nil + } + first += groupsPageSize + } +} + +func (h *Helper) GetUsersByClientRoleName(realm, clientID, roleName string) ([]*gocloak.User, error) { + return paginateClientRoleUsers(func(p gocloak.GetUsersByRoleParams) ([]*gocloak.User, error) { + ctx, cancel := context.WithTimeout(wait.CtxSeconds(10)) + defer cancel() + return h.Client.GetUsersByClientRoleName(ctx, h.Token, realm, clientID, roleName, p) + }, gocloak.GetUsersByRoleParams{}) +} diff --git a/pkg/keycloak/keycloak_test.go b/pkg/keycloak/keycloak_test.go index 4cac9a0..e044a94 100644 --- a/pkg/keycloak/keycloak_test.go +++ b/pkg/keycloak/keycloak_test.go @@ -582,6 +582,114 @@ func TestHasClientRole(t *testing.T) { } } +func TestGetUsersByClientRoleName(t *testing.T) { + errBoom := errors.New("boom") + + tests := []struct { + name string + users []*gocloak.User + fetchError error + expectedError error + }{ + { + name: "Should propagate an error fetching the role's users", + fetchError: errBoom, + expectedError: errBoom, + }, + { + name: "Should return every user holding the role", + users: []*gocloak.User{ + {Username: strPtr("alice")}, + {Username: strPtr("bob")}, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + client := NewMockClient(t) + client.On("GetUsersByClientRoleName", mock.Anything, mock.Anything, "master", "client-id", "super-admin", mock.Anything). + Return(tc.users, tc.fetchError) + + h := &Helper{Client: client} + got, err := h.GetUsersByClientRoleName("master", "client-id", "super-admin") + + require.ErrorIs(t, err, tc.expectedError) + require.Equal(t, tc.users, got) + }) + } +} + +func TestPaginateClientRoleUsers(t *testing.T) { + errBoom := errors.New("boom") + + tests := []struct { + name string + params gocloak.GetUsersByRoleParams + pages [][]*gocloak.User + pageError error + expectedCalls int + expectedFirsts []int + expectedLen int + expectedError error + }{ + { + name: "Should fetch a single page as-is when the caller already set Max", + params: gocloak.GetUsersByRoleParams{Max: gocloak.IntP(10)}, + pages: [][]*gocloak.User{{{Username: strPtr("alice")}}}, + pageError: nil, + expectedCalls: 1, + expectedFirsts: nil, + expectedLen: 1, + expectedError: nil, + }, + { + name: "Should keep paging while pages come back full, and stop on a short page", + params: gocloak.GetUsersByRoleParams{}, + pages: [][]*gocloak.User{make([]*gocloak.User, groupsPageSize), {{Username: strPtr("last")}}}, + pageError: nil, + expectedCalls: 2, + expectedFirsts: []int{0, groupsPageSize}, + expectedLen: groupsPageSize + 1, + expectedError: nil, + }, + { + name: "Should propagate an error from any page", + params: gocloak.GetUsersByRoleParams{}, + pages: nil, + pageError: errBoom, + expectedCalls: 1, + expectedFirsts: []int{0}, + expectedLen: 0, + expectedError: errBoom, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + calls := 0 + var seenFirsts []int + fetch := func(p gocloak.GetUsersByRoleParams) ([]*gocloak.User, error) { + if p.First != nil { + seenFirsts = append(seenFirsts, *p.First) + } + calls++ + if tc.pageError != nil { + return nil, tc.pageError + } + return tc.pages[calls-1], nil + } + + got, err := paginateClientRoleUsers(fetch, tc.params) + + require.ErrorIs(t, err, tc.expectedError) + require.Equal(t, tc.expectedCalls, calls) + require.Equal(t, tc.expectedFirsts, seenFirsts) + require.Len(t, got, tc.expectedLen) + }) + } +} + func TestGetUser(t *testing.T) { errBoom := errors.New("boom") diff --git a/pkg/keycloak/mock_Client_test.go b/pkg/keycloak/mock_Client_test.go index d129806..ae7e843 100644 --- a/pkg/keycloak/mock_Client_test.go +++ b/pkg/keycloak/mock_Client_test.go @@ -1870,6 +1870,98 @@ func (_c *MockClient_GetUsers_Call) RunAndReturn(run func(context1 context.Conte return _c } +// GetUsersByClientRoleName provides a mock function for the type MockClient +func (_mock *MockClient) GetUsersByClientRoleName(ctx context.Context, token string, realm string, idOfClient string, roleName string, params gocloak.GetUsersByRoleParams) ([]*gocloak.User, error) { + ret := _mock.Called(ctx, token, realm, idOfClient, roleName, params) + + if len(ret) == 0 { + panic("no return value specified for GetUsersByClientRoleName") + } + + var r0 []*gocloak.User + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, string, string, gocloak.GetUsersByRoleParams) ([]*gocloak.User, error)); ok { + return returnFunc(ctx, token, realm, idOfClient, roleName, params) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, string, string, gocloak.GetUsersByRoleParams) []*gocloak.User); ok { + r0 = returnFunc(ctx, token, realm, idOfClient, roleName, params) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*gocloak.User) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, string, string, string, string, gocloak.GetUsersByRoleParams) error); ok { + r1 = returnFunc(ctx, token, realm, idOfClient, roleName, params) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_GetUsersByClientRoleName_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetUsersByClientRoleName' +type MockClient_GetUsersByClientRoleName_Call struct { + *mock.Call +} + +// GetUsersByClientRoleName is a helper method to define mock.On call +// - ctx context.Context +// - token string +// - realm string +// - idOfClient string +// - roleName string +// - params gocloak.GetUsersByRoleParams +func (_e *MockClient_Expecter) GetUsersByClientRoleName(ctx any, token any, realm any, idOfClient any, roleName any, params any) *MockClient_GetUsersByClientRoleName_Call { + return &MockClient_GetUsersByClientRoleName_Call{Call: _e.mock.On("GetUsersByClientRoleName", ctx, token, realm, idOfClient, roleName, params)} +} + +func (_c *MockClient_GetUsersByClientRoleName_Call) Run(run func(ctx context.Context, token string, realm string, idOfClient string, roleName string, params gocloak.GetUsersByRoleParams)) *MockClient_GetUsersByClientRoleName_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + var arg2 string + if args[2] != nil { + arg2 = args[2].(string) + } + var arg3 string + if args[3] != nil { + arg3 = args[3].(string) + } + var arg4 string + if args[4] != nil { + arg4 = args[4].(string) + } + var arg5 gocloak.GetUsersByRoleParams + if args[5] != nil { + arg5 = args[5].(gocloak.GetUsersByRoleParams) + } + run( + arg0, + arg1, + arg2, + arg3, + arg4, + arg5, + ) + }) + return _c +} + +func (_c *MockClient_GetUsersByClientRoleName_Call) Return(users []*gocloak.User, err error) *MockClient_GetUsersByClientRoleName_Call { + _c.Call.Return(users, err) + return _c +} + +func (_c *MockClient_GetUsersByClientRoleName_Call) RunAndReturn(run func(ctx context.Context, token string, realm string, idOfClient string, roleName string, params gocloak.GetUsersByRoleParams) ([]*gocloak.User, error)) *MockClient_GetUsersByClientRoleName_Call { + _c.Call.Return(run) + return _c +} + // Login provides a mock function for the type MockClient func (_mock *MockClient) Login(context1 context.Context, s string, s1 string, s2 string, s3 string, s4 string) (*gocloak.JWT, error) { ret := _mock.Called(context1, s, s1, s2, s3, s4) From 47d2a19f578237b4b332024a15a72acf6194fe51 Mon Sep 17 00:00:00 2001 From: Shawn Hsu Date: Thu, 10 Sep 2026 18:21:42 +0800 Subject: [PATCH 2/4] fix(ci): derive the unit-test job's Go version from go.mod go.mod was bumped to require go 1.26.0 (2026-09-07's minor-and-patch dependency update), but ci.yaml still hard-pinned actions/setup-go to 1.25 -- GOTOOLCHAIN=local means it never auto-upgrades, so the mockery step has failed with "go.mod requires go >= 1.26.0" on every PR since, this one included. Switching to go-version-file avoids the same drift happening again next time go.mod's Go version moves. Signed-off-by: Shawn Hsu Co-Authored-By: Claude Sonnet 5 --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 451112e..f1d392c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -16,7 +16,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v7 with: - go-version: '1.25' + go-version-file: 'go.mod' - name: Setup mockery uses: brokeyourbike/go-mockery-action@v0 From a35be1429fefa1551c094d7a971c276dff2c6c02 Mon Sep 17 00:00:00 2001 From: Shawn Hsu Date: Thu, 10 Sep 2026 17:56:43 +0800 Subject: [PATCH 3/4] feat(keycloak): add Helper.GetGroupByPath cubecmp needs to resolve a Group's ID from its full path (e.g. /cubecmp/{project}/admin) before it can list that group's members, without walking/creating the path like GetOrCreateGroupPath does. Signed-off-by: Shawn Hsu Co-Authored-By: Claude Sonnet 5 --- pkg/keycloak/keycloak.go | 7 +++ pkg/keycloak/keycloak_test.go | 35 ++++++++++++++ pkg/keycloak/mock_Client_test.go | 80 ++++++++++++++++++++++++++++++++ 3 files changed, 122 insertions(+) diff --git a/pkg/keycloak/keycloak.go b/pkg/keycloak/keycloak.go index ce049c8..aba7e8f 100644 --- a/pkg/keycloak/keycloak.go +++ b/pkg/keycloak/keycloak.go @@ -49,6 +49,7 @@ type Client interface { */ GetGroups(ctx context.Context, token string, realm string, params gocloak.GetGroupsParams) ([]*gocloak.Group, error) GetGroup(ctx context.Context, token string, realm string, groupID string) (*gocloak.Group, error) + GetGroupByPath(ctx context.Context, token string, realm string, groupPath string) (*gocloak.Group, error) CreateGroup(ctx context.Context, token string, realm string, group gocloak.Group) (string, error) CreateChildGroup(ctx context.Context, token string, realm string, groupID string, group gocloak.Group) (string, error) GetUserGroups(ctx context.Context, token string, realm string, userID string, params gocloak.GetGroupsParams) ([]*gocloak.Group, error) @@ -414,6 +415,12 @@ func (h *Helper) GetGroups(realm string, params gocloak.GetGroupsParams) ([]*goc }, params) } +func (h *Helper) GetGroupByPath(realm, path string) (*gocloak.Group, error) { + ctx, cancel := context.WithTimeout(wait.CtxSeconds(10)) + defer cancel() + return h.Client.GetGroupByPath(ctx, h.Token, realm, path) +} + func (h *Helper) GetUserGroups(realm, userID string) ([]*gocloak.Group, error) { return paginateGroups(func(p gocloak.GetGroupsParams) ([]*gocloak.Group, error) { ctx, cancel := context.WithTimeout(wait.CtxSeconds(10)) diff --git a/pkg/keycloak/keycloak_test.go b/pkg/keycloak/keycloak_test.go index e044a94..fb14ac7 100644 --- a/pkg/keycloak/keycloak_test.go +++ b/pkg/keycloak/keycloak_test.go @@ -343,6 +343,41 @@ func TestPaginateGroups(t *testing.T) { }) } +func TestHelperGetGroupByPath(t *testing.T) { + errBoom := errors.New("boom") + + tests := []struct { + name string + group *gocloak.Group + fetchError error + expectedError error + }{ + { + name: "Should propagate an error fetching the group by path", + fetchError: errBoom, + expectedError: errBoom, + }, + { + name: "Should return the group at the given path", + group: groupAt("g1", "admin", "/cubecmp/PROJ001/admin"), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + client := NewMockClient(t) + client.On("GetGroupByPath", mock.Anything, mock.Anything, "master", "/cubecmp/PROJ001/admin"). + Return(tc.group, tc.fetchError) + + h := &Helper{Client: client} + got, err := h.GetGroupByPath("master", "/cubecmp/PROJ001/admin") + + require.ErrorIs(t, err, tc.expectedError) + require.Equal(t, tc.group, got) + }) + } +} + func TestHelperGetUserGroups(t *testing.T) { errBoom := errors.New("boom") diff --git a/pkg/keycloak/mock_Client_test.go b/pkg/keycloak/mock_Client_test.go index ae7e843..ef92627 100644 --- a/pkg/keycloak/mock_Client_test.go +++ b/pkg/keycloak/mock_Client_test.go @@ -1458,6 +1458,86 @@ func (_c *MockClient_GetGroup_Call) RunAndReturn(run func(ctx context.Context, t return _c } +// GetGroupByPath provides a mock function for the type MockClient +func (_mock *MockClient) GetGroupByPath(ctx context.Context, token string, realm string, groupPath string) (*gocloak.Group, error) { + ret := _mock.Called(ctx, token, realm, groupPath) + + if len(ret) == 0 { + panic("no return value specified for GetGroupByPath") + } + + var r0 *gocloak.Group + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, string) (*gocloak.Group, error)); ok { + return returnFunc(ctx, token, realm, groupPath) + } + if returnFunc, ok := ret.Get(0).(func(context.Context, string, string, string) *gocloak.Group); ok { + r0 = returnFunc(ctx, token, realm, groupPath) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*gocloak.Group) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context, string, string, string) error); ok { + r1 = returnFunc(ctx, token, realm, groupPath) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockClient_GetGroupByPath_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetGroupByPath' +type MockClient_GetGroupByPath_Call struct { + *mock.Call +} + +// GetGroupByPath is a helper method to define mock.On call +// - ctx context.Context +// - token string +// - realm string +// - groupPath string +func (_e *MockClient_Expecter) GetGroupByPath(ctx any, token any, realm any, groupPath any) *MockClient_GetGroupByPath_Call { + return &MockClient_GetGroupByPath_Call{Call: _e.mock.On("GetGroupByPath", ctx, token, realm, groupPath)} +} + +func (_c *MockClient_GetGroupByPath_Call) Run(run func(ctx context.Context, token string, realm string, groupPath string)) *MockClient_GetGroupByPath_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + var arg1 string + if args[1] != nil { + arg1 = args[1].(string) + } + var arg2 string + if args[2] != nil { + arg2 = args[2].(string) + } + var arg3 string + if args[3] != nil { + arg3 = args[3].(string) + } + run( + arg0, + arg1, + arg2, + arg3, + ) + }) + return _c +} + +func (_c *MockClient_GetGroupByPath_Call) Return(group *gocloak.Group, err error) *MockClient_GetGroupByPath_Call { + _c.Call.Return(group, err) + return _c +} + +func (_c *MockClient_GetGroupByPath_Call) RunAndReturn(run func(ctx context.Context, token string, realm string, groupPath string) (*gocloak.Group, error)) *MockClient_GetGroupByPath_Call { + _c.Call.Return(run) + return _c +} + // GetGroupMembers provides a mock function for the type MockClient func (_mock *MockClient) GetGroupMembers(ctx context.Context, token string, realm string, groupID string, params gocloak.GetGroupsParams) ([]*gocloak.User, error) { ret := _mock.Called(ctx, token, realm, groupID, params) From 117364b166d1ba6fb60a3d2a7cc709f47a001c70 Mon Sep 17 00:00:00 2001 From: Shawn Hsu Date: Thu, 10 Sep 2026 18:10:57 +0800 Subject: [PATCH 4/4] fix(deps): pin go-micro.dev/v5 back to v5.3.0 The prior dependabot bump (PR #90) moved go-micro.dev/v5 from v5.3.0 to v5.30.0 as part of an unrelated grouped minor/patch update, even though our own use of it (via micro/plugins/v5/logger/zap) only ever needed v5.2.0+. v5.30.0 removed the util/addr package that consumers still on github.com/micro/plugins/v5/server/http@v1.0.2 (the latest available version of that plugin) need to compile -- there is no newer plugin release that drops the dependency. Any consumer bumping past the v5.30.0 commit breaks as a result. Signed-off-by: Shawn Hsu Co-Authored-By: Claude Sonnet 5 --- go.mod | 14 +++++++++++++- go.sum | 22 ++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 5d23bd5..36e4759 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/slack-go/slack v0.29.0 github.com/stretchr/testify v1.12.1 github.com/wneessen/go-mail v0.8.1 - go-micro.dev/v5 v5.30.0 + go-micro.dev/v5 v5.3.0 go.mongodb.org/mongo-driver v1.17.9 go.uber.org/zap v1.28.0 golang.org/x/crypto v0.55.0 @@ -64,15 +64,18 @@ require ( github.com/aws/aws-sdk-go-v2/service/ssooidc v1.41.0 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.48.0 // indirect github.com/aws/smithy-go v1.28.1 // indirect + github.com/bitly/go-simplejson v0.5.0 // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect github.com/cloudflare/circl v1.6.3 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect github.com/cyphar/filepath-securejoin v0.7.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emicklei/go-restful/v3 v3.13.0 // indirect github.com/evanphx/json-patch v5.9.11+incompatible // indirect github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect github.com/fatih/color v1.16.0 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/go-errors/errors v1.4.2 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect @@ -89,6 +92,7 @@ require ( github.com/go-openapi/validate v0.20.3 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/golang-jwt/jwt/v5 v5.3.0 // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/btree v1.1.3 // indirect github.com/google/gnostic-models v0.7.0 // indirect @@ -99,6 +103,7 @@ require ( github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-version v1.9.0 // indirect github.com/huandu/xstrings v1.5.0 // indirect + github.com/imdario/mergo v0.3.13 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 // indirect github.com/jmoiron/sqlx v1.4.0 // indirect @@ -114,6 +119,7 @@ require ( github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.9 // indirect + github.com/miekg/dns v1.1.57 // 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 @@ -129,6 +135,8 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect + github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect + github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/rubenv/sql-migrate v1.8.1 // indirect @@ -140,16 +148,19 @@ require ( github.com/spf13/cobra v1.10.2 // indirect github.com/spf13/pflag v1.0.10 // indirect github.com/stretchr/objx v0.5.3 // indirect + github.com/urfave/cli/v2 v2.25.7 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect github.com/xdg-go/scram v1.1.2 // indirect github.com/xdg-go/stringprep v1.0.4 // indirect github.com/xlab/treeprint v1.2.0 // indirect + github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect github.com/zclconf/go-cty v1.18.1 // indirect go.uber.org/multierr v1.11.0 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/mod v0.38.0 // indirect golang.org/x/net v0.57.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect golang.org/x/sync v0.22.0 // indirect @@ -157,6 +168,7 @@ require ( golang.org/x/term v0.45.0 // indirect golang.org/x/text v0.41.0 // indirect golang.org/x/time v0.14.0 // indirect + golang.org/x/tools v0.48.0 // indirect google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/go.sum b/go.sum index ce18c2e..45d4715 100644 --- a/go.sum +++ b/go.sum @@ -85,6 +85,8 @@ github.com/aws/smithy-go v1.28.1 h1:R/nXH00c8qcfCzQVELtRw+eLQWtzv+VAIEFJ1/xxXlQ= github.com/aws/smithy-go v1.28.1/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y= +github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= @@ -100,6 +102,7 @@ github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= github.com/coreos/go-systemd/v22 v22.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA= github.com/coreos/go-systemd/v22 v22.7.0/go.mod h1:xNUYtjHu2EDXbsxz1i41wouACIwT7Ybq9o0BQhMwD0w= +github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= @@ -142,6 +145,8 @@ github.com/foxcpp/go-mockdns v1.2.0 h1:omK3OrHRD1IWJz1FuFBCFquhXslXoF17OvBS6JPzZ github.com/foxcpp/go-mockdns v1.2.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= @@ -264,6 +269,7 @@ github.com/go-openapi/validate v0.20.3/go.mod h1:goDdqVGiigM3jChcrYJxD2joalke3ZX github.com/go-resty/resty/v2 v2.17.2 h1:FQW5oHYcIlkCNrMD2lloGScxcHJ0gkjshV3qcQAyHQk= github.com/go-resty/resty/v2 v2.17.2/go.mod h1:kCKZ3wWmwJaNc7S29BRtUhJwy7iqmn+2mLtQrOyQlVA= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-sql-driver/mysql v1.9.2 h1:4cNKDYQ1I84SXslGddlsrMhc8k4LeDVj6Ad6WRjiHuU= github.com/go-sql-driver/mysql v1.9.2/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= @@ -304,6 +310,8 @@ github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9v github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -361,6 +369,8 @@ github.com/hashicorp/terraform-json v0.28.0 h1:dOkJT55rWfU6T1/VklHde51ym4LfNP+9x github.com/hashicorp/terraform-json v0.28.0/go.mod h1:PJIRf+Yzu5iLb52c/xYp1tUOL4jzMzfIAB5gvWWKIWE= github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +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.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= @@ -431,6 +441,7 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mattn/go-sqlite3 v1.14.34 h1:3NtcvcUnFBPsuRcno8pUtupspG/GM+9nZ88zgJcp6Zk= github.com/mattn/go-sqlite3 v1.14.34/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= @@ -481,6 +492,10 @@ github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJw github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= +github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw= +github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0= +github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= @@ -567,6 +582,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs= +github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= github.com/wneessen/go-mail v0.8.1 h1:tVcncj02/QySVFw3zr/kXOzZcuFQqBNT6K+Rbgm/pcM= github.com/wneessen/go-mail v0.8.1/go.mod h1:dWZ61zadzCIyvB4y1/YzC5O7MrbbzBfPkARmbosdf8w= @@ -586,12 +603,16 @@ github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhe github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zclconf/go-cty v1.18.1 h1:yEGE8M4iIZlyKQURZNb2SnEyZlZHUcBCnx6KF81KuwM= github.com/zclconf/go-cty v1.18.1/go.mod h1:qpnV6EDNgC1sns/AleL1fvatHw72j+S+nS+MJ+T2CSg= +go-micro.dev/v5 v5.3.0 h1:XjFydJiQHU14zEA/JAXXR7aJVbwIwBjUajLrqi0wrJI= +go-micro.dev/v5 v5.3.0/go.mod h1:QMSZE1zIaB1TY0JI7AsePls8+slRzqWz4MKyoQMNNcM= go-micro.dev/v5 v5.30.0 h1:OHvY8LV39Pk/pWUhxihCa26q25SpDDnfwU3n6z9R2Vs= go-micro.dev/v5 v5.30.0/go.mod h1:eWTF7KQRYXmjm9s5p4Pzrk5CObHQKlh6B/u573gd/Qo= go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= @@ -793,6 +814,7 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= helm.sh/helm/v3 v3.21.4 h1:T/GcIEXU/gNjJnkITlIZ3e9xqkZjhFTmISuStTZ6+Qg=