diff --git a/airflow/airflow.go b/airflow/airflow.go index 1e809f7ba..6898f8e12 100644 --- a/airflow/airflow.go +++ b/airflow/airflow.go @@ -150,6 +150,11 @@ func Init(path, airflowImageName, airflowImageTag, template string) error { var files map[string]string switch airflowversions.AirflowMajorVersionForRuntimeVersion(airflowImageTag) { case "3": + // Use the floating tag for the runtime version, so that the latest patch versions are automatically used + airflowImageFloatingTag := airflowversions.RuntimeVersionMajorMinor(airflowImageTag) + if airflowImageFloatingTag != "" { + airflowImageTag = airflowImageFloatingTag + } files = map[string]string{ ".dockerignore": Af3Dockerignore, "Dockerfile": fmt.Sprintf(Af3Dockerfile, airflowImageName, airflowImageTag), diff --git a/airflow/docker_registry_test.go b/airflow/docker_registry_test.go deleted file mode 100644 index a088aa719..000000000 --- a/airflow/docker_registry_test.go +++ /dev/null @@ -1,47 +0,0 @@ -package airflow - -import ( - "context" - - "github.com/astronomer/astro-cli/airflow/mocks" - "github.com/docker/docker/api/types/registry" - "github.com/stretchr/testify/mock" -) - -func (s *Suite) TestDockerRegistryInit() { - resp, err := DockerRegistryInit("test") - s.NoError(err) - s.Equal(resp.registry, "test") -} - -func (s *Suite) TestRegistryLogin() { - s.Run("success", func() { - mockClient := new(mocks.DockerRegistryAPI) - mockClient.On("NegotiateAPIVersion", context.Background()).Return(nil).Once() - mockClient.On("RegistryLogin", context.Background(), mock.AnythingOfType("registry.AuthConfig")).Return(registry.AuthenticateOKBody{}, nil).Once() - - handler := DockerRegistry{ - registry: "test", - cli: mockClient, - } - - err := handler.Login("", "") - s.NoError(err) - mockClient.AssertExpectations(s.T()) - }) - - s.Run("registry error", func() { - mockClient := new(mocks.DockerRegistryAPI) - mockClient.On("NegotiateAPIVersion", context.Background()).Return(nil).Once() - mockClient.On("RegistryLogin", context.Background(), mock.AnythingOfType("registry.AuthConfig")).Return(registry.AuthenticateOKBody{}, errMockDocker).Once() - - handler := DockerRegistry{ - registry: "test", - cli: mockClient, - } - - err := handler.Login("", "") - s.ErrorIs(err, errMockDocker) - mockClient.AssertExpectations(s.T()) - }) -} diff --git a/airflow_versions/runtime_versions.go b/airflow_versions/runtime_versions.go index fa3df6039..3ff80ad6c 100644 --- a/airflow_versions/runtime_versions.go +++ b/airflow_versions/runtime_versions.go @@ -40,9 +40,9 @@ func isNewFormat(v string) bool { return newFormatRegex.MatchString(v) } -// parseNewFormat splits a version string into its constituent parts +// ParseNewFormat splits a version string into its constituent parts // Returns nil if the version string does not have valid format -func parseNewFormat(v string) *versionParts { +func ParseNewFormat(v string) *versionParts { matches := newFormatRegex.FindStringSubmatch(v) if matches == nil { return nil @@ -60,7 +60,7 @@ func parseNewFormat(v string) *versionParts { // normalizeVersion converts new format to semver format // Returns the original string if it can't be normalized func normalizeVersion(v string) string { - parts := parseNewFormat(v) + parts := ParseNewFormat(v) if parts == nil { // maybe it didn't parse because it's semver, then semver will handle it // maybe it didn't parse because it's neither valid semver nor valid astrover, still semver will handle it @@ -141,6 +141,18 @@ func RuntimeVersionMajor(version string) string { return strings.TrimPrefix(semver.Major(v), "v") } +// RuntimeVersionMajorMinor returns the major and minor version prefix of the version string +// For example, RuntimeVersionMajorMinor("3.0-1") == "3.0" +// If v is an invalid version string, RuntimeVersionMajorMinor returns the empty string +func RuntimeVersionMajorMinor(version string) string { + version = stripVersionPrefix(version) + v := "v" + normalizeVersion(version) + if !semver.IsValid(v) { + return "" + } + return strings.TrimPrefix(semver.MajorMinor(v), "v") +} + func AirflowMajorVersionForRuntimeVersion(runtimeVersion string) string { if !isNewFormat(runtimeVersion) { version := stripVersionPrefix(runtimeVersion) diff --git a/airflow_versions/runtime_versions_test.go b/airflow_versions/runtime_versions_test.go index d9d82722e..0cc7b5f78 100644 --- a/airflow_versions/runtime_versions_test.go +++ b/airflow_versions/runtime_versions_test.go @@ -63,6 +63,27 @@ func (s *runtimeVersionsSuite) TestRuntimeVersionMajor() { } } +func (s *runtimeVersionsSuite) TestRuntimeVersionMajorMinor() { + tests := []struct { + name string + input string + expected string + }{ + {"new format", "3.0-7", "3.0"}, + {"old format", "12.1.1", "12.1"}, + {"invalid version", "invalid", ""}, + } + + for _, tt := range tests { + s.t.Run(tt.name, func(t *testing.T) { + result := RuntimeVersionMajorMinor(tt.input) + if result != tt.expected { + t.Errorf("RuntimeVersionMajorMinor(%q) = %q, want %q", tt.input, result, tt.expected) + } + }) + } +} + func (s *runtimeVersionsSuite) TestRuntimeVersionAirflowMajorVersion() { tests := []struct { name string diff --git a/cmd/airflow_test.go b/cmd/airflow_test.go index 31eb36b87..fa6e2386d 100644 --- a/cmd/airflow_test.go +++ b/cmd/airflow_test.go @@ -182,10 +182,6 @@ func (s *AirflowSuite) Test_airflowInitNonEmptyDir() { defer testUtil.MockUserInput(s.T(), "y")() err := airflowInit(cmd, args) s.NoError(err) - - b, _ := os.ReadFile(filepath.Join(s.tempDir, "Dockerfile")) - dockerfileContents := string(b) - s.True(strings.Contains(dockerfileContents, "FROM quay.io/astronomer/astro-runtime:")) }) } @@ -198,10 +194,6 @@ func (s *AirflowSuite) Test_airflowInitNoDefaultImageTag() { err := airflowInit(cmd, args) s.NoError(err) - // assert contents of Dockerfile - b, _ := os.ReadFile(filepath.Join(s.tempDir, "Dockerfile")) - dockerfileContents := string(b) - s.True(strings.Contains(dockerfileContents, "FROM quay.io/astronomer/astro-runtime:")) }) } @@ -305,10 +297,6 @@ func (s *AirflowSuite) TestAirflowInit() { os.Stdin = r err := airflowInit(cmd, args) s.NoError(err) - - b, _ := os.ReadFile(filepath.Join(s.tempDir, "Dockerfile")) - dockerfileContents := string(b) - s.True(strings.Contains(dockerfileContents, "FROM quay.io/astronomer/astro-runtime:")) }) s.Run("invalid project name", func() {