Skip to content

Commit 61c2a8b

Browse files
committed
Implement capabilities list via reflection
1 parent 97865ae commit 61c2a8b

7 files changed

Lines changed: 217 additions & 79 deletions

File tree

internal/providers/dockerhub/metadata.go

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,20 @@ package dockerhub
55

66
import (
77
minderv1 "github.com/mindersec/minder/pkg/api/protobuf/go/minder/v1"
8+
provifv1 "github.com/mindersec/minder/pkg/providers/v1"
89
)
910

1011
const (
1112
providerDocsBaseURL = "https://docs.mindersec.dev"
1213
providerDocsURL = providerDocsBaseURL + "/understand/providers"
1314
)
1415

15-
// ClassInfo returns metadata for the Docker Hub provider class.
16-
func ClassInfo() *minderv1.ProviderClassInfo {
16+
func (d *dockerHubImageLister) ProviderClassInfo() *minderv1.ProviderClassInfo {
1717
return &minderv1.ProviderClassInfo{
18-
Class: DockerHub,
19-
DisplayName: "Docker Hub",
20-
Description: "Docker Hub registry provider for image and OCI interactions.",
21-
SupportedProviderTypes: []minderv1.ProviderType{
22-
minderv1.ProviderType_PROVIDER_TYPE_IMAGE_LISTER,
23-
minderv1.ProviderType_PROVIDER_TYPE_OCI,
24-
},
18+
Class: DockerHub,
19+
DisplayName: "Docker Hub",
20+
Description: "Docker Hub registry provider for image and OCI interactions.",
21+
SupportedProviderTypes: provifv1.ProviderTypesFromImpl(d),
2522
SupportedAuthFlows: []minderv1.AuthorizationFlow{
2623
minderv1.AuthorizationFlow_AUTHORIZATION_FLOW_USER_INPUT,
2724
},
@@ -30,6 +27,8 @@ func ClassInfo() *minderv1.ProviderClassInfo {
3027
}
3128
}
3229

33-
func (*dockerHubImageLister) ProviderClassInfo() *minderv1.ProviderClassInfo {
34-
return ClassInfo()
30+
// ClassInfo returns metadata for the Docker Hub provider class.
31+
// It uses a nil-pointer receiver to avoid needing a live client instance.
32+
func ClassInfo() *minderv1.ProviderClassInfo {
33+
return (*dockerHubImageLister)(nil).ProviderClassInfo()
3534
}

internal/providers/dockerhub/provider_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ package dockerhub
66
import (
77
"testing"
88

9+
"github.com/stretchr/testify/assert"
10+
"github.com/stretchr/testify/require"
11+
12+
minderv1 "github.com/mindersec/minder/pkg/api/protobuf/go/minder/v1"
913
testhelper "github.com/mindersec/minder/pkg/providers/v1/testing"
1014
)
1115

@@ -15,3 +19,26 @@ func TestRegistration(t *testing.T) {
1519
dh := &dockerHubImageLister{}
1620
testhelper.CheckRegistrationExcept(t, dh)
1721
}
22+
23+
func TestClassInfo(t *testing.T) {
24+
t.Parallel()
25+
26+
info := ClassInfo()
27+
require.NotNil(t, info)
28+
29+
assert.Equal(t, DockerHub, info.Class)
30+
assert.Equal(t, "Docker Hub", info.DisplayName)
31+
assert.NotEmpty(t, info.Description)
32+
assert.Equal(t, providerDocsURL, info.DocumentationUrl)
33+
34+
assert.ElementsMatch(t, []minderv1.AuthorizationFlow{
35+
minderv1.AuthorizationFlow_AUTHORIZATION_FLOW_USER_INPUT,
36+
}, info.SupportedAuthFlows)
37+
38+
assert.Empty(t, info.SupportedEntities)
39+
40+
assert.ElementsMatch(t, []minderv1.ProviderType{
41+
minderv1.ProviderType_PROVIDER_TYPE_IMAGE_LISTER,
42+
minderv1.ProviderType_PROVIDER_TYPE_OCI,
43+
}, info.SupportedProviderTypes)
44+
}

internal/providers/github/metadata.go

Lines changed: 26 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88

99
"github.com/mindersec/minder/internal/db"
1010
minderv1 "github.com/mindersec/minder/pkg/api/protobuf/go/minder/v1"
11+
provifv1 "github.com/mindersec/minder/pkg/providers/v1"
1112
)
1213

1314
const (
@@ -42,80 +43,49 @@ var AppAuthorizationFlows = []db.AuthorizationFlow{
4243
db.AuthorizationFlowGithubAppFlow,
4344
}
4445

45-
// ProviderClassInfo returns class metadata for GitHub-backed provider classes.
46-
func ProviderClassInfo(class db.ProviderClass) (*minderv1.ProviderClassInfo, error) {
47-
switch class {
46+
// ProviderClassInfo implements the Provider interface.
47+
func (c *GitHub) ProviderClassInfo() *minderv1.ProviderClassInfo {
48+
supportedTypes := provifv1.ProviderTypesFromImpl(c)
49+
supportedEntities := []minderv1.Entity{
50+
minderv1.Entity_ENTITY_REPOSITORIES,
51+
minderv1.Entity_ENTITY_PULL_REQUESTS,
52+
minderv1.Entity_ENTITY_ARTIFACTS,
53+
minderv1.Entity_ENTITY_RELEASE,
54+
}
55+
switch c.providerClass {
4856
case db.ProviderClassGithub:
4957
return &minderv1.ProviderClassInfo{
5058
Class: string(db.ProviderClassGithub),
5159
DisplayName: "GitHub OAuth",
5260
Description: "GitHub provider using OAuth credentials.",
53-
SupportedProviderTypes: dbProviderTypesToPB(OAuthImplements),
61+
SupportedProviderTypes: supportedTypes,
5462
SupportedAuthFlows: dbAuthFlowsToPB(OAuthAuthorizationFlows),
55-
SupportedEntities: []minderv1.Entity{
56-
minderv1.Entity_ENTITY_REPOSITORIES,
57-
minderv1.Entity_ENTITY_PULL_REQUESTS,
58-
minderv1.Entity_ENTITY_ARTIFACTS,
59-
minderv1.Entity_ENTITY_RELEASE,
60-
},
61-
DocumentationUrl: providerDocsURL,
62-
}, nil
63+
SupportedEntities: supportedEntities,
64+
DocumentationUrl: providerDocsURL,
65+
}
6366
case db.ProviderClassGithubApp:
6467
return &minderv1.ProviderClassInfo{
6568
Class: string(db.ProviderClassGithubApp),
6669
DisplayName: "GitHub App",
6770
Description: "GitHub App-based provider with installation-based authorization.",
68-
SupportedProviderTypes: dbProviderTypesToPB(AppImplements),
71+
SupportedProviderTypes: supportedTypes,
6972
SupportedAuthFlows: dbAuthFlowsToPB(AppAuthorizationFlows),
70-
SupportedEntities: []minderv1.Entity{
71-
minderv1.Entity_ENTITY_REPOSITORIES,
72-
minderv1.Entity_ENTITY_PULL_REQUESTS,
73-
minderv1.Entity_ENTITY_ARTIFACTS,
74-
minderv1.Entity_ENTITY_RELEASE,
75-
},
76-
DocumentationUrl: providerDocsURL,
77-
}, nil
78-
case db.ProviderClassGhcr:
79-
fallthrough
80-
case db.ProviderClassDockerhub:
81-
fallthrough
82-
case db.ProviderClassGitlab:
83-
return nil, fmt.Errorf("unsupported GitHub provider class: %s", class)
73+
SupportedEntities: supportedEntities,
74+
DocumentationUrl: providerDocsURL,
75+
}
8476
default:
85-
return nil, fmt.Errorf("unsupported GitHub provider class: %s", class)
86-
}
87-
}
88-
89-
// ProviderClassInfo implements the Provider interface.
90-
func (c *GitHub) ProviderClassInfo() *minderv1.ProviderClassInfo {
91-
info, err := ProviderClassInfo(c.providerClass)
92-
if err != nil {
9377
return nil
9478
}
95-
96-
return info
9779
}
9880

99-
func dbProviderTypesToPB(types []db.ProviderType) []minderv1.ProviderType {
100-
out := make([]minderv1.ProviderType, 0, len(types))
101-
for _, t := range types {
102-
switch t {
103-
case db.ProviderTypeGit:
104-
out = append(out, minderv1.ProviderType_PROVIDER_TYPE_GIT)
105-
case db.ProviderTypeGithub:
106-
out = append(out, minderv1.ProviderType_PROVIDER_TYPE_GITHUB)
107-
case db.ProviderTypeRest:
108-
out = append(out, minderv1.ProviderType_PROVIDER_TYPE_REST)
109-
case db.ProviderTypeRepoLister:
110-
out = append(out, minderv1.ProviderType_PROVIDER_TYPE_REPO_LISTER)
111-
case db.ProviderTypeOci:
112-
out = append(out, minderv1.ProviderType_PROVIDER_TYPE_OCI)
113-
case db.ProviderTypeImageLister:
114-
out = append(out, minderv1.ProviderType_PROVIDER_TYPE_IMAGE_LISTER)
115-
}
81+
// ProviderClassInfo returns class metadata for GitHub-backed provider classes.
82+
// It uses a nil-pointer receiver to avoid needing a live client instance.
83+
func ProviderClassInfo(class db.ProviderClass) (*minderv1.ProviderClassInfo, error) {
84+
info := (&GitHub{providerClass: class}).ProviderClassInfo()
85+
if info == nil {
86+
return nil, fmt.Errorf("unsupported GitHub provider class: %s", class)
11687
}
117-
118-
return out
88+
return info, nil
11989
}
12090

12191
func dbAuthFlowsToPB(flows []db.AuthorizationFlow) []minderv1.AuthorizationFlow {

internal/providers/github/provider_test.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,13 @@
44
package github
55

66
import (
7+
"slices"
78
"testing"
89

10+
"github.com/stretchr/testify/assert"
11+
"github.com/stretchr/testify/require"
12+
13+
"github.com/mindersec/minder/internal/db"
914
"github.com/mindersec/minder/internal/providers/github/properties"
1015
minderv1 "github.com/mindersec/minder/pkg/api/protobuf/go/minder/v1"
1116
testhelper "github.com/mindersec/minder/pkg/providers/v1/testing"
@@ -21,3 +26,75 @@ func TestRegistration(t *testing.T) {
2126
// in this test -- we test them in common_test.go.
2227
testhelper.CheckRegistrationExcept(t, gh, minderv1.Entity_ENTITY_REPOSITORIES)
2328
}
29+
30+
func TestProviderClassInfo(t *testing.T) {
31+
t.Parallel()
32+
33+
wantEntities := []minderv1.Entity{
34+
minderv1.Entity_ENTITY_REPOSITORIES,
35+
minderv1.Entity_ENTITY_PULL_REQUESTS,
36+
minderv1.Entity_ENTITY_ARTIFACTS,
37+
minderv1.Entity_ENTITY_RELEASE,
38+
}
39+
wantTypes := []minderv1.ProviderType{
40+
minderv1.ProviderType_PROVIDER_TYPE_GITHUB,
41+
minderv1.ProviderType_PROVIDER_TYPE_GIT,
42+
minderv1.ProviderType_PROVIDER_TYPE_REST,
43+
minderv1.ProviderType_PROVIDER_TYPE_REPO_LISTER,
44+
minderv1.ProviderType_PROVIDER_TYPE_IMAGE_LISTER,
45+
}
46+
47+
tests := []struct {
48+
name string
49+
providerClass db.ProviderClass
50+
wantClass string
51+
wantDisplay string
52+
wantAuthFlows []minderv1.AuthorizationFlow
53+
}{
54+
{
55+
name: "GitHub OAuth",
56+
providerClass: db.ProviderClassGithub,
57+
wantClass: string(db.ProviderClassGithub),
58+
wantDisplay: "GitHub OAuth",
59+
wantAuthFlows: []minderv1.AuthorizationFlow{
60+
minderv1.AuthorizationFlow_AUTHORIZATION_FLOW_USER_INPUT,
61+
minderv1.AuthorizationFlow_AUTHORIZATION_FLOW_OAUTH2_AUTHORIZATION_CODE_FLOW,
62+
},
63+
},
64+
{
65+
name: "GitHub App",
66+
providerClass: db.ProviderClassGithubApp,
67+
wantClass: string(db.ProviderClassGithubApp),
68+
wantDisplay: "GitHub App",
69+
wantAuthFlows: []minderv1.AuthorizationFlow{
70+
minderv1.AuthorizationFlow_AUTHORIZATION_FLOW_GITHUB_APP_FLOW,
71+
},
72+
},
73+
}
74+
75+
for _, tt := range tests {
76+
t.Run(tt.name, func(t *testing.T) {
77+
t.Parallel()
78+
gh := &GitHub{
79+
propertyFetchers: properties.NewPropertyFetcherFactory(),
80+
providerClass: tt.providerClass,
81+
}
82+
83+
info := gh.ProviderClassInfo()
84+
require.NotNil(t, info)
85+
86+
assert.Equal(t, tt.wantClass, info.Class)
87+
assert.Equal(t, tt.wantDisplay, info.DisplayName)
88+
assert.NotEmpty(t, info.Description)
89+
assert.Equal(t, providerDocsURL, info.DocumentationUrl)
90+
91+
assert.ElementsMatch(t, tt.wantAuthFlows, info.SupportedAuthFlows)
92+
assert.ElementsMatch(t, wantEntities, info.SupportedEntities)
93+
94+
for _, want := range wantTypes {
95+
assert.True(t, slices.Contains(info.SupportedProviderTypes, want),
96+
"expected SupportedProviderTypes to contain %v", want)
97+
}
98+
})
99+
}
100+
}

internal/providers/gitlab/metadata.go

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,20 @@ package gitlab
55

66
import (
77
minderv1 "github.com/mindersec/minder/pkg/api/protobuf/go/minder/v1"
8+
provifv1 "github.com/mindersec/minder/pkg/providers/v1"
89
)
910

1011
const (
1112
providerDocsBaseURL = "https://docs.mindersec.dev"
1213
providerDocsURL = providerDocsBaseURL + "/understand/providers"
1314
)
1415

15-
// ClassInfo returns metadata for the GitLab provider class.
16-
func ClassInfo() *minderv1.ProviderClassInfo {
16+
func (c *gitlabClient) ProviderClassInfo() *minderv1.ProviderClassInfo {
1717
return &minderv1.ProviderClassInfo{
18-
Class: Class,
19-
DisplayName: "GitLab",
20-
Description: "GitLab provider using OAuth credentials.",
21-
SupportedProviderTypes: []minderv1.ProviderType{
22-
minderv1.ProviderType_PROVIDER_TYPE_GIT,
23-
minderv1.ProviderType_PROVIDER_TYPE_REST,
24-
minderv1.ProviderType_PROVIDER_TYPE_REPO_LISTER,
25-
},
18+
Class: Class,
19+
DisplayName: "GitLab",
20+
Description: "GitLab provider using OAuth credentials.",
21+
SupportedProviderTypes: provifv1.ProviderTypesFromImpl(c),
2622
SupportedAuthFlows: []minderv1.AuthorizationFlow{
2723
minderv1.AuthorizationFlow_AUTHORIZATION_FLOW_USER_INPUT,
2824
minderv1.AuthorizationFlow_AUTHORIZATION_FLOW_OAUTH2_AUTHORIZATION_CODE_FLOW,
@@ -36,6 +32,8 @@ func ClassInfo() *minderv1.ProviderClassInfo {
3632
}
3733
}
3834

39-
func (*gitlabClient) ProviderClassInfo() *minderv1.ProviderClassInfo {
40-
return ClassInfo()
35+
// ClassInfo returns metadata for the GitLab provider class.
36+
// It uses a nil-pointer receiver to avoid needing a live client instance.
37+
func ClassInfo() *minderv1.ProviderClassInfo {
38+
return (*gitlabClient)(nil).ProviderClassInfo()
4139
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// SPDX-FileCopyrightText: Copyright 2026 The Minder Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package gitlab
5+
6+
import (
7+
"testing"
8+
9+
"github.com/stretchr/testify/assert"
10+
"github.com/stretchr/testify/require"
11+
12+
minderv1 "github.com/mindersec/minder/pkg/api/protobuf/go/minder/v1"
13+
)
14+
15+
func TestClassInfo(t *testing.T) {
16+
t.Parallel()
17+
18+
info := ClassInfo()
19+
require.NotNil(t, info)
20+
21+
assert.Equal(t, Class, info.Class)
22+
assert.Equal(t, "GitLab", info.DisplayName)
23+
assert.NotEmpty(t, info.Description)
24+
assert.Equal(t, providerDocsURL, info.DocumentationUrl)
25+
26+
assert.ElementsMatch(t, []minderv1.AuthorizationFlow{
27+
minderv1.AuthorizationFlow_AUTHORIZATION_FLOW_USER_INPUT,
28+
minderv1.AuthorizationFlow_AUTHORIZATION_FLOW_OAUTH2_AUTHORIZATION_CODE_FLOW,
29+
}, info.SupportedAuthFlows)
30+
31+
assert.ElementsMatch(t, []minderv1.Entity{
32+
minderv1.Entity_ENTITY_REPOSITORIES,
33+
minderv1.Entity_ENTITY_PULL_REQUESTS,
34+
minderv1.Entity_ENTITY_RELEASE,
35+
}, info.SupportedEntities)
36+
37+
assert.ElementsMatch(t, []minderv1.ProviderType{
38+
minderv1.ProviderType_PROVIDER_TYPE_GIT,
39+
minderv1.ProviderType_PROVIDER_TYPE_REST,
40+
minderv1.ProviderType_PROVIDER_TYPE_REPO_LISTER,
41+
}, info.SupportedProviderTypes)
42+
}

0 commit comments

Comments
 (0)