Allow relative and ~ path for container mount
This patch allow to specify relative and home (~/) path for container mounts.src field. In case if specified path is not absolute, it will be created by following pattern: 'targetPath+mounts.src'; in case if path contains ~/ - it will be properly expanded (it will allow us to mount ~/.airship working directory). Change-Id: I878094371a2bc4e48216b1d076e466e3d29a86f6 Signed-off-by: Ruslan Aliev <raliev@mirantis.com> Relates-To: #484 Closes: #484
This commit is contained in:
parent
36d5f9822a
commit
92ce88fc29
@ -47,7 +47,7 @@ type GenericContainer struct {
|
|||||||
// Holds container configuration
|
// Holds container configuration
|
||||||
Spec GenericContainerSpec `json:"spec,omitempty"`
|
Spec GenericContainerSpec `json:"spec,omitempty"`
|
||||||
|
|
||||||
// Config will be passed to stdin of the container togather with other objects
|
// Config will be passed to stdin of the container together with other objects
|
||||||
// more information on easy ways to consume the config can be found here
|
// more information on easy ways to consume the config can be found here
|
||||||
// https://googlecontainertools.github.io/kpt/guides/producer/functions/golang/
|
// https://googlecontainertools.github.io/kpt/guides/producer/functions/golang/
|
||||||
Config string `json:"config,omitempty"`
|
Config string `json:"config,omitempty"`
|
||||||
@ -68,7 +68,7 @@ type GenericContainerSpec struct {
|
|||||||
// Supported types are "airship" and "krm"
|
// Supported types are "airship" and "krm"
|
||||||
Type GenericContainerType `json:"type,omitempty"`
|
Type GenericContainerType `json:"type,omitempty"`
|
||||||
|
|
||||||
// Ariship container spec
|
// Airship container spec
|
||||||
Airship AirshipContainerSpec `json:"airship,omitempty"`
|
Airship AirshipContainerSpec `json:"airship,omitempty"`
|
||||||
|
|
||||||
// KRM container function spec
|
// KRM container function spec
|
||||||
@ -121,6 +121,9 @@ type StorageMount struct {
|
|||||||
// For named volumes, this is the name of the volume.
|
// For named volumes, this is the name of the volume.
|
||||||
// For anonymous volumes, this field is omitted (empty string).
|
// For anonymous volumes, this field is omitted (empty string).
|
||||||
// For bind mounts, this is the path to the file or directory on the host.
|
// For bind mounts, this is the path to the file or directory on the host.
|
||||||
|
// If provided path is relative, it will be expanded to absolute one by following patterns:
|
||||||
|
// - if starts with '~/' or contains only '~' : $HOME + Src
|
||||||
|
// - in other cases : TargetPath + Src
|
||||||
Src string `json:"src,omitempty" yaml:"src,omitempty"`
|
Src string `json:"src,omitempty" yaml:"src,omitempty"`
|
||||||
|
|
||||||
// The path where the file or directory is mounted in the container.
|
// The path where the file or directory is mounted in the container.
|
||||||
|
@ -20,6 +20,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
// TODO this small library needs to be moved to airshipctl and extended
|
// TODO this small library needs to be moved to airshipctl and extended
|
||||||
@ -33,6 +34,7 @@ import (
|
|||||||
|
|
||||||
"opendev.org/airship/airshipctl/pkg/api/v1alpha1"
|
"opendev.org/airship/airshipctl/pkg/api/v1alpha1"
|
||||||
"opendev.org/airship/airshipctl/pkg/log"
|
"opendev.org/airship/airshipctl/pkg/log"
|
||||||
|
"opendev.org/airship/airshipctl/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ClientV1Alpha1 provides airship generic container API
|
// ClientV1Alpha1 provides airship generic container API
|
||||||
@ -46,13 +48,15 @@ type ClientV1Alpha1FactoryFunc func(
|
|||||||
resultsDir string,
|
resultsDir string,
|
||||||
input io.Reader,
|
input io.Reader,
|
||||||
output io.Writer,
|
output io.Writer,
|
||||||
conf *v1alpha1.GenericContainer) ClientV1Alpha1
|
conf *v1alpha1.GenericContainer,
|
||||||
|
targetPath string) ClientV1Alpha1
|
||||||
|
|
||||||
type clientV1Alpha1 struct {
|
type clientV1Alpha1 struct {
|
||||||
resultsDir string
|
resultsDir string
|
||||||
input io.Reader
|
input io.Reader
|
||||||
output io.Writer
|
output io.Writer
|
||||||
conf *v1alpha1.GenericContainer
|
conf *v1alpha1.GenericContainer
|
||||||
|
targetPath string
|
||||||
|
|
||||||
containerFunc containerFunc
|
containerFunc containerFunc
|
||||||
}
|
}
|
||||||
@ -64,18 +68,22 @@ func NewClientV1Alpha1(
|
|||||||
resultsDir string,
|
resultsDir string,
|
||||||
input io.Reader,
|
input io.Reader,
|
||||||
output io.Writer,
|
output io.Writer,
|
||||||
conf *v1alpha1.GenericContainer) ClientV1Alpha1 {
|
conf *v1alpha1.GenericContainer,
|
||||||
|
targetPath string) ClientV1Alpha1 {
|
||||||
return &clientV1Alpha1{
|
return &clientV1Alpha1{
|
||||||
resultsDir: resultsDir,
|
resultsDir: resultsDir,
|
||||||
output: output,
|
output: output,
|
||||||
input: input,
|
input: input,
|
||||||
conf: conf,
|
conf: conf,
|
||||||
containerFunc: NewContainer,
|
containerFunc: NewContainer,
|
||||||
|
targetPath: targetPath,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run will peform container run action based on the configuration
|
// Run will perform container run action based on the configuration
|
||||||
func (c *clientV1Alpha1) Run() error {
|
func (c *clientV1Alpha1) Run() error {
|
||||||
|
// expand Src paths for mount if they are relative
|
||||||
|
ExpandSourceMounts(c.conf.Spec.StorageMounts, c.targetPath)
|
||||||
// set default runtime
|
// set default runtime
|
||||||
switch c.conf.Spec.Type {
|
switch c.conf.Spec.Type {
|
||||||
case v1alpha1.GenericContainerTypeAirship, "":
|
case v1alpha1.GenericContainerTypeAirship, "":
|
||||||
@ -83,7 +91,7 @@ func (c *clientV1Alpha1) Run() error {
|
|||||||
case v1alpha1.GenericContainerTypeKrm:
|
case v1alpha1.GenericContainerTypeKrm:
|
||||||
return c.runKRM()
|
return c.runKRM()
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("uknown generic container type %s", c.conf.Spec.Type)
|
return fmt.Errorf("unknown generic container type %s", c.conf.Spec.Type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -275,3 +283,16 @@ func convertDockerMount(airMounts []v1alpha1.StorageMount) (mounts []Mount) {
|
|||||||
}
|
}
|
||||||
return mounts
|
return mounts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExpandSourceMounts converts relative paths into absolute ones
|
||||||
|
func ExpandSourceMounts(storageMounts []v1alpha1.StorageMount, targetPath string) {
|
||||||
|
for i, mount := range storageMounts {
|
||||||
|
// Try to expand Src path
|
||||||
|
path := util.ExpandTilde(mount.Src)
|
||||||
|
// If still relative - add targetPath prefix
|
||||||
|
if !filepath.IsAbs(path) {
|
||||||
|
path = filepath.Join(targetPath, mount.Src)
|
||||||
|
}
|
||||||
|
storageMounts[i].Src = path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -19,6 +19,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
@ -29,6 +30,7 @@ import (
|
|||||||
"opendev.org/airship/airshipctl/pkg/api/v1alpha1"
|
"opendev.org/airship/airshipctl/pkg/api/v1alpha1"
|
||||||
"opendev.org/airship/airshipctl/pkg/document"
|
"opendev.org/airship/airshipctl/pkg/document"
|
||||||
"opendev.org/airship/airshipctl/pkg/phase/ifc"
|
"opendev.org/airship/airshipctl/pkg/phase/ifc"
|
||||||
|
"opendev.org/airship/airshipctl/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func bundlePathToInput(t *testing.T, bundlePath string) io.Reader {
|
func bundlePathToInput(t *testing.T, bundlePath string) io.Reader {
|
||||||
@ -56,7 +58,7 @@ func TestGenericContainer(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "error unknown container type",
|
name: "error unknown container type",
|
||||||
expectedErr: "uknown generic container type",
|
expectedErr: "unknown generic container type",
|
||||||
containerAPI: &v1alpha1.GenericContainer{
|
containerAPI: &v1alpha1.GenericContainer{
|
||||||
Spec: v1alpha1.GenericContainerSpec{
|
Spec: v1alpha1.GenericContainerSpec{
|
||||||
Type: "unknown",
|
Type: "unknown",
|
||||||
@ -65,7 +67,7 @@ func TestGenericContainer(t *testing.T) {
|
|||||||
execFunc: NewContainer,
|
execFunc: NewContainer,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "error kyaml cant parse config",
|
name: "error kyaml can't parse config",
|
||||||
containerAPI: &v1alpha1.GenericContainer{
|
containerAPI: &v1alpha1.GenericContainer{
|
||||||
Spec: v1alpha1.GenericContainerSpec{
|
Spec: v1alpha1.GenericContainerSpec{
|
||||||
Type: v1alpha1.GenericContainerTypeKrm,
|
Type: v1alpha1.GenericContainerTypeKrm,
|
||||||
@ -145,6 +147,11 @@ func TestGenericContainer(t *testing.T) {
|
|||||||
Src: "test",
|
Src: "test",
|
||||||
DstPath: "/mount",
|
DstPath: "/mount",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
MountType: "bind",
|
||||||
|
Src: "~/test",
|
||||||
|
DstPath: "/mnt",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Config: `kind: ConfigMap`,
|
Config: `kind: ConfigMap`,
|
||||||
@ -234,6 +241,42 @@ func TestGenericContainer(t *testing.T) {
|
|||||||
|
|
||||||
// Dummy test to keep up with coverage.
|
// Dummy test to keep up with coverage.
|
||||||
func TestNewClientV1alpha1(t *testing.T) {
|
func TestNewClientV1alpha1(t *testing.T) {
|
||||||
client := NewClientV1Alpha1("", nil, nil, v1alpha1.DefaultGenericContainer())
|
client := NewClientV1Alpha1("", nil, nil, v1alpha1.DefaultGenericContainer(), "")
|
||||||
require.NotNil(t, client)
|
require.NotNil(t, client)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestExpandSourceMounts(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
inputMounts []v1alpha1.StorageMount
|
||||||
|
targetPath string
|
||||||
|
expectedMounts []v1alpha1.StorageMount
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "absolute path",
|
||||||
|
inputMounts: []v1alpha1.StorageMount{{Src: "/test"}},
|
||||||
|
targetPath: "/target-path",
|
||||||
|
expectedMounts: []v1alpha1.StorageMount{{Src: "/test"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tilde path",
|
||||||
|
inputMounts: []v1alpha1.StorageMount{{Src: "~/test"}},
|
||||||
|
targetPath: "/target-path",
|
||||||
|
expectedMounts: []v1alpha1.StorageMount{{Src: util.ExpandTilde("~/test")}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "relative path",
|
||||||
|
inputMounts: []v1alpha1.StorageMount{{Src: "test"}},
|
||||||
|
targetPath: "/target-path",
|
||||||
|
expectedMounts: []v1alpha1.StorageMount{{Src: filepath.Join("/target-path", "test")}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
tt := tt
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
ExpandSourceMounts(tt.inputMounts, tt.targetPath)
|
||||||
|
require.Equal(t, tt.expectedMounts, tt.inputMounts)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -187,7 +187,7 @@ func (b *Builder) trySource(clusterID, dstContext string, source v1alpha1.Kubeco
|
|||||||
getter = b.fromClusterAPI(clusterID, source.ClusterAPI)
|
getter = b.fromClusterAPI(clusterID, source.ClusterAPI)
|
||||||
default:
|
default:
|
||||||
// TODO add validation for fast fails to clustermap interface instead of this
|
// TODO add validation for fast fails to clustermap interface instead of this
|
||||||
return nil, &ErrUknownKubeconfigSourceType{Type: string(source.Type)}
|
return nil, &ErrUnknownKubeconfigSourceType{Type: string(source.Type)}
|
||||||
}
|
}
|
||||||
kubeBytes, err := getter()
|
kubeBytes, err := getter()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -40,11 +40,11 @@ func IsErrAllSourcesFailedErr(err error) bool {
|
|||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrUknownKubeconfigSourceType returned type of kubeconfig source is unknown
|
// ErrUnknownKubeconfigSourceType returned type of kubeconfig source is unknown
|
||||||
type ErrUknownKubeconfigSourceType struct {
|
type ErrUnknownKubeconfigSourceType struct {
|
||||||
Type string
|
Type string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ErrUknownKubeconfigSourceType) Error() string {
|
func (e *ErrUnknownKubeconfigSourceType) Error() string {
|
||||||
return fmt.Sprintf("unknown source type %s", e.Type)
|
return fmt.Sprintf("unknown source type %s", e.Type)
|
||||||
}
|
}
|
||||||
|
@ -113,10 +113,7 @@ func FromSecret(c client.Interface, o *client.GetKubeconfigOptions) KubeSourceFu
|
|||||||
// FromFile returns KubeSource type, uses path to kubeconfig on FS as source to construct kubeconfig object
|
// FromFile returns KubeSource type, uses path to kubeconfig on FS as source to construct kubeconfig object
|
||||||
func FromFile(path string, fSys fs.FileSystem) KubeSourceFunc {
|
func FromFile(path string, fSys fs.FileSystem) KubeSourceFunc {
|
||||||
return func() ([]byte, error) {
|
return func() ([]byte, error) {
|
||||||
expandedPath, err := util.ExpandTilde(path)
|
expandedPath := util.ExpandTilde(path)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if fSys == nil {
|
if fSys == nil {
|
||||||
fSys = fs.NewDocumentFs()
|
fSys = fs.NewDocumentFs()
|
||||||
}
|
}
|
||||||
|
@ -124,7 +124,7 @@ func (c *ContainerExecutor) Run(evtCh chan events.Event, opts ifc.RunOptions) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = c.ClientFunc(c.ResultsDir, input, output, c.Container).Run()
|
err = c.ClientFunc(c.ResultsDir, input, output, c.Container, c.Helper.TargetPath()).Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
handleError(evtCh, err)
|
handleError(evtCh, err)
|
||||||
return
|
return
|
||||||
|
@ -48,11 +48,15 @@ const (
|
|||||||
src: /home/ubuntu/mounts
|
src: /home/ubuntu/mounts
|
||||||
dst: /my-mounts
|
dst: /my-mounts
|
||||||
rw: true
|
rw: true
|
||||||
|
- type: bind
|
||||||
|
src: ~/mounts
|
||||||
|
dst: /my-tilde-mount
|
||||||
|
rw: true
|
||||||
config: |
|
config: |
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: ConfigMap
|
kind: ConfigMap
|
||||||
metadata:
|
metadata:
|
||||||
name: my-srange-name
|
name: my-strange-name
|
||||||
data:
|
data:
|
||||||
cmd: encrypt
|
cmd: encrypt
|
||||||
unencrypted-regex: '^(kind|apiVersion|group|metadata)$'`
|
unencrypted-regex: '^(kind|apiVersion|group|metadata)$'`
|
||||||
@ -114,7 +118,7 @@ func TestGenericContainer(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "error unknown container type",
|
name: "error unknown container type",
|
||||||
expectedErr: "uknown generic container type",
|
expectedErr: "unknown generic container type",
|
||||||
containerAPI: &v1alpha1.GenericContainer{
|
containerAPI: &v1alpha1.GenericContainer{
|
||||||
Spec: v1alpha1.GenericContainerSpec{
|
Spec: v1alpha1.GenericContainerSpec{
|
||||||
Type: "unknown",
|
Type: "unknown",
|
||||||
@ -202,7 +206,7 @@ type: Opaque
|
|||||||
ch := make(chan events.Event)
|
ch := make(chan events.Event)
|
||||||
go container.Run(ch, tt.runOptions)
|
go container.Run(ch, tt.runOptions)
|
||||||
|
|
||||||
var actualEvt []events.Event
|
actualEvt := make([]events.Event, 0)
|
||||||
for evt := range ch {
|
for evt := range ch {
|
||||||
actualEvt = append(actualEvt, evt)
|
actualEvt = append(actualEvt, evt)
|
||||||
}
|
}
|
||||||
@ -275,7 +279,7 @@ type fakeKubeConfig struct {
|
|||||||
|
|
||||||
func (k fakeKubeConfig) GetFile() (string, kubeconfig.Cleanup, error) { return k.getFile() }
|
func (k fakeKubeConfig) GetFile() (string, kubeconfig.Cleanup, error) { return k.getFile() }
|
||||||
func (k fakeKubeConfig) Write(_ io.Writer) error { return nil }
|
func (k fakeKubeConfig) Write(_ io.Writer) error { return nil }
|
||||||
func (k fakeKubeConfig) WriteFile(path string) error { return nil }
|
func (k fakeKubeConfig) WriteFile(_ string) error { return nil }
|
||||||
func (k fakeKubeConfig) WriteTempFile(dumpRoot string) (string, kubeconfig.Cleanup, error) {
|
func (k fakeKubeConfig) WriteTempFile(_ string) (string, kubeconfig.Cleanup, error) {
|
||||||
return k.getFile()
|
return k.getFile()
|
||||||
}
|
}
|
||||||
|
@ -1,26 +0,0 @@
|
|||||||
/*
|
|
||||||
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
|
|
||||||
|
|
||||||
https://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 util
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
// ErrCantExpandTildePath returned when malformed path is passed to ExpandTilde function
|
|
||||||
type ErrCantExpandTildePath struct {
|
|
||||||
Path string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *ErrCantExpandTildePath) Error() string {
|
|
||||||
return fmt.Sprintf("cannot expand user-specific home dir: %s", e.Path)
|
|
||||||
}
|
|
@ -17,6 +17,7 @@ package util
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// UserHomeDir is a utility function that wraps os.UserHomeDir and returns no
|
// UserHomeDir is a utility function that wraps os.UserHomeDir and returns no
|
||||||
@ -31,21 +32,18 @@ func UserHomeDir() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ExpandTilde expands the path to include the home directory if the path
|
// ExpandTilde expands the path to include the home directory if the path
|
||||||
// is prefixed with `~`. If it isn't prefixed with `~`, the path is
|
// is prefixed with `~`. If it isn't prefixed with `~` or has no slash after tilde,
|
||||||
// returned as-is.
|
// the path is returned as-is.
|
||||||
// Original source code: https://github.com/mitchellh/go-homedir/blob/master/homedir.go#L55-L77
|
func ExpandTilde(path string) string {
|
||||||
func ExpandTilde(path string) (string, error) {
|
// Just tilde - return current $HOME dir
|
||||||
if len(path) == 0 {
|
if path == "~" {
|
||||||
return path, nil
|
return UserHomeDir()
|
||||||
|
}
|
||||||
|
// If path starts with ~/ - expand it
|
||||||
|
if strings.HasPrefix(path, "~/") {
|
||||||
|
return filepath.Join(UserHomeDir(), path[1:])
|
||||||
}
|
}
|
||||||
|
|
||||||
if path[0] != '~' {
|
// empty strings, absolute paths, ~<(dir/file)name> return as-is
|
||||||
return path, nil
|
return path
|
||||||
}
|
|
||||||
|
|
||||||
if len(path) > 1 && path[1] != '/' {
|
|
||||||
return "", &ErrCantExpandTildePath{}
|
|
||||||
}
|
|
||||||
|
|
||||||
return filepath.Join(UserHomeDir(), path[1:]), nil
|
|
||||||
}
|
}
|
||||||
|
@ -16,11 +16,11 @@ package util_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"path"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"opendev.org/airship/airshipctl/pkg/util"
|
"opendev.org/airship/airshipctl/pkg/util"
|
||||||
"opendev.org/airship/airshipctl/testutil"
|
"opendev.org/airship/airshipctl/testutil"
|
||||||
@ -45,33 +45,42 @@ func setHome(path string) (resetHome func()) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestExpandTilde(t *testing.T) {
|
func TestExpandTilde(t *testing.T) {
|
||||||
t.Run("success home path", func(t *testing.T) {
|
tests := []struct {
|
||||||
airshipDir := ".airship"
|
name string
|
||||||
dir := filepath.Join("~", airshipDir)
|
input string
|
||||||
expandedDir, err := util.ExpandTilde(dir)
|
expectedOut string
|
||||||
assert.NoError(t, err)
|
}{
|
||||||
homedir := path.Join(util.UserHomeDir(), airshipDir)
|
{
|
||||||
assert.Equal(t, homedir, expandedDir)
|
name: "expand home path",
|
||||||
})
|
input: "~/.airship",
|
||||||
|
expectedOut: filepath.Join(util.UserHomeDir(), "~/.airship"[1:]),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "expand just ~",
|
||||||
|
input: "~",
|
||||||
|
expectedOut: util.UserHomeDir(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty path",
|
||||||
|
input: "",
|
||||||
|
expectedOut: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "absolute path",
|
||||||
|
input: "/.airship",
|
||||||
|
expectedOut: "/.airship",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tilde path",
|
||||||
|
input: "~.airship",
|
||||||
|
expectedOut: "~.airship",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
t.Run("success nothing to expand", func(t *testing.T) {
|
for _, tt := range tests {
|
||||||
dir := "/home/ubuntu/.airship"
|
tt := tt
|
||||||
expandedDir, err := util.ExpandTilde(dir)
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
require.Equal(t, tt.expectedOut, util.ExpandTilde(tt.input))
|
||||||
assert.Equal(t, dir, expandedDir)
|
})
|
||||||
})
|
}
|
||||||
|
|
||||||
t.Run("error malformed path", func(t *testing.T) {
|
|
||||||
malformedDir := "~.home/ubuntu/.airship"
|
|
||||||
expandedDir, err := util.ExpandTilde(malformedDir)
|
|
||||||
assert.Error(t, err)
|
|
||||||
assert.Equal(t, "", expandedDir)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("success empty path", func(t *testing.T) {
|
|
||||||
emptyDir := ""
|
|
||||||
expandedDir, err := util.ExpandTilde(emptyDir)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, emptyDir, expandedDir)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user