Skip to content

Commit

Permalink
feat: Querying spaces by relative paths
Browse files Browse the repository at this point in the history
  • Loading branch information
TheMacies committed Oct 24, 2024
1 parent 154331b commit b087c3d
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 14 deletions.
7 changes: 7 additions & 0 deletions docs/data-sources/space_by_path.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ page_title: "spacelift_space_by_path Data Source - terraform-provider-spacelift"
subcategory: ""
description: |-
spacelift_space_by_path represents a Spacelift space - a collection of resources such as stacks, modules, policies, etc. Allows for more granular access control. Can have a parent space. In contrary to spacelift_space, this resource is identified by a path, not by an ID. For this data source to work, path must be unique. If there are multiple spaces with the same path, this datasource will fail.
This data source can be used either with absolute paths (starting with root) or relative paths. In the latter case, the path is relative to the current space the spacelift run is.
Disclaimer:
This datasource can only be used in a stack that resides in a space with inheritance enabled. In addition, the parent spaces (excluding root) must also have inheritance enabled.
---

# spacelift_space_by_path (Data Source)

`spacelift_space_by_path` represents a Spacelift **space** - a collection of resources such as stacks, modules, policies, etc. Allows for more granular access control. Can have a parent space. In contrary to `spacelift_space`, this resource is identified by a path, not by an ID. For this data source to work, path must be unique. If there are multiple spaces with the same path, this datasource will fail.
This data source can be used either with absolute paths (starting with `root`) or relative paths. In the latter case, the path is relative to the current space the spacelift run is.
**Disclaimer:**
This datasource can only be used in a stack that resides in a space with inheritance enabled. In addition, the parent spaces (excluding root) must also have inheritance enabled.

Expand All @@ -24,6 +26,11 @@ data "spacelift_space_by_path" "space" {
output "space_description" {
value = data.spacelift_space_by_path.space.description
}
// Assuming this data source is invoked in a run that belongs to a stack in a space located at "root", then the following data source shall be equal to the one above.
data "spacelift_space_by_relative_path" "space" {
space_path = "second space/my space"
}
```

<!-- schema generated by tfplugindocs -->
Expand Down
7 changes: 6 additions & 1 deletion examples/data-sources/spacelift_space_by_path/data-source.tf
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,9 @@ data "spacelift_space_by_path" "space" {

output "space_description" {
value = data.spacelift_space_by_path.space.description
}
}

// Assuming this data source is invoked in a run that belongs to a stack in a space located at "root", then the following data source shall be equal to the one above.
data "spacelift_space_by_relative_path" "space" {
space_path = "second space/my space"
}
31 changes: 24 additions & 7 deletions spacelift/data_current_space.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package spacelift

import (
"context"
"fmt"
"path"
"strings"

Expand Down Expand Up @@ -54,25 +55,28 @@ func dataCurrentSpace() *schema.Resource {
}
}

func dataCurrentSpaceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
func getStackIDFromToken(token string) (string, error) {
var claims jwt.StandardClaims

_, _, err := (&jwt.Parser{}).ParseUnverified(meta.(*internal.Client).Token, &claims)
_, _, err := (&jwt.Parser{}).ParseUnverified(token, &claims)
if err != nil {
// Don't care about validation errors, we don't actually validate those
// tokens, we only parse them.
var unverifiable *jwt.UnverfiableTokenError
if !errors.As(err, &unverifiable) {
return diag.Errorf("could not parse client token: %v", err)
return "", fmt.Errorf("could not parse client token: %v", err)
}
}

if issuer := claims.Issuer; issuer != "spacelift" {
return diag.Errorf("unexpected token issuer %s, is this a Spacelift run?", issuer)
return "", fmt.Errorf("unexpected token issuer %s, is this a Spacelift run?", issuer)
}

stackID, _ := path.Split(claims.Subject)
return stackID, nil
}

func getSpaceForStack(ctx context.Context, stackID string, meta interface{}) (structs.Space, error) {
var query struct {
Stack *structs.Stack `graphql:"stack(id: $id)"`
Module *structs.Module `graphql:"module(id: $id)"`
Expand All @@ -81,9 +85,9 @@ func dataCurrentSpaceRead(ctx context.Context, d *schema.ResourceData, meta inte
variables := map[string]interface{}{"id": toID(strings.TrimRight(stackID, "/"))}
if err := meta.(*internal.Client).Query(ctx, "StackRead", &query, variables); err != nil {
if strings.Contains(err.Error(), "denied") {
return diag.Errorf("could not query for stack: %v, is this stack administrative?", err)
return structs.Space{}, fmt.Errorf("could not query for stack: %v, is this stack administrative?", err)
}
return diag.Errorf("could not query for stack: %v", err)
return structs.Space{}, fmt.Errorf("could not query for stack: %v", err)
}

var space structs.Space
Expand All @@ -94,7 +98,20 @@ func dataCurrentSpaceRead(ctx context.Context, d *schema.ResourceData, meta inte
case query.Module != nil:
space = query.Module.SpaceDetails
default:
return diag.Errorf("could not find stack or module with ID %s", stackID)
return structs.Space{}, fmt.Errorf("could not find stack or module with ID %s", stackID)
}
return space, nil
}

func dataCurrentSpaceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
stackID, err := getStackIDFromToken(meta.(*internal.Client).Token)
if err != nil {
return diag.Errorf("%v", err)
}

space, err := getSpaceForStack(ctx, stackID, meta)
if err != nil {
return diag.Errorf("%v", err)
}

d.SetId(space.ID)
Expand Down
32 changes: 26 additions & 6 deletions spacelift/data_space_by_path.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ func dataSpaceByPath() *schema.Resource {
Description: "`spacelift_space_by_path` represents a Spacelift **space** - " +
"a collection of resources such as stacks, modules, policies, etc. Allows for more granular access control. Can have a parent space. In contrary to `spacelift_space`, this resource is identified by a path, not by an ID. " +
"For this data source to work, path must be unique. If there are multiple spaces with the same path, this datasource will fail. \n" +
"This data source can be used either with absolute paths (starting with `root`) or relative paths. In the latter case, the path is relative to the current space the spacelift run is. \n" +
"**Disclaimer:** \n" +
"This datasource can only be used in a stack that resides in a space with inheritance enabled. In addition, the parent spaces (excluding root) must also have inheritance enabled.",

Expand Down Expand Up @@ -62,8 +63,9 @@ func dataSpaceByPath() *schema.Resource {

func dataSpaceByPathRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
path := d.Get("space_path").(string)
if !strings.HasPrefix(path, "root/") && path != "root" {
return diag.Errorf("space path must start with `root`")

if strings.HasPrefix(path, "/") {
return diag.Errorf("path must not start with a slash")
}

var query struct {
Expand All @@ -74,7 +76,25 @@ func dataSpaceByPathRead(ctx context.Context, d *schema.ResourceData, meta inter
return diag.Errorf("could not query for spaces: %v", err)
}

space, err := findSpaceByPath(query.Spaces, path)
startingSpace := "root"
if !strings.HasPrefix(path, "root/") && path != "root" {
// if path does not start with root, we think it's a relative path. In this case it's relative to the current space the spacelift run is in

stackID, err := getStackIDFromToken(meta.(*internal.Client).Token)
if err != nil {
return diag.Errorf("couldn't identify the run: %v", err)
}

space, err := getSpaceForStack(ctx, stackID, meta)
if err != nil {
return diag.Errorf("couldn't determine current space: %v", err)
}

startingSpace = space.ID
path = space.Name + "/" + path // to be consistent with full path search where root is always included in the path
}

space, err := findSpaceByPath(query.Spaces, path, startingSpace)
if err != nil {
return diag.Errorf("error while traversing space path: %v", err)
}
Expand All @@ -97,12 +117,12 @@ func dataSpaceByPathRead(ctx context.Context, d *schema.ResourceData, meta inter
return nil
}

func findSpaceByPath(spaces []*structs.Space, path string) (*structs.Space, error) {
func findSpaceByPath(spaces []*structs.Space, path, startingSpace string) (*structs.Space, error) {
childrenMap := make(map[string][]*structs.Space, len(spaces))
var currentSpace *structs.Space

for _, space := range spaces {
if space.ID == "root" {
if space.ID == startingSpace {
currentSpace = space
}
if space.ParentSpace != nil {
Expand All @@ -111,7 +131,7 @@ func findSpaceByPath(spaces []*structs.Space, path string) (*structs.Space, erro
}

if currentSpace == nil {
return nil, fmt.Errorf("root space not found")
return nil, fmt.Errorf("%v space not found", startingSpace)
}

pathSplit := strings.Split(path, "/")
Expand Down

0 comments on commit b087c3d

Please sign in to comment.