Skip to content

Commit

Permalink
Merge branch 'main' into abuch/chart-flag
Browse files Browse the repository at this point in the history
  • Loading branch information
abuchanan-airbyte committed Sep 20, 2024
2 parents 05232d1 + f4db29a commit 97b7908
Show file tree
Hide file tree
Showing 16 changed files with 416 additions and 128 deletions.
22 changes: 0 additions & 22 deletions internal/cmd/cmd.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package cmd

import (
"errors"
"fmt"
"os"

Expand All @@ -16,27 +15,6 @@ import (
"k8s.io/client-go/tools/clientcmd"
)

func HandleErr(err error) {
if err == nil {
return
}

pterm.Error.Println(err)

var errParse *kong.ParseError
if errors.As(err, &errParse) {
_ = kong.DefaultHelpPrinter(kong.HelpOptions{}, errParse.Context)
}

var e *localerr.LocalError
if errors.As(err, &e) {
pterm.Println()
pterm.Info.Println(e.Help())
}

os.Exit(1)
}

type verbose bool

func (v verbose) BeforeApply() error {
Expand Down
13 changes: 13 additions & 0 deletions internal/cmd/local/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"net"
"os"
"regexp"
"runtime"
"strconv"
"syscall"
Expand Down Expand Up @@ -46,6 +47,8 @@ func dockerInstalled(ctx context.Context, telClient telemetry.Client) (docker.Ve
if info, err := dockerClient.Client.Info(ctx); err == nil {
telClient.Attr("docker_ncpu", fmt.Sprintf("%d", info.NCPU))
telClient.Attr("docker_memtotal", fmt.Sprintf("%d", info.MemTotal))
telClient.Attr("docker_cgroup_driver", info.CgroupDriver)
telClient.Attr("docker_cgroup_version", info.CgroupVersion)
}

pterm.Success.Println(fmt.Sprintf("Found Docker installation: version %s", version.Version))
Expand Down Expand Up @@ -165,3 +168,13 @@ func (e InvalidPortError) Unwrap() error {
func (e InvalidPortError) Error() string {
return fmt.Sprintf("unable to convert host port %s to integer: %s", e.Port, e.Inner)
}

func validateHostFlag(host string) error {
if ip := net.ParseIP(host); ip != nil {
return localerr.ErrIpAddressForHostFlag
}
if !regexp.MustCompile(`^[a-z0-9](?:[-a-z0-9]*[a-z0-9])?(?:\.[a-z0-9](?:[-a-z0-9]*[a-z0-9])?)*$`).MatchString(host) {
return localerr.ErrInvalidHostFlag
}
return nil
}
25 changes: 25 additions & 0 deletions internal/cmd/local/check_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,31 @@ func TestGetPort_InpsectErr(t *testing.T) {
}
}

func TestValidateHostFlag(t *testing.T) {
expectErr := func(host string, expect error) {
err := validateHostFlag(host)
if !errors.Is(err, expect) {
t.Errorf("expected error %v for host %q but got %v", expect, host, err)
}
}
expectErr("1.2.3.4", localerr.ErrIpAddressForHostFlag)
expectErr("1.2.3.4:8000", localerr.ErrInvalidHostFlag)
expectErr("1.2.3.4:8000", localerr.ErrInvalidHostFlag)
expectErr("ABC-DEF-GHI.abcd.efgh", localerr.ErrInvalidHostFlag)
expectErr("http://airbyte.foo-data-platform-sbx.bar.cloud", localerr.ErrInvalidHostFlag)

expectOk := func(host string) {
err := validateHostFlag(host)
if err != nil {
t.Errorf("unexpected error for host %q: %s", host, err)
}
}
expectOk("foo")
expectOk("foo.bar")
expectOk("example.com")
expectOk("sub.example01.com")
}

// port returns the port from a string value in the format of "ipv4:port" or "ip::v6:port"
func port(s string) int {
vals := strings.Split(s, ":")
Expand Down
36 changes: 11 additions & 25 deletions internal/cmd/local/docker/secret.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,28 +22,14 @@ import (
// }
func Secret(server, user, pass, email string) ([]byte, error) {
// map of the server to the credentials
servers := map[string]credential{
server: newCredential(user, pass, email),
}
auths := map[string]map[string]credential{
"auths": servers,
}

return json.Marshal(auths)
}

type credential struct {
Username string `json:"username"`
Password string `json:"password"`
Email string `json:"email"`
Auth string `json:"auth"`
}

func newCredential(user, pass, email string) credential {
return credential{
Username: user,
Password: pass,
Email: email,
Auth: base64.StdEncoding.EncodeToString([]byte(user + ":" + pass)),
}
}
return json.Marshal(map[string]any{
"auths": map[string]any {
server: map[string]any {
"username": user,
"password": pass,
"email": email,
"auth": base64.StdEncoding.EncodeToString([]byte(user + ":" + pass)),
},
},
})
}
8 changes: 3 additions & 5 deletions internal/cmd/local/docker/secret_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,15 @@ package docker

import (
"testing"

"github.com/google/go-cmp/cmp"
)

func Test_Secret(t *testing.T) {
exp := `{"auths":{"my-registry.example:5000":{"username":"tiger","password":"pass1234","email":"[email protected]","auth":"dGlnZXI6cGFzczEyMzQ="}}}`
exp := `{"auths":{"my-registry.example:5000":{"auth":"dGlnZXI6cGFzczEyMzQ=","email":"[email protected]","password":"pass1234","username":"tiger"}}}`
act, err := Secret("my-registry.example:5000", "tiger", "pass1234", "[email protected]")
if err != nil {
t.Fatal(err)
}
if d := cmp.Diff(exp, string(act)); d != "" {
t.Errorf("Secret mismatch (-want +got):\n%s", d)
if exp != string(act) {
t.Errorf("Secret mismatch:\nwant: %s\ngot: %s", exp, act)
}
}
18 changes: 7 additions & 11 deletions internal/cmd/local/local/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ const (
type InstallOpts struct {
HelmChartFlag string
HelmChartVersion string
ValuesFile string
HelmValues map[string]any
Secrets []string
Migrate bool
Hosts []string
Expand Down Expand Up @@ -242,9 +242,9 @@ func (c *Command) Install(ctx context.Context, opts InstallOpts) error {
pterm.Success.Println(fmt.Sprintf("Secret from '%s' created or updated", secretFile))
}

valuesYAML, err := mergeValuesWithValuesYAML(airbyteValues, opts.ValuesFile)
valuesYAML, err := mergeValuesWithValuesYAML(airbyteValues, opts.HelmValues)
if err != nil {
return fmt.Errorf("unable to merge values with values file '%s': %w", opts.ValuesFile, err)
return err
}

if err := c.handleChart(ctx, chartRequest{
Expand Down Expand Up @@ -747,19 +747,15 @@ func determineHelmChartAction(helm helm.Client, chart *chart.Chart, releaseName
// defined in this code at a higher priority than the values defined in the values.yaml file.
// This function returns a string representation of the value.yaml file after all
// values provided were potentially overridden by the valuesYML file.
func mergeValuesWithValuesYAML(values []string, valuesYAML string) (string, error) {
func mergeValuesWithValuesYAML(values []string, userValues map[string]any) (string, error) {
a := maps.FromSlice(values)
b, err := maps.FromYAMLFile(valuesYAML)
if err != nil {
return "", fmt.Errorf("unable to read values from yaml file '%s': %w", valuesYAML, err)
}
maps.Merge(a, b)

maps.Merge(a, userValues)

res, err := maps.ToYAML(a)
if err != nil {
return "", fmt.Errorf("unable to merge values from yaml file '%s': %w", valuesYAML, err)
return "", fmt.Errorf("unable to merge values: %w", err)
}

return res, nil

}
11 changes: 8 additions & 3 deletions internal/cmd/local/local/install_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"errors"
"fmt"
"net/http"
"strings"
"testing"
"time"

Expand Down Expand Up @@ -201,7 +200,7 @@ func TestCommand_Install(t *testing.T) {
}
}

func TestCommand_Install_ValuesFile(t *testing.T) {
func TestCommand_Install_HelmValues(t *testing.T) {
expChartRepoCnt := 0
expChartRepo := []struct {
name string
Expand Down Expand Up @@ -365,7 +364,12 @@ func TestCommand_Install_ValuesFile(t *testing.T) {
t.Fatal(err)
}

if err := c.Install(context.Background(), InstallOpts{ValuesFile: "testdata/values.yml"}); err != nil {
helmValues := map[string]any{
"global": map[string]any{
"edition": "test",
},
}
if err := c.Install(context.Background(), InstallOpts{HelmValues: helmValues}); err != nil {
t.Fatal(err)
}
}
Expand Down Expand Up @@ -396,4 +400,5 @@ func TestCommand_Install_InvalidValuesFile(t *testing.T) {
if !strings.Contains(err.Error(), fmt.Sprintf("unable to read values from yaml file '%s'", valuesFile)) {

Check failure on line 400 in internal/cmd/local/local/install_test.go

View workflow job for this annotation

GitHub Actions / build

undefined: strings
t.Error("unexpected error:", err)
}

}
65 changes: 57 additions & 8 deletions internal/cmd/local/local/locate.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,45 @@
package local

import (
"errors"
"fmt"
"strings"

"github.com/pterm/pterm"
"golang.org/x/mod/semver"
"helm.sh/helm/v3/pkg/cli"
"helm.sh/helm/v3/pkg/getter"
"helm.sh/helm/v3/pkg/repo"
)

// chartRepo exists only for testing purposes.
// This allows the DownloadIndexFile method to be mocked.
type chartRepo interface {
DownloadIndexFile() (string, error)
}

var _ chartRepo = (*repo.ChartRepository)(nil)

// newChartRepo exists only for testing purposes.
// This allows a test implementation of the repo.NewChartRepository function to exist.
type newChartRepo func(cfg *repo.Entry, getters getter.Providers) (chartRepo, error)

// loadIndexFile exists only for testing purposes.
// This allows a test implementation of the repo.LoadIndexFile function to exist.
type loadIndexFile func(path string) (*repo.IndexFile, error)

// defaultNewChartRepo is the default implementation of the newChartRepo function.
// It simply wraps the repo.NewChartRepository function.
// This variable should only be modified for testing purposes.
var defaultNewChartRepo newChartRepo = func(cfg *repo.Entry, getters getter.Providers) (chartRepo, error) {
return repo.NewChartRepository(cfg, getters)
}

// defaultLoadIndexFile is the default implementation of the loadIndexFile function.
// It simply wraps the repo.LoadIndexFile function.
// This variable should only be modified for testing purposes.
var defaultLoadIndexFile loadIndexFile = repo.LoadIndexFile

func locateLatestAirbyteChart(chartName, chartVersion, chartFlag string) string {
pterm.Debug.Printf("getting helm chart %q with version %q\n", chartName, chartVersion)

Expand Down Expand Up @@ -37,36 +68,54 @@ func locateLatestAirbyteChart(chartName, chartVersion, chartFlag string) string
}

func getLatestAirbyteChartUrlFromRepoIndex(repoName, repoUrl string) (string, error) {
chartRepo, err := repo.NewChartRepository(&repo.Entry{
chartRepository, err := defaultNewChartRepo(&repo.Entry{
Name: repoName,
URL: repoUrl,
}, getter.All(cli.New()))
if err != nil {
return "", fmt.Errorf("unable to access repo index: %w", err)
}

idxPath, err := chartRepo.DownloadIndexFile()
idxPath, err := chartRepository.DownloadIndexFile()
if err != nil {
return "", fmt.Errorf("unable to download index file: %w", err)
}

idx, err := repo.LoadIndexFile(idxPath)
idx, err := defaultLoadIndexFile(idxPath)
if err != nil {
return "", fmt.Errorf("unable to load index file (%s): %w", idxPath, err)
}

airbyteEntry, ok := idx.Entries["airbyte"]
entries, ok := idx.Entries["airbyte"]
if !ok {
return "", fmt.Errorf("no entry for airbyte in repo index")
}

if len(airbyteEntry) == 0 {
return "", fmt.Errorf("no chart version found")
if len(entries) == 0 {
return "", errors.New("no chart version found")
}

var latest *repo.ChartVersion
for _, entry := range entries {
version := entry.Version
// the semver library requires a `v` prefix
if !strings.HasPrefix(version, "v") {
version = "v" + version
}

if semver.Prerelease(version) == "" {
latest = entry
break
}
}

if latest == nil {
return "", fmt.Errorf("no valid version of airbyte chart found in repo index")
}

latest := airbyteEntry[0]
if len(latest.URLs) != 1 {
return "", fmt.Errorf("unexpected number of URLs")
return "", fmt.Errorf("unexpected number of URLs - %d", len(latest.URLs))
}

return airbyteRepoURL + "/" + latest.URLs[0], nil
}
Loading

0 comments on commit 97b7908

Please sign in to comment.