diff --git a/internal/cmd/cmd.go b/internal/cmd/cmd.go index 4aabcd2..ae764bd 100644 --- a/internal/cmd/cmd.go +++ b/internal/cmd/cmd.go @@ -14,6 +14,10 @@ import ( // Help messages to display for specific error situations. const ( + // helpAirbyteDir is display if ErrAirbyteDir is ever returned + helpAirbyteDir = `The ~/.airbyte directory is inaccessible. +You may need to remove this directory before trying your command again.` + // helpDocker is displayed if ErrDocker is ever returned helpDocker = `An error occurred while communicating with the Docker daemon. Ensure that Docker is running and is accessible. You may need to upgrade to a newer version of Docker. @@ -41,16 +45,20 @@ func Execute(ctx context.Context, cmd *cobra.Command) { if err := cmd.ExecuteContext(ctx); err != nil { pterm.Error.Println(err) - if errors.Is(err, localerr.ErrDocker) { + switch { + case errors.Is(err, localerr.ErrAirbyteDir): + pterm.Println() + pterm.Info.Println(helpAirbyteDir) + case errors.Is(err, localerr.ErrDocker): pterm.Println() pterm.Info.Println(helpDocker) - } else if errors.Is(err, localerr.ErrKubernetes) { + case errors.Is(err, localerr.ErrKubernetes): pterm.Println() pterm.Info.Println(helpKubernetes) - } else if errors.Is(err, localerr.ErrIngress) { + case errors.Is(err, localerr.ErrIngress): pterm.Println() pterm.Info.Println(helpIngress) - } else if errors.Is(err, localerr.ErrPort) { + case errors.Is(err, localerr.ErrPort): pterm.Println() pterm.Info.Printfln(helpPort) } diff --git a/internal/cmd/local/local.go b/internal/cmd/local/local.go index cd64bf5..19402a4 100644 --- a/internal/cmd/local/local.go +++ b/internal/cmd/local/local.go @@ -1,10 +1,16 @@ package local import ( + "errors" + "fmt" "github.com/airbytehq/abctl/internal/cmd/local/k8s" + "github.com/airbytehq/abctl/internal/cmd/local/localerr" + "github.com/airbytehq/abctl/internal/cmd/local/paths" "github.com/airbytehq/abctl/internal/telemetry" "github.com/pterm/pterm" "github.com/spf13/cobra" + "io/fs" + "os" ) var telClient telemetry.Client @@ -14,6 +20,10 @@ func NewCmdLocal(provider k8s.Provider) *cobra.Command { cmd := &cobra.Command{ Use: "local", PersistentPreRunE: func(cmd *cobra.Command, _ []string) error { + if err := checkAirbyteDir(); err != nil { + return fmt.Errorf("%w: %w", localerr.ErrAirbyteDir, err) + } + // telemetry client configuration { var telOpts []telemetry.GetOption @@ -39,5 +49,40 @@ func NewCmdLocal(provider k8s.Provider) *cobra.Command { } func printProviderDetails(p k8s.Provider) { - pterm.Info.Printfln("Using Kubernetes provider:\n Provider: %s\n Kubeconfig: %s\n Context: %s", p.Name, p.Kubeconfig, p.Context) + pterm.Info.Println(fmt.Sprintf( + "Using Kubernetes provider:\n Provider: %s\n Kubeconfig: %s\n Context: %s", + p.Name, p.Kubeconfig, p.Context, + )) +} + +// checkAirbyteDir verifies that, if the paths.Airbyte directory exists, that it has proper permissions. +// If the directory does not have the proper permissions, this method will attempt to fix them. +// A nil response either indicates that either: +// - no paths.Airbyte directory exists +// - the permissions are already correct +// - this function was able to fix the incorrect permissions. +func checkAirbyteDir() error { + fileInfo, err := os.Stat(paths.Airbyte) + if err != nil { + if errors.Is(err, fs.ErrNotExist) { + // nothing to do, directory will be created later on + return nil + } + return fmt.Errorf("unable to determine status of '%s': %w", paths.Airbyte, err) + } + + if !fileInfo.IsDir() { + return errors.New(paths.Airbyte + " is not a directory") + } + + if fileInfo.Mode().Perm() >= 0744 { + // directory has minimal permissions + return nil + } + + if err := os.Chmod(paths.Airbyte, 0744); err != nil { + return fmt.Errorf("unable to change permissions of '%s': %w", paths.Airbyte, err) + } + + return nil } diff --git a/internal/cmd/local/local/cmd.go b/internal/cmd/local/local/cmd.go index 28f6b36..f9153e2 100644 --- a/internal/cmd/local/local/cmd.go +++ b/internal/cmd/local/local/cmd.go @@ -651,7 +651,14 @@ func (c *Command) handleChart( c.tel.Attr(fmt.Sprintf("helm_%s_chart_version", req.name), helmChart.Metadata.Version) - c.spinner.UpdateText(fmt.Sprintf("Installing '%s' (version: %s) Helm Chart (this may take several minutes)", req.chartName, helmChart.Metadata.Version)) + pterm.Info.Println(fmt.Sprintf( + "Starting Helm Chart installation of '%s' (version: %s)", + req.chartName, helmChart.Metadata.Version, + )) + c.spinner.UpdateText(fmt.Sprintf( + "Installing '%s' (version: %s) Helm Chart (this may take several minutes)", + req.chartName, helmChart.Metadata.Version, + )) helmRelease, err := c.helm.InstallOrUpgradeChart(ctx, &helmclient.ChartSpec{ ReleaseName: req.chartRelease, ChartName: req.chartName, diff --git a/internal/cmd/local/local_test.go b/internal/cmd/local/local_test.go new file mode 100644 index 0000000..0635341 --- /dev/null +++ b/internal/cmd/local/local_test.go @@ -0,0 +1,90 @@ +package local + +import ( + "github.com/airbytehq/abctl/internal/cmd/local/paths" + "github.com/google/go-cmp/cmp" + "os" + "path/filepath" + "testing" +) + +func TestCheckAirbyteDir(t *testing.T) { + origDir := paths.Airbyte + t.Cleanup(func() { + paths.Airbyte = origDir + }) + + t.Run("no directory", func(t *testing.T) { + paths.Airbyte = filepath.Join(t.TempDir(), "does-not-exist") + if err := checkAirbyteDir(); err != nil { + t.Error("unexpected error", err) + } + }) + + t.Run("directory with correct permissions", func(t *testing.T) { + paths.Airbyte = filepath.Join(t.TempDir(), "correct-perms") + if err := os.MkdirAll(paths.Airbyte, 0744); err != nil { + t.Fatal("unable to create test directory", err) + } + if err := os.Chmod(paths.Airbyte, 0744); err != nil { + t.Fatal("unable to change permissions", err) + } + if err := checkAirbyteDir(); err != nil { + t.Error("unexpected error", err) + } + + // permissions should be unchanged + perms, err := os.Stat(paths.Airbyte) + if err != nil { + t.Fatal("unable to check permissions", err) + } + if d := cmp.Diff(0744, int(perms.Mode().Perm())); d != "" { + t.Errorf("permissions mismatch (-want +got):\n%s", d) + } + }) + + t.Run("directory with higher permissions", func(t *testing.T) { + paths.Airbyte = filepath.Join(t.TempDir(), "correct-perms") + if err := os.MkdirAll(paths.Airbyte, 0777); err != nil { + t.Fatal("unable to create test directory", err) + } + if err := os.Chmod(paths.Airbyte, 0777); err != nil { + t.Fatal("unable to change permissions", err) + } + if err := checkAirbyteDir(); err != nil { + t.Error("unexpected error", err) + } + + // permissions should be unchanged + perms, err := os.Stat(paths.Airbyte) + if err != nil { + t.Fatal("unable to check permissions", err) + } + if d := cmp.Diff(0777, int(perms.Mode().Perm())); d != "" { + t.Errorf("permissions mismatch (-want +got):\n%s", d) + } + }) + + t.Run("directory with incorrect permissions", func(t *testing.T) { + paths.Airbyte = filepath.Join(t.TempDir(), "incorrect-perms") + if err := os.MkdirAll(paths.Airbyte, 0200); err != nil { + t.Fatal("unable to create test directory", err) + } + if err := os.Chmod(paths.Airbyte, 0200); err != nil { + t.Fatal("unable to change permissions", err) + } + // although the permissions are incorrect, checkAirbyteDir should fix them + if err := checkAirbyteDir(); err != nil { + t.Fatal("unexpected error", err) + } + + // permissions should be changed + perms, err := os.Stat(paths.Airbyte) + if err != nil { + t.Fatal("unable to check permissions", err) + } + if d := cmp.Diff(0744, int(perms.Mode().Perm())); d != "" { + t.Errorf("permissions mismatch (-want +got):\n%s", d) + } + }) +} diff --git a/internal/cmd/local/localerr/localerr.go b/internal/cmd/local/localerr/localerr.go index 795f35c..f3516a6 100644 --- a/internal/cmd/local/localerr/localerr.go +++ b/internal/cmd/local/localerr/localerr.go @@ -3,6 +3,9 @@ package localerr import "errors" var ( + // ErrAirbyteDir is returned anytime an there is an issue in accessing the paths.Airbyte directory. + ErrAirbyteDir = errors.New("airbyte directory is inaccessible") + // ErrDocker is returned anytime an error occurs when attempting to communicate with docker. ErrDocker = errors.New("error communicating with docker")