Skip to content

Commit

Permalink
Return spec in backstage_entities data source (#138)
Browse files Browse the repository at this point in the history
The PR adds support for `spec` attribute for entities returned by
`backstage_entities` data source and fixes #101.

It might not be ideal as it is returned as JSON string instead of a
proper object. I wasn't able to achieve this with dynamic types so
that's the reason I chose JSON.
[`jsondecode`](https://developer.hashicorp.com/terraform/language/functions/jsondecode)
function can be used to decode the object and get the specific values.

Usage example:

```terraform
terraform {
  required_providers {
    backstage = {
      source = "tdabasinskas/backstage"
      version = "2.4.0-rc1"
    }
  }
}

provider "backstage" {
  base_url = "https://demo.backstage.io"
}

data "backstage_entities" "example" {
  filters = []
}

output "example" {
  value = jsondecode(data.backstage_entities.example.entities[0].spec)["profile"]["email"]
}
```
  • Loading branch information
tdabasinskas authored Aug 11, 2024
2 parents 64f54d2 + aa4803c commit 6f77edc
Show file tree
Hide file tree
Showing 9 changed files with 52 additions and 5 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ jobs:
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@286f3b13b1b49da4ac219696163fb8c1c93e1200 # v6.0.0
with:
args: release --rm-dist
args: release --clean
env:
# GitHub sets the GITHUB_TOKEN secret automatically.
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,9 @@ jobs:
matrix:
# list whatever Terraform versions here you would like to support
terraform:
- '1.5.*'
- '1.6.*'
- '1.7.*'
- '1.8.*'
- '1.9.*'
steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2
Expand Down
6 changes: 4 additions & 2 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Visit https://goreleaser.com for documentation on how to customize this
# behavior.
version: 2

before:
hooks:
# this is just an example and not a requirement for provider building/publishing
Expand Down Expand Up @@ -41,7 +43,7 @@ checksum:
signs:
- artifacts: checksum
args:
# if you are using this in a GitHub action or some other automated pipeline, you
# if you are using this in a GitHub action or some other automated pipeline, you
# need to pass the batch flag to indicate its not interactive.
- "--batch"
- "--local-user"
Expand All @@ -57,4 +59,4 @@ release:
# If you want to manually examine the release before its live, uncomment this line:
# draft: true
changelog:
skip: true
disable: true
15 changes: 15 additions & 0 deletions backstage/data_source_entities.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package backstage

import (
"context"
"encoding/json"
"fmt"
"github.com/hashicorp/terraform-plugin-framework-jsontypes/jsontypes"
"net/http"

"github.com/hashicorp/terraform-plugin-framework/datasource"
Expand Down Expand Up @@ -35,6 +37,7 @@ type entityDataSourceModel struct {

type entityModel struct {
ApiVersion types.String `tfsdk:"api_version"`
Spec jsontypes.Normalized `tfsdk:"spec"`
Kind types.String `tfsdk:"kind"`
Metadata *entityMetadataModel `tfsdk:"metadata"`
Relations []entityRelationModel `tfsdk:"relations"`
Expand Down Expand Up @@ -76,6 +79,7 @@ const (
patternEntityName = `^[a-zA-Z0-9\-_\.]*$`
descriptionEntityFilters = "A set of conditions that can be used to filter entities."
descriptionEntitySpec = "The specification data describing the entity itself."
descriptionEntitySpecJson = "The specification data describing the entity itself (as JSON)."
descriptionEntityApiVersion = "Version of specification format for this particular entity that this is written against."
descriptionEntityKind = "The high level entity type being described."
descriptionEntityMetadata = "Metadata fields common to all versions/kinds of entity."
Expand Down Expand Up @@ -124,6 +128,7 @@ func (d *entityDataSource) Schema(_ context.Context, _ datasource.SchemaRequest,
"entities": schema.ListNestedAttribute{Computed: true, Description: descriptionEntitySpec, NestedObject: schema.NestedAttributeObject{
Attributes: map[string]schema.Attribute{
"api_version": schema.StringAttribute{Computed: true, Description: descriptionEntityApiVersion},
"spec": schema.StringAttribute{Computed: true, Description: descriptionEntitySpecJson, CustomType: jsontypes.NormalizedType{}},
"kind": schema.StringAttribute{Computed: true, Description: descriptionEntityKind},
"metadata": schema.SingleNestedAttribute{Computed: true, Description: descriptionEntityMetadata, Attributes: map[string]schema.Attribute{
"uid": schema.StringAttribute{Computed: true, Description: descriptionEntityMetadataUID},
Expand Down Expand Up @@ -201,9 +206,19 @@ func (d *entityDataSource) Read(ctx context.Context, req datasource.ReadRequest,
state.ID = types.StringValue(fmt.Sprint(state.Filters))

for _, e := range entities {
v, err := json.Marshal(e.Spec)
if err != nil {
resp.Diagnostics.AddError(
"Error parsing Backstage entity specs",
fmt.Sprintf("Could not parse Specs for Backstage entity %v: %s", e.Metadata.Name, err.Error()),
)
continue
}

entity := entityModel{
ApiVersion: types.StringValue(e.ApiVersion),
Kind: types.StringValue(e.Kind),
Spec: jsontypes.NewNormalizedValue(string(v)),
}

for _, i := range e.Relations {
Expand Down
14 changes: 14 additions & 0 deletions backstage/data_source_entities_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package backstage

import (
"fmt"
"github.com/hashicorp/go-cty/cty"
"github.com/hashicorp/go-cty/cty/function/stdlib"
"testing"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
Expand Down Expand Up @@ -29,6 +31,18 @@ func TestAccDataSourceEntities(t *testing.T) {
"metadata.annotations.backstage.io/managed-by-location": "url:https://github.com/backstage/backstage/tree/master/packages/catalog-model/examples/acme/team-a-group.yaml",
"relations.0.target_ref": "group:default/team-a",
}),
resource.TestCheckResourceAttrWith("data.backstage_entities.test", "entities.0.spec", func(spec string) error {
v, err := stdlib.JSONDecode(cty.StringVal(spec))
if err != nil {
return err
}

if v.GetAttr("profile").GetAttr("email").AsString() != "janelle-dawe@example.com" {
return fmt.Errorf("expected spec to be parsed and email be 'janelle-dawe@example.com', but couldn't parse it from %s", spec)
}

return nil
}),
),
},
},
Expand Down
6 changes: 6 additions & 0 deletions docs/data-sources/entities.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ data "backstage_entities" "example" {
"kind=Group,metadata.namespace=default",
]
}
# Outputs data from `spec` from an entity:
output "example" {
value = jsondecode(data.backstage_entities.example.entities[0].spec)["profile"]["email"]
}
```

<!-- schema generated by tfplugindocs -->
Expand All @@ -44,6 +49,7 @@ Read-Only:
- `kind` (String) The high level entity type being described.
- `metadata` (Attributes) Metadata fields common to all versions/kinds of entity. (see [below for nested schema](#nestedatt--entities--metadata))
- `relations` (Attributes List) Relations that this entity has with other entities (see [below for nested schema](#nestedatt--entities--relations))
- `spec` (String) The specification data describing the entity itself (as JSON).

<a id="nestedatt--entities--metadata"></a>
### Nested Schema for `entities.metadata`
Expand Down
5 changes: 5 additions & 0 deletions examples/data-sources/backstage_entities/data-source.tf
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,8 @@ data "backstage_entities" "example" {
"kind=Group,metadata.namespace=default",
]
}

# Outputs data from `spec` from an entity:
output "example" {
value = jsondecode(data.backstage_entities.example.entities[0].spec)["profile"]["email"]
}
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ require (
github.com/h2non/gock v1.2.0
github.com/hashicorp/terraform-plugin-docs v0.19.4
github.com/hashicorp/terraform-plugin-framework v1.11.0
github.com/hashicorp/terraform-plugin-framework-jsontypes v0.1.0
github.com/hashicorp/terraform-plugin-framework-validators v0.13.0
github.com/hashicorp/terraform-plugin-go v0.23.0
github.com/hashicorp/terraform-plugin-log v0.9.0
Expand All @@ -22,6 +23,7 @@ require (
github.com/Masterminds/sprig/v3 v3.2.3 // indirect
github.com/ProtonMail/go-crypto v1.1.0-alpha.2 // indirect
github.com/agext/levenshtein v1.2.2 // indirect
github.com/apparentlymart/go-textseg/v12 v12.0.0 // indirect
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
github.com/armon/go-radix v1.0.0 // indirect
github.com/bgentry/speakeasy v0.1.0 // indirect
Expand Down
3 changes: 3 additions & 0 deletions go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 6f77edc

Please sign in to comment.