Skip to content

Commit 12d2c6e

Browse files
CLOUDP-297410: Add watcher annotations support (#3614)
1 parent 7caf26d commit 12d2c6e

10 files changed

+2903
-2
lines changed

internal/api/api.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ type Command struct {
3434
Description string
3535
RequestParameters RequestParameters
3636
Versions []Version
37+
Watcher *WatcherProperties
3738
}
3839

3940
type RequestParameters struct {
@@ -90,3 +91,24 @@ func ToHTTPVerb(s string) (string, error) {
9091
return "", fmt.Errorf("invalid HTTP verb: %s", s)
9192
}
9293
}
94+
95+
type WatcherProperties struct {
96+
Get WatcherGetProperties
97+
Expect *WatcherExpectProperties
98+
}
99+
100+
type WatcherGetProperties struct {
101+
OperationID string
102+
Version string
103+
Params map[string]string
104+
}
105+
106+
type WatcherExpectProperties struct {
107+
HTTPCode string
108+
Match *WatcherMatchProperties
109+
}
110+
111+
type WatcherMatchProperties struct {
112+
Path string
113+
Values []string
114+
}

internal/api/commands.go

Lines changed: 2448 additions & 1 deletion
Large diffs are not rendered by default.

tools/api-generator/.snapshots/04-spec-with-overrides.yaml.snapshot

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,24 @@ var Commands = GroupedAndSortedCommands{
9595
},
9696
},
9797
},
98+
Watcher: &WatcherProperties{
99+
Get: WatcherGetProperties{
100+
OperationID: `getCluster`,
101+
Version: `2023-02-01`,
102+
Params: map[string]string{
103+
`orgId`: `123`,
104+
},
105+
},
106+
Expect: &WatcherExpectProperties{
107+
HTTPCode: ``,
108+
Match: &WatcherMatchProperties{
109+
Path: `$.stateName`,
110+
Values: []string{
111+
`IDLE`,
112+
},
113+
},
114+
},
115+
},
98116
},
99117
},
100118
},

tools/api-generator/.snapshots/06-spec-with-cli-specific-extensions.yaml.snapshot

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,16 @@ NOTE: Groups and projects are synonymous terms. Your group id is the same as you
101101
},
102102
},
103103
},
104+
Watcher: &WatcherProperties{
105+
Get: WatcherGetProperties{
106+
OperationID: `getCluster`,
107+
Version: ``,
108+
Params: map[string]string{
109+
`orgId`: `123`,
110+
},
111+
},
112+
Expect: nil,
113+
},
104114
},
105115
},
106116
},

tools/api-generator/commands.go.tmpl

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,27 @@ var Commands = GroupedAndSortedCommands{
7474
},
7575
},
7676
{{- end }}
77-
},
77+
},{{if .Watcher}}
78+
Watcher: &WatcherProperties{
79+
Get: WatcherGetProperties{
80+
OperationID: `{{ .Watcher.Get.OperationID }}`,
81+
Version: `{{ .Watcher.Get.Version }}`,
82+
Params: map[string]string{
83+
`orgId`: `123`,
84+
},
85+
},
86+
Expect: {{if not .Watcher.Expect }} nil,{{else}}&WatcherExpectProperties{
87+
HTTPCode: `{{ .Watcher.Expect.HTTPCode }}`,
88+
Match: {{if not .Watcher.Expect.Match }} nil,{{else}}&WatcherMatchProperties{
89+
Path: `{{ .Watcher.Expect.Match.Path }}`,
90+
Values: []string{
91+
{{- range .Watcher.Expect.Match.Values }}
92+
`{{ . }}`,
93+
{{- end }}
94+
},
95+
},{{end}}
96+
},{{end}}
97+
},{{end}}
7898
},
7999
{{- end }}
80100
},

tools/api-generator/convert.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,17 @@ func operationToCommand(path, verb string, operation *openapi3.Operation) (*api.
132132
return nil, fmt.Errorf("failed to clean description: %w", err)
133133
}
134134

135+
if overrides := extractOverrides(operation.Extensions); overrides != nil {
136+
if overriddenOperationID, ok := overrides["operationId"].(string); ok && overriddenOperationID != "" {
137+
operationID = overriddenOperationID
138+
}
139+
}
140+
141+
watcher, err := extractWatcherProperties(operation.Extensions)
142+
if err != nil {
143+
return nil, err
144+
}
145+
135146
command := api.Command{
136147
OperationID: operationID,
137148
Aliases: aliases,
@@ -143,6 +154,7 @@ func operationToCommand(path, verb string, operation *openapi3.Operation) (*api.
143154
Verb: httpVerb,
144155
},
145156
Versions: versions,
157+
Watcher: watcher,
146158
}
147159

148160
return &command, nil

tools/api-generator/fixtures/04-spec-with-overrides.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,18 @@ paths:
3737
override:
3838
description: OVERRIDDEN
3939
operationId: createClusterX
40+
watcher:
41+
get:
42+
operation-id: getCluster
43+
version: '2023-02-01'
44+
params:
45+
groupId: 'input:groupId'
46+
clusterName: 'body:$.name'
47+
expect:
48+
http-code: 200
49+
match:
50+
path: $.stateName
51+
values: IDLE
4052
operationId: createCluster
4153
parameters:
4254
- $ref: '#/components/parameters/envelope'

tools/api-generator/fixtures/06-spec-with-cli-specific-extensions.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ paths:
8181
command-aliases:
8282
- createDatabase
8383
- createServer
84+
watcher:
85+
get:
86+
operation-id: getCluster
8487
operationId: createCluster
8588
parameters:
8689
- $ref: '#/components/parameters/envelope'

tools/api-generator/watcher.go

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
// Copyright 2025 MongoDB Inc
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package main
16+
17+
import (
18+
"errors"
19+
"strings"
20+
21+
"github.com/mongodb/mongodb-atlas-cli/atlascli/internal/api"
22+
)
23+
24+
var (
25+
ErrWatcherGetPropertiesExtIsNil = errors.New("extension map is nil")
26+
ErrWatcherGetPropertiesInvalidOperationID = errors.New("invalid OperationID")
27+
ErrWatcherMatchPropertiesPathIsMissing = errors.New("path is empty or missing")
28+
ErrWatcherMatchPropertiesValuesAreMissing = errors.New("values are empty or missing")
29+
)
30+
31+
/* This is what the YAML looks like: */
32+
// watcher:
33+
// get:
34+
// operation-id: getCluster
35+
// version: 2023-02-01
36+
// params:
37+
// - groupId: input:groupId
38+
// - clusterName: body:$.name
39+
// expect:
40+
// http-code: 200
41+
// match:
42+
// path: $.stateName
43+
// values: IDLE,...
44+
45+
func extractWatcherProperties(ext map[string]any) (*api.WatcherProperties, error) {
46+
cliExt := extractObject(ext, "x-xgen-atlascli")
47+
if cliExt == nil {
48+
return nil, nil
49+
}
50+
51+
watcherExt := extractObject(cliExt, "watcher")
52+
if watcherExt == nil {
53+
return nil, nil
54+
}
55+
56+
getExt := extractObject(watcherExt, "get")
57+
if getExt == nil {
58+
return nil, nil
59+
}
60+
61+
get, err := newWatcherGetProperties(getExt)
62+
if err != nil {
63+
return nil, err
64+
}
65+
66+
var expect *api.WatcherExpectProperties
67+
expectExt := extractObject(watcherExt, "expect")
68+
if expectExt != nil {
69+
expect, err = newWatcherExpectProperties(expectExt)
70+
if err != nil {
71+
return nil, err
72+
}
73+
}
74+
75+
return &api.WatcherProperties{
76+
Get: *get,
77+
Expect: expect,
78+
}, nil
79+
}
80+
81+
func newWatcherGetProperties(ext map[string]any) (*api.WatcherGetProperties, error) {
82+
if ext == nil {
83+
return nil, ErrWatcherGetPropertiesExtIsNil
84+
}
85+
86+
operationID, operationIDOk := ext["operation-id"].(string)
87+
if operationID == "" || !operationIDOk {
88+
return nil, ErrWatcherGetPropertiesInvalidOperationID
89+
}
90+
91+
version, _ := ext["version"].(string)
92+
93+
params := make(map[string]string)
94+
for key, value := range extractObject(ext, "params") {
95+
if stringValue, ok := value.(string); ok {
96+
params[key] = stringValue
97+
}
98+
}
99+
100+
return &api.WatcherGetProperties{
101+
OperationID: operationID,
102+
Version: version,
103+
Params: params,
104+
}, nil
105+
}
106+
107+
func newWatcherExpectProperties(ext map[string]any) (*api.WatcherExpectProperties, error) {
108+
httpCode, _ := ext["http-code"].(string)
109+
110+
var match *api.WatcherMatchProperties
111+
matchExt := extractObject(ext, "match")
112+
if matchExt != nil {
113+
var err error
114+
match, err = newWatcherMatchProperties(matchExt)
115+
if err != nil {
116+
return nil, err
117+
}
118+
}
119+
120+
return &api.WatcherExpectProperties{
121+
HTTPCode: httpCode,
122+
Match: match,
123+
}, nil
124+
}
125+
126+
func newWatcherMatchProperties(ext map[string]any) (*api.WatcherMatchProperties, error) {
127+
if ext == nil {
128+
return nil, nil
129+
}
130+
131+
path, pathOk := ext["path"].(string)
132+
if path == "" || !pathOk {
133+
return nil, ErrWatcherMatchPropertiesPathIsMissing
134+
}
135+
136+
valuesString, valuesOk := ext["values"].(string)
137+
if valuesString == "" || !valuesOk {
138+
return nil, ErrWatcherMatchPropertiesValuesAreMissing
139+
}
140+
141+
values := strings.Split(valuesString, ",")
142+
for i, value := range values {
143+
values[i] = strings.TrimSpace(value)
144+
}
145+
146+
return &api.WatcherMatchProperties{
147+
Path: path,
148+
Values: values,
149+
}, nil
150+
}
151+
152+
func extractObject(ext map[string]any, name string) map[string]any {
153+
if object, ok := ext[name].(map[string]any); ok && object != nil {
154+
return object
155+
}
156+
157+
return nil
158+
}
159+
160+
/*
161+
func validateWatcherProperties(spec *openapi3.T, properties *api.WatcherProperties) error {
162+
// TODO
163+
return nil
164+
}
165+
*/

0 commit comments

Comments
 (0)