diff --git a/docs/data-sources/port_search.md b/docs/data-sources/port_search.md
new file mode 100644
index 00000000..77e55be7
--- /dev/null
+++ b/docs/data-sources/port_search.md
@@ -0,0 +1,227 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "port_search Data Source - terraform-provider-port-labs"
+subcategory: ""
+description: |-
+ Search Data Source
+ The search data source allows you to search for entities in Port.
+ See the Port documentation https://docs.getport.io/search-and-query/ for more information about the search capabilities in Port.
+ Example Usage
+ Search for all entities in a specific blueprint:
+ ```hcl
+ data "portsearch" "allservice" {
+ query = jsonencode({
+ "combinator" : "and", "rules" : [
+ { "operator" : "=", "property" : "$blueprint", "value" : "Service" },
+ ]
+ })
+ }
+ ```
+ Search for entity with specific identifier in a specific blueprint to create another resource based on the values of the entity:
+ ```hcl
+ data "portsearch" "adsservice" {
+ query = jsonencode({
+ "combinator" : "and", "rules" : [
+ { "operator" : "=", "property" : "$blueprint", "value" : "Service" },
+ { "operator" : "=", "property" : "$identifier", "value" : "Ads" },
+ ]
+ })
+ }
+ ```
+ Scorecards automation example
+ In this example we are creating a jira task for each service that its Ownership Scorecard hasn't reached Gold level :
+ ```hcl
+ data "portsearch" "allservices" {
+ query = jsonencode({
+ "combinator" : "and", "rules" : [
+ { "operator" : "=", "property" : "$blueprint", "value" : "microservice" },
+ ]
+ })
+ }
+ locals {
+ // Count the number of services that are not owned by a team with a Gold level
+ microserviceownershipwithoutgoldlevel = length([
+ for entity in data.portsearch.allservices.entities : entity.scorecards["ownership"].level
+ if entity.scorecards["ownership"].level != "Gold"
+ ])
+ }
+ // create jira issue per service that is not owned by a team with a Gold level
+ resource "jiraissue" "microserviceownershipwithoutgoldlevel" {
+ count = local.microserviceownershipwithoutgoldlevel
+ issuetype = "Task"
+ project_key = "PORT"
+ summary = "Service ${data.portsearch.backendservices.entities[count.index].title} hasn't reached Gold level in Ownership Scorecard"
+ description = "Service https://app.getport.io/${port_blueprint.microservice.identifier}Entity/${data.port_search.backend_services.entities[count.index].identifier} is not owned by a team with a Gold level, please assign a team with a Gold level to the service"
+ }
+ ```
+---
+
+# port_search (Data Source)
+
+# Search Data Source
+
+The search data source allows you to search for entities in Port.
+
+See the [Port documentation](https://docs.getport.io/search-and-query/) for more information about the search capabilities in Port.
+
+## Example Usage
+
+### Search for all entities in a specific blueprint:
+
+```hcl
+
+data "port_search" "all_service" {
+ query = jsonencode({
+ "combinator" : "and", "rules" : [
+ { "operator" : "=", "property" : "$blueprint", "value" : "Service" },
+ ]
+ })
+}
+
+
+```
+
+### Search for entity with specific identifier in a specific blueprint to create another resource based on the values of the entity:
+
+
+```hcl
+
+data "port_search" "ads_service" {
+ query = jsonencode({
+ "combinator" : "and", "rules" : [
+ { "operator" : "=", "property" : "$blueprint", "value" : "Service" },
+ { "operator" : "=", "property" : "$identifier", "value" : "Ads" },
+ ]
+ })
+}
+
+
+```
+
+### Scorecards automation example
+In this example we are creating a jira task for each service that its Ownership Scorecard hasn't reached Gold level :
+
+```hcl
+
+data "port_search" "all_services" {
+ query = jsonencode({
+ "combinator" : "and", "rules" : [
+ { "operator" : "=", "property" : "$blueprint", "value" : "microservice" },
+ ]
+ })
+}
+
+locals {
+ // Count the number of services that are not owned by a team with a Gold level
+ microservice_ownership_without_gold_level = length([
+ for entity in data.port_search.all_services.entities : entity.scorecards["ownership"].level
+ if entity.scorecards["ownership"].level != "Gold"
+ ])
+}
+
+// create jira issue per service that is not owned by a team with a Gold level
+resource "jira_issue" "microservice_ownership_without_gold_level" {
+ count = local.microservice_ownership_without_gold_level
+ issue_type = "Task"
+
+ project_key = "PORT"
+
+ summary = "Service ${data.port_search.backend_services.entities[count.index].title} hasn't reached Gold level in Ownership Scorecard"
+ description = "[Service](https://app.getport.io/${port_blueprint.microservice.identifier}Entity/${data.port_search.backend_services.entities[count.index].identifier}) is not owned by a team with a Gold level, please assign a team with a Gold level to the service"
+}
+
+
+```
+
+
+
+
+## Schema
+
+### Required
+
+- `query` (String) The search query
+
+### Optional
+
+- `attach_title_to_relation` (Boolean) Attach title to relation
+- `exclude` (List of String) Properties to exclude from the results
+- `exclude_calculated_properties` (Boolean) Exclude calculated properties
+- `include` (List of String) Properties to include in the results
+
+### Read-Only
+
+- `entities` (Attributes List) A list of entities matching the search query (see [below for nested schema](#nestedatt--entities))
+- `id` (String) The ID of this resource.
+- `matching_blueprints` (List of String) The matching blueprints for the search query
+
+
+### Nested Schema for `entities`
+
+Optional:
+
+- `icon` (String) The icon of the entity
+- `properties` (Attributes) The properties of the entity (see [below for nested schema](#nestedatt--entities--properties))
+- `relations` (Attributes) The relations of the entity (see [below for nested schema](#nestedatt--entities--relations))
+- `run_id` (String) The runID of the action run that created the entity
+- `scorecards` (Map of Object) The scorecards of the entity (see [below for nested schema](#nestedatt--entities--scorecards))
+- `teams` (List of String) The teams the entity belongs to
+- `title` (String) The title of the entity
+
+Read-Only:
+
+- `blueprint` (String) The blueprint identifier the entity relates to
+- `created_at` (String) The creation date of the entity
+- `created_by` (String) The creator of the entity
+- `identifier` (String) The identifier of the entity
+- `updated_at` (String) The last update date of the entity
+- `updated_by` (String) The last updater of the entity
+
+
+### Nested Schema for `entities.properties`
+
+Optional:
+
+- `array_props` (Attributes) The array properties of the entity (see [below for nested schema](#nestedatt--entities--properties--array_props))
+- `boolean_props` (Map of Boolean) The bool properties of the entity
+- `number_props` (Map of Number) The number properties of the entity
+- `object_props` (Map of String) The object properties of the entity
+- `string_props` (Map of String) The string properties of the entity
+
+
+### Nested Schema for `entities.properties.array_props`
+
+Optional:
+
+- `boolean_items` (Map of List of Boolean)
+- `number_items` (Map of List of Number)
+- `object_items` (Map of List of String)
+- `string_items` (Map of List of String)
+
+
+
+
+### Nested Schema for `entities.relations`
+
+Optional:
+
+- `many_relations` (Map of List of String) The many relation of the entity
+- `single_relations` (Map of String) The single relation of the entity
+
+
+
+### Nested Schema for `entities.scorecards`
+
+Read-Only:
+
+- `level` (String)
+- `rules` (List of Object) (see [below for nested schema](#nestedobjatt--entities--scorecards--rules))
+
+
+### Nested Schema for `entities.scorecards.rules`
+
+Read-Only:
+
+- `identifier` (String)
+- `level` (String)
+- `status` (String)
diff --git a/internal/cli/models.go b/internal/cli/models.go
index 2d223410..f5301b43 100644
--- a/internal/cli/models.go
+++ b/internal/cli/models.go
@@ -19,14 +19,27 @@ type (
ExpiresIn int64 `json:"expiresIn"`
TokenType string `json:"tokenType"`
}
+
+ ScorecardRulesModel struct {
+ Identifier string `tfsdk:"identifier"`
+ Status string `tfsdk:"status"`
+ Level string `tfsdk:"level"`
+ }
+
+ ScorecardModel struct {
+ Rules []ScorecardRulesModel `tfsdk:"rules"`
+ Level string `tfsdk:"level"`
+ }
+
Entity struct {
Meta
- Identifier string `json:"identifier,omitempty"`
- Title string `json:"title"`
- Blueprint string `json:"blueprint"`
- Team []string `json:"team,omitempty"`
- Properties map[string]any `json:"properties"`
- Relations map[string]any `json:"relations"`
+ Identifier string `json:"identifier,omitempty"`
+ Title string `json:"title"`
+ Blueprint string `json:"blueprint"`
+ Team []string `json:"team,omitempty"`
+ Properties map[string]any `json:"properties"`
+ Relations map[string]any `json:"relations"`
+ Scorecards map[string]ScorecardModel `json:"scorecards,omitempty"`
// TODO: add the rest of the fields.
}
@@ -385,6 +398,14 @@ type (
FailureCount int `json:"failureCount,omitempty"`
SuccessCount int `json:"successCount,omitempty"`
}
+
+ SearchRequestQuery struct {
+ Query *map[string]any `json:"query"`
+ ExcludeCalculatedProperties *bool `json:"exclude_calculated_properties,omitempty"`
+ Include []string `json:"include,omitempty"`
+ Exclude []string `json:"exclude,omitempty"`
+ AttachTitleToRelation *bool `json:"attach_title_to_relation,omitempty"`
+ }
)
type PortBody struct {
@@ -402,6 +423,22 @@ type PortBody struct {
Migration Migration `json:"migration"`
}
+type SearchEntityResult struct {
+ Meta
+ Identifier string `json:"identifier,omitempty"`
+ Title string `json:"title,omitempty"`
+ Icon *string `json:"icon,omitempty"`
+ Team []string `json:"team,omitempty"`
+ Properties map[string]any `json:"properties,omitempty"`
+ Relations map[string]any `json:"relations,omitempty"`
+}
+
+type SearchResult struct {
+ OK bool `json:"ok"`
+ MatchingBlueprints []string `json:"matchingBlueprints"`
+ Entities []Entity `json:"entities"`
+}
+
type PortPagePermissionsBody struct {
OK bool `json:"ok"`
PagePermissions PagePermissions `json:"permissions"`
diff --git a/internal/cli/search.go b/internal/cli/search.go
new file mode 100644
index 00000000..47c8292f
--- /dev/null
+++ b/internal/cli/search.go
@@ -0,0 +1,48 @@
+package cli
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "strings"
+)
+
+func (c *PortClient) Search(ctx context.Context, searchRequest *SearchRequestQuery) (*SearchResult, error) {
+ url := "v1/entities/search"
+
+ req := c.Client.R().
+ SetContext(ctx).
+ SetBody(*searchRequest.Query).
+ SetHeader("Accept", "application/json")
+
+ if searchRequest.ExcludeCalculatedProperties != nil {
+ req.SetQueryParam("exclude_calculated_properties", fmt.Sprintf("%v", &searchRequest.ExcludeCalculatedProperties))
+ }
+
+ if searchRequest.Include != nil && len(searchRequest.Include) > 0 {
+ req.SetQueryParam("include", strings.Join(searchRequest.Include, ","))
+ }
+
+ if searchRequest.Exclude != nil && len(searchRequest.Exclude) > 0 {
+ req.SetQueryParam("exclude", strings.Join(searchRequest.Exclude, ","))
+ }
+
+ if searchRequest.AttachTitleToRelation != nil {
+ req.SetQueryParam("attach_title_to_relation", fmt.Sprintf("%v", &searchRequest.AttachTitleToRelation))
+ }
+
+ resp, err := req.Post(url)
+
+ if err != nil {
+ return nil, err
+ }
+ var searchResult SearchResult
+ err = json.Unmarshal(resp.Body(), &searchResult)
+ if err != nil {
+ return nil, err
+ }
+ if !searchResult.OK {
+ return nil, fmt.Errorf("failed to search, got: %s", resp.Body())
+ }
+ return &searchResult, nil
+}
diff --git a/port/integration/schema.go b/port/integration/schema.go
index f83e56cb..663ec365 100644
--- a/port/integration/schema.go
+++ b/port/integration/schema.go
@@ -51,10 +51,6 @@ func IntegrationSchema() map[string]schema.Attribute {
}
}
-func StaticString(s string) {
- panic("unimplemented")
-}
-
func (r *IntegrationResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
MarkdownDescription: IntegrationResourceMarkdownDescription,
diff --git a/port/search/dataSource.go b/port/search/dataSource.go
new file mode 100644
index 00000000..8804aefa
--- /dev/null
+++ b/port/search/dataSource.go
@@ -0,0 +1,77 @@
+package search
+
+import (
+ "context"
+ "github.com/hashicorp/terraform-plugin-framework/datasource"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ "github.com/port-labs/terraform-provider-port-labs/v2/internal/cli"
+)
+
+var _ datasource.DataSource = &SearchDataSource{}
+
+func NewSearchDataSource() datasource.DataSource {
+ return &SearchDataSource{}
+}
+
+type SearchDataSource struct {
+ portClient *cli.PortClient
+}
+
+func (d *SearchDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
+ if req.ProviderData == nil {
+ return
+ }
+
+ d.portClient = req.ProviderData.(*cli.PortClient)
+}
+
+func (d *SearchDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
+ resp.TypeName = req.ProviderTypeName + "_search"
+}
+
+func (d *SearchDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
+ var data SearchDataModel
+ resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
+
+ searchRequest, err := searchResourceToPortBody(&data)
+ if err != nil {
+ resp.Diagnostics.AddError("failed to convert search data to port body", err.Error())
+ return
+ }
+
+ searchResult, err := d.portClient.Search(ctx, searchRequest)
+ if err != nil {
+ resp.Diagnostics.AddError("failed to search", err.Error())
+ return
+ }
+
+ data.ID = types.StringValue(data.GenerateID())
+ data.MatchingBlueprints = goStringListToTFList(searchResult.MatchingBlueprints)
+
+ blueprints := make(map[string]cli.Blueprint)
+ for _, blueprint := range searchResult.MatchingBlueprints {
+ b, _, err := d.portClient.ReadBlueprint(ctx, blueprint)
+ if err != nil {
+ resp.Diagnostics.AddError("failed to read blueprint", err.Error())
+ return
+ }
+ blueprints[blueprint] = *b
+ }
+
+ for _, entity := range searchResult.Entities {
+ matchingEntityBlueprint := blueprints[entity.Blueprint]
+ e := refreshEntityState(ctx, &entity, &matchingEntityBlueprint)
+ data.Entities = append(data.Entities, *e)
+ }
+
+ resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+}
+
+func goStringListToTFList(list []string) []types.String {
+ var result = make([]types.String, len(list))
+ for i, u := range list {
+ result[i] = types.StringValue(u)
+ }
+
+ return result
+}
diff --git a/port/search/dataSourceSchema.go b/port/search/dataSourceSchema.go
new file mode 100644
index 00000000..d3700305
--- /dev/null
+++ b/port/search/dataSourceSchema.go
@@ -0,0 +1,278 @@
+package search
+
+import (
+ "context"
+ "github.com/hashicorp/terraform-plugin-framework/attr"
+ "github.com/hashicorp/terraform-plugin-framework/datasource"
+ "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+)
+
+func EntitySchema() map[string]schema.Attribute {
+ return map[string]schema.Attribute{
+ "identifier": schema.StringAttribute{
+ MarkdownDescription: "The identifier of the entity",
+ Computed: true,
+ },
+ "title": schema.StringAttribute{
+ MarkdownDescription: "The title of the entity",
+ Computed: true,
+ Optional: true,
+ },
+ "icon": schema.StringAttribute{
+ MarkdownDescription: "The icon of the entity",
+ Computed: true,
+ Optional: true,
+ },
+ "run_id": schema.StringAttribute{
+ MarkdownDescription: "The runID of the action run that created the entity",
+ Computed: true,
+ Optional: true,
+ },
+ "teams": schema.ListAttribute{
+ MarkdownDescription: "The teams the entity belongs to",
+ Computed: true,
+ Optional: true,
+ ElementType: types.StringType,
+ },
+ "blueprint": schema.StringAttribute{
+ MarkdownDescription: "The blueprint identifier the entity relates to",
+ Computed: true,
+ },
+ "properties": schema.SingleNestedAttribute{
+ MarkdownDescription: "The properties of the entity",
+ Computed: true,
+ Optional: true,
+ Attributes: map[string]schema.Attribute{
+ "string_props": schema.MapAttribute{
+ MarkdownDescription: "The string properties of the entity",
+ Computed: true,
+ Optional: true,
+ ElementType: types.StringType,
+ },
+ "number_props": schema.MapAttribute{
+ MarkdownDescription: "The number properties of the entity",
+ Computed: true,
+ Optional: true,
+ ElementType: types.Float64Type,
+ },
+ "boolean_props": schema.MapAttribute{
+ MarkdownDescription: "The bool properties of the entity",
+ Computed: true,
+ Optional: true,
+ ElementType: types.BoolType,
+ },
+ "object_props": schema.MapAttribute{
+ MarkdownDescription: "The object properties of the entity",
+ Computed: true,
+ Optional: true,
+ ElementType: types.StringType,
+ },
+ "array_props": schema.SingleNestedAttribute{
+ MarkdownDescription: "The array properties of the entity",
+ Computed: true,
+ Optional: true,
+ Attributes: map[string]schema.Attribute{
+ "string_items": schema.MapAttribute{
+ ElementType: types.ListType{ElemType: types.StringType},
+ Computed: true,
+ Optional: true,
+ },
+ "number_items": schema.MapAttribute{
+ ElementType: types.ListType{ElemType: types.Float64Type},
+ Computed: true,
+ Optional: true,
+ },
+ "boolean_items": schema.MapAttribute{
+ ElementType: types.ListType{ElemType: types.BoolType},
+ Computed: true,
+ Optional: true,
+ },
+ "object_items": schema.MapAttribute{
+ ElementType: types.ListType{ElemType: types.StringType},
+ Computed: true,
+ Optional: true,
+ },
+ },
+ },
+ },
+ },
+ "scorecards": schema.MapAttribute{
+ MarkdownDescription: "The scorecards of the entity",
+ Computed: true,
+ Optional: true,
+ ElementType: types.ObjectType{
+ AttrTypes: map[string]attr.Type{
+ "rules": types.ListType{
+ ElemType: types.ObjectType{
+ AttrTypes: map[string]attr.Type{
+ "identifier": types.StringType,
+ "status": types.StringType,
+ "level": types.StringType,
+ },
+ },
+ },
+ "level": types.StringType,
+ },
+ },
+ },
+ "relations": schema.SingleNestedAttribute{
+ MarkdownDescription: "The relations of the entity",
+ Computed: true,
+ Optional: true,
+ Attributes: map[string]schema.Attribute{
+ "single_relations": schema.MapAttribute{
+ MarkdownDescription: "The single relation of the entity",
+ Computed: true,
+ Optional: true,
+ ElementType: types.StringType,
+ },
+ "many_relations": schema.MapAttribute{
+ MarkdownDescription: "The many relation of the entity",
+ Computed: true,
+ Optional: true,
+ ElementType: types.ListType{ElemType: types.StringType},
+ },
+ },
+ },
+ "created_at": schema.StringAttribute{
+ MarkdownDescription: "The creation date of the entity",
+ Computed: true,
+ },
+ "created_by": schema.StringAttribute{
+ MarkdownDescription: "The creator of the entity",
+ Computed: true,
+ },
+ "updated_at": schema.StringAttribute{
+ MarkdownDescription: "The last update date of the entity",
+ Computed: true,
+ },
+ "updated_by": schema.StringAttribute{
+ MarkdownDescription: "The last updater of the entity",
+ Computed: true,
+ },
+ }
+}
+
+func Schema() map[string]schema.Attribute {
+ return map[string]schema.Attribute{
+ "id": schema.StringAttribute{
+ Computed: true,
+ },
+ "query": schema.StringAttribute{
+ MarkdownDescription: "The search query",
+ Required: true,
+ },
+ "exclude_calculated_properties": schema.BoolAttribute{
+ MarkdownDescription: "Exclude calculated properties",
+ Optional: true,
+ },
+ "include": schema.ListAttribute{
+ MarkdownDescription: "Properties to include in the results",
+ Optional: true,
+ ElementType: types.StringType,
+ },
+ "exclude": schema.ListAttribute{
+ MarkdownDescription: "Properties to exclude from the results",
+ Optional: true,
+ ElementType: types.StringType,
+ },
+ "attach_title_to_relation": schema.BoolAttribute{
+ MarkdownDescription: "Attach title to relation",
+ Optional: true,
+ },
+ "matching_blueprints": schema.ListAttribute{
+ MarkdownDescription: "The matching blueprints for the search query",
+ Computed: true,
+ ElementType: types.StringType,
+ },
+ "entities": schema.ListNestedAttribute{
+ MarkdownDescription: "A list of entities matching the search query",
+ Computed: true,
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: EntitySchema(),
+ },
+ },
+ }
+}
+
+func (d *SearchDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
+ resp.Schema = schema.Schema{
+ MarkdownDescription: SearchDataSourceMarkdownDescription,
+ Attributes: Schema(),
+ }
+}
+
+var SearchDataSourceMarkdownDescription = `
+
+# Search Data Source
+
+The search data source allows you to search for entities in Port.
+
+See the [Port documentation](https://docs.getport.io/search-and-query/) for more information about the search capabilities in Port.
+
+## Example Usage
+
+### Search for all entities in a specific blueprint:
+
+` + "```hcl" + `
+
+data "port_search" "all_service" {
+ query = jsonencode({
+ "combinator" : "and", "rules" : [
+ { "operator" : "=", "property" : "$blueprint", "value" : "Service" },
+ ]
+ })
+}
+
+` + "\n```" + `
+
+### Search for entity with specific identifier in a specific blueprint to create another resource based on the values of the entity:
+
+
+` + "```hcl" + `
+
+data "port_search" "ads_service" {
+ query = jsonencode({
+ "combinator" : "and", "rules" : [
+ { "operator" : "=", "property" : "$blueprint", "value" : "Service" },
+ { "operator" : "=", "property" : "$identifier", "value" : "Ads" },
+ ]
+ })
+}
+
+` + "\n```" + `
+
+### Scorecards automation example
+In this example we are creating a jira task for each service that its Ownership Scorecard hasn't reached Gold level :
+
+` + "```hcl" + `
+
+data "port_search" "all_services" {
+ query = jsonencode({
+ "combinator" : "and", "rules" : [
+ { "operator" : "=", "property" : "$blueprint", "value" : "microservice" },
+ ]
+ })
+}
+
+locals {
+ // Count the number of services that are not owned by a team with a Gold level
+ microservice_ownership_without_gold_level = length([
+ for entity in data.port_search.all_services.entities : entity.scorecards["ownership"].level
+ if entity.scorecards["ownership"].level != "Gold"
+ ])
+}
+
+// create jira issue per service that is not owned by a team with a Gold level
+resource "jira_issue" "microservice_ownership_without_gold_level" {
+ count = local.microservice_ownership_without_gold_level
+ issue_type = "Task"
+
+ project_key = "PORT"
+
+ summary = "Service ${data.port_search.backend_services.entities[count.index].title} hasn't reached Gold level in Ownership Scorecard"
+ description = "[Service](https://app.getport.io/${port_blueprint.microservice.identifier}Entity/${data.port_search.backend_services.entities[count.index].identifier}) is not owned by a team with a Gold level, please assign a team with a Gold level to the service"
+}
+
+` + "\n```" + ``
diff --git a/port/search/data_test.go b/port/search/data_test.go
new file mode 100644
index 00000000..d98ce40d
--- /dev/null
+++ b/port/search/data_test.go
@@ -0,0 +1,737 @@
+package search_test
+
+import (
+ "fmt"
+ "github.com/port-labs/terraform-provider-port-labs/v2/internal/utils"
+ "testing"
+
+ "github.com/hashicorp/terraform-plugin-testing/helper/resource"
+ "github.com/port-labs/terraform-provider-port-labs/v2/internal/acctest"
+)
+
+func TestAccPortEntity(t *testing.T) {
+ identifier := utils.GenID()
+ var testAccActionConfigCreate = fmt.Sprintf(`
+ resource "port_blueprint" "microservice" {
+ title = "TF Provider Test BP0"
+ icon = "Terraform"
+ identifier = "%s"
+ properties = {
+ "string_props" = {
+ "myStringIdentifier" = {
+ "title" = "My String Identifier"
+ }
+ }
+ "number_props" = {
+ "myNumberIdentifier" = {
+ "title" = "My Number Identifier"
+ }
+ }
+ "boolean_props" = {
+ "myBooleanIdentifier" = {
+ "title" = "My Boolean Identifier"
+ }
+ }
+ "object_props" = {
+ "myObjectIdentifier" = {
+ "title" = "My Object Identifier"
+ }
+ }
+ "array_props" = {
+ "myStringArrayIdentifier" = {
+ "title" = "My String Array Identifier"
+ "string_items" = {}
+ }
+ "myNumberArrayIdentifier" = {
+ "title" = "My Number Array Identifier"
+ "number_items" = {}
+ }
+ "myBooleanArrayIdentifier" = {
+ "title" = "My Boolean Array Identifier"
+ "boolean_items" = {}
+ }
+ "myObjectArrayIdentifier" = {
+ "title" = "My Object Array Identifier"
+ "object_items" = {}
+ }
+ }
+ }
+ }
+ resource "port_entity" "microservice" {
+ title = "TF Provider Test Entity0"
+ blueprint = port_blueprint.microservice.identifier
+ properties = {
+ "string_props" = {
+ "myStringIdentifier" = "My String Value"
+ }
+ "number_props" = {
+ "myNumberIdentifier" = 123.456
+ }
+ "boolean_props" = {
+ "myBooleanIdentifier" = true
+ }
+ "object_props" = {
+ "myObjectIdentifier" = jsonencode({"foo": "bar"})
+ }
+ "array_props" = {
+ string_items = {
+ "myStringArrayIdentifier" = ["My Array Value", "My Array Value2"]
+ }
+ number_items = {
+ "myNumberArrayIdentifier" = [123, 456]
+ }
+ boolean_items = {
+ "myBooleanArrayIdentifier" = [true, false]
+ }
+ object_items = {
+ "myObjectArrayIdentifier" = [jsonencode({"foo": "bar"}), jsonencode({"foo": "bar2"})]
+ }
+ }
+ }
+ }
+ `, identifier)
+
+ var testSearchActionQuery = fmt.Sprintf(`
+ data "port_search" "microservice" {
+ query = jsonencode({
+ "combinator" : "and", "rules" : [
+ { "operator" : "=", "property" : "$blueprint", "value" : "%s" },
+ { "operator" : "=", "property" : "$identifier", "value" : port_entity.microservice.identifier }
+ ]
+ })
+}`, identifier)
+
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { acctest.TestAccPreCheck(t) },
+ ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories,
+
+ Steps: []resource.TestStep{
+ {
+ Config: acctest.ProviderConfig + testAccActionConfigCreate,
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr("port_entity.microservice", "title", "TF Provider Test Entity0"),
+ resource.TestCheckResourceAttr("port_entity.microservice", "blueprint", identifier),
+ resource.TestCheckResourceAttr("port_entity.microservice", "properties.string_props.myStringIdentifier", "My String Value"),
+ resource.TestCheckResourceAttr("port_entity.microservice", "properties.number_props.myNumberIdentifier", "123.456"),
+ resource.TestCheckResourceAttr("port_entity.microservice", "properties.boolean_props.myBooleanIdentifier", "true"),
+ resource.TestCheckResourceAttr("port_entity.microservice", "properties.object_props.myObjectIdentifier", "{\"foo\":\"bar\"}"),
+ resource.TestCheckResourceAttr("port_entity.microservice", "properties.array_props.string_items.myStringArrayIdentifier.0", "My Array Value"),
+ resource.TestCheckResourceAttr("port_entity.microservice", "properties.array_props.string_items.myStringArrayIdentifier.1", "My Array Value2"),
+ resource.TestCheckResourceAttr("port_entity.microservice", "properties.array_props.number_items.myNumberArrayIdentifier.0", "123"),
+ resource.TestCheckResourceAttr("port_entity.microservice", "properties.array_props.number_items.myNumberArrayIdentifier.1", "456"),
+ resource.TestCheckResourceAttr("port_entity.microservice", "properties.array_props.boolean_items.myBooleanArrayIdentifier.0", "true"),
+ resource.TestCheckResourceAttr("port_entity.microservice", "properties.array_props.boolean_items.myBooleanArrayIdentifier.1", "false"),
+ resource.TestCheckResourceAttr("port_entity.microservice", "properties.array_props.object_items.myObjectArrayIdentifier.0", "{\"foo\":\"bar\"}"),
+ resource.TestCheckResourceAttr("port_entity.microservice", "properties.array_props.object_items.myObjectArrayIdentifier.1", "{\"foo\":\"bar2\"}"),
+ ),
+ },
+ {
+ Config: acctest.ProviderConfig + testAccActionConfigCreate + testSearchActionQuery,
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr("data.port_search.microservice", "entities.0.title", "TF Provider Test Entity0"),
+ resource.TestCheckResourceAttr("data.port_search.microservice", "entities.0.blueprint", identifier),
+ resource.TestCheckResourceAttr("data.port_search.microservice", "entities.0.properties.string_props.myStringIdentifier", "My String Value"),
+ resource.TestCheckResourceAttr("data.port_search.microservice", "entities.0.properties.number_props.myNumberIdentifier", "123.456"),
+ resource.TestCheckResourceAttr("data.port_search.microservice", "entities.0.properties.boolean_props.myBooleanIdentifier", "true"),
+ resource.TestCheckResourceAttr("data.port_search.microservice", "entities.0.properties.object_props.myObjectIdentifier", "{\"foo\":\"bar\"}"),
+ resource.TestCheckResourceAttr("data.port_search.microservice", "entities.0.properties.array_props.string_items.myStringArrayIdentifier.0", "My Array Value"),
+ resource.TestCheckResourceAttr("data.port_search.microservice", "entities.0.properties.array_props.string_items.myStringArrayIdentifier.1", "My Array Value2"),
+ resource.TestCheckResourceAttr("data.port_search.microservice", "entities.0.properties.array_props.number_items.myNumberArrayIdentifier.0", "123"),
+ resource.TestCheckResourceAttr("data.port_search.microservice", "entities.0.properties.array_props.number_items.myNumberArrayIdentifier.1", "456"),
+ resource.TestCheckResourceAttr("data.port_search.microservice", "entities.0.properties.array_props.boolean_items.myBooleanArrayIdentifier.0", "true"),
+ resource.TestCheckResourceAttr("data.port_search.microservice", "entities.0.properties.array_props.boolean_items.myBooleanArrayIdentifier.1", "false"),
+ resource.TestCheckResourceAttr("data.port_search.microservice", "entities.0.properties.array_props.object_items.myObjectArrayIdentifier.0", "{\"foo\":\"bar\"}"),
+ resource.TestCheckResourceAttr("data.port_search.microservice", "entities.0.properties.array_props.object_items.myObjectArrayIdentifier.1", "{\"foo\":\"bar2\"}"),
+ ),
+ },
+ },
+ })
+}
+
+func TestAccPortEntityWithNulls(t *testing.T) {
+ identifier := utils.GenID()
+ var testAccActionConfigCreate = fmt.Sprintf(`
+ resource "port_blueprint" "microservice" {
+ title = "TF Provider Test BP0"
+ icon = "Terraform"
+ identifier = "%s"
+ properties = {
+ "string_props" = {
+ "myStringIdentifier" = {
+ "title" = "My String Identifier"
+ }
+ }
+ "number_props" = {
+ "myNumberIdentifier" = {
+ "title" = "My Number Identifier"
+ }
+ }
+ "boolean_props" = {
+ "myBooleanIdentifier" = {
+ "title" = "My Boolean Identifier"
+ }
+ }
+ "object_props" = {
+ "myObjectIdentifier" = {
+ "title" = "My Object Identifier"
+ }
+ }
+ "array_props" = {
+ "myStringArrayIdentifier" = {
+ "title" = "My String Array Identifier"
+ "string_items" = {}
+ }
+ "myNumberArrayIdentifier" = {
+ "title" = "My Number Array Identifier"
+ "number_items" = {}
+ }
+ "myBooleanArrayIdentifier" = {
+ "title" = "My Boolean Array Identifier"
+ "boolean_items" = {}
+ }
+ "myObjectArrayIdentifier" = {
+ "title" = "My Object Array Identifier"
+ "object_items" = {}
+ }
+ }
+ }
+ }
+ resource "port_entity" "microservice" {
+ title = "TF Provider Test Entity0"
+ blueprint = port_blueprint.microservice.identifier
+ properties = {
+ "string_props" = {
+ "myStringIdentifier" = null
+ }
+ "number_props" = {
+ "myNumberIdentifier" = null
+ }
+ "boolean_props" = {
+ "myBooleanIdentifier" = null
+ }
+ "object_props" = {
+ "myObjectIdentifier" = null
+ }
+ "array_props" = {
+ string_items = {
+ "myStringArrayIdentifier" = null
+ }
+ number_items = {
+ "myNumberArrayIdentifier" = null
+ }
+ boolean_items = {
+ "myBooleanArrayIdentifier" = null
+ }
+ object_items = {
+ "myObjectArrayIdentifier" = null
+ }
+ }
+ }
+ }
+ `, identifier)
+
+ var testSearchActionQuery = fmt.Sprintf(`
+ data "port_search" "microservice" {
+ query = jsonencode({
+ "combinator" : "and", "rules" : [
+ { "operator" : "=", "property" : "$blueprint", "value" : "%s" },
+ { "operator" : "=", "property" : "$identifier", "value" : port_entity.microservice.identifier }
+ ]
+ })
+ }`, identifier)
+
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { acctest.TestAccPreCheck(t) },
+ ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories,
+
+ Steps: []resource.TestStep{
+ {
+ Config: acctest.ProviderConfig + testAccActionConfigCreate,
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr("port_entity.microservice", "title", "TF Provider Test Entity0"),
+ resource.TestCheckResourceAttr("port_entity.microservice", "blueprint", identifier),
+ resource.TestCheckNoResourceAttr("port_entity.microservice", "properties.string_props.myStringIdentifier"),
+ resource.TestCheckNoResourceAttr("port_entity.microservice", "properties.number_props.myNumberIdentifier"),
+ resource.TestCheckNoResourceAttr("port_entity.microservice", "properties.boolean_props.myBooleanIdentifier"),
+ resource.TestCheckNoResourceAttr("port_entity.microservice", "properties.object_props.myObjectIdentifier"),
+ resource.TestCheckResourceAttr("port_entity.microservice", "properties.array_props.string_items.myStringArrayIdentifier.#", "0"),
+ resource.TestCheckResourceAttr("port_entity.microservice", "properties.array_props.number_items.myNumberArrayIdentifier.#", "0"),
+ resource.TestCheckResourceAttr("port_entity.microservice", "properties.array_props.boolean_items.myBooleanArrayIdentifier.#", "0"),
+ resource.TestCheckResourceAttr("port_entity.microservice", "properties.array_props.object_items.myObjectArrayIdentifier.#", "0"),
+ ),
+ },
+ {
+ Config: acctest.ProviderConfig + testAccActionConfigCreate + testSearchActionQuery,
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr("data.port_search.microservice", "entities.0.title", "TF Provider Test Entity0"),
+ resource.TestCheckResourceAttr("data.port_search.microservice", "entities.0.blueprint", identifier),
+ resource.TestCheckNoResourceAttr("data.port_search.microservice", "entities.0.properties.string_props.myStringIdentifier"),
+ resource.TestCheckNoResourceAttr("data.port_search.microservice", "entities.0.properties.number_props.myNumberIdentifier"),
+ resource.TestCheckNoResourceAttr("data.port_search.microservice", "entities.0.properties.boolean_props.myBooleanIdentifier"),
+ resource.TestCheckNoResourceAttr("data.port_search.microservice", "entities.0.properties.object_props.myObjectIdentifier"),
+ resource.TestCheckResourceAttr("data.port_search.microservice", "entities.0.properties.array_props.string_items.myStringArrayIdentifier.#", "0"),
+ resource.TestCheckResourceAttr("data.port_search.microservice", "entities.0.properties.array_props.number_items.myNumberArrayIdentifier.#", "0"),
+ resource.TestCheckResourceAttr("data.port_search.microservice", "entities.0.properties.array_props.boolean_items.myBooleanArrayIdentifier.#", "0"),
+ resource.TestCheckResourceAttr("data.port_search.microservice", "entities.0.properties.array_props.object_items.myObjectArrayIdentifier.#", "0"),
+ ),
+ },
+ },
+ })
+}
+
+func TestAccPortEntityWithRelation(t *testing.T) {
+ identifier := utils.GenID()
+ identifier2 := utils.GenID()
+ var testAccActionConfigCreate = fmt.Sprintf(`
+ resource "port_blueprint" "microservice" {
+ title = "TF Provider Test BP0"
+ icon = "Terraform"
+ identifier = "%s"
+ properties = {
+ "string_props" = {
+ "myStringIdentifier" = {
+ "title" = "My String Identifier"
+ }
+ }
+ }
+ relations = {
+ "tfRelation" = {
+ "title" = "Test Relation"
+ "target" = port_blueprint.microservice2.identifier
+ }
+ }
+ }
+ resource "port_blueprint" "microservice2" {
+ title = "TF Provider Test BP1"
+ icon = "Terraform"
+ identifier = "%s"
+ properties = {
+ "string_props" = {
+ "myStringIdentifier2" = {
+ "title" = "My String Identifier2"
+ }
+ }
+ }
+ }
+
+ resource "port_entity" "microservice" {
+ title = "TF Provider Test Entity0"
+ blueprint = port_blueprint.microservice.identifier
+ properties = {
+ "string_props" = {
+ "myStringIdentifier" = "My String Value"
+ }
+ }
+ relations = {
+ single_relations = {
+ "tfRelation" = port_entity.microservice2.identifier
+ }
+ }
+ }
+
+ resource "port_entity" "microservice2" {
+ title = "TF Provider Test Entity1"
+ identifier = "tf-entity-2"
+ blueprint = port_blueprint.microservice2.identifier
+ properties = {
+ "string_props" = {
+ "myStringIdentifier2" = "My String Value2"
+ }
+ }
+ }
+ `, identifier, identifier2)
+
+ var testSearchActionQuery1 = fmt.Sprintf(`
+ data "port_search" "microservice" {
+ query = jsonencode({
+ "combinator" : "and", "rules" : [
+ { "operator" : "=", "property" : "$blueprint", "value" : "%s" },
+ { "operator" : "=", "property" : "$identifier", "value" : port_entity.microservice.identifier }
+ ]
+ })
+ }`, identifier)
+
+ var testSearchActionQuery2 = fmt.Sprintf(`
+ data "port_search" "microservice2" {
+ query = jsonencode({
+ "combinator" : "and", "rules" : [
+ { "operator" : "=", "property" : "$blueprint", "value" : "%s" },
+ { "operator" : "=", "property" : "$identifier", "value" : port_entity.microservice2.identifier }
+ ]
+ })
+ }`, identifier2)
+
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { acctest.TestAccPreCheck(t) },
+ ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories,
+
+ Steps: []resource.TestStep{
+ {
+ Config: acctest.ProviderConfig + testAccActionConfigCreate,
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr("port_entity.microservice", "title", "TF Provider Test Entity0"),
+ resource.TestCheckResourceAttr("port_entity.microservice", "blueprint", identifier),
+ resource.TestCheckResourceAttr("port_entity.microservice", "properties.string_props.myStringIdentifier", "My String Value"),
+ resource.TestCheckResourceAttr("port_entity.microservice", "relations.single_relations.tfRelation", "tf-entity-2"),
+ ),
+ },
+ {
+ Config: acctest.ProviderConfig + testAccActionConfigCreate + testSearchActionQuery1,
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr("data.port_search.microservice", "entities.0.title", "TF Provider Test Entity0"),
+ resource.TestCheckResourceAttr("data.port_search.microservice", "entities.0.blueprint", identifier),
+ resource.TestCheckResourceAttr("data.port_search.microservice", "entities.0.properties.string_props.myStringIdentifier", "My String Value"),
+ resource.TestCheckResourceAttr("data.port_search.microservice", "entities.0.relations.single_relations.tfRelation", "tf-entity-2"),
+ ),
+ },
+ {
+ Config: acctest.ProviderConfig + testAccActionConfigCreate + testSearchActionQuery2,
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr("data.port_search.microservice2", "entities.0.title", "TF Provider Test Entity1"),
+ resource.TestCheckResourceAttr("data.port_search.microservice2", "entities.0.blueprint", identifier2),
+ resource.TestCheckResourceAttr("data.port_search.microservice2", "entities.0.properties.string_props.myStringIdentifier2", "My String Value2"),
+ ),
+ },
+ },
+ })
+}
+
+func TestAccPortEntityWithManyRelation(t *testing.T) {
+ identifier1 := utils.GenID()
+ identifier2 := utils.GenID()
+ var testAccActionConfigCreate = fmt.Sprintf(`
+ resource "port_blueprint" "microservice" {
+ title = "TF Provider Test BP0"
+ icon = "Terraform"
+ identifier = "%s"
+ properties = {
+ "string_props" = {
+ "myStringIdentifier" = {
+ "title" = "My String Identifier"
+ }
+ }
+ }
+ relations = {
+ "tfRelation" = {
+ "title" = "Test Relation"
+ "target" = port_blueprint.microservice2.identifier
+ "many" = true
+ }
+ }
+ }
+ resource "port_blueprint" "microservice2" {
+ title = "TF Provider Test BP1"
+ icon = "Terraform"
+ identifier = "%s"
+ properties = {
+ "string_props" = {
+ "myStringIdentifier2" = {
+ "title" = "My String Identifier2"
+ }
+ }
+ }
+ }
+
+ resource "port_entity" "microservice" {
+ title = "TF Provider Test Entity0"
+ blueprint = port_blueprint.microservice.identifier
+ properties = {
+ "string_props" = {
+ "myStringIdentifier" = "My String Value"
+ }
+ }
+ relations = {
+ "many_relations" = {
+ "tfRelation" = [port_entity.microservice2.identifier, port_entity.microservice3.identifier]
+ }
+ }
+ }
+
+ resource "port_entity" "microservice2" {
+ title = "TF Provider Test Entity1"
+ identifier = "tf-entity-2"
+ blueprint = port_blueprint.microservice2.identifier
+ properties = {
+ "string_props" = {
+ "myStringIdentifier2" = "My String Value2"
+ }
+ }
+ }
+
+ resource "port_entity" "microservice3" {
+ depends_on = [port_entity.microservice2]
+ title = "TF Provider Test Entity2"
+ identifier = "tf-entity-3"
+ blueprint = port_blueprint.microservice2.identifier
+ properties = {
+ "string_props" = {
+ "myStringIdentifier2" = "My String Value3"
+ }
+ }
+ }
+ `, identifier1, identifier2)
+
+ var testSearchActionQuery1 = fmt.Sprintf(`
+ data "port_search" "microservice" {
+ query = jsonencode({
+ "combinator" : "and", "rules" : [
+ { "operator" : "=", "property" : "$blueprint", "value" : "%s" },
+ { "operator" : "=", "property" : "$identifier", "value" : port_entity.microservice.identifier }
+ ]
+ })
+ }`, identifier1)
+
+ var testSearchActionQuery2 = fmt.Sprintf(`
+ data "port_search" "microservice2" {
+ query = jsonencode({
+ "combinator" : "and", "rules" : [
+ { "operator" : "=", "property" : "$blueprint", "value" : "%s" },
+ { "operator" : "=", "property" : "$identifier", "value" : port_entity.microservice2.identifier }
+ ]
+ })
+ }`, identifier2)
+
+ var testSearchActionQuery3 = fmt.Sprintf(`
+ data "port_search" "microservice3" {
+ query = jsonencode({
+ "combinator" : "and", "rules" : [
+ { "operator" : "=", "property" : "$blueprint", "value" : "%s" },
+ { "operator" : "=", "property" : "$identifier", "value" : port_entity.microservice3.identifier }
+ ]
+ })
+ }`, identifier2)
+
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { acctest.TestAccPreCheck(t) },
+ ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories,
+
+ Steps: []resource.TestStep{
+ {
+ Config: acctest.ProviderConfig + testAccActionConfigCreate,
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr("port_entity.microservice", "title", "TF Provider Test Entity0"),
+ resource.TestCheckResourceAttr("port_entity.microservice", "blueprint", identifier1),
+ resource.TestCheckResourceAttr("port_entity.microservice", "properties.string_props.myStringIdentifier", "My String Value"),
+ resource.TestCheckResourceAttr("port_entity.microservice", "relations.many_relations.tfRelation.0", "tf-entity-2"),
+ resource.TestCheckResourceAttr("port_entity.microservice", "relations.many_relations.tfRelation.1", "tf-entity-3"),
+ ),
+ },
+ {
+ Config: acctest.ProviderConfig + testAccActionConfigCreate + testSearchActionQuery1,
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr("data.port_search.microservice", "entities.0.title", "TF Provider Test Entity0"),
+ resource.TestCheckResourceAttr("data.port_search.microservice", "entities.0.blueprint", identifier1),
+ resource.TestCheckResourceAttr("data.port_search.microservice", "entities.0.properties.string_props.myStringIdentifier", "My String Value"),
+ resource.TestCheckResourceAttr("data.port_search.microservice", "entities.0.relations.many_relations.tfRelation.0", "tf-entity-2"),
+ resource.TestCheckResourceAttr("data.port_search.microservice", "entities.0.relations.many_relations.tfRelation.1", "tf-entity-3"),
+ ),
+ },
+ {
+ Config: acctest.ProviderConfig + testAccActionConfigCreate + testSearchActionQuery2,
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr("data.port_search.microservice2", "entities.0.title", "TF Provider Test Entity1"),
+ resource.TestCheckResourceAttr("data.port_search.microservice2", "entities.0.blueprint", identifier2),
+ resource.TestCheckResourceAttr("data.port_search.microservice2", "entities.0.properties.string_props.myStringIdentifier2", "My String Value2"),
+ ),
+ },
+ {
+ Config: acctest.ProviderConfig + testAccActionConfigCreate + testSearchActionQuery3,
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr("data.port_search.microservice3", "entities.0.title", "TF Provider Test Entity2"),
+ resource.TestCheckResourceAttr("data.port_search.microservice3", "entities.0.blueprint", identifier2),
+ resource.TestCheckResourceAttr("data.port_search.microservice3", "entities.0.properties.string_props.myStringIdentifier2", "My String Value3"),
+ ),
+ },
+ },
+ })
+}
+
+func TestAccPortEntityWithEmptyRelation(t *testing.T) {
+ identifier := utils.GenID()
+ identifier2 := utils.GenID()
+ var testAccActionConfigCreate = fmt.Sprintf(`
+ resource "port_blueprint" "microservice" {
+ title = "TF Provider Test BP0"
+ icon = "Terraform"
+ identifier = "%s"
+ properties = {
+ "string_props" = {
+ "myStringIdentifier" = {
+ "title" = "My String Identifier"
+ }
+ }
+ }
+ relations = {
+ "tfRelation" = {
+ "title" = "Test Relation"
+ "target" = port_blueprint.microservice2.identifier
+ }
+ }
+ }
+ resource "port_blueprint" "microservice2" {
+ title = "TF Provider Test BP1"
+ icon = "Terraform"
+ identifier = "%s"
+ properties = {
+ "string_props" = {
+ "myStringIdentifier2" = {
+ "title" = "My String Identifier2"
+ }
+ }
+ }
+ }
+
+ resource "port_entity" "microservice" {
+ title = "TF Provider Test Entity0"
+ blueprint = port_blueprint.microservice.identifier
+ properties = {
+ "string_props" = {
+ "myStringIdentifier" = "My String Value"
+ }
+ }
+ relations = {
+ single_relations = {
+ "tfRelation" = null
+ }
+ }
+ }
+ `, identifier, identifier2)
+
+ var testSearchActionQuery = fmt.Sprintf(`
+ data "port_search" "microservice" {
+ query = jsonencode({
+ "combinator" : "and", "rules" : [
+ { "operator" : "=", "property" : "$blueprint", "value" : "%s" },
+ { "operator" : "=", "property" : "$identifier", "value" : port_entity.microservice.identifier }
+ ]
+ })
+ }`, identifier)
+
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { acctest.TestAccPreCheck(t) },
+ ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories,
+
+ Steps: []resource.TestStep{
+ {
+ Config: acctest.ProviderConfig + testAccActionConfigCreate,
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr("port_entity.microservice", "title", "TF Provider Test Entity0"),
+ resource.TestCheckResourceAttr("port_entity.microservice", "blueprint", identifier),
+ resource.TestCheckResourceAttr("port_entity.microservice", "properties.string_props.myStringIdentifier", "My String Value"),
+ resource.TestCheckNoResourceAttr("port_entity.microservice", "relations.single_relations.tfRelation"),
+ ),
+ },
+ {
+ Config: acctest.ProviderConfig + testAccActionConfigCreate + testSearchActionQuery,
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr("data.port_search.microservice", "entities.0.title", "TF Provider Test Entity0"),
+ resource.TestCheckResourceAttr("data.port_search.microservice", "entities.0.blueprint", identifier),
+ resource.TestCheckResourceAttr("data.port_search.microservice", "entities.0.properties.string_props.myStringIdentifier", "My String Value"),
+ resource.TestCheckNoResourceAttr("data.port_search.microservice", "entities.0.relations.single_relations.tfRelation"),
+ ),
+ },
+ },
+ })
+}
+
+func TestAccPortEntityUpdateProp(t *testing.T) {
+ identifier := utils.GenID()
+ entityIdentifier := utils.GenID()
+ var testAccActionConfigCreate = fmt.Sprintf(`
+ resource "port_blueprint" "microservice" {
+ title = "TF Provider Test BP0"
+ icon = "Terraform"
+ identifier = "%s"
+ properties = {
+ "string_props" = {
+ "myStringIdentifier" = {
+ "title" = "My String Identifier"
+ }
+ }
+ }
+ }
+ resource "port_entity" "microservice" {
+ title = "TF Provider Test Entity0"
+ blueprint = port_blueprint.microservice.identifier
+ identifier = "%s"
+ properties = {
+ "string_props" = {
+ "myStringIdentifier" = "My String Value"
+ }
+ }
+ }`, identifier, entityIdentifier)
+
+ var testSearchActionQuery = fmt.Sprintf(`
+ data "port_search" "microservice" {
+ query = jsonencode({
+ "combinator" : "and", "rules" : [
+ { "operator" : "=", "property" : "$blueprint", "value" : "%s" },
+ { "operator" : "=", "property" : "$identifier", "value" : port_entity.microservice.identifier }
+ ]
+ })
+ }`, identifier)
+
+ var testAccActionConfigUpdate = fmt.Sprintf(`
+ resource "port_blueprint" "microservice" {
+ title = "TF Provider Test BP0"
+ icon = "Terraform"
+ identifier = "%s"
+ properties = {
+ "string_props" = {
+ "myStringIdentifier" = {
+ "title" = "My String Identifier"
+ }
+ }
+ }
+ }
+ resource "port_entity" "microservice" {
+ title = "TF Provider Test Entity0"
+ blueprint = port_blueprint.microservice.identifier
+ identifier = "%s"
+ properties = {
+ "string_props" = {
+ "myStringIdentifier" = "My String Value2"
+ }
+ }
+ }`, identifier, entityIdentifier)
+
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { acctest.TestAccPreCheck(t) },
+ ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories,
+
+ Steps: []resource.TestStep{
+ {
+ Config: acctest.ProviderConfig + testAccActionConfigCreate,
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr("port_entity.microservice", "title", "TF Provider Test Entity0"),
+ resource.TestCheckResourceAttr("port_entity.microservice", "blueprint", identifier),
+ resource.TestCheckResourceAttr("port_entity.microservice", "properties.string_props.myStringIdentifier", "My String Value"),
+ ),
+ },
+ {
+ Config: acctest.ProviderConfig + testAccActionConfigCreate + testSearchActionQuery,
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr("data.port_search.microservice", "entities.0.title", "TF Provider Test Entity0"),
+ resource.TestCheckResourceAttr("data.port_search.microservice", "entities.0.blueprint", identifier),
+ resource.TestCheckResourceAttr("data.port_search.microservice", "entities.0.properties.string_props.myStringIdentifier", "My String Value"),
+ ),
+ },
+ {
+ Config: acctest.ProviderConfig + testAccActionConfigUpdate,
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr("port_entity.microservice", "title", "TF Provider Test Entity0"),
+ resource.TestCheckResourceAttr("port_entity.microservice", "blueprint", identifier),
+ resource.TestCheckResourceAttr("port_entity.microservice", "properties.string_props.myStringIdentifier", "My String Value2"),
+ ),
+ },
+ {
+ Config: acctest.ProviderConfig + testAccActionConfigUpdate + testSearchActionQuery,
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr("data.port_search.microservice", "entities.0.title", "TF Provider Test Entity0"),
+ resource.TestCheckResourceAttr("data.port_search.microservice", "entities.0.blueprint", identifier),
+ resource.TestCheckResourceAttr("data.port_search.microservice", "entities.0.properties.string_props.myStringIdentifier", "My String Value2"),
+ ),
+ },
+ },
+ })
+}
diff --git a/port/search/model.go b/port/search/model.go
new file mode 100644
index 00000000..f1a5a70e
--- /dev/null
+++ b/port/search/model.go
@@ -0,0 +1,89 @@
+package search
+
+import (
+ "crypto/sha256"
+ "encoding/hex"
+ "fmt"
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ "strings"
+)
+
+type ArrayPropsModel struct {
+ StringItems types.Map `tfsdk:"string_items"`
+ NumberItems types.Map `tfsdk:"number_items"`
+ BooleanItems types.Map `tfsdk:"boolean_items"`
+ ObjectItems types.Map `tfsdk:"object_items"`
+}
+
+type EntityPropertiesModel struct {
+ StringProps map[string]types.String `tfsdk:"string_props"`
+ NumberProps map[string]types.Float64 `tfsdk:"number_props"`
+ BooleanProps map[string]types.Bool `tfsdk:"boolean_props"`
+ ObjectProps map[string]types.String `tfsdk:"object_props"`
+ ArrayProps *ArrayPropsModel `tfsdk:"array_props"`
+}
+
+type RelationModel struct {
+ SingleRelation map[string]*string `tfsdk:"single_relations"`
+ ManyRelations map[string][]string `tfsdk:"many_relations"`
+}
+
+type ScorecardRulesModel struct {
+ Identifier types.String `tfsdk:"identifier"`
+ Status types.String `tfsdk:"status"`
+ Level types.String `tfsdk:"level"`
+}
+
+type ScorecardModel struct {
+ Rules []ScorecardRulesModel `tfsdk:"rules"`
+ Level types.String `tfsdk:"level"`
+}
+
+type EntityModel struct {
+ Identifier types.String `tfsdk:"identifier"`
+ Blueprint types.String `tfsdk:"blueprint"`
+ Title types.String `tfsdk:"title"`
+ Icon types.String `tfsdk:"icon"`
+ RunID types.String `tfsdk:"run_id"`
+ CreatedAt types.String `tfsdk:"created_at"`
+ CreatedBy types.String `tfsdk:"created_by"`
+ UpdatedAt types.String `tfsdk:"updated_at"`
+ UpdatedBy types.String `tfsdk:"updated_by"`
+ Properties *EntityPropertiesModel `tfsdk:"properties"`
+ Teams []types.String `tfsdk:"teams"`
+ Scorecards *map[string]ScorecardModel `tfsdk:"scorecards"`
+ Relations *RelationModel `tfsdk:"relations"`
+}
+
+type SearchDataModel struct {
+ ID types.String `tfsdk:"id"`
+ Query types.String `tfsdk:"query"`
+ ExcludeCalculatedProperties types.Bool `tfsdk:"exclude_calculated_properties"`
+ Include []types.String `tfsdk:"include"`
+ Exclude []types.String `tfsdk:"exclude"`
+ AttachTitleToRelation types.Bool `tfsdk:"attach_title_to_relation"`
+ MatchingBlueprints []types.String `tfsdk:"matching_blueprints"`
+ Entities []EntityModel `tfsdk:"entities"`
+}
+
+func (m *SearchDataModel) GenerateID() string {
+ // Concatenate the model fields into a single string
+ var sb strings.Builder
+ sb.WriteString(m.Query.ValueString())
+ sb.WriteString(fmt.Sprintf("%t", m.ExcludeCalculatedProperties.ValueBool()))
+ for _, include := range m.Include {
+ sb.WriteString(include.ValueString())
+ }
+ for _, exclude := range m.Exclude {
+ sb.WriteString(exclude.ValueString())
+ }
+ sb.WriteString(fmt.Sprintf("%t", m.AttachTitleToRelation.ValueBool()))
+
+ // Compute the SHA-256 hash of the concatenated string
+ hash := sha256.Sum256([]byte(sb.String()))
+
+ // Convert the hash to a hexadecimal string
+ hashString := hex.EncodeToString(hash[:])
+
+ return hashString
+}
diff --git a/port/search/searchResultToState.go b/port/search/searchResultToState.go
new file mode 100644
index 00000000..404f6c2d
--- /dev/null
+++ b/port/search/searchResultToState.go
@@ -0,0 +1,231 @@
+package search
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ "github.com/port-labs/terraform-provider-port-labs/v2/internal/cli"
+)
+
+func refreshArrayEntityState(ctx context.Context, state *EntityModel, arrayProperties map[string][]interface{}, blueprint *cli.Blueprint) {
+ mapStringItems := make(map[string][]*string)
+ mapNumberItems := make(map[string][]*float64)
+ mapBooleanItems := make(map[string][]*bool)
+ mapObjectItems := make(map[string][]*string)
+
+ if state.Properties.ArrayProps == nil {
+ state.Properties.ArrayProps = &ArrayPropsModel{
+ StringItems: types.MapNull(types.ListType{ElemType: types.StringType}),
+ NumberItems: types.MapNull(types.ListType{ElemType: types.Float64Type}),
+ BooleanItems: types.MapNull(types.ListType{ElemType: types.BoolType}),
+ ObjectItems: types.MapNull(types.ListType{ElemType: types.StringType}),
+ }
+ }
+ for k, t := range arrayProperties {
+
+ switch blueprint.Schema.Properties[k].Items["type"] {
+ case "string":
+ if t != nil {
+ for _, item := range t {
+ stringItem := item.(string)
+ mapStringItems[k] = append(mapStringItems[k], &stringItem)
+ }
+ if len(t) == 0 {
+ mapStringItems[k] = []*string{}
+ }
+ } else {
+ mapStringItems[k] = nil
+ }
+ state.Properties.ArrayProps.StringItems, _ = types.MapValueFrom(ctx, types.ListType{ElemType: types.StringType}, mapStringItems)
+ case "number":
+ if t != nil {
+ for _, item := range t {
+ floatItem := item.(float64)
+ mapNumberItems[k] = append(mapNumberItems[k], &floatItem)
+ }
+ if len(t) == 0 {
+ mapNumberItems[k] = []*float64{}
+ }
+ } else {
+ mapNumberItems[k] = nil
+ }
+ state.Properties.ArrayProps.NumberItems, _ = types.MapValueFrom(ctx, types.ListType{ElemType: types.Float64Type}, mapNumberItems)
+
+ case "boolean":
+ if t != nil {
+ for _, item := range t {
+ boolItem := item.(bool)
+ mapBooleanItems[k] = append(mapBooleanItems[k], &boolItem)
+ }
+ if len(t) == 0 {
+ mapBooleanItems[k] = []*bool{}
+ }
+ } else {
+ mapBooleanItems[k] = nil
+ }
+ state.Properties.ArrayProps.BooleanItems, _ = types.MapValueFrom(ctx, types.ListType{ElemType: types.BoolType}, mapBooleanItems)
+
+ case "object":
+ if t != nil {
+ for _, item := range t {
+ js, _ := json.Marshal(&item)
+ stringJs := string(js)
+ mapObjectItems[k] = append(mapObjectItems[k], &stringJs)
+ }
+ if len(t) == 0 {
+ mapObjectItems[k] = []*string{}
+ }
+ } else {
+ mapObjectItems[k] = nil
+ }
+ state.Properties.ArrayProps.ObjectItems, _ = types.MapValueFrom(ctx, types.ListType{ElemType: types.StringType}, mapObjectItems)
+
+ }
+ }
+}
+
+func refreshPropertiesEntityState(ctx context.Context, state *EntityModel, e *cli.Entity, blueprint *cli.Blueprint) {
+ state.Properties = &EntityPropertiesModel{}
+ arrayProperties := make(map[string][]interface{})
+ for k, v := range e.Properties {
+ switch t := v.(type) {
+ case float64:
+ if state.Properties.NumberProps == nil {
+ state.Properties.NumberProps = make(map[string]types.Float64)
+ }
+ state.Properties.NumberProps[k] = types.Float64Value(t)
+ case string:
+ if state.Properties.StringProps == nil {
+ state.Properties.StringProps = make(map[string]types.String)
+ }
+ state.Properties.StringProps[k] = types.StringValue(t)
+ case bool:
+ if state.Properties.BooleanProps == nil {
+ state.Properties.BooleanProps = make(map[string]types.Bool)
+ }
+ state.Properties.BooleanProps[k] = types.BoolValue(t)
+ case []interface{}:
+ arrayProperties[k] = t
+ case interface{}:
+ if state.Properties.ObjectProps == nil {
+ state.Properties.ObjectProps = make(map[string]types.String)
+ }
+ js, _ := json.Marshal(&t)
+ state.Properties.ObjectProps[k] = types.StringValue(string(js))
+ case nil:
+ switch blueprint.Schema.Properties[k].Type {
+ case "string":
+ if state.Properties.StringProps == nil {
+ state.Properties.StringProps = make(map[string]types.String)
+ }
+ state.Properties.StringProps[k] = types.StringNull()
+ case "number":
+ if state.Properties.NumberProps == nil {
+ state.Properties.NumberProps = make(map[string]types.Float64)
+ }
+ state.Properties.NumberProps[k] = types.Float64Null()
+ case "boolean":
+ if state.Properties.BooleanProps == nil {
+ state.Properties.BooleanProps = make(map[string]types.Bool)
+ }
+ state.Properties.BooleanProps[k] = types.BoolNull()
+ case "object":
+ if state.Properties.ObjectProps == nil {
+ state.Properties.ObjectProps = make(map[string]types.String)
+ }
+ state.Properties.ObjectProps[k] = types.StringNull()
+ case "array":
+ arrayProperties[k] = []interface{}(nil)
+ }
+ }
+ }
+ if len(arrayProperties) != 0 {
+ refreshArrayEntityState(ctx, state, arrayProperties, blueprint)
+ }
+}
+
+func refreshRelationsEntityState(state *EntityModel, e *cli.Entity) {
+ relations := &RelationModel{
+ SingleRelation: make(map[string]*string),
+ ManyRelations: make(map[string][]string),
+ }
+
+ for identifier, r := range e.Relations {
+ switch v := r.(type) {
+ case []interface{}:
+ if len(v) != 0 {
+ switch v[0].(type) {
+ case string:
+ relations.ManyRelations[identifier] = make([]string, len(v))
+ for i, s := range v {
+ relations.ManyRelations[identifier][i] = s.(string)
+ }
+ }
+ }
+
+ case interface{}:
+ if v != nil {
+ value := fmt.Sprintf("%v", v)
+ relations.SingleRelation[identifier] = &value
+ }
+ }
+ }
+
+ state.Relations = relations
+}
+
+func refreshScorecardsEntityState(state *EntityModel, e *cli.Entity) {
+ if len(e.Scorecards) != 0 {
+ state.Scorecards = &map[string]ScorecardModel{}
+ *state.Scorecards = make(map[string]ScorecardModel)
+
+ for k, v := range e.Scorecards {
+ rules := make([]ScorecardRulesModel, len(v.Rules))
+ for i, r := range v.Rules {
+ rules[i] = ScorecardRulesModel{
+ Identifier: types.StringValue(r.Identifier),
+ Status: types.StringValue(r.Status),
+ Level: types.StringValue(r.Level),
+ }
+ }
+ (*state.Scorecards)[k] = ScorecardModel{
+ Rules: rules,
+ Level: types.StringValue(v.Level),
+ }
+ }
+ }
+}
+
+func refreshEntityState(ctx context.Context, e *cli.Entity, b *cli.Blueprint) *EntityModel {
+ state := &EntityModel{}
+ state.Identifier = types.StringValue(e.Identifier)
+ state.Blueprint = types.StringValue(e.Blueprint)
+ state.Title = types.StringValue(e.Title)
+ state.CreatedAt = types.StringValue(e.CreatedAt.String())
+ state.CreatedBy = types.StringValue(e.CreatedBy)
+ state.UpdatedAt = types.StringValue(e.UpdatedAt.String())
+ state.UpdatedBy = types.StringValue(e.UpdatedBy)
+
+ if len(e.Team) != 0 {
+ state.Teams = make([]types.String, len(e.Team))
+ for i, t := range e.Team {
+ state.Teams[i] = types.StringValue(t)
+ }
+ }
+
+ if len(e.Properties) != 0 {
+ refreshPropertiesEntityState(ctx, state, e, b)
+ }
+
+ if len(e.Relations) != 0 {
+ refreshRelationsEntityState(state, e)
+ }
+
+ if len(e.Scorecards) != 0 {
+ refreshScorecardsEntityState(state, e)
+ }
+
+ return state
+}
diff --git a/port/search/searchToPortBody.go b/port/search/searchToPortBody.go
new file mode 100644
index 00000000..323695d7
--- /dev/null
+++ b/port/search/searchToPortBody.go
@@ -0,0 +1,22 @@
+package search
+
+import (
+ "github.com/port-labs/terraform-provider-port-labs/v2/internal/cli"
+ "github.com/port-labs/terraform-provider-port-labs/v2/internal/flex"
+ "github.com/port-labs/terraform-provider-port-labs/v2/internal/utils"
+)
+
+func searchResourceToPortBody(state *SearchDataModel) (*cli.SearchRequestQuery, error) {
+ query, err := utils.TerraformJsonStringToGoObject(state.Query.ValueStringPointer())
+ if err != nil {
+ return nil, err
+ }
+
+ return &cli.SearchRequestQuery{
+ Query: query,
+ ExcludeCalculatedProperties: state.ExcludeCalculatedProperties.ValueBoolPointer(),
+ Include: flex.TerraformStringListToGoArray(state.Include),
+ Exclude: flex.TerraformStringListToGoArray(state.Exclude),
+ AttachTitleToRelation: state.AttachTitleToRelation.ValueBoolPointer(),
+ }, nil
+}
diff --git a/provider/provider.go b/provider/provider.go
index 3d60e258..11c63b03 100644
--- a/provider/provider.go
+++ b/provider/provider.go
@@ -18,6 +18,7 @@ import (
"github.com/port-labs/terraform-provider-port-labs/v2/port/page"
"github.com/port-labs/terraform-provider-port-labs/v2/port/page-permissions"
"github.com/port-labs/terraform-provider-port-labs/v2/port/scorecard"
+ "github.com/port-labs/terraform-provider-port-labs/v2/port/search"
"github.com/port-labs/terraform-provider-port-labs/v2/port/team"
"github.com/port-labs/terraform-provider-port-labs/v2/port/webhook"
"github.com/port-labs/terraform-provider-port-labs/v2/version"
@@ -124,6 +125,7 @@ func (p *PortLabsProvider) Configure(ctx context.Context, req provider.Configure
}
resp.ResourceData = c
+ resp.DataSourceData = c
}
@@ -145,5 +147,7 @@ func (p *PortLabsProvider) Resources(ctx context.Context) []func() resource.Reso
}
func (p *PortLabsProvider) DataSources(ctx context.Context) []func() datasource.DataSource {
- return []func() datasource.DataSource{}
+ return []func() datasource.DataSource{
+ search.NewSearchDataSource,
+ }
}