Skip to content

Add Support for An Open API Specification to be Associated with an Entity #50

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,4 @@ General TODO file of things left to implement.
Entities:

- x-cortex-infra (AWS, GCP, etc)
- OpenAPI docs
- Packages
25 changes: 25 additions & 0 deletions docs/resources/catalog_entity_openapi.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "cortex_catalog_entity_openapi Resource - terraform-provider-cortex"
subcategory: ""
description: |-
Manages OpenAPI specifications for Cortex catalog entities.
---

# cortex_catalog_entity_openapi (Resource)

Manages OpenAPI specifications for Cortex catalog entities.



<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `entity_tag` (String) The tag or ID of the catalog entity that the OpenAPI specification will be associated with.
- `spec` (String) The OpenAPI specification in YAML or JSON format.

### Read-Only

- `id` (String) The ID of the OpenAPI specification.
35 changes: 35 additions & 0 deletions examples/resources/catalog_entity_openapi/resource.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
terraform {
required_providers {
cortex = {
source = "cortexlocal/cortex"
}
}
}

provider "cortex" {
token = "access-token-here"
}

resource "cortex_catalog_entity_openapi" "test_service_oas1" {
entity_tag = "test-service"
spec = <<EOF
{
"openapi": "3.0.0",
"info": {
"title": "test-service",
"version": "1.0.0"
},
"paths": {
"/test": {
"get": {
"responses": {
"200": {
"description": "OK"
}
}
}
}
}
}
EOF
}
114 changes: 114 additions & 0 deletions internal/cortex/catalog_entity_openapi.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package cortex

import (
"context"
"errors"
"fmt"

"github.com/dghubble/sling"
)

type CatalogEntityOpenAPIClientInterface interface {
Get(ctx context.Context, entityTag string) (CatalogEntityOpenAPI, error)
Upsert(ctx context.Context, entityTag string, req UpsertCatalogEntityOpenAPIRequest) (CatalogEntityOpenAPI, error)
Delete(ctx context.Context, entityTag string) error
}

type CatalogEntityOpenAPIClient struct {
client *HttpClient
}

var _ CatalogEntityOpenAPIClientInterface = &CatalogEntityOpenAPIClient{}

func (c *CatalogEntityOpenAPIClient) Client() *sling.Sling {
return c.client.Client()
}

/***********************************************************************************************************************
* Types
**********************************************************************************************************************/

type CatalogEntityOpenAPI struct {
Tag string `json:"tag"` // tag of catalog entity
Spec string `json:"spec"` // OpenAPI specification
}

func (c *CatalogEntityOpenAPI) ID() string {
return c.Tag
}

/***********************************************************************************************************************
* GET /api/v1/catalog/:tag/documentation/openapi
**********************************************************************************************************************/

func (c *CatalogEntityOpenAPIClient) Get(ctx context.Context, entityTag string) (CatalogEntityOpenAPI, error) {
entity := CatalogEntityOpenAPI{}
apiError := ApiError{}
response, err := c.Client().Get(Route("catalog_entities", entityTag+"/documentation/openapi")).Receive(&entity, &apiError)
if err != nil {
return entity, errors.New("could not get catalog entity OpenAPI spec: " + err.Error())
}

err = c.client.handleResponseStatus(response, &apiError)
if err != nil {
return entity, errors.Join(errors.New("failed getting catalog entity OpenAPI spec: "), err)
}

entity.Tag = entityTag
return entity, nil
}

/***********************************************************************************************************************
* POST /api/v1/catalog/:tag/documentation/openapi
**********************************************************************************************************************/

type UpsertCatalogEntityOpenAPIRequest struct {
Spec string `json:"spec"`
Force bool `json:"force" url:"force,omitempty"`
}

func (c *CatalogEntityOpenAPI) ToUpsertRequest() UpsertCatalogEntityOpenAPIRequest {
return UpsertCatalogEntityOpenAPIRequest{
Spec: c.Spec,
}
}

func (c *CatalogEntityOpenAPIClient) Upsert(ctx context.Context, entityTag string, req UpsertCatalogEntityOpenAPIRequest) (CatalogEntityOpenAPI, error) {
entity := CatalogEntityOpenAPI{}
apiError := ApiError{}

req.Force = true

body, err := c.Client().Put(Route("catalog_entities", entityTag+"/documentation/openapi")).BodyJSON(&req).Receive(&entity, &apiError)
if err != nil {
return entity, fmt.Errorf("failed upserting OpenAPI spec for entity: %+v", err)
}

err = c.client.handleResponseStatus(body, &apiError)
if err != nil {
return entity, err
}

entity.Tag = entityTag
return entity, nil
}

/***********************************************************************************************************************
* DELETE /api/v1/catalog/:tag/documentation/openapi
**********************************************************************************************************************/

func (c *CatalogEntityOpenAPIClient) Delete(ctx context.Context, entityTag string) error {
apiError := ApiError{}

response, err := c.Client().Delete(Route("catalog_entities", entityTag+"/documentation/openapi")).Receive(nil, &apiError)
if err != nil {
return errors.New("could not delete OpenAPI spec: " + err.Error())
}

err = c.client.handleResponseStatus(response, &apiError)
if err != nil {
return errors.Join(errors.New("failed deleting OpenAPI spec: "), err)
}

return nil
}
76 changes: 76 additions & 0 deletions internal/cortex/catalog_entity_openapi_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package cortex_test

import (
"context"
"testing"

"github.com/cortexapps/terraform-provider-cortex/internal/cortex"
"github.com/stretchr/testify/assert"
)

var testOpenAPISpec = &cortex.CatalogEntityOpenAPI{
Tag: "test-catalog-entity",
Spec: `openapi: 3.0.0
info:
title: Test API
version: 1.0.0
paths:
/test:
get:
responses:
'200':
description: OK`,
}

func TestGetCatalogEntityOpenAPI(t *testing.T) {
tag := testOpenAPISpec.Tag
c, teardown, err := setupClient(
cortex.Route("catalog_entities", tag+"/documentation/openapi"),
testOpenAPISpec,
AssertRequestMethod(t, "GET"),
)
assert.Nil(t, err, "could not setup client")
defer teardown()

res, err := c.CatalogEntityOpenAPI().Get(context.Background(), tag)
assert.Nil(t, err, "error retrieving catalog entity OpenAPI spec")
assert.Equal(t, testOpenAPISpec.Tag, res.Tag)
assert.Equal(t, testOpenAPISpec.Spec, res.Spec)
}

func TestUpsertCatalogEntityOpenAPI(t *testing.T) {
tag := testOpenAPISpec.Tag
req := cortex.UpsertCatalogEntityOpenAPIRequest{
Spec: testOpenAPISpec.Spec,
Force: true,
}
c, teardown, err := setupClient(
cortex.Route("catalog_entities", tag+"/documentation/openapi"),
testOpenAPISpec,
AssertRequestMethod(t, "PUT"),
AssertRequestBody(t, req),
)
assert.Nil(t, err, "could not setup client")
defer teardown()

updatedSpec, err := c.CatalogEntityOpenAPI().Upsert(context.Background(), tag, req)
assert.Nil(t, err, "error upserting catalog entity OpenAPI spec")
assert.Equal(t, updatedSpec.Tag, tag)
assert.Equal(t, updatedSpec.Spec, req.Spec)
}

func TestDeleteCatalogEntityOpenAPI(t *testing.T) {
tag := testOpenAPISpec.Tag

c, teardown, err := setupClient(
cortex.Route("catalog_entities", tag+"/documentation/openapi"),
nil,
AssertRequestMethod(t, "DELETE"),
AssertRequestURI(t, "/api/v1/catalog/"+tag+"/documentation/openapi"),
)
assert.Nil(t, err, "could not setup client")
defer teardown()

err = c.CatalogEntityOpenAPI().Delete(context.Background(), tag)
assert.Nil(t, err, "error deleting catalog entity OpenAPI spec")
}
11 changes: 8 additions & 3 deletions internal/cortex/http_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ import (
"context"
"errors"
"fmt"
"github.com/dghubble/sling"
"github.com/motemen/go-loghttp"
_ "github.com/motemen/go-loghttp/global" // Just this line!
"net/http"
"net/url"
"os"

"github.com/dghubble/sling"
"github.com/motemen/go-loghttp"
_ "github.com/motemen/go-loghttp/global" // Just this line!
)

const (
Expand Down Expand Up @@ -153,6 +154,10 @@ func (c *HttpClient) CatalogEntityCustomData() CatalogEntityCustomDataClientInte
return &CatalogEntityCustomDataClient{client: c}
}

func (c *HttpClient) CatalogEntityOpenAPI() CatalogEntityOpenAPIClientInterface {
return &CatalogEntityOpenAPIClient{client: c}
}

func (c *HttpClient) Teams() TeamsClientInterface {
return &TeamsClient{client: c}
}
Expand Down
Loading
Loading