diff --git a/go.mod b/go.mod index 43165e2351a..7b789ca1232 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,8 @@ require ( github.com/hinshun/vt10x v0.0.0-20180809195222-d55458df857c github.com/onsi/ginkgo v1.10.1 github.com/onsi/gomega v1.7.0 + github.com/pkg/errors v0.8.1 // indirect + github.com/spf13/afero v1.2.2 github.com/spf13/cobra v0.0.5 github.com/stretchr/testify v1.4.0 gopkg.in/yaml.v3 v3.0.0-20190905181640-827449938966 diff --git a/go.sum b/go.sum index 6cb69a53bb8..f529c3dbae1 100644 --- a/go.sum +++ b/go.sum @@ -77,6 +77,7 @@ github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -87,6 +88,8 @@ github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= diff --git a/internal/pkg/archer/workspace.go b/internal/pkg/archer/workspace.go new file mode 100644 index 00000000000..3f82b577d87 --- /dev/null +++ b/internal/pkg/archer/workspace.go @@ -0,0 +1,24 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package archer + +// WorkspaceSummary is a description of what's associated with this workspace. +type WorkspaceSummary struct { + ProjectName string `yaml:"project"` +} + +// Workspace can bootstrap a workspace with a manifest directory and workspace summary +// and it can manage manifest files. +type Workspace interface { + ManifestIO + Create(projectName string) error + Summary() (*WorkspaceSummary, error) +} + +// ManifestIO can read, write and list local manifest files. +type ManifestIO interface { + WriteManifest(manifestBlob []byte, applicationName string) error + ReadManifestFile(manifestFileName string) ([]byte, error) + ListManifestFiles() ([]string, error) +} diff --git a/internal/pkg/workspace/errors.go b/internal/pkg/workspace/errors.go new file mode 100644 index 00000000000..3875bc8e65b --- /dev/null +++ b/internal/pkg/workspace/errors.go @@ -0,0 +1,46 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package workspace + +import "fmt" + +// ErrWorkspaceNotFound means we couldn't locate a workspace root. +type ErrWorkspaceNotFound struct { + CurrentDirectory string + ManifestDirectoryName string + NumberOfLevelsChecked int +} + +func (e *ErrWorkspaceNotFound) Error() string { + return fmt.Sprintf("couldn't find a directory called %s up to %d levels up from %s", + e.ManifestDirectoryName, + e.NumberOfLevelsChecked, + e.CurrentDirectory) +} + +// ErrNoProjectAssociated means we couldn't locate a workspace summary file. +type ErrNoProjectAssociated struct{} + +func (e *ErrNoProjectAssociated) Error() string { + return fmt.Sprint("couldn't find a project associated with this workspace") +} + +// ErrWorkspaceHasExistingProject means we tried to create a workspace for a project +// but it already belongs to another project. +type ErrWorkspaceHasExistingProject struct { + ExistingProjectName string +} + +func (e *ErrWorkspaceHasExistingProject) Error() string { + return fmt.Sprintf("this workspace is already registered with project %s", e.ExistingProjectName) +} + +// ErrManifestNotFound means we we couldn't find a manifest in the manifest root. +type ErrManifestNotFound struct { + ManifestName string +} + +func (e *ErrManifestNotFound) Error() string { + return fmt.Sprintf("manifest file %s does not exists", e.ManifestName) +} diff --git a/internal/pkg/workspace/workspace.go b/internal/pkg/workspace/workspace.go new file mode 100644 index 00000000000..07045eba395 --- /dev/null +++ b/internal/pkg/workspace/workspace.go @@ -0,0 +1,234 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +// Package workspace contains the service to manage a user's local workspace. This includes +// creating a manifest directory, reading and writing a summary file +// to that directory and managing manifest files (reading, writing and listing). +// The typical workspace will be structured like: +// . +// ├── ecs (manifest directory) +// │ ├── .project (workspace summary) +// │ └── my-app.yml (manifest) +// └── my-app (customer application) +// +package workspace + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/aws/PRIVATE-amazon-ecs-archer/internal/pkg/archer" + "github.com/spf13/afero" + "gopkg.in/yaml.v3" +) + +const ( + workspaceSummaryFileName = ".project" + manifestDirectoryName = "ecs" + maximumParentDirsToSearch = 5 + manifestFileSuffix = "-app.yml" +) + +// Service manages a local workspace, including creating and managing manifest files. +type Service struct { + workingDir string + manifestDir string + fsUtils *afero.Afero +} + +// New returns a workspace Service, used for reading and writing to +// user's local workspace. +func New() (*Service, error) { + appFs := afero.NewOsFs() + fsUtils := &afero.Afero{Fs: appFs} + + workingDir, err := os.Getwd() + if err != nil { + return nil, err + } + ws := Service{ + workingDir: workingDir, + fsUtils: fsUtils, + } + + return &ws, nil +} + +// Create creates the manifest directory (if it doesn't already exist) in +// the current working directory, and saves a summary in the manifest +// directory with the project name. +func (ws *Service) Create(projectName string) error { + // Create a manifest directory, if one doesn't exist + if createDirErr := ws.createManifestDirectory(); createDirErr != nil { + return createDirErr + } + + // Grab an existing workspace summary, if one exists. + existingWorkspaceSummary, existingWorkspaceSummaryErr := ws.Summary() + + if existingWorkspaceSummaryErr == nil { + // If a summary exists, but is registered to a different project, throw an error. + if existingWorkspaceSummary.ProjectName != projectName { + return &ErrWorkspaceHasExistingProject{ExistingProjectName: existingWorkspaceSummary.ProjectName} + } + // Otherwise our work is all done. + return nil + } + + // If there isn't an existing workspace summary, create it. + var noProjectFound *ErrNoProjectAssociated + if errors.As(existingWorkspaceSummaryErr, &noProjectFound) { + return ws.writeSummary(projectName) + } + + return existingWorkspaceSummaryErr +} + +// Summary returns a summary of the workspace - including the project name. +func (ws *Service) Summary() (*archer.WorkspaceSummary, error) { + summaryPath, err := ws.summaryPath() + if err != nil { + return nil, err + } + summaryFileExists, err := ws.fsUtils.Exists(summaryPath) + if summaryFileExists { + value, err := ws.fsUtils.ReadFile(summaryPath) + if err != nil { + return nil, err + } + wsSummary := archer.WorkspaceSummary{} + return &wsSummary, yaml.Unmarshal(value, &wsSummary) + } + return nil, &ErrNoProjectAssociated{} +} + +func (ws *Service) writeSummary(projectName string) error { + summaryPath, err := ws.summaryPath() + if err != nil { + return err + } + + workspaceSummary := archer.WorkspaceSummary{ + ProjectName: projectName, + } + + serializedWorkspaceSummary, err := yaml.Marshal(workspaceSummary) + + if err != nil { + return err + } + return ws.fsUtils.WriteFile(summaryPath, serializedWorkspaceSummary, 0644) +} + +func (ws *Service) summaryPath() (string, error) { + manifestPath, err := ws.manifestDirectoryPath() + if err != nil { + return "", err + } + workspaceSummaryPath := filepath.Join(manifestPath, workspaceSummaryFileName) + return workspaceSummaryPath, nil +} + +func (ws *Service) createManifestDirectory() error { + // First check to see if a manifest directory already exists + dirExists, err := ws.fsUtils.DirExists(filepath.Join(ws.workingDir, manifestDirectoryName)) + if err != nil { + return err + } + // If a manifest directory doesn't exist, create it - otherwise fast succeed + if !dirExists { + return ws.fsUtils.Mkdir(manifestDirectoryName, 0755) + } + return nil +} + +func (ws *Service) manifestDirectoryPath() (string, error) { + if ws.manifestDir != "" { + return ws.manifestDir, nil + } + // Are we in the manifest directory? + inEcsDir := filepath.Base(ws.workingDir) == manifestDirectoryName + if inEcsDir { + ws.manifestDir = ws.workingDir + return ws.manifestDir, nil + } + + searchingDir := ws.workingDir + for try := 0; try < maximumParentDirsToSearch; try++ { + currentDirectoryPath := filepath.Join(searchingDir, manifestDirectoryName) + inCurrentDirPath, err := ws.fsUtils.DirExists(currentDirectoryPath) + if err != nil { + return "", err + } + if inCurrentDirPath { + ws.manifestDir = currentDirectoryPath + return ws.manifestDir, nil + } + searchingDir = filepath.Dir(searchingDir) + } + return "", &ErrWorkspaceNotFound{ + CurrentDirectory: ws.workingDir, + ManifestDirectoryName: manifestDirectoryName, + NumberOfLevelsChecked: maximumParentDirsToSearch, + } +} + +// ListManifestFiles returns a list of all the local manifests filenames. +func (ws *Service) ListManifestFiles() ([]string, error) { + manifestDir, err := ws.manifestDirectoryPath() + if err != nil { + return nil, err + } + manifestDirFiles, err := ws.fsUtils.ReadDir(manifestDir) + if err != nil { + return nil, err + } + + var manifestFiles []string + for _, file := range manifestDirFiles { + if !file.IsDir() && strings.HasSuffix(file.Name(), manifestFileSuffix) { + manifestFiles = append(manifestFiles, file.Name()) + } + } + + return manifestFiles, nil +} + +// ReadManifestFile takes in a manifest file (e.g. frontend-app.yml) and returns +// the read bytes. +func (ws *Service) ReadManifestFile(manifestFile string) ([]byte, error) { + manifestDirPath, err := ws.manifestDirectoryPath() + if err != nil { + return nil, err + } + manifestPath := filepath.Join(manifestDirPath, manifestFile) + manifestFileExists, err := ws.fsUtils.Exists(manifestPath) + + if err != nil { + return nil, err + } + + if !manifestFileExists { + return nil, &ErrManifestNotFound{ManifestName: manifestFile} + } + + value, err := ws.fsUtils.ReadFile(manifestPath) + if err != nil { + return nil, err + } + + return value, nil +} + +// WriteManifest takes a manifest blob and writes it to the manifest directory. +func (ws *Service) WriteManifest(manifestBlob []byte, applicationName string) error { + manifestPath, err := ws.manifestDirectoryPath() + if err != nil { + return err + } + manifestFileName := fmt.Sprintf("%s%s", applicationName, manifestFileSuffix) + return ws.fsUtils.WriteFile(filepath.Join(manifestPath, manifestFileName), manifestBlob, 0644) +} diff --git a/internal/pkg/workspace/workspace_test.go b/internal/pkg/workspace/workspace_test.go new file mode 100644 index 00000000000..2b63e4b898f --- /dev/null +++ b/internal/pkg/workspace/workspace_test.go @@ -0,0 +1,382 @@ +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package workspace + +import ( + "fmt" + "testing" + + "github.com/aws/PRIVATE-amazon-ecs-archer/internal/pkg/archer" + "github.com/spf13/afero" + "github.com/stretchr/testify/require" +) + +func TestListManifests(t *testing.T) { + testCases := map[string]struct { + expectedManifests []string + workingDir string + expectedError error + mockFileSystem func(appFS afero.Fs) + }{ + "manifests with extra files": { + expectedManifests: []string{"frontend-app.yml", "backend-app.yml"}, + workingDir: "test/", + mockFileSystem: func(appFS afero.Fs) { + appFS.MkdirAll("test/ecs", 0755) + afero.WriteFile(appFS, "test/ecs/frontend-app.yml", []byte("frontend"), 0644) + afero.WriteFile(appFS, "test/ecs/backend-app.yml", []byte("backend"), 0644) + afero.WriteFile(appFS, "test/ecs/not-manifest.yml", []byte("nothing"), 0644) + afero.WriteFile(appFS, "test/ecs/.project", []byte("hiddenproject"), 0644) + }, + }, + + "no existing manifests": { + expectedManifests: []string{}, + workingDir: "test/", + mockFileSystem: func(appFS afero.Fs) { + appFS.MkdirAll("test/ecs", 0755) + afero.WriteFile(appFS, "test/ecs/not-manifest.yml", []byte("nothing"), 0644) + afero.WriteFile(appFS, "test/ecs/.project", []byte("hiddenproject"), 0644) + }, + }, + + "not in a valid workspace": { + expectedError: fmt.Errorf("couldn't find a directory called ecs up to 5 levels up from test/"), + workingDir: "test/", + mockFileSystem: func(appFS afero.Fs) { + }, + }, + } + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + // Create an empty FileSystem + appFS := afero.NewMemMapFs() + // Set it up + tc.mockFileSystem(appFS) + + ws := Service{ + workingDir: tc.workingDir, + fsUtils: &afero.Afero{Fs: appFS}, + } + manifests, err := ws.ListManifestFiles() + if tc.expectedError == nil { + require.NoError(t, err) + require.ElementsMatch(t, tc.expectedManifests, manifests) + } else { + require.Equal(t, tc.expectedError.Error(), err.Error()) + } + }) + } +} + +func TestReadManifest(t *testing.T) { + testCases := map[string]struct { + expectedContent string + workingDir string + manifestFile string + expectedError error + mockFileSystem func(appFS afero.Fs) + }{ + "existing manifest": { + manifestFile: "frontend-app.yml", + expectedContent: "frontend", + workingDir: "test/", + mockFileSystem: func(appFS afero.Fs) { + appFS.MkdirAll("test/ecs", 0755) + afero.WriteFile(appFS, "test/ecs/frontend-app.yml", []byte("frontend"), 0644) + afero.WriteFile(appFS, "test/ecs/backend-app.yml", []byte("backend"), 0644) + afero.WriteFile(appFS, "test/ecs/not-manifest.yml", []byte("nothing"), 0644) + afero.WriteFile(appFS, "test/ecs/.project", []byte("hiddenproject"), 0644) + }, + }, + + "non-existent manifest": { + manifestFile: "traveling-salesman-app.yml", + expectedError: fmt.Errorf("manifest file traveling-salesman-app.yml does not exists"), + workingDir: "test/", + mockFileSystem: func(appFS afero.Fs) { + appFS.MkdirAll("test/ecs", 0755) + afero.WriteFile(appFS, "test/ecs/frontend-app.yml", []byte("frontend"), 0644) + afero.WriteFile(appFS, "test/ecs/backend-app.yml", []byte("backend"), 0644) + afero.WriteFile(appFS, "test/ecs/not-manifest.yml", []byte("nothing"), 0644) + afero.WriteFile(appFS, "test/ecs/.project", []byte("hiddenproject"), 0644) + }, + }, + + "invalid workspace": { + manifestFile: "frontend-app.yml", + expectedError: fmt.Errorf("couldn't find a directory called ecs up to 5 levels up from /"), + workingDir: "/", + mockFileSystem: func(appFS afero.Fs) { + appFS.MkdirAll("test/ecs", 0755) + afero.WriteFile(appFS, "test/ecs/frontend-app.yml", []byte("frontend"), 0644) + afero.WriteFile(appFS, "test/ecs/backend-app.yml", []byte("backend"), 0644) + afero.WriteFile(appFS, "test/ecs/not-manifest.yml", []byte("nothing"), 0644) + afero.WriteFile(appFS, "test/ecs/.project", []byte("hiddenproject"), 0644) + }, + }, + } + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + // Create an empty FileSystem + appFS := afero.NewMemMapFs() + // Set it up + tc.mockFileSystem(appFS) + + ws := Service{ + workingDir: tc.workingDir, + fsUtils: &afero.Afero{Fs: appFS}, + } + content, err := ws.ReadManifestFile(tc.manifestFile) + if tc.expectedError == nil { + require.NoError(t, err) + require.Equal(t, tc.expectedContent, string(content)) + } else { + require.Equal(t, tc.expectedError.Error(), err.Error()) + } + }) + } +} + +func TestWriteManifest(t *testing.T) { + testCases := map[string]struct { + expectedContent string + manifestFile string + appName string + workingDir string + expectedError error + mockFileSystem func(appFS afero.Fs) + }{ + "new content": { + manifestFile: "frontend-app.yml", + appName: "frontend", + expectedContent: "frontend", + workingDir: "test/", + mockFileSystem: func(appFS afero.Fs) { + appFS.MkdirAll("test/ecs", 0755) + }, + }, + "no manifest dir": { + manifestFile: "frontend-app.yml", + appName: "frontend", + expectedError: fmt.Errorf("couldn't find a directory called ecs up to 5 levels up from /"), + workingDir: "/", + mockFileSystem: func(appFS afero.Fs) { + }, + }, + } + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + // Create an empty FileSystem + appFS := afero.NewMemMapFs() + // Set it up + tc.mockFileSystem(appFS) + + ws := Service{ + workingDir: tc.workingDir, + fsUtils: &afero.Afero{Fs: appFS}, + } + err := ws.WriteManifest([]byte(tc.expectedContent), tc.appName) + if tc.expectedError == nil { + require.NoError(t, err) + readContent, err := ws.ReadManifestFile(tc.manifestFile) + require.NoError(t, err) + require.Equal(t, tc.expectedContent, string(readContent)) + } else { + require.Equal(t, tc.expectedError.Error(), err.Error()) + } + }) + } +} + +func TestManifestDirectoryPath(t *testing.T) { + testCases := map[string]struct { + expectedManifestDir string + presetManifestDir string + workingDir string + expectedError error + mockFileSystem func(appFS afero.Fs) + }{ + "same directory level": { + expectedManifestDir: "test/ecs", + workingDir: "test/", + mockFileSystem: func(appFS afero.Fs) { + appFS.MkdirAll("test/ecs", 0755) + }, + }, + + "same directory": { + expectedManifestDir: "test/ecs", + workingDir: "test/ecs", + mockFileSystem: func(appFS afero.Fs) { + appFS.MkdirAll("test/ecs", 0755) + }, + }, + + "several levels deep": { + expectedManifestDir: "test/ecs", + workingDir: "test/1/2/3/4", + mockFileSystem: func(appFS afero.Fs) { + appFS.MkdirAll("test/ecs", 0755) + appFS.MkdirAll("test/1/2/3/4", 0755) + }, + }, + + "too many levels deep": { + expectedError: fmt.Errorf("couldn't find a directory called ecs up to 5 levels up from test/1/2/3/4/5"), + workingDir: "test/1/2/3/4/5", + mockFileSystem: func(appFS afero.Fs) { + appFS.MkdirAll("test/ecs", 0755) + appFS.MkdirAll("test/1/2/3/4/5", 0755) + }, + }, + + "out of a workspace": { + expectedError: fmt.Errorf("couldn't find a directory called ecs up to 5 levels up from /"), + workingDir: "/", + mockFileSystem: func(appFS afero.Fs) { + appFS.MkdirAll("test/ecs", 0755) + }, + }, + + "uses precomputed manifest path": { + expectedManifestDir: "test/ecs", + workingDir: "/", + mockFileSystem: func(appFS afero.Fs) {}, + presetManifestDir: "test/ecs", + }, + } + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + // Create an empty FileSystem + appFS := afero.NewMemMapFs() + // Set it up + tc.mockFileSystem(appFS) + + ws := Service{ + workingDir: tc.workingDir, + fsUtils: &afero.Afero{Fs: appFS}, + manifestDir: tc.presetManifestDir, + } + manifestDirPath, err := ws.manifestDirectoryPath() + if tc.expectedError == nil { + require.NoError(t, err) + require.Equal(t, tc.expectedManifestDir, manifestDirPath) + } else { + require.Equal(t, tc.expectedError.Error(), err.Error()) + } + }) + } +} + +func TestReadSummary(t *testing.T) { + testCases := map[string]struct { + expectedSummary archer.WorkspaceSummary + workingDir string + expectedError error + mockFileSystem func(appFS afero.Fs) + }{ + "existing workspace summary": { + expectedSummary: archer.WorkspaceSummary{ProjectName: "DavidsProject"}, + workingDir: "test/", + mockFileSystem: func(appFS afero.Fs) { + appFS.MkdirAll("test/ecs", 0755) + afero.WriteFile(appFS, "test/ecs/.project", []byte(fmt.Sprintf("---\nproject: %s", "DavidsProject")), 0644) + }, + }, + "no existing workspace summary": { + workingDir: "test/", + expectedError: fmt.Errorf("couldn't find a project associated with this workspace"), + mockFileSystem: func(appFS afero.Fs) { + appFS.MkdirAll("test/ecs", 0755) + }, + }, + "no existing manifest dir": { + workingDir: "test/", + expectedError: fmt.Errorf("couldn't find a directory called ecs up to 5 levels up from test/"), + mockFileSystem: func(appFS afero.Fs) {}, + }, + } + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + // Create an empty FileSystem + appFS := afero.NewMemMapFs() + // Set it up + tc.mockFileSystem(appFS) + + ws := Service{ + workingDir: tc.workingDir, + fsUtils: &afero.Afero{Fs: appFS}, + } + summary, err := ws.Summary() + if tc.expectedError == nil { + require.NoError(t, err) + require.Equal(t, tc.expectedSummary, *summary) + } else { + require.Equal(t, tc.expectedError.Error(), err.Error()) + } + }) + } +} + +func TestCreate(t *testing.T) { + testCases := map[string]struct { + projectName string + workingDir string + expectedError error + mockFileSystem func(appFS afero.Fs) + }{ + "existing workspace and workspace summary": { + workingDir: "test/", + projectName: "DavidsProject", + mockFileSystem: func(appFS afero.Fs) { + appFS.MkdirAll("test/ecs", 0755) + afero.WriteFile(appFS, "test/ecs/.project", []byte(fmt.Sprintf("---\nproject: %s", "DavidsProject")), 0644) + }, + }, + "existing workspace and different project": { + workingDir: "test/", + projectName: "DavidsProject", + expectedError: fmt.Errorf("this workspace is already registered with project DavidsOtherProject"), + mockFileSystem: func(appFS afero.Fs) { + appFS.MkdirAll("test/ecs", 0755) + afero.WriteFile(appFS, "test/ecs/.project", []byte(fmt.Sprintf("---\nproject: %s", "DavidsOtherProject")), 0644) + }, + }, + "existing workspace but no workspace summary": { + workingDir: "test/", + projectName: "DavidsProject", + mockFileSystem: func(appFS afero.Fs) { + appFS.MkdirAll("test/ecs", 0755) + }, + }, + "no existing workspace or workspace summary": { + workingDir: "test/", + projectName: "DavidsProject", + mockFileSystem: func(appFS afero.Fs) {}, + }, + } + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + // Create an empty FileSystem + appFS := afero.NewMemMapFs() + // Set it up + tc.mockFileSystem(appFS) + + ws := Service{ + workingDir: tc.workingDir, + fsUtils: &afero.Afero{Fs: appFS}, + } + err := ws.Create(tc.projectName) + if tc.expectedError == nil { + require.NoError(t, err) + project, err := ws.Summary() + require.NoError(t, err) + require.Equal(t, tc.projectName, project.ProjectName) + } else { + require.Equal(t, tc.expectedError.Error(), err.Error()) + } + }) + } +}