Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Run func without HOME defined/ unaccessible .config dir #2236

Draft
wants to merge 15 commits into
base: main
Choose a base branch
from
3 changes: 0 additions & 3 deletions cmd/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,9 +159,6 @@ func runBuild(cmd *cobra.Command, _ []string, newClient ClientFactory) (err erro
cfg buildConfig
f fn.Function
)
if err = config.CreatePaths(); err != nil { // for possible auth.json usage
return
}
if cfg, err = newBuildConfig().Prompt(); err != nil { // gather values into a single instruction set
return
}
Expand Down
3 changes: 0 additions & 3 deletions cmd/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,9 +233,6 @@ func runDeploy(cmd *cobra.Command, newClient ClientFactory) (err error) {
cfg deployConfig
f fn.Function
)
if err = config.CreatePaths(); err != nil { // for possible auth.json usage
return
}
if cfg, err = newDeployConfig(cmd).Prompt(); err != nil {
return
}
Expand Down
33 changes: 33 additions & 0 deletions cmd/deploy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1894,3 +1894,36 @@ func TestDeploy_NoErrorOnOldFunctionNotFound(t *testing.T) {
t.Fatal(err)
}
}

// TestDeploy_WithoutHome ensures that deploying a function without HOME &
// XDG_CONFIG_HOME defined succeeds
func TestDeploy_WithoutHome(t *testing.T) {
var (
root = FromTempDirectory(t)
ns = "myns"
)

t.Setenv("HOME", "")
t.Setenv("XDG_CONFIG_HOME", "")

// Create a basic go Function
f := fn.Function{
Runtime: "go",
Root: root,
}
_, err := fn.New().Init(f)
if err != nil {
t.Fatal(err)
}

// Deploy the function
cmd := NewDeployCmd(NewTestClient(
fn.WithDeployer(mock.NewDeployer()),
fn.WithRegistry(TestRegistry)))

cmd.SetArgs([]string{fmt.Sprintf("--namespace=%s", ns)})
err = cmd.Execute()
if err != nil {
t.Fatal(err)
}
}
2 changes: 1 addition & 1 deletion pkg/builders/buildpacks/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ func (b *Builder) Build(ctx context.Context, f fn.Function, platforms []fn.Platf
if err = impl.Build(ctx, opts); err != nil {
if ctx.Err() != nil {
return // SIGINT
} else if !b.verbose {
} else if b.verbose {
err = fmt.Errorf("failed to build the function: %w", err)
fmt.Fprintln(color.Stderr(), "")
_, _ = io.Copy(color.Stderr(), &b.outBuff)
Expand Down
42 changes: 27 additions & 15 deletions pkg/docker/creds/credentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,24 +165,34 @@ func NewCredentialsProvider(configPath string, opts ...Opt) docker.CredentialsPr
}
}

// default credential loaders map -- load only those that should be there.
// Dont include loaders that dont have valid config paths etc.
var defaultCredentialLoaders = []CredentialsCallback{}

c.authFilePath = filepath.Join(configPath, "auth.json")
sys := &containersTypes.SystemContext{
AuthFilePath: c.authFilePath,
}

home, err := os.UserHomeDir()
if err != nil {
panic(err)
// if path to the config file does not exist -- dont include it.
// That is HOME/func/auth.json or XDG_CONFIG_HOME/func/auth.json (higher pref)
if _, err := os.Stat(c.authFilePath); err == nil {
defaultCredentialLoaders = append(defaultCredentialLoaders,
func(registry string) (docker.Credentials, error) {
return getCredentialsByCredentialHelper(c.authFilePath, registry)
})
}
dockerConfigPath := filepath.Join(home, ".docker", "config.json")

var defaultCredentialLoaders = []CredentialsCallback{
func(registry string) (docker.Credentials, error) {
return getCredentialsByCredentialHelper(c.authFilePath, registry)
},
func(registry string) (docker.Credentials, error) {
return getCredentialsByCredentialHelper(dockerConfigPath, registry)
},
// check that home is defined for .docker/config.json creds
home, err := os.UserHomeDir()
if err == nil {
dockerConfigPath := filepath.Join(home, ".docker", "config.json")
defaultCredentialLoaders = append(defaultCredentialLoaders,
func(registry string) (docker.Credentials, error) {
return getCredentialsByCredentialHelper(dockerConfigPath, registry)
})
}
defaultCredentialLoaders = append(defaultCredentialLoaders,
func(registry string) (docker.Credentials, error) {
creds, err := dockerConfig.GetCredentials(sys, registry)
if err != nil {
Expand All @@ -195,7 +205,8 @@ func NewCredentialsProvider(configPath string, opts ...Opt) docker.CredentialsPr
Username: creds.Username,
Password: creds.Password,
}, nil
},
})
defaultCredentialLoaders = append(defaultCredentialLoaders,
func(registry string) (docker.Credentials, error) {
// Fallback onto default docker config locations
emptySys := &containersTypes.SystemContext{}
Expand All @@ -207,11 +218,11 @@ func NewCredentialsProvider(configPath string, opts ...Opt) docker.CredentialsPr
Username: creds.Username,
Password: creds.Password,
}, nil
},
})
defaultCredentialLoaders = append(defaultCredentialLoaders,
func(registry string) (docker.Credentials, error) { // empty credentials provider for unsecured registries
return docker.Credentials{}, nil
},
}
})

c.credentialLoaders = append(c.credentialLoaders, defaultCredentialLoaders...)

Expand Down Expand Up @@ -283,6 +294,7 @@ func (c *credentialsProvider) getCredentials(ctx context.Context, image string)
helper = strings.TrimPrefix(helper, "docker-credential-")
err = setCredentialHelperToConfig(c.authFilePath, helper)
if err != nil {
// fmt.Fprintf(os.Stderr, "Warning: failed to set helper to the config with error: '%v'\n", err)
return docker.Credentials{}, fmt.Errorf("faild to set the helper to the config: %w", err)
}
err = setCredentialsByCredentialHelper(c.authFilePath, registry, result.Username, result.Password)
Expand Down
80 changes: 79 additions & 1 deletion pkg/docker/creds/credentials_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"net/http"
"os"
"path/filepath"
"reflect"
"runtime"
"strings"
"sync"
Expand Down Expand Up @@ -284,7 +285,6 @@ func startServer(t *testing.T, uname, pwd string) (addr, addrTLS string) {
panic(err)
}
}()

// make the testing CA trusted by default HTTP transport/client
oldDefaultTransport := http.DefaultTransport
newDefaultTransport := http.DefaultTransport.(*http.Transport).Clone()
Expand Down Expand Up @@ -549,6 +549,77 @@ func TestCredentialsProviderSavingFromUserInput(t *testing.T) {
}
}

// TestCredentialsWithoutHome tests different scenarios when HOME is not set
func TestCredentialsWithoutHome(t *testing.T) {
type args struct {
promptUser creds.CredentialsCallback
verifyCredentials creds.VerifyCredentialsCallback
registry string
setUpEnv setUpEnv
}
tests := []struct {
name string
testHomePathEmpty bool
args args
want Credentials
}{
{
name: "empty home with correct user prompt",
testHomePathEmpty: true,
args: args{
promptUser: correctPwdCallback, // user inputs correct credentials
verifyCredentials: correctVerifyCbk,
registry: "docker.io",
setUpEnv: setEmptyHome,
},
want: Credentials{Username: dockerIoUser, Password: dockerIoUserPwd},
},
{
name: "empty config with user prompt",
args: args{
promptUser: correctPwdCallback,
verifyCredentials: correctVerifyCbk,
registry: "docker.io",
},
want: Credentials{Username: dockerIoUser, Password: dockerIoUserPwd},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
resetHomeDir(t)
if tt.args.setUpEnv != nil {
tt.args.setUpEnv(t)
}

// prepare config path for credentials provider
var configPath string
if tt.testHomePathEmpty {
configPath = ""
} else {
configPath = testConfigPath(t)
}

credentialsProvider := creds.NewCredentialsProvider(
configPath,
creds.WithPromptForCredentials(tt.args.promptUser),
creds.WithVerifyCredentials(tt.args.verifyCredentials),
)

got, err := credentialsProvider(context.Background(), tt.args.registry+"/someorg/someimage:sometag")
if err != nil {
t.Errorf("unexpected error: %v", err)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("got: %v, want: %v", got, tt.want)
}
})
}
}

// ********************** helper functions below **************************** \\

func resetHomeDir(t *testing.T) {
t.TempDir()
if err := os.RemoveAll(homeTempDir); err != nil {
Expand Down Expand Up @@ -903,3 +974,10 @@ func (i *inMemoryHelper) Delete(serverURL string) error {

return credentials.NewErrCredentialsNotFound()
}

// set home variables to empty values
func setEmptyHome(t *testing.T) {
t.Helper()
t.Setenv("HOME", "")
t.Setenv("XDG_CONFIG_HOME", "")
}
47 changes: 47 additions & 0 deletions pkg/functions/client_int_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/docker/docker/client"

"knative.dev/func/pkg/builders/buildpacks"
"knative.dev/func/pkg/builders/s2i"
"knative.dev/func/pkg/docker"
fn "knative.dev/func/pkg/functions"
"knative.dev/func/pkg/knative"
Expand Down Expand Up @@ -545,6 +546,31 @@ func Handle(ctx context.Context, w http.ResponseWriter, req *http.Request) {
}
}

// TestDeployWithoutHome ensures that running client.New works without
// home
func TestDeployWithoutHome(t *testing.T) {
root, cleanup := Mktemp(t)
defer cleanup()

t.Setenv("HOME", "")
t.Setenv("XDG_CONFIG_HOME", "")
verbose := false
name := "test-deploy-no-home"

f := fn.Function{Runtime: "node", Name: name, Root: root, Namespace: DefaultNamespace}

// client with s2i builder because pack needs HOME
client := newClientWithS2i(verbose)

// expect to succeed
_, _, err := client.New(context.Background(), f)
if err != nil {
t.Fatalf("expected no errors but got %v", err)
}

defer del(t, client, name, DefaultNamespace)
}

// ***********
// Helpers
// ***********
Expand All @@ -571,6 +597,27 @@ func newClient(verbose bool) *fn.Client {
)
}

// copy of newClient just builder is s2i instead of buildpacks
func newClientWithS2i(verbose bool) *fn.Client {
builder := s2i.NewBuilder(s2i.WithVerbose(verbose))
pusher := docker.NewPusher(docker.WithVerbose(verbose))
deployer := knative.NewDeployer(knative.WithDeployerVerbose(verbose))
describer := knative.NewDescriber(verbose)
remover := knative.NewRemover(verbose)
lister := knative.NewLister(verbose)

return fn.New(
fn.WithRegistry(DefaultRegistry),
fn.WithVerbose(verbose),
fn.WithBuilder(builder),
fn.WithPusher(pusher),
fn.WithDeployer(deployer),
fn.WithDescriber(describer),
fn.WithRemover(remover),
fn.WithLister(lister),
)
}

// Del cleans up after a test by removing a function by name.
// (test fails if the named function does not exist)
//
Expand Down