Skip to content

Commit 9896601

Browse files
authored
add account provisioning and deprovisioning (#44)
* add account provisioning and deprovisioning * go mod tidy && go mod vendor * update e2e tests and use Terraform for setup/teardown * fix tf * fix lint * fix jq * fix artifact * trim new lines * fix tfstate file name * remove stray quote * fix tfstate again * set resource version on user Delete op * return v2.GrantAlreadyRevoked
1 parent f34fcc3 commit 9896601

File tree

22 files changed

+4842
-461
lines changed

22 files changed

+4842
-461
lines changed

.github/actions/install-tcld/action.yml

Lines changed: 0 additions & 24 deletions
This file was deleted.

.github/workflows/ci.yaml

Lines changed: 81 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
name: ci
2-
on: pull_request
2+
on:
3+
pull_request:
4+
paths-ignore:
5+
- "README.md"
36
env:
47
TEST_NAMESPACE_NAME: "ci-test"
58

@@ -40,71 +43,35 @@ jobs:
4043
with:
4144
test-results: test.json
4245

43-
# TODO use Terraform for the setup/teardown
44-
4546
setup-temporal-cloud:
4647
runs-on: ubuntu-latest
4748
env:
4849
TEMPORAL_CLOUD_API_KEY: ${{ secrets.TEMPORAL_CLOUD_API_KEY }}
4950
AUTO_CONFIRM: true
50-
outputs:
51-
user-id: ${{ steps.user-id.outputs.user }}
5251
steps:
5352
- name: Checkout code
5453
uses: actions/checkout@v4
55-
- name: Install tcld
56-
uses: ./.github/actions/install-tcld
57-
- name: Create test namespace
58-
run: |
59-
if ! tcld gen ca --org c1 -d 30d --ca-cert ca.pem --ca-key key.pem; then
60-
exit 1
61-
fi
62-
if ! request_id=$(tcld namespace create --ca-certificate-file=ca.pem --namespace=${{ env.TEST_NAMESPACE_NAME }} --region="us-west-2" | jq --exit-status '.requestStatus.requestId' | tr -d '"'); then
63-
exit 1
64-
fi
65-
while true; do
66-
if ! resp=$(tcld request get --request-id=$request_id | jq --exit-status '.requestStatus'); then
67-
exit 1
68-
fi
69-
if ! request_state=$(echo $resp | jq --exit-status '.state' | tr -d '"'); then
70-
exit 1
71-
fi
72-
if ! failure_reason=$(echo $resp | jq --exit-status '.failureReason' | tr -d '"'); then
73-
exit 1
74-
fi
75-
if [[ "$failure_reason" != "" ]]; then
76-
exit 1
77-
fi
78-
if [[ "$request_state" == "Fulfilled" ]]; then
79-
break
80-
fi
81-
done
82-
- name: Create test user
83-
run: |
84-
if ! request_id=$(tcld user invite --account-role="Read" --user-email "ci-test@${{ secrets.TEST_EMAIL_DOMAIN}}" | jq --exit-status '.requestId' | tr -d '"'); then
85-
exit 1
86-
fi
87-
while true; do
88-
if ! resp=$(tcld request get --request-id=$request_id | jq --exit-status '.requestStatus'); then
89-
exit 1
90-
fi
91-
if ! request_state=$(echo $resp | jq --exit-status '.state' | tr -d '"'); then
92-
exit 1
93-
fi
94-
if ! failure_reason=$(echo $resp | jq --exit-status '.failureReason' | tr -d '"'); then
95-
exit 1
96-
fi
97-
if [[ "$failure_reason" != "" ]]; then
98-
exit 1
99-
fi
100-
if [[ "$request_state" == "Fulfilled" ]]; then
101-
break
102-
fi
103-
done
104-
- name: Send user ID to job output
105-
id: user-id
106-
run: |
107-
ID=$(tcld user get --user-email "ci-test@${{ secrets.TEST_EMAIL_DOMAIN}}" | jq --exit-status '.id' | tr -d '"') && echo "user=$ID" >> "$GITHUB_OUTPUT"
54+
- name: Setup Terraform
55+
uses: hashicorp/setup-terraform@v3
56+
with:
57+
terraform_version: "1.1.7"
58+
- run: |
59+
terraform init -input=false
60+
working-directory: dev
61+
name: "Initialize Terraform"
62+
- run: |
63+
terraform apply -auto-approve -input=false
64+
working-directory: dev
65+
id: apply
66+
name: "Create test namespace"
67+
- name: "Upload state"
68+
if: ${{ success() }}
69+
uses: actions/upload-artifact@v4
70+
with:
71+
name: state
72+
path: dev/terraform.tfstate
73+
retention-days: 1
74+
overwrite: true
10875

10976
test:
11077
runs-on: ubuntu-latest
@@ -115,8 +82,8 @@ jobs:
11582
CONNECTOR_GRANT: 'namespace:ci-test.iv3js:read:user'
11683
CONNECTOR_IMMUTABLE_ENTITLEMENT: 'account-role:iv3js-owner:member'
11784
CONNECTOR_ENTITLEMENT: 'namespace:ci-test.iv3js:read'
118-
CONNECTOR_PRINCIPAL_ID: ${{ needs.setup-temporal-cloud.outputs.user-id }}
11985
CONNECTOR_PRINCIPAL_TYPE: 'user'
86+
USER_EMAIL: "ci-test@${{ secrets.TEST_EMAIL_DOMAIN}}"
12087
steps:
12188
- name: Install Go
12289
uses: actions/setup-go@v4
@@ -130,28 +97,61 @@ jobs:
13097
run: ./baton-temporalcloud
13198
- name: Install baton
13299
run: ./scripts/get-baton.sh && mv baton /usr/local/bin
100+
- name: provision user
101+
run: |
102+
./baton-temporalcloud && ./baton-temporalcloud --create-account-profile '{"email": "${{ env.USER_EMAIL }}"}'
103+
- uses: mathiasvr/command-output@v2.0.0
104+
name: Check user was provisioned
105+
id: user
106+
with:
107+
run: |
108+
./baton-temporalcloud && baton resources --resource-type=user --output-format=json | jq --exit-status --raw-output '.resources | map(if .resource.displayName == "${{ env.USER_EMAIL }}" then .resource.id.resource else empty end) | if length == 1 then .[0] else null end' | tr -d "\n"
133109
- name: list grants
134110
run: |
135111
./baton-temporalcloud && baton grants
136112
- name: Grant entitlement
137-
run: ./baton-temporalcloud && ./baton-temporalcloud --grant-entitlement="${{ env.CONNECTOR_ENTITLEMENT }}" --grant-principal="${{ env.CONNECTOR_PRINCIPAL_ID }}" --grant-principal-type="${{ env.CONNECTOR_PRINCIPAL_TYPE }}"
113+
env:
114+
PRINCIPAL_ID: ${{ steps.user.outputs.stdout }}
115+
run: |
116+
./baton-temporalcloud && ./baton-temporalcloud --grant-entitlement="${{ env.CONNECTOR_ENTITLEMENT }}" --grant-principal="$PRINCIPAL_ID" --grant-principal-type="${{ env.CONNECTOR_PRINCIPAL_TYPE }}"
138117
- name: list grants
139118
run: |
140119
./baton-temporalcloud && baton grants
141120
- name: Check grant was granted
142121
run: |
143-
./baton-temporalcloud && baton grants --entitlement="${{ env.CONNECTOR_ENTITLEMENT }}" --output-format=json | jq --exit-status ".grants[].principal.id.resource == \"${{ env.CONNECTOR_PRINCIPAL_ID }}\""
122+
./baton-temporalcloud && baton grants --entitlement="${{ env.CONNECTOR_ENTITLEMENT }}" --output-format=json | jq --exit-status '.grants[].principal.id.resource == env.PRINCIPAL_ID'
123+
env:
124+
PRINCIPAL_ID: ${{ steps.user.outputs.stdout }}
144125
- name: Revoke grants
145-
run: ./baton-temporalcloud && ./baton-temporalcloud --revoke-grant="${{ env.CONNECTOR_GRANT }}:${{ env.CONNECTOR_PRINCIPAL_ID }}"
126+
run: ./baton-temporalcloud && ./baton-temporalcloud --revoke-grant="${{ env.CONNECTOR_GRANT }}:$PRINCIPAL_ID"
127+
env:
128+
PRINCIPAL_ID: ${{ steps.user.outputs.stdout }}
146129
- name: Check grant was revoked
147-
run: ./baton-temporalcloud && baton grants --entitlement="${{ env.CONNECTOR_ENTITLEMENT }}" --output-format=json | jq --exit-status "if .grants then .grants[]?.principal.id.resource != \"${{ env.CONNECTOR_PRINCIPAL_ID }}\" else . end"
130+
run: ./baton-temporalcloud && baton grants --entitlement="${{ env.CONNECTOR_ENTITLEMENT }}" --output-format=json | jq --exit-status 'if .grants then .grants[]?.principal.id.resource != env.PRINCIPAL_ID else . end'
131+
env:
132+
PRINCIPAL_ID: ${{ steps.user.outputs.stdout }}
148133
- name: Attempt to grant immutable grant
149134
# CLI will return a non-zero code here, but we want to continue anyway
150135
run: |
151-
./baton-temporalcloud && (./baton-temporalcloud --grant-entitlement="${{ env.CONNECTOR_IMMUTABLE_ENTITLEMENT }}" --grant-principal="${{ env.CONNECTOR_PRINCIPAL_ID }}" --grant-principal-type="${{ env.CONNECTOR_PRINCIPAL_TYPE }}" || true)
136+
./baton-temporalcloud && (./baton-temporalcloud --grant-entitlement="${{ env.CONNECTOR_IMMUTABLE_ENTITLEMENT }}" --grant-principal="$PRINCIPAL_ID" --grant-principal-type="${{ env.CONNECTOR_PRINCIPAL_TYPE }}" || true)
137+
env:
138+
PRINCIPAL_ID: ${{ steps.user.outputs.stdout }}
152139
- name: Check immutable grant was not granted
140+
env:
141+
PRINCIPAL_ID: ${{ steps.user.outputs.stdout }}
142+
run: |
143+
./baton-temporalcloud && baton grants --entitlement="${{ env.CONNECTOR_IMMUTABLE_ENTITLEMENT }}" --output-format=json | jq --exit-status '.grants | map(if .principal.id.resource == env.PRINCIPAL_ID then .principal else empty end) | length == 0'
144+
- name: Deprovision user
145+
env:
146+
PRINCIPAL_ID: ${{ steps.user.outputs.stdout }}
147+
run: |
148+
./baton-temporalcloud && ./baton-temporalcloud --delete-resource="$PRINCIPAL_ID" --delete-resource-type=user
149+
- name: Check user was deprovisioned
150+
env:
151+
PRINCIPAL_ID: ${{ steps.user.outputs.stdout }}
153152
run: |
154-
./baton-temporalcloud && baton grants --entitlement="${{ env.CONNECTOR_IMMUTABLE_ENTITLEMENT }}" --output-format=json | jq --exit-status "if .grants then .grants[]?.principal.id.resource != \"${{ env.CONNECTOR_PRINCIPAL_ID }}\" else . end"
153+
./baton-temporalcloud && baton resources --resource-type=user --output-format=json | jq --exit-status '.resources | map(if .resource.id.resource == env.PRINCIPAL_ID then .resource else empty end) | length == 0'
154+
155155

156156
teardown-temporal-cloud:
157157
runs-on: ubuntu-latest
@@ -164,11 +164,20 @@ jobs:
164164
steps:
165165
- name: Checkout code
166166
uses: actions/checkout@v4
167-
- name: Install tcld
168-
uses: ./.github/actions/install-tcld
169-
- name: Delete test namespace
170-
run: |
171-
tcld namespace delete --namespace "${{ env.TEST_NAMESPACE_NAME }}.iv3js"
172-
- name: Delete test user
167+
- name: Setup Terraform
168+
uses: hashicorp/setup-terraform@v3
169+
with:
170+
terraform_version: "1.1.7"
171+
- run: |
172+
terraform init -input=false
173+
working-directory: dev
174+
name: "Initialize Terraform"
175+
- name: "Get state"
176+
uses: actions/download-artifact@v4
177+
with:
178+
name: state
179+
path: dev/
180+
- name: "Delete test namesapce"
173181
run: |
174-
tcld user delete --user-email "ci-test@${{ secrets.TEST_EMAIL_DOMAIN}}"
182+
terraform apply -destroy -auto-approve -input=false
183+
working-directory: dev

cmd/baton-temporalcloud/main.go

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,14 @@ import (
1616
"github.com/conductorone/baton-temporalcloud/pkg/connector"
1717
)
1818

19+
var defaultAccountRoleOpts = []string{"read", "developer", "admin"}
20+
1921
const (
20-
version = "dev"
21-
connectorName = "baton-temporalcloud"
22-
apiKey = "api-key"
23-
allowInsecure = "allow-insecure"
22+
version = "dev"
23+
connectorName = "baton-temporalcloud"
24+
apiKey = "api-key"
25+
allowInsecure = "allow-insecure"
26+
defaultAccountRole = "default-account-role"
2427
)
2528

2629
var (
@@ -30,7 +33,16 @@ var (
3033
field.WithDefaultValue(false),
3134
field.WithDescription("Allow insecure TLS connections to the Temporal Cloud API."),
3235
)
33-
configurationFields = []field.SchemaField{APIKeyField, AllowInsecureField}
36+
DefaultAccountRoleField = field.StringField(
37+
defaultAccountRole,
38+
field.WithRequired(false),
39+
field.WithDescription(fmt.Sprintf("The default account role to use for account provisioning, must be one of %v", defaultAccountRoleOpts)),
40+
field.WithDefaultValue("read"),
41+
field.WithString(func(r *field.StringRuler) {
42+
r.In(defaultAccountRoleOpts)
43+
}),
44+
)
45+
configurationFields = []field.SchemaField{APIKeyField, AllowInsecureField, DefaultAccountRoleField}
3446
)
3547

3648
func main() {
@@ -55,9 +67,15 @@ func main() {
5567

5668
func getConnector(ctx context.Context, cfg *viper.Viper) (types.ConnectorServer, error) {
5769
l := ctxzap.Extract(ctx)
70+
var opts []connector.Opt
71+
if cfg.IsSet(defaultAccountRole) && cfg.GetString(defaultAccountRole) != "" {
72+
opts = append(opts, connector.WithDefaultAccountRole(cfg.GetString(defaultAccountRole)))
73+
}
74+
5875
cb, err := connector.New(ctx,
5976
cfg.GetString(apiKey),
6077
cfg.GetBool(allowInsecure),
78+
opts...,
6179
)
6280
if err != nil {
6381
l.Error("error creating connector", zap.Error(err))

dev/main.tf

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
terraform {
2+
required_providers {
3+
temporalcloud = {
4+
source = "temporalio/temporalcloud"
5+
}
6+
}
7+
}
8+
9+
provider "temporalcloud" {
10+
allowed_account_id = "iv3js"
11+
}
12+
13+
resource "temporalcloud_namespace" "test_namespace" {
14+
name = var.TEST_NAMESPACE_NAME
15+
regions = ["aws-us-west-2"]
16+
api_key_auth = true
17+
retention_days = 7
18+
}

dev/variables.tf

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
variable "TEST_NAMESPACE_NAME" {
2+
type = string
3+
default = "ci-test"
4+
}

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ require (
1010
github.com/quasilyte/go-ruleguard/dsl v0.3.22
1111
github.com/spf13/viper v1.20.1
1212
github.com/stretchr/testify v1.10.0
13-
go.temporal.io/cloud-sdk v0.3.0
13+
go.temporal.io/cloud-sdk v0.3.1
1414
go.uber.org/zap v1.27.0
1515
golang.org/x/text v0.25.0
1616
google.golang.org/grpc v1.72.0

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -275,8 +275,8 @@ go.opentelemetry.io/proto/otlp v1.6.0 h1:jQjP+AQyTf+Fe7OKj/MfkDrmK4MNVtw2NpXsf9f
275275
go.opentelemetry.io/proto/otlp v1.6.0/go.mod h1:cicgGehlFuNdgZkcALOCh3VE6K/u2tAjzlRhDwmVpZc=
276276
go.temporal.io/api v1.49.0 h1:aL+zfrdZC6iRU0Lqc1Qds83oMEj1DwhmPUdfiIenGE4=
277277
go.temporal.io/api v1.49.0/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM=
278-
go.temporal.io/cloud-sdk v0.3.0 h1:i3Wsn2feIrE1lIAV/ggK+MhUgWCaPZt40ACqrUAJ6CU=
279-
go.temporal.io/cloud-sdk v0.3.0/go.mod h1:AueDDyuayosk+zalfrnuftRqnRQTHwD0HYwNgEQc0YE=
278+
go.temporal.io/cloud-sdk v0.3.1 h1:yhS0XnPOnsu80opXIgFnihGU3tBeHHQA479GHUo/cv8=
279+
go.temporal.io/cloud-sdk v0.3.1/go.mod h1:AueDDyuayosk+zalfrnuftRqnRQTHwD0HYwNgEQc0YE=
280280
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
281281
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
282282
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=

0 commit comments

Comments
 (0)