Skip to content

Commit 1e3a7d2

Browse files
authored
Read the Team entity Identifier when reading team data (#226)
* Read the Team entity Identifier when reading team data This is needed for the Users & Teams migration as entities will reference teams by their identifier (not their names like before). * Updated the old port_team example and added an example for port_team after migration * updated docs * Shuffle order of test files * shorten package name for CI titles
1 parent 368a475 commit 1e3a7d2

File tree

22 files changed

+260
-64
lines changed

22 files changed

+260
-64
lines changed

.github/workflows/ci.yml

+17-9
Original file line numberDiff line numberDiff line change
@@ -50,18 +50,22 @@ jobs:
5050
import json
5151
import os
5252
import subprocess
53+
from random import Random
5354
5455
result = subprocess.run(("go", "list", "./..."), capture_output=True, text=True)
5556
assert result.returncode == 0, result.stderr
5657
packages: list[str] = list(filter(lambda x: x != '', result.stdout.split('\n')))
5758
files: list[str] = []
5859
include: list[dict[str, str]] = []
5960
for package in packages:
60-
file = package.replace('/', '-').replace('.', '-') + ".test"
61+
package_short = package.removeprefix('github.com/port-labs/terraform-provider-port-labs/')
62+
file = package_short.replace('/', '-').replace('.', '-') + ".test"
6163
subprocess.run(("go", "test", package, "-c", "-o", file), capture_output=True, text=True, check=True)
6264
if os.path.isfile(file):
6365
files.append(file)
64-
include.append(dict(test_file=file, package=package))
66+
include.append(dict(test_file=file, package=package, package_short=package_short))
67+
68+
Random(int("${{ github.run_id }}")).shuffle(files)
6569
json_files = json.dumps(files)
6670
with open(os.environ['GITHUB_OUTPUT'], 'a') as github_output:
6771
print(f'test_files=' + json.dumps(files), file=github_output)
@@ -74,7 +78,7 @@ jobs:
7478
if-no-files-found: error
7579

7680
acctest:
77-
name: Acceptance Test - ${{matrix.package}}
81+
name: Test ${{matrix.package_short}}
7882
needs: acctest-build
7983
concurrency:
8084
group: acctest
@@ -98,7 +102,7 @@ jobs:
98102
- uses: actions/download-artifact@v4
99103
with:
100104
name: test-files
101-
- name: Run tests - ${{matrix.package}}
105+
- name: Test ${{ matrix.package }}
102106
env:
103107
TF_ACC: 1
104108
CI_USER_NAME: ${{ secrets.CI_USER_NAME }}
@@ -107,7 +111,8 @@ jobs:
107111
PORT_BASE_URL: ${{ secrets.PORT_BASE_URL }}
108112
run: |
109113
chmod u+x "${{matrix.test_file}}"
110-
gotestsum --raw-command --format testname --junitfile "./test-results/${{matrix.test_file}}.xml" -- go tool test2json -t -p "${{matrix.package}}" "./${{matrix.test_file}}" \
114+
gotestsum --raw-command --rerun-fails --format testname --junitfile "./test-results/${{matrix.test_file}}.xml" \
115+
-- go tool test2json -t -p "${{matrix.package}}" "./${{matrix.test_file}}" \
111116
-test.v=test2json -test.timeout 10m -test.parallel 1 -test.shuffle "${{ github.run_id }}"
112117
113118
- uses: actions/upload-artifact@v4
@@ -129,9 +134,12 @@ jobs:
129134
merge-multiple: 'true'
130135
path: 'test-results'
131136
- name: Publish Test Report
132-
uses: mikepenz/action-junit-report@v4
137+
uses: mikepenz/action-junit-report@v5
133138
with:
134139
report_paths: './test-results/*.xml'
135-
include_passed: true
136-
require_tests: true
137-
fail_on_failure: true
140+
include_passed: 'true'
141+
require_tests: 'true'
142+
fail_on_failure: 'true'
143+
comment: 'true'
144+
check_retries: 'true'
145+
flaky_summary: 'true'

docs/resources/port_blueprint.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ resource "port_blueprint" "microservice" {
287287
- `ownership` (Attributes) Optional ownership field for Blueprint. 'type' can be Inherited or Direct. If 'Inherited', then 'path' is required and must be a valid relation identifiers path. (see [below for nested schema](#nestedatt--ownership))
288288
- `properties` (Attributes) The properties of the blueprint (see [below for nested schema](#nestedatt--properties))
289289
- `relations` (Attributes Map) The relations of the blueprint (see [below for nested schema](#nestedatt--relations))
290-
- `team_inheritance` (Attributes) The team inheritance of the blueprint (see [below for nested schema](#nestedatt--team_inheritance))
290+
- `team_inheritance` (Attributes, Deprecated) The team inheritance of the blueprint (see [below for nested schema](#nestedatt--team_inheritance))
291291
- `webhook_changelog_destination` (Attributes) The webhook changelog destination of the blueprint (see [below for nested schema](#nestedatt--webhook_changelog_destination))
292292

293293
### Read-Only

docs/resources/port_team.md

+1
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,6 @@ Team resource
2828

2929
- `created_at` (String) The creation date of the team
3030
- `id` (String) The ID of this resource.
31+
- `identifier` (String) The machine-readable identifier of the _team entity
3132
- `provider_name` (String) The provider of the team
3233
- `updated_at` (String) The last update date of the team

examples/provider.tf

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ terraform {
22
required_providers {
33
port = {
44
source = "port-labs/port-labs"
5-
version = "~> 2.0.0"
5+
version = "~> 2"
66
}
77
}
88
}

examples/resources/port_team/main.tf

-10
This file was deleted.
+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
resource "port_team" "Example-Team" {
2+
name = "Example Team"
3+
description = "An example team"
4+
# Note, this will need real users to work!
5+
users = [
6+
7+
8+
9+
]
10+
}
11+
12+
resource "port_blueprint" "some-blueprint" {
13+
identifier = "some-blueprint"
14+
title = "Some Blueprint"
15+
}
16+
17+
resource "port_entity" "some-entity" {
18+
blueprint = port_blueprint.some-blueprint.identifier
19+
title = "Some Entity"
20+
teams = [
21+
port_team.Example-Team.name,
22+
]
23+
}
+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
resource "port_team" "Example-Team" {
2+
name = "Example Team"
3+
description = "An example team"
4+
# Note, this will need real users to work!
5+
users = [
6+
7+
8+
9+
]
10+
}
11+
12+
resource "port_blueprint" "some-blueprint" {
13+
identifier = "some-blueprint"
14+
title = "Some Blueprint"
15+
ownership = {
16+
type = "Direct"
17+
}
18+
}
19+
20+
resource "port_entity" "some-entity" {
21+
blueprint = port_blueprint.some-blueprint.identifier
22+
title = "Some Entity"
23+
teams = [
24+
port_team.Example-Team.identifier,
25+
]
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../provider.tf

go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ require (
1515
require (
1616
github.com/ProtonMail/go-crypto v1.1.0-alpha.5-proton // indirect
1717
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
18+
github.com/avast/retry-go/v4 v4.6.1
1819
github.com/cloudflare/circl v1.3.9 // indirect
1920
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
2021
github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0 // indirect

go.sum

+4-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmms
2121
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
2222
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
2323
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
24+
github.com/avast/retry-go/v4 v4.6.1 h1:VkOLRubHdisGrHnTu89g08aQEWEgRU7LVEop3GbIcMk=
25+
github.com/avast/retry-go/v4 v4.6.1/go.mod h1:V6oF8njAwxJ5gRo1Q7Cxab24xs5NCWZBeaHHBklR8mA=
2426
github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY=
2527
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
2628
github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA=
@@ -195,8 +197,9 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
195197
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
196198
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
197199
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
198-
github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s=
199200
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
201+
github.com/stretchr/tetify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
202+
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
200203
github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
201204
github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI=
202205
github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=

internal/cli/client.go

+36-8
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,21 @@ package cli
33
import (
44
"context"
55
"encoding/json"
6+
"fmt"
7+
"slices"
68
"strings"
79

810
"github.com/go-resty/resty/v2"
911
)
1012

11-
type (
12-
Option func(*PortClient)
13-
PortClient struct {
14-
Client *resty.Client
15-
ClientID string
16-
Token string
17-
}
18-
)
13+
type Option func(*PortClient)
14+
15+
type PortClient struct {
16+
Client *resty.Client
17+
ClientID string
18+
Token string
19+
featureFlags []string
20+
}
1921

2022
func New(baseURL string, opts ...Option) (*PortClient, error) {
2123
c := &PortClient{
@@ -42,6 +44,32 @@ func New(baseURL string, opts ...Option) (*PortClient, error) {
4244
return c, nil
4345
}
4446

47+
// FeatureFlags Fetches the feature flags from the Organization API. It caches the feature flags locally to reduce call
48+
// count.
49+
func (c *PortClient) FeatureFlags(ctx context.Context) ([]string, error) {
50+
if c.featureFlags == nil {
51+
organization, _, err := c.ReadOrganization(ctx)
52+
if err != nil {
53+
return nil, fmt.Errorf("failed to read organization data: %w", err)
54+
}
55+
c.featureFlags = organization.FeatureFlags
56+
}
57+
return slices.Clone(c.featureFlags), nil
58+
}
59+
60+
func (c *PortClient) HasFeatureFlags(ctx context.Context, flags ...string) (bool, error) {
61+
orgFlags, err := c.FeatureFlags(ctx)
62+
if err != nil {
63+
return false, err
64+
}
65+
for _, flag := range flags {
66+
if !slices.Contains(orgFlags, flag) {
67+
return false, nil
68+
}
69+
}
70+
return true, nil
71+
}
72+
4573
func (c *PortClient) Authenticate(ctx context.Context, clientID, clientSecret string) (string, error) {
4674
url := "v1/auth/access_token"
4775
resp, err := c.Client.R().

internal/cli/models.go

+13-2
Original file line numberDiff line numberDiff line change
@@ -411,7 +411,7 @@ type (
411411
Entity *EntityProperty `json:"entity,omitempty"`
412412
}
413413

414-
Team struct {
414+
PortTeam struct {
415415
CreatedAt *time.Time `json:"createdAt,omitempty"`
416416
UpdatedAt *time.Time `json:"updatedAt,omitempty"`
417417
Name string `json:"name,omitempty"`
@@ -420,6 +420,11 @@ type (
420420
Provider string `json:"provider,omitempty"`
421421
}
422422

423+
Team struct {
424+
PortTeam
425+
Identifier *string `json:"identifier,omitempty"`
426+
}
427+
423428
Migration struct {
424429
Meta
425430
Id string `json:"id,omitempty"`
@@ -490,11 +495,12 @@ type PortBody struct {
490495
ActionPermissions ActionPermissions `json:"permissions"`
491496
Webhook Webhook `json:"integration"`
492497
Scorecard Scorecard `json:"Scorecard"`
493-
Team Team `json:"team"`
498+
Team PortTeam `json:"team"`
494499
Page Page `json:"page"`
495500
MigrationId string `json:"migrationId"`
496501
Migration Migration `json:"migration"`
497502
Folder Folder `json:"folder"`
503+
Organization *Organization `json:"organization"`
498504
}
499505

500506
type SearchEntityResult struct {
@@ -560,3 +566,8 @@ type Integration struct {
560566
Config *map[string]any `json:"config"`
561567
ChangelogDestination *ChangelogDestination `json:"changelogDestination,omitempty"`
562568
}
569+
570+
type Organization struct {
571+
Name string `json:"name"`
572+
FeatureFlags []string `json:"featureFlags"`
573+
}

internal/cli/organization.go

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package cli
2+
3+
import (
4+
"context"
5+
"fmt"
6+
)
7+
8+
const orgUrl = "/v1/organization"
9+
10+
func (c *PortClient) ReadOrganization(ctx context.Context) (*Organization, int, error) {
11+
pb := &PortBody{}
12+
resp, err := c.Client.R().
13+
SetContext(ctx).
14+
SetHeader("Accept", "application/json").
15+
SetResult(pb).
16+
Get(orgUrl)
17+
if err != nil {
18+
return nil, 0, err
19+
} else if resp.IsError() {
20+
return nil, resp.StatusCode(), fmt.Errorf("failed to read organization, got: %s", resp.Body())
21+
}
22+
if pb.Organization == nil {
23+
return nil, 0, fmt.Errorf("port-api returned an invalid response: Organization is nil")
24+
}
25+
return pb.Organization, resp.StatusCode(), nil
26+
}

0 commit comments

Comments
 (0)