Skip to content

Commit b3e5d49

Browse files
tphoneyactions-user
authored andcommitted
feat: support listable and searchable adapter in dynamic adapters (#1923)
# Support Searchable and Listable adapters We should support searchable and listable adapters that provides both methods at the same time. ## Metadata - URL: [https://linear.app/overmind/issue/ENG-598/support-searchable-and-listable-adapters](https://linear.app/overmind/issue/ENG-598/support-searchable-and-listable-adapters) - Identifier: ENG-598 - Status: In Review - Priority: No priority - Assignee: [email protected] - Labels: Feature - Project: [GCP Support](https://linear.app/overmind/project/gcp-support-871ebbdcd5f9). - Created: 2025-06-13T11:06:01.895Z - Updated: 2025-06-18T17:05:03.642Z ## Pull requests - [#1878 Support Searchable and Listable adapters](https://github.com/overmindtech/workspace/issues/1878) - [feat: support listable and searchable adapter in dynamic adapters](https://github.com/overmindtech/workspace/pull/1923) ## Comments - GitHub integration: This comment thread is synced to a corresponding [GitHub issue](https://github.com/overmindtech/workspace/issues/1878). All replies are displayed in both locations. GitOrigin-RevId: e2f324b98a1771338bfe36140663137971b27510
1 parent fe8d5bd commit b3e5d49

File tree

3 files changed

+364
-60
lines changed

3 files changed

+364
-60
lines changed
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
package dynamic
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net/http"
7+
"strings"
8+
9+
"github.com/overmindtech/cli/discovery"
10+
"github.com/overmindtech/cli/sdp-go"
11+
gcpshared "github.com/overmindtech/cli/sources/gcp/shared"
12+
)
13+
14+
type SearchableListableDiscoveryAdapter interface {
15+
discovery.SearchableAdapter
16+
discovery.ListableAdapter
17+
}
18+
19+
// SearchableListableAdapter implements discovery.SearchableAdapter for GCP dynamic adapters.
20+
type SearchableListableAdapter struct {
21+
searchEndpointFunc gcpshared.EndpointFunc
22+
ListableAdapter
23+
}
24+
25+
// NewSearchableListableAdapter creates a new GCP dynamic adapter.
26+
func NewSearchableListableAdapter(searchURLFunc gcpshared.EndpointFunc, listEndpoint string, config *AdapterConfig) SearchableListableDiscoveryAdapter {
27+
return SearchableListableAdapter{
28+
searchEndpointFunc: searchURLFunc,
29+
ListableAdapter: ListableAdapter{
30+
listEndpoint: listEndpoint,
31+
Adapter: Adapter{
32+
projectID: config.ProjectID,
33+
scope: config.Scope,
34+
httpCli: config.HTTPClient,
35+
getURLFunc: config.GetURLFunc,
36+
httpHeaders: http.Header{
37+
"Authorization": []string{"Bearer " + config.Token},
38+
},
39+
sdpAssetType: config.SDPAssetType,
40+
sdpAdapterCategory: config.SDPAdapterCategory,
41+
terraformMappings: config.TerraformMappings,
42+
linker: config.Linker,
43+
potentialLinks: potentialLinksFromBlasts(config.SDPAssetType, gcpshared.BlastPropagations),
44+
uniqueAttributeKeys: config.UniqueAttributeKeys,
45+
},
46+
},
47+
}
48+
}
49+
50+
func (g SearchableListableAdapter) Metadata() *sdp.AdapterMetadata {
51+
return &sdp.AdapterMetadata{
52+
Type: g.sdpAssetType.String(),
53+
Category: g.sdpAdapterCategory,
54+
DescriptiveName: g.sdpAssetType.Readable(),
55+
SupportedQueryMethods: &sdp.AdapterSupportedQueryMethods{
56+
Get: true,
57+
GetDescription: getDescription(g.sdpAssetType, g.scope, g.uniqueAttributeKeys),
58+
Search: true,
59+
SearchDescription: searchDescription(g.sdpAssetType, g.scope, g.uniqueAttributeKeys),
60+
List: true,
61+
ListDescription: listDescription(g.sdpAssetType, g.scope),
62+
},
63+
TerraformMappings: g.terraformMappings,
64+
PotentialLinks: g.potentialLinks,
65+
}
66+
}
67+
68+
func (g SearchableListableAdapter) Search(ctx context.Context, scope, query string, ignoreCache bool) ([]*sdp.Item, error) {
69+
if scope != g.scope {
70+
return nil, &sdp.QueryError{
71+
ErrorType: sdp.QueryError_NOSCOPE,
72+
ErrorString: fmt.Sprintf("requested scope %v does not match any adapter scope %v", scope, g.Scopes()),
73+
}
74+
}
75+
searchEndpoint := g.searchEndpointFunc(query)
76+
if searchEndpoint == "" {
77+
return nil, &sdp.QueryError{
78+
ErrorType: sdp.QueryError_OTHER,
79+
ErrorString: fmt.Sprintf("no search endpoint found for query \"%s\". %s", query, g.Metadata().GetSupportedQueryMethods().GetSearchDescription()),
80+
}
81+
}
82+
83+
var items []*sdp.Item
84+
itemsSelector := g.uniqueAttributeKeys[len(g.uniqueAttributeKeys)-1] // Use the last key as the item selector
85+
86+
if strings.HasPrefix(query, "projects/") {
87+
// This is a single item query for terraform search method mappings.
88+
// See: https://linear.app/overmind/issue/ENG-580/handle-terraform-mappings-in-search-method
89+
resp, err := externalCallSingle(ctx, g.httpCli, g.httpHeaders, searchEndpoint)
90+
if err != nil {
91+
return nil, err
92+
}
93+
94+
item, err := externalToSDP(ctx, g.projectID, g.scope, g.uniqueAttributeKeys, resp, g.sdpAssetType, g.linker)
95+
if err != nil {
96+
return nil, err
97+
}
98+
99+
return append(items, item), nil
100+
}
101+
102+
multiResp, err := externalCallMulti(ctx, itemsSelector, g.httpCli, g.httpHeaders, searchEndpoint)
103+
if err != nil {
104+
return nil, fmt.Errorf("failed to retrieve items for %s: %w", searchEndpoint, err)
105+
}
106+
107+
for _, resp := range multiResp {
108+
item, err := externalToSDP(ctx, g.projectID, g.scope, g.uniqueAttributeKeys, resp, g.sdpAssetType, g.linker)
109+
if err != nil {
110+
return nil, err
111+
}
112+
113+
items = append(items, item)
114+
}
115+
116+
return items, nil
117+
}

sources/gcp/dynamic/adapters.go

Lines changed: 71 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,15 @@ import (
1010
"github.com/overmindtech/cli/sources/shared"
1111
)
1212

13+
type typeOfAdapter string
14+
15+
const (
16+
Standard typeOfAdapter = "standard"
17+
Listable typeOfAdapter = "listable"
18+
Searchable typeOfAdapter = "searchable"
19+
SearchableListable typeOfAdapter = "searchableListable"
20+
)
21+
1322
var adaptersByScope = map[gcpshared.Scope]map[shared.ItemType]gcpshared.AdapterMeta{}
1423

1524
func init() {
@@ -52,29 +61,12 @@ func Adapters(projectID string, token string, regions []string, zones []string,
5261
UniqueAttributeKeys: meta.UniqueAttributeKeys,
5362
}
5463

55-
if meta.ListEndpointFunc != nil {
56-
listEndpoint, err := meta.ListEndpointFunc(projectID)
57-
if err != nil {
58-
return nil, err
59-
}
60-
61-
adapters = append(adapters, NewListableAdapter(listEndpoint, cfg))
62-
63-
continue
64-
}
65-
66-
if meta.SearchEndpointFunc != nil {
67-
searchEndpointFunc, err := meta.SearchEndpointFunc(projectID)
68-
if err != nil {
69-
return nil, err
70-
}
71-
72-
adapters = append(adapters, NewSearchableAdapter(searchEndpointFunc, cfg))
73-
74-
continue
64+
adapter, err := makeAdapter(meta, cfg, projectID)
65+
if err != nil {
66+
return nil, fmt.Errorf("failed to add adapter for %s: %w", sdpItemType, err)
7567
}
7668

77-
adapters = append(adapters, NewAdapter(cfg))
69+
adapters = append(adapters, adapter)
7870
}
7971

8072
// Regional adapters
@@ -105,28 +97,12 @@ func Adapters(projectID string, token string, regions []string, zones []string,
10597
UniqueAttributeKeys: meta.UniqueAttributeKeys,
10698
}
10799

108-
if meta.ListEndpointFunc != nil {
109-
listEndpoint, err := meta.ListEndpointFunc(projectID, region)
110-
if err != nil {
111-
return nil, err
112-
}
113-
adapters = append(adapters, NewListableAdapter(listEndpoint, cfg))
114-
115-
continue
116-
}
117-
118-
if meta.SearchEndpointFunc != nil {
119-
searchEndpointFunc, err := meta.SearchEndpointFunc(projectID, region)
120-
if err != nil {
121-
return nil, err
122-
}
123-
124-
adapters = append(adapters, NewSearchableAdapter(searchEndpointFunc, cfg))
125-
126-
continue
100+
adapter, err := makeAdapter(meta, cfg, projectID, region)
101+
if err != nil {
102+
return nil, fmt.Errorf("failed to add adapter for %s in region %s: %w", sdpItemType, region, err)
127103
}
128104

129-
adapters = append(adapters, NewAdapter(cfg))
105+
adapters = append(adapters, adapter)
130106
}
131107
}
132108

@@ -158,30 +134,65 @@ func Adapters(projectID string, token string, regions []string, zones []string,
158134
UniqueAttributeKeys: meta.UniqueAttributeKeys,
159135
}
160136

161-
if meta.ListEndpointFunc != nil {
162-
listEndpoint, err := meta.ListEndpointFunc(projectID, zone)
163-
if err != nil {
164-
return nil, err
165-
}
166-
adapters = append(adapters, NewListableAdapter(listEndpoint, cfg))
167-
168-
continue
137+
adapter, err := makeAdapter(meta, cfg, projectID, zone)
138+
if err != nil {
139+
return nil, fmt.Errorf("failed to add adapter for %s in zone %s: %w", sdpItemType, zone, err)
169140
}
170141

171-
if meta.SearchEndpointFunc != nil {
172-
searchEndpointFunc, err := meta.SearchEndpointFunc(projectID, zone)
173-
if err != nil {
174-
return nil, err
175-
}
142+
adapters = append(adapters, adapter)
143+
}
144+
}
176145

177-
adapters = append(adapters, NewSearchableAdapter(searchEndpointFunc, cfg))
146+
return adapters, nil
147+
}
178148

179-
continue
180-
}
149+
func adapterType(meta gcpshared.AdapterMeta) typeOfAdapter {
150+
if meta.ListEndpointFunc != nil && meta.SearchEndpointFunc == nil {
151+
return Listable
152+
}
181153

182-
adapters = append(adapters, NewAdapter(cfg))
183-
}
154+
if meta.SearchEndpointFunc != nil && meta.ListEndpointFunc == nil {
155+
return Searchable
184156
}
185157

186-
return adapters, nil
158+
if meta.ListEndpointFunc != nil && meta.SearchEndpointFunc != nil {
159+
return SearchableListable
160+
}
161+
162+
return Standard
163+
}
164+
165+
func makeAdapter(meta gcpshared.AdapterMeta, cfg *AdapterConfig, opts ...string) (discovery.Adapter, error) {
166+
switch adapterType(meta) {
167+
case SearchableListable:
168+
searchEndpointFunc, err := meta.SearchEndpointFunc(opts...)
169+
if err != nil {
170+
return nil, err
171+
}
172+
173+
listEndpoint, err := meta.ListEndpointFunc(opts...)
174+
if err != nil {
175+
return nil, err
176+
}
177+
178+
return NewSearchableListableAdapter(searchEndpointFunc, listEndpoint, cfg), nil
179+
case Searchable:
180+
searchEndpointFunc, err := meta.SearchEndpointFunc(opts...)
181+
if err != nil {
182+
return nil, err
183+
}
184+
185+
return NewSearchableAdapter(searchEndpointFunc, cfg), nil
186+
case Listable:
187+
listEndpoint, err := meta.ListEndpointFunc(opts...)
188+
if err != nil {
189+
return nil, err
190+
}
191+
192+
return NewListableAdapter(listEndpoint, cfg), nil
193+
case Standard:
194+
return NewAdapter(cfg), nil
195+
default:
196+
return nil, fmt.Errorf("unknown adapter type %s", adapterType(meta))
197+
}
187198
}

0 commit comments

Comments
 (0)