Skip to content

Commit 7448c38

Browse files
authored
PORT-3191-terraform-provider-support-many-relation-embedded-url-authentication (#39)
1 parent 6069654 commit 7448c38

7 files changed

+227
-18
lines changed

docs/resources/blueprint.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,17 @@ Optional:
6161
- `items` (Map of String) A metadata of an array's items, in case the type is an array
6262
- `required` (Boolean) Whether or not the property is required
6363
- `spec` (String) The specification of the property, one of "async-api", "open-api", "embedded-url"
64+
- `spec_authentication` (Block List, Max: 1) The authentication of the specification (see [below for nested schema](#nestedblock--properties--spec_authentication))
65+
66+
<a id="nestedblock--properties--spec_authentication"></a>
67+
68+
### Nested Schema for `properties.spec_authentication`
69+
70+
Required:
71+
72+
- `authorization_url` (String) The authorization url of the specification
73+
- `client_id` (String) The client id of the specification
74+
- `token_url` (String) The token url of the specification
6475

6576
<a id="nestedblock--calculation_properties"></a>
6677

docs/resources/entity.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,11 @@ Optional:
5656

5757
Required:
5858

59-
- `identifier` (String) The id of the connected entity
6059
- `name` (String) The name of the relation
6160

61+
Optional:
62+
63+
- `identifier` (String) The id of the connected entity
64+
- `identifiers` (Set of String) The ids of the connected entities
65+
6266

port/cli/models.go

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,24 +24,31 @@ type (
2424
Blueprint string `json:"blueprint"`
2525
Team []string `json:"team,omitempty"`
2626
Properties map[string]interface{} `json:"properties"`
27-
Relations map[string]string `json:"relations"`
27+
Relations map[string]interface{} `json:"relations"`
2828
// TODO: add the rest of the fields.
2929
}
3030

3131
BlueprintProperty struct {
32-
Type string `json:"type,omitempty"`
33-
Title string `json:"title,omitempty"`
34-
Identifier string `json:"identifier,omitempty"`
35-
Items map[string]any `json:"items,omitempty"`
36-
Default interface{} `json:"default,omitempty"`
37-
Icon string `json:"icon,omitempty"`
38-
Format string `json:"format,omitempty"`
39-
Description string `json:"description,omitempty"`
40-
Blueprint string `json:"blueprint,omitempty"`
41-
Pattern string `json:"pattern,omitempty"`
42-
Enum []string `json:"enum,omitempty"`
43-
Spec string `json:"spec,omitempty"`
44-
EnumColors map[string]string `json:"enumColors,omitempty"`
32+
Type string `json:"type,omitempty"`
33+
Title string `json:"title,omitempty"`
34+
Identifier string `json:"identifier,omitempty"`
35+
Items map[string]any `json:"items,omitempty"`
36+
Default interface{} `json:"default,omitempty"`
37+
Icon string `json:"icon,omitempty"`
38+
Format string `json:"format,omitempty"`
39+
Description string `json:"description,omitempty"`
40+
Blueprint string `json:"blueprint,omitempty"`
41+
Pattern string `json:"pattern,omitempty"`
42+
Enum []string `json:"enum,omitempty"`
43+
Spec string `json:"spec,omitempty"`
44+
SpecAuthentication *SpecAuthentication `json:"specAuthentication,omitempty"`
45+
EnumColors map[string]string `json:"enumColors,omitempty"`
46+
}
47+
48+
SpecAuthentication struct {
49+
ClientId string `json:"clientId,omitempty"`
50+
AuthorizationUrl string `json:"authorizationUrl,omitempty"`
51+
TokenUrl string `json:"tokenUrl,omitempty"`
4552
}
4653

4754
BlueprintCalculationProperty struct {

port/resource_port_blueprint.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,32 @@ func newBlueprintResource() *schema.Resource {
151151
ValidateFunc: validation.StringInSlice([]string{"async-api", "open-api", "embedded-url"}, false),
152152
Description: "The specification of the property, one of \"async-api\", \"open-api\", \"embedded-url\"",
153153
},
154+
"spec_authentication": {
155+
Type: schema.TypeList,
156+
Optional: true,
157+
MinItems: 1,
158+
MaxItems: 1,
159+
Elem: &schema.Resource{
160+
Schema: map[string]*schema.Schema{
161+
"client_id": {
162+
Type: schema.TypeString,
163+
Required: true,
164+
Description: "The client id of the specification",
165+
},
166+
"authorization_url": {
167+
Type: schema.TypeString,
168+
Required: true,
169+
Description: "The authorization url of the specification",
170+
},
171+
"token_url": {
172+
Type: schema.TypeString,
173+
Required: true,
174+
Description: "The token url of the specification",
175+
},
176+
},
177+
},
178+
Description: "The authentication of the specification",
179+
},
154180
"enum": {
155181
Elem: &schema.Schema{
156182
Type: schema.TypeString,
@@ -419,6 +445,14 @@ func writeBlueprintFieldsToResource(d *schema.ResourceData, b *cli.Blueprint) {
419445
writeDefaultFieldToResource(v, k, d, p)
420446
}
421447

448+
if v.SpecAuthentication != nil {
449+
p["spec_authentication"] = []any{map[string]any{
450+
"token_url": v.SpecAuthentication.TokenUrl,
451+
"client_id": v.SpecAuthentication.ClientId,
452+
"authorization_url": v.SpecAuthentication.AuthorizationUrl,
453+
}}
454+
}
455+
422456
properties.Add(p)
423457
}
424458

@@ -591,6 +625,21 @@ func blueprintResourceToBody(d *schema.ResourceData) (*cli.Blueprint, error) {
591625
propFields.Spec = s.(string)
592626
}
593627

628+
if s, ok := p["spec_authentication"]; ok && len(s.([]any)) > 0 {
629+
if propFields.Spec != "embedded-url" {
630+
return nil, fmt.Errorf("spec_authentication can only be used when spec is embedded-url for property %s", p["identifier"].(string))
631+
}
632+
633+
if propFields.SpecAuthentication == nil {
634+
propFields.SpecAuthentication = &cli.SpecAuthentication{}
635+
}
636+
637+
propFields.SpecAuthentication.TokenUrl = s.([]any)[0].(map[string]interface{})["token_url"].(string)
638+
propFields.SpecAuthentication.AuthorizationUrl = s.([]any)[0].(map[string]interface{})["authorization_url"].(string)
639+
propFields.SpecAuthentication.ClientId = s.([]any)[0].(map[string]interface{})["client_id"].(string)
640+
641+
}
642+
594643
if r, ok := p["required"]; ok && r.(bool) {
595644
required = append(required, p["identifier"].(string))
596645
}

port/resource_port_blueprint_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,11 @@ func TestAccBlueprintWithSpecification(t *testing.T) {
179179
type = "string"
180180
format = "url"
181181
spec = "embedded-url"
182+
spec_authentication {
183+
token_url = "https://getport.io"
184+
client_id = "123"
185+
authorization_url = "https://getport.io"
186+
}
182187
}
183188
}
184189
`, identifier)
@@ -191,6 +196,7 @@ func TestAccBlueprintWithSpecification(t *testing.T) {
191196
Config: testAccActionConfigCreate,
192197
Check: resource.ComposeTestCheckFunc(
193198
resource.TestCheckResourceAttr("port-labs_blueprint.microservice", "properties.0.spec", "embedded-url"),
199+
resource.TestCheckResourceAttr("port-labs_blueprint.microservice", "properties.0.spec_authentication.0.token_url", "https://getport.io"),
194200
),
195201
},
196202
},

port/resource_port_entity.go

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,17 @@ func newEntityResource() *schema.Resource {
6767
},
6868
"identifier": {
6969
Type: schema.TypeString,
70-
Required: true,
70+
Optional: true,
7171
Description: "The id of the connected entity",
7272
},
73+
"identifiers": {
74+
Type: schema.TypeSet,
75+
Optional: true,
76+
Description: "The ids of the connected entities",
77+
Elem: &schema.Schema{
78+
Type: schema.TypeString,
79+
},
80+
},
7381
},
7482
},
7583
Optional: true,
@@ -163,6 +171,18 @@ func convert(prop map[string]interface{}, bp *cli.Blueprint) (interface{}, error
163171
return "", fmt.Errorf("unsupported type %s", valType)
164172
}
165173

174+
func validateRelation(rel map[string]interface{}) error {
175+
if rel["identifier"] == "" && len(rel["identifiers"].(*schema.Set).List()) == 0 {
176+
return fmt.Errorf("either relation identifier or identifiers is required for %s", rel["name"])
177+
}
178+
179+
if rel["identifier"] != "" && len(rel["identifiers"].(*schema.Set).List()) > 0 {
180+
return fmt.Errorf("either relation identifier or identifiers is required for %s", rel["name"])
181+
}
182+
183+
return nil
184+
}
185+
166186
func entityResourceToBody(d *schema.ResourceData, bp *cli.Blueprint) (*cli.Entity, error) {
167187
e := &cli.Entity{}
168188
if identifier, ok := d.GetOk("identifier"); ok {
@@ -190,11 +210,23 @@ func entityResourceToBody(d *schema.ResourceData, bp *cli.Blueprint) (*cli.Entit
190210
e.Team = teams
191211

192212
rels := d.Get("relations").(*schema.Set)
193-
relations := make(map[string]string)
213+
relations := make(map[string]interface{})
194214
for _, rel := range rels.List() {
195215
r := rel.(map[string]interface{})
196-
relations[r["name"].(string)] = r["identifier"].(string)
216+
identifier := r["identifier"].(string)
217+
identifiers := r["identifiers"].(*schema.Set).List()
218+
err := validateRelation(r)
219+
if err != nil {
220+
return nil, err
221+
}
222+
223+
if identifier != "" {
224+
relations[r["name"].(string)] = identifier
225+
} else {
226+
relations[r["name"].(string)] = identifiers
227+
}
197228
}
229+
198230
e.Relations = relations
199231
props := d.Get("properties").(*schema.Set)
200232
properties := make(map[string]interface{}, props.Len())
@@ -269,6 +301,28 @@ func writeEntityFieldsToResource(d *schema.ResourceData, e *cli.Entity) {
269301
properties.Add(p)
270302
}
271303
d.Set("properties", &properties)
304+
305+
relations := schema.Set{F: func(i interface{}) int {
306+
name := (i.(map[string]interface{}))["name"].(string)
307+
return schema.HashString(name)
308+
}}
309+
310+
for k, v := range e.Relations {
311+
if v == nil {
312+
continue
313+
}
314+
r := map[string]interface{}{}
315+
r["name"] = k
316+
switch t := v.(type) {
317+
case []interface{}:
318+
r["identifiers"] = t
319+
case string:
320+
r["identifier"] = t
321+
}
322+
relations.Add(r)
323+
324+
}
325+
d.Set("relations", &relations)
272326
}
273327

274328
func createEntity(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {

port/resource_port_entity_test.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,3 +289,81 @@ func TestAccPortEntitiesRelation(t *testing.T) {
289289
},
290290
})
291291
}
292+
293+
func TestAccPortEntitiesManyRelation(t *testing.T) {
294+
identifier1 := genID()
295+
identifier2 := genID()
296+
var testAccActionConfigCreate = fmt.Sprintf(`
297+
resource "port-labs_blueprint" "microservice" {
298+
title = "TF Provider Test BP0"
299+
icon = "Terraform"
300+
identifier = "%s"
301+
properties {
302+
identifier = "text"
303+
type = "string"
304+
title = "text"
305+
}
306+
relations {
307+
identifier = "tf-relation"
308+
title = "Test Relation"
309+
target = port-labs_blueprint.env.identifier
310+
many = true
311+
}
312+
}
313+
resource "port-labs_blueprint" "env" {
314+
title = "TF Provider Test BP0"
315+
icon = "Terraform"
316+
identifier = "%s"
317+
properties {
318+
identifier = "str"
319+
type = "string"
320+
title = "text"
321+
}
322+
}
323+
resource "port-labs_entity" "microservice" {
324+
title = "monolith"
325+
blueprint = port-labs_blueprint.microservice.id
326+
relations {
327+
name = "tf-relation"
328+
identifiers = ["production","staging"]
329+
}
330+
properties {
331+
name = "text"
332+
value = "test-relation"
333+
}
334+
}
335+
resource "port-labs_entity" "env" {
336+
title = "staging"
337+
identifier = "staging"
338+
blueprint = port-labs_blueprint.env.id
339+
properties {
340+
name = "str"
341+
value = "test-many-relation"
342+
}
343+
}
344+
345+
resource "port-labs_entity" "env2" {
346+
title = "production"
347+
identifier = "production"
348+
blueprint = port-labs_blueprint.env.id
349+
properties {
350+
name = "str"
351+
value = "test-many-relation"
352+
}
353+
}
354+
`, identifier1, identifier2)
355+
resource.Test(t, resource.TestCase{
356+
Providers: map[string]*schema.Provider{
357+
"port-labs": Provider(),
358+
},
359+
Steps: []resource.TestStep{
360+
{
361+
Config: testAccActionConfigCreate,
362+
Check: resource.ComposeTestCheckFunc(
363+
resource.TestCheckResourceAttr("port-labs_entity.microservice", "relations.0.identifiers.0", "production"),
364+
resource.TestCheckResourceAttr("port-labs_entity.microservice", "relations.0.identifiers.1", "staging"),
365+
),
366+
},
367+
},
368+
})
369+
}

0 commit comments

Comments
 (0)