diff --git a/go.mod b/go.mod index 4a82f8b..2c5b0f5 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/airbytehq/abctl -go 1.22.2 +go 1.23.1 require ( github.com/alecthomas/kong v0.9.0 @@ -15,6 +15,7 @@ require ( github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 github.com/pterm/pterm v0.12.79 golang.org/x/mod v0.17.0 + gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 helm.sh/helm/v3 v3.14.2 k8s.io/api v0.29.2 @@ -157,7 +158,6 @@ require ( google.golang.org/protobuf v1.33.0 // indirect gopkg.in/evanphx/json-patch.v5 v5.7.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect k8s.io/apiextensions-apiserver v0.29.0 // indirect k8s.io/apiserver v0.29.0 // indirect k8s.io/cli-runtime v0.29.0 // indirect diff --git a/go.sum b/go.sum index 9965fb1..cbc4be4 100644 --- a/go.sum +++ b/go.sum @@ -10,8 +10,6 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9 github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= -github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= @@ -50,8 +48,6 @@ github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0= -github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= github.com/alessio/shellescape v1.4.2 h1:MHPfaU+ddJ0/bYWpgIeUnQUqKrlJ1S7BfEYPM4uEoM0= github.com/alessio/shellescape v1.4.2/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= @@ -618,8 +614,6 @@ oras.land/oras-go v1.2.5 h1:XpYuAwAb0DfQsunIyMfeET92emK8km3W4yEzZvUbsTo= oras.land/oras-go v1.2.5/go.mod h1:PuAwRShRZCsZb7g8Ar3jKKQR/2A/qN+pkYxIOd/FAoo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= -sigs.k8s.io/kind v0.23.0 h1:8fyDGWbWTeCcCTwA04v4Nfr45KKxbSPH1WO9K+jVrBg= -sigs.k8s.io/kind v0.23.0/go.mod h1:ZQ1iZuJLh3T+O8fzhdi3VWcFTzsdXtNv2ppsHc8JQ7s= sigs.k8s.io/kind v0.24.0 h1:g4y4eu0qa+SCeKESLpESgMmVFBebL0BDa6f777OIWrg= sigs.k8s.io/kind v0.24.0/go.mod h1:t7ueEpzPYJvHA8aeLtI52rtFftNgUYUaCwvxjk7phfw= sigs.k8s.io/kustomize/api v0.16.0 h1:/zAR4FOQDCkgSDmVzV2uiFbuy9bhu3jEzthrHCuvm1g= diff --git a/internal/cmd/local/local/install.go b/internal/cmd/local/local/install.go index 917f5e4..6b822be 100644 --- a/internal/cmd/local/local/install.go +++ b/internal/cmd/local/local/install.go @@ -27,8 +27,8 @@ import ( "helm.sh/helm/v3/pkg/release" "helm.sh/helm/v3/pkg/repo" corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" eventsv1 "k8s.io/api/events/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) const ( @@ -46,7 +46,7 @@ type InstallOpts struct { ValuesFile string Secrets []string Migrate bool - Host string + Hosts []string Docker *docker.Docker @@ -307,7 +307,7 @@ func (c *Command) Install(ctx context.Context, opts InstallOpts) error { return fmt.Errorf("unable to install nginx chart: %w", err) } - if err := c.handleIngress(ctx, opts.Host); err != nil { + if err := c.handleIngress(ctx, opts.Hosts); err != nil { return err } @@ -329,12 +329,12 @@ func (c *Command) Install(ctx context.Context, opts InstallOpts) error { return nil } -func (c *Command) handleIngress(ctx context.Context, host string) error { +func (c *Command) handleIngress(ctx context.Context, hosts []string) error { c.spinner.UpdateText("Checking for existing Ingress") if c.k8s.IngressExists(ctx, airbyteNamespace, airbyteIngress) { pterm.Success.Println("Found existing Ingress") - if err := c.k8s.IngressUpdate(ctx, airbyteNamespace, ingress(host)); err != nil { + if err := c.k8s.IngressUpdate(ctx, airbyteNamespace, ingress(hosts)); err != nil { pterm.Error.Printfln("Unable to update existing Ingress") return fmt.Errorf("unable to update existing ingress: %w", err) } @@ -343,7 +343,7 @@ func (c *Command) handleIngress(ctx context.Context, host string) error { } pterm.Info.Println("No existing Ingress found, creating one") - if err := c.k8s.IngressCreate(ctx, airbyteNamespace, ingress(host)); err != nil { + if err := c.k8s.IngressCreate(ctx, airbyteNamespace, ingress(hosts)); err != nil { pterm.Error.Println("Unable to create ingress") return fmt.Errorf("unable to create ingress: %w", err) } diff --git a/internal/cmd/local/local/specs.go b/internal/cmd/local/local/specs.go index 9772628..272ac1b 100644 --- a/internal/cmd/local/local/specs.go +++ b/internal/cmd/local/local/specs.go @@ -2,22 +2,34 @@ package local import ( "fmt" + "slices" networkingv1 "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // ingress creates an ingress type for defining the webapp ingress rules. -func ingress(host string) *networkingv1.Ingress { +func ingress(hosts []string) *networkingv1.Ingress { var ingressClassName = "nginx" - // Always add a localhost and host.docker.internal route. - // This is necessary to ensure that this code can verify the Airbyte installation via `localhost`. - // Additionally, make it easy for dockerized applications (Airflow) to talk with Airbyte over the docker host. - rules := []networkingv1.IngressRule{ingressRule("localhost"), ingressRule("host.docker.internal")} - // If a host that isn't `localhost` was provided, create a second rule for that host. - // This is required to support non-local installation, such as running on an EC2 instance. - if host != "localhost" { + // if no host is defined, default to an empty host + if len(hosts) == 0 { + hosts = append(hosts, "") + } else { + // If a host that isn't `localhost` was provided, create a second rule for localhost. + // This is required to ensure we can talk to Airbyte via localhost + if !slices.Contains(hosts, "localhost") { + hosts = append(hosts, "localhost") + } + // If a host that isn't `host.docker.internal` was provided, create a second rule for localhost. + // This is required to ensure we can talk to other containers. + if !slices.Contains(hosts, "host.docker.internal") { + hosts = append(hosts, "host.docker.internal") + } + } + + var rules []networkingv1.IngressRule + for _, host := range hosts { rules = append(rules, ingressRule(host)) } diff --git a/internal/cmd/local/local/specs_test.go b/internal/cmd/local/local/specs_test.go new file mode 100644 index 0000000..56bec16 --- /dev/null +++ b/internal/cmd/local/local/specs_test.go @@ -0,0 +1,68 @@ +package local + +import ( + "sort" + "testing" + + "github.com/google/go-cmp/cmp" + networkingv1 "k8s.io/api/networking/v1" +) + +func TestIngress(t *testing.T) { + tests := []struct { + name string + hosts []string + expHosts []string + }{ + { + name: "nil hosts", + hosts: nil, + expHosts: []string{""}, + }, + { + name: "empty hosts", + hosts: []string{}, + expHosts: []string{""}, + }, + { + name: "single new host", + hosts: []string{"example.test"}, + expHosts: []string{"example.test", "localhost", "host.docker.internal"}, + }, + { + name: "localhost", + hosts: []string{"localhost"}, + expHosts: []string{"localhost", "host.docker.internal"}, + }, + { + name: "host.docker.internal", + hosts: []string{"host.docker.internal"}, + expHosts: []string{"localhost", "host.docker.internal"}, + }, + { + name: "multiple new hosts", + hosts: []string{"abc.test", "xyz.test"}, + expHosts: []string{"abc.test", "localhost", "host.docker.internal", "xyz.test"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actHosts := extractHosts(ingress(tt.hosts)) + sort.Strings(actHosts) + sort.Strings(tt.expHosts) + if d := cmp.Diff(tt.expHosts, actHosts); d != "" { + t.Errorf("unexpected hosts (-want, +got):\n%v", d) + } + }) + } +} + +// extractHosts returns the host fields from the ingress rules. +func extractHosts(ingress *networkingv1.Ingress) []string { + var hosts []string + for _, h := range ingress.Spec.Rules { + hosts = append(hosts, h.Host) + } + return hosts +} diff --git a/internal/cmd/local/local_install.go b/internal/cmd/local/local_install.go index a9cf800..e869b15 100644 --- a/internal/cmd/local/local_install.go +++ b/internal/cmd/local/local_install.go @@ -17,7 +17,7 @@ type InstallCmd struct { DockerPassword string `group:"docker" help:"Docker password." env:"ABCTL_LOCAL_INSTALL_DOCKER_PASSWORD"` DockerServer string `group:"docker" default:"https://index.docker.io/v1/" help:"Docker server." env:"ABCTL_LOCAL_INSTALL_DOCKER_SERVER"` DockerUsername string `group:"docker" help:"Docker username." env:"ABCTL_LOCAL_INSTALL_DOCKER_USERNAME"` - Host string `default:"localhost" help:"HTTP ingress host."` + Host []string `help:"HTTP ingress host."` InsecureCookies bool `help:"Allow cookies to be served over HTTP."` LowResourceMode bool `help:"Run Airbyte in low resource mode."` Migrate bool `help:"Migrate data from a previous Docker Compose Airbyte installation."` @@ -106,7 +106,7 @@ func (i *InstallCmd) Run(ctx context.Context, provider k8s.Provider, telClient t Secrets: i.Secret, Migrate: i.Migrate, Docker: dockerClient, - Host: i.Host, + Hosts: i.Host, DockerServer: i.DockerServer, DockerUser: i.DockerUsername,