Skip to content

Commit b087c3d

Browse files
committed
feat: Querying spaces by relative paths
1 parent 154331b commit b087c3d

File tree

4 files changed

+63
-14
lines changed

4 files changed

+63
-14
lines changed

docs/data-sources/space_by_path.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ page_title: "spacelift_space_by_path Data Source - terraform-provider-spacelift"
44
subcategory: ""
55
description: |-
66
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.
7+
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.
78
Disclaimer:
89
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.
910
---
1011

1112
# spacelift_space_by_path (Data Source)
1213

1314
`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.
15+
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.
1416
**Disclaimer:**
1517
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.
1618

@@ -24,6 +26,11 @@ data "spacelift_space_by_path" "space" {
2426
output "space_description" {
2527
value = data.spacelift_space_by_path.space.description
2628
}
29+
30+
// 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.
31+
data "spacelift_space_by_relative_path" "space" {
32+
space_path = "second space/my space"
33+
}
2734
```
2835

2936
<!-- schema generated by tfplugindocs -->

examples/data-sources/spacelift_space_by_path/data-source.tf

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,9 @@ data "spacelift_space_by_path" "space" {
44

55
output "space_description" {
66
value = data.spacelift_space_by_path.space.description
7-
}
7+
}
8+
9+
// 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.
10+
data "spacelift_space_by_relative_path" "space" {
11+
space_path = "second space/my space"
12+
}

spacelift/data_current_space.go

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package spacelift
22

33
import (
44
"context"
5+
"fmt"
56
"path"
67
"strings"
78

@@ -54,25 +55,28 @@ func dataCurrentSpace() *schema.Resource {
5455
}
5556
}
5657

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

60-
_, _, err := (&jwt.Parser{}).ParseUnverified(meta.(*internal.Client).Token, &claims)
61+
_, _, err := (&jwt.Parser{}).ParseUnverified(token, &claims)
6162
if err != nil {
6263
// Don't care about validation errors, we don't actually validate those
6364
// tokens, we only parse them.
6465
var unverifiable *jwt.UnverfiableTokenError
6566
if !errors.As(err, &unverifiable) {
66-
return diag.Errorf("could not parse client token: %v", err)
67+
return "", fmt.Errorf("could not parse client token: %v", err)
6768
}
6869
}
6970

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

7475
stackID, _ := path.Split(claims.Subject)
76+
return stackID, nil
77+
}
7578

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

8993
var space structs.Space
@@ -94,7 +98,20 @@ func dataCurrentSpaceRead(ctx context.Context, d *schema.ResourceData, meta inte
9498
case query.Module != nil:
9599
space = query.Module.SpaceDetails
96100
default:
97-
return diag.Errorf("could not find stack or module with ID %s", stackID)
101+
return structs.Space{}, fmt.Errorf("could not find stack or module with ID %s", stackID)
102+
}
103+
return space, nil
104+
}
105+
106+
func dataCurrentSpaceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
107+
stackID, err := getStackIDFromToken(meta.(*internal.Client).Token)
108+
if err != nil {
109+
return diag.Errorf("%v", err)
110+
}
111+
112+
space, err := getSpaceForStack(ctx, stackID, meta)
113+
if err != nil {
114+
return diag.Errorf("%v", err)
98115
}
99116

100117
d.SetId(space.ID)

spacelift/data_space_by_path.go

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ func dataSpaceByPath() *schema.Resource {
1818
Description: "`spacelift_space_by_path` represents a Spacelift **space** - " +
1919
"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. " +
2020
"For this data source to work, path must be unique. If there are multiple spaces with the same path, this datasource will fail. \n" +
21+
"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" +
2122
"**Disclaimer:** \n" +
2223
"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.",
2324

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

6364
func dataSpaceByPathRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
6465
path := d.Get("space_path").(string)
65-
if !strings.HasPrefix(path, "root/") && path != "root" {
66-
return diag.Errorf("space path must start with `root`")
66+
67+
if strings.HasPrefix(path, "/") {
68+
return diag.Errorf("path must not start with a slash")
6769
}
6870

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

77-
space, err := findSpaceByPath(query.Spaces, path)
79+
startingSpace := "root"
80+
if !strings.HasPrefix(path, "root/") && path != "root" {
81+
// 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
82+
83+
stackID, err := getStackIDFromToken(meta.(*internal.Client).Token)
84+
if err != nil {
85+
return diag.Errorf("couldn't identify the run: %v", err)
86+
}
87+
88+
space, err := getSpaceForStack(ctx, stackID, meta)
89+
if err != nil {
90+
return diag.Errorf("couldn't determine current space: %v", err)
91+
}
92+
93+
startingSpace = space.ID
94+
path = space.Name + "/" + path // to be consistent with full path search where root is always included in the path
95+
}
96+
97+
space, err := findSpaceByPath(query.Spaces, path, startingSpace)
7898
if err != nil {
7999
return diag.Errorf("error while traversing space path: %v", err)
80100
}
@@ -97,12 +117,12 @@ func dataSpaceByPathRead(ctx context.Context, d *schema.ResourceData, meta inter
97117
return nil
98118
}
99119

100-
func findSpaceByPath(spaces []*structs.Space, path string) (*structs.Space, error) {
120+
func findSpaceByPath(spaces []*structs.Space, path, startingSpace string) (*structs.Space, error) {
101121
childrenMap := make(map[string][]*structs.Space, len(spaces))
102122
var currentSpace *structs.Space
103123

104124
for _, space := range spaces {
105-
if space.ID == "root" {
125+
if space.ID == startingSpace {
106126
currentSpace = space
107127
}
108128
if space.ParentSpace != nil {
@@ -111,7 +131,7 @@ func findSpaceByPath(spaces []*structs.Space, path string) (*structs.Space, erro
111131
}
112132

113133
if currentSpace == nil {
114-
return nil, fmt.Errorf("root space not found")
134+
return nil, fmt.Errorf("%v space not found", startingSpace)
115135
}
116136

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

0 commit comments

Comments
 (0)