Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Search data source #153

Merged
merged 9 commits into from
Jun 6, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 147 additions & 0 deletions docs/data-sources/port_search.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
---
# 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.
Tankilevitch marked this conversation as resolved.
Show resolved Hide resolved
Example Usage
Search for all entities in a specific blueprint:
```hcl
data "portsearch" "allservice" {
Tankilevitch marked this conversation as resolved.
Show resolved Hide resolved
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" "allservice" {
Tankilevitch marked this conversation as resolved.
Show resolved Hide resolved
query = jsonencode({
"combinator" : "and", "rules" : [
{ "operator" : "=", "property" : "$blueprint", "value" : "Service" },
{ "operator" : "=", "property" : "$identifier", "value" : "Ads" },
]
})
}
```
---

# 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" },
]
})
}


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that an example of the usage could be cool here, E.g:

locals {
    has_services = length(data.port_search.all_services.Entities) > 0
}
my_other_module "identifier" {
   count = locals.has_services
   ...
}

```

### 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" "all_service" {
query = jsonencode({
"combinator" : "and", "rules" : [
{ "operator" : "=", "property" : "$blueprint", "value" : "Service" },
{ "operator" : "=", "property" : "$identifier", "value" : "Ads" },
]
})
}


```



<!-- schema generated by tfplugindocs -->
## 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

<a id="nestedatt--entities"></a>
### 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
- `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

<a id="nestedatt--entities--properties"></a>
### 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

<a id="nestedatt--entities--properties--array_props"></a>
### 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)



<a id="nestedatt--entities--relations"></a>
### 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
24 changes: 24 additions & 0 deletions internal/cli/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,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 {
Expand All @@ -401,6 +409,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"`
Expand Down
48 changes: 48 additions & 0 deletions internal/cli/search.go
Original file line number Diff line number Diff line change
@@ -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
}
77 changes: 77 additions & 0 deletions port/search/dataSource.go
Original file line number Diff line number Diff line change
@@ -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
}
Loading
Loading