• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

astronomer / astro-cli / c96acaea-1e4f-4b01-8d01-6e86d38d5afb

02 Sep 2025 01:09PM UTC coverage: 39.378% (+0.004%) from 39.374%
c96acaea-1e4f-4b01-8d01-6e86d38d5afb

Pull #1926

circleci

pgvishnuram
fix condition
Pull Request #1926: improve the dag server upload url flow

15 of 17 new or added lines in 1 file covered. (88.24%)

6 existing lines in 1 file now uncovered.

23655 of 60071 relevant lines covered (39.38%)

11.03 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.59
/software/deploy/deploy.go
1
package deploy
2

3
import (
4
        "errors"
5
        "fmt"
6
        neturl "net/url"
7
        "os"
8
        "path/filepath"
9
        "strconv"
10
        "strings"
11
        "time"
12

13
        "github.com/astronomer/astro-cli/airflow"
14
        "github.com/astronomer/astro-cli/airflow/types"
15
        "github.com/astronomer/astro-cli/config"
16
        "github.com/astronomer/astro-cli/docker"
17
        "github.com/astronomer/astro-cli/houston"
18
        "github.com/astronomer/astro-cli/pkg/fileutil"
19
        "github.com/astronomer/astro-cli/pkg/input"
20
        "github.com/astronomer/astro-cli/pkg/logger"
21
        "github.com/astronomer/astro-cli/pkg/printutil"
22
)
23

24
var (
25
        // this is used to monkey patch the function in order to write unit test cases
26
        imageHandlerInit = airflow.ImageHandlerInit
27

28
        dockerfile = "Dockerfile"
29

30
        deployImagePlatformSupport = []string{"linux/amd64"}
31

32
        gzipFile = fileutil.GzipFile
33

34
        getDeploymentIDForCurrentCommandVar = getDeploymentIDForCurrentCommand
35
)
36

37
var (
38
        ErrNoWorkspaceID                         = errors.New("no workspace id provided")
39
        errNoDomainSet                           = errors.New("no domain set, re-authenticate")
40
        errInvalidDeploymentID                   = errors.New("please specify a valid deployment ID")
41
        errDeploymentNotFound                    = errors.New("no airflow deployments found")
42
        errInvalidDeploymentSelected             = errors.New("invalid deployment selection\n") //nolint
43
        ErrDagOnlyDeployDisabledInConfig         = errors.New("to perform this operation, set both deployments.dagOnlyDeployment and deployments.configureDagDeployment to true in your Astronomer cluster")
44
        ErrDagOnlyDeployNotEnabledForDeployment  = errors.New("to perform this operation, first set the Deployment type to 'dag_deploy' via the UI or the API or the CLI")
45
        ErrEmptyDagFolderUserCancelledOperation  = errors.New("no DAGs found in the dags folder. User canceled the operation")
46
        ErrBYORegistryDomainNotSet               = errors.New("Custom registry host is not set in config. It can be set at astronomer.houston.config.deployments.registry.protectedCustomRegistry.updateRegistry.host") //nolint
47
        ErrDeploymentTypeIncorrectForImageOnly   = errors.New("--image only works for Dag-only, Git-sync-based and NFS-based deployments")
48
        WarningInvalidImageNameMsg               = "WARNING! The image in your Dockerfile '%s' is not based on Astro Runtime and is not supported. Change your Dockerfile with an image that pulls from 'quay.io/astronomer/astro-runtime' to proceed.\n"
49
        ErrNoRuntimeLabelOnCustomImage           = errors.New("the image should have label io.astronomer.docker.runtime.version")
50
        ErrRuntimeVersionNotPassedForRemoteImage = errors.New("if --image-name and --remote is passed, it's mandatory to pass --runtime-version")
51
)
52

53
const (
54
        houstonDeploymentHeader       = "Authenticated to %s \n\n"
55
        houstonSelectDeploymentPrompt = "Select which airflow deployment you want to deploy to:"
56
        houstonDeploymentPrompt       = "Deploying: %s\n"
57

58
        imageBuildingPrompt = "Building image..."
59

60
        warningInvalidImageName                   = "WARNING! The image in your Dockerfile is pulling from '%s', which is not supported. We strongly recommend that you use Astronomer Certified or Runtime images that pull from 'astronomerinc/ap-airflow', 'quay.io/astronomer/ap-airflow' or 'quay.io/astronomer/astro-runtime'. If you're running a custom image, you can override this. Are you sure you want to continue?\n"
61
        warningInvalidNameTag                     = "WARNING! You are about to push an image using the '%s' tag. This is not recommended.\nPlease use one of the following tags: %s.\nAre you sure you want to continue?"
62
        warningInvalidNameTagEmptyRecommendations = "WARNING! You are about to push an image using the '%s' tag. This is not recommended.\nAre you sure you want to continue?"
63

64
        registryDomainPrefix              = "registry."
65
        runtimeImageLabel                 = "io.astronomer.docker.runtime.version"
66
        airflowImageLabel                 = "io.astronomer.docker.airflow.version"
67
        composeSkipImageBuildingPromptMsg = "Skipping building image since --image-name flag is used..."
68
)
69

70
var tab = printutil.Table{
71
        Padding:        []int{5, 30, 30, 50},
72
        DynamicPadding: true,
73
        Header:         []string{"#", "LABEL", "DEPLOYMENT NAME", "WORKSPACE", "DEPLOYMENT ID"},
74
}
75

76
func Airflow(houstonClient houston.ClientInterface, path, deploymentID, wsID, byoRegistryDomain string, ignoreCacheDeploy, byoRegistryEnabled, prompt bool, description string, isImageOnlyDeploy bool, imageName string) (string, error) {
14✔
77
        deploymentID, deployments, err := getDeploymentIDForCurrentCommand(houstonClient, wsID, deploymentID, prompt)
14✔
78
        if err != nil {
21✔
79
                return deploymentID, err
7✔
80
        }
7✔
81

82
        c, _ := config.GetCurrentContext()
7✔
83
        cloudDomain := c.Domain
7✔
84
        nextTag := ""
7✔
85
        releaseName := ""
7✔
86
        for i := range deployments {
14✔
87
                deployment := deployments[i]
7✔
88
                if deployment.ID == deploymentID {
14✔
89
                        nextTag = deployment.DeploymentInfo.NextCli
7✔
90
                        releaseName = deployment.ReleaseName
7✔
91
                }
7✔
92
        }
93

94
        if byoRegistryEnabled {
7✔
95
                nextTag = "deploy-" + time.Now().UTC().Format("2006-01-02T15-04") // updating nextTag logic for private registry, since houston won't maintain next tag in case of BYO registry
×
96
        }
×
97

98
        deploymentInfo, err := houston.Call(houstonClient.GetDeployment)(deploymentID)
7✔
99
        if err != nil {
8✔
100
                return deploymentID, fmt.Errorf("failed to get deployment info: %w", err)
1✔
101
        }
1✔
102

103
        appConfig, err := houston.Call(houstonClient.GetAppConfig)(deploymentInfo.ClusterID)
6✔
104
        if err != nil {
6✔
105
                return deploymentID, fmt.Errorf("failed to get app config: %w", err)
×
106
        }
×
107

108
        if appConfig != nil && appConfig.Flags.BYORegistryEnabled {
6✔
109
                byoRegistryEnabled = true
×
110
                byoRegistryDomain = appConfig.BYORegistryDomain
×
111
                if byoRegistryDomain == "" {
×
112
                        return deploymentID, ErrBYORegistryDomainNotSet
×
113
                }
×
114
        }
115

116
        // isImageOnlyDeploy is not valid for image-based deployments since image-based deployments inherently mean that the image itself contains dags.
117
        // If we deploy only the image, the deployment will not have any dags for image-based deployments.
118
        // Even on astro, image-based deployments are not allowed to be deployed with --image flag.
119
        if isImageOnlyDeploy && deploymentInfo.DagDeployment.Type == houston.ImageDeploymentType {
7✔
120
                return "", ErrDeploymentTypeIncorrectForImageOnly
1✔
121
        }
1✔
122
        // We don't need to exclude the dags from the image because the dags present in the image are not respected anyways for non-image based deployments
123

124
        fmt.Printf(houstonDeploymentPrompt, releaseName)
5✔
125

5✔
126
        // Build the image to deploy
5✔
127
        err = buildPushDockerImage(houstonClient, &c, deploymentInfo, releaseName, path, nextTag, cloudDomain, byoRegistryDomain, ignoreCacheDeploy, byoRegistryEnabled, description, imageName)
5✔
128
        if err != nil {
7✔
129
                return deploymentID, err
2✔
130
        }
2✔
131

132
        deploymentLink := getAirflowUILink(deploymentID, deploymentInfo.Urls)
3✔
133
        fmt.Printf("Successfully pushed Docker image to Astronomer registry, it can take a few minutes to update the deployment with the new image. Navigate to the Astronomer UI to confirm the state of your deployment (%s).\n", deploymentLink)
3✔
134

3✔
135
        return deploymentID, nil
3✔
136
}
137

138
// Find deployment ID in deployments slice
139
func deploymentExists(deploymentID string, deployments []houston.Deployment) bool {
10✔
140
        for idx := range deployments {
20✔
141
                deployment := deployments[idx]
10✔
142
                if deployment.ID == deploymentID {
18✔
143
                        return true
8✔
144
                }
8✔
145
        }
146
        return false
2✔
147
}
148

149
func validateRuntimeVersion(houstonClient houston.ClientInterface, tag string, deploymentInfo *houston.Deployment) error {
11✔
150
        // Get valid image tags for platform using Deployment Info request
11✔
151
        deploymentConfig, err := houston.Call(houstonClient.GetDeploymentConfig)(nil)
11✔
152
        if err != nil {
12✔
153
                return err
1✔
154
        }
1✔
155
        vars := make(map[string]interface{})
10✔
156
        vars["clusterId"] = deploymentInfo.ClusterID
10✔
157
        // ignoring the error as user can be connected to platform where runtime is not enabled
10✔
158
        runtimeReleases, _ := houston.Call(houstonClient.GetRuntimeReleases)(vars)
10✔
159
        var validTags string
10✔
160
        if config.CFG.ShowWarnings.GetBool() && deploymentInfo.DesiredAirflowVersion != "" && !deploymentConfig.IsValidTag(tag) {
12✔
161
                validTags = strings.Join(deploymentConfig.GetValidTags(tag), ", ")
2✔
162
        }
2✔
163
        if config.CFG.ShowWarnings.GetBool() && deploymentInfo.DesiredRuntimeVersion != "" && !runtimeReleases.IsValidVersion(tag) {
10✔
164
                validTags = strings.Join(runtimeReleases.GreaterVersions(tag), ", ")
×
165
        }
×
166
        if validTags != "" {
10✔
167
                validTags := strings.Join(deploymentConfig.GetValidTags(tag), ", ")
×
168

×
169
                msg := fmt.Sprintf(warningInvalidNameTag, tag, validTags)
×
170
                if validTags == "" {
×
171
                        msg = fmt.Sprintf(warningInvalidNameTagEmptyRecommendations, tag)
×
172
                }
×
173

174
                i, _ := input.Confirm(msg)
×
175
                if !i {
×
176
                        fmt.Println("Canceling deploy...")
×
177
                        os.Exit(1)
×
178
                }
×
179
        }
180
        return nil
10✔
181
}
182

183
func UpdateDeploymentImage(houstonClient houston.ClientInterface, deploymentID, wsID, runtimeVersion, imageName string) (string, error) {
5✔
184
        if runtimeVersion == "" {
6✔
185
                return "", ErrRuntimeVersionNotPassedForRemoteImage
1✔
186
        }
1✔
187
        deploymentID, _, err := getDeploymentIDForCurrentCommandVar(houstonClient, wsID, deploymentID, deploymentID == "")
4✔
188
        if err != nil {
5✔
189
                return "", err
1✔
190
        }
1✔
191
        if deploymentID == "" {
3✔
192
                return "", errInvalidDeploymentID
×
193
        }
×
194
        deploymentInfo, err := houston.Call(houstonClient.GetDeployment)(deploymentID)
3✔
195
        if err != nil {
4✔
196
                return "", fmt.Errorf("failed to get deployment info: %w", err)
1✔
197
        }
1✔
198
        fmt.Println("Skipping building the image since --image-name flag is used...")
2✔
199
        req := houston.UpdateDeploymentImageRequest{ReleaseName: deploymentInfo.ReleaseName, Image: imageName, AirflowVersion: "", RuntimeVersion: runtimeVersion}
2✔
200
        _, err = houston.Call(houstonClient.UpdateDeploymentImage)(req)
2✔
201
        fmt.Println("Image successfully updated")
2✔
202
        return deploymentID, err
2✔
203
}
204

205
func pushDockerImage(byoRegistryEnabled bool, byoRegistryDomain, name, nextTag, cloudDomain string, imageHandler airflow.ImageHandler, houstonClient houston.ClientInterface, c *config.Context, customImageName string) error {
9✔
206
        var registry, remoteImage, token string
9✔
207
        if byoRegistryEnabled {
12✔
208
                registry = byoRegistryDomain
3✔
209
                remoteImage = fmt.Sprintf("%s:%s", registry, fmt.Sprintf("%s-%s", name, nextTag))
3✔
210
        } else {
9✔
211
                registry = registryDomainPrefix + cloudDomain
6✔
212
                remoteImage = fmt.Sprintf("%s/%s", registry, airflow.ImageName(name, nextTag))
6✔
213
                token = c.Token
6✔
214
        }
6✔
215
        if customImageName != "" {
11✔
216
                if tagFromImageName := getGetTagFromImageName(customImageName); tagFromImageName != "" {
3✔
217
                        remoteImage = fmt.Sprintf("%s:%s", registry, tagFromImageName)
1✔
218
                }
1✔
219
        }
220
        useShaAsTag := config.CFG.ShaAsTag.GetBool()
9✔
221
        sha, err := imageHandler.Push(remoteImage, "", token, useShaAsTag)
9✔
222
        if err != nil {
10✔
223
                return err
1✔
224
        }
1✔
225
        if byoRegistryEnabled {
11✔
226
                if useShaAsTag {
4✔
227
                        remoteImage = fmt.Sprintf("%s@%s", registry, sha)
1✔
228
                }
1✔
229
                runtimeVersion, _ := imageHandler.GetLabel("", runtimeImageLabel)
3✔
230
                airflowVersion, _ := imageHandler.GetLabel("", airflowImageLabel)
3✔
231
                req := houston.UpdateDeploymentImageRequest{ReleaseName: name, Image: remoteImage, AirflowVersion: airflowVersion, RuntimeVersion: runtimeVersion}
3✔
232
                _, err = houston.Call(houstonClient.UpdateDeploymentImage)(req)
3✔
233
                return err
3✔
234
        }
235
        return nil
5✔
236
}
237

238
func buildDockerImageForCustomImage(imageHandler airflow.ImageHandler, customImageName string, deploymentInfo *houston.Deployment, houstonClient houston.ClientInterface) error {
3✔
239
        fmt.Println(composeSkipImageBuildingPromptMsg)
3✔
240
        err := imageHandler.TagLocalImage(customImageName)
3✔
241
        if err != nil {
3✔
242
                return err
×
243
        }
×
244
        runtimeLabel, err := imageHandler.GetLabel("", airflow.RuntimeImageLabel)
3✔
245
        if err != nil {
3✔
246
                fmt.Println("unable get runtime version from image")
×
247
                return err
×
248
        }
×
249
        if runtimeLabel == "" {
4✔
250
                return ErrNoRuntimeLabelOnCustomImage
1✔
251
        }
1✔
252
        err = validateRuntimeVersion(houstonClient, runtimeLabel, deploymentInfo)
2✔
253
        return err
2✔
254
}
255

256
func buildDockerImageFromWorkingDir(path string, imageHandler airflow.ImageHandler, houstonClient houston.ClientInterface, deploymentInfo *houston.Deployment, ignoreCacheDeploy bool, description string) error {
11✔
257
        // all these checks inside Dockerfile should happen only when no image-name is provided
11✔
258
        // parse dockerfile
11✔
259
        cmds, err := docker.ParseFile(filepath.Join(path, dockerfile))
11✔
260
        if err != nil {
13✔
261
                return fmt.Errorf("failed to parse dockerfile: %s: %w", filepath.Join(path, dockerfile), err)
2✔
262
        }
2✔
263

264
        _, tag := docker.GetImageTagFromParsedFile(cmds)
9✔
265

9✔
266
        // Get valid image tags for platform using Deployment Info request
9✔
267
        err = validateRuntimeVersion(houstonClient, tag, deploymentInfo)
9✔
268
        if err != nil {
10✔
269
                return err
1✔
270
        }
1✔
271
        // Build our image
272
        fmt.Println(imageBuildingPrompt)
8✔
273
        deployLabels := []string{"io.astronomer.skip.revision=true"}
8✔
274
        if description != "" {
16✔
275
                deployLabels = append(deployLabels, "io.astronomer.deploy.revision.description="+description)
8✔
276
        }
8✔
277
        buildConfig := types.ImageBuildConfig{
8✔
278
                Path:            config.WorkingPath,
8✔
279
                NoCache:         ignoreCacheDeploy,
8✔
280
                TargetPlatforms: deployImagePlatformSupport,
8✔
281
                Labels:          deployLabels,
8✔
282
        }
8✔
283

8✔
284
        err = imageHandler.Build("", "", buildConfig)
8✔
285
        return err
8✔
286
}
287

288
func buildDockerImage(ignoreCacheDeploy bool, deploymentInfo *houston.Deployment, customImageName, path string, imageHandler airflow.ImageHandler, houstonClient houston.ClientInterface, description string) error {
14✔
289
        if customImageName == "" {
25✔
290
                return buildDockerImageFromWorkingDir(path, imageHandler, houstonClient, deploymentInfo, ignoreCacheDeploy, description)
11✔
291
        }
11✔
292
        return buildDockerImageForCustomImage(imageHandler, customImageName, deploymentInfo, houstonClient)
3✔
293
}
294

295
func getGetTagFromImageName(imageName string) string {
2✔
296
        parts := strings.Split(imageName, ":")
2✔
297
        if len(parts) == 2 {
3✔
298
                return parts[1]
1✔
299
        }
1✔
300
        return ""
1✔
301
}
302

303
func buildPushDockerImage(houstonClient houston.ClientInterface, c *config.Context, deploymentInfo *houston.Deployment, name, path, nextTag, cloudDomain, byoRegistryDomain string, ignoreCacheDeploy, byoRegistryEnabled bool, description, customImageName string) error {
14✔
304
        imageName := airflow.ImageName(name, "latest")
14✔
305
        imageHandler := imageHandlerInit(imageName)
14✔
306
        err := buildDockerImage(ignoreCacheDeploy, deploymentInfo, customImageName, path, imageHandler, houstonClient, description)
14✔
307
        if err != nil {
19✔
308
                return err
5✔
309
        }
5✔
310
        return pushDockerImage(byoRegistryEnabled, byoRegistryDomain, name, nextTag, cloudDomain, imageHandler, houstonClient, c, customImageName)
9✔
311
}
312

313
func getAirflowUILink(deploymentID string, deploymentURLs []houston.DeploymentURL) string {
6✔
314
        if deploymentID == "" {
7✔
315
                return ""
1✔
316
        }
1✔
317

318
        for _, url := range deploymentURLs {
6✔
319
                if url.Type == houston.AirflowURLType {
2✔
320
                        return url.URL
1✔
321
                }
1✔
322
        }
323
        return ""
4✔
324
}
325

326
func getDeploymentIDForCurrentCommand(houstonClient houston.ClientInterface, wsID, deploymentID string, prompt bool) (string, []houston.Deployment, error) {
14✔
327
        if wsID == "" {
15✔
328
                return deploymentID, []houston.Deployment{}, ErrNoWorkspaceID
1✔
329
        }
1✔
330

331
        // Validate workspace
332
        currentWorkspace, err := houston.Call(houstonClient.GetWorkspace)(wsID)
13✔
333
        if err != nil {
14✔
334
                return deploymentID, []houston.Deployment{}, err
1✔
335
        }
1✔
336

337
        // Get Deployments from workspace ID
338
        request := houston.ListDeploymentsRequest{
12✔
339
                WorkspaceID: currentWorkspace.ID,
12✔
340
        }
12✔
341
        deployments, err := houston.Call(houstonClient.ListDeployments)(request)
12✔
342
        if err != nil {
13✔
343
                return deploymentID, deployments, err
1✔
344
        }
1✔
345

346
        c, err := config.GetCurrentContext()
11✔
347
        if err != nil {
12✔
348
                return deploymentID, deployments, err
1✔
349
        }
1✔
350

351
        cloudDomain := c.Domain
10✔
352
        if cloudDomain == "" {
10✔
353
                return deploymentID, deployments, errNoDomainSet
×
354
        }
×
355

356
        // Use config deployment if provided
357
        if deploymentID == "" {
12✔
358
                deploymentID = config.CFG.ProjectDeployment.GetProjectString()
2✔
359
        }
2✔
360

361
        if deploymentID != "" && !deploymentExists(deploymentID, deployments) {
11✔
362
                return deploymentID, deployments, errInvalidDeploymentID
1✔
363
        }
1✔
364

365
        // Prompt user for deployment if no deployment passed in
366
        if deploymentID == "" || prompt {
11✔
367
                if len(deployments) == 0 {
3✔
368
                        return deploymentID, deployments, errDeploymentNotFound
1✔
369
                }
1✔
370

371
                fmt.Printf(houstonDeploymentHeader, cloudDomain)
1✔
372
                fmt.Println(houstonSelectDeploymentPrompt)
1✔
373

1✔
374
                deployMap := map[string]houston.Deployment{}
1✔
375
                for i := range deployments {
2✔
376
                        deployment := deployments[i]
1✔
377
                        index := i + 1
1✔
378
                        tab.AddRow([]string{strconv.Itoa(index), deployment.Label, deployment.ReleaseName, currentWorkspace.Label, deployment.ID}, false)
1✔
379

1✔
380
                        deployMap[strconv.Itoa(index)] = deployment
1✔
381
                }
1✔
382

383
                tab.Print(os.Stdout)
1✔
384
                choice := input.Text("\n> ")
1✔
385
                selected, ok := deployMap[choice]
1✔
386
                if !ok {
2✔
387
                        return deploymentID, deployments, errInvalidDeploymentSelected
1✔
388
                }
1✔
389
                deploymentID = selected.ID
×
390
        }
391
        return deploymentID, deployments, nil
7✔
392
}
393

394
func isDagOnlyDeploymentEnabled(appConfig *houston.AppConfig) bool {
10✔
395
        return appConfig != nil && appConfig.Flags.DagOnlyDeployment
10✔
396
}
10✔
397

398
func isDagOnlyDeploymentEnabledForDeployment(deploymentInfo *houston.Deployment) bool {
9✔
399
        return deploymentInfo != nil && deploymentInfo.DagDeployment.Type == houston.DagOnlyDeploymentType
9✔
400
}
9✔
401

402
func validateIfDagDeployURLCanBeConstructed(deploymentInfo *houston.Deployment) error {
5✔
403
        _, err := config.GetCurrentContext()
5✔
404
        if err != nil {
6✔
405
                return fmt.Errorf("could not get current context! Error: %w", err)
1✔
406
        }
1✔
407
        if deploymentInfo == nil || deploymentInfo.ReleaseName == "" {
5✔
408
                return errInvalidDeploymentID
1✔
409
        }
1✔
410
        return nil
3✔
411
}
412

413
func getDagDeployURL(deploymentInfo *houston.Deployment) string {
6✔
414
        // Checks if dagserver URL exists and returns the URL
6✔
415
        for _, url := range deploymentInfo.Urls {
9✔
416
                if url.Type == houston.DagServerURLType {
4✔
417
                        logger.Infof("Using dag deploy URL from dagserver: %s", url.URL)
1✔
418
                        return url.URL
1✔
419
                }
1✔
420
        }
421

422
        // If no dagserver URL is found, we look for airflow URL to detect upload url
423
        for _, url := range deploymentInfo.Urls {
7✔
424
                if url.Type != houston.AirflowURLType {
3✔
425
                        continue
1✔
426
                }
427

428
                parsedAirflowURL, err := neturl.Parse(url.URL)
1✔
429
                if err != nil {
1✔
NEW
430
                        logger.Infof("Error parsing airflow URL: %v", err)
×
NEW
431
                        break
×
432
                }
433

434
                // Use URL scheme and host from the airflow URL
435
                dagUploadURL := fmt.Sprintf("https://%s/%s/dags/upload", parsedAirflowURL.Host, deploymentInfo.ReleaseName)
1✔
436
                logger.Infof("Constructed URL from airflow base URL: %s", dagUploadURL)
1✔
437
                return dagUploadURL
1✔
438
        }
439
        return ""
4✔
440
}
441

442
func DagsOnlyDeploy(houstonClient houston.ClientInterface, wsID, deploymentID, dagsParentPath string, dagDeployURL *string, cleanUpFiles bool, description string) error {
12✔
443
        deploymentID, _, err := getDeploymentIDForCurrentCommandVar(houstonClient, wsID, deploymentID, deploymentID == "")
12✔
444
        if err != nil {
13✔
445
                return err
1✔
446
        }
1✔
447

448
        if deploymentID == "" {
11✔
449
                return errInvalidDeploymentID
×
450
        }
×
451

452
        // Throw error if the feature is disabled at Deployment level
453
        deploymentInfo, err := houston.Call(houstonClient.GetDeployment)(deploymentID)
11✔
454
        if err != nil {
12✔
455
                return fmt.Errorf("failed to get deployment info: %w", err)
1✔
456
        }
1✔
457
        appConfig, err := houston.Call(houstonClient.GetAppConfig)(deploymentInfo.ClusterID)
10✔
458
        if err != nil {
10✔
459
                return fmt.Errorf("failed to get app config: %w", err)
×
460
        }
×
461
        // Throw error if the feature is disabled at Houston level
462
        if !isDagOnlyDeploymentEnabled(appConfig) {
11✔
463
                return ErrDagOnlyDeployDisabledInConfig
1✔
464
        }
1✔
465
        if !isDagOnlyDeploymentEnabledForDeployment(deploymentInfo) {
10✔
466
                return ErrDagOnlyDeployNotEnabledForDeployment
1✔
467
        }
1✔
468

469
        uploadURL := ""
8✔
470
        if dagDeployURL == nil {
13✔
471
                // Throw error if the upload URL can't be constructed
5✔
472
                err = validateIfDagDeployURLCanBeConstructed(deploymentInfo)
5✔
473
                if err != nil {
7✔
474
                        return err
2✔
475
                }
2✔
476
                uploadURL = getDagDeployURL(deploymentInfo)
3✔
477
        } else {
3✔
478
                uploadURL = *dagDeployURL
3✔
479
        }
3✔
480

481
        dagsPath := filepath.Join(dagsParentPath, "dags")
6✔
482
        dagsTarPath := filepath.Join(dagsParentPath, "dags.tar")
6✔
483
        dagsTarGzPath := dagsTarPath + ".gz"
6✔
484
        dagFiles := fileutil.GetFilesWithSpecificExtension(dagsPath, ".py")
6✔
485

6✔
486
        // Alert the user if dags folder is empty
6✔
487
        if len(dagFiles) == 0 && config.CFG.ShowWarnings.GetBool() {
9✔
488
                i, _ := input.Confirm("Warning: No DAGs found. This will delete any existing DAGs. Are you sure you want to deploy?")
3✔
489
                if !i {
4✔
490
                        return ErrEmptyDagFolderUserCancelledOperation
1✔
491
                }
1✔
492
        }
493

494
        // Generate the dags tar
495
        err = fileutil.Tar(dagsPath, dagsTarPath, true, nil)
5✔
496
        if err != nil {
6✔
497
                return err
1✔
498
        }
1✔
499
        if cleanUpFiles {
5✔
500
                defer os.Remove(dagsTarPath)
1✔
501
        }
1✔
502

503
        // Gzip the tar
504
        err = gzipFile(dagsTarPath, dagsTarGzPath)
4✔
505
        if err != nil {
5✔
506
                return err
1✔
507
        }
1✔
508
        if cleanUpFiles {
4✔
509
                defer os.Remove(dagsTarGzPath)
1✔
510
        }
1✔
511

512
        c, _ := config.GetCurrentContext()
3✔
513

3✔
514
        headers := map[string]string{
3✔
515
                "authorization": c.Token,
3✔
516
        }
3✔
517

3✔
518
        uploadFileArgs := fileutil.UploadFileArguments{
3✔
519
                FilePath:            dagsTarGzPath,
3✔
520
                TargetURL:           uploadURL,
3✔
521
                FormFileFieldName:   "file",
3✔
522
                Headers:             headers,
3✔
523
                Description:         description,
3✔
524
                MaxTries:            8,
3✔
525
                InitialDelayInMS:    1 * 1000,
3✔
526
                BackoffFactor:       2,
3✔
527
                RetryDisplayMessage: "please wait, attempting to upload the dags",
3✔
528
        }
3✔
529
        return fileutil.UploadFile(&uploadFileArgs)
3✔
530
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc