blob: 4832226780b07e003f1f60bb7c3a6468aa4ebf7d [file] [edit]
// Copyright 2016 Google, Inc.
//
// 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 credhelper
import (
"errors"
"fmt"
"strings"
"testing"
"github.com/GoogleCloudPlatform/docker-credential-gcr/v2/mock/mock_cmd"
"github.com/GoogleCloudPlatform/docker-credential-gcr/v2/mock/mock_config" // mocks must be generated before test execution
"github.com/GoogleCloudPlatform/docker-credential-gcr/v2/mock/mock_store"
"github.com/GoogleCloudPlatform/docker-credential-gcr/v2/config"
"github.com/GoogleCloudPlatform/docker-credential-gcr/v2/store"
"github.com/GoogleCloudPlatform/docker-credential-gcr/v2/util/cmd"
"github.com/golang/mock/gomock"
)
var expectedGCRUsername = fmt.Sprintf("_dcgcr_%s_token", strings.ReplaceAll(config.Version, ".", "_"))
var expectedGCRZeroUsername = "_dcgcr_0_0_0_token"
var testGCRHosts = [...]string{
"gcr.io",
"us.gcr.io",
"eu.gcr.io",
"asia.gcr.io",
"staging-k8s.gcr.io",
"marketplace.gcr.io",
"appengine.gcr.io",
"hypothetical-alias.gcr.io",
}
func TestGet_GCRCredentials(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
// create a mocks for the helper to use
mockStore := mock_store.NewMockGCRCredStore(mockCtrl)
mockUserCfg := mock_config.NewMockUserConfig(mockCtrl)
// mock the helper methods used by getGCRAccessToken
expectedSecret := "secrets!"
tested := &gcrCredHelper{
store: mockStore,
userCfg: mockUserCfg,
envToken: func() (string, error) {
return expectedSecret, nil
},
gcloudSDKToken: func(_ cmd.Command) (string, error) {
return "", errors.New("no token here")
},
credStoreToken: func(_ store.GCRCredStore) (string, error) {
return "", errors.New("no token here")
},
}
// Verify that all of GCR's hostnames return GCR's access token.
for _, host := range testGCRHosts {
mockUserCfg.EXPECT().TokenSources().Return(config.DefaultTokenSources[:])
username, secret, err := tested.Get("https://" + host)
if err != nil {
t.Errorf("get returned an error: %v", err)
} else if username != expectedGCRUsername && username != expectedGCRZeroUsername {
t.Errorf("expected GCR username: %s but got: %s", expectedGCRUsername, username)
} else if secret != expectedSecret {
t.Errorf("expected secret: %s but got: %s", expectedSecret, secret)
}
}
}
/*
The following tests verify the behavior of getGCRAccessToken. Preference
is defined by tokenSources
*/
func TestGetGCRAccessToken_Env(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
// create a mock store to use
mockStore := mock_store.NewMockGCRCredStore(mockCtrl)
mockUserCfg := mock_config.NewMockUserConfig(mockCtrl)
mockUserCfg.EXPECT().TokenSources().Return(config.DefaultTokenSources[:])
// mock the helper methods used by getGCRAccessToken
const expected = "application default creds!"
tested := &gcrCredHelper{
store: mockStore,
userCfg: mockUserCfg,
envToken: func() (string, error) {
return expected, nil
},
gcloudSDKToken: func(_ cmd.Command) (string, error) {
return "", errors.New("no token from gcloud")
},
credStoreToken: func(_ store.GCRCredStore) (string, error) {
return "", errors.New("no token in the cred store")
},
}
token, err := tested.getGCRAccessToken()
if err != nil {
t.Fatalf("getGCRAccessToken returned an error: %v", err)
} else if token != expected {
t.Fatalf("Expected: %s got: %s", expected, token)
}
}
func TestGetGCRAccessToken_PrivateStore(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
// create a mock store to use
mockStore := mock_store.NewMockGCRCredStore(mockCtrl)
mockUserCfg := mock_config.NewMockUserConfig(mockCtrl)
mockUserCfg.EXPECT().TokenSources().Return(config.DefaultTokenSources[:])
// mock the helper methods used by getGCRAccessToken
const expected = "private creds!"
tested := &gcrCredHelper{
store: mockStore,
userCfg: mockUserCfg,
envToken: func() (string, error) {
return "creds from `env`", nil
},
gcloudSDKToken: func(_ cmd.Command) (string, error) {
return "creds from `gcloud`", nil
},
credStoreToken: func(_ store.GCRCredStore) (string, error) {
return expected, nil
},
}
token, err := tested.getGCRAccessToken()
if err != nil {
t.Fatalf("getGCRAccessToken returned an error: %v", err)
} else if token != expected {
t.Fatalf("Expected: %s got: %s", expected, token)
}
}
func TestGetGCRAccessToken_NoneExist(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
// create a mock store to use
mockStore := mock_store.NewMockGCRCredStore(mockCtrl)
mockUserCfg := mock_config.NewMockUserConfig(mockCtrl)
mockUserCfg.EXPECT().TokenSources().Return(config.DefaultTokenSources[:])
// mock the helper methods used by getGCRAccessToken
tested := &gcrCredHelper{
store: mockStore,
userCfg: mockUserCfg,
envToken: func() (string, error) {
return "", errors.New("no token here")
},
gcloudSDKToken: func(_ cmd.Command) (string, error) {
return "", errors.New("still no token here")
},
credStoreToken: func(_ store.GCRCredStore) (string, error) {
return "", errors.New("sad panda")
},
}
token, err := tested.getGCRAccessToken()
if err == nil {
t.Fatalf("Expected an error, got token: %s", token)
}
}
func TestGetGCRAccessToken_CustomTokenSources(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
// create a mock store to use
mockStore := mock_store.NewMockGCRCredStore(mockCtrl)
// Mock a user config, re-arranging the token sources.
mockUserCfg := mock_config.NewMockUserConfig(mockCtrl)
mockUserCfg.EXPECT().TokenSources().Return([]string{"store", "gcloud", "env"}) // reversed from default
const (
gcloudCreds = "gcloud sdk creds!"
storeCreds = "private creds!"
envCreds = "environment creds!"
)
// mock the helper methods used by getGCRAccessToken
tested := &gcrCredHelper{
store: mockStore,
userCfg: mockUserCfg,
envToken: func() (string, error) {
return envCreds, nil
},
gcloudSDKToken: func(_ cmd.Command) (string, error) {
return gcloudCreds, nil
},
credStoreToken: func(_ store.GCRCredStore) (string, error) {
return storeCreds, nil
},
}
token, err := tested.getGCRAccessToken()
if err != nil {
t.Fatalf("getGCRAccessToken returned an error: %v", err)
} else if token != storeCreds {
t.Fatalf("Expected: %s got: %s", storeCreds, token)
}
}
func TestGetGCRAccessToken_CustomTokenSources_ValidSourcesDisabled(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
// create a mock store to use
mockStore := mock_store.NewMockGCRCredStore(mockCtrl)
// Mock a user config, disabling some token sources.
mockUserCfg := mock_config.NewMockUserConfig(mockCtrl)
mockUserCfg.EXPECT().TokenSources().Return([]string{"gcloud"}) // gcloud only configured source
const (
storeCreds = "private creds!"
envCreds = "environment creds!"
)
// mock the helper methods used by getGCRAccessToken
tested := &gcrCredHelper{
store: mockStore,
userCfg: mockUserCfg,
envToken: func() (string, error) {
return envCreds, nil
},
gcloudSDKToken: func(_ cmd.Command) (string, error) {
return "", errors.New("no token here")
},
credStoreToken: func(_ store.GCRCredStore) (string, error) {
return storeCreds, nil
},
}
token, err := tested.getGCRAccessToken()
if err == nil {
t.Fatalf("Expected an error, got token: %s", token)
}
}
func TestGetGCRAccessToken_OldGcloudSdkTokenSourceString(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
// create a mock store to use
mockStore := mock_store.NewMockGCRCredStore(mockCtrl)
// Mock a user config, disabling some token sources.
mockUserCfg := mock_config.NewMockUserConfig(mockCtrl)
mockUserCfg.EXPECT().TokenSources().Return([]string{"gcloud_sdk"}) // the string that was initially used for specify gcloud
const (
envCreds = "environment creds!"
gcloudCreds = "gcloud sdk creds!"
storeCreds = "private creds!"
)
// mock the helper methods used by getGCRAccessToken
tested := &gcrCredHelper{
store: mockStore,
userCfg: mockUserCfg,
envToken: func() (string, error) {
return envCreds, nil
},
gcloudSDKToken: func(_ cmd.Command) (string, error) {
return gcloudCreds, nil
},
credStoreToken: func(_ store.GCRCredStore) (string, error) {
return storeCreds, nil
},
}
token, err := tested.getGCRAccessToken()
if err != nil {
t.Fatalf("tokenFromGcloudSDK returned an error: %v", err)
} else if token != gcloudCreds {
t.Fatalf("Expected: '%s' got: '%s'", gcloudCreds, token)
}
}
func TestGetGCRAccessToken_CustomTokenSources_InvalidSource(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
// create a mock store to use
mockStore := mock_store.NewMockGCRCredStore(mockCtrl)
mockUserCfg := mock_config.NewMockUserConfig(mockCtrl)
mockUserCfg.EXPECT().TokenSources().Return([]string{"invalid"})
const (
gcloudCreds = "gcloud sdk creds!"
storeCreds = "private creds!"
envCreds = "environment creds!"
)
// mock the helper methods used by getGCRAccessToken
tested := &gcrCredHelper{
store: mockStore,
userCfg: mockUserCfg,
envToken: func() (string, error) {
return envCreds, nil
},
gcloudSDKToken: func(_ cmd.Command) (string, error) {
return gcloudCreds, nil
},
credStoreToken: func(_ store.GCRCredStore) (string, error) {
return storeCreds, nil
},
}
token, err := tested.getGCRAccessToken()
if err == nil {
t.Fatalf("Expected an error, got token: %s", token)
}
}
func TestTokenFromGcloudSDK(t *testing.T) {
t.Parallel()
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
const gcloudCreds = "gcloud sdk creds!"
// This test is more-or-less tautological, but it's important to verify
// that gcloud is being queried in a supported way.
mockCmd := mock_cmd.NewMockCommand(mockCtrl)
mockCmd.EXPECT().Exec("config", "config-helper", "--force-auth-refresh", "--format=value(credential.access_token)").Return([]uint8(gcloudCreds), nil)
token, err := tokenFromGcloudSDK(mockCmd)
if err != nil {
t.Fatalf("tokenFromGcloudSDK returned an error: %v", err)
} else if token != gcloudCreds {
t.Fatalf("Expected: '%s' got: '%s'", gcloudCreds, token)
}
}