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

astronomer / astro-cli / 28970289308

08 Jul 2026 07:35PM UTC coverage: 43.611% (-1.7%) from 45.328%
28970289308

Pull #2198

github

web-flow
Merge c33eb2682 into 44bc04a46
Pull Request #2198: Expose non-DAG bundle deploys and bundle management

376 of 3398 new or added lines in 11 files covered. (11.07%)

17 existing lines in 2 files now uncovered.

25531 of 58542 relevant lines covered (43.61%)

8.25 hits per line

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

83.22
/cloud/deployment/bundle.go
1
package deployment
2

3
import (
4
        httpContext "context"
5
        "fmt"
6
        "io"
7

8
        "github.com/pkg/errors"
9

10
        "github.com/astronomer/astro-cli/astro-client-v1"
11
        astrov1alpha1 "github.com/astronomer/astro-cli/astro-client-v1alpha1"
12
        "github.com/astronomer/astro-cli/pkg/input"
13
        "github.com/astronomer/astro-cli/pkg/output"
14
)
15

16
// The bundle endpoints live only on the v1alpha1 public API, so bundle operations
17
// use the v1alpha1 client while the deployment lookup (which resolves the org and
18
// deployment ids) stays on v1. Collapse onto a single client once the bundle API
19
// reaches v1.
20

21
const bundleListLimit = 1000
22

23
var (
24
        errCreateBundleTarget   = errors.New("specify exactly one of --name (DAG bundle) or --mount-path (non-DAG bundle)")
25
        errDagBundleNonDagFlags = errors.New("--bundle-type and --dag-bundle-ids are only valid for non-DAG bundles (--mount-path)")
26
        errUpdateBundleNoOp     = errors.New("specify at least one of --description or --dag-bundle-ids")
27
)
28

29
// BundleList is the wire shape for `bundle list` output.
30
type BundleList struct {
31
        Bundles []astrov1alpha1.DeploymentBundle `json:"bundles"`
32
}
33

34
func bundleTableConfig() *output.TableConfig {
3✔
35
        columns := []output.Column[astrov1alpha1.DeploymentBundle]{
3✔
36
                {Header: "BUNDLE ID", Value: func(b astrov1alpha1.DeploymentBundle) string { return b.Id }},
7✔
37
                {Header: "IS DAG BUNDLE", Value: func(b astrov1alpha1.DeploymentBundle) string {
4✔
38
                        return fmt.Sprintf("%t", b.IsDagBundle != nil && *b.IsDagBundle)
4✔
39
                }},
4✔
40
                {Header: "NAME", Value: func(b astrov1alpha1.DeploymentBundle) string {
4✔
41
                        if b.Name == nil {
7✔
42
                                return notApplicable
3✔
43
                        }
3✔
44
                        return orNA(*b.Name)
1✔
45
                }},
46
                {Header: "MOUNT PATH", Value: func(b astrov1alpha1.DeploymentBundle) string {
4✔
47
                        if b.NonDagMountPath == nil {
7✔
48
                                return notApplicable
3✔
49
                        }
3✔
50
                        return orNA(*b.NonDagMountPath)
1✔
51
                }},
52
                {Header: "CURRENT VERSION", Value: func(b astrov1alpha1.DeploymentBundle) string {
4✔
53
                        if b.CurrentVersion == nil {
8✔
54
                                return notApplicable
4✔
55
                        }
4✔
NEW
56
                        return orNA(*b.CurrentVersion)
×
57
                }},
58
                {Header: "DESIRED VERSION", Value: func(b astrov1alpha1.DeploymentBundle) string {
4✔
59
                        if b.DesiredVersion == nil {
8✔
60
                                return notApplicable
4✔
61
                        }
4✔
NEW
62
                        return orNA(*b.DesiredVersion)
×
63
                }},
64
        }
65
        return output.BuildTableConfig(
3✔
66
                columns,
3✔
67
                func(d any) []astrov1alpha1.DeploymentBundle { return d.(*BundleList).Bundles },
5✔
68
                output.WithNoResultsMsg("No bundles found on this deployment"),
69
        )
70
}
71

72
// CreateBundle registers a bundle on a deployment. A DAG bundle is created with a
73
// name; a non-DAG bundle is created with a mount path (and optional bundle type
74
// plus the DAG bundles it is served alongside).
75
func CreateBundle(name, mountPath, bundleType, bundleDescription string, dagBundleIDs []string, wsID, deploymentID string, out io.Writer, astroV1Client astrov1.APIClient, astroV1Alpha1Client astrov1alpha1.APIClient) error {
6✔
76
        if (name == "") == (mountPath == "") {
8✔
77
                return errCreateBundleTarget
2✔
78
        }
2✔
79
        if name != "" && (bundleType != "" || len(dagBundleIDs) > 0) {
5✔
80
                return errDagBundleNonDagFlags
1✔
81
        }
1✔
82

83
        dep, err := GetDeployment(wsID, deploymentID, "", false, nil, astroV1Client)
3✔
84
        if err != nil {
3✔
NEW
85
                return err
×
NEW
86
        }
×
87

88
        isDagBundle := name != ""
3✔
89
        request := astrov1alpha1.CreateBundleRequest{
3✔
90
                Type:        astrov1alpha1.CreateBundleRequestTypeDEPLOY,
3✔
91
                IsDagBundle: &isDagBundle,
3✔
92
        }
3✔
93
        if name != "" {
5✔
94
                request.Name = &name
2✔
95
        }
2✔
96
        if mountPath != "" {
4✔
97
                request.NonDagMountPath = &mountPath
1✔
98
        }
1✔
99
        if bundleType != "" {
4✔
100
                request.NonDagBundleType = &bundleType
1✔
101
        }
1✔
102
        if bundleDescription != "" {
4✔
103
                request.Description = &bundleDescription
1✔
104
        }
1✔
105
        if len(dagBundleIDs) > 0 {
4✔
106
                request.DagBundleIds = &dagBundleIDs
1✔
107
        }
1✔
108

109
        resp, err := astroV1Alpha1Client.CreateBundleWithResponse(httpContext.Background(), dep.OrganizationId, dep.Id, request)
3✔
110
        if err != nil {
4✔
111
                return err
1✔
112
        }
1✔
113
        err = astrov1alpha1.NormalizeAPIError(resp.HTTPResponse, resp.Body)
2✔
114
        if err != nil {
2✔
NEW
115
                return err
×
NEW
116
        }
×
117

118
        fmt.Fprintf(out, "Created bundle %s on deployment %s\n", resp.JSON200.Id, dep.Id)
2✔
119
        return nil
2✔
120
}
121

122
// UpdateBundle changes a bundle's description and, for non-DAG bundles, the set of
123
// DAG bundles it is served alongside.
124
func UpdateBundle(bundleID, bundleDescription string, dagBundleIDs []string, wsID, deploymentID string, out io.Writer, astroV1Client astrov1.APIClient, astroV1Alpha1Client astrov1alpha1.APIClient) error {
2✔
125
        if bundleDescription == "" && len(dagBundleIDs) == 0 {
3✔
126
                return errUpdateBundleNoOp
1✔
127
        }
1✔
128

129
        dep, err := GetDeployment(wsID, deploymentID, "", false, nil, astroV1Client)
1✔
130
        if err != nil {
1✔
NEW
131
                return err
×
NEW
132
        }
×
133

134
        request := astrov1alpha1.UpdateBundleRequest{}
1✔
135
        if bundleDescription != "" {
2✔
136
                request.Description = &bundleDescription
1✔
137
        }
1✔
138
        if len(dagBundleIDs) > 0 {
2✔
139
                request.DagBundleIds = &dagBundleIDs
1✔
140
        }
1✔
141

142
        resp, err := astroV1Alpha1Client.UpdateBundleWithResponse(httpContext.Background(), dep.OrganizationId, dep.Id, bundleID, request)
1✔
143
        if err != nil {
1✔
NEW
144
                return err
×
NEW
145
        }
×
146
        err = astrov1alpha1.NormalizeAPIError(resp.HTTPResponse, resp.Body)
1✔
147
        if err != nil {
1✔
NEW
148
                return err
×
NEW
149
        }
×
150

151
        fmt.Fprintf(out, "Updated bundle %s on deployment %s\n", bundleID, dep.Id)
1✔
152
        return nil
1✔
153
}
154

155
// ListBundlesData fetches every bundle configured on a deployment, paging through
156
// all results.
157
func ListBundlesData(wsID, deploymentID string, astroV1Client astrov1.APIClient, astroV1Alpha1Client astrov1alpha1.APIClient) (*BundleList, error) {
3✔
158
        dep, err := GetDeployment(wsID, deploymentID, "", false, nil, astroV1Client)
3✔
159
        if err != nil {
3✔
NEW
160
                return nil, err
×
NEW
161
        }
×
162

163
        var bundles []astrov1alpha1.DeploymentBundle
3✔
164
        limit := bundleListLimit
3✔
165
        for {
7✔
166
                offset := len(bundles)
4✔
167
                params := &astrov1alpha1.ListBundlesParams{Limit: &limit, Offset: &offset}
4✔
168
                resp, err := astroV1Alpha1Client.ListBundlesWithResponse(httpContext.Background(), dep.OrganizationId, dep.Id, params)
4✔
169
                if err != nil {
4✔
NEW
170
                        return nil, err
×
NEW
171
                }
×
172
                err = astrov1alpha1.NormalizeAPIError(resp.HTTPResponse, resp.Body)
4✔
173
                if err != nil {
4✔
NEW
174
                        return nil, err
×
NEW
175
                }
×
176

177
                bundles = append(bundles, resp.JSON200.Bundles...)
4✔
178
                if len(resp.JSON200.Bundles) == 0 || len(bundles) >= resp.JSON200.TotalCount {
7✔
179
                        break
3✔
180
                }
181
        }
182

183
        return &BundleList{Bundles: bundles}, nil
3✔
184
}
185

186
// ListBundlesWithFormat prints every bundle on a deployment in the requested format.
187
func ListBundlesWithFormat(wsID, deploymentID string, format output.Format, tmpl string, out io.Writer, astroV1Client astrov1.APIClient, astroV1Alpha1Client astrov1alpha1.APIClient) error {
3✔
188
        return output.PrintData(
3✔
189
                func() (*BundleList, error) {
6✔
190
                        return ListBundlesData(wsID, deploymentID, astroV1Client, astroV1Alpha1Client)
3✔
191
                },
3✔
192
                bundleTableConfig(), format, tmpl, out,
193
        )
194
}
195

196
// DeleteBundle removes a bundle from a deployment.
197
func DeleteBundle(bundleID, wsID, deploymentID string, force bool, out io.Writer, astroV1Client astrov1.APIClient, astroV1Alpha1Client astrov1alpha1.APIClient) error {
2✔
198
        dep, err := GetDeployment(wsID, deploymentID, "", false, nil, astroV1Client)
2✔
199
        if err != nil {
2✔
NEW
200
                return err
×
NEW
201
        }
×
202

203
        if !force {
3✔
204
                confirmed, _ := input.Confirm(fmt.Sprintf("Are you sure you want to delete bundle %s from deployment %s?", bundleID, dep.Id))
1✔
205
                if !confirmed {
2✔
206
                        fmt.Fprintln(out, "Canceling bundle deletion")
1✔
207
                        return nil
1✔
208
                }
1✔
209
        }
210

211
        resp, err := astroV1Alpha1Client.DeleteBundleWithResponse(httpContext.Background(), dep.OrganizationId, dep.Id, bundleID)
1✔
212
        if err != nil {
1✔
NEW
213
                return err
×
NEW
214
        }
×
215
        err = astrov1alpha1.NormalizeAPIError(resp.HTTPResponse, resp.Body)
1✔
216
        if err != nil {
1✔
NEW
217
                return err
×
NEW
218
        }
×
219

220
        fmt.Fprintf(out, "Deleted bundle %s from deployment %s\n", bundleID, dep.Id)
1✔
221
        return nil
1✔
222
}
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