Skip to content

Commit

Permalink
feat(catalog): implement by-refs multi-get
Browse files Browse the repository at this point in the history
  • Loading branch information
odsod committed Mar 30, 2023
1 parent 08e0517 commit ed149e7
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 12 deletions.
52 changes: 40 additions & 12 deletions catalog/client.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package catalog

import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
)
Expand Down Expand Up @@ -68,34 +69,61 @@ func (c *Client) get(
path string,
query url.Values,
fn func(*http.Response) error,
) error {
return c.execute(ctx, http.MethodGet, path, query, nil, fn)
) (err error) {
const method = http.MethodGet
defer func() {
if err != nil {
err = fmt.Errorf("%s %s: %w", method, path, err)
}
}()
requestURL, err := url.Parse(c.config.baseURL + path)
if err != nil {
return err
}
if len(query) > 0 {
requestURL.RawQuery = query.Encode()
}
httpRequest, err := http.NewRequestWithContext(ctx, method, requestURL.String(), nil)
if err != nil {
return err
}
httpResponse, err := c.httpClient.Do(httpRequest)
if err != nil {
return err
}
defer func() {
_ = httpResponse.Body.Close()
}()
if httpResponse.StatusCode != http.StatusOK {
return newStatusError(httpResponse)
}
if fn != nil {
return fn(httpResponse)
}
return nil
}

func (c *Client) execute(
func (c *Client) post(
ctx context.Context,
method string,
path string,
query url.Values,
body io.Reader,
body any,
fn func(*http.Response) error,
) (err error) {
const method = http.MethodPost
defer func() {
if err != nil {
err = fmt.Errorf("%s %s: %w", method, path, err)
}
}()
requestURL, err := url.Parse(c.config.baseURL + path)
bodyData, err := json.Marshal(body)
if err != nil {
return err
}
if len(query) > 0 {
requestURL.RawQuery = query.Encode()
}
httpRequest, err := http.NewRequestWithContext(ctx, method, requestURL.String(), body)
httpRequest, err := http.NewRequestWithContext(ctx, method, c.config.baseURL+path, bytes.NewReader(bodyData))
if err != nil {
return err
}
httpRequest.Header.Set("Content-Type", "application/json")
httpResponse, err := c.httpClient.Do(httpRequest)
if err != nil {
return err
Expand Down
59 changes: 59 additions & 0 deletions catalog/client_entities_batchgetbyrefs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package catalog

import (
"bytes"
"context"
"encoding/json"
"net/http"
)

// BatchGetEntitiesByRefsRequest is the request to the [Client.BatchGetEntitiesByRefs] method.
type BatchGetEntitiesByRefsRequest struct {
// EntityRefs to fetch.
// See: https://backstage.io/docs/features/software-catalog/references
EntityRefs []string `json:"entityRefs"`

// Fields to fetch.
Fields []string `json:"fields,omitempty"`
}

// BatchGetEntitiesByRefsResponse is the response from the [Client.BatchGetEntitiesByRefs] method.
type BatchGetEntitiesByRefsResponse struct {
// Entities returned.
// Has the same length and the same order as the input entityRefs array.
Entities []*Entity `json:"items"`
}

// BatchGetEntitiesByRefs gets an entity by its kind, namespace and name.
// See: https://backstage.io/docs/features/software-catalog/software-catalog-api#post-entitiesby-refs
func (c *Client) BatchGetEntitiesByRefs(
ctx context.Context,
request *BatchGetEntitiesByRefsRequest,
) (*BatchGetEntitiesByRefsResponse, error) {
const path = "/api/catalog/entities/by-refs"
var responseBody struct {
RawEntities []json.RawMessage `json:"items"`
}
if err := c.post(ctx, path, request, func(response *http.Response) error {
return json.NewDecoder(response.Body).Decode(&responseBody)
}); err != nil {
return nil, err
}
response := BatchGetEntitiesByRefsResponse{
Entities: make([]*Entity, 0, len(responseBody.RawEntities)),
}
for _, rawEntity := range responseBody.RawEntities {
if bytes.Equal(rawEntity, []byte("null")) {
response.Entities = append(response.Entities, nil)
} else {
entity := Entity{
Raw: rawEntity,
}
if err := json.Unmarshal(rawEntity, &entity); err != nil {
return nil, err
}
response.Entities = append(response.Entities, &entity)
}
}
return &response, nil
}
32 changes: 32 additions & 0 deletions cmd/backstage/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ func newEntitiesCommand() *cobra.Command {
cmd.AddCommand(newEntitiesGetByUIDCommand())
cmd.AddCommand(newEntitiesGetByNameCommand())
cmd.AddCommand(newEntitiesDeleteByUIDCommand())
cmd.AddCommand(newEntitiesBatchGetByRefsCommand())
return cmd
}

Expand Down Expand Up @@ -217,6 +218,37 @@ func newEntitiesDeleteByUIDCommand() *cobra.Command {
return cmd
}

func newEntitiesBatchGetByRefsCommand() *cobra.Command {
cmd := newCommand()
cmd.Use = "batch-get-by-refs"
cmd.Short = "Batch get entities by their refs"
entityRefs := cmd.Flags().StringSlice("entity-refs", nil, "refs of the entities to get")
_ = cmd.MarkFlagRequired("entity-refs")
fields := cmd.Flags().StringSlice("fields", nil, "select only parts of each entity")
cmd.RunE = func(cmd *cobra.Command, args []string) error {
client, err := newCatalogClient()
if err != nil {
return err
}
response, err := client.BatchGetEntitiesByRefs(cmd.Context(), &catalog.BatchGetEntitiesByRefsRequest{
EntityRefs: *entityRefs,
Fields: *fields,
})
if err != nil {
return err
}
for _, entity := range response.Entities {
if entity != nil {
printRawJSON(cmd, entity.Raw)
} else {
cmd.Println("null")
}
}
return nil
}
return cmd
}

func printRawJSON(cmd *cobra.Command, raw json.RawMessage) {
var indented bytes.Buffer
indented.Grow(len(raw) * 2)
Expand Down

0 comments on commit ed149e7

Please sign in to comment.