Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add --values flag to local install command #39

Merged
merged 2 commits into from
Jun 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
}