Skip to content

Commit

Permalink
refactor cred_helper_binary for easier testing, added basic unit test…
Browse files Browse the repository at this point in the history
…s, fixed unit test failure in nerdctl_config_applier_test
  • Loading branch information
kiryl1 committed Jun 26, 2023
1 parent 48110c2 commit 43ec832
Show file tree
Hide file tree
Showing 7 changed files with 233 additions and 35 deletions.
2 changes: 1 addition & 1 deletion pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ type AdditionalDirectory struct {
type Finch struct {
CPUs *int `yaml:"cpus"`
Memory *string `yaml:"memory"`
CredsHelper *string `yaml:"credsHelper"`
CredsHelper *string `yaml:"credsHelper,omitempty"`
// AdditionalDirectories are the work directories that are not supported by default. In macOS, only home directory is supported by default.
// For example, if you want to mount a directory into a container, and that directory is not under your home directory,
// then you'll need to specify this field to add that directory or any ascendant of it as a work directory.
Expand Down
9 changes: 6 additions & 3 deletions pkg/config/nerdctl_config_applier.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,10 @@ func NewNerdctlApplier(dialer fssh.Dialer, fs afero.Fs, privateKeyPath, hostUser
hostUser: hostUser,
}
}

func addLinetoFilePath(fs afero.Fs, profileFilePath string, profStr string, cmd string) (string, error) {
if !strings.Contains(profStr, cmd) {
profBufWithCmd := fmt.Sprintf("%s\n%s\n", profStr, cmd)
profBufWithCmd := fmt.Sprintf("%s\n%s", profStr, cmd)
if err := afero.WriteFile(fs, profileFilePath, []byte(profBufWithCmd), 0o644); err != nil {
return "", fmt.Errorf("failed to write to profile file: %w", err)
}
Expand All @@ -68,11 +69,13 @@ func addLinetoFilePath(fs afero.Fs, profileFilePath string, profStr string, cmd
// [registry nerdctl docs]: https://github.com/containerd/nerdctl/blob/master/docs/registry.md

func updateEnvironment(fs afero.Fs, user string) error {
cmdArr := [4]string{fmt.Sprintf("export DOCKER_CONFIG=\"/Users/%s/.finch\"", user),
cmdArr := [4]string{
fmt.Sprintf("export DOCKER_CONFIG=\"/Users/%s/.finch\"", user),
fmt.Sprintf("[ -L /usr/local/bin/docker-credential-ecr-login ] "+
"|| sudo ln -s /Users/%s/.finch/cred-helpers/docker-credential-ecr-login /usr/local/bin/", user),
fmt.Sprintf("[ -L $HOME/.aws ] || ln -s /Users/%s/.aws $HOME/.aws", user),
fmt.Sprintf("[ -L /root/.aws ] || sudo ln -fs /Users/%s/.aws /root/.aws", user)}
fmt.Sprintf("[ -L /root/.aws ] || sudo ln -fs /Users/%s/.aws /root/.aws", user),
}

profileFilePath := fmt.Sprintf("/home/%s.linux/.bashrc", user)
profBuf, err := afero.ReadFile(fs, profileFilePath)
Expand Down
16 changes: 13 additions & 3 deletions pkg/config/nerdctl_config_applier_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,11 @@ func Test_updateEnvironment(t *testing.T) {
postRunCheck: func(t *testing.T, fs afero.Fs) {
fileBytes, err := afero.ReadFile(fs, "/home/mock_user.linux/.bashrc")
require.NoError(t, err)
assert.Equal(t, []byte("\n"+`export DOCKER_CONFIG="/Users/mock_user/.finch"`+"\n"), fileBytes)
assert.Equal(t,
[]byte("\nexport DOCKER_CONFIG=\"/Users/mock_user/.finch\"\n[ -L /usr/local/bin/docker-credential-ecr-login ] || sudo ln -s "+
"/Users/mock_user/.finch/cred-helpers/docker-credential-ecr-login /usr/local/bin/"+
"\n[ -L $HOME/.aws ] || ln -s /Users/mock_user/.aws $HOME/.aws\n"+
"[ -L /root/.aws ] || sudo ln -fs /Users/mock_user/.aws /root/.aws"), fileBytes)
},
want: nil,
},
Expand All @@ -60,15 +64,21 @@ func Test_updateEnvironment(t *testing.T) {
afero.WriteFile(
fs,
"/home/mock_user.linux/.bashrc",
[]byte(`export DOCKER_CONFIG="/Users/mock_user/.finch"`),
[]byte("export DOCKER_CONFIG=\"/Users/mock_user/.finch\""+"\n"+"[ -L /usr/local/bin/docker-credential-ecr-login ] "+
"|| sudo ln -s /Users/mock_user/.finch/cred-helpers/docker-credential-ecr-login /usr/local/bin/"+
"\n"+"[ -L $HOME/.aws ] || ln -s /Users/mock_user/.aws $HOME/.aws"+"\n"+
"[ -L /root/.aws ] || sudo ln -fs /Users/mock_user/.aws /root/.aws"),
0o644,
),
)
},
postRunCheck: func(t *testing.T, fs afero.Fs) {
fileBytes, err := afero.ReadFile(fs, "/home/mock_user.linux/.bashrc")
require.NoError(t, err)
assert.Equal(t, []byte(`export DOCKER_CONFIG="/Users/mock_user/.finch"`), fileBytes)
assert.Equal(t, []byte(`export DOCKER_CONFIG="/Users/mock_user/.finch"`+"\n"+"[ -L /usr/local/bin/docker-credential-ecr-login ] "+
"|| sudo ln -s /Users/mock_user/.finch/cred-helpers/docker-credential-ecr-login /usr/local/bin/"+
"\n"+"[ -L $HOME/.aws ] || ln -s /Users/mock_user/.aws $HOME/.aws"+"\n"+
"[ -L /root/.aws ] || sudo ln -fs /Users/mock_user/.aws /root/.aws"), fileBytes)
},
want: nil,
},
Expand Down
18 changes: 16 additions & 2 deletions pkg/dependency/credHelper/cred_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ func NewDependencyGroup(
return dependency.NewGroup(deps, description, errMsg)
}

type helperConfig struct {
binaryName string
credHelperUrl string
hash string
installFolder string
finchPath string
}

func newDeps(
execCmdCreator command.Creator,
fs afero.Fs,
Expand All @@ -46,8 +54,14 @@ func newDeps(

credHelperUrl := fmt.Sprintf("https://amazon-ecr-credential-helper-releases.s3.us-east-2.amazonaws.com"+
"/0.7.0/linux-%s/docker-credential-ecr-login", arch)
binaries := newCredHelperBinary(fp, fs, execCmdCreator, logger, fc, user, "docker-credential-ecr-login",
credHelperUrl, "sha256:ff14a4da40d28a2d2d81a12a7c9c36294ddf8e6439780c4ccbc96622991f3714")
installFolder := fmt.Sprintf("/Users/%s/.finch/cred-helpers/", user)
finchPath := fmt.Sprintf("/Users/%s/.finch/", user)
hc := helperConfig{
binaryName: "docker-credential-ecr-login", credHelperUrl: credHelperUrl,
hash: "sha256:ff14a4da40d28a2d2d81a12a7c9c36294ddf8e6439780c4ccbc96622991f3714", installFolder: installFolder,
finchPath: finchPath,
}
binaries := newCredHelperBinary(fp, fs, execCmdCreator, logger, fc, user, hc)
deps = append(deps, dependency.Dependency(binaries))

return deps
Expand Down
41 changes: 15 additions & 26 deletions pkg/dependency/credHelper/cred_helper_binary.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,14 @@ type binaries struct {
l flog.Logger
fc *config.Finch
user string
binaryName string
binaryUrl string
hash string
hcfg helperConfig
}

var _ dependency.Dependency = (*binaries)(nil)

func newCredHelperBinary(fp path.Finch, fs afero.Fs, cmdCreator command.Creator, l flog.Logger, fc *config.Finch,
user string, binaryName string, binaryUrl string, hash string) *binaries {
user string, hcfg helperConfig,
) *binaries {
return &binaries{
// TODO: consider replacing fp with only the strings that are used instead of the entire type
fp: fp,
Expand All @@ -70,21 +69,19 @@ func newCredHelperBinary(fp path.Finch, fs afero.Fs, cmdCreator command.Creator,
l: l,
fc: fc,
user: user,
binaryName: binaryName,
binaryUrl: binaryUrl,
hash: hash,
hcfg: hcfg,
}
}

func updateConfigFile(bin *binaries) error {
cfgPath := fmt.Sprintf("%s%s", bin.finchPath(), "config.json")
cfgPath := fmt.Sprintf("%s%s", bin.hcfg.finchPath, "config.json")
binCfgName := bin.credHelperConfigName()
fileExists, err := afero.Exists(bin.fs, cfgPath)
if err != nil {
return err
}
if !fileExists {
//create file

file, err := bin.fs.Create(cfgPath)
if err != nil {
return err
Expand All @@ -109,7 +106,7 @@ func updateConfigFile(bin *binaries) error {
credsStore := cfg.CredentialsStore
defer fileRead.Close()
if strings.Compare(credsStore, binCfgName) != 0 {
//if credsStore field does not match finch.yaml

//populate credsStore field with correct name
file, err := bin.fs.OpenFile(cfgPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o755)
if err != nil {
Expand All @@ -132,23 +129,15 @@ func updateConfigFile(bin *binaries) error {
}

func (bin *binaries) credHelperConfigName() string {
return strings.Replace(bin.binaryName, "docker-credential-", "", -1)
}

func (bin *binaries) installationPath() string {
return fmt.Sprintf("/Users/%s/.finch/cred-helpers/", bin.user)
}

func (bin *binaries) finchPath() string {
return fmt.Sprintf("/Users/%s/.finch/", bin.user)
return strings.Replace(bin.hcfg.binaryName, "docker-credential-", "", -1)
}

func (bin *binaries) fullInstallPath() string {
return fmt.Sprintf("%s%s", bin.installationPath(), bin.binaryName)
return fmt.Sprintf("%s%s", bin.hcfg.installFolder, bin.hcfg.binaryName)
}

func (bin *binaries) Installed() bool {
dirExists, err := afero.DirExists(bin.fs, bin.installationPath())
dirExists, err := afero.DirExists(bin.fs, bin.hcfg.installFolder)
if err != nil {
bin.l.Errorf("failed to get status of binaries directory: %v", err)
return false
Expand All @@ -175,7 +164,7 @@ func (bin *binaries) Installed() bool {
return false
}
defer file.Close()
if strings.Compare(hash.String(), bin.hash) != 0 {
if strings.Compare(hash.String(), bin.hcfg.hash) != 0 {
bin.l.Error("Hash of the installed credential helper binary does not match")
err := bin.fs.Remove(bin.fullInstallPath())
if err != nil {
Expand All @@ -195,18 +184,18 @@ func (bin *binaries) Install() error {
return nil
}
bin.l.Info("Installing credential helper")
mkdirCmd := bin.cmdCreator.Create("mkdir", "-p", bin.installationPath())
mkdirCmd := bin.cmdCreator.Create("mkdir", "-p", bin.hcfg.installFolder)
_, err := mkdirCmd.Output()
if err != nil {
return fmt.Errorf("error creating installation directory %s, err: %w", bin.installationPath(), err)
return fmt.Errorf("error creating installation directory %s, err: %w", bin.hcfg.installFolder, err)
}

curlCmd := bin.cmdCreator.Create("curl", "--retry", "5", "--retry-max-time", "30", "--url",
bin.binaryUrl, "--output", bin.fullInstallPath())
bin.hcfg.credHelperUrl, "--output", bin.fullInstallPath())

_, err = curlCmd.Output()
if err != nil {
return fmt.Errorf("error installation binary %s, err: %w", bin.binaryName, err)
return fmt.Errorf("error installation binary %s, err: %w", bin.hcfg.binaryName, err)
}
err = bin.fs.Chmod(bin.fullInstallPath(), 0o755)
if err != nil {
Expand Down
153 changes: 153 additions & 0 deletions pkg/dependency/credHelper/cred_helper_binary_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

// Ensures that the binaries required for networking are installed in a privileged location.
// More information here: https://github.com/lima-vm/socket_vmnet
package credHelper

import (
"github.com/runfinch/finch/pkg/config"
"io/fs"
"testing"

"github.com/runfinch/finch/pkg/mocks"
"github.com/runfinch/finch/pkg/path"

"github.com/golang/mock/gomock"
"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

const (
mockFinchPathString = "mock_prefix"
mockFinchPath = path.Finch(mockFinchPathString)
)

func Test_credHelperConfigName(t *testing.T) {
t.Parallel()

got := newCredHelperBinary("", nil, nil, nil, nil, "user",
helperConfig{"docker-credential-cred-helper", "", "",
"", ""}).credHelperConfigName()
assert.Equal(t, "cred-helper", got)
}
func Test_fullInstallPath(t *testing.T) {
t.Parallel()

got := newCredHelperBinary("", nil, nil, nil, nil, "user",
helperConfig{"docker-credential-cred-helper", "", "", "/folder/",
""}).fullInstallPath()
assert.Equal(t, "/folder/docker-credential-cred-helper", got)
}

func TestBinaries_Installed(t *testing.T) {
t.Parallel()

testCases := []struct {
name string
mockSvc func(t *testing.T, mFs afero.Fs, l *mocks.Logger)
want bool
}{
{
name: "happy path",
mockSvc: func(t *testing.T, mFs afero.Fs, l *mocks.Logger) {
require.NoError(t, mFs.MkdirAll("/mock_prefix/cred-helpers/", fs.ModeDir))
fileData := []byte("")
_, err := mFs.Create("mock_prefix/cred-helpers/docker-credential-binary")
require.NoError(t, err)
err = afero.WriteFile(mFs, "mock_prefix/cred-helpers/docker-credential-binary",
fileData, 0o666)

require.NoError(t, err)
},
want: true,
},
{
name: "installation path doesn't exist",
mockSvc: func(t *testing.T, mFs afero.Fs, l *mocks.Logger) {
},
want: false,
}}

for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()

ctrl := gomock.NewController(t)
mFs := afero.NewMemMapFs()
l := mocks.NewLogger(ctrl)
tc.mockSvc(t, mFs, l)
hc := helperConfig{"docker-credential-binary", "",
"sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "mock_prefix/cred-helpers/",
"mock_prefix/.finch/"}
//hash of an empty file
got := newCredHelperBinary(mockFinchPath, mFs, nil, l, nil, "", hc).Installed()

assert.Equal(t, tc.want, got)
})
}
}

func TestBinaries_Install(t *testing.T) {
t.Parallel()

testCases := []struct {
name string
mockSvc func(
l *mocks.Logger,
cmd *mocks.Command,
creator *mocks.CommandCreator,
mFs afero.Fs)
want error
}{
{
name: "happy path",
mockSvc: func(l *mocks.Logger, cmd *mocks.Command, creator *mocks.CommandCreator, mFs afero.Fs) {
_, err := mFs.Create("mock_prefix/cred-helpers/docker-credential-ecr-login")
require.NoError(t, err)
cmd.EXPECT().Output().Times(2)
l.EXPECT().Info("Installing credential helper")
creator.EXPECT().Create("mkdir", "-p", "mock_prefix/cred-helpers/").Return(cmd)
creator.EXPECT().Create("curl", "--retry", "5", "--retry-max-time", "30", "--url",
"https://amazon-ecr-credential-helper-releases.s3.us-east-2.amazonaws.com"+
"/0.7.0/linux-arm64/docker-credential-ecr-login", "--output", "mock_prefix/cred-helpers/docker-credential-ecr-login").Return(cmd)
},
want: nil,
},
}

for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()

ctrl := gomock.NewController(t)
cmd := mocks.NewCommand(ctrl)
mFs := afero.NewMemMapFs()
l := mocks.NewLogger(ctrl)
creator := mocks.NewCommandCreator(ctrl)
tc.mockSvc(l, cmd, creator, mFs)

credHelperUrl := "https://amazon-ecr-credential-helper-releases.s3.us-east-2.amazonaws.com" +
"/0.7.0/linux-arm64/docker-credential-ecr-login"

hc := helperConfig{"docker-credential-ecr-login", credHelperUrl,
"sha256:ff14a4da40d28a2d2d81a12a7c9c36294ddf8e6439780c4ccbc96622991f3714",
"mock_prefix/cred-helpers/",
"mock_prefix/.finch/"}
fc := "ecr-login"
got := newCredHelperBinary(mockFinchPath, mFs, creator, l, &config.Finch{CredsHelper: &fc}, "", hc).Install()
assert.Equal(t, tc.want, got)
})
}
}

func TestBinaries_RequiresRoot(t *testing.T) {
t.Parallel()

got := newCredHelperBinary(mockFinchPath, nil, nil, nil, nil, "",
helperConfig{}).RequiresRoot()
assert.Equal(t, false, got)
}
Loading

0 comments on commit 43ec832

Please sign in to comment.