From 5a18a5bf446cd9ce05f05d8ae9b77edf5e3b9e1b Mon Sep 17 00:00:00 2001 From: Marc Cataford Date: Sun, 10 Nov 2024 11:08:16 -0500 Subject: [PATCH] refactor: isolate + inject DefinitionFetcher docs: add documentation to core functions and structs --- cli/build.go | 2 +- cli/start_service.go | 2 +- git/main.go | 26 +++++++++ service_definition/fetcher.go | 40 ++++++++++--- service_definition/fetcher_test.go | 58 +++++++++++++++++++ service_definition/main.go | 12 ---- service_definition/service_definition_test.go | 2 +- 7 files changed, 120 insertions(+), 22 deletions(-) create mode 100644 git/main.go create mode 100644 service_definition/fetcher_test.go diff --git a/cli/build.go b/cli/build.go index 8f1ed6b..bccbecb 100644 --- a/cli/build.go +++ b/cli/build.go @@ -18,7 +18,7 @@ func getBuildCommand() *cobra.Command { if err != nil { return fmt.Errorf("%+v", err) } - def, err := service_definition.GetServiceDefinition(pathProvided) + def, err := service_definition.NewDefinitionFetcher().GetDefinition(pathProvided) if err != nil { return fmt.Errorf("Failed to read service definition from file: %+v", err) diff --git a/cli/start_service.go b/cli/start_service.go index 29736a8..4bd12a8 100644 --- a/cli/start_service.go +++ b/cli/start_service.go @@ -49,7 +49,7 @@ func getStartCommand() *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { ctx := cmd.Context() flags := ctx.Value("flags").(ParsedFlags) - def, err := service_definition.GetServiceDefinition(flags.definitionPath) + def, err := service_definition.NewDefinitionFetcher().GetDefinition(flags.definitionPath) if err != nil { return fmt.Errorf("Failed to read service definition: %+v", err) diff --git a/git/main.go b/git/main.go new file mode 100644 index 0000000..35c0a31 --- /dev/null +++ b/git/main.go @@ -0,0 +1,26 @@ +// Git wrapper +// +// Facilitates the usage of `git` commands when dealing with +// data living in repositories. + +package git + +import ( + "os/exec" +) + +type GitClient interface { + Clone(path string, destination string) (string, error) +} + +type Git struct{} + +func (g Git) Clone(path string, destination string) (string, error) { + cloneCmd := exec.Command("git", "clone", path, destination) + + if err := cloneCmd.Run(); err != nil { + return "", err + } + + return path, nil +} diff --git a/service_definition/fetcher.go b/service_definition/fetcher.go index d7de849..461a5cf 100644 --- a/service_definition/fetcher.go +++ b/service_definition/fetcher.go @@ -1,32 +1,58 @@ +// Definition fetcher +// +// Handles fetching and building ServiceDefinition structs from different +// data sources. + package service_definition import ( "github.com/goccy/go-yaml" "os" - "os/exec" + git "spud/git" "strings" ) +type DefinitionFetcher struct { + Git git.GitClient +} + +func NewDefinitionFetcher() DefinitionFetcher { + return DefinitionFetcher{ + Git: git.Git{}, + } +} + +// Gets a ServiceDefinition from the given location. +// +// Depending on location prefix, different sources are used: +// git+: Clones the target git repository and extracts a service definition from it. +// : Uses the location as a filepath to the service definition. +func (f DefinitionFetcher) GetDefinition(path string) (ServiceDefinition, error) { + if strings.HasPrefix(path, "git+") { + return f.getDefinitionFromGit(path) + } + + return f.getDefinitionFromFile(path) +} + // Clones the target git repository and uses it as a basis to extract // a service definition. -func getDefinitionFromGit(path string) (ServiceDefinition, error) { +func (f DefinitionFetcher) getDefinitionFromGit(path string) (ServiceDefinition, error) { dir, err := os.MkdirTemp("/tmp", "spud-service-") if err != nil { return ServiceDefinition{}, err } - cloneCmd := exec.Command("git", "clone", strings.TrimPrefix(path, "git+"), dir) - - if err := cloneCmd.Run(); err != nil { + if _, err := f.Git.Clone(strings.TrimPrefix(path, "git+"), dir); err != nil { return ServiceDefinition{}, err } - return getDefinitionFromFile(dir + "/service.yml") + return f.getDefinitionFromFile(dir + "/service.yml") } // Extracts a service definition from the given filepath. -func getDefinitionFromFile(path string) (ServiceDefinition, error) { +func (f DefinitionFetcher) getDefinitionFromFile(path string) (ServiceDefinition, error) { var definition ServiceDefinition defData, err := os.ReadFile(path) diff --git a/service_definition/fetcher_test.go b/service_definition/fetcher_test.go new file mode 100644 index 0000000..68eef08 --- /dev/null +++ b/service_definition/fetcher_test.go @@ -0,0 +1,58 @@ +package service_definition + +import ( + "os" + "strings" + "testing" +) + +type MockGit struct { + calls []string +} + +func (g *MockGit) Clone(path string, destination string) (string, error) { + g.calls = append(g.calls, path+":"+destination) + + return path, nil +} + +func TestGetDefinitionDetectsGitPathPrefix(t *testing.T) { + fetcher := NewDefinitionFetcher() + + mockGit := MockGit{} + fetcher.Git = &mockGit + + fetcher.GetDefinition("git+https://git.com/owner/repo.git") + + if len(mockGit.calls) == 0 { + t.Errorf("Expected at least one call to git, got none.") + } +} + +func TestGetDefinitionDefaultsToFilePathIfNoPrefix(t *testing.T) { + defPath := t.TempDir() + "/service.yml" + + os.WriteFile(defPath, []byte("name: test-service"), 0755) + + fetcher := NewDefinitionFetcher() + + def, _ := fetcher.GetDefinition(defPath) + + if def.Name != "test-service" { + t.Errorf("Expected mock service name to be 'test-service', got %s instead.", def.Name) + } +} + +func TestGetDefinitionGetDefinitionFromGit(t *testing.T) { + fetcher := NewDefinitionFetcher() + + mockGit := MockGit{} + fetcher.Git = &mockGit + + mockUrl := "https://git.com/owner/repo.git" + fetcher.GetDefinition("git+" + mockUrl) + + if !strings.HasPrefix(mockGit.calls[0], mockUrl) { + t.Errorf("Expected git cloning for %s, got %s instead.", mockUrl, mockGit.calls[0]) + } +} diff --git a/service_definition/main.go b/service_definition/main.go index 7b57423..929720b 100644 --- a/service_definition/main.go +++ b/service_definition/main.go @@ -1,9 +1,5 @@ package service_definition -import ( - "strings" -) - type BuildImage struct { Path string `yaml:"path"` TagPrefix string `yaml:"tag"` @@ -47,11 +43,3 @@ type ServiceDefinition struct { Containers []ContainerDefinition `yaml:"containers"` Ports []PortMapping `yaml:"ports"` } - -func GetServiceDefinition(path string) (ServiceDefinition, error) { - if strings.HasPrefix(path, "git+") { - return getDefinitionFromGit(path) - } - - return getDefinitionFromFile(path) -} diff --git a/service_definition/service_definition_test.go b/service_definition/service_definition_test.go index a63b02a..5cebc88 100644 --- a/service_definition/service_definition_test.go +++ b/service_definition/service_definition_test.go @@ -5,7 +5,7 @@ import ( ) func TestGetServiceDefinitionFromFileDoesNotExist(t *testing.T) { - _, err := GetServiceDefinition(t.TempDir() + "/not-a-file.yml") + _, err := NewDefinitionFetcher().GetDefinition(t.TempDir() + "/not-a-file.yml") if err == nil { t.Errorf("Expected error, got nil.")