Skip to content

Commit

Permalink
feat: add support for --secret (#60)
Browse files Browse the repository at this point in the history
  • Loading branch information
colesnodgrass authored Jul 25, 2024
1 parent a52f0a6 commit f2a6d28
Show file tree
Hide file tree
Showing 8 changed files with 104 additions and 69 deletions.
10 changes: 5 additions & 5 deletions internal/cmd/local/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,14 @@ func dockerInstalled(ctx context.Context) (docker.Version, error) {
var err error
if dockerClient == nil {
if dockerClient, err = docker.New(ctx); err != nil {
pterm.Error.Println("Could not create Docker client")
return docker.Version{}, fmt.Errorf("%w: could not create client: %w", localerr.ErrDocker, err)
pterm.Error.Println("Unable to create Docker client")
return docker.Version{}, fmt.Errorf("%w: unable to create client: %w", localerr.ErrDocker, err)
}
}

version, err := dockerClient.Version(ctx)
if err != nil {
pterm.Error.Println("Could not communicate with the Docker daemon")
pterm.Error.Println("Unable to communicate with the Docker daemon")
return docker.Version{}, fmt.Errorf("%w: %w", localerr.ErrDocker, err)
}
pterm.Success.Println(fmt.Sprintf("Found Docker installation: version %s", version.Version))
Expand Down Expand Up @@ -72,13 +72,13 @@ func portAvailable(ctx context.Context, port int) error {
req, errInner := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("http://localhost:%d", port), nil)
if errInner != nil {
pterm.Error.Printfln("Port %d request could not be created", port)
return fmt.Errorf("%w: could not create request: %w", localerr.ErrPort, err)
return fmt.Errorf("%w: unable to create request: %w", localerr.ErrPort, err)
}

res, errInner := httpClient.Do(req)
if errInner != nil {
pterm.Error.Printfln("Port %d appears to already be in use", port)
return fmt.Errorf("%w: could not send request: %w", localerr.ErrPort, err)
return fmt.Errorf("%w: unable to send request: %w", localerr.ErrPort, err)
}

if res.StatusCode == http.StatusUnauthorized && strings.Contains(res.Header.Get("WWW-Authenticate"), "abctl") {
Expand Down
2 changes: 1 addition & 1 deletion internal/cmd/local/helm/helm.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func New(kubecfg, kubectx, namespace string) (Client, error) {

restCfg, err := k8sCfg.ClientConfig()
if err != nil {
return nil, fmt.Errorf("%w: could not create rest config: %w", localerr.ErrKubernetes, err)
return nil, fmt.Errorf("%w: unable to create rest config: %w", localerr.ErrKubernetes, err)
}

logger := helmLogger{}
Expand Down
19 changes: 6 additions & 13 deletions internal/cmd/local/k8s/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ type Client interface {
PersistentVolumeClaimDelete(ctx context.Context, namespace, name, volumeName string) error

// SecretCreateOrUpdate will update or create the secret name with the payload of data in the specified namespace
SecretCreateOrUpdate(ctx context.Context, secretType corev1.SecretType, namespace, name string, data map[string][]byte) error
SecretCreateOrUpdate(ctx context.Context, secret corev1.Secret) error

// ServiceGet returns a the service for the given namespace and name
ServiceGet(ctx context.Context, namespace, name string) (*corev1.Service, error)
Expand Down Expand Up @@ -175,28 +175,21 @@ func (d *DefaultK8sClient) PersistentVolumeClaimDelete(ctx context.Context, name
return d.ClientSet.CoreV1().PersistentVolumeClaims(namespace).Delete(ctx, name, metav1.DeleteOptions{})
}

func (d *DefaultK8sClient) SecretCreateOrUpdate(ctx context.Context, secretType corev1.SecretType, namespace, name string, data map[string][]byte) error {
secret := &corev1.Secret{
TypeMeta: metav1.TypeMeta{},
ObjectMeta: metav1.ObjectMeta{
Namespace: namespace,
Name: name,
},
Data: data,
Type: secretType,
}
func (d *DefaultK8sClient) SecretCreateOrUpdate(ctx context.Context, secret corev1.Secret) error {
namespace := secret.ObjectMeta.Namespace
name := secret.ObjectMeta.Name
_, err := d.ClientSet.CoreV1().Secrets(namespace).Get(ctx, name, metav1.GetOptions{})
if err == nil {
// update
if _, err := d.ClientSet.CoreV1().Secrets(namespace).Update(ctx, secret, metav1.UpdateOptions{}); err != nil {
if _, err := d.ClientSet.CoreV1().Secrets(namespace).Update(ctx, &secret, metav1.UpdateOptions{}); err != nil {
return fmt.Errorf("unable to update the secret %s: %w", name, err)
}

return nil
}

if k8serrors.IsNotFound(err) {
if _, err := d.ClientSet.CoreV1().Secrets(namespace).Create(ctx, secret, metav1.CreateOptions{}); err != nil {
if _, err := d.ClientSet.CoreV1().Secrets(namespace).Create(ctx, &secret, metav1.CreateOptions{}); err != nil {
return fmt.Errorf("unable to create the secret %s: %w", name, err)
}

Expand Down
83 changes: 63 additions & 20 deletions internal/cmd/local/local/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/airbytehq/abctl/internal/cmd/local/migrate"
"github.com/airbytehq/abctl/internal/cmd/local/paths"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/yaml"
"net/http"
"os"
"path/filepath"
Expand Down Expand Up @@ -186,7 +187,7 @@ func New(provider k8s.Provider, opts ...Option) (*Command, error) {
{
k8sVersion, err := c.k8s.ServerVersionGet()
if err != nil {
return nil, fmt.Errorf("%w: could not fetch kubernetes server version: %w", localerr.ErrKubernetes, err)
return nil, fmt.Errorf("%w: unable to fetch kubernetes server version: %w", localerr.ErrKubernetes, err)
}
c.tel.Attr("k8s_version", k8sVersion)
}
Expand All @@ -203,6 +204,7 @@ type InstallOpts struct {

HelmChartVersion string
ValuesFile string
Secrets []string
Migrate bool
Host string

Expand Down Expand Up @@ -251,12 +253,12 @@ func (c *Command) persistentVolume(ctx context.Context, namespace, name string)

pterm.Debug.Println(fmt.Sprintf("Creating directory '%s'", path))
if err := os.MkdirAll(path, 0766); err != nil {
pterm.Error.Println(fmt.Sprintf("Could not create directory '%s'", name))
pterm.Error.Println(fmt.Sprintf("Unable to create directory '%s'", name))
return fmt.Errorf("unable to create persistent volume '%s': %w", name, err)
}

if err := c.k8s.PersistentVolumeCreate(ctx, namespace, name); err != nil {
pterm.Error.Println(fmt.Sprintf("Could not create persistent volume '%s'", name))
pterm.Error.Println(fmt.Sprintf("Unable to create persistent volume '%s'", name))
return fmt.Errorf("unable to create persistent volume '%s': %w", name, err)
}

Expand All @@ -273,7 +275,7 @@ func (c *Command) persistentVolume(ctx context.Context, namespace, name string)
// access to the persisted volume directory.
pterm.Debug.Println(fmt.Sprintf("Updating permissions for '%s'", path))
if err := os.Chmod(path, 0777); err != nil {
pterm.Error.Println(fmt.Sprintf("Could not set permissions for '%s'", path))
pterm.Error.Println(fmt.Sprintf("Unable to set permissions for '%s'", path))
return fmt.Errorf("unable to set permissions for '%s': %w", path, err)
}

Expand All @@ -289,7 +291,7 @@ func (c *Command) persistentVolumeClaim(ctx context.Context, namespace, name, vo
if !c.k8s.PersistentVolumeClaimExists(ctx, namespace, name, volumeName) {
c.spinner.UpdateText(fmt.Sprintf("Creating persistent volume claim '%s'", name))
if err := c.k8s.PersistentVolumeClaimCreate(ctx, namespace, name, volumeName); err != nil {
pterm.Error.Println(fmt.Sprintf("Could not create persistent volume claim '%s'", name))
pterm.Error.Println(fmt.Sprintf("Unable to create persistent volume claim '%s'", name))
return fmt.Errorf("unable to create persistent volume claim '%s': %w", name, err)
}
pterm.Info.Println(fmt.Sprintf("Persistent volume claim '%s' created", name))
Expand All @@ -302,21 +304,21 @@ func (c *Command) persistentVolumeClaim(ctx context.Context, namespace, name, vo

// Install handles the installation of Airbyte
func (c *Command) Install(ctx context.Context, opts InstallOpts) error {
var values string
var vals string
if opts.ValuesFile != "" {
raw, err := os.ReadFile(opts.ValuesFile)
if err != nil {
return fmt.Errorf("unable to read values file '%s': %w", opts.ValuesFile, err)
}
values = string(raw)
vals = string(raw)
}

go c.watchEvents(ctx)

if !c.k8s.NamespaceExists(ctx, airbyteNamespace) {
c.spinner.UpdateText(fmt.Sprintf("Creating namespace '%s'", airbyteNamespace))
if err := c.k8s.NamespaceCreate(ctx, airbyteNamespace); err != nil {
pterm.Error.Println(fmt.Sprintf("Could not create namespace '%s'", airbyteNamespace))
pterm.Error.Println(fmt.Sprintf("Unable to create namespace '%s'", airbyteNamespace))
return fmt.Errorf("unable to create airbyte namespace: %w", err)
}
pterm.Info.Println(fmt.Sprintf("Namespace '%s' created", airbyteNamespace))
Expand Down Expand Up @@ -362,13 +364,36 @@ func (c *Command) Install(ctx context.Context, opts InstallOpts) error {
if opts.dockerAuth() {
pterm.Debug.Println(fmt.Sprintf("Creating '%s' secret", dockerAuthSecretName))
if err := c.handleDockerSecret(ctx, opts.DockerServer, opts.DockerUser, opts.DockerPass, opts.DockerEmail); err != nil {
pterm.Debug.Println(fmt.Sprintf("Could not create '%s' secret", dockerAuthSecretName))
pterm.Debug.Println(fmt.Sprintf("Unable to create '%s' secret", dockerAuthSecretName))
return fmt.Errorf("unable to create '%s' secret: %w", dockerAuthSecretName, err)
}
pterm.Debug.Println(fmt.Sprintf("Created '%s' secret", dockerAuthSecretName))
airbyteValues = append(airbyteValues, fmt.Sprintf("global.imagePullSecrets[0].name=%s", dockerAuthSecretName))
}

for _, secretFile := range opts.Secrets {
c.spinner.UpdateText(fmt.Sprintf("Creating secret from '%s'", secretFile))
raw, err := os.ReadFile(secretFile)
if err != nil {
pterm.Error.Println(fmt.Sprintf("Unable to read secret file '%s': %s", secretFile, err))
return fmt.Errorf("unable to read secret file '%s': %w", secretFile, err)
}

var secret corev1.Secret
if err := yaml.Unmarshal(raw, &secret); err != nil {
pterm.Error.Println(fmt.Sprintf("Unable to unmarshal secret file '%s': %s", secretFile, err))
return fmt.Errorf("unable to unmarshal secret file '%s': %w", secretFile, err)
}
secret.ObjectMeta.Namespace = airbyteNamespace

if err := c.k8s.SecretCreateOrUpdate(ctx, secret); err != nil {
pterm.Error.Println(fmt.Sprintf("Unable to create secret from file '%s'", secretFile))
return fmt.Errorf("unable to create secret from file '%s': %w", secretFile, err)
}

pterm.Success.Println(fmt.Sprintf("Secret from '%s' created or updated", secretFile))
}

if err := c.handleChart(ctx, chartRequest{
name: "airbyte",
repoName: airbyteRepoName,
Expand All @@ -378,7 +403,7 @@ func (c *Command) Install(ctx context.Context, opts InstallOpts) error {
chartVersion: opts.HelmChartVersion,
namespace: airbyteNamespace,
values: airbyteValues,
valuesYAML: values,
valuesYAML: vals,
}); err != nil {
return fmt.Errorf("unable to install airbyte chart: %w", err)
}
Expand Down Expand Up @@ -545,26 +570,44 @@ func (c *Command) handleBasicAuthSecret(ctx context.Context, user, pass string)
return fmt.Errorf("unable to hash basic auth password: %w", err)
}

data := map[string][]byte{"auth": []byte(fmt.Sprintf("%s:%s", user, hashedPass))}
if err := c.k8s.SecretCreateOrUpdate(ctx, corev1.SecretTypeOpaque, airbyteNamespace, "basic-auth", data); err != nil {
pterm.Error.Println("Could not create Basic-Auth secret")
secret := corev1.Secret{
TypeMeta: metav1.TypeMeta{},
ObjectMeta: metav1.ObjectMeta{
Namespace: airbyteNamespace,
Name: "basic-auth",
},
Data: map[string][]byte{"auth": []byte(fmt.Sprintf("%s:%s", user, hashedPass))},
StringData: nil,
Type: corev1.SecretTypeOpaque,
}

if err := c.k8s.SecretCreateOrUpdate(ctx, secret); err != nil {
pterm.Error.Println("Unable to create Basic-Auth secret")
return fmt.Errorf("unable to create Basic-Auth secret: %w", err)
}
pterm.Success.Println("Basic-Auth secret created")
return nil
}

func (c *Command) handleDockerSecret(ctx context.Context, server, user, pass, email string) error {
data := map[string][]byte{}
secret, err := docker.Secret(server, user, pass, email)
secretBody, err := docker.Secret(server, user, pass, email)
if err != nil {
pterm.Error.Println("Unable to create docker secret")
return fmt.Errorf("unable to create docker secret: %w", err)
}
data[corev1.DockerConfigJsonKey] = secret

if err := c.k8s.SecretCreateOrUpdate(ctx, corev1.SecretTypeDockerConfigJson, airbyteNamespace, dockerAuthSecretName, data); err != nil {
pterm.Error.Println("Could not create Docker-auth secret")
secret := corev1.Secret{
TypeMeta: metav1.TypeMeta{},
ObjectMeta: metav1.ObjectMeta{
Namespace: airbyteNamespace,
Name: dockerAuthSecretName,
},
Data: map[string][]byte{corev1.DockerConfigJsonKey: secretBody},
Type: corev1.SecretTypeDockerConfigJson,
}

if err := c.k8s.SecretCreateOrUpdate(ctx, secret); err != nil {
pterm.Error.Println("Unable to create Docker-auth secret")
return fmt.Errorf("unable to create docker-auth secret: %w", err)
}
pterm.Success.Println("Docker-Auth secret created")
Expand Down Expand Up @@ -598,8 +641,8 @@ func (c *Command) Status(_ context.Context) error {

rel, err := c.helm.GetRelease(name)
if err != nil {
pterm.Warning.Println("Could not get airbyte release")
pterm.Debug.Printfln("unable to get airbyte release: %s", err)
pterm.Warning.Println("Unable to fetch airbyte release")
pterm.Debug.Printfln("unable to fetch airbyte release: %s", err)
continue
}

Expand Down
10 changes: 5 additions & 5 deletions internal/cmd/local/local/cmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ func TestCommand_Install(t *testing.T) {
serverVersionGet: func() (string, error) {
return "test", nil
},
secretCreateOrUpdate: func(ctx context.Context, secretType coreV1.SecretType, namespace, name string, data map[string][]byte) error {
secretCreateOrUpdate: func(ctx context.Context, secret coreV1.Secret) error {
return nil
},
ingressExists: func(ctx context.Context, namespace string, ingress string) bool {
Expand Down Expand Up @@ -265,7 +265,7 @@ func TestCommand_Install_ValuesFile(t *testing.T) {
serverVersionGet: func() (string, error) {
return "test", nil
},
secretCreateOrUpdate: func(ctx context.Context, secretType coreV1.SecretType, namespace, name string, data map[string][]byte) error {
secretCreateOrUpdate: func(ctx context.Context, secret coreV1.Secret) error {
return nil
},
ingressExists: func(ctx context.Context, namespace string, ingress string) bool {
Expand Down Expand Up @@ -384,7 +384,7 @@ type mockK8sClient struct {
persistentVolumeClaimCreate func(ctx context.Context, namespace, name, volumeName string) error
persistentVolumeClaimExists func(ctx context.Context, namespace, name, volumeName string) bool
persistentVolumeClaimDelete func(ctx context.Context, namespace, name, volumeName string) error
secretCreateOrUpdate func(ctx context.Context, secretType coreV1.SecretType, namespace, name string, data map[string][]byte) error
secretCreateOrUpdate func(ctx context.Context, secret coreV1.Secret) error
serviceGet func(ctx context.Context, namespace, name string) (*coreV1.Service, error)
serverVersionGet func() (string, error)
eventsWatch func(ctx context.Context, namespace string) (watch.Interface, error)
Expand Down Expand Up @@ -471,9 +471,9 @@ func (m *mockK8sClient) PersistentVolumeClaimDelete(ctx context.Context, namespa
return nil
}

func (m *mockK8sClient) SecretCreateOrUpdate(ctx context.Context, secretType coreV1.SecretType, namespace, name string, data map[string][]byte) error {
func (m *mockK8sClient) SecretCreateOrUpdate(ctx context.Context, secret coreV1.Secret) error {
if m.secretCreateOrUpdate != nil {
return m.secretCreateOrUpdate(ctx, secretType, namespace, name, data)
return m.secretCreateOrUpdate(ctx, secret)
}

return nil
Expand Down
Loading

0 comments on commit f2a6d28

Please sign in to comment.