Skip to content

Commit 52492bf

Browse files
committed
full cleanup + tests
1 parent f555397 commit 52492bf

File tree

10 files changed

+96
-428
lines changed

10 files changed

+96
-428
lines changed

cmd/ecr-credential-provider/main.go

Lines changed: 31 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -30,36 +30,24 @@ import (
3030
"github.com/aws/aws-sdk-go/aws"
3131
"github.com/aws/aws-sdk-go/aws/session"
3232
"github.com/aws/aws-sdk-go/service/ecr"
33-
"github.com/aws/aws-sdk-go/service/ecrpublic"
34-
"github.com/spf13/cobra"
3533

3634
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
37-
"k8s.io/component-base/logs"
3835
"k8s.io/klog/v2"
3936
v1 "k8s.io/kubelet/pkg/apis/credentialprovider/v1"
4037
)
4138

42-
const ecrPublicRegion string = "us-east-1"
43-
const ecrPublicHost string = "public.ecr.aws"
44-
45-
var ecrPrivateHostPattern = regexp.MustCompile(`^(\d{12})\.dkr[\.\-]ecr(\-fips)?\.([a-zA-Z0-9][a-zA-Z0-9-_]*)\.(amazonaws\.com(?:\.cn)?|on\.(?:aws|amazonwebservices\.com\.cn)|sc2s\.sgov\.gov|c2s\.ic\.gov|cloud\.adc-e\.uk|csp\.hci\.ic\.gov)$`)
39+
var ecrPattern = regexp.MustCompile(`^(\d{12})\.dkr\.ecr(\-fips)?\.([a-zA-Z0-9][a-zA-Z0-9-_]*)\.(amazonaws\.com(\.cn)?|sc2s\.sgov\.gov|c2s\.ic\.gov|cloud\.adc-e\.uk|csp\.hci\.ic\.gov)$`)
4640

4741
// ECR abstracts the calls we make to aws-sdk for testing purposes
4842
type ECR interface {
4943
GetAuthorizationToken(input *ecr.GetAuthorizationTokenInput) (*ecr.GetAuthorizationTokenOutput, error)
5044
}
5145

52-
// ECRPublic abstracts the calls we make to aws-sdk for testing purposes
53-
type ECRPublic interface {
54-
GetAuthorizationToken(input *ecrpublic.GetAuthorizationTokenInput) (*ecrpublic.GetAuthorizationTokenOutput, error)
55-
}
56-
5746
type ecrPlugin struct {
58-
ecr ECR
59-
ecrPublic ECRPublic
47+
ecr ECR
6048
}
6149

62-
func defaultECRProvider(region string) (*ecr.ECR, error) {
50+
func defaultECRProvider(region string, registryID string) (*ecr.ECR, error) {
6351
sess, err := session.NewSessionWithOptions(session.Options{
6452
Config: aws.Config{Region: aws.String(region)},
6553
SharedConfigState: session.SharedConfigEnable,
@@ -71,107 +59,40 @@ func defaultECRProvider(region string) (*ecr.ECR, error) {
7159
return ecr.New(sess), nil
7260
}
7361

74-
func publicECRProvider() (*ecrpublic.ECRPublic, error) {
75-
// ECR public registries are only in one region and only accessible from regions
76-
// in the "aws" partition.
77-
sess, err := session.NewSessionWithOptions(session.Options{
78-
Config: aws.Config{Region: aws.String(ecrPublicRegion)},
79-
SharedConfigState: session.SharedConfigEnable,
80-
})
81-
if err != nil {
82-
return nil, err
83-
}
84-
85-
return ecrpublic.New(sess), nil
86-
}
87-
88-
type credsData struct {
89-
authToken *string
90-
expiresAt *time.Time
91-
}
92-
93-
func (e *ecrPlugin) getPublicCredsData() (*credsData, error) {
94-
klog.Infof("Getting creds for public registry")
95-
var err error
96-
97-
if e.ecrPublic == nil {
98-
e.ecrPublic, err = publicECRProvider()
99-
}
100-
if err != nil {
101-
return nil, err
102-
}
103-
104-
output, err := e.ecrPublic.GetAuthorizationToken(&ecrpublic.GetAuthorizationTokenInput{})
62+
func (e *ecrPlugin) GetCredentials(ctx context.Context, image string, args []string) (*v1.CredentialProviderResponse, error) {
63+
registryID, region, registry, err := parseRepoURL(image)
10564
if err != nil {
10665
return nil, err
10766
}
10867

109-
if output == nil {
110-
return nil, errors.New("response output from ECR was nil")
111-
}
112-
113-
if output.AuthorizationData == nil {
114-
return nil, errors.New("authorization data was empty")
115-
}
116-
117-
return &credsData{
118-
authToken: output.AuthorizationData.AuthorizationToken,
119-
expiresAt: output.AuthorizationData.ExpiresAt,
120-
}, nil
121-
}
122-
123-
func (e *ecrPlugin) getPrivateCredsData(imageHost string, image string) (*credsData, error) {
124-
klog.Infof("Getting creds for private image %s", image)
125-
region, err := parseRegionFromECRPrivateHost(imageHost)
126-
if err != nil {
127-
return nil, err
128-
}
12968
if e.ecr == nil {
130-
e.ecr, err = defaultECRProvider(region)
69+
e.ecr, err = defaultECRProvider(region, registryID)
13170
if err != nil {
13271
return nil, err
13372
}
13473
}
135-
output, err := e.ecr.GetAuthorizationToken(&ecr.GetAuthorizationTokenInput{})
74+
75+
output, err := e.ecr.GetAuthorizationToken(&ecr.GetAuthorizationTokenInput{
76+
RegistryIds: []*string{aws.String(registryID)},
77+
})
13678
if err != nil {
13779
return nil, err
13880
}
81+
13982
if output == nil {
14083
return nil, errors.New("response output from ECR was nil")
14184
}
85+
14286
if len(output.AuthorizationData) == 0 {
14387
return nil, errors.New("authorization data was empty")
14488
}
145-
return &credsData{
146-
authToken: output.AuthorizationData[0].AuthorizationToken,
147-
expiresAt: output.AuthorizationData[0].ExpiresAt,
148-
}, nil
149-
}
150-
151-
func (e *ecrPlugin) GetCredentials(ctx context.Context, image string, args []string) (*v1.CredentialProviderResponse, error) {
152-
var creds *credsData
153-
var err error
154-
155-
imageHost, err := parseHostFromImageReference(image)
156-
if err != nil {
157-
return nil, err
158-
}
159-
160-
if imageHost == ecrPublicHost {
161-
creds, err = e.getPublicCredsData()
162-
} else {
163-
creds, err = e.getPrivateCredsData(imageHost, image)
164-
}
165-
166-
if err != nil {
167-
return nil, err
168-
}
16989

170-
if creds.authToken == nil {
90+
data := output.AuthorizationData[0]
91+
if data.AuthorizationToken == nil {
17192
return nil, errors.New("authorization token in response was nil")
17293
}
17394

174-
decodedToken, err := base64.StdEncoding.DecodeString(aws.StringValue(creds.authToken))
95+
decodedToken, err := base64.StdEncoding.DecodeString(aws.StringValue(data.AuthorizationToken))
17596
if err != nil {
17697
return nil, err
17798
}
@@ -181,13 +102,13 @@ func (e *ecrPlugin) GetCredentials(ctx context.Context, image string, args []str
181102
return nil, errors.New("error parsing username and password from authorization token")
182103
}
183104

184-
cacheDuration := getCacheDuration(creds.expiresAt)
105+
cacheDuration := getCacheDuration(data.ExpiresAt)
185106

186107
return &v1.CredentialProviderResponse{
187108
CacheKeyType: v1.RegistryPluginCacheKeyType,
188109
CacheDuration: cacheDuration,
189110
Auth: map[string]v1.AuthConfig{
190-
imageHost: {
111+
registry: {
191112
Username: parts[0],
192113
Password: parts[1],
193114
},
@@ -214,50 +135,30 @@ func getCacheDuration(expiresAt *time.Time) *metav1.Duration {
214135
return cacheDuration
215136
}
216137

217-
// parseHostFromImageReference parses the hostname from an image reference
218-
func parseHostFromImageReference(image string) (string, error) {
219-
// a URL needs a scheme to be parsed correctly
220-
if !strings.Contains(image, "://") {
138+
// parseRepoURL parses and splits the registry URL
139+
// returns (registryID, region, registry).
140+
// <registryID>.dkr.ecr(-fips).<region>.amazonaws.com(.cn)
141+
func parseRepoURL(image string) (string, string, string, error) {
142+
if !strings.Contains(image, "https://") {
221143
image = "https://" + image
222144
}
223145
parsed, err := url.Parse(image)
224146
if err != nil {
225-
return "", fmt.Errorf("error parsing image reference %s: %v", image, err)
147+
return "", "", "", fmt.Errorf("error parsing image %s: %v", image, err)
226148
}
227-
return parsed.Hostname(), nil
228-
}
229149

230-
func parseRegionFromECRPrivateHost(host string) (string, error) {
231-
splitHost := ecrPrivateHostPattern.FindStringSubmatch(host)
232-
if len(splitHost) != 5 {
233-
return "", fmt.Errorf("invalid private ECR host: %s", host)
150+
splitURL := ecrPattern.FindStringSubmatch(parsed.Hostname())
151+
if len(splitURL) < 4 {
152+
return "", "", "", fmt.Errorf("%s is not a valid ECR repository URL", parsed.Hostname())
234153
}
235-
return splitHost[3], nil
154+
155+
return splitURL[1], splitURL[3], parsed.Hostname(), nil
236156
}
237157

238158
func main() {
239-
logs.InitLogs()
240-
defer logs.FlushLogs()
241-
242-
if err := newCredentialProviderCommand().Execute(); err != nil {
159+
p := NewCredentialProvider(&ecrPlugin{})
160+
if err := p.Run(context.TODO()); err != nil {
161+
klog.Errorf("Error running credential provider plugin: %v", err)
243162
os.Exit(1)
244163
}
245164
}
246-
247-
var gitVersion string
248-
249-
func newCredentialProviderCommand() *cobra.Command {
250-
cmd := &cobra.Command{
251-
Use: "ecr-credential-provider",
252-
Short: "ECR credential provider for kubelet",
253-
Version: gitVersion,
254-
Run: func(cmd *cobra.Command, args []string) {
255-
p := NewCredentialProvider(&ecrPlugin{})
256-
if err := p.Run(context.TODO()); err != nil {
257-
klog.Errorf("Error running credential provider plugin: %v", err)
258-
os.Exit(1)
259-
}
260-
},
261-
}
262-
return cmd
263-
}

0 commit comments

Comments
 (0)