Skip to content

Commit c140e70

Browse files
authored
feat: v2 charts with backwards compatibility (#174)
1 parent a4697da commit c140e70

File tree

17 files changed

+846
-215
lines changed

17 files changed

+846
-215
lines changed

internal/cmd/images/manifest_cmd.go

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,29 @@ func (c *ManifestCmd) Run(ctx context.Context, newSvcMgrClients service.ManagerC
4545
func (c *ManifestCmd) findAirbyteImages(ctx context.Context, helmClient goHelm.Client) ([]string, error) {
4646
valuesYaml, err := helm.BuildAirbyteValues(ctx, helm.ValuesOpts{
4747
ValuesFile: c.Values,
48-
})
48+
}, c.ChartVersion)
4949
if err != nil {
5050
return nil, err
5151
}
5252

53-
airbyteChartLoc := helm.LocateLatestAirbyteChart(c.ChartVersion, c.Chart)
54-
return helm.FindImagesFromChart(helmClient, valuesYaml, airbyteChartLoc, c.ChartVersion)
53+
// Determine and set defaults for chart flags.
54+
err = c.setDefaultChartFlags(helmClient)
55+
if err != nil {
56+
return nil, fmt.Errorf("failed to set chart flag defaults: %w", err)
57+
}
58+
59+
return helm.FindImagesFromChart(helmClient, valuesYaml, c.Chart, c.ChartVersion)
60+
}
61+
62+
func (c *ManifestCmd) setDefaultChartFlags(helmClient goHelm.Client) error {
63+
resolver := helm.NewChartResolver(helmClient)
64+
resolvedChart, resolvedVersion, err := resolver.ResolveChartReference(c.Chart, c.ChartVersion)
65+
if err != nil {
66+
return fmt.Errorf("failed to resolve chart flags: %w", err)
67+
}
68+
69+
c.Chart = resolvedChart
70+
c.ChartVersion = resolvedVersion
71+
72+
return nil
5573
}

internal/cmd/local/install.go

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/airbytehq/abctl/internal/service"
1111
"github.com/airbytehq/abctl/internal/telemetry"
1212
"github.com/airbytehq/abctl/internal/trace"
13+
goHelm "github.com/mittwald/go-helm-client"
1314
"github.com/pterm/pterm"
1415
"go.opentelemetry.io/otel/attribute"
1516
)
@@ -103,6 +104,18 @@ func (i *InstallCmd) Run(ctx context.Context, provider k8s.Provider, newSvcMgrCl
103104
pterm.Success.Printfln("Cluster '%s' created", provider.ClusterName)
104105
}
105106

107+
// Load the required service manager clients.
108+
k8sClient, helmClient, err := newSvcMgrClients(provider.Kubeconfig, provider.Context)
109+
if err != nil {
110+
return err
111+
}
112+
113+
// Determine and set defaults for chart flags.
114+
err = i.setDefaultChartFlags(helmClient)
115+
if err != nil {
116+
return fmt.Errorf("failed to set chart defaults: %w", err)
117+
}
118+
106119
// Overrides Helm chart images.
107120
overrideImages := []string{}
108121

@@ -115,15 +128,6 @@ func (i *InstallCmd) Run(ctx context.Context, provider k8s.Provider, newSvcMgrCl
115128
overrideImages = append(overrideImages, "airbyte/db:"+helm.Psql17AirbyteTag)
116129
}
117130

118-
// Load the required service manager clients.
119-
// TODO(bernielomax): The Helm client will eventually be dependency-injected
120-
// into the build values function to support querying the Helm chart for
121-
// version compatibility operations.
122-
k8sClient, helmClient, err := newSvcMgrClients(provider.Kubeconfig, provider.Context)
123-
if err != nil {
124-
return err
125-
}
126-
127131
svcMgr, err := service.NewManager(provider,
128132
service.WithK8sClient(k8sClient),
129133
service.WithHelmClient(helmClient),
@@ -186,7 +190,7 @@ func (i *InstallCmd) installOpts(ctx context.Context, user string) (*service.Ins
186190

187191
opts := &service.InstallOpts{
188192
HelmChartVersion: i.ChartVersion,
189-
AirbyteChartLoc: helm.LocateLatestAirbyteChart(i.ChartVersion, i.Chart),
193+
AirbyteChartLoc: i.Chart,
190194
Secrets: i.Secret,
191195
Hosts: i.Host,
192196
LocalStorage: !supportMinio,
@@ -216,11 +220,26 @@ func (i *InstallCmd) installOpts(ctx context.Context, user string) (*service.Ins
216220
valuesOpts.TelemetryUser = user
217221
}
218222

219-
valuesYAML, err := helm.BuildAirbyteValues(ctx, valuesOpts)
223+
// Build the values.yaml file for the Airbyte chart.
224+
valuesYAML, err := helm.BuildAirbyteValues(ctx, valuesOpts, i.ChartVersion)
220225
if err != nil {
221226
return nil, err
222227
}
228+
223229
opts.HelmValuesYaml = valuesYAML
224230

225231
return opts, nil
226232
}
233+
234+
func (i *InstallCmd) setDefaultChartFlags(helmClient goHelm.Client) error {
235+
resolver := helm.NewChartResolver(helmClient)
236+
resolvedChart, resolvedVersion, err := resolver.ResolveChartReference(i.Chart, i.ChartVersion)
237+
if err != nil {
238+
return fmt.Errorf("failed to resolve chart flags: %w", err)
239+
}
240+
241+
i.Chart = resolvedChart
242+
i.ChartVersion = resolvedVersion
243+
244+
return nil
245+
}

internal/cmd/local/local_test.go

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@ import (
1010

1111
"github.com/airbytehq/abctl/internal/abctl"
1212
"github.com/airbytehq/abctl/internal/k8s"
13-
"github.com/airbytehq/abctl/internal/service"
14-
"github.com/alecthomas/kong"
15-
1613
"github.com/airbytehq/abctl/internal/paths"
14+
"github.com/airbytehq/abctl/internal/service"
1715
"github.com/airbytehq/abctl/internal/telemetry"
16+
"github.com/alecthomas/kong"
1817
"github.com/google/go-cmp/cmp"
18+
goHelm "github.com/mittwald/go-helm-client"
1919
)
2020

2121
func TestCheckAirbyteDir(t *testing.T) {
@@ -119,27 +119,39 @@ func TestValues_FileDoesntExist(t *testing.T) {
119119

120120
func TestValues_BadYaml(t *testing.T) {
121121
cmd := InstallCmd{Values: "./testdata/invalid.values.yaml"}
122-
err := cmd.Run(context.Background(), k8s.TestProvider, nil, telemetry.NoopClient{})
122+
// Does not need actual clients for tests.
123+
testFactory := func(kubeConfig, kubeContext string) (k8s.Client, goHelm.Client, error) {
124+
return nil, nil, nil
125+
}
126+
err := cmd.Run(context.Background(), k8s.TestProvider, testFactory, telemetry.NoopClient{})
123127
if err == nil {
124128
t.Fatal("expected error")
125129
}
126130

127-
if !strings.HasPrefix(err.Error(), "failed to unmarshal file") {
131+
if !strings.HasPrefix(err.Error(), "failed to build values yaml for v2 chart: failed to unmarshal file") {
128132
t.Errorf("unexpected error: %v", err)
129133
}
130134
}
131135

132136
func TestInvalidHostFlag_IpAddr(t *testing.T) {
133137
cmd := InstallCmd{Host: []string{"ok", "1.2.3.4"}}
134-
err := cmd.Run(context.Background(), k8s.TestProvider, nil, telemetry.NoopClient{})
138+
// Does not need actual clients for tests.
139+
testFactory := func(kubeConfig, kubeContext string) (k8s.Client, goHelm.Client, error) {
140+
return nil, nil, nil
141+
}
142+
err := cmd.Run(context.Background(), k8s.TestProvider, testFactory, telemetry.NoopClient{})
135143
if !errors.Is(err, abctl.ErrIpAddressForHostFlag) {
136144
t.Errorf("expected ErrIpAddressForHostFlag but got %v", err)
137145
}
138146
}
139147

140148
func TestInvalidHostFlag_IpAddrWithPort(t *testing.T) {
141149
cmd := InstallCmd{Host: []string{"ok", "1.2.3.4:8000"}}
142-
err := cmd.Run(context.Background(), k8s.TestProvider, nil, telemetry.NoopClient{})
150+
// Does not need actual clients for tests.
151+
testFactory := func(kubeConfig, kubeContext string) (k8s.Client, goHelm.Client, error) {
152+
return nil, nil, nil
153+
}
154+
err := cmd.Run(context.Background(), k8s.TestProvider, testFactory, telemetry.NoopClient{})
143155
if !errors.Is(err, abctl.ErrInvalidHostFlag) {
144156
t.Errorf("expected ErrInvalidHostFlag but got %v", err)
145157
}

internal/common/const.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ const (
88
AirbyteNamespace = "airbyte-abctl"
99
AirbyteKubeContext = "kind-airbyte-abctl"
1010
AirbyteRepoName = "airbyte"
11-
AirbyteRepoURL = "https://airbytehq.github.io/helm-charts"
11+
AirbyteRepoURLv1 = "https://airbytehq.github.io/helm-charts"
12+
AirbyteRepoURLv2 = "https://airbytehq.github.io/charts"
1213
NginxChartName = "nginx/ingress-nginx"
1314
NginxChartRelease = "ingress-nginx"
1415
NginxNamespace = "ingress-nginx"

internal/helm/airbyte_values.go

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"go.opentelemetry.io/otel/trace"
1010
)
1111

12+
// ValuesOpts contains configuration options for building Airbyte Helm values.
1213
type ValuesOpts struct {
1314
ValuesFile string
1415
LowResourceMode bool
@@ -21,10 +22,31 @@ type ValuesOpts struct {
2122
}
2223

2324
const (
25+
// Psql17AirbyteTag is the image tag for PostgreSQL 17 compatibility
2426
Psql17AirbyteTag = "1.7.0-17"
2527
)
2628

27-
func BuildAirbyteValues(ctx context.Context, opts ValuesOpts) (string, error) {
29+
// BuildAirbyteValues generates a values yaml string for the Airbyte Helm chart based on the chart version.
30+
// It delegates to BuildAirbyteValuesV1 for v1 charts and BuildAirbyteValuesV2 for v2+ charts.
31+
func BuildAirbyteValues(ctx context.Context, valuesOpts ValuesOpts, chartVersion string) (string, error) {
32+
if ChartIsV2Plus(chartVersion) {
33+
valuesYAML, err := buildAirbyteValuesV2(ctx, valuesOpts)
34+
if err != nil {
35+
return "", fmt.Errorf("failed to build values yaml for v2 chart: %w", err)
36+
}
37+
return valuesYAML, nil
38+
} else {
39+
valuesYAML, err := buildAirbyteValuesV1(ctx, valuesOpts)
40+
if err != nil {
41+
return "", fmt.Errorf("failed to build values yaml for v1 chart: %w", err)
42+
}
43+
return valuesYAML, nil
44+
}
45+
}
46+
47+
// buildAirbyteValuesV1 generates values string for v1 Airbyte Helm charts.
48+
// It applies configuration options and merges them with user-provided values file.
49+
func buildAirbyteValuesV1(ctx context.Context, opts ValuesOpts) (string, error) {
2850
span := trace.SpanFromContext(ctx)
2951

3052
vals := []string{
@@ -89,6 +111,74 @@ func BuildAirbyteValues(ctx context.Context, opts ValuesOpts) (string, error) {
89111
return mergeValuesWithValuesYAML(vals, fileVals)
90112
}
91113

114+
// buildAirbyteValuesV2 generates values string for v2+ Airbyte Helm charts.
115+
// It applies configuration options and merges them with user-provided values file.
116+
func buildAirbyteValuesV2(ctx context.Context, opts ValuesOpts) (string, error) {
117+
span := trace.SpanFromContext(ctx)
118+
119+
vals := []string{
120+
"server.env_vars.WEBAPP_URL=http://airbyte-abctl-airbyte-server-svc:80",
121+
"global.env_vars.AIRBYTE_INSTALLATION_ID=" + opts.TelemetryUser,
122+
"global.jobs.resources.limits.cpu=3",
123+
"global.jobs.resources.limits.memory=4Gi",
124+
"airbyte-bootloader.env_vars.PLATFORM_LOG_FORMAT=json",
125+
}
126+
127+
if opts.LocalStorage {
128+
vals = append(vals, "global.storage.type=local")
129+
}
130+
131+
if opts.EnablePsql17 {
132+
vals = append(vals, "postgresql.image.tag="+Psql17AirbyteTag)
133+
}
134+
135+
span.SetAttributes(
136+
attribute.Bool("low-resource-mode", opts.LowResourceMode),
137+
attribute.Bool("insecure-cookies", opts.InsecureCookies),
138+
attribute.Bool("image-pull-secret", opts.ImagePullSecret != ""),
139+
attribute.Bool("local-storage", opts.LocalStorage),
140+
)
141+
142+
if !opts.DisableAuth {
143+
vals = append(vals, "global.auth.enabled=true")
144+
}
145+
146+
if opts.LowResourceMode {
147+
vals = append(vals,
148+
"server.env_vars.JOB_RESOURCE_VARIANT_OVERRIDE=lowresource",
149+
"global.jobs.resources.requests.cpu=0",
150+
"global.jobs.resources.requests.memory=0",
151+
152+
"connectorBuilderServer.enabled=false",
153+
154+
"workloadLauncher.env_vars.CHECK_JOB_MAIN_CONTAINER_CPU_REQUEST=0",
155+
"workloadLauncher.env_vars.CHECK_JOB_MAIN_CONTAINER_MEMORY_REQUEST=0",
156+
"workloadLauncher.env_vars.DISCOVER_JOB_MAIN_CONTAINER_CPU_REQUEST=0",
157+
"workloadLauncher.env_vars.DISCOVER_JOB_MAIN_CONTAINER_MEMORY_REQUEST=0",
158+
"workloadLauncher.env_vars.SPEC_JOB_MAIN_CONTAINER_CPU_REQUEST=0",
159+
"workloadLauncher.env_vars.SPEC_JOB_MAIN_CONTAINER_MEMORY_REQUEST=0",
160+
"workloadLauncher.env_vars.SIDECAR_MAIN_CONTAINER_CPU_REQUEST=0",
161+
"workloadLauncher.env_vars.SIDECAR_MAIN_CONTAINER_MEMORY_REQUEST=0",
162+
)
163+
}
164+
165+
if opts.ImagePullSecret != "" {
166+
vals = append(vals, fmt.Sprintf("global.imagePullSecrets[0].name=%s", opts.ImagePullSecret))
167+
}
168+
169+
if opts.InsecureCookies {
170+
// Boolean is a string value in the v1 Helm chart.
171+
vals = append(vals, `global.auth.cookieSecureSetting="false"`)
172+
}
173+
174+
fileVals, err := maps.FromYAMLFile(opts.ValuesFile)
175+
if err != nil {
176+
return "", err
177+
}
178+
179+
return mergeValuesWithValuesYAML(vals, fileVals)
180+
}
181+
92182
// mergeValuesWithValuesYAML ensures that the values defined within this code have a lower
93183
// priority than any values defined in a values.yaml file.
94184
// By default, the helm-client we're using reversed this priority, putting the values

0 commit comments

Comments
 (0)