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

astronomer / astro-cli / d6577a0a-65ef-44a3-bfd5-f59c4b2237fd

04 Feb 2026 08:08PM UTC coverage: 32.873%. First build
d6577a0a-65ef-44a3-bfd5-f59c4b2237fd

push

circleci

jeremybeard
Fix flag bugs

0 of 22 new or added lines in 2 files covered. (0.0%)

21210 of 64521 relevant lines covered (32.87%)

8.35 hits per line

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

0.0
/cmd/api/cloud.go
1
package api
2

3
import (
4
        "fmt"
5
        "io"
6
        "regexp"
7
        "strings"
8
        "time"
9

10
        "github.com/astronomer/astro-cli/config"
11
        "github.com/astronomer/astro-cli/context"
12
        "github.com/astronomer/astro-cli/pkg/ansi"
13
        "github.com/astronomer/astro-cli/pkg/openapi"
14
        "github.com/spf13/cobra"
15
)
16

17
const (
18
        cloudAPIBaseURL = "https://api.astronomer.io/v1"
19
)
20

21
// CloudOptions holds all options for the cloud api command.
22
type CloudOptions struct {
23
        Out io.Writer
24

25
        // Request options
26
        RequestMethod       string
27
        RequestMethodPassed bool
28
        RequestPath         string
29
        RequestInputFile    string
30
        MagicFields         []string
31
        RawFields           []string
32
        RequestHeaders      []string
33

34
        // Output options
35
        ShowResponseHeaders bool
36
        Paginate            bool
37
        Slurp               bool
38
        Silent              bool
39
        Template            string
40
        FilterOutput        string
41
        Verbose             bool
42
        CacheTTL            time.Duration
43

44
        // Other options
45
        GenerateCurl bool
46

47
        // Internal
48
        specCache *openapi.Cache
49
}
50

51
// NewCloudCmd creates the 'astro api cloud' command.
52
func NewCloudCmd(out io.Writer) *cobra.Command {
×
53
        opts := &CloudOptions{
×
54
                Out:       out,
×
55
                specCache: openapi.NewCache(),
×
56
        }
×
57

×
58
        cmd := &cobra.Command{
×
59
                Use:   "cloud [endpoint]",
×
60
                Short: "Make authenticated requests to the Astro Cloud API",
×
61
                Long: `Make authenticated HTTP requests to the Astro Cloud API (api.astronomer.io).
×
62

×
63
The endpoint argument should be a path of an Astro Cloud API endpoint.
×
64
Placeholder values {organizationId} and {workspaceId} will be replaced
×
65
with values from the current context.
×
66

×
67
The default HTTP request method is GET normally and POST if any parameters
×
68
were added. Override the method with --method.
×
69

×
70
Pass one or more -f/--raw-field values in key=value format to add static string
×
71
parameters to the request payload. To add non-string or placeholder-determined
×
72
values, see -F/--field below.
×
73

×
74
The -F/--field flag has magic type conversion based on the format of the value:
×
75
  - literal values true, false, null, and integer numbers get converted to
×
76
    appropriate JSON types;
×
77
  - if the value starts with @, the rest of the value is interpreted as a
×
78
    filename to read the value from. Pass - to read from standard input.
×
79

×
80
To pass nested parameters in the request payload, use key[subkey]=value syntax.
×
81
To pass nested values as arrays, declare multiple fields with key[]=value1.`,
×
82
                Example: `  # List all Cloud API endpoints
×
83
  astro api cloud ls
×
84

×
85
  # Get organization details (auto-injects organizationId)
×
86
  astro api cloud /organizations/{organizationId}
×
87

×
88
  # List deployments with jq filter
×
89
  astro api cloud /organizations/{organizationId}/deployments --jq '.[].name'
×
90

×
91
  # Create a resource with typed fields
×
92
  astro api cloud -X POST /organizations/{organizationId}/workspaces \
×
93
    -F name=my-workspace \
×
94
    -F description="My new workspace"
×
95

×
96
  # Use Go template for output
×
97
  astro api cloud /organizations/{organizationId}/deployments \
×
98
    --template '{{range .}}{{.name}} ({{.status}}){{"\n"}}{{end}}'
×
99

×
100
  # Generate curl command instead of executing
×
101
  astro api cloud /organizations/{organizationId}/deployments --generate
×
102

×
103
  # Include response headers
×
104
  astro api cloud /organizations/{organizationId} -i
×
105

×
106
  # Verbose mode showing full request/response
×
107
  astro api cloud /organizations/{organizationId} --verbose`,
×
108
                Args: cobra.MaximumNArgs(1),
×
109
                PreRunE: func(cmd *cobra.Command, args []string) error {
×
110
                        opts.RequestMethodPassed = cmd.Flags().Changed("method")
×
111
                        return nil
×
112
                },
×
113
                RunE: func(cmd *cobra.Command, args []string) error {
×
114
                        if len(args) == 0 {
×
115
                                // Interactive mode - show endpoint selection
×
116
                                return runCloudInteractive(opts)
×
117
                        }
×
118
                        opts.RequestPath = args[0]
×
119
                        return runCloud(opts)
×
120
                },
121
        }
122

123
        // Request flags
124
        cmd.Flags().StringVarP(&opts.RequestMethod, "method", "X", "GET", "The HTTP method for the request")
×
125
        cmd.Flags().StringArrayVarP(&opts.MagicFields, "field", "F", nil, "Add a typed parameter in key=value format")
×
126
        cmd.Flags().StringArrayVarP(&opts.RawFields, "raw-field", "f", nil, "Add a string parameter in key=value format")
×
127
        cmd.Flags().StringArrayVarP(&opts.RequestHeaders, "header", "H", nil, "Add a HTTP request header in key:value format")
×
128
        cmd.Flags().StringVar(&opts.RequestInputFile, "input", "", "The file to use as body for the HTTP request (use \"-\" for stdin)")
×
129

×
130
        // Output flags
×
131
        cmd.Flags().BoolVarP(&opts.ShowResponseHeaders, "include", "i", false, "Include HTTP response status line and headers in the output")
×
132
        cmd.Flags().BoolVar(&opts.Paginate, "paginate", false, "Make additional HTTP requests to fetch all pages of results")
×
133
        cmd.Flags().BoolVar(&opts.Slurp, "slurp", false, "Use with --paginate to return an array of all pages")
×
134
        cmd.Flags().BoolVar(&opts.Silent, "silent", false, "Do not print the response body")
×
135
        cmd.Flags().StringVarP(&opts.Template, "template", "t", "", "Format JSON output using a Go template")
×
136
        cmd.Flags().StringVarP(&opts.FilterOutput, "jq", "q", "", "Query to select values from the response using jq syntax")
×
137
        cmd.Flags().BoolVar(&opts.Verbose, "verbose", false, "Include full HTTP request and response in the output")
×
138
        cmd.Flags().DurationVar(&opts.CacheTTL, "cache", 0, "Cache the response, e.g. \"3600s\", \"60m\", \"1h\"")
×
139

×
140
        // Other flags
×
141
        cmd.Flags().BoolVar(&opts.GenerateCurl, "generate", false, "Output a curl command instead of executing the request")
×
142

×
143
        // Add list subcommand
×
144
        cmd.AddCommand(NewListCmd(out, opts.specCache))
×
145
        cmd.AddCommand(NewDescribeCmd(out, opts.specCache))
×
146

×
147
        return cmd
×
148
}
149

150
// runCloud executes the cloud API request.
151
func runCloud(opts *CloudOptions) error {
×
152
        // Check if we're in a cloud context
×
153
        if !context.IsCloudContext() {
×
154
                return fmt.Errorf("the 'astro api cloud' command is only available in cloud context. Run 'astro login' to connect to Astro Cloud")
×
155
        }
×
156

157
        // Get current context for auth and placeholders
158
        ctx, err := context.GetCurrentContext()
×
159
        if err != nil {
×
160
                return fmt.Errorf("getting current context: %w", err)
×
161
        }
×
162

163
        // Check for token
164
        if ctx.Token == "" {
×
165
                return fmt.Errorf("not authenticated. Run 'astro login' to authenticate")
×
166
        }
×
167

168
        // Fill placeholders in the path
169
        requestPath, err := fillPlaceholders(opts.RequestPath, ctx)
×
170
        if err != nil {
×
171
                return fmt.Errorf("filling placeholders: %w", err)
×
172
        }
×
173

174
        // Parse fields into request body
175
        params, err := parseFields(opts.MagicFields, opts.RawFields)
×
176
        if err != nil {
×
177
                return fmt.Errorf("parsing fields: %w", err)
×
178
        }
×
179

180
        // Determine HTTP method
181
        method := opts.RequestMethod
×
182
        if !opts.RequestMethodPassed && (len(params) > 0 || opts.RequestInputFile != "") {
×
183
                method = "POST"
×
184
        }
×
185

186
        // Build the full URL
187
        url := buildURL(cloudAPIBaseURL, requestPath)
×
188

×
189
        // Generate curl command if requested
×
190
        if opts.GenerateCurl {
×
NEW
191
                return generateCurl(opts.Out, method, url, ctx.Token, opts.RequestHeaders, params, opts.RequestInputFile)
×
192
        }
×
193

194
        // Build and execute the request
195
        return executeRequest(opts, method, url, ctx.Token, params)
×
196
}
197

198
// runCloudInteractive runs the cloud API command in interactive mode.
199
func runCloudInteractive(opts *CloudOptions) error {
×
200
        // Check if we're in a cloud context
×
201
        if !context.IsCloudContext() {
×
202
                return fmt.Errorf("the 'astro api cloud' command is only available in cloud context. Run 'astro login' to connect to Astro Cloud")
×
203
        }
×
204

205
        // Load OpenAPI spec
206
        if err := opts.specCache.Load(false); err != nil {
×
207
                return fmt.Errorf("loading OpenAPI spec: %w", err)
×
208
        }
×
209

210
        endpoints := opts.specCache.GetEndpoints()
×
211
        if len(endpoints) == 0 {
×
212
                return fmt.Errorf("no endpoints found in API specification")
×
213
        }
×
214

215
        // Show endpoint selection
216
        fmt.Fprintf(opts.Out, "\nFound %d endpoints. Use '%s' to list them.\n", len(endpoints), ansi.Bold("astro api cloud ls"))
×
217
        fmt.Fprintf(opts.Out, "Run '%s' to make a request.\n\n", ansi.Bold("astro api cloud <endpoint>"))
×
218

×
219
        return nil
×
220
}
221

222
// placeholderRE matches placeholders like {organizationId}, {workspaceId}, etc.
223
var placeholderRE = regexp.MustCompile(`\{([a-zA-Z][a-zA-Z0-9]*)\}`)
224

225
// fillPlaceholders replaces placeholders with values from the context.
226
func fillPlaceholders(path string, ctx config.Context) (string, error) {
×
227
        var errs []string
×
228

×
229
        result := placeholderRE.ReplaceAllStringFunc(path, func(match string) string {
×
230
                // Extract the name without braces
×
231
                name := match[1 : len(match)-1]
×
232

×
233
                switch strings.ToLower(name) {
×
234
                case "organizationid":
×
235
                        if ctx.Organization == "" {
×
236
                                errs = append(errs, "organizationId not set in context (run 'astro organization switch')")
×
237
                                return match
×
238
                        }
×
239
                        return ctx.Organization
×
240
                case "workspaceid":
×
241
                        if ctx.Workspace == "" {
×
242
                                errs = append(errs, "workspaceId not set in context (run 'astro workspace switch')")
×
243
                                return match
×
244
                        }
×
245
                        return ctx.Workspace
×
246
                default:
×
247
                        // Keep unknown placeholders as-is (user might provide them)
×
248
                        return match
×
249
                }
250
        })
251

252
        if len(errs) > 0 {
×
253
                return result, fmt.Errorf("placeholder error: %s", strings.Join(errs, "; "))
×
254
        }
×
255

256
        return result, nil
×
257
}
258

259
// buildURL constructs the full URL from base and path.
260
func buildURL(baseURL, path string) string {
×
261
        // Ensure path starts with /
×
262
        if !strings.HasPrefix(path, "/") {
×
263
                path = "/" + path
×
264
        }
×
265
        return strings.TrimSuffix(baseURL, "/") + path
×
266
}
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