Skip to content

Commit

Permalink
feat: add --values flag to local install command (#39)
Browse files Browse the repository at this point in the history
  • Loading branch information
colesnodgrass authored Jun 18, 2024
1 parent 9d30fe6 commit 60582fc
Show file tree
Hide file tree
Showing 4 changed files with 200 additions and 14 deletions.
14 changes: 13 additions & 1 deletion internal/cmd/local/local/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,16 @@ func New(provider k8s.Provider, opts ...Option) (*Command, error) {
}

// Install handles the installation of Airbyte
func (c *Command) Install(ctx context.Context, user, pass string) error {
func (c *Command) Install(ctx context.Context, user, pass, valuesFile string) error {
var values string
if valuesFile != "" {
raw, err := os.ReadFile(valuesFile)
if err != nil {
return fmt.Errorf("could not read values file '%s': %w", valuesFile, err)
}
values = string(raw)
}

go c.watchEvents(ctx)

var telUser string
Expand All @@ -239,6 +248,7 @@ func (c *Command) Install(ctx context.Context, user, pass string) error {
chartVersion: c.helmChartVersion,
namespace: airbyteNamespace,
values: []string{fmt.Sprintf("global.env_vars.AIRBYTE_INSTALLATION_ID=%s", telUser)},
valuesYAML: values,
}); err != nil {
return fmt.Errorf("could not install airbyte chart: %w", err)
}
Expand Down Expand Up @@ -528,6 +538,7 @@ type chartRequest struct {
chartVersion string
namespace string
values []string
valuesYAML string
}

// handleChart will handle the installation of a chart
Expand Down Expand Up @@ -563,6 +574,7 @@ func (c *Command) handleChart(
Wait: true,
Timeout: 10 * time.Minute,
ValuesOptions: values.Options{Values: req.values},
ValuesYaml: req.valuesYAML,
Version: req.chartVersion,
},
&helmclient.GenericHelmOptions{},
Expand Down
178 changes: 175 additions & 3 deletions internal/cmd/local/local/cmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
networkingv1 "k8s.io/api/networking/v1"
"k8s.io/apimachinery/pkg/watch"
"net/http"
"strings"
"testing"
"time"
)
Expand Down Expand Up @@ -155,11 +156,177 @@ func TestCommand_Install(t *testing.T) {
t.Fatal(err)
}

if err := c.Install(context.Background(), "user", "pass"); err != nil {
if err := c.Install(context.Background(), "user", "pass", ""); err != nil {
t.Fatal(err)
}
}

func TestCommand_Install_ValuesFile(t *testing.T) {
expChartRepoCnt := 0
expChartRepo := []struct {
name string
url string
}{
{name: airbyteRepoName, url: airbyteRepoURL},
{name: nginxRepoName, url: nginxRepoURL},
}

// userID is for telemetry tracking purposes
userID := uuid.New()

expChartCnt := 0
expChart := []struct {
chart helmclient.ChartSpec
release release.Release
}{
{
chart: helmclient.ChartSpec{
ReleaseName: airbyteChartRelease,
ChartName: airbyteChartName,
Namespace: airbyteNamespace,
CreateNamespace: true,
Wait: true,
Timeout: 10 * time.Minute,
ValuesOptions: values.Options{Values: []string{"global.env_vars.AIRBYTE_INSTALLATION_ID=" + userID.String()}},
ValuesYaml: "global:\n edition: \"test\"\n",
},
release: release.Release{
Chart: &chart.Chart{Metadata: &chart.Metadata{Version: "1.2.3.4"}},
Name: airbyteChartRelease,
Namespace: airbyteNamespace,
Version: 0,
},
},
{
chart: helmclient.ChartSpec{
ReleaseName: nginxChartRelease,
ChartName: nginxChartName,
Namespace: nginxNamespace,
CreateNamespace: true,
Wait: true,
Timeout: 10 * time.Minute,
ValuesOptions: values.Options{Values: []string{fmt.Sprintf("controller.service.ports.http=%d", portTest)}},
},
release: release.Release{
Chart: &chart.Chart{Metadata: &chart.Metadata{Version: "4.3.2.1"}},
Name: nginxChartRelease,
Namespace: nginxNamespace,
Version: 0,
},
},
}
helm := mockHelmClient{
addOrUpdateChartRepo: func(entry repo.Entry) error {
if d := cmp.Diff(expChartRepo[expChartRepoCnt].name, entry.Name); d != "" {
t.Error("chart name mismatch", d)
}
if d := cmp.Diff(expChartRepo[expChartRepoCnt].url, entry.URL); d != "" {
t.Error("chart url mismatch", d)
}

expChartRepoCnt++

return nil
},

getChart: func(name string, _ *action.ChartPathOptions) (*chart.Chart, string, error) {
switch {
case name == airbyteChartName:
return &chart.Chart{Metadata: &chart.Metadata{Version: "test.airbyte.version"}}, "", nil
case name == nginxChartName:
return &chart.Chart{Metadata: &chart.Metadata{Version: "test.nginx.version"}}, "", nil
default:
t.Error("unsupported chart name", name)
return nil, "", errors.New("unexpected chart name")
}
},

installOrUpgradeChart: func(ctx context.Context, spec *helmclient.ChartSpec, opts *helmclient.GenericHelmOptions) (*release.Release, error) {
if d := cmp.Diff(&expChart[expChartCnt].chart, spec); d != "" {
t.Error("chart mismatch", d)
}

defer func() { expChartCnt++ }()

return &expChart[expChartCnt].release, nil
},
}

k8sClient := mockK8sClient{
serverVersionGet: func() (string, error) {
return "test", nil
},
secretCreateOrUpdate: func(ctx context.Context, namespace, name string, data map[string][]byte) error {
return nil
},
ingressExists: func(ctx context.Context, namespace string, ingress string) bool {
return false
},
ingressCreate: func(ctx context.Context, namespace string, ingress *networkingv1.Ingress) error {
return nil
},
}

attrs := map[string]string{}
tel := mockTelemetryClient{
attr: func(key, val string) { attrs[key] = val },
user: func() uuid.UUID { return userID },
}

httpClient := mockHTTP{do: func(req *http.Request) (*http.Response, error) {
return &http.Response{StatusCode: 200}, nil
}}

c, err := New(
k8s.TestProvider,
WithPortHTTP(portTest),
WithHelmClient(&helm),
WithK8sClient(&k8sClient),
WithTelemetryClient(&tel),
WithHTTPClient(&httpClient),
WithBrowserLauncher(func(url string) error {
return nil
}),
)

if err != nil {
t.Fatal(err)
}

if err := c.Install(context.Background(), "user", "pass", "testdata/values.yml"); err != nil {
t.Fatal(err)
}
}

func TestCommand_Install_InvalidValuesFile(t *testing.T) {
c, err := New(
k8s.TestProvider,
WithPortHTTP(portTest),
WithHelmClient(&mockHelmClient{}),
WithK8sClient(&mockK8sClient{}),
WithTelemetryClient(&mockTelemetryClient{}),
WithHTTPClient(&mockHTTP{}),
WithBrowserLauncher(func(url string) error {
return nil
}),
)

if err != nil {
t.Fatal(err)
}

valuesFile := "testdata/dne.yml"

err = c.Install(context.Background(), "user", "pass", valuesFile)
if err == nil {
t.Fatal("expecting an error, received none")
}
if !strings.Contains(err.Error(), fmt.Sprintf("could not read values file '%s'", valuesFile)) {
t.Error("unexpected error:", err)
}

}

// ---
// only mocks below here
// ---
Expand Down Expand Up @@ -237,7 +404,10 @@ func (m *mockK8sClient) ServiceGet(ctx context.Context, namespace, name string)
}

func (m *mockK8sClient) ServerVersionGet() (string, error) {
return m.serverVersionGet()
if m.serverVersionGet != nil {
return m.serverVersionGet()
}
return "test", nil
}

func (m *mockK8sClient) EventsWatch(ctx context.Context, namespace string) (watch.Interface, error) {
Expand Down Expand Up @@ -277,7 +447,9 @@ func (m *mockTelemetryClient) Failure(ctx context.Context, eventType telemetry.E
}

func (m *mockTelemetryClient) Attr(key, val string) {
m.attr(key, val)
if m.attr != nil {
m.attr(key, val)
}
}

func (m *mockTelemetryClient) User() uuid.UUID {
Expand Down
2 changes: 2 additions & 0 deletions internal/cmd/local/local/testdata/values.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
global:
edition: "test"
20 changes: 10 additions & 10 deletions internal/cmd/local/local_install.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,11 @@ func NewCmdInstall(provider k8s.Provider) *cobra.Command {
spinner := &pterm.DefaultSpinner

var (
flagChartVersion string
flagUsername string
flagPassword string
flagPort int
flagChartValuesFile string
flagChartVersion string
flagUsername string
flagPassword string
flagPort int
)

cmd := &cobra.Command{
Expand Down Expand Up @@ -113,16 +114,14 @@ func NewCmdInstall(provider k8s.Provider) *cobra.Command {
return fmt.Errorf("could not initialize local command: %w", err)
}

user := flagUsername
if env := os.Getenv(envBasicAuthUser); env != "" {
user = env
flagUsername = env
}
pass := flagPassword
if env := os.Getenv(envBasicAuthPass); env != "" {
pass = env
flagPassword = env
}

if err := lc.Install(cmd.Context(), user, pass); err != nil {
if err := lc.Install(cmd.Context(), flagUsername, flagPassword, flagChartValuesFile); err != nil {
spinner.Fail("Unable to install Airbyte locally")
return err
}
Expand All @@ -137,7 +136,8 @@ func NewCmdInstall(provider k8s.Provider) *cobra.Command {
cmd.Flags().StringVarP(&flagPassword, "password", "p", "password", "basic auth password, can also be specified via "+envBasicAuthPass)
cmd.Flags().IntVar(&flagPort, "port", local.Port, "ingress http port")

cmd.Flags().StringVar(&flagChartVersion, "chart-version", "latest", "specify the specific Airbyte helm chart version to install")
cmd.Flags().StringVar(&flagChartVersion, "chart-version", "latest", "the Airbyte helm chart version to install")
cmd.Flags().StringVar(&flagChartValuesFile, "values", "", "the Airbyte helm chart values file to load")

return cmd
}

0 comments on commit 60582fc

Please sign in to comment.