diff --git a/GNUmakefile b/GNUmakefile index 54aff45..3be568d 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -24,7 +24,7 @@ fmt: ## Runs go formatter gofmt -s -w -e . .PHONY: dev/enter -dev/enter: write-terraform-rc ## Updates the terraformrc to point to the DEV_BIN_PATH. Installs the provider to the DEV_BIN_PATH +dev/enter: write-terraform-rc cleanup-examples ## Updates the terraformrc to point to the DEV_BIN_PATH. Installs the provider to the DEV_BIN_PATH mkdir -vp $(PLUGINS_DIR) go build -o $(PLUGINS_DIR)/$(DEV_BIN_PATH) @@ -40,6 +40,12 @@ write-terraform-rc: ## Write to terraformrc file to mirror mondoohq/mondoo to DE remove-terraform-rc: ## Remove the terraformrc file @rm -vf "$(HOME)/.terraformrc" +.PHONY: cleanup-examples +cleanup-examples: ## A quick way to clean up any left over Terraform files inside the examples/ folder + find . -name ".terraform*" -type f -exec rm -rf {} \; + find . -name "terraform.tfstate*" -type f -exec rm -rf {} \; + find . -name ".terraform.lock.hcl" -type f -exec rm -rf {} \; + help: ## Show this help @grep -E '^([a-zA-Z_/-]+):.*## ' $(MAKEFILE_LIST) | awk -F ':.*## ' '{printf "%-20s %s\n", $$1, $$2}' diff --git a/docs/data-sources/space.md b/docs/data-sources/space.md index 21fff70..681d60f 100644 --- a/docs/data-sources/space.md +++ b/docs/data-sources/space.md @@ -55,8 +55,8 @@ output "space_id" { ### Optional - `id` (String) Space ID -- `mrn` (String) Space MRN ### Read-Only +- `mrn` (String) Space MRN - `name` (String) Space name diff --git a/docs/index.md b/docs/index.md index 8a5e987..351f366 100644 --- a/docs/index.md +++ b/docs/index.md @@ -15,13 +15,14 @@ terraform { required_providers { mondoo = { source = "mondoohq/mondoo" - version = ">= 0.4.0" + version = ">= 0.19" } } } provider "mondoo" { - region = "us" # use "eu" for the European region + space = "hungry-poet-1988" + region = "us" } ``` @@ -47,5 +48,5 @@ You can alternatively use the `credentials` field, or any of the following envir - `credentials` (String) The contents of a service account key file in JSON format. - `endpoint` (String) The endpoint url of the server to manage resources -- `region` (String) The default region to manage resources in. +- `region` (String) The default region to manage resources in. Valid regions are `us` or `eu`. - `space` (String) The default space to manage resources in. diff --git a/docs/resources/custom_framework.md b/docs/resources/custom_framework.md index 888c546..0247194 100644 --- a/docs/resources/custom_framework.md +++ b/docs/resources/custom_framework.md @@ -14,13 +14,7 @@ Set custom Compliance Frameworks for a Mondoo Space. ```terraform provider "mondoo" { - region = "us" -} - -variable "mondoo_org" { - description = "The Mondoo Organization ID" - type = string - default = "my-org-1234567" + space = "hungry-poet-123456" } variable "my_custom_framework" { @@ -29,14 +23,7 @@ variable "my_custom_framework" { default = "framework.mql.yaml" } -# Create a new space -resource "mondoo_space" "my_space" { - name = "Custom Framework Space" - org_id = var.mondoo_org -} - resource "mondoo_custom_framework" "custom_framework" { - space_id = mondoo_space.my_space.id data_url = var.my_custom_framework } ``` @@ -47,7 +34,10 @@ resource "mondoo_custom_framework" "custom_framework" { ### Required - `data_url` (String) URL to the custom Compliance Framework data. -- `space_id` (String) Mondoo Space Identifier. + +### Optional + +- `space_id` (String) Mondoo Space Identifier. If it is not provided, the provider space is used. ### Read-Only diff --git a/docs/resources/custom_policy.md b/docs/resources/custom_policy.md index 832fadc..eb1bc5d 100644 --- a/docs/resources/custom_policy.md +++ b/docs/resources/custom_policy.md @@ -13,53 +13,40 @@ Custom Policy resource ## Example Usage ```terraform -provider "mondoo" {} - -resource "mondoo_space" "my_space" { - name = "My Custom Space" - org_id = "your-org-1234567" -} - variable "my_custom_policy" { description = "Path to the custom policy file. The file must be in MQL format." type = string default = "policy.mql.yaml" } +provider "mondoo" { + space = "hungry-poet-123456" +} + resource "mondoo_custom_policy" "my_policy" { - space_id = mondoo_space.my_space.id source = var.my_custom_policy overwrite = true } resource "mondoo_policy_assignment" "space" { - space_id = mondoo_space.my_space.id - policies = concat( mondoo_custom_policy.my_policy.mrns, [], ) state = "enabled" - - depends_on = [ - mondoo_space.my_space - ] } ``` ## Schema -### Required - -- `space_id` (String) Mondoo Space Identifier. - ### Optional - `content` (String, Sensitive) Data as string to be uploaded. Must be defined if source is not. Note: The content field is marked as sensitive. To view the raw contents of the object, please define an output. - `overwrite` (Boolean) If set to true, existing policies are overwritten. - `source` (String) A path to the data you want to upload. Must be defined if content is not. +- `space_id` (String) Mondoo Space Identifier. If it is not provided, the provider space is used. ### Read-Only diff --git a/docs/resources/custom_querypack.md b/docs/resources/custom_querypack.md index 6b90b51..46f961b 100644 --- a/docs/resources/custom_querypack.md +++ b/docs/resources/custom_querypack.md @@ -13,59 +13,39 @@ Custom Query Pack resource ## Example Usage ```terraform -provider "mondoo" { - region = "us" -} - -variable "mondoo_org" { - description = "Mondoo Organization" - type = string -} - -resource "mondoo_space" "my_space" { - name = "My Space Name" - org_id = var.mondoo_org -} - variable "my_custom_querypack" { description = "Path to custom querypack file. File must be in MQL format." type = string default = "querypack.mql.yaml" } +provider "mondoo" { + space = "hungry-poet-123456" +} + resource "mondoo_custom_querypack" "my_query_pack" { - space_id = mondoo_space.my_space.id - source = var.my_custom_querypack + source = var.my_custom_querypack } resource "mondoo_querypack_assignment" "space" { - space_id = mondoo_space.my_space.id - querypacks = concat( mondoo_custom_querypack.my_query_pack.mrns, [], ) state = "enabled" - - depends_on = [ - mondoo_space.my_space - ] } ``` ## Schema -### Required - -- `space_id` (String) Mondoo Space Identifier. - ### Optional - `content` (String, Sensitive) Data as string to be uploaded. Must be defined if source is not. Note: The content field is marked as sensitive. To view the raw contents of the object, please define an output. - `overwrite` (Boolean) If set to true, existing policies are overwritten. - `source` (String) A path to the data you want to upload. Must be defined if content is not. +- `space_id` (String) Mondoo Space Identifier. If it is not provided, the provider space is used. ### Read-Only diff --git a/docs/resources/framework_assignment.md b/docs/resources/framework_assignment.md index 0299f81..2c85bd0 100644 --- a/docs/resources/framework_assignment.md +++ b/docs/resources/framework_assignment.md @@ -14,25 +14,14 @@ Set Compliance Frameworks for a Mondoo Space. ```terraform provider "mondoo" { - region = "us" -} - -variable "mondoo_org" { - description = "The Mondoo Organization ID" - type = string - default = "my-org-1234567" -} - -# Create a new space -resource "mondoo_space" "my_space" { - name = "Framework Space" - org_id = var.mondoo_org + space = "hungry-poet-123456" } resource "mondoo_framework_assignment" "framework_assignment" { - space_id = mondoo_space.my_space.id - framework_mrn = ["//policy.api.mondoo.app/frameworks/cis-controls-8", - "//policy.api.mondoo.app/frameworks/iso-27001-2022"] + framework_mrn = [ + "//policy.api.mondoo.app/frameworks/cis-controls-8", + "//policy.api.mondoo.app/frameworks/iso-27001-2022" + ] enabled = true } ``` @@ -44,4 +33,7 @@ resource "mondoo_framework_assignment" "framework_assignment" { - `enabled` (Boolean) Enable or disable the Compliance Framework. - `framework_mrn` (List of String) Compliance Framework MRN. -- `space_id` (String) Mondoo Space Identifier. + +### Optional + +- `space_id` (String) Mondoo Space Identifier. If it is not provided, the provider space is used. diff --git a/docs/resources/integration_aws.md b/docs/resources/integration_aws.md index a610e1b..767cdbb 100644 --- a/docs/resources/integration_aws.md +++ b/docs/resources/integration_aws.md @@ -13,11 +13,6 @@ Continuously scan Google AWS organization and accounts for misconfigurations and ## Example Usage ```terraform -variable "mondoo_org" { - description = "Mondoo Organization" - type = string -} - variable "aws_access_key" { description = "AWS access key" type = string @@ -30,18 +25,13 @@ variable "aws_secret_key" { sensitive = true } -provider "mondoo" {} - -# Create a new space -resource "mondoo_space" "my_space" { - name = "AWS Terraform" - org_id = var.mondoo_org +provider "mondoo" { + space = "hungry-poet-123456" } # Setup the AWS integration resource "mondoo_integration_aws" "name" { - space_id = mondoo_space.my_space.id - name = "AWS Integration" + name = "AWS Integration" credentials = { key = { @@ -59,7 +49,10 @@ resource "mondoo_integration_aws" "name" { - `credentials` (Attributes) (see [below for nested schema](#nestedatt--credentials)) - `name` (String) Name of the integration. -- `space_id` (String) Mondoo Space Identifier. + +### Optional + +- `space_id` (String) Mondoo Space Identifier. If it is not provided, the provider space is used. ### Read-Only diff --git a/docs/resources/integration_aws_serverless.md b/docs/resources/integration_aws_serverless.md index 352380f..7f57e29 100644 --- a/docs/resources/integration_aws_serverless.md +++ b/docs/resources/integration_aws_serverless.md @@ -13,11 +13,6 @@ Continuously scan AWS organization and accounts for misconfigurations and vulner ## Example Usage ```terraform -variable "mondoo_org" { - description = "Mondoo Organization" - type = string -} - variable "origin_aws_account" { description = "Origin AWS Account" type = string @@ -42,7 +37,7 @@ variable "aws_account_id" { } provider "mondoo" { - region = "us" + space = "hungry-poet-123456" } provider "aws" { @@ -51,15 +46,8 @@ provider "aws" { data "aws_region" "current" {} -# Create a new space -resource "mondoo_space" "my_space" { - name = "AWS Terraform" - org_id = var.mondoo_org -} - # Setup the AWS integration resource "mondoo_integration_aws_serverless" "aws_serverless" { - space_id = mondoo_space.my_space.id name = "AWS Integration" region = data.aws_region.current.name is_organization = false @@ -96,7 +84,7 @@ resource "aws_cloudformation_stack" "mondoo_stack" { } } -# for organisation wide deploys use aws_cloudformation_stack_set and aws_cloudformation_stack_set_instance instead of aws_cloudformation_stack +# for organization wide deployments use aws_cloudformation_stack_set and aws_cloudformation_stack_set_instance instead of aws_cloudformation_stack # https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudformation_stack_set # https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudformation_stack_set_instance ``` @@ -109,7 +97,6 @@ resource "aws_cloudformation_stack" "mondoo_stack" { - `name` (String) Name of the integration. - `region` (String) AWS region. - `scan_configuration` (Attributes) (see [below for nested schema](#nestedatt--scan_configuration)) -- `space_id` (String) Mondoo Space Identifier. ### Optional @@ -117,6 +104,7 @@ resource "aws_cloudformation_stack" "mondoo_stack" { - `console_sign_in_trigger` (Boolean) Enable console sign in trigger. - `instance_state_change_trigger` (Boolean) Enable instance state change trigger. - `is_organization` (Boolean) Is organization. +- `space_id` (String) Mondoo Space Identifier. If it is not provided, the provider space is used. ### Read-Only diff --git a/docs/resources/integration_azure.md b/docs/resources/integration_azure.md index f4ab7a4..10a732d 100644 --- a/docs/resources/integration_azure.md +++ b/docs/resources/integration_azure.md @@ -22,12 +22,6 @@ variable "tenant_id" { default = "ffffffff-ffff-ffff-ffff-ffffffffffff" } -variable "mondoo_org" { - description = "The Mondoo Organization ID" - type = string - default = "your-org-1234567" -} - variable "primary_subscription" { description = "The primary Azure Subscription ID" type = string @@ -255,18 +249,11 @@ resource "azurerm_role_assignment" "reader" { # ---------------------------------------------- provider "mondoo" { - region = "us" -} - -# Create a new space -resource "mondoo_space" "azure_space" { - name = "Azure Terraform Integration" - org_id = var.mondoo_org + space = "hungry-poet-123456" } # Setup the Azure integration resource "mondoo_integration_azure" "azure_integration" { - space_id = mondoo_space.azure_space.id name = "Azure ${local.mondoo_security_integration_name}" tenant_id = var.tenant_id client_id = azuread_application.mondoo_security.client_id @@ -278,7 +265,6 @@ resource "mondoo_integration_azure" "azure_integration" { } # wait for the permissions to provisioned depends_on = [ - mondoo_space.azure_space, azuread_application.mondoo_security, azuread_service_principal.mondoo_security, azurerm_role_assignment.mondoo_security, @@ -295,12 +281,12 @@ resource "mondoo_integration_azure" "azure_integration" { - `client_id` (String) Azure Client ID. - `credentials` (Attributes) (see [below for nested schema](#nestedatt--credentials)) - `name` (String) Name of the integration. -- `space_id` (String) Mondoo Space Identifier. - `tenant_id` (String) Azure Tenant ID. ### Optional - `scan_vms` (Boolean) Scan VMs. +- `space_id` (String) Mondoo Space Identifier. If it is not provided, the provider space is used. - `subscription_allow_list` (List of String) List of Azure subscriptions to scan. - `subscription_deny_list` (List of String) List of Azure subscriptions to exclude from scanning. diff --git a/docs/resources/integration_domain.md b/docs/resources/integration_domain.md index 7bd83e4..4cf2365 100644 --- a/docs/resources/integration_domain.md +++ b/docs/resources/integration_domain.md @@ -13,27 +13,15 @@ Continuously scan endpoints to evaluate domain TLS, SSL, HTTP, and HTTPS securit ## Example Usage ```terraform -variable "mondoo_org" { - description = "The Mondoo Organization ID" - type = string -} - provider "mondoo" { - region = "us" -} - -# Create a new space -resource "mondoo_space" "domain_space" { - name = "My Space Name" - org_id = var.mondoo_org + space = "hungry-poet-123456" } # Setup the Domain integration resource "mondoo_integration_domain" "domain_integration" { - space_id = mondoo_space.domain_space.id - host = "mondoo.com" - https = true - http = false + host = "mondoo.com" + https = true + http = false } ``` @@ -43,12 +31,12 @@ resource "mondoo_integration_domain" "domain_integration" { ### Required - `host` (String) Domain name or IP address. -- `space_id` (String) Mondoo Space Identifier. ### Optional - `http` (Boolean) Enable HTTP port. - `https` (Boolean) Enable HTTPS port. +- `space_id` (String) Mondoo Space Identifier. If it is not provided, the provider space is used. ### Read-Only diff --git a/docs/resources/integration_gcp.md b/docs/resources/integration_gcp.md index d38600c..ab608e6 100644 --- a/docs/resources/integration_gcp.md +++ b/docs/resources/integration_gcp.md @@ -21,11 +21,6 @@ variable "gcp_project" { type = string } -variable "mondoo_org" { - description = "Mondoo Organization" - type = string -} - # Create GCP Service Account # ---------------------------------------------- @@ -60,18 +55,11 @@ output "google_service_account_key" { # ---------------------------------------------- provider "mondoo" { - region = "us" -} - -# Create a new space -resource "mondoo_space" "my_space" { - name = "GCP ${data.google_project.project.name}" - org_id = var.mondoo_org + space = "hungry-poet-123456" } # Setup the GCP integration resource "mondoo_integration_gcp" "name" { - space_id = mondoo_space.my_space.id name = "GCP ${data.google_project.project.name}" project_id = data.google_project.project.project_id credentials = { @@ -87,11 +75,11 @@ resource "mondoo_integration_gcp" "name" { - `credentials` (Attributes) (see [below for nested schema](#nestedatt--credentials)) - `name` (String) Name of the integration. -- `space_id` (String) Mondoo Space Identifier. ### Optional - `project_id` (String) GCP project id +- `space_id` (String) Mondoo Space Identifier. If it is not provided, the provider space is used. ### Read-Only diff --git a/docs/resources/integration_github.md b/docs/resources/integration_github.md index fa13814..3678ccb 100644 --- a/docs/resources/integration_github.md +++ b/docs/resources/integration_github.md @@ -13,11 +13,6 @@ Continuously scan GitHub organizations and repositories for misconfigurations. ## Example Usage ```terraform -variable "mondoo_org" { - description = "The Mondoo Organization ID" - type = string -} - variable "github_token" { description = "The GitHub Token" type = string @@ -25,20 +20,12 @@ variable "github_token" { } provider "mondoo" { - region = "us" -} - -# Create a new space -resource "mondoo_space" "gh_space" { - name = "My GitHub Space Name" - org_id = var.mondoo_org + space = "hungry-poet-123456" } # Setup the GitHub integration resource "mondoo_integration_github" "gh_integration" { - space_id = mondoo_space.gh_space.id - name = "GitHub Integration" - + name = "GitHub Integration" owner = "lunalectric" # define a repository if you want to restrict scan to a single repository @@ -62,13 +49,13 @@ resource "mondoo_integration_github" "gh_integration" { - `credentials` (Attributes) (see [below for nested schema](#nestedatt--credentials)) - `name` (String) Name of the integration. - `owner` (String) GitHub Owner. -- `space_id` (String) Mondoo Space Identifier. ### Optional - `repository` (String) GitHub Repository. - `repository_allow_list` (List of String) List of GitHub repositories to scan. - `repository_deny_list` (List of String) List of GitHub repositories to exclude from scanning. +- `space_id` (String) Mondoo Space Identifier. If it is not provided, the provider space is used. ### Read-Only diff --git a/docs/resources/integration_ms365.md b/docs/resources/integration_ms365.md index 4f063b9..eea962d 100644 --- a/docs/resources/integration_ms365.md +++ b/docs/resources/integration_ms365.md @@ -22,12 +22,6 @@ variable "tenant_id" { default = "ffffffff-ffff-ffff-ffff-ffffffffffff" } -variable "mondoo_org" { - description = "The Mondoo Organization ID" - type = string - default = "your-org-1234567" -} - locals { mondoo_security_integration_name = "Mondoo Security Integration" } @@ -169,18 +163,11 @@ resource "azuread_directory_role_assignment" "global_reader" { # ---------------------------------------------- provider "mondoo" { - region = "us" -} - -# Create a new space -resource "mondoo_space" "ms365_space" { - name = "Ms365 Terraform Integration" - org_id = var.mondoo_org + space = "hungry-poet-123456" } # Setup the Azure integration resource "mondoo_integration_ms365" "ms365_integration" { - space_id = mondoo_space.ms365_space.id name = "Ms365 ${local.mondoo_security_integration_name}" tenant_id = var.tenant_id client_id = azuread_application.mondoo_security.client_id @@ -189,7 +176,6 @@ resource "mondoo_integration_ms365" "ms365_integration" { } # wait for the permissions to provisioned depends_on = [ - mondoo_space.ms365_space, azuread_application.mondoo_security, azuread_service_principal.mondoo_security, azuread_directory_role_assignment.global_reader, @@ -205,9 +191,12 @@ resource "mondoo_integration_ms365" "ms365_integration" { - `client_id` (String) Azure Client ID. - `credentials` (Attributes) (see [below for nested schema](#nestedatt--credentials)) - `name` (String) Name of the integration. -- `space_id` (String) Mondoo Space Identifier. - `tenant_id` (String) Azure Tenant ID. +### Optional + +- `space_id` (String) Mondoo Space Identifier. If it is not provided, the provider space is used. + ### Read-Only - `mrn` (String) Integration identifier diff --git a/docs/resources/integration_oci_tenant.md b/docs/resources/integration_oci_tenant.md index 0041bbe..5a28589 100644 --- a/docs/resources/integration_oci_tenant.md +++ b/docs/resources/integration_oci_tenant.md @@ -13,32 +13,13 @@ Example resource ## Example Usage ```terraform -# Variables -# ---------------------------------------------- - -variable "mondoo_org" { - description = "The Mondoo Organization ID" - type = string - default = "your-org-1234567" -} - -# Configure the Mondoo -# ---------------------------------------------- - provider "mondoo" { - region = "us" -} - -resource "mondoo_space" "my_space" { - name = "My Space Name" - org_id = var.mondoo_org + space = "hungry-poet-123456" } # Setup the OCI integration resource "mondoo_integration_oci_tenant" "tenant_abc" { - space_id = mondoo_space.my_space.id - name = "tenant ABC" - + name = "tenant ABC" tenancy = "ocid1.tenancy.oc1..aaaaaaaavvvvvvvvwwwwwwwwxxxxxx..." region = "us-ashburn-1" user = "ocid1.user.oc1..aaaaaaaabbbbbbbbccccccccddddeeeeee..." @@ -65,13 +46,13 @@ EOT - `credentials` (Attributes) (see [below for nested schema](#nestedatt--credentials)) - `region` (String) OCI region -- `space_id` (String) Mondoo Space Identifier. - `tenancy` (String) OCI tenancy - `user` (String) OCI user ### Optional - `name` (String) Name of the integration. +- `space_id` (String) Mondoo Space Identifier. If it is not provided, the provider space is used. ### Read-Only diff --git a/docs/resources/integration_shodan.md b/docs/resources/integration_shodan.md index ef64902..2b3c360 100644 --- a/docs/resources/integration_shodan.md +++ b/docs/resources/integration_shodan.md @@ -3,21 +3,16 @@ page_title: "mondoo_integration_shodan Resource - terraform-provider-mondoo" subcategory: "" description: |- - Continuously scan Internet-connected devices with Shodan. + Continuously assess external risk for domains and IP addresses. --- # mondoo_integration_shodan (Resource) -Continuously scan Internet-connected devices with Shodan. +Continuously assess external risk for domains and IP addresses. ## Example Usage ```terraform -variable "mondoo_org" { - description = "The Mondoo Organization ID" - type = string -} - variable "shodan_token" { description = "The Shodan Token" type = string @@ -25,21 +20,13 @@ variable "shodan_token" { } provider "mondoo" { - region = "us" -} - -# Create a new space -resource "mondoo_space" "shodan_space" { - name = "My Shodan Space Name" - org_id = var.mondoo_org + space = "hungry-poet-123456" } # Setup the Shodan integration resource "mondoo_integration_shodan" "shodan_integration" { - space_id = mondoo_space.shodan_space.id - name = "Shodan Integration" - - targets = ["8.8.8.8", "mondoo.com"] + name = "Shodan Integration" + targets = ["8.8.8.8", "mondoo.com", "63.192.236.0/24"] credentials = { token = var.shodan_token @@ -54,9 +41,12 @@ resource "mondoo_integration_shodan" "shodan_integration" { - `credentials` (Attributes) (see [below for nested schema](#nestedatt--credentials)) - `name` (String) Name of the integration. -- `space_id` (String) Mondoo Space Identifier. - `targets` (List of String) Shodan scan targets. +### Optional + +- `space_id` (String) Mondoo Space Identifier. If it is not provided, the provider space is used. + ### Read-Only - `mrn` (String) Integration identifier diff --git a/docs/resources/integration_slack.md b/docs/resources/integration_slack.md index 8b01e32..7b943c2 100644 --- a/docs/resources/integration_slack.md +++ b/docs/resources/integration_slack.md @@ -13,11 +13,6 @@ Continuously scan your Slack Teams for security misconfigurations. ## Example Usage ```terraform -variable "mondoo_org" { - description = "The Mondoo Organization ID" - type = string -} - variable "slack_token" { description = "The Slack Token" type = string @@ -25,20 +20,12 @@ variable "slack_token" { } provider "mondoo" { - region = "us" -} - -# Create a new space -resource "mondoo_space" "my_space" { - name = "My Slack Space" - org_id = var.mondoo_org + space = "hungry-poet-123456" } # Setup the Slack integration resource "mondoo_integration_slack" "slack_integration" { - space_id = mondoo_space.my_space.id - name = "My Slack Integration" - + name = "My Slack Integration" slack_token = var.slack_token } ``` @@ -50,7 +37,10 @@ resource "mondoo_integration_slack" "slack_integration" { - `name` (String) Name of the integration. - `slack_token` (String, Sensitive) The Slack token to authenticate with the Slack API. -- `space_id` (String) Mondoo Space Identifier. + +### Optional + +- `space_id` (String) Mondoo Space Identifier. If it is not provided, the provider space is used. ### Read-Only diff --git a/docs/resources/policy_assignment.md b/docs/resources/policy_assignment.md index a17b9af..1134c15 100644 --- a/docs/resources/policy_assignment.md +++ b/docs/resources/policy_assignment.md @@ -14,37 +14,21 @@ description: |- ```terraform provider "mondoo" { - region = "us" -} - -resource "mondoo_space" "my_space" { - name = "My Space Name" - org_id = "your-org-1234567" + space = "hungry-poet-123456" } resource "mondoo_policy_assignment" "space" { - space_id = mondoo_space.my_space.id - policies = [ "//policy.api.mondoo.app/policies/mondoo-aws-security", ] - - state = "enabled" # default is enabled, we also support preview and disabled - - depends_on = [ - mondoo_space.my_space - ] } ``` ## Schema -### Required - -- `space_id` (String) Mondoo Space Identifier. - ### Optional - `policies` (List of String) Policies to assign to the space. +- `space_id` (String) Mondoo Space Identifier. If it is not provided, the provider space is used. - `state` (String) Policy Assignment State (preview, enabled, disabled). diff --git a/docs/resources/querypack_assignment.md b/docs/resources/querypack_assignment.md index 4b5b520..fd43a09 100644 --- a/docs/resources/querypack_assignment.md +++ b/docs/resources/querypack_assignment.md @@ -14,37 +14,21 @@ description: |- ```terraform provider "mondoo" { - region = "us" -} - -resource "mondoo_space" "my_space" { - name = "My Space Name" - org_id = "your-org-1234567" + space = "hungry-poet-123456" } resource "mondoo_querypack_assignment" "space" { - space_id = mondoo_space.my_space.id - querypacks = [ "//policy.api.mondoo.app/policies/mondoo-incident-response-aws", ] - - state = "enabled" # default is enabled, we also support preview and disabled - - depends_on = [ - mondoo_space.my_space - ] } ``` ## Schema -### Required - -- `space_id` (String) Mondoo Space Identifier. - ### Optional - `querypacks` (List of String) QueryPacks to assign to the space. +- `space_id` (String) Mondoo Space Identifier. If it is not provided, the provider space is used. - `state` (String) QueryPack Assignment State (enabled, disabled). diff --git a/docs/resources/registration_token.md b/docs/resources/registration_token.md index 685f37d..63857b3 100644 --- a/docs/resources/registration_token.md +++ b/docs/resources/registration_token.md @@ -29,9 +29,7 @@ variable "org_id" { # Configure the Mondoo # ---------------------------------------------- -provider "mondoo" { - region = "us" -} +provider "mondoo" {} resource "mondoo_space" "my_space" { count = length(var.space_names) @@ -253,10 +251,6 @@ output. ## Schema -### Required - -- `space_id` (String) Mondoo Space Identifier to create the token in. - ### Optional - `description` (String) Description of the token. @@ -264,6 +258,7 @@ output. - `expires_in` (String) The duration after which the token will expire. Format: 1h, 1d, 1w, 1m, 1y - `no_expiration` (Boolean) If set to true, the token will not expire. - `revoked` (Boolean) If set to true, the token is revoked. +- `space_id` (String) Mondoo Space Identifier to create the token in. If it is not provided, the provider space is used. ### Read-Only diff --git a/docs/resources/service_account.md b/docs/resources/service_account.md index ce87551..8a588fd 100644 --- a/docs/resources/service_account.md +++ b/docs/resources/service_account.md @@ -13,24 +13,8 @@ Allows management of a Mondoo service account. ## Example Usage ```terraform -# Variables -# ---------------------------------------------- - -variable "mondoo_org" { - description = "The Mondoo Organization ID" - type = string -} - -# Configure the Mondoo -# ---------------------------------------------- - provider "mondoo" { - region = "us" -} - -resource "mondoo_space" "my_space" { - name = "My Terraform Space" - org_id = var.mondoo_org + space = "hungry-poet-123456" } resource "mondoo_service_account" "service_account" { @@ -39,11 +23,6 @@ resource "mondoo_service_account" "service_account" { roles = [ "//iam.api.mondoo.app/roles/viewer", ] - space_id = mondoo_space.my_space.id - - depends_on = [ - mondoo_space.my_space - ] } output "service_account_json" { diff --git a/docs/resources/space.md b/docs/resources/space.md index 7016104..768ec50 100644 --- a/docs/resources/space.md +++ b/docs/resources/space.md @@ -13,15 +13,18 @@ Space resource ## Example Usage ```terraform -provider "mondoo" { - region = "us" +variable "org_id" { + description = "The organization id to create the spaces in" + type = string } +provider "mondoo" {} + resource "mondoo_space" "my_space" { name = "My Space New" # optional id otherwise it will be auto-generated # id = "your-space-id" - org_id = "your-org-1234567" + org_id = var.org_id } ``` @@ -34,7 +37,7 @@ resource "mondoo_space" "my_space" { ### Optional -- `id` (String) Id of the space. Must be globally unique. +- `id` (String) Id of the space. Must be globally unique. If the provider has a space configured and this field is not provided, the provider space is used. - `name` (String) Name of the space. ### Read-Only diff --git a/examples/data-sources/mondoo_assets/main.tf b/examples/data-sources/mondoo_assets/main.tf index 1483327..6aeddc8 100644 --- a/examples/data-sources/mondoo_assets/main.tf +++ b/examples/data-sources/mondoo_assets/main.tf @@ -2,7 +2,7 @@ terraform { required_providers { mondoo = { source = "mondoohq/mondoo" - version = ">= 0.4.0" + version = ">= 0.19" } } } diff --git a/examples/data-sources/mondoo_frameworks/main.tf b/examples/data-sources/mondoo_frameworks/main.tf index 1483327..6aeddc8 100644 --- a/examples/data-sources/mondoo_frameworks/main.tf +++ b/examples/data-sources/mondoo_frameworks/main.tf @@ -2,7 +2,7 @@ terraform { required_providers { mondoo = { source = "mondoohq/mondoo" - version = ">= 0.4.0" + version = ">= 0.19" } } } diff --git a/examples/data-sources/mondoo_organization/main.tf b/examples/data-sources/mondoo_organization/main.tf index 1483327..6aeddc8 100644 --- a/examples/data-sources/mondoo_organization/main.tf +++ b/examples/data-sources/mondoo_organization/main.tf @@ -2,7 +2,7 @@ terraform { required_providers { mondoo = { source = "mondoohq/mondoo" - version = ">= 0.4.0" + version = ">= 0.19" } } } diff --git a/examples/data-sources/mondoo_policies/main.tf b/examples/data-sources/mondoo_policies/main.tf index 1483327..6aeddc8 100644 --- a/examples/data-sources/mondoo_policies/main.tf +++ b/examples/data-sources/mondoo_policies/main.tf @@ -2,7 +2,7 @@ terraform { required_providers { mondoo = { source = "mondoohq/mondoo" - version = ">= 0.4.0" + version = ">= 0.19" } } } diff --git a/examples/data-sources/mondoo_space/main.tf b/examples/data-sources/mondoo_space/main.tf index 1483327..6aeddc8 100644 --- a/examples/data-sources/mondoo_space/main.tf +++ b/examples/data-sources/mondoo_space/main.tf @@ -2,7 +2,7 @@ terraform { required_providers { mondoo = { source = "mondoohq/mondoo" - version = ">= 0.4.0" + version = ">= 0.19" } } } diff --git a/examples/provider/provider.tf b/examples/provider/provider.tf index ae23359..e32821d 100644 --- a/examples/provider/provider.tf +++ b/examples/provider/provider.tf @@ -2,11 +2,12 @@ terraform { required_providers { mondoo = { source = "mondoohq/mondoo" - version = ">= 0.4.0" + version = ">= 0.19" } } } provider "mondoo" { - region = "us" # use "eu" for the European region -} \ No newline at end of file + space = "hungry-poet-1988" + region = "us" +} diff --git a/examples/resources/mondoo_custom_framework/main.tf b/examples/resources/mondoo_custom_framework/main.tf index 1483327..6aeddc8 100644 --- a/examples/resources/mondoo_custom_framework/main.tf +++ b/examples/resources/mondoo_custom_framework/main.tf @@ -2,7 +2,7 @@ terraform { required_providers { mondoo = { source = "mondoohq/mondoo" - version = ">= 0.4.0" + version = ">= 0.19" } } } diff --git a/examples/resources/mondoo_custom_framework/resource.tf b/examples/resources/mondoo_custom_framework/resource.tf index d709c7a..ef2fa4e 100644 --- a/examples/resources/mondoo_custom_framework/resource.tf +++ b/examples/resources/mondoo_custom_framework/resource.tf @@ -1,11 +1,5 @@ provider "mondoo" { - region = "us" -} - -variable "mondoo_org" { - description = "The Mondoo Organization ID" - type = string - default = "my-org-1234567" + space = "hungry-poet-123456" } variable "my_custom_framework" { @@ -14,13 +8,6 @@ variable "my_custom_framework" { default = "framework.mql.yaml" } -# Create a new space -resource "mondoo_space" "my_space" { - name = "Custom Framework Space" - org_id = var.mondoo_org -} - resource "mondoo_custom_framework" "custom_framework" { - space_id = mondoo_space.my_space.id data_url = var.my_custom_framework } diff --git a/examples/resources/mondoo_custom_policy/main.tf b/examples/resources/mondoo_custom_policy/main.tf index 1483327..6aeddc8 100644 --- a/examples/resources/mondoo_custom_policy/main.tf +++ b/examples/resources/mondoo_custom_policy/main.tf @@ -2,7 +2,7 @@ terraform { required_providers { mondoo = { source = "mondoohq/mondoo" - version = ">= 0.4.0" + version = ">= 0.19" } } } diff --git a/examples/resources/mondoo_custom_policy/resource.tf b/examples/resources/mondoo_custom_policy/resource.tf index 4999c07..8a32fa0 100644 --- a/examples/resources/mondoo_custom_policy/resource.tf +++ b/examples/resources/mondoo_custom_policy/resource.tf @@ -1,33 +1,23 @@ -provider "mondoo" {} - -resource "mondoo_space" "my_space" { - name = "My Custom Space" - org_id = "your-org-1234567" -} - variable "my_custom_policy" { description = "Path to the custom policy file. The file must be in MQL format." type = string default = "policy.mql.yaml" } +provider "mondoo" { + space = "hungry-poet-123456" +} + resource "mondoo_custom_policy" "my_policy" { - space_id = mondoo_space.my_space.id source = var.my_custom_policy overwrite = true } resource "mondoo_policy_assignment" "space" { - space_id = mondoo_space.my_space.id - policies = concat( mondoo_custom_policy.my_policy.mrns, [], ) state = "enabled" - - depends_on = [ - mondoo_space.my_space - ] -} \ No newline at end of file +} diff --git a/examples/resources/mondoo_custom_querypack/main.tf b/examples/resources/mondoo_custom_querypack/main.tf index 1483327..6aeddc8 100644 --- a/examples/resources/mondoo_custom_querypack/main.tf +++ b/examples/resources/mondoo_custom_querypack/main.tf @@ -2,7 +2,7 @@ terraform { required_providers { mondoo = { source = "mondoohq/mondoo" - version = ">= 0.4.0" + version = ">= 0.19" } } } diff --git a/examples/resources/mondoo_custom_querypack/resource.tf b/examples/resources/mondoo_custom_querypack/resource.tf index dfe3636..099056d 100644 --- a/examples/resources/mondoo_custom_querypack/resource.tf +++ b/examples/resources/mondoo_custom_querypack/resource.tf @@ -1,39 +1,22 @@ -provider "mondoo" { - region = "us" -} - -variable "mondoo_org" { - description = "Mondoo Organization" - type = string -} - -resource "mondoo_space" "my_space" { - name = "My Space Name" - org_id = var.mondoo_org -} - variable "my_custom_querypack" { description = "Path to custom querypack file. File must be in MQL format." type = string default = "querypack.mql.yaml" } +provider "mondoo" { + space = "hungry-poet-123456" +} + resource "mondoo_custom_querypack" "my_query_pack" { - space_id = mondoo_space.my_space.id - source = var.my_custom_querypack + source = var.my_custom_querypack } resource "mondoo_querypack_assignment" "space" { - space_id = mondoo_space.my_space.id - querypacks = concat( mondoo_custom_querypack.my_query_pack.mrns, [], ) state = "enabled" - - depends_on = [ - mondoo_space.my_space - ] -} \ No newline at end of file +} diff --git a/examples/resources/mondoo_framework_assignment/main.tf b/examples/resources/mondoo_framework_assignment/main.tf index 1483327..6aeddc8 100644 --- a/examples/resources/mondoo_framework_assignment/main.tf +++ b/examples/resources/mondoo_framework_assignment/main.tf @@ -2,7 +2,7 @@ terraform { required_providers { mondoo = { source = "mondoohq/mondoo" - version = ">= 0.4.0" + version = ">= 0.19" } } } diff --git a/examples/resources/mondoo_framework_assignment/resource.tf b/examples/resources/mondoo_framework_assignment/resource.tf index dca5c12..f1ae5a5 100644 --- a/examples/resources/mondoo_framework_assignment/resource.tf +++ b/examples/resources/mondoo_framework_assignment/resource.tf @@ -1,22 +1,11 @@ provider "mondoo" { - region = "us" -} - -variable "mondoo_org" { - description = "The Mondoo Organization ID" - type = string - default = "my-org-1234567" -} - -# Create a new space -resource "mondoo_space" "my_space" { - name = "Framework Space" - org_id = var.mondoo_org + space = "hungry-poet-123456" } resource "mondoo_framework_assignment" "framework_assignment" { - space_id = mondoo_space.my_space.id - framework_mrn = ["//policy.api.mondoo.app/frameworks/cis-controls-8", - "//policy.api.mondoo.app/frameworks/iso-27001-2022"] + framework_mrn = [ + "//policy.api.mondoo.app/frameworks/cis-controls-8", + "//policy.api.mondoo.app/frameworks/iso-27001-2022" + ] enabled = true } diff --git a/examples/resources/mondoo_integration_aws/main.tf b/examples/resources/mondoo_integration_aws/main.tf index c487c32..baa2b26 100644 --- a/examples/resources/mondoo_integration_aws/main.tf +++ b/examples/resources/mondoo_integration_aws/main.tf @@ -2,7 +2,7 @@ terraform { required_providers { mondoo = { source = "mondoohq/mondoo" - version = ">= 0.4.0" + version = ">= 0.19" } } } diff --git a/examples/resources/mondoo_integration_aws/resource.tf b/examples/resources/mondoo_integration_aws/resource.tf index 0f9cbdf..3b6365e 100644 --- a/examples/resources/mondoo_integration_aws/resource.tf +++ b/examples/resources/mondoo_integration_aws/resource.tf @@ -1,8 +1,3 @@ -variable "mondoo_org" { - description = "Mondoo Organization" - type = string -} - variable "aws_access_key" { description = "AWS access key" type = string @@ -15,18 +10,13 @@ variable "aws_secret_key" { sensitive = true } -provider "mondoo" {} - -# Create a new space -resource "mondoo_space" "my_space" { - name = "AWS Terraform" - org_id = var.mondoo_org +provider "mondoo" { + space = "hungry-poet-123456" } # Setup the AWS integration resource "mondoo_integration_aws" "name" { - space_id = mondoo_space.my_space.id - name = "AWS Integration" + name = "AWS Integration" credentials = { key = { diff --git a/examples/resources/mondoo_integration_aws_serverless/main.tf b/examples/resources/mondoo_integration_aws_serverless/main.tf index dd86232..8805025 100644 --- a/examples/resources/mondoo_integration_aws_serverless/main.tf +++ b/examples/resources/mondoo_integration_aws_serverless/main.tf @@ -2,7 +2,7 @@ terraform { required_providers { mondoo = { source = "mondoohq/mondoo" - version = ">= 0.4.0" + version = ">= 0.19" } aws = { source = "hashicorp/aws" diff --git a/examples/resources/mondoo_integration_aws_serverless/resource.tf b/examples/resources/mondoo_integration_aws_serverless/resource.tf index 256fa41..9ba7af8 100644 --- a/examples/resources/mondoo_integration_aws_serverless/resource.tf +++ b/examples/resources/mondoo_integration_aws_serverless/resource.tf @@ -1,8 +1,3 @@ -variable "mondoo_org" { - description = "Mondoo Organization" - type = string -} - variable "origin_aws_account" { description = "Origin AWS Account" type = string @@ -27,7 +22,7 @@ variable "aws_account_id" { } provider "mondoo" { - region = "us" + space = "hungry-poet-123456" } provider "aws" { @@ -36,15 +31,8 @@ provider "aws" { data "aws_region" "current" {} -# Create a new space -resource "mondoo_space" "my_space" { - name = "AWS Terraform" - org_id = var.mondoo_org -} - # Setup the AWS integration resource "mondoo_integration_aws_serverless" "aws_serverless" { - space_id = mondoo_space.my_space.id name = "AWS Integration" region = data.aws_region.current.name is_organization = false @@ -81,6 +69,6 @@ resource "aws_cloudformation_stack" "mondoo_stack" { } } -# for organisation wide deploys use aws_cloudformation_stack_set and aws_cloudformation_stack_set_instance instead of aws_cloudformation_stack +# for organization wide deployments use aws_cloudformation_stack_set and aws_cloudformation_stack_set_instance instead of aws_cloudformation_stack # https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudformation_stack_set # https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudformation_stack_set_instance diff --git a/examples/resources/mondoo_integration_azure/main.tf b/examples/resources/mondoo_integration_azure/main.tf index 9f8e236..70e7508 100644 --- a/examples/resources/mondoo_integration_azure/main.tf +++ b/examples/resources/mondoo_integration_azure/main.tf @@ -10,7 +10,7 @@ terraform { } mondoo = { source = "mondoohq/mondoo" - version = ">= 0.4.0" + version = ">= 0.19" } tls = { source = "hashicorp/tls" diff --git a/examples/resources/mondoo_integration_azure/resource.tf b/examples/resources/mondoo_integration_azure/resource.tf index 40ec61b..43f2f27 100644 --- a/examples/resources/mondoo_integration_azure/resource.tf +++ b/examples/resources/mondoo_integration_azure/resource.tf @@ -7,12 +7,6 @@ variable "tenant_id" { default = "ffffffff-ffff-ffff-ffff-ffffffffffff" } -variable "mondoo_org" { - description = "The Mondoo Organization ID" - type = string - default = "your-org-1234567" -} - variable "primary_subscription" { description = "The primary Azure Subscription ID" type = string @@ -240,18 +234,11 @@ resource "azurerm_role_assignment" "reader" { # ---------------------------------------------- provider "mondoo" { - region = "us" -} - -# Create a new space -resource "mondoo_space" "azure_space" { - name = "Azure Terraform Integration" - org_id = var.mondoo_org + space = "hungry-poet-123456" } # Setup the Azure integration resource "mondoo_integration_azure" "azure_integration" { - space_id = mondoo_space.azure_space.id name = "Azure ${local.mondoo_security_integration_name}" tenant_id = var.tenant_id client_id = azuread_application.mondoo_security.client_id @@ -263,7 +250,6 @@ resource "mondoo_integration_azure" "azure_integration" { } # wait for the permissions to provisioned depends_on = [ - mondoo_space.azure_space, azuread_application.mondoo_security, azuread_service_principal.mondoo_security, azurerm_role_assignment.mondoo_security, diff --git a/examples/resources/mondoo_integration_domain/main.tf b/examples/resources/mondoo_integration_domain/main.tf index 787ff93..24d24a1 100644 --- a/examples/resources/mondoo_integration_domain/main.tf +++ b/examples/resources/mondoo_integration_domain/main.tf @@ -2,7 +2,7 @@ terraform { required_providers { mondoo = { source = "mondoohq/mondoo" - version = ">= 0.4.0" + version = ">= 0.19" } } } \ No newline at end of file diff --git a/examples/resources/mondoo_integration_domain/resource.tf b/examples/resources/mondoo_integration_domain/resource.tf index add920e..0d2aec2 100644 --- a/examples/resources/mondoo_integration_domain/resource.tf +++ b/examples/resources/mondoo_integration_domain/resource.tf @@ -1,22 +1,10 @@ -variable "mondoo_org" { - description = "The Mondoo Organization ID" - type = string -} - provider "mondoo" { - region = "us" -} - -# Create a new space -resource "mondoo_space" "domain_space" { - name = "My Space Name" - org_id = var.mondoo_org + space = "hungry-poet-123456" } # Setup the Domain integration resource "mondoo_integration_domain" "domain_integration" { - space_id = mondoo_space.domain_space.id - host = "mondoo.com" - https = true - http = false -} \ No newline at end of file + host = "mondoo.com" + https = true + http = false +} diff --git a/examples/resources/mondoo_integration_gcp/main.tf b/examples/resources/mondoo_integration_gcp/main.tf index 7b060da..169b26b 100644 --- a/examples/resources/mondoo_integration_gcp/main.tf +++ b/examples/resources/mondoo_integration_gcp/main.tf @@ -2,7 +2,7 @@ terraform { required_providers { mondoo = { source = "mondoohq/mondoo" - version = ">= 0.4.0" + version = ">= 0.19" } google = { source = "hashicorp/google" diff --git a/examples/resources/mondoo_integration_gcp/resource.tf b/examples/resources/mondoo_integration_gcp/resource.tf index 585a1a0..791f8b6 100644 --- a/examples/resources/mondoo_integration_gcp/resource.tf +++ b/examples/resources/mondoo_integration_gcp/resource.tf @@ -6,11 +6,6 @@ variable "gcp_project" { type = string } -variable "mondoo_org" { - description = "Mondoo Organization" - type = string -} - # Create GCP Service Account # ---------------------------------------------- @@ -45,18 +40,11 @@ output "google_service_account_key" { # ---------------------------------------------- provider "mondoo" { - region = "us" -} - -# Create a new space -resource "mondoo_space" "my_space" { - name = "GCP ${data.google_project.project.name}" - org_id = var.mondoo_org + space = "hungry-poet-123456" } # Setup the GCP integration resource "mondoo_integration_gcp" "name" { - space_id = mondoo_space.my_space.id name = "GCP ${data.google_project.project.name}" project_id = data.google_project.project.project_id credentials = { diff --git a/examples/resources/mondoo_integration_github/main.tf b/examples/resources/mondoo_integration_github/main.tf index 787ff93..24d24a1 100644 --- a/examples/resources/mondoo_integration_github/main.tf +++ b/examples/resources/mondoo_integration_github/main.tf @@ -2,7 +2,7 @@ terraform { required_providers { mondoo = { source = "mondoohq/mondoo" - version = ">= 0.4.0" + version = ">= 0.19" } } } \ No newline at end of file diff --git a/examples/resources/mondoo_integration_github/resource.tf b/examples/resources/mondoo_integration_github/resource.tf index a9fc43d..6a5fc34 100644 --- a/examples/resources/mondoo_integration_github/resource.tf +++ b/examples/resources/mondoo_integration_github/resource.tf @@ -1,8 +1,3 @@ -variable "mondoo_org" { - description = "The Mondoo Organization ID" - type = string -} - variable "github_token" { description = "The GitHub Token" type = string @@ -10,20 +5,12 @@ variable "github_token" { } provider "mondoo" { - region = "us" -} - -# Create a new space -resource "mondoo_space" "gh_space" { - name = "My GitHub Space Name" - org_id = var.mondoo_org + space = "hungry-poet-123456" } # Setup the GitHub integration resource "mondoo_integration_github" "gh_integration" { - space_id = mondoo_space.gh_space.id - name = "GitHub Integration" - + name = "GitHub Integration" owner = "lunalectric" # define a repository if you want to restrict scan to a single repository diff --git a/examples/resources/mondoo_integration_ms365/main.tf b/examples/resources/mondoo_integration_ms365/main.tf index 9f8e236..70e7508 100644 --- a/examples/resources/mondoo_integration_ms365/main.tf +++ b/examples/resources/mondoo_integration_ms365/main.tf @@ -10,7 +10,7 @@ terraform { } mondoo = { source = "mondoohq/mondoo" - version = ">= 0.4.0" + version = ">= 0.19" } tls = { source = "hashicorp/tls" diff --git a/examples/resources/mondoo_integration_ms365/resource.tf b/examples/resources/mondoo_integration_ms365/resource.tf index 2316269..9664752 100644 --- a/examples/resources/mondoo_integration_ms365/resource.tf +++ b/examples/resources/mondoo_integration_ms365/resource.tf @@ -7,12 +7,6 @@ variable "tenant_id" { default = "ffffffff-ffff-ffff-ffff-ffffffffffff" } -variable "mondoo_org" { - description = "The Mondoo Organization ID" - type = string - default = "your-org-1234567" -} - locals { mondoo_security_integration_name = "Mondoo Security Integration" } @@ -154,18 +148,11 @@ resource "azuread_directory_role_assignment" "global_reader" { # ---------------------------------------------- provider "mondoo" { - region = "us" -} - -# Create a new space -resource "mondoo_space" "ms365_space" { - name = "Ms365 Terraform Integration" - org_id = var.mondoo_org + space = "hungry-poet-123456" } # Setup the Azure integration resource "mondoo_integration_ms365" "ms365_integration" { - space_id = mondoo_space.ms365_space.id name = "Ms365 ${local.mondoo_security_integration_name}" tenant_id = var.tenant_id client_id = azuread_application.mondoo_security.client_id @@ -174,7 +161,6 @@ resource "mondoo_integration_ms365" "ms365_integration" { } # wait for the permissions to provisioned depends_on = [ - mondoo_space.ms365_space, azuread_application.mondoo_security, azuread_service_principal.mondoo_security, azuread_directory_role_assignment.global_reader, diff --git a/examples/resources/mondoo_integration_oci_tenant/main.tf b/examples/resources/mondoo_integration_oci_tenant/main.tf index 787ff93..24d24a1 100644 --- a/examples/resources/mondoo_integration_oci_tenant/main.tf +++ b/examples/resources/mondoo_integration_oci_tenant/main.tf @@ -2,7 +2,7 @@ terraform { required_providers { mondoo = { source = "mondoohq/mondoo" - version = ">= 0.4.0" + version = ">= 0.19" } } } \ No newline at end of file diff --git a/examples/resources/mondoo_integration_oci_tenant/resource.tf b/examples/resources/mondoo_integration_oci_tenant/resource.tf index a11cfa2..1b74da9 100644 --- a/examples/resources/mondoo_integration_oci_tenant/resource.tf +++ b/examples/resources/mondoo_integration_oci_tenant/resource.tf @@ -1,29 +1,10 @@ -# Variables -# ---------------------------------------------- - -variable "mondoo_org" { - description = "The Mondoo Organization ID" - type = string - default = "your-org-1234567" -} - -# Configure the Mondoo -# ---------------------------------------------- - provider "mondoo" { - region = "us" -} - -resource "mondoo_space" "my_space" { - name = "My Space Name" - org_id = var.mondoo_org + space = "hungry-poet-123456" } # Setup the OCI integration resource "mondoo_integration_oci_tenant" "tenant_abc" { - space_id = mondoo_space.my_space.id - name = "tenant ABC" - + name = "tenant ABC" tenancy = "ocid1.tenancy.oc1..aaaaaaaavvvvvvvvwwwwwwwwxxxxxx..." region = "us-ashburn-1" user = "ocid1.user.oc1..aaaaaaaabbbbbbbbccccccccddddeeeeee..." diff --git a/examples/resources/mondoo_integration_shodan/main.tf b/examples/resources/mondoo_integration_shodan/main.tf index 92d396d..baa2b26 100644 --- a/examples/resources/mondoo_integration_shodan/main.tf +++ b/examples/resources/mondoo_integration_shodan/main.tf @@ -2,7 +2,7 @@ terraform { required_providers { mondoo = { source = "mondoohq/mondoo" - version = ">= 0.5" + version = ">= 0.19" } } } diff --git a/examples/resources/mondoo_integration_shodan/resource.tf b/examples/resources/mondoo_integration_shodan/resource.tf index 46a215d..9dfec65 100644 --- a/examples/resources/mondoo_integration_shodan/resource.tf +++ b/examples/resources/mondoo_integration_shodan/resource.tf @@ -1,8 +1,3 @@ -variable "mondoo_org" { - description = "The Mondoo Organization ID" - type = string -} - variable "shodan_token" { description = "The Shodan Token" type = string @@ -10,21 +5,13 @@ variable "shodan_token" { } provider "mondoo" { - region = "us" -} - -# Create a new space -resource "mondoo_space" "shodan_space" { - name = "My Shodan Space Name" - org_id = var.mondoo_org + space = "hungry-poet-123456" } # Setup the Shodan integration resource "mondoo_integration_shodan" "shodan_integration" { - space_id = mondoo_space.shodan_space.id - name = "Shodan Integration" - - targets = ["8.8.8.8", "mondoo.com"] + name = "Shodan Integration" + targets = ["8.8.8.8", "mondoo.com", "63.192.236.0/24"] credentials = { token = var.shodan_token diff --git a/examples/resources/mondoo_integration_slack/main.tf b/examples/resources/mondoo_integration_slack/main.tf index 787ff93..24d24a1 100644 --- a/examples/resources/mondoo_integration_slack/main.tf +++ b/examples/resources/mondoo_integration_slack/main.tf @@ -2,7 +2,7 @@ terraform { required_providers { mondoo = { source = "mondoohq/mondoo" - version = ">= 0.4.0" + version = ">= 0.19" } } } \ No newline at end of file diff --git a/examples/resources/mondoo_integration_slack/resource.tf b/examples/resources/mondoo_integration_slack/resource.tf index 9fe35c8..2d877e4 100644 --- a/examples/resources/mondoo_integration_slack/resource.tf +++ b/examples/resources/mondoo_integration_slack/resource.tf @@ -1,8 +1,3 @@ -variable "mondoo_org" { - description = "The Mondoo Organization ID" - type = string -} - variable "slack_token" { description = "The Slack Token" type = string @@ -10,19 +5,11 @@ variable "slack_token" { } provider "mondoo" { - region = "us" -} - -# Create a new space -resource "mondoo_space" "my_space" { - name = "My Slack Space" - org_id = var.mondoo_org + space = "hungry-poet-123456" } # Setup the Slack integration resource "mondoo_integration_slack" "slack_integration" { - space_id = mondoo_space.my_space.id - name = "My Slack Integration" - + name = "My Slack Integration" slack_token = var.slack_token } diff --git a/examples/resources/mondoo_policy_assignment/main.tf b/examples/resources/mondoo_policy_assignment/main.tf index 787ff93..24d24a1 100644 --- a/examples/resources/mondoo_policy_assignment/main.tf +++ b/examples/resources/mondoo_policy_assignment/main.tf @@ -2,7 +2,7 @@ terraform { required_providers { mondoo = { source = "mondoohq/mondoo" - version = ">= 0.4.0" + version = ">= 0.19" } } } \ No newline at end of file diff --git a/examples/resources/mondoo_policy_assignment/resource.tf b/examples/resources/mondoo_policy_assignment/resource.tf index ce463fe..1327e7c 100644 --- a/examples/resources/mondoo_policy_assignment/resource.tf +++ b/examples/resources/mondoo_policy_assignment/resource.tf @@ -1,22 +1,9 @@ provider "mondoo" { - region = "us" -} - -resource "mondoo_space" "my_space" { - name = "My Space Name" - org_id = "your-org-1234567" + space = "hungry-poet-123456" } resource "mondoo_policy_assignment" "space" { - space_id = mondoo_space.my_space.id - policies = [ "//policy.api.mondoo.app/policies/mondoo-aws-security", ] - - state = "enabled" # default is enabled, we also support preview and disabled - - depends_on = [ - mondoo_space.my_space - ] } diff --git a/examples/resources/mondoo_querypack_assignment/main.tf b/examples/resources/mondoo_querypack_assignment/main.tf index 787ff93..24d24a1 100644 --- a/examples/resources/mondoo_querypack_assignment/main.tf +++ b/examples/resources/mondoo_querypack_assignment/main.tf @@ -2,7 +2,7 @@ terraform { required_providers { mondoo = { source = "mondoohq/mondoo" - version = ">= 0.4.0" + version = ">= 0.19" } } } \ No newline at end of file diff --git a/examples/resources/mondoo_querypack_assignment/resource.tf b/examples/resources/mondoo_querypack_assignment/resource.tf index 189c108..aff0a67 100644 --- a/examples/resources/mondoo_querypack_assignment/resource.tf +++ b/examples/resources/mondoo_querypack_assignment/resource.tf @@ -1,22 +1,9 @@ provider "mondoo" { - region = "us" -} - -resource "mondoo_space" "my_space" { - name = "My Space Name" - org_id = "your-org-1234567" + space = "hungry-poet-123456" } resource "mondoo_querypack_assignment" "space" { - space_id = mondoo_space.my_space.id - querypacks = [ "//policy.api.mondoo.app/policies/mondoo-incident-response-aws", ] - - state = "enabled" # default is enabled, we also support preview and disabled - - depends_on = [ - mondoo_space.my_space - ] } diff --git a/examples/resources/mondoo_registration_token/main.tf b/examples/resources/mondoo_registration_token/main.tf index 1483327..6aeddc8 100644 --- a/examples/resources/mondoo_registration_token/main.tf +++ b/examples/resources/mondoo_registration_token/main.tf @@ -2,7 +2,7 @@ terraform { required_providers { mondoo = { source = "mondoohq/mondoo" - version = ">= 0.4.0" + version = ">= 0.19" } } } diff --git a/examples/resources/mondoo_registration_token/resource.tf b/examples/resources/mondoo_registration_token/resource.tf index aa62a1b..d885f69 100644 --- a/examples/resources/mondoo_registration_token/resource.tf +++ b/examples/resources/mondoo_registration_token/resource.tf @@ -16,9 +16,7 @@ variable "org_id" { # Configure the Mondoo # ---------------------------------------------- -provider "mondoo" { - region = "us" -} +provider "mondoo" {} resource "mondoo_space" "my_space" { count = length(var.space_names) diff --git a/examples/resources/mondoo_scim_group_mapping/main.tf b/examples/resources/mondoo_scim_group_mapping/main.tf index 787ff93..24d24a1 100644 --- a/examples/resources/mondoo_scim_group_mapping/main.tf +++ b/examples/resources/mondoo_scim_group_mapping/main.tf @@ -2,7 +2,7 @@ terraform { required_providers { mondoo = { source = "mondoohq/mondoo" - version = ">= 0.4.0" + version = ">= 0.19" } } } \ No newline at end of file diff --git a/examples/resources/mondoo_service_account/main.tf b/examples/resources/mondoo_service_account/main.tf index 787ff93..24d24a1 100644 --- a/examples/resources/mondoo_service_account/main.tf +++ b/examples/resources/mondoo_service_account/main.tf @@ -2,7 +2,7 @@ terraform { required_providers { mondoo = { source = "mondoohq/mondoo" - version = ">= 0.4.0" + version = ">= 0.19" } } } \ No newline at end of file diff --git a/examples/resources/mondoo_service_account/resource.tf b/examples/resources/mondoo_service_account/resource.tf index 34cd646..31966eb 100644 --- a/examples/resources/mondoo_service_account/resource.tf +++ b/examples/resources/mondoo_service_account/resource.tf @@ -1,21 +1,5 @@ -# Variables -# ---------------------------------------------- - -variable "mondoo_org" { - description = "The Mondoo Organization ID" - type = string -} - -# Configure the Mondoo -# ---------------------------------------------- - provider "mondoo" { - region = "us" -} - -resource "mondoo_space" "my_space" { - name = "My Terraform Space" - org_id = var.mondoo_org + space = "hungry-poet-123456" } resource "mondoo_service_account" "service_account" { @@ -24,11 +8,6 @@ resource "mondoo_service_account" "service_account" { roles = [ "//iam.api.mondoo.app/roles/viewer", ] - space_id = mondoo_space.my_space.id - - depends_on = [ - mondoo_space.my_space - ] } output "service_account_json" { diff --git a/examples/resources/mondoo_space/main.tf b/examples/resources/mondoo_space/main.tf index 787ff93..24d24a1 100644 --- a/examples/resources/mondoo_space/main.tf +++ b/examples/resources/mondoo_space/main.tf @@ -2,7 +2,7 @@ terraform { required_providers { mondoo = { source = "mondoohq/mondoo" - version = ">= 0.4.0" + version = ">= 0.19" } } } \ No newline at end of file diff --git a/examples/resources/mondoo_space/resource.tf b/examples/resources/mondoo_space/resource.tf index 878c31c..be9e1d2 100644 --- a/examples/resources/mondoo_space/resource.tf +++ b/examples/resources/mondoo_space/resource.tf @@ -1,11 +1,13 @@ -provider "mondoo" { - region = "us" +variable "org_id" { + description = "The organization id to create the spaces in" + type = string } +provider "mondoo" {} + resource "mondoo_space" "my_space" { name = "My Space New" # optional id otherwise it will be auto-generated # id = "your-space-id" - org_id = "your-org-1234567" + org_id = var.org_id } - diff --git a/internal/provider/assets_data_source.go b/internal/provider/assets_data_source.go index 51eb686..c91242e 100644 --- a/internal/provider/assets_data_source.go +++ b/internal/provider/assets_data_source.go @@ -13,7 +13,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" - mondoov1 "go.mondoo.com/mondoo-go" ) var _ datasource.DataSource = (*assetsDataSource)(nil) @@ -161,7 +160,7 @@ func (d *assetsDataSource) Configure(ctx context.Context, req datasource.Configu return } - client, ok := req.ProviderData.(*mondoov1.Client) + client, ok := req.ProviderData.(*ExtendedGqlClient) if !ok { resp.Diagnostics.AddError( @@ -172,7 +171,7 @@ func (d *assetsDataSource) Configure(ctx context.Context, req datasource.Configu return } - d.client = &ExtendedGqlClient{client} + d.client = client } func (d *assetsDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { diff --git a/internal/provider/custom_framework_resource.go b/internal/provider/custom_framework_resource.go index d7af24b..4d646b5 100644 --- a/internal/provider/custom_framework_resource.go +++ b/internal/provider/custom_framework_resource.go @@ -9,12 +9,13 @@ import ( "os" "strings" + "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/types" - mondoov1 "go.mondoo.com/mondoo-go" + "github.com/hashicorp/terraform-plugin-log/tflog" "gopkg.in/yaml.v2" ) @@ -30,7 +31,7 @@ type customFrameworkResource struct { type customFrameworkResourceModel struct { // scope - SpaceId types.String `tfsdk:"space_id"` + SpaceID types.String `tfsdk:"space_id"` // resource details Mrn types.String `tfsdk:"mrn"` @@ -70,13 +71,13 @@ func (r *customFrameworkResource) getFrameworkContent(data customFrameworkResour return frameworkData, config.Frameworks[0].UID, nil } -func (r *customFrameworkResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { +func (r *customFrameworkResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ MarkdownDescription: `Set custom Compliance Frameworks for a Mondoo Space.`, Attributes: map[string]schema.Attribute{ "space_id": schema.StringAttribute{ - MarkdownDescription: "Mondoo Space Identifier.", - Required: true, + MarkdownDescription: "Mondoo Space Identifier. If it is not provided, the provider space is used.", + Optional: true, }, "mrn": schema.StringAttribute{ MarkdownDescription: "Mondoo Resource Name.", @@ -93,13 +94,13 @@ func (r *customFrameworkResource) Schema(ctx context.Context, req resource.Schem } } -func (r *customFrameworkResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { +func (r *customFrameworkResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { // Prevent panic if the provider has not been configured. if req.ProviderData == nil { return } - client, ok := req.ProviderData.(*mondoov1.Client) + client, ok := req.ProviderData.(*ExtendedGqlClient) if !ok { resp.Diagnostics.AddError( @@ -110,7 +111,7 @@ func (r *customFrameworkResource) Configure(ctx context.Context, req resource.Co return } - r.client = &ExtendedGqlClient{client} + r.client = client } func (r *customFrameworkResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { @@ -123,27 +124,39 @@ func (r *customFrameworkResource) Create(ctx context.Context, req resource.Creat return } - // Do GraphQL request to API to create the resource. - spaceMrn := "" - if data.SpaceId.ValueString() != "" { - spaceMrn = spacePrefix + data.SpaceId.ValueString() + space, err := r.client.ComputeSpace(data.SpaceID) + if err != nil { + resp.Diagnostics.AddError("Invalid Configuration", err.Error()) + return } + ctx = tflog.SetField(ctx, "space_mrn", space.MRN()) content, uid, err := r.getFrameworkContent(data) if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to get Compliance Framework Content, got error: %s", err)) + resp.Diagnostics. + AddError("Client Error", + fmt.Sprintf("Unable to get Compliance Framework Content, got error: %s", err), + ) return } - err = r.client.UploadFramework(ctx, spaceMrn, content) + // Do GraphQL request to API to create the resource. + tflog.Debug(ctx, "Creating framework") + err = r.client.UploadFramework(ctx, space.MRN(), content) if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to upload Compliance Framework, got error: %s", err)) + resp.Diagnostics. + AddError("Client Error", + fmt.Sprintf("Unable to upload Compliance Framework, got error: %s", err), + ) return } - framework, err := r.client.GetFramework(ctx, spaceMrn, data.SpaceId.ValueString(), uid) + framework, err := r.client.GetFramework(ctx, space.MRN(), data.SpaceID.ValueString(), uid) if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to get Compliance Framework, got error: %s", err)) + resp.Diagnostics. + AddError("Client Error", + fmt.Sprintf("Unable to get Compliance Framework, got error: %s", err), + ) return } @@ -179,21 +192,42 @@ func (r *customFrameworkResource) Update(ctx context.Context, req resource.Updat return } - // Do GraphQL request to API to create the resource. - spaceMrn := "" - if data.SpaceId.ValueString() != "" { - spaceMrn = spacePrefix + data.SpaceId.ValueString() + space, err := r.client.ComputeSpace(data.SpaceID) + if err != nil { + resp.Diagnostics.AddError("Invalid Configuration", err.Error()) + return + } + ctx = tflog.SetField(ctx, "space_mrn", space.MRN()) + + // ensure space id is not changed + var planSpaceID string + req.Plan.GetAttribute(ctx, path.Root("space_id"), &planSpaceID) + + if space.ID() != planSpaceID { + resp.Diagnostics.AddError( + "Space ID cannot be changed", + "Note that the Mondoo space can be configured at the resource or provider level.", + ) + return } content, _, err := r.getFrameworkContent(data) if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to get Compliance Framework Content, got error: %s", err)) + resp.Diagnostics. + AddError("Client Error", + fmt.Sprintf("Unable to get Compliance Framework Content, got error: %s", err), + ) return } - err = r.client.UploadFramework(ctx, spaceMrn, content) + // Do GraphQL request to API to update the resource. + tflog.Debug(ctx, "Updating framework") + err = r.client.UploadFramework(ctx, space.MRN(), content) if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to upload Compliance Framework, got error: %s", err)) + resp.Diagnostics. + AddError("Client Error", + fmt.Sprintf("Unable to upload Compliance Framework, got error: %s", err), + ) return } @@ -214,7 +248,10 @@ func (r *customFrameworkResource) Delete(ctx context.Context, req resource.Delet // Do GraphQL request to API to update the resource. err := r.client.DeleteFramework(ctx, data.Mrn.ValueString()) if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to delete Compliance Framework, got error: %s", err)) + resp.Diagnostics. + AddError("Client Error", + fmt.Sprintf("Unable to delete Compliance Framework, got error: %s", err), + ) return } } @@ -224,19 +261,34 @@ func (r *customFrameworkResource) ImportState(ctx context.Context, req resource. mrn := req.ID splitMrn := strings.Split(mrn, "/") spaceMrn := spacePrefix + splitMrn[len(splitMrn)-3] - spaceId := splitMrn[len(splitMrn)-3] + spaceID := splitMrn[len(splitMrn)-3] uid := splitMrn[len(splitMrn)-1] - framework, err := r.client.GetFramework(ctx, spaceMrn, spaceId, uid) + if r.client.Space().ID() != "" && r.client.Space().ID() != spaceID { + // The provider is configured to manage resources in a different space than the one the + // resource is currently configured, we won't allow that + resp.Diagnostics.AddError( + "Conflict Error", + fmt.Sprintf( + "Unable to import integration, the provider is configured in a different space than the resource. (%s != %s)", + r.client.Space().ID(), spaceID), + ) + return + } + + framework, err := r.client.GetFramework(ctx, spaceMrn, spaceID, uid) if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to get Compliance Framework, got error: %s", err)) + resp.Diagnostics. + AddError("Client Error", + fmt.Sprintf("Unable to get Compliance Framework, got error: %s", err), + ) return } model := customFrameworkResourceModel{ Mrn: types.StringValue(string(framework.Mrn)), DataUrl: types.StringPointerValue(nil), - SpaceId: types.StringValue(spaceId), + SpaceID: types.StringValue(spaceID), } resp.State.Set(ctx, &model) diff --git a/internal/provider/custom_policy.go b/internal/provider/custom_policy.go index 4ef4382..7ad15d8 100644 --- a/internal/provider/custom_policy.go +++ b/internal/provider/custom_policy.go @@ -19,7 +19,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" - mondoov1 "go.mondoo.com/mondoo-go" + "github.com/hashicorp/terraform-plugin-log/tflog" ) // Ensure provider defined types fully satisfy framework interfaces. @@ -37,7 +37,7 @@ type customPolicyResource struct { // customPolicyResourceModel describes the resource data model. type customPolicyResourceModel struct { // scope - SpaceId types.String `tfsdk:"space_id"` + SpaceID types.String `tfsdk:"space_id"` // policy mrn Mrns types.List `tfsdk:"mrns"` @@ -60,8 +60,8 @@ func (r *customPolicyResource) Schema(ctx context.Context, req resource.SchemaRe MarkdownDescription: "Custom Policy resource", Attributes: map[string]schema.Attribute{ "space_id": schema.StringAttribute{ - MarkdownDescription: "Mondoo Space Identifier.", - Required: true, + MarkdownDescription: "Mondoo Space Identifier. If it is not provided, the provider space is used.", + Optional: true, }, "mrns": schema.ListAttribute{ MarkdownDescription: "The Mondoo Resource Name (MRN) of the created policies", @@ -114,18 +114,21 @@ func (r *customPolicyResource) Configure(ctx context.Context, req resource.Confi return } - client, ok := req.ProviderData.(*mondoov1.Client) + client, ok := req.ProviderData.(*ExtendedGqlClient) if !ok { resp.Diagnostics.AddError( "Unexpected Resource Configure Type", - fmt.Sprintf("Expected *http.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), + fmt.Sprintf( + "Expected *http.Client, got: %T. Please report this issue to the provider developers.", + req.ProviderData, + ), ) return } - r.client = &ExtendedGqlClient{client} + r.client = client } // newCrc32Checksum generates a crc32 checksum for a given content. @@ -162,19 +165,15 @@ func (r *customPolicyResource) Create(ctx context.Context, req resource.CreateRe return } - // Do GraphQL request to API to create the resource - - scopeMrn := "" - if data.SpaceId.ValueString() != "" { - scopeMrn = spacePrefix + data.SpaceId.ValueString() - } else { - resp.Diagnostics.AddError( - "Either space_id needs to be set", - "Either space_id needs to be set", - ) + // Compute and validate the space + space, err := r.client.ComputeSpace(data.SpaceID) + if err != nil { + resp.Diagnostics.AddError("Invalid Configuration", err.Error()) return } + ctx = tflog.SetField(ctx, "space_mrn", space.MRN()) + // Do GraphQL request to API to create the resource if data.Content.IsNull() && data.Source.IsNull() { resp.Diagnostics.AddError( "Either content or source needs to be set", @@ -193,9 +192,17 @@ func (r *customPolicyResource) Create(ctx context.Context, req resource.CreateRe } // call graphql api - setCustomPolicy, err := r.client.SetCustomPolicy(ctx, scopeMrn, data.Overwrite.ValueBoolPointer(), policyBundleData) + tflog.Debug(ctx, "Creating custom policy") + setCustomPolicy, err := r.client.SetCustomPolicy(ctx, + space.MRN(), + data.Overwrite.ValueBoolPointer(), + policyBundleData, + ) if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to store policy, got error: %s", err)) + resp.Diagnostics. + AddError("Client Error", + fmt.Sprintf("Unable to store policy, got error: %s", err), + ) return } @@ -246,6 +253,14 @@ func (r *customPolicyResource) Update(ctx context.Context, req resource.UpdateRe return } + // Compute and validate the space + space, err := r.client.ComputeSpace(data.SpaceID) + if err != nil { + resp.Diagnostics.AddError("Invalid Configuration", err.Error()) + return + } + ctx = tflog.SetField(ctx, "space_mrn", space.MRN()) + // check if the local content has changed, if so, update the policy policyBundleData, checksum, err := r.getContent(data) if err != nil { @@ -260,9 +275,16 @@ func (r *customPolicyResource) Update(ctx context.Context, req resource.UpdateRe // update the policy // call graphql api - setCustomPolicy, err := r.client.SetCustomPolicy(ctx, spacePrefix+data.SpaceId.ValueString(), data.Overwrite.ValueBoolPointer(), policyBundleData) + setCustomPolicy, err := r.client.SetCustomPolicy(ctx, + space.MRN(), + data.Overwrite.ValueBoolPointer(), + policyBundleData, + ) if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to store policy, got error: %s", err)) + resp.Diagnostics. + AddError("Client Error", + fmt.Sprintf("Unable to store policy, got error: %s", err), + ) return } @@ -300,11 +322,20 @@ func (r *customPolicyResource) Delete(ctx context.Context, req resource.DeleteRe func (r *customPolicyResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { mrn := req.ID - splitMrn := strings.Split(mrn, "/") - spaceMrn := spacePrefix + splitMrn[len(splitMrn)-3] - spaceId := splitMrn[len(splitMrn)-3] + spaceID := strings.Split(mrn, "/")[len(strings.Split(mrn, "/"))-3] + if r.client.Space().ID() != "" && r.client.Space().ID() != spaceID { + // The provider is configured to manage resources in a different space than the one the resource is + // currently configured, we won't allow that + resp.Diagnostics.AddError( + "Conflict Error", + fmt.Sprintf( + "Unable to import integration, the provider is configured in a different space than the resource. (%s != %s)", + r.client.Space().ID(), spaceID), + ) + return + } - policy, err := r.client.GetPolicy(ctx, mrn, spaceMrn) + policy, err := r.client.GetPolicy(ctx, mrn, SpaceFrom(spaceID).MRN()) if err != nil { resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to get policy, got error: %s", err)) return @@ -319,7 +350,7 @@ func (r *customPolicyResource) ImportState(ctx context.Context, req resource.Imp mrns, _ := types.ListValueFrom(ctx, types.StringType, []string{mrn}) model := customPolicyResourceModel{ - SpaceId: types.StringValue(spaceId), + SpaceID: types.StringValue(spaceID), Mrns: mrns, Overwrite: types.BoolValue(false), Source: types.StringPointerValue(nil), diff --git a/internal/provider/custom_querypack.go b/internal/provider/custom_querypack.go index af70a21..cfefd21 100644 --- a/internal/provider/custom_querypack.go +++ b/internal/provider/custom_querypack.go @@ -18,7 +18,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" - mondoov1 "go.mondoo.com/mondoo-go" + "github.com/hashicorp/terraform-plugin-log/tflog" ) // Ensure provider defined types fully satisfy framework interfaces. @@ -36,7 +36,7 @@ type customQueryPackResource struct { // customQueryPackResourceModel describes the resource data model. type customQueryPackResourceModel struct { // scope - SpaceId types.String `tfsdk:"space_id"` + SpaceID types.String `tfsdk:"space_id"` // policy mrn Mrns types.List `tfsdk:"mrns"` @@ -50,17 +50,17 @@ type customQueryPackResourceModel struct { Crc32Checksum types.String `tfsdk:"crc32c"` } -func (r *customQueryPackResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { +func (r *customQueryPackResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { resp.TypeName = req.ProviderTypeName + "_custom_querypack" } -func (r *customQueryPackResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { +func (r *customQueryPackResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ MarkdownDescription: "Custom Query Pack resource", Attributes: map[string]schema.Attribute{ "space_id": schema.StringAttribute{ - MarkdownDescription: "Mondoo Space Identifier.", - Required: true, + MarkdownDescription: "Mondoo Space Identifier. If it is not provided, the provider space is used.", + Optional: true, }, "mrns": schema.ListAttribute{ MarkdownDescription: "The Mondoo Resource Name (MRN) of the created query packs", @@ -107,13 +107,13 @@ func (r *customQueryPackResource) Schema(ctx context.Context, req resource.Schem } } -func (r *customQueryPackResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { +func (r *customQueryPackResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { // Prevent panic if the provider has not been configured. if req.ProviderData == nil { return } - client, ok := req.ProviderData.(*mondoov1.Client) + client, ok := req.ProviderData.(*ExtendedGqlClient) if !ok { resp.Diagnostics.AddError( @@ -124,7 +124,7 @@ func (r *customQueryPackResource) Configure(ctx context.Context, req resource.Co return } - r.client = &ExtendedGqlClient{client} + r.client = client } func (r *customQueryPackResource) getContent(data customQueryPackResourceModel) ([]byte, string, error) { @@ -154,18 +154,13 @@ func (r *customQueryPackResource) Create(ctx context.Context, req resource.Creat return } - // Do GraphQL request to API to create the resource - - scopeMrn := "" - if data.SpaceId.ValueString() != "" { - scopeMrn = spacePrefix + data.SpaceId.ValueString() - } else { - resp.Diagnostics.AddError( - "Either space_id needs to be set", - "Either space_id needs to be set", - ) + // Compute and validate the space + space, err := r.client.ComputeSpace(data.SpaceID) + if err != nil { + resp.Diagnostics.AddError("Invalid Configuration", err.Error()) return } + ctx = tflog.SetField(ctx, "space_mrn", space.MRN()) if data.Content.IsNull() && data.Source.IsNull() { resp.Diagnostics.AddError( @@ -184,17 +179,29 @@ func (r *customQueryPackResource) Create(ctx context.Context, req resource.Creat return } - // call graphql api - setCustomPolicyPayload, err := r.client.SetCustomQueryPack(ctx, scopeMrn, data.Overwrite.ValueBoolPointer(), policyBundleData) + // Do GraphQL request to API to create the resource + tflog.Debug(ctx, "Creating custom querypack") + setCustomPolicyPayload, err := r.client.SetCustomQueryPack(ctx, + space.MRN(), + data.Overwrite.ValueBoolPointer(), + policyBundleData, + ) if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to store policy, got error: %s", err)) + resp.Diagnostics. + AddError("Client Error", + fmt.Sprintf("Unable to store policy, got error: %s", err), + ) return } // Save data into Terraform state data.Content = types.StringValue(string(policyBundleData)) data.Crc32Checksum = types.StringValue(checksum) - data.Mrns, _ = types.ListValueFrom(ctx, types.StringType, setCustomPolicyPayload.QueryPackMrns) + data.SpaceID = types.StringValue(space.ID()) + data.Mrns, _ = types.ListValueFrom(ctx, + types.StringType, + setCustomPolicyPayload.QueryPackMrns, + ) resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } @@ -249,12 +256,37 @@ func (r *customQueryPackResource) Update(ctx context.Context, req resource.Updat } if data.Crc32Checksum.ValueString() != checksum { - // update the policy + // Compute and validate the space + space, err := r.client.ComputeSpace(data.SpaceID) + if err != nil { + resp.Diagnostics.AddError("Invalid Configuration", err.Error()) + return + } + ctx = tflog.SetField(ctx, "space_mrn", space.MRN()) - // call graphql api - setCustomPolicyPayload, err := r.client.SetCustomQueryPack(ctx, spacePrefix+data.SpaceId.ValueString(), data.Overwrite.ValueBoolPointer(), policyBundleData) + // ensure space id is not changed + var planSpaceID string + req.Plan.GetAttribute(ctx, path.Root("space_id"), &planSpaceID) + + if space.ID() != planSpaceID { + resp.Diagnostics.AddError( + "Space ID cannot be changed", + "Note that the Mondoo space can be configured at the resource or provider level.", + ) + return + } + + // Do GraphQL request to API to update the resource. + setCustomPolicyPayload, err := r.client.SetCustomQueryPack(ctx, + SpaceFrom(planSpaceID).MRN(), + data.Overwrite.ValueBoolPointer(), + policyBundleData, + ) if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to store policy, got error: %s", err)) + resp.Diagnostics. + AddError("Client Error", + fmt.Sprintf("Unable to store policy, got error: %s", err), + ) return } @@ -284,7 +316,10 @@ func (r *customQueryPackResource) Delete(ctx context.Context, req resource.Delet for _, policyMrn := range queryPackMrns { err := r.client.DeletePolicy(ctx, policyMrn) if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to delete query pack, got error: %s", err)) + resp.Diagnostics. + AddError("Client Error", + fmt.Sprintf("Unable to delete query pack, got error: %s", err), + ) return } } @@ -294,24 +329,42 @@ func (r *customQueryPackResource) ImportState(ctx context.Context, req resource. mrn := req.ID splitMrn := strings.Split(mrn, "/") spaceMrn := spacePrefix + splitMrn[len(splitMrn)-3] - spaceId := splitMrn[len(splitMrn)-3] + spaceID := splitMrn[len(splitMrn)-3] + + if r.client.Space().ID() != "" && r.client.Space().ID() != spaceID { + // The provider is configured to manage resources in a different space than the one the + // resource is currently configured, we won't allow that + resp.Diagnostics.AddError( + "Conflict Error", + fmt.Sprintf( + "Unable to import integration, the provider is configured in a different space than the resource. (%s != %s)", + r.client.Space().ID(), spaceID), + ) + return + } queryPack, err := r.client.GetPolicy(ctx, mrn, spaceMrn) if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to get policy, got error: %s", err)) + resp.Diagnostics. + AddError("Client Error", + fmt.Sprintf("Unable to get policy, got error: %s", err), + ) return } content, err := r.client.DownloadBundle(ctx, string(queryPack.Mrn)) if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to download bundle, got error: %s", err)) + resp.Diagnostics. + AddError("Client Error", + fmt.Sprintf("Unable to download bundle, got error: %s", err), + ) return } mrns, _ := types.ListValueFrom(ctx, types.StringType, []string{mrn}) model := customQueryPackResourceModel{ - SpaceId: types.StringValue(spaceId), + SpaceID: types.StringValue(spaceID), Mrns: mrns, Overwrite: types.BoolValue(false), Source: types.StringPointerValue(nil), diff --git a/internal/provider/framework_assignment_resource.go b/internal/provider/framework_assignment_resource.go index 6ef2b82..4832f9c 100644 --- a/internal/provider/framework_assignment_resource.go +++ b/internal/provider/framework_assignment_resource.go @@ -8,7 +8,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/types" - mondoov1 "go.mondoo.com/mondoo-go" + "github.com/hashicorp/terraform-plugin-log/tflog" ) var _ resource.Resource = (*frameworkAssignmentResource)(nil) @@ -23,24 +23,24 @@ type frameworkAssignmentResource struct { type frameworkAssignmentResourceModel struct { // scope - SpaceId types.String `tfsdk:"space_id"` + SpaceID types.String `tfsdk:"space_id"` // resource details FrameworkMrn types.List `tfsdk:"framework_mrn"` Enabled types.Bool `tfsdk:"enabled"` } -func (r *frameworkAssignmentResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { +func (r *frameworkAssignmentResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { resp.TypeName = req.ProviderTypeName + "_framework_assignment" } -func (r *frameworkAssignmentResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { +func (r *frameworkAssignmentResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ MarkdownDescription: `Set Compliance Frameworks for a Mondoo Space.`, Attributes: map[string]schema.Attribute{ "space_id": schema.StringAttribute{ - MarkdownDescription: "Mondoo Space Identifier.", - Required: true, + MarkdownDescription: "Mondoo Space Identifier. If it is not provided, the provider space is used.", + Optional: true, }, "framework_mrn": schema.ListAttribute{ MarkdownDescription: "Compliance Framework MRN.", @@ -55,13 +55,13 @@ func (r *frameworkAssignmentResource) Schema(ctx context.Context, req resource.S } } -func (r *frameworkAssignmentResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { +func (r *frameworkAssignmentResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { // Prevent panic if the provider has not been configured. if req.ProviderData == nil { return } - client, ok := req.ProviderData.(*mondoov1.Client) + client, ok := req.ProviderData.(*ExtendedGqlClient) if !ok { resp.Diagnostics.AddError( @@ -72,7 +72,7 @@ func (r *frameworkAssignmentResource) Configure(ctx context.Context, req resourc return } - r.client = &ExtendedGqlClient{client} + r.client = client } func (r *frameworkAssignmentResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { @@ -85,9 +85,26 @@ func (r *frameworkAssignmentResource) Create(ctx context.Context, req resource.C return } - err := r.client.BulkUpdateFramework(ctx, data.FrameworkMrn, data.SpaceId.ValueString(), data.Enabled.ValueBool()) + // Compute and validate the space + space, err := r.client.ComputeSpace(data.SpaceID) + if err != nil { + resp.Diagnostics.AddError("Invalid Configuration", err.Error()) + return + } + ctx = tflog.SetField(ctx, "space_mrn", space.MRN()) + + // Do GraphQL request to API to create the resource. + tflog.Debug(ctx, "Creating framework assignment") + err = r.client.BulkUpdateFramework(ctx, + data.FrameworkMrn, + space.ID(), + data.Enabled.ValueBool(), + ) if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to create Compliance Framework, got error: %s", err)) + resp.Diagnostics. + AddError("Client Error", + fmt.Sprintf("Unable to create Compliance Framework, got error: %s", err), + ) return } @@ -121,9 +138,36 @@ func (r *frameworkAssignmentResource) Update(ctx context.Context, req resource.U return } - err := r.client.BulkUpdateFramework(ctx, data.FrameworkMrn, data.SpaceId.ValueString(), data.Enabled.ValueBool()) + // Compute and validate the space + space, err := r.client.ComputeSpace(data.SpaceID) + if err != nil { + resp.Diagnostics.AddError("Invalid Configuration", err.Error()) + return + } + ctx = tflog.SetField(ctx, "space_mrn", space.MRN()) + + // ensure space id is not changed + var planSpaceID string + req.Plan.GetAttribute(ctx, path.Root("space_id"), &planSpaceID) + + if space.ID() != planSpaceID { + resp.Diagnostics.AddError( + "Space ID cannot be changed", + "Note that the Mondoo space can be configured at the resource or provider level.", + ) + return + } + + tflog.Debug(ctx, "Updating framework assignment") + err = r.client.BulkUpdateFramework(ctx, + data.FrameworkMrn, + planSpaceID, + data.Enabled.ValueBool()) if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to create Compliance Framework, got error: %s", err)) + resp.Diagnostics. + AddError("Client Error", + fmt.Sprintf("Unable to create Compliance Framework, got error: %s", err), + ) return } @@ -141,9 +185,15 @@ func (r *frameworkAssignmentResource) Delete(ctx context.Context, req resource.D return } - err := r.client.BulkUpdateFramework(ctx, data.FrameworkMrn, data.SpaceId.ValueString(), false) + err := r.client.BulkUpdateFramework(ctx, + data.FrameworkMrn, + data.SpaceID.ValueString(), + false) if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to create Compliance Framework, got error: %s", err)) + resp.Diagnostics. + AddError("Client Error", + fmt.Sprintf("Unable to create Compliance Framework, got error: %s", err), + ) return } } diff --git a/internal/provider/frameworks_data_source.go b/internal/provider/frameworks_data_source.go index 54a4c3b..f85500d 100644 --- a/internal/provider/frameworks_data_source.go +++ b/internal/provider/frameworks_data_source.go @@ -10,7 +10,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" - mondoov1 "go.mondoo.com/mondoo-go" ) var _ datasource.DataSource = (*frameworksDataSource)(nil) @@ -161,7 +160,7 @@ func (d *frameworksDataSource) Configure(ctx context.Context, req datasource.Con return } - client, ok := req.ProviderData.(*mondoov1.Client) + client, ok := req.ProviderData.(*ExtendedGqlClient) if !ok { resp.Diagnostics.AddError( @@ -172,7 +171,7 @@ func (d *frameworksDataSource) Configure(ctx context.Context, req datasource.Con return } - d.client = &ExtendedGqlClient{client} + d.client = client } func (d *frameworksDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { diff --git a/internal/provider/gql.go b/internal/provider/gql.go index 74c68bd..48cff17 100644 --- a/internal/provider/gql.go +++ b/internal/provider/gql.go @@ -6,15 +6,44 @@ package provider import ( "context" "encoding/base64" + "errors" "fmt" + "strings" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" "github.com/hashicorp/terraform-plugin-log/tflog" mondoov1 "go.mondoo.com/mondoo-go" ) +const orgPrefix = "//captain.api.mondoo.app/organizations/" + +// The extended GraphQL client allows us to pass additional information to +// resources and data sources, things like the Mondoo space. type ExtendedGqlClient struct { *mondoov1.Client + + // The default space configured at the provider level, if configured, all resources + // will be managed there unless the resource itself specifies a different space + space Space +} + +// Space returns the space configured into the extended GraphQL client. +func (c *ExtendedGqlClient) Space() Space { + return c.space +} + +// ComputeSpace receives an optional space ID, if it is empty, it tries to return the space +// configured into the exptended client, but if both are empty, it throws an error. +func (c *ExtendedGqlClient) ComputeSpace(spaceID types.String) (Space, error) { + if spaceID.ValueString() != "" { + return Space(spaceID.ValueString()), nil + } + if c.space != "" { + return c.space, nil + } + return c.space, errors.New("no space configured on either resource or provider blocks") } // newDataUrl generates a https://tools.ietf.org/html/rfc2397 data url for a given content. @@ -598,6 +627,18 @@ type Integration struct { ConfigurationOptions ClientIntegrationConfigurationOptions `graphql:"configurationOptions"` } +// SpaceID returns the space where the integration is configured (using the integration MRN). +func (i Integration) SpaceID() string { + // we are expecting MRNs like: + // => "//captain.api.mondoo.app/spaces/{ID}/integrations/{ID}" + mrnSplit := strings.Split(i.Mrn, "/") + l := len(mrnSplit) + if l >= 3 { // check for safety + return mrnSplit[l-3] + } + return "" +} + type ClientIntegration struct { Integration Integration } @@ -862,3 +903,32 @@ func (c *ExtendedGqlClient) DeleteFramework(ctx context.Context, mrn string) err // Execute the mutation return c.Mutate(ctx, &deleteMutation, input, nil) } + +// ImportIntegration is a generic way to import an integration, this function fetches the integration from +// the provided MRN and if it exists, it compares the space configured at the provider level (if any). +func (c *ExtendedGqlClient) ImportIntegration(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) (*Integration, bool) { + mrn := req.ID + integration, err := c.GetClientIntegration(ctx, mrn) + if err != nil { + resp.Diagnostics. + AddError("Client Error", + fmt.Sprintf("Unable to get integration, got error: %s", err), + ) + return nil, false + } + + spaceID := integration.SpaceID() + if c.Space().ID() != "" && c.Space().ID() != spaceID { + // The provider is configured to manage resources in a different space than the one the + // resource is currently configured, we won't allow that + resp.Diagnostics.AddError( + "Conflict Error", + fmt.Sprintf( + "Unable to import integration, the provider is configured in a different space than the resource. (%s != %s)", + c.Space().ID(), spaceID), + ) + return nil, false + } + + return &integration, true +} diff --git a/internal/provider/integration_aws_resource.go b/internal/provider/integration_aws_resource.go index 871201b..75861e6 100644 --- a/internal/provider/integration_aws_resource.go +++ b/internal/provider/integration_aws_resource.go @@ -7,7 +7,6 @@ import ( "context" "fmt" "regexp" - "strings" "github.com/hashicorp/terraform-plugin-framework-validators/objectvalidator" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" @@ -18,6 +17,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" mondoov1 "go.mondoo.com/mondoo-go" ) @@ -33,7 +33,7 @@ type integrationAwsResource struct { type integrationAwsResourceModel struct { // scope - SpaceId types.String `tfsdk:"space_id"` + SpaceID types.String `tfsdk:"space_id"` // integration details Mrn types.String `tfsdk:"mrn"` @@ -93,8 +93,8 @@ func (r *integrationAwsResource) Schema(ctx context.Context, req resource.Schema MarkdownDescription: `Continuously scan Google AWS organization and accounts for misconfigurations and vulnerabilities.`, Attributes: map[string]schema.Attribute{ "space_id": schema.StringAttribute{ - MarkdownDescription: "Mondoo Space Identifier.", - Required: true, + MarkdownDescription: "Mondoo Space Identifier. If it is not provided, the provider space is used.", + Optional: true, }, "mrn": schema.StringAttribute{ Computed: true, @@ -169,7 +169,7 @@ func (r *integrationAwsResource) Configure(ctx context.Context, req resource.Con return } - client, ok := req.ProviderData.(*mondoov1.Client) + client, ok := req.ProviderData.(*ExtendedGqlClient) if !ok { resp.Diagnostics.AddError( @@ -180,7 +180,7 @@ func (r *integrationAwsResource) Configure(ctx context.Context, req resource.Con return } - r.client = &ExtendedGqlClient{client} + r.client = client } func (r *integrationAwsResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { @@ -193,28 +193,35 @@ func (r *integrationAwsResource) Create(ctx context.Context, req resource.Create return } - // Do GraphQL request to API to create the resource. - spaceMrn := "" - if data.SpaceId.ValueString() != "" { - spaceMrn = spacePrefix + data.SpaceId.ValueString() + // Compute and validate the space + space, err := r.client.ComputeSpace(data.SpaceID) + if err != nil { + resp.Diagnostics.AddError("Invalid Configuration", err.Error()) + return } + ctx = tflog.SetField(ctx, "space_mrn", space.MRN()) + // Do GraphQL request to API to create the resource. + tflog.Debug(ctx, "Creating integration") integration, err := r.client.CreateIntegration(ctx, - spaceMrn, + space.MRN(), data.Name.ValueString(), mondoov1.ClientIntegrationTypeAwsHosted, mondoov1.ClientIntegrationConfigurationInput{ AwsHostedConfigurationOptions: data.GetConfigurationOptions(), }) if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to create AWS integration, got error: %s", err)) + resp.Diagnostics. + AddError("Client Error", + fmt.Sprintf("Unable to create AWS integration, got error: %s", err), + ) return } // Save space mrn into the Terraform state. data.Mrn = types.StringValue(string(integration.Mrn)) data.Name = types.StringValue(string(integration.Name)) - data.SpaceId = types.StringValue(data.SpaceId.ValueString()) + data.SpaceID = types.StringValue(space.ID()) // Save data into Terraform state resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) @@ -251,9 +258,17 @@ func (r *integrationAwsResource) Update(ctx context.Context, req resource.Update AwsHostedConfigurationOptions: data.GetConfigurationOptions(), } - _, err := r.client.UpdateIntegration(ctx, data.Mrn.ValueString(), data.Name.ValueString(), mondoov1.ClientIntegrationTypeAwsHosted, opts) + _, err := r.client.UpdateIntegration(ctx, + data.Mrn.ValueString(), + data.Name.ValueString(), + mondoov1.ClientIntegrationTypeAwsHosted, + opts, + ) if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to update AWS integration, got error: %s", err)) + resp.Diagnostics. + AddError("Client Error", + fmt.Sprintf("Unable to update AWS integration, got error: %s", err), + ) return } @@ -274,21 +289,22 @@ func (r *integrationAwsResource) Delete(ctx context.Context, req resource.Delete // Do GraphQL request to API to update the resource. _, err := r.client.DeleteIntegration(ctx, data.Mrn.ValueString()) if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to delete AWS integration, got error: %s", err)) + resp.Diagnostics. + AddError("Client Error", + fmt.Sprintf("Unable to delete AWS integration, got error: %s", err), + ) return } } func (r *integrationAwsResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { - mrn := req.ID - integration, err := r.client.GetClientIntegration(ctx, mrn) - if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to import AWS integration, got error: %s", err)) + integration, ok := r.client.ImportIntegration(ctx, req, resp) + if !ok { return } model := integrationAwsResourceModel{ - SpaceId: types.StringValue(strings.Split(integration.Mrn, "/")[len(strings.Split(integration.Mrn, "/"))-3]), + SpaceID: types.StringValue(integration.SpaceID()), Mrn: types.StringValue(integration.Mrn), Name: types.StringValue(integration.Name), Credential: integrationAwsCredentialModel{ diff --git a/internal/provider/integration_aws_serverless_resource.go b/internal/provider/integration_aws_serverless_resource.go index 1b1c9e9..2498c35 100644 --- a/internal/provider/integration_aws_serverless_resource.go +++ b/internal/provider/integration_aws_serverless_resource.go @@ -4,12 +4,12 @@ import ( "context" "fmt" - "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" mondoov1 "go.mondoo.com/mondoo-go" ) @@ -25,7 +25,7 @@ type integrationAwsServerlessResource struct { type integrationAwsServerlessResourceModel struct { // scope - SpaceId types.String `tfsdk:"space_id"` + SpaceID types.String `tfsdk:"space_id"` // integration details Mrn types.String `tfsdk:"mrn"` @@ -209,8 +209,8 @@ func (r *integrationAwsServerlessResource) Schema(ctx context.Context, req resou MarkdownDescription: `Continuously scan AWS organization and accounts for misconfigurations and vulnerabilities.`, Attributes: map[string]schema.Attribute{ "space_id": schema.StringAttribute{ - MarkdownDescription: "Mondoo Space Identifier.", - Required: true, + MarkdownDescription: "Mondoo Space Identifier. If it is not provided, the provider space is used.", + Optional: true, }, "mrn": schema.StringAttribute{ Computed: true, @@ -394,7 +394,7 @@ func (r *integrationAwsServerlessResource) Configure(ctx context.Context, req re return } - client, ok := req.ProviderData.(*mondoov1.Client) + client, ok := req.ProviderData.(*ExtendedGqlClient) if !ok { resp.Diagnostics.AddError( @@ -405,7 +405,7 @@ func (r *integrationAwsServerlessResource) Configure(ctx context.Context, req re return } - r.client = &ExtendedGqlClient{client} + r.client = client } func (r *integrationAwsServerlessResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { @@ -418,39 +418,49 @@ func (r *integrationAwsServerlessResource) Create(ctx context.Context, req resou return } - // Do GraphQL request to API to create the resource. - spaceMrn := "" - if data.SpaceId.ValueString() != "" { - spaceMrn = spacePrefix + data.SpaceId.ValueString() + // Compute and validate the space + space, err := r.client.ComputeSpace(data.SpaceID) + if err != nil { + resp.Diagnostics.AddError("Invalid Configuration", err.Error()) + return } + ctx = tflog.SetField(ctx, "space_mrn", space.MRN()) + // Do GraphQL request to API to create the resource. var accountIDs []mondoov1.String accountIds, _ := data.AccountIDs.ToListValue(context.Background()) accountIds.ElementsAs(context.Background(), &accountIDs, true) // Check if both whitelist and blacklist are provided if len(accountIDs) > 0 && data.IsOrganization.ValueBool() { - resp.Diagnostics.AddError("ConflictingAttributesError", "Cannot install CloudFormation Stack to both AWS organization and accounts.") + resp.Diagnostics. + AddError("ConflictingAttributesError", + "Cannot install CloudFormation Stack to both AWS organization and accounts.", + ) return } + tflog.Debug(ctx, "Creating integration") integration, err := r.client.CreateIntegration(ctx, - spaceMrn, + space.MRN(), data.Name.ValueString(), mondoov1.ClientIntegrationTypeAws, mondoov1.ClientIntegrationConfigurationInput{ AwsConfigurationOptions: data.GetConfigurationOptions(), }) if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to create AWS integration, got error: %s", err)) + resp.Diagnostics. + AddError("Client Error", + fmt.Sprintf("Unable to create AWS integration, got error: %s", err), + ) return } // Save space mrn into the Terraform state. data.Mrn = types.StringValue(string(integration.Mrn)) data.Name = types.StringValue(string(integration.Name)) - data.SpaceId = types.StringValue(data.SpaceId.ValueString()) data.Token = types.StringValue(string(integration.Token)) + data.SpaceID = types.StringValue(space.ID()) // Save data into Terraform state resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) @@ -484,7 +494,10 @@ func (r *integrationAwsServerlessResource) Update(ctx context.Context, req resou // Check if both whitelist and blacklist are provided if len(accountIDs) > 0 && data.IsOrganization.ValueBool() { - resp.Diagnostics.AddError("ConflictingAttributesError", "Cannot install CloudFormation Stack to both AWS organization and accounts.") + resp.Diagnostics. + AddError("ConflictingAttributesError", + "Cannot install CloudFormation Stack to both AWS organization and accounts.", + ) return } @@ -497,9 +510,17 @@ func (r *integrationAwsServerlessResource) Update(ctx context.Context, req resou AwsConfigurationOptions: data.GetConfigurationOptions(), } - _, err := r.client.UpdateIntegration(ctx, data.Mrn.ValueString(), data.Name.ValueString(), mondoov1.ClientIntegrationTypeAws, opts) + _, err := r.client.UpdateIntegration(ctx, + data.Mrn.ValueString(), + data.Name.ValueString(), + mondoov1.ClientIntegrationTypeAws, + opts, + ) if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to update AWS integration, got error: %s", err)) + resp.Diagnostics. + AddError("Client Error", + fmt.Sprintf("Unable to update AWS integration, got error: %s", err), + ) return } @@ -519,11 +540,28 @@ func (r *integrationAwsServerlessResource) Delete(ctx context.Context, req resou _, err := r.client.DeleteIntegration(ctx, data.Mrn.ValueString()) if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to delete AWS serverless integration '%s', got error: %s", data.Mrn.ValueString(), err.Error())) + resp.Diagnostics. + AddError("Client Error", + fmt.Sprintf( + "Unable to delete AWS serverless integration '%s', got error: %s", + data.Mrn.ValueString(), err.Error(), + ), + ) return } } func (r *integrationAwsServerlessResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { - resource.ImportStatePassthroughID(ctx, path.Root("mrn"), req, resp) + integration, ok := r.client.ImportIntegration(ctx, req, resp) + if !ok { + return + } + + model := integrationAwsServerlessResourceModel{ + Mrn: types.StringValue(integration.Mrn), + Name: types.StringValue(integration.Name), + SpaceID: types.StringValue(integration.SpaceID()), + } + + resp.State.Set(ctx, &model) } diff --git a/internal/provider/integration_azure_resource.go b/internal/provider/integration_azure_resource.go index 21fb403..eb4fdf3 100644 --- a/internal/provider/integration_azure_resource.go +++ b/internal/provider/integration_azure_resource.go @@ -3,7 +3,6 @@ package provider import ( "context" "fmt" - "strings" "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" @@ -14,6 +13,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" mondoov1 "go.mondoo.com/mondoo-go" ) @@ -29,7 +29,7 @@ type integrationAzureResource struct { type integrationAzureResourceModel struct { // scope - SpaceId types.String `tfsdk:"space_id"` + SpaceID types.String `tfsdk:"space_id"` // integration details Mrn types.String `tfsdk:"mrn"` @@ -57,8 +57,8 @@ func (r *integrationAzureResource) Schema(ctx context.Context, req resource.Sche MarkdownDescription: `Continuously scan Microsoft Azure subscriptions and resources for misconfigurations and vulnerabilities. See [Mondoo documentation](https://mondoo.com/docs/platform/infra/cloud/azure/azure-integration-scan-subscription/) for more details.`, Attributes: map[string]schema.Attribute{ "space_id": schema.StringAttribute{ - MarkdownDescription: "Mondoo Space Identifier.", - Required: true, + MarkdownDescription: "Mondoo Space Identifier. If it is not provided, the provider space is used.", + Optional: true, }, "mrn": schema.StringAttribute{ Computed: true, @@ -128,7 +128,7 @@ func (r *integrationAzureResource) Configure(ctx context.Context, req resource.C return } - client, ok := req.ProviderData.(*mondoov1.Client) + client, ok := req.ProviderData.(*ExtendedGqlClient) if !ok { resp.Diagnostics.AddError( @@ -139,7 +139,7 @@ func (r *integrationAzureResource) Configure(ctx context.Context, req resource.C return } - r.client = &ExtendedGqlClient{client} + r.client = client } func (r *integrationAzureResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { @@ -153,12 +153,14 @@ func (r *integrationAzureResource) Create(ctx context.Context, req resource.Crea return } - // Do GraphQL request to API to create the resource. - spaceMrn := "" - if data.SpaceId.ValueString() != "" { - spaceMrn = spacePrefix + data.SpaceId.ValueString() + space, err := r.client.ComputeSpace(data.SpaceID) + if err != nil { + resp.Diagnostics.AddError("Invalid Configuration", err.Error()) + return } + ctx = tflog.SetField(ctx, "space_mrn", space.MRN()) + // Do GraphQL request to API to create the resource. var listAllow []mondoov1.String allowlist, _ := data.SubscriptionAllowList.ToListValue(ctx) allowlist.ElementsAs(ctx, &listAllow, true) @@ -169,12 +171,16 @@ func (r *integrationAzureResource) Create(ctx context.Context, req resource.Crea // Check if both whitelist and blacklist are provided if len(listDeny) > 0 && len(listAllow) > 0 { - resp.Diagnostics.AddError("ConflictingAttributesError", "Both subscription_allow_list and subscription_deny_list cannot be provided simultaneously.") + resp.Diagnostics. + AddError("ConflictingAttributesError", + "Both subscription_allow_list and subscription_deny_list cannot be provided simultaneously.", + ) return } + tflog.Debug(ctx, "Creating integration") integration, err := r.client.CreateIntegration(ctx, - spaceMrn, + space.MRN(), data.Name.ValueString(), mondoov1.ClientIntegrationTypeAzure, mondoov1.ClientIntegrationConfigurationInput{ @@ -188,7 +194,10 @@ func (r *integrationAzureResource) Create(ctx context.Context, req resource.Crea }, }) if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to create Azure integration, got error: %s", err)) + resp.Diagnostics. + AddError("Client Error", + fmt.Sprintf("Unable to create Azure integration, got error: %s", err), + ) return } @@ -196,14 +205,17 @@ func (r *integrationAzureResource) Create(ctx context.Context, req resource.Crea // NOTE: we ignore the error since the integration state does not depend on it _, err = r.client.TriggerAction(ctx, string(integration.Mrn), mondoov1.ActionTypeRunScan) if err != nil { - resp.Diagnostics.AddWarning("Client Error", fmt.Sprintf("Unable to trigger integration, got error: %s", err)) + resp.Diagnostics. + AddWarning("Client Error", + fmt.Sprintf("Unable to trigger integration, got error: %s", err), + ) return } // Save space mrn into the Terraform state. data.Mrn = types.StringValue(string(integration.Mrn)) data.Name = types.StringValue(string(integration.Name)) - data.SpaceId = types.StringValue(data.SpaceId.ValueString()) + data.SpaceID = types.StringValue(space.ID()) // Save data into Terraform state resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) @@ -246,7 +258,10 @@ func (r *integrationAzureResource) Update(ctx context.Context, req resource.Upda // Check if both whitelist and blacklist are provided if len(listDeny) > 0 && len(listAllow) > 0 { - resp.Diagnostics.AddError("ConflictingAttributesError", "Both subscription_allow_list and subscription_deny_list cannot be provided simultaneously.") + resp.Diagnostics. + AddError("ConflictingAttributesError", + "Both subscription_allow_list and subscription_deny_list cannot be provided simultaneously.", + ) return } @@ -261,9 +276,17 @@ func (r *integrationAzureResource) Update(ctx context.Context, req resource.Upda }, } - _, err := r.client.UpdateIntegration(ctx, data.Mrn.ValueString(), data.Name.ValueString(), mondoov1.ClientIntegrationTypeAzure, opts) + _, err := r.client.UpdateIntegration(ctx, + data.Mrn.ValueString(), + data.Name.ValueString(), + mondoov1.ClientIntegrationTypeAzure, + opts, + ) if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to update Azure integration, got error: %s", err)) + resp.Diagnostics. + AddError("Client Error", + fmt.Sprintf("Unable to update Azure integration, got error: %s", err), + ) return } @@ -284,16 +307,17 @@ func (r *integrationAzureResource) Delete(ctx context.Context, req resource.Dele // Do GraphQL request to API to update the resource. _, err := r.client.DeleteIntegration(ctx, data.Mrn.ValueString()) if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to delete Azure integration, got error: %s", err)) + resp.Diagnostics. + AddError("Client Error", + fmt.Sprintf("Unable to delete Azure integration, got error: %s", err), + ) return } } func (r *integrationAzureResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { - mrn := req.ID - integration, err := r.client.GetClientIntegration(ctx, mrn) - if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to import Azure integration, got error: %s", err)) + integration, ok := r.client.ImportIntegration(ctx, req, resp) + if !ok { return } @@ -301,7 +325,7 @@ func (r *integrationAzureResource) ImportState(ctx context.Context, req resource denyList := ConvertListValue(integration.ConfigurationOptions.AzureConfigurationOptions.SubscriptionsBlacklist) model := integrationAzureResourceModel{ - SpaceId: types.StringValue(strings.Split(integration.Mrn, "/")[len(strings.Split(integration.Mrn, "/"))-3]), + SpaceID: types.StringValue(integration.SpaceID()), Mrn: types.StringValue(integration.Mrn), Name: types.StringValue(integration.Name), ClientId: types.StringValue(integration.ConfigurationOptions.AzureConfigurationOptions.ClientId), diff --git a/internal/provider/integration_domain_resource.go b/internal/provider/integration_domain_resource.go index 4c173bc..6ef53a3 100644 --- a/internal/provider/integration_domain_resource.go +++ b/internal/provider/integration_domain_resource.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "regexp" - "strings" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/resource" @@ -13,6 +12,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" mondoov1 "go.mondoo.com/mondoo-go" ) @@ -28,7 +28,7 @@ type integrationDomainResource struct { type integrationDomainResourceModel struct { // scope - SpaceId types.String `tfsdk:"space_id"` + SpaceID types.String `tfsdk:"space_id"` // integration details Mrn types.String `tfsdk:"mrn"` @@ -46,8 +46,8 @@ func (r *integrationDomainResource) Schema(ctx context.Context, req resource.Sch MarkdownDescription: `Continuously scan endpoints to evaluate domain TLS, SSL, HTTP, and HTTPS security`, Attributes: map[string]schema.Attribute{ "space_id": schema.StringAttribute{ - MarkdownDescription: "Mondoo Space Identifier.", - Required: true, + MarkdownDescription: "Mondoo Space Identifier. If it is not provided, the provider space is used.", + Optional: true, }, "mrn": schema.StringAttribute{ Computed: true, @@ -84,7 +84,7 @@ func (r *integrationDomainResource) Configure(ctx context.Context, req resource. return } - client, ok := req.ProviderData.(*mondoov1.Client) + client, ok := req.ProviderData.(*ExtendedGqlClient) if !ok { resp.Diagnostics.AddError( @@ -95,7 +95,7 @@ func (r *integrationDomainResource) Configure(ctx context.Context, req resource. return } - r.client = &ExtendedGqlClient{client} + r.client = client } func (r *integrationDomainResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { @@ -109,14 +109,18 @@ func (r *integrationDomainResource) Create(ctx context.Context, req resource.Cre return } - // Do GraphQL request to API to create the resource. - spaceMrn := "" - if data.SpaceId.ValueString() != "" { - spaceMrn = spacePrefix + data.SpaceId.ValueString() + // Compute and validate the space + space, err := r.client.ComputeSpace(data.SpaceID) + if err != nil { + resp.Diagnostics.AddError("Invalid Configuration", err.Error()) + return } + ctx = tflog.SetField(ctx, "space_mrn", space.MRN()) + // Do GraphQL request to API to create the resource. + tflog.Debug(ctx, "Creating integration") integration, err := r.client.CreateIntegration(ctx, - spaceMrn, + space.MRN(), data.Host.ValueString(), mondoov1.ClientIntegrationTypeHost, mondoov1.ClientIntegrationConfigurationInput{ @@ -127,7 +131,10 @@ func (r *integrationDomainResource) Create(ctx context.Context, req resource.Cre }, }) if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to create Domain integration, got error: %s", err)) + resp.Diagnostics. + AddError("Client Error", + fmt.Sprintf("Unable to create Domain integration, got error: %s", err), + ) return } @@ -135,14 +142,17 @@ func (r *integrationDomainResource) Create(ctx context.Context, req resource.Cre // NOTE: we ignore the error since the integration state does not depend on it _, err = r.client.TriggerAction(ctx, string(integration.Mrn), mondoov1.ActionTypeRunScan) if err != nil { - resp.Diagnostics.AddWarning("Client Error", fmt.Sprintf("Unable to trigger integration, got error: %s", err)) + resp.Diagnostics. + AddWarning("Client Error", + fmt.Sprintf("Unable to trigger integration, got error: %s", err), + ) return } // Save space mrn into the Terraform state. data.Mrn = types.StringValue(string(integration.Mrn)) data.Host = types.StringValue(data.Host.ValueString()) - data.SpaceId = types.StringValue(data.SpaceId.ValueString()) + data.SpaceID = types.StringValue(space.ID()) // Save data into Terraform state resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) @@ -183,9 +193,17 @@ func (r *integrationDomainResource) Update(ctx context.Context, req resource.Upd }, } - _, err := r.client.UpdateIntegration(ctx, data.Mrn.ValueString(), data.Host.ValueString(), mondoov1.ClientIntegrationTypeHost, opts) + _, err := r.client.UpdateIntegration(ctx, + data.Mrn.ValueString(), + data.Host.ValueString(), + mondoov1.ClientIntegrationTypeHost, + opts, + ) if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to update Domain integration, got error: %s", err)) + resp.Diagnostics. + AddError("Client Error", + fmt.Sprintf("Unable to update Domain integration, got error: %s", err), + ) return } @@ -206,21 +224,22 @@ func (r *integrationDomainResource) Delete(ctx context.Context, req resource.Del // Do GraphQL request to API to update the resource. _, err := r.client.DeleteIntegration(ctx, data.Mrn.ValueString()) if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to delete Domain integration, got error: %s", err)) + resp.Diagnostics. + AddError("Client Error", + fmt.Sprintf("Unable to delete Domain integration, got error: %s", err), + ) return } } func (r *integrationDomainResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { - mrn := req.ID - integration, err := r.client.GetClientIntegration(ctx, mrn) - if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to retrieve integration, got error: %s", err)) + integration, ok := r.client.ImportIntegration(ctx, req, resp) + if !ok { return } model := integrationDomainResourceModel{ - SpaceId: types.StringValue(strings.Split(integration.Mrn, "/")[len(strings.Split(integration.Mrn, "/"))-3]), + SpaceID: types.StringValue(integration.SpaceID()), Mrn: types.StringValue(integration.Mrn), Host: types.StringValue(integration.ConfigurationOptions.HostConfigurationOptions.Host), Https: types.BoolValue(integration.ConfigurationOptions.HostConfigurationOptions.HTTPS), diff --git a/internal/provider/integration_gcp_resource.go b/internal/provider/integration_gcp_resource.go index 8325ed8..10bae07 100644 --- a/internal/provider/integration_gcp_resource.go +++ b/internal/provider/integration_gcp_resource.go @@ -6,7 +6,6 @@ package provider import ( "context" "fmt" - "strings" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/resource" @@ -15,6 +14,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" mondoov1 "go.mondoo.com/mondoo-go" ) @@ -30,12 +30,12 @@ type integrationGcpResource struct { type integrationGcpResourceModel struct { // scope - SpaceId types.String `tfsdk:"space_id"` + SpaceID types.String `tfsdk:"space_id"` // integration details Mrn types.String `tfsdk:"mrn"` Name types.String `tfsdk:"name"` - ProjectId types.String `tfsdk:"project_id"` + ProjectID types.String `tfsdk:"project_id"` // credentials Credential integrationGcpCredentialModel `tfsdk:"credentials"` @@ -54,8 +54,8 @@ func (r *integrationGcpResource) Schema(ctx context.Context, req resource.Schema MarkdownDescription: `Continuously scan Google GCP organizations and projects for misconfigurations and vulnerabilities.`, Attributes: map[string]schema.Attribute{ "space_id": schema.StringAttribute{ - MarkdownDescription: "Mondoo Space Identifier.", - Required: true, + MarkdownDescription: "Mondoo Space Identifier. If it is not provided, the provider space is used.", + Optional: true, }, "mrn": schema.StringAttribute{ Computed: true, @@ -94,7 +94,7 @@ func (r *integrationGcpResource) Configure(ctx context.Context, req resource.Con return } - client, ok := req.ProviderData.(*mondoov1.Client) + client, ok := req.ProviderData.(*ExtendedGqlClient) if !ok { resp.Diagnostics.AddError( @@ -105,7 +105,7 @@ func (r *integrationGcpResource) Configure(ctx context.Context, req resource.Con return } - r.client = &ExtendedGqlClient{client} + r.client = client } func (r *integrationGcpResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { @@ -118,25 +118,32 @@ func (r *integrationGcpResource) Create(ctx context.Context, req resource.Create return } - // Do GraphQL request to API to create the resource. - spaceMrn := "" - if data.SpaceId.ValueString() != "" { - spaceMrn = spacePrefix + data.SpaceId.ValueString() + // Compute and validate the space + space, err := r.client.ComputeSpace(data.SpaceID) + if err != nil { + resp.Diagnostics.AddError("Invalid Configuration", err.Error()) + return } + ctx = tflog.SetField(ctx, "space_mrn", space.MRN()) + // Do GraphQL request to API to create the resource. + tflog.Debug(ctx, "Creating integration") integration, err := r.client.CreateIntegration(ctx, - spaceMrn, + space.MRN(), data.Name.ValueString(), mondoov1.ClientIntegrationTypeGcp, mondoov1.ClientIntegrationConfigurationInput{ GcpConfigurationOptions: &mondoov1.GcpConfigurationOptionsInput{ - ProjectID: mondoov1.NewStringPtr(mondoov1.String(data.ProjectId.ValueString())), + ProjectID: mondoov1.NewStringPtr(mondoov1.String(data.ProjectID.ValueString())), ServiceAccount: mondoov1.NewStringPtr(mondoov1.String(data.Credential.PrivateKey.ValueString())), DiscoverAll: mondoov1.NewBooleanPtr(mondoov1.Boolean(true)), }, }) if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to create GCP integration, got error: %s", err)) + resp.Diagnostics. + AddError("Client Error", + fmt.Sprintf("Unable to create GCP integration, got error: %s", err), + ) return } @@ -144,14 +151,17 @@ func (r *integrationGcpResource) Create(ctx context.Context, req resource.Create // NOTE: we ignore the error since the integration state does not depend on it _, err = r.client.TriggerAction(ctx, string(integration.Mrn), mondoov1.ActionTypeRunScan) if err != nil { - resp.Diagnostics.AddWarning("Client Error", fmt.Sprintf("Unable to trigger integration, got error: %s", err)) + resp.Diagnostics. + AddWarning("Client Error", + fmt.Sprintf("Unable to trigger integration, got error: %s", err), + ) return } // Save space mrn into the Terraform state. data.Mrn = types.StringValue(string(integration.Mrn)) data.Name = types.StringValue(string(integration.Name)) - data.SpaceId = types.StringValue(data.SpaceId.ValueString()) + data.SpaceID = types.StringValue(space.ID()) // Save data into Terraform state resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) @@ -186,15 +196,23 @@ func (r *integrationGcpResource) Update(ctx context.Context, req resource.Update // Do GraphQL request to API to update the resource. opts := mondoov1.ClientIntegrationConfigurationInput{ GcpConfigurationOptions: &mondoov1.GcpConfigurationOptionsInput{ - ProjectID: mondoov1.NewStringPtr(mondoov1.String(data.ProjectId.ValueString())), + ProjectID: mondoov1.NewStringPtr(mondoov1.String(data.ProjectID.ValueString())), ServiceAccount: mondoov1.NewStringPtr(mondoov1.String(data.Credential.PrivateKey.ValueString())), DiscoverAll: mondoov1.NewBooleanPtr(mondoov1.Boolean(true)), }, } - _, err := r.client.UpdateIntegration(ctx, data.Mrn.ValueString(), data.Name.ValueString(), mondoov1.ClientIntegrationTypeGcp, opts) + _, err := r.client.UpdateIntegration(ctx, + data.Mrn.ValueString(), + data.Name.ValueString(), + mondoov1.ClientIntegrationTypeGcp, + opts, + ) if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to update Gcp integration, got error: %s", err)) + resp.Diagnostics. + AddError("Client Error", + fmt.Sprintf("Unable to update Gcp integration, got error: %s", err), + ) return } @@ -215,24 +233,26 @@ func (r *integrationGcpResource) Delete(ctx context.Context, req resource.Delete // Do GraphQL request to API to update the resource. _, err := r.client.DeleteIntegration(ctx, data.Mrn.ValueString()) if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to delete Gcp integration, got error: %s", err)) + resp.Diagnostics. + AddError("Client Error", + fmt.Sprintf("Unable to delete Gcp integration, got error: %s", err), + ) return } } func (r *integrationGcpResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { - mrn := req.ID - integration, err := r.client.GetClientIntegration(ctx, mrn) - if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to get GCP integration, got error: %s", err)) + + integration, ok := r.client.ImportIntegration(ctx, req, resp) + if !ok { return } model := integrationGcpResourceModel{ Mrn: types.StringValue(integration.Mrn), Name: types.StringValue(integration.Name), - SpaceId: types.StringValue(strings.Split(integration.Mrn, "/")[len(strings.Split(integration.Mrn, "/"))-3]), - ProjectId: types.StringValue(integration.ConfigurationOptions.GcpConfigurationOptions.ProjectId), + SpaceID: types.StringValue(integration.SpaceID()), + ProjectID: types.StringValue(integration.ConfigurationOptions.GcpConfigurationOptions.ProjectId), Credential: integrationGcpCredentialModel{ PrivateKey: types.StringPointerValue(nil), }, diff --git a/internal/provider/integration_github_resource.go b/internal/provider/integration_github_resource.go index f304457..f7d0c35 100644 --- a/internal/provider/integration_github_resource.go +++ b/internal/provider/integration_github_resource.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "regexp" - "strings" "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" @@ -15,6 +14,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" mondoov1 "go.mondoo.com/mondoo-go" ) @@ -30,7 +30,7 @@ type integrationGithubResource struct { type integrationGithubResourceModel struct { // scope - SpaceId types.String `tfsdk:"space_id"` + SpaceID types.String `tfsdk:"space_id"` // integration details Mrn types.String `tfsdk:"mrn"` @@ -93,8 +93,8 @@ func (r *integrationGithubResource) Schema(ctx context.Context, req resource.Sch MarkdownDescription: `Continuously scan GitHub organizations and repositories for misconfigurations.`, Attributes: map[string]schema.Attribute{ "space_id": schema.StringAttribute{ - MarkdownDescription: "Mondoo Space Identifier.", - Required: true, + MarkdownDescription: "Mondoo Space Identifier. If it is not provided, the provider space is used.", + Optional: true, }, "mrn": schema.StringAttribute{ Computed: true, @@ -166,7 +166,7 @@ func (r *integrationGithubResource) Configure(ctx context.Context, req resource. return } - client, ok := req.ProviderData.(*mondoov1.Client) + client, ok := req.ProviderData.(*ExtendedGqlClient) if !ok { resp.Diagnostics.AddError( @@ -177,7 +177,7 @@ func (r *integrationGithubResource) Configure(ctx context.Context, req resource. return } - r.client = &ExtendedGqlClient{client} + r.client = client } func (r *integrationGithubResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { @@ -190,22 +190,28 @@ func (r *integrationGithubResource) Create(ctx context.Context, req resource.Cre if resp.Diagnostics.HasError() { return } - - // Do GraphQL request to API to create the resource. - spaceMrn := "" - if data.SpaceId.ValueString() != "" { - spaceMrn = spacePrefix + data.SpaceId.ValueString() + // Compute and validate the space + space, err := r.client.ComputeSpace(data.SpaceID) + if err != nil { + resp.Diagnostics.AddError("Invalid Configuration", err.Error()) + return } + ctx = tflog.SetField(ctx, "space_mrn", space.MRN()) + // Do GraphQL request to API to create the resource. + tflog.Debug(ctx, "Creating integration") integration, err := r.client.CreateIntegration(ctx, - spaceMrn, + space.MRN(), data.Name.ValueString(), mondoov1.ClientIntegrationTypeGitHub, mondoov1.ClientIntegrationConfigurationInput{ GitHubConfigurationOptions: data.GetConfigurationOptions(), }) if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to create Domain integration, got error: %s", err)) + resp.Diagnostics. + AddError("Client Error", + fmt.Sprintf("Unable to create Domain integration, got error: %s", err), + ) return } @@ -213,14 +219,17 @@ func (r *integrationGithubResource) Create(ctx context.Context, req resource.Cre // NOTE: we ignore the error since the integration state does not depend on it _, err = r.client.TriggerAction(ctx, string(integration.Mrn), mondoov1.ActionTypeRunScan) if err != nil { - resp.Diagnostics.AddWarning("Client Error", fmt.Sprintf("Unable to trigger integration, got error: %s", err)) + resp.Diagnostics. + AddWarning("Client Error", + fmt.Sprintf("Unable to trigger integration, got error: %s", err), + ) return } // Save space mrn into the Terraform state. data.Mrn = types.StringValue(string(integration.Mrn)) data.Name = types.StringValue(data.Name.ValueString()) - data.SpaceId = types.StringValue(data.SpaceId.ValueString()) + data.SpaceID = types.StringValue(space.ID()) // Save data into Terraform state resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) @@ -257,9 +266,17 @@ func (r *integrationGithubResource) Update(ctx context.Context, req resource.Upd GitHubConfigurationOptions: data.GetConfigurationOptions(), } - _, err := r.client.UpdateIntegration(ctx, data.Mrn.ValueString(), data.Name.ValueString(), mondoov1.ClientIntegrationTypeGitHub, opts) + _, err := r.client.UpdateIntegration(ctx, + data.Mrn.ValueString(), + data.Name.ValueString(), + mondoov1.ClientIntegrationTypeGitHub, + opts, + ) if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to update Domain integration, got error: %s", err)) + resp.Diagnostics. + AddError("Client Error", + fmt.Sprintf("Unable to update Domain integration, got error: %s", err), + ) return } @@ -280,16 +297,17 @@ func (r *integrationGithubResource) Delete(ctx context.Context, req resource.Del // Do GraphQL request to API to update the resource. _, err := r.client.DeleteIntegration(ctx, data.Mrn.ValueString()) if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to delete Domain integration, got error: %s", err)) + resp.Diagnostics. + AddError("Client Error", + fmt.Sprintf("Unable to delete Domain integration, got error: %s", err), + ) return } } func (r *integrationGithubResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { - mrn := req.ID - integration, err := r.client.GetClientIntegration(ctx, mrn) - if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to get GitHub integration, got error: %s", err)) + integration, ok := r.client.ImportIntegration(ctx, req, resp) + if !ok { return } @@ -297,9 +315,9 @@ func (r *integrationGithubResource) ImportState(ctx context.Context, req resourc denyList := ConvertListValue(integration.ConfigurationOptions.GithubConfigurationOptions.ReposDenyList) model := integrationGithubResourceModel{ - Mrn: types.StringValue(mrn), + Mrn: types.StringValue(integration.Mrn), Name: types.StringValue(integration.Name), - SpaceId: types.StringValue(strings.Split(integration.Mrn, "/")[len(strings.Split(integration.Mrn, "/"))-3]), + SpaceID: types.StringValue(integration.SpaceID()), Owner: types.StringValue(integration.ConfigurationOptions.GithubConfigurationOptions.Owner), Repository: types.StringValue(integration.ConfigurationOptions.GithubConfigurationOptions.Repository), RepositoryAllowList: allowList, diff --git a/internal/provider/integration_ms365_resource.go b/internal/provider/integration_ms365_resource.go index cdec3b5..bc6146f 100644 --- a/internal/provider/integration_ms365_resource.go +++ b/internal/provider/integration_ms365_resource.go @@ -12,6 +12,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" mondoov1 "go.mondoo.com/mondoo-go" ) @@ -27,7 +28,7 @@ type integrationMs365Resource struct { type integrationMs365ResourceModel struct { // scope - SpaceId types.String `tfsdk:"space_id"` + SpaceID types.String `tfsdk:"space_id"` // integration details Mrn types.String `tfsdk:"mrn"` @@ -52,8 +53,8 @@ func (r *integrationMs365Resource) Schema(ctx context.Context, req resource.Sche MarkdownDescription: `Continuously monitor your Microsoft 365 resources for misconfigurations and vulnerabilities. See [Mondoo documentation](https://mondoo.com/docs/platform/infra/saas/ms365/ms365-auto/) for more details.`, Attributes: map[string]schema.Attribute{ "space_id": schema.StringAttribute{ - MarkdownDescription: "Mondoo Space Identifier.", - Required: true, + MarkdownDescription: "Mondoo Space Identifier. If it is not provided, the provider space is used.", + Optional: true, }, "mrn": schema.StringAttribute{ Computed: true, @@ -97,7 +98,7 @@ func (r *integrationMs365Resource) Configure(ctx context.Context, req resource.C return } - client, ok := req.ProviderData.(*mondoov1.Client) + client, ok := req.ProviderData.(*ExtendedGqlClient) if !ok { resp.Diagnostics.AddError( @@ -108,7 +109,7 @@ func (r *integrationMs365Resource) Configure(ctx context.Context, req resource.C return } - r.client = &ExtendedGqlClient{client} + r.client = client } func (r *integrationMs365Resource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { @@ -121,14 +122,18 @@ func (r *integrationMs365Resource) Create(ctx context.Context, req resource.Crea return } - // Do GraphQL request to API to create the resource. - spaceMrn := "" - if data.SpaceId.ValueString() != "" { - spaceMrn = spacePrefix + data.SpaceId.ValueString() + // Compute and validate the space + space, err := r.client.ComputeSpace(data.SpaceID) + if err != nil { + resp.Diagnostics.AddError("Invalid Configuration", err.Error()) + return } + ctx = tflog.SetField(ctx, "space_mrn", space.MRN()) + // Do GraphQL request to API to create the resource. + tflog.Debug(ctx, "Creating integration") integration, err := r.client.CreateIntegration(ctx, - spaceMrn, + space.MRN(), data.Name.ValueString(), mondoov1.ClientIntegrationTypeMs365, mondoov1.ClientIntegrationConfigurationInput{ @@ -139,7 +144,10 @@ func (r *integrationMs365Resource) Create(ctx context.Context, req resource.Crea }, }) if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to create MS365 integration, got error: %s", err)) + resp.Diagnostics. + AddError("Client Error", + fmt.Sprintf("Unable to create MS365 integration, got error: %s", err), + ) return } @@ -147,14 +155,17 @@ func (r *integrationMs365Resource) Create(ctx context.Context, req resource.Crea // NOTE: we ignore the error since the integration state does not depend on it _, err = r.client.TriggerAction(ctx, string(integration.Mrn), mondoov1.ActionTypeRunScan) if err != nil { - resp.Diagnostics.AddWarning("Client Error", fmt.Sprintf("Unable to trigger integration, got error: %s", err)) + resp.Diagnostics. + AddWarning("Client Error", + fmt.Sprintf("Unable to trigger integration, got error: %s", err), + ) return } // Save space mrn into the Terraform state. data.Mrn = types.StringValue(string(integration.Mrn)) data.Name = types.StringValue(string(integration.Name)) - data.SpaceId = types.StringValue(data.SpaceId.ValueString()) + data.SpaceID = types.StringValue(space.ID()) // Save data into Terraform state resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) @@ -195,9 +206,17 @@ func (r *integrationMs365Resource) Update(ctx context.Context, req resource.Upda }, } - _, err := r.client.UpdateIntegration(ctx, data.Mrn.ValueString(), data.Name.ValueString(), mondoov1.ClientIntegrationTypeMs365, opts) + _, err := r.client.UpdateIntegration(ctx, + data.Mrn.ValueString(), + data.Name.ValueString(), + mondoov1.ClientIntegrationTypeMs365, + opts, + ) if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to update Ms365 integration, got error: %s", err)) + resp.Diagnostics. + AddError("Client Error", + fmt.Sprintf("Unable to update Ms365 integration, got error: %s", err), + ) return } @@ -218,7 +237,10 @@ func (r *integrationMs365Resource) Delete(ctx context.Context, req resource.Dele // Do GraphQL request to API to update the resource. _, err := r.client.DeleteIntegration(ctx, data.Mrn.ValueString()) if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to delete Ms365 integration, got error: %s", err)) + resp.Diagnostics. + AddError("Client Error", + fmt.Sprintf("Unable to delete Ms365 integration, got error: %s", err), + ) return } } @@ -227,14 +249,30 @@ func (r *integrationMs365Resource) ImportState(ctx context.Context, req resource mrn := req.ID integration, err := r.client.GetClientIntegration(ctx, mrn) if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to get Ms365 integration, got error: %s", err)) + resp.Diagnostics. + AddError("Client Error", + fmt.Sprintf("Unable to get integration, got error: %s", err), + ) + return + } + + spaceID := strings.Split(integration.Mrn, "/")[len(strings.Split(integration.Mrn, "/"))-3] + if r.client.Space().ID() != "" && r.client.Space().ID() != spaceID { + // The provider is configured to manage resources in a different space than the one the + // resource is currently configured, we won't allow that + resp.Diagnostics.AddError( + "Conflict Error", + fmt.Sprintf( + "Unable to import integration, the provider is configured in a different space than the resource. (%s != %s)", + r.client.Space().ID(), spaceID), + ) return } model := integrationMs365ResourceModel{ Mrn: types.StringValue(integration.Mrn), Name: types.StringValue(integration.Name), - SpaceId: types.StringValue(strings.Split(integration.Mrn, "/")[len(strings.Split(integration.Mrn, "/"))-3]), + SpaceID: types.StringValue(spaceID), TenantId: types.StringValue(integration.ConfigurationOptions.Ms365ConfigurationOptions.TenantId), ClientId: types.StringValue(integration.ConfigurationOptions.Ms365ConfigurationOptions.ClientId), Credential: integrationMs365CredentialModel{ diff --git a/internal/provider/integration_oci_tenant.go b/internal/provider/integration_oci_tenant.go index ae40ba1..8f64758 100644 --- a/internal/provider/integration_oci_tenant.go +++ b/internal/provider/integration_oci_tenant.go @@ -6,9 +6,9 @@ package provider import ( "context" "fmt" + "strings" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" - "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" @@ -35,7 +35,7 @@ type integrationOciTenantResource struct { // integrationOciTenantResourceModel describes the resource data model. type integrationOciTenantResourceModel struct { // scope - SpaceId types.String `tfsdk:"space_id"` + SpaceID types.String `tfsdk:"space_id"` // integration details Mrn types.String `tfsdk:"mrn"` @@ -64,8 +64,8 @@ func (r *integrationOciTenantResource) Schema(ctx context.Context, req resource. Attributes: map[string]schema.Attribute{ "space_id": schema.StringAttribute{ - MarkdownDescription: "Mondoo Space Identifier.", - Required: true, + MarkdownDescription: "Mondoo Space Identifier. If it is not provided, the provider space is used.", + Optional: true, }, "mrn": schema.StringAttribute{ Computed: true, @@ -115,7 +115,7 @@ func (r *integrationOciTenantResource) Configure(ctx context.Context, req resour return } - client, ok := req.ProviderData.(*mondoov1.Client) + client, ok := req.ProviderData.(*ExtendedGqlClient) if !ok { resp.Diagnostics.AddError( @@ -126,7 +126,7 @@ func (r *integrationOciTenantResource) Configure(ctx context.Context, req resour return } - r.client = &ExtendedGqlClient{client} + r.client = client } func (r *integrationOciTenantResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { @@ -139,14 +139,18 @@ func (r *integrationOciTenantResource) Create(ctx context.Context, req resource. return } - // Do GraphQL request to API to create the resource. - spaceMrn := "" - if data.SpaceId.ValueString() != "" { - spaceMrn = spacePrefix + data.SpaceId.ValueString() + // Compute and validate the space + space, err := r.client.ComputeSpace(data.SpaceID) + if err != nil { + resp.Diagnostics.AddError("Invalid Configuration", err.Error()) + return } + ctx = tflog.SetField(ctx, "space_mrn", space.MRN()) + // Do GraphQL request to API to create the resource. + tflog.Debug(ctx, "Creating integration") integration, err := r.client.CreateIntegration(ctx, - spaceMrn, + space.MRN(), data.Name.ValueString(), mondoov1.ClientIntegrationTypeOci, mondoov1.ClientIntegrationConfigurationInput{ @@ -159,7 +163,10 @@ func (r *integrationOciTenantResource) Create(ctx context.Context, req resource. }, }) if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to create OCI tenant integration, got error: %s", err)) + resp.Diagnostics. + AddError("Client Error", + fmt.Sprintf("Unable to create OCI tenant integration, got error: %s", err), + ) return } @@ -167,14 +174,17 @@ func (r *integrationOciTenantResource) Create(ctx context.Context, req resource. // NOTE: we ignore the error since the integration state does not depend on it _, err = r.client.TriggerAction(ctx, string(integration.Mrn), mondoov1.ActionTypeRunScan) if err != nil { - resp.Diagnostics.AddWarning("Client Error", fmt.Sprintf("Unable to trigger integration, got error: %s", err)) + resp.Diagnostics. + AddWarning("Client Error", + fmt.Sprintf("Unable to trigger integration, got error: %s", err), + ) return } // Save space mrn into the Terraform state. data.Mrn = types.StringValue(string(integration.Mrn)) data.Name = types.StringValue(string(integration.Name)) - data.SpaceId = types.StringValue(data.SpaceId.ValueString()) + data.SpaceID = types.StringValue(space.ID()) // Save data into Terraform state resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) @@ -218,9 +228,17 @@ func (r *integrationOciTenantResource) Update(ctx context.Context, req resource. } // Do GraphQL request to API to update the resource. - _, err := r.client.UpdateIntegration(ctx, data.Mrn.ValueString(), data.Name.ValueString(), mondoov1.ClientIntegrationTypeOci, opts) + _, err := r.client.UpdateIntegration(ctx, + data.Mrn.ValueString(), + data.Name.ValueString(), + mondoov1.ClientIntegrationTypeOci, + opts, + ) if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to update OCI tenant integration, got error: %s", err)) + resp.Diagnostics. + AddError("Client Error", + fmt.Sprintf("Unable to update OCI tenant integration, got error: %s", err), + ) return } @@ -241,11 +259,43 @@ func (r *integrationOciTenantResource) Delete(ctx context.Context, req resource. // Do GraphQL request to API to update the resource. _, err := r.client.DeleteIntegration(ctx, data.Mrn.ValueString()) if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to delete Oci tenant integration, got error: %s", err)) + resp.Diagnostics. + AddError("Client Error", + fmt.Sprintf("Unable to delete Oci tenant integration, got error: %s", err), + ) return } } func (r *integrationOciTenantResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { - resource.ImportStatePassthroughID(ctx, path.Root("mrn"), req, resp) + mrn := req.ID + integration, err := r.client.GetClientIntegration(ctx, mrn) + if err != nil { + resp.Diagnostics. + AddError("Client Error", + fmt.Sprintf("Unable to get integration, got error: %s", err), + ) + return + } + + spaceID := strings.Split(integration.Mrn, "/")[len(strings.Split(integration.Mrn, "/"))-3] + if r.client.Space().ID() != "" && r.client.Space().ID() != spaceID { + // The provider is configured to manage resources in a different space than the one the + // resource is currently configured, we won't allow that + resp.Diagnostics.AddError( + "Conflict Error", + fmt.Sprintf( + "Unable to import integration, the provider is configured in a different space than the resource. (%s != %s)", + r.client.Space().ID(), spaceID), + ) + return + } + + model := integrationOciTenantResourceModel{ + Mrn: types.StringValue(integration.Mrn), + Name: types.StringValue(integration.Name), + SpaceID: types.StringValue(spaceID), + } + + resp.State.Set(ctx, &model) } diff --git a/internal/provider/integration_shodan_resource.go b/internal/provider/integration_shodan_resource.go index 58c129e..0511b37 100644 --- a/internal/provider/integration_shodan_resource.go +++ b/internal/provider/integration_shodan_resource.go @@ -15,6 +15,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" mondoov1 "go.mondoo.com/mondoo-go" ) @@ -30,7 +31,7 @@ type integrationShodanResource struct { type integrationShodanResourceModel struct { // scope - SpaceId types.String `tfsdk:"space_id"` + SpaceID types.String `tfsdk:"space_id"` // integration details Mrn types.String `tfsdk:"mrn"` @@ -47,17 +48,18 @@ type integrationShodanCredentialModel struct { Token types.String `tfsdk:"token"` } -func (r *integrationShodanResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { +func (r *integrationShodanResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { resp.TypeName = req.ProviderTypeName + "_integration_shodan" } -func (r *integrationShodanResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { +func (r *integrationShodanResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ - MarkdownDescription: `Continuously scan Internet-connected devices with Shodan.`, + MarkdownDescription: `Continuously assess external risk for domains and IP addresses.`, Attributes: map[string]schema.Attribute{ "space_id": schema.StringAttribute{ - MarkdownDescription: "Mondoo Space Identifier.", - Required: true, + MarkdownDescription: "Mondoo Space Identifier. If it is not provided, the provider space is used.", + Optional: true, + Computed: true, }, "mrn": schema.StringAttribute{ Computed: true, @@ -101,7 +103,7 @@ func (r *integrationShodanResource) Configure(ctx context.Context, req resource. return } - client, ok := req.ProviderData.(*mondoov1.Client) + client, ok := req.ProviderData.(*ExtendedGqlClient) if !ok { resp. @@ -115,7 +117,7 @@ func (r *integrationShodanResource) Configure(ctx context.Context, req resource. return } - r.client = &ExtendedGqlClient{client} + r.client = client } func (r *integrationShodanResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { @@ -129,15 +131,21 @@ func (r *integrationShodanResource) Create(ctx context.Context, req resource.Cre return } - // Do GraphQL request to API to create the resource. - spaceMrn := "" - if data.SpaceId.ValueString() != "" { - spaceMrn = spacePrefix + data.SpaceId.ValueString() + // Compute and validate the space + space, err := r.client.ComputeSpace(data.SpaceID) + if err != nil { + resp.Diagnostics.AddError("Invalid Configuration", err.Error()) + return } + ctx = tflog.SetField(ctx, "space_mrn", space.MRN()) + // Do GraphQL request to API to create the resource. targets := ConvertSliceStrings(data.Targets) + ctx = tflog.SetField(ctx, "targets", targets) + + tflog.Debug(ctx, "Creating integration") integration, err := r.client.CreateIntegration(ctx, - spaceMrn, + space.MRN(), data.Name.ValueString(), mondoov1.ClientIntegrationTypeShodan, mondoov1.ClientIntegrationConfigurationInput{ @@ -151,7 +159,7 @@ func (r *integrationShodanResource) Create(ctx context.Context, req resource.Cre Diagnostics. AddError("Client Error", fmt.Sprintf( - "Unable to create Domain integration, got error: %s", err, + "Unable to create integration, got error: %s", err, ), ) return @@ -176,7 +184,7 @@ func (r *integrationShodanResource) Create(ctx context.Context, req resource.Cre // Save space mrn into the Terraform state. data.Mrn = types.StringValue(string(integration.Mrn)) data.Name = types.StringValue(data.Name.ValueString()) - data.SpaceId = types.StringValue(data.SpaceId.ValueString()) + data.SpaceID = types.StringValue(space.ID()) // Save data into Terraform state resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) @@ -266,14 +274,30 @@ func (r *integrationShodanResource) ImportState(ctx context.Context, req resourc mrn := req.ID integration, err := r.client.GetClientIntegration(ctx, mrn) if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to get Shodan integration, got error: %s", err)) + resp.Diagnostics. + AddError("Client Error", + fmt.Sprintf("Unable to get integration, got error: %s", err), + ) + return + } + + spaceID := strings.Split(integration.Mrn, "/")[len(strings.Split(integration.Mrn, "/"))-3] + if r.client.Space().ID() != "" && r.client.Space().ID() != spaceID { + // The provider is configured to manage resources in a different space than the one the + // resource is currently configured, we won't allow that + resp.Diagnostics.AddError( + "Conflict Error", + fmt.Sprintf( + "Unable to import integration, the provider is configured in a different space than the resource. (%s != %s)", + r.client.Space().ID(), spaceID), + ) return } model := integrationShodanResourceModel{ Mrn: types.StringValue(mrn), Name: types.StringValue(integration.Name), - SpaceId: types.StringValue(strings.Split(integration.Mrn, "/")[len(strings.Split(integration.Mrn, "/"))-3]), + SpaceID: types.StringValue(spaceID), Targets: ConvertListValue( integration.ConfigurationOptions.ShodanConfigurationOptions.Targets, ), diff --git a/internal/provider/integration_slack_resource.go b/internal/provider/integration_slack_resource.go index 59c13bd..f1561e5 100644 --- a/internal/provider/integration_slack_resource.go +++ b/internal/provider/integration_slack_resource.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "regexp" - "strings" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/resource" @@ -13,6 +12,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" mondoov1 "go.mondoo.com/mondoo-go" ) @@ -28,7 +28,7 @@ type integrationSlackResource struct { type integrationSlackResourceModel struct { // scope - SpaceId types.String `tfsdk:"space_id"` + SpaceID types.String `tfsdk:"space_id"` // integration details Mrn types.String `tfsdk:"mrn"` @@ -47,8 +47,8 @@ func (r *integrationSlackResource) Schema(ctx context.Context, req resource.Sche MarkdownDescription: "Continuously scan your Slack Teams for security misconfigurations.", Attributes: map[string]schema.Attribute{ "space_id": schema.StringAttribute{ - MarkdownDescription: "Mondoo Space Identifier.", - Required: true, + MarkdownDescription: "Mondoo Space Identifier. If it is not provided, the provider space is used.", + Optional: true, }, "mrn": schema.StringAttribute{ Computed: true, @@ -85,7 +85,7 @@ func (r *integrationSlackResource) Configure(ctx context.Context, req resource.C return } - client, ok := req.ProviderData.(*mondoov1.Client) + client, ok := req.ProviderData.(*ExtendedGqlClient) if !ok { resp.Diagnostics.AddError( @@ -96,7 +96,7 @@ func (r *integrationSlackResource) Configure(ctx context.Context, req resource.C return } - r.client = &ExtendedGqlClient{client} + r.client = client } func (r *integrationSlackResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { @@ -109,14 +109,18 @@ func (r *integrationSlackResource) Create(ctx context.Context, req resource.Crea return } - // Do GraphQL request to API to create the resource. - spaceMrn := "" - if data.SpaceId.ValueString() != "" { - spaceMrn = spacePrefix + data.SpaceId.ValueString() + // Compute and validate the space + space, err := r.client.ComputeSpace(data.SpaceID) + if err != nil { + resp.Diagnostics.AddError("Invalid Configuration", err.Error()) + return } + ctx = tflog.SetField(ctx, "space_mrn", space.MRN()) + // Do GraphQL request to API to create the resource. + tflog.Debug(ctx, "Creating integration") integration, err := r.client.CreateIntegration(ctx, - spaceMrn, + space.MRN(), data.Name.ValueString(), mondoov1.ClientIntegrationTypeHostedSlack, mondoov1.ClientIntegrationConfigurationInput{ @@ -125,7 +129,10 @@ func (r *integrationSlackResource) Create(ctx context.Context, req resource.Crea }, }) if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to create Slack integration, got error: %s", err)) + resp.Diagnostics. + AddError("Client Error", + fmt.Sprintf("Unable to create Slack integration, got error: %s", err), + ) return } @@ -133,14 +140,17 @@ func (r *integrationSlackResource) Create(ctx context.Context, req resource.Crea // NOTE: we ignore the error since the integration state does not depend on it _, err = r.client.TriggerAction(ctx, string(integration.Mrn), mondoov1.ActionTypeRunScan) if err != nil { - resp.Diagnostics.AddWarning("Client Error", fmt.Sprintf("Unable to trigger integration, got error: %s", err)) + resp.Diagnostics. + AddWarning("Client Error", + fmt.Sprintf("Unable to trigger integration, got error: %s", err), + ) return } // Save space mrn into the Terraform state. data.Mrn = types.StringValue(string(integration.Mrn)) data.Name = types.StringValue(string(integration.Name)) - data.SpaceId = types.StringValue(data.SpaceId.ValueString()) + data.SpaceID = types.StringValue(space.ID()) // Save data into Terraform state resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) @@ -180,9 +190,17 @@ func (r *integrationSlackResource) Update(ctx context.Context, req resource.Upda } // Do GraphQL request to API to update the resource. - _, err := r.client.UpdateIntegration(ctx, data.Mrn.ValueString(), data.Name.ValueString(), mondoov1.ClientIntegrationTypeHostedSlack, opts) + _, err := r.client.UpdateIntegration(ctx, + data.Mrn.ValueString(), + data.Name.ValueString(), + mondoov1.ClientIntegrationTypeHostedSlack, + opts, + ) if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to update OCI tenant integration, got error: %s", err)) + resp.Diagnostics. + AddError("Client Error", + fmt.Sprintf("Unable to update OCI tenant integration, got error: %s", err), + ) return } @@ -203,24 +221,25 @@ func (r *integrationSlackResource) Delete(ctx context.Context, req resource.Dele // Do GraphQL request to API to update the resource. _, err := r.client.DeleteIntegration(ctx, data.Mrn.ValueString()) if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to delete Slack integration, got error: %s", err)) + resp.Diagnostics. + AddError("Client Error", + fmt.Sprintf("Unable to delete Slack integration, got error: %s", err), + ) return } } func (r *integrationSlackResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { - mrn := req.ID - integration, err := r.client.GetClientIntegration(ctx, mrn) - if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to get Slack integration, got error: %s", err)) + integration, ok := r.client.ImportIntegration(ctx, req, resp) + if !ok { return } model := integrationSlackResourceModel{ Mrn: types.StringValue(integration.Mrn), Name: types.StringValue(integration.Name), + SpaceID: types.StringValue(integration.SpaceID()), SlackToken: types.StringPointerValue(nil), - SpaceId: types.StringValue(strings.Split(integration.Mrn, "/")[len(strings.Split(integration.Mrn, "/"))-3]), } resp.State.Set(ctx, &model) diff --git a/internal/provider/organization_data_source.go b/internal/provider/organization_data_source.go index ff691c8..33c66e2 100644 --- a/internal/provider/organization_data_source.go +++ b/internal/provider/organization_data_source.go @@ -13,7 +13,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" - mondoov1 "go.mondoo.com/mondoo-go" ) // Ensure provider defined types fully satisfy framework interfaces. @@ -80,7 +79,7 @@ func (d *OrganizationDataSource) Configure(ctx context.Context, req datasource.C return } - client, ok := req.ProviderData.(*mondoov1.Client) + client, ok := req.ProviderData.(*ExtendedGqlClient) if !ok { resp.Diagnostics.AddError( @@ -91,7 +90,7 @@ func (d *OrganizationDataSource) Configure(ctx context.Context, req datasource.C return } - d.client = &ExtendedGqlClient{client} + d.client = client } func (d *OrganizationDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { diff --git a/internal/provider/policies_data_source.go b/internal/provider/policies_data_source.go index 57bfad6..659310a 100644 --- a/internal/provider/policies_data_source.go +++ b/internal/provider/policies_data_source.go @@ -10,7 +10,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" - mondoov1 "go.mondoo.com/mondoo-go" ) var _ datasource.DataSource = (*policiesDataSource)(nil) @@ -135,7 +134,7 @@ func (d *policiesDataSource) Configure(ctx context.Context, req datasource.Confi return } - client, ok := req.ProviderData.(*mondoov1.Client) + client, ok := req.ProviderData.(*ExtendedGqlClient) if !ok { resp.Diagnostics.AddError( @@ -146,7 +145,7 @@ func (d *policiesDataSource) Configure(ctx context.Context, req datasource.Confi return } - d.client = &ExtendedGqlClient{client} + d.client = client } func (d *policiesDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { diff --git a/internal/provider/policy_assignment_resource.go b/internal/provider/policy_assignment_resource.go index 46ae153..904bc88 100644 --- a/internal/provider/policy_assignment_resource.go +++ b/internal/provider/policy_assignment_resource.go @@ -12,6 +12,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-log/tflog" mondoov1 "go.mondoo.com/mondoo-go" "github.com/hashicorp/terraform-plugin-framework/resource" @@ -31,7 +32,7 @@ type policyAssignmentResource struct { type policyAssigmentsResourceModel struct { // scope - SpaceId types.String `tfsdk:"space_id"` + SpaceID types.String `tfsdk:"space_id"` // assigned policies PolicyMrns types.List `tfsdk:"policies"` @@ -40,16 +41,16 @@ type policyAssigmentsResourceModel struct { State types.String `tfsdk:"state"` } -func (r *policyAssignmentResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { +func (r *policyAssignmentResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { resp.TypeName = req.ProviderTypeName + "_policy_assignment" } -func (r *policyAssignmentResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { +func (r *policyAssignmentResource) Schema(_ context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ "space_id": schema.StringAttribute{ - MarkdownDescription: "Mondoo Space Identifier.", - Required: true, + MarkdownDescription: "Mondoo Space Identifier. If it is not provided, the provider space is used.", + Optional: true, }, "policies": schema.ListAttribute{ MarkdownDescription: "Policies to assign to the space.", @@ -79,7 +80,7 @@ func (r *policyAssignmentResource) Configure(ctx context.Context, req resource.C return } - client, ok := req.ProviderData.(*mondoov1.Client) + client, ok := req.ProviderData.(*ExtendedGqlClient) if !ok { resp.Diagnostics.AddError( @@ -90,7 +91,7 @@ func (r *policyAssignmentResource) Configure(ctx context.Context, req resource.C return } - r.client = &ExtendedGqlClient{client} + r.client = client } func (r *policyAssignmentResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { @@ -103,32 +104,29 @@ func (r *policyAssignmentResource) Create(ctx context.Context, req resource.Crea return } - // Do GraphQL request to API to create the resource - scopeMrn := "" - if data.SpaceId.ValueString() != "" { - scopeMrn = spacePrefix + data.SpaceId.ValueString() - } else { - resp.Diagnostics.AddError( - "Either space_id needs to be set", - "Either space_id needs to be set", - ) + // Compute and validate the space + space, err := r.client.ComputeSpace(data.SpaceID) + if err != nil { + resp.Diagnostics.AddError("Invalid Configuration", err.Error()) return } + ctx = tflog.SetField(ctx, "space_mrn", space.MRN()) + // Do GraphQL request to API to create the resource policyMrns := []string{} data.PolicyMrns.ElementsAs(ctx, &policyMrns, false) state := data.State.ValueString() - var err error + tflog.Debug(ctx, "Creating policy assignment") // default action is active if state == "" || state == "enabled" { action := mondoov1.PolicyActionActive - err = r.client.AssignPolicy(ctx, scopeMrn, action, policyMrns) + err = r.client.AssignPolicy(ctx, space.MRN(), action, policyMrns) } else if state == "preview" { action := mondoov1.PolicyActionIgnore - err = r.client.AssignPolicy(ctx, scopeMrn, action, policyMrns) + err = r.client.AssignPolicy(ctx, space.MRN(), action, policyMrns) } else if state == "disabled" { - err = r.client.UnassignPolicy(ctx, scopeMrn, policyMrns) + err = r.client.UnassignPolicy(ctx, space.MRN(), policyMrns) } else { resp.Diagnostics.AddError( "Invalid state: "+state, @@ -175,32 +173,29 @@ func (r *policyAssignmentResource) Update(ctx context.Context, req resource.Upda return } - // Do GraphQL request to API to create the resource - scopeMrn := "" - if data.SpaceId.ValueString() != "" { - scopeMrn = spacePrefix + data.SpaceId.ValueString() - } else { - resp.Diagnostics.AddError( - "Either space_id needs to be set", - "Either space_id needs to be set", - ) + // Compute and validate the space + space, err := r.client.ComputeSpace(data.SpaceID) + if err != nil { + resp.Diagnostics.AddError("Invalid Configuration", err.Error()) return } + ctx = tflog.SetField(ctx, "space_mrn", space.MRN()) + // Do GraphQL request to API to create the resource policyMrns := []string{} data.PolicyMrns.ElementsAs(ctx, &policyMrns, false) state := data.State.ValueString() - var err error + tflog.Debug(ctx, "Updating policy assignment") // default action is active if state == "" || state == "enabled" { action := mondoov1.PolicyActionActive - err = r.client.AssignPolicy(ctx, scopeMrn, action, policyMrns) + err = r.client.AssignPolicy(ctx, space.MRN(), action, policyMrns) } else if state == "preview" { action := mondoov1.PolicyActionIgnore - err = r.client.AssignPolicy(ctx, scopeMrn, action, policyMrns) + err = r.client.AssignPolicy(ctx, space.MRN(), action, policyMrns) } else if state == "disabled" { - err = r.client.UnassignPolicy(ctx, scopeMrn, policyMrns) + err = r.client.UnassignPolicy(ctx, space.MRN(), policyMrns) } else { resp.Diagnostics.AddError( "Invalid state: "+state, @@ -231,23 +226,21 @@ func (r *policyAssignmentResource) Delete(ctx context.Context, req resource.Dele return } - // Do GraphQL request to API to create the resource - scopeMrn := "" - if data.SpaceId.ValueString() != "" { - scopeMrn = spacePrefix + data.SpaceId.ValueString() - } else { - resp.Diagnostics.AddError( - "Either space_id needs to be set", - "Either space_id needs to be set", - ) + // Compute and validate the space + space, err := r.client.ComputeSpace(data.SpaceID) + if err != nil { + resp.Diagnostics.AddError("Invalid Configuration", err.Error()) return } + ctx = tflog.SetField(ctx, "space_mrn", space.MRN()) + // Do GraphQL request to API to create the resource policyMrns := []string{} data.PolicyMrns.ElementsAs(ctx, &policyMrns, false) + tflog.Debug(ctx, "Deleting policy assignment") // no matter the state, we unassign the policies - err := r.client.UnassignPolicy(ctx, scopeMrn, policyMrns) + err = r.client.UnassignPolicy(ctx, space.MRN(), policyMrns) if err != nil { resp.Diagnostics.AddError( "Error creating policy assignment", diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 1b8c0f3..fe54ab6 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -42,12 +42,12 @@ type MondooProviderModel struct { Endpoint types.String `tfsdk:"endpoint"` } -func (p *MondooProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) { +func (p *MondooProvider) Metadata(_ context.Context, _ provider.MetadataRequest, resp *provider.MetadataResponse) { resp.TypeName = "mondoo" resp.Version = p.version } -func (p *MondooProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) { +func (p *MondooProvider) Schema(ctx context.Context, _ provider.SchemaRequest, resp *provider.SchemaResponse) { resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ "credentials": schema.StringAttribute{ @@ -59,7 +59,7 @@ func (p *MondooProvider) Schema(ctx context.Context, req provider.SchemaRequest, Optional: true, }, "region": schema.StringAttribute{ - MarkdownDescription: "The default region to manage resources in.", + MarkdownDescription: "The default region to manage resources in. Valid regions are `us` or `eu`.", Optional: true, Validators: []validator.String{ stringvalidator.OneOf("us", "eu"), @@ -105,12 +105,16 @@ func (p *MondooProvider) Configure(ctx context.Context, req provider.ConfigureRe return } opts = append(opts, option.WithServiceAccount(data)) + ctx = tflog.SetField(ctx, "env_config_base64", true) } else if configPath != "" { opts = append(opts, option.WithServiceAccountFile(configPath)) + ctx = tflog.SetField(ctx, "env_config_path", true) } else if token != "" { opts = append(opts, option.WithAPIToken(token)) + ctx = tflog.SetField(ctx, "env_api_token", true) } else if data.Credentials.ValueString() != "" { opts = append(opts, option.WithServiceAccount([]byte(data.Credentials.ValueString()))) + ctx = tflog.SetField(ctx, "field_credentials", true) } else { // if no option was provided, try the default location of Mondoo CLI configuration file defaultConfigPath, err := detectDefaultConfig() @@ -123,7 +127,9 @@ func (p *MondooProvider) Configure(ctx context.Context, req provider.ConfigureRe return } opts = append(opts, option.WithServiceAccountFile(defaultConfigPath)) + ctx = tflog.SetField(ctx, "default_cli_config_file", true) } + tflog.Debug(ctx, "Detected authentication credentials") // allow the override of the endpoint // 1. via MONDOO_API_ENDPOINT @@ -136,12 +142,14 @@ func (p *MondooProvider) Configure(ctx context.Context, req provider.ConfigureRe url = url + "/query" } opts = append(opts, option.WithEndpoint(url)) + ctx = tflog.SetField(ctx, "env_api_endpoint", true) } else if data.Endpoint.ValueString() != "" { url := data.Endpoint.ValueString() if !strings.HasSuffix(url, "/query") { url = url + "/query" } opts = append(opts, option.WithEndpoint(url)) + ctx = tflog.SetField(ctx, "field_endpoint", true) } else if data.Region.ValueString() != "" { switch data.Region.ValueString() { case "eu": @@ -149,8 +157,15 @@ func (p *MondooProvider) Configure(ctx context.Context, req provider.ConfigureRe case "us": opts = append(opts, option.UseUSRegion()) } + ctx = tflog.SetField(ctx, "field_region", true) } + space := data.Space.ValueString() + if space != "" { + ctx = tflog.SetField(ctx, "provider_space", space) + } + + tflog.Debug(ctx, "Creating Mondoo client") client, err := mondoov1.NewClient(opts...) if err != nil { resp.Diagnostics.AddError( @@ -159,8 +174,12 @@ func (p *MondooProvider) Configure(ctx context.Context, req provider.ConfigureRe ) return } - resp.DataSourceData = client - resp.ResourceData = client + + // The extended GraphQL client allows us to pass additional information to + // resources and data sources, things like the Mondoo space + extendedClient := &ExtendedGqlClient{client, SpaceFrom(space)} + resp.DataSourceData = extendedClient + resp.ResourceData = extendedClient } func (p *MondooProvider) Resources(ctx context.Context) []func() resource.Resource { diff --git a/internal/provider/querypack_assignment_resource.go b/internal/provider/querypack_assignment_resource.go index de7f251..6ce3772 100644 --- a/internal/provider/querypack_assignment_resource.go +++ b/internal/provider/querypack_assignment_resource.go @@ -13,6 +13,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-log/tflog" mondoov1 "go.mondoo.com/mondoo-go" "github.com/hashicorp/terraform-plugin-framework/resource" @@ -32,7 +33,7 @@ type queryPackAssignmentResource struct { type queryPackAssigmentsResourceModel struct { // scope - SpaceId types.String `tfsdk:"space_id"` + SpaceID types.String `tfsdk:"space_id"` // assigned query packs QueryPackMrns types.List `tfsdk:"querypacks"` @@ -41,16 +42,16 @@ type queryPackAssigmentsResourceModel struct { State types.String `tfsdk:"state"` } -func (r *queryPackAssignmentResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { +func (r *queryPackAssignmentResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { resp.TypeName = req.ProviderTypeName + "_querypack_assignment" } -func (r *queryPackAssignmentResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { +func (r *queryPackAssignmentResource) Schema(_ context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ "space_id": schema.StringAttribute{ - MarkdownDescription: "Mondoo Space Identifier.", - Required: true, + MarkdownDescription: "Mondoo Space Identifier. If it is not provided, the provider space is used.", + Optional: true, }, "querypacks": schema.ListAttribute{ MarkdownDescription: "QueryPacks to assign to the space.", @@ -80,7 +81,7 @@ func (r *queryPackAssignmentResource) Configure(ctx context.Context, req resourc return } - client, ok := req.ProviderData.(*mondoov1.Client) + client, ok := req.ProviderData.(*ExtendedGqlClient) if !ok { resp.Diagnostics.AddError( @@ -91,7 +92,7 @@ func (r *queryPackAssignmentResource) Configure(ctx context.Context, req resourc return } - r.client = &ExtendedGqlClient{client} + r.client = client } func (r *queryPackAssignmentResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { @@ -104,29 +105,26 @@ func (r *queryPackAssignmentResource) Create(ctx context.Context, req resource.C return } - // Do GraphQL request to API to create the resource - scopeMrn := "" - if data.SpaceId.ValueString() != "" { - scopeMrn = spacePrefix + data.SpaceId.ValueString() - } else { - resp.Diagnostics.AddError( - "Either space_id needs to be set", - "Either space_id needs to be set", - ) + // Compute and validate the space + space, err := r.client.ComputeSpace(data.SpaceID) + if err != nil { + resp.Diagnostics.AddError("Invalid Configuration", err.Error()) return } + ctx = tflog.SetField(ctx, "space_mrn", space.MRN()) + // Do GraphQL request to API to create the resource queryPackMrns := []string{} data.QueryPackMrns.ElementsAs(ctx, &queryPackMrns, false) state := data.State.ValueString() - var err error + tflog.Debug(ctx, "Creating query pack assignment") // default action is active if state == "" || state == "enabled" { action := mondoov1.PolicyActionActive - err = r.client.AssignPolicy(ctx, scopeMrn, action, queryPackMrns) + err = r.client.AssignPolicy(ctx, space.MRN(), action, queryPackMrns) } else if state == "disabled" { - err = r.client.UnassignPolicy(ctx, scopeMrn, queryPackMrns) + err = r.client.UnassignPolicy(ctx, space.MRN(), queryPackMrns) } else { resp.Diagnostics.AddError( "Invalid state: "+state, @@ -138,7 +136,10 @@ func (r *queryPackAssignmentResource) Create(ctx context.Context, req resource.C if err != nil { resp.Diagnostics.AddError( "Error creating query pack assignment", - fmt.Sprintf("Error creating query pack assignment: %s\nSpace: %s\nQueryPacks: %s", err, scopeMrn, strings.Join(queryPackMrns, "\n")), + fmt.Sprintf( + "Error creating query pack assignment: %s\nSpace: %s\nQueryPacks: %s", + err, space.MRN(), strings.Join(queryPackMrns, "\n"), + ), ) return } @@ -173,29 +174,26 @@ func (r *queryPackAssignmentResource) Update(ctx context.Context, req resource.U return } - // Do GraphQL request to API to create the resource - scopeMrn := "" - if data.SpaceId.ValueString() != "" { - scopeMrn = spacePrefix + data.SpaceId.ValueString() - } else { - resp.Diagnostics.AddError( - "Either space_id needs to be set", - "Either space_id needs to be set", - ) + // Compute and validate the space + space, err := r.client.ComputeSpace(data.SpaceID) + if err != nil { + resp.Diagnostics.AddError("Invalid Configuration", err.Error()) return } + ctx = tflog.SetField(ctx, "space_mrn", space.MRN()) + // Do GraphQL request to API to create the resource queryPackMrns := []string{} data.QueryPackMrns.ElementsAs(ctx, &queryPackMrns, false) state := data.State.ValueString() - var err error + tflog.Debug(ctx, "Updating query pack assignment") // default action is active if state == "" || state == "enabled" { action := mondoov1.PolicyActionActive - err = r.client.AssignPolicy(ctx, scopeMrn, action, queryPackMrns) + err = r.client.AssignPolicy(ctx, space.MRN(), action, queryPackMrns) } else if state == "disabled" { - err = r.client.UnassignPolicy(ctx, scopeMrn, queryPackMrns) + err = r.client.UnassignPolicy(ctx, space.MRN(), queryPackMrns) } else { resp.Diagnostics.AddError( "Invalid state: "+state, @@ -207,7 +205,10 @@ func (r *queryPackAssignmentResource) Update(ctx context.Context, req resource.U if err != nil { resp.Diagnostics.AddError( "Error creating query pack assignment", - fmt.Sprintf("Error creating query pack assignment: %s\nSpace: %s\nQueryPacks: %s", err, scopeMrn, strings.Join(queryPackMrns, "\n")), + fmt.Sprintf( + "Error creating query pack assignment: %s\nSpace: %s\nQueryPacks: %s", + err, space.MRN(), strings.Join(queryPackMrns, "\n"), + ), ) return } @@ -226,22 +227,20 @@ func (r *queryPackAssignmentResource) Delete(ctx context.Context, req resource.D return } - // Do GraphQL request to API to create the resource - scopeMrn := "" - if data.SpaceId.ValueString() != "" { - scopeMrn = spacePrefix + data.SpaceId.ValueString() - } else { - resp.Diagnostics.AddError( - "Either space_id needs to be set", - "Either space_id needs to be set", - ) + // Compute and validate the space + space, err := r.client.ComputeSpace(data.SpaceID) + if err != nil { + resp.Diagnostics.AddError("Invalid Configuration", err.Error()) return } + ctx = tflog.SetField(ctx, "space_mrn", space.MRN()) + // Do GraphQL request to API to delete the resource queryPackMrns := []string{} data.QueryPackMrns.ElementsAs(ctx, &queryPackMrns, false) - err := r.client.UnassignPolicy(ctx, scopeMrn, queryPackMrns) + tflog.Debug(ctx, "Deleting query pack assignment") + err = r.client.UnassignPolicy(ctx, space.MRN(), queryPackMrns) if err != nil { resp.Diagnostics.AddError( "Error creating query pack assignment", diff --git a/internal/provider/registration_token.go b/internal/provider/registration_token.go index 9da4660..d863791 100644 --- a/internal/provider/registration_token.go +++ b/internal/provider/registration_token.go @@ -26,7 +26,7 @@ func NewRegistrationTokenResource() resource.Resource { // RegistrationTokenResource defines the resource implementation. type RegistrationTokenResource struct { - client *mondoov1.Client + client *ExtendedGqlClient } // RegistrationTokenResourceModel describes the resource data model. @@ -34,7 +34,7 @@ type RegistrationTokenResourceModel struct { Mrn types.String `tfsdk:"mrn"` // scope - SpaceId types.String `tfsdk:"space_id"` + SpaceID types.String `tfsdk:"space_id"` // registration token details Description types.String `tfsdk:"description"` @@ -57,8 +57,8 @@ func (r *RegistrationTokenResource) Schema(ctx context.Context, req resource.Sch Attributes: map[string]schema.Attribute{ "space_id": schema.StringAttribute{ - MarkdownDescription: "Mondoo Space Identifier to create the token in.", - Required: true, + MarkdownDescription: "Mondoo Space Identifier to create the token in. If it is not provided, the provider space is used.", + Optional: true, }, "mrn": schema.StringAttribute{ Computed: true, @@ -107,7 +107,7 @@ func (r *RegistrationTokenResource) Configure(ctx context.Context, req resource. return } - client, ok := req.ProviderData.(*mondoov1.Client) + client, ok := req.ProviderData.(*ExtendedGqlClient) if !ok { resp.Diagnostics.AddError( @@ -134,16 +134,13 @@ func (r *RegistrationTokenResource) Create(ctx context.Context, req resource.Cre // Do GraphQL request to API to create the resource description := data.Description.ValueString() - scopeMrn := "" - if data.SpaceId.ValueString() != "" { - scopeMrn = spacePrefix + data.SpaceId.ValueString() - } else { - resp.Diagnostics.AddError( - "Either space_id needs to be set", - "Either space_id needs to be set", - ) + // Compute and validate the space + space, err := r.client.ComputeSpace(data.SpaceID) + if err != nil { + resp.Diagnostics.AddError("Invalid Configuration", err.Error()) return } + ctx = tflog.SetField(ctx, "space_mrn", space.MRN()) var expires_in *mondoov1.Int if !data.ExpiresIn.IsNull() { @@ -173,7 +170,7 @@ func (r *RegistrationTokenResource) Create(ctx context.Context, req resource.Cre registrationTokenInput := mondoov1.RegistrationTokenInput{ Description: mondoov1.NewStringPtr(mondoov1.String(description)), - ScopeMrn: mondoov1.NewStringPtr(mondoov1.String(scopeMrn)), + ScopeMrn: mondoov1.NewStringPtr(mondoov1.String(space.MRN())), ExpiresIn: expires_in, NoExpiration: noExpiration, } @@ -192,9 +189,12 @@ func (r *RegistrationTokenResource) Create(ctx context.Context, req resource.Cre } `graphql:"generateRegistrationToken(input: $input)"` } - err := r.client.Mutate(ctx, &generateRegistrationToken, registrationTokenInput, nil) + err = r.client.Mutate(ctx, &generateRegistrationToken, registrationTokenInput, nil) if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to create space, got error: %s", err)) + resp.Diagnostics. + AddError("Client Error", + fmt.Sprintf("Unable to create registration token, got error: %s", err), + ) return } @@ -264,15 +264,27 @@ func (r *RegistrationTokenResource) Delete(ctx context.Context, req resource.Del } `graphql:"... on RevokeRegistrationTokenFailure"` } `graphql:"revokeRegistrationToken(input: $input)"` } + + // Compute and validate the space + space, err := r.client.ComputeSpace(data.SpaceID) + if err != nil { + resp.Diagnostics.AddError("Invalid Configuration", err.Error()) + return + } + ctx = tflog.SetField(ctx, "space_mrn", space.MRN()) + revokeInput := mondoov1.RevokeRegistrationTokenInput{ - Mrn: mondoov1.String(spacePrefix + data.SpaceId.ValueString()), + Mrn: mondoov1.String(space.MRN()), } tflog.Trace(ctx, "RevokeRegistrationTokenInput", map[string]interface{}{ "input": fmt.Sprintf("%+v", revokeInput), }) - err := r.client.Mutate(ctx, &revokeMutation, revokeInput, nil) + err = r.client.Mutate(ctx, &revokeMutation, revokeInput, nil) if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to update service account, got error: %s", err)) + resp.Diagnostics. + AddError("Client Error", + fmt.Sprintf("Unable to update service account, got error: %s", err), + ) return } } diff --git a/internal/provider/scim_group_mapping.go b/internal/provider/scim_group_mapping.go index 1503ef0..2958e8d 100644 --- a/internal/provider/scim_group_mapping.go +++ b/internal/provider/scim_group_mapping.go @@ -83,7 +83,7 @@ func (r *scimGroupMappingResource) Configure(ctx context.Context, req resource.C return } - client, ok := req.ProviderData.(*mondoov1.Client) + client, ok := req.ProviderData.(*ExtendedGqlClient) if !ok { resp.Diagnostics.AddError( @@ -94,7 +94,7 @@ func (r *scimGroupMappingResource) Configure(ctx context.Context, req resource.C return } - r.client = &ExtendedGqlClient{client} + r.client = client } func (r *scimGroupMappingResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { diff --git a/internal/provider/service_account_resource.go b/internal/provider/service_account_resource.go index 10ce2ec..82087ee 100644 --- a/internal/provider/service_account_resource.go +++ b/internal/provider/service_account_resource.go @@ -8,6 +8,7 @@ import ( "encoding/base64" "encoding/json" "fmt" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" @@ -45,7 +46,7 @@ func NewServiceAccountResource() resource.Resource { // ServiceAccountResource defines the resource implementation. type ServiceAccountResource struct { - client *mondoov1.Client + client *ExtendedGqlClient } // ServiceAccountResourceModel describes the resource data model. @@ -133,7 +134,7 @@ func (r *ServiceAccountResource) Configure(ctx context.Context, req resource.Con return } - client, ok := req.ProviderData.(*mondoov1.Client) + client, ok := req.ProviderData.(*ExtendedGqlClient) if !ok { resp.Diagnostics.AddError( @@ -147,13 +148,17 @@ func (r *ServiceAccountResource) Configure(ctx context.Context, req resource.Con r.client = client } -func getScope(data ServiceAccountResourceModel) string { +func (r *ServiceAccountResource) getScope(ctx context.Context, data ServiceAccountResourceModel) string { scopeMrn := "" - if data.SpaceID.ValueString() != "" { - scopeMrn = spacePrefix + data.SpaceID.ValueString() - } else if data.OrgID.ValueString() != "" { + // Give presedence to the org id + if data.OrgID.ValueString() != "" { scopeMrn = orgPrefix + data.OrgID.ValueString() + ctx = tflog.SetField(ctx, "org_mrn", scopeMrn) + } else if space, err := r.client.ComputeSpace(data.SpaceID); err == nil { + scopeMrn = space.MRN() + ctx = tflog.SetField(ctx, "space_mrn", scopeMrn) } + tflog.Debug(ctx, "Scope for service account") return scopeMrn } @@ -191,7 +196,7 @@ func (r *ServiceAccountResource) Create(ctx context.Context, req resource.Create rolesInput = append(rolesInput, mondoov1.RoleInput{Mrn: mondoov1.String(role)}) } - scopeMrn := getScope(data) + scopeMrn := r.getScope(ctx, data) if scopeMrn == "" { resp.Diagnostics.AddError( "Either space_id or org_id needs to be set", @@ -207,7 +212,7 @@ func (r *ServiceAccountResource) Create(ctx context.Context, req resource.Create Roles: &rolesInput, } - tflog.Trace(ctx, "CreateSpaceInput", map[string]interface{}{ + tflog.Debug(ctx, "CreateSpaceInput", map[string]interface{}{ "input": fmt.Sprintf("%+v", createInput), }) @@ -223,7 +228,10 @@ func (r *ServiceAccountResource) Create(ctx context.Context, req resource.Create err := r.client.Mutate(ctx, &createMutation, createInput, nil) if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to create space, got error: %s", err)) + resp.Diagnostics. + AddError("Client Error", + fmt.Sprintf("Unable to create service sccount, got error: %s", err), + ) return } @@ -243,7 +251,10 @@ func (r *ServiceAccountResource) Create(ctx context.Context, req resource.Create jsonData, err := json.Marshal(serviceAccount) if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to create service account, got error: %s", err)) + resp.Diagnostics. + AddError("Client Error", + fmt.Sprintf("Unable to create service account, got error: %s", err), + ) return } @@ -251,7 +262,7 @@ func (r *ServiceAccountResource) Create(ctx context.Context, req resource.Create data.Credential = types.StringValue(base64.StdEncoding.EncodeToString(jsonData)) // Write logs using the tflog package - tflog.Trace(ctx, "created a service account resource") + tflog.Debug(ctx, "created a service account resource") // Save data into Terraform state resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) @@ -342,7 +353,7 @@ func (r *ServiceAccountResource) Update(ctx context.Context, req resource.Update Name: mondoov1.NewStringPtr(mondoov1.String(data.Name.ValueString())), Notes: mondoov1.NewStringPtr(mondoov1.String(data.Description.ValueString())), } - tflog.Trace(ctx, "UpdateServiceAccountInput", map[string]interface{}{ + tflog.Debug(ctx, "UpdateServiceAccountInput", map[string]interface{}{ "input": fmt.Sprintf("%+v", updateInput), }) err := r.client.Mutate(ctx, &updateMutation, updateInput, nil) @@ -365,7 +376,7 @@ func (r *ServiceAccountResource) Delete(ctx context.Context, req resource.Delete return } - scopeMrn := getScope(data) + scopeMrn := r.getScope(ctx, data) if scopeMrn == "" { resp.Diagnostics.AddError( "Either space_id or org_id needs to be set", @@ -384,7 +395,7 @@ func (r *ServiceAccountResource) Delete(ctx context.Context, req resource.Delete ScopeMrn: mondoov1.String(scopeMrn), Mrns: []mondoov1.String{mondoov1.String(data.Mrn.ValueString())}, } - tflog.Trace(ctx, "UpdateServiceAccountInput", map[string]interface{}{ + tflog.Debug(ctx, "UpdateServiceAccountInput", map[string]interface{}{ "input": fmt.Sprintf("%+v", deleteInput), }) err := r.client.Mutate(ctx, &deleteMutation, deleteInput, nil) diff --git a/internal/provider/space.go b/internal/provider/space.go new file mode 100644 index 0000000..7aab6d7 --- /dev/null +++ b/internal/provider/space.go @@ -0,0 +1,33 @@ +// Copyright (c) Mondoo, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package provider + +import "strings" + +const spacePrefix = "//captain.api.mondoo.app/spaces/" + +// Helper type to handle both, space id and space mrn, interchangeably. +type Space string + +// SpaceFrom receives either a space id or a space mrn and returns a `Space`. +func SpaceFrom(space string) Space { + if strings.HasPrefix(space, spacePrefix) { + // MRN + spaceID := strings.Split(space, "/")[4] + // Using this index is safe since we check for a prefix that has 4 slashes + return Space(spaceID) + } + // ID + return Space(space) +} + +func (s Space) ID() string { + return string(s) +} +func (s Space) MRN() string { + if s == "" { + return "" + } + return spacePrefix + string(s) +} diff --git a/internal/provider/space_data_source.go b/internal/provider/space_data_source.go index b837baf..e45302a 100644 --- a/internal/provider/space_data_source.go +++ b/internal/provider/space_data_source.go @@ -7,13 +7,10 @@ import ( "context" "fmt" - "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" - "github.com/hashicorp/terraform-plugin-framework/path" - "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" - mondoov1 "go.mondoo.com/mondoo-go" + "github.com/hashicorp/terraform-plugin-log/tflog" ) // Ensure provider defined types fully satisfy framework interfaces. @@ -48,23 +45,10 @@ func (d *SpaceDataSource) Schema(ctx context.Context, req datasource.SchemaReque MarkdownDescription: "Space ID", Computed: true, Optional: true, - Validators: []validator.String{ - // Validate only this attribute or other_attr is configured. - stringvalidator.ExactlyOneOf(path.Expressions{ - path.MatchRoot("mrn"), - }...), - }, }, "mrn": schema.StringAttribute{ MarkdownDescription: "Space MRN", Computed: true, - Optional: true, - Validators: []validator.String{ - // Validate only this attribute or other_attr is configured. - stringvalidator.ExactlyOneOf(path.Expressions{ - path.MatchRoot("id"), - }...), - }, }, "name": schema.StringAttribute{ MarkdownDescription: "Space name", @@ -80,7 +64,7 @@ func (d *SpaceDataSource) Configure(ctx context.Context, req datasource.Configur return } - client, ok := req.ProviderData.(*mondoov1.Client) + client, ok := req.ProviderData.(*ExtendedGqlClient) if !ok { resp.Diagnostics.AddError( @@ -91,7 +75,7 @@ func (d *SpaceDataSource) Configure(ctx context.Context, req datasource.Configur return } - d.client = &ExtendedGqlClient{client} + d.client = client } func (d *SpaceDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { @@ -104,22 +88,21 @@ func (d *SpaceDataSource) Read(ctx context.Context, req datasource.ReadRequest, return } - // we fetch the organization id from the service account - spaceMrn := "" - if data.SpaceMrn.ValueString() != "" { - spaceMrn = data.SpaceMrn.ValueString() - } else if data.SpaceID.ValueString() != "" { - spaceMrn = spacePrefix + data.SpaceID.ValueString() - } - - if spaceMrn == "" { - resp.Diagnostics.AddError("Invalid Configuration", "Either `id` or `mrn` must be set") + // Compute and validate the space + space, err := d.client.ComputeSpace(data.SpaceID) + if err != nil { + resp.Diagnostics.AddError("Invalid Configuration", err.Error()) return } - payload, err := d.client.GetSpace(ctx, spaceMrn) + ctx = tflog.SetField(ctx, "space_mrn", space.MRN()) + + tflog.Debug(ctx, "Fetching space information") + payload, err := d.client.GetSpace(ctx, space.MRN()) if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to fetch organization, got error: %s", err)) + resp.Diagnostics.AddError("Client Error", + fmt.Sprintf("Unable to fetch space, got error: %s", err), + ) return } diff --git a/internal/provider/space_data_source_test.go b/internal/provider/space_data_source_test.go index 5325424..1d8fa4a 100644 --- a/internal/provider/space_data_source_test.go +++ b/internal/provider/space_data_source_test.go @@ -39,7 +39,7 @@ resource "mondoo_space" "test" { } data "mondoo_space" "space"{ - mrn = mondoo_space.test.mrn + id = mondoo_space.test.id depends_on = [ mondoo_space.test diff --git a/internal/provider/space_resource.go b/internal/provider/space_resource.go index e78cb4c..ac09c5b 100644 --- a/internal/provider/space_resource.go +++ b/internal/provider/space_resource.go @@ -17,12 +17,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-log/tflog" - mondoov1 "go.mondoo.com/mondoo-go" -) - -const ( - orgPrefix = "//captain.api.mondoo.app/organizations/" - spacePrefix = "//captain.api.mondoo.app/spaces/" ) // Ensure provider defined types fully satisfy framework interfaces. @@ -67,9 +61,9 @@ func (r *SpaceResource) Schema(ctx context.Context, req resource.SchemaRequest, }, }, "id": schema.StringAttribute{ - MarkdownDescription: "Id of the space. Must be globally unique.", - Computed: true, + MarkdownDescription: "Id of the space. Must be globally unique. If the provider has a space configured and this field is not provided, the provider space is used.", Optional: true, + Computed: true, PlanModifiers: []planmodifier.String{ stringplanmodifier.UseStateForUnknown(), }, @@ -107,7 +101,7 @@ func (r *SpaceResource) Configure(ctx context.Context, req resource.ConfigureReq return } - client, ok := req.ProviderData.(*mondoov1.Client) + client, ok := req.ProviderData.(*ExtendedGqlClient) if !ok { resp.Diagnostics.AddError( "Unexpected Resource Configure Type", @@ -116,7 +110,7 @@ func (r *SpaceResource) Configure(ctx context.Context, req resource.ConfigureReq return } - r.client = &ExtendedGqlClient{client} + r.client = client } func (r *SpaceResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { @@ -129,10 +123,25 @@ func (r *SpaceResource) Create(ctx context.Context, req resource.CreateRequest, return } + // Compute and validate the space + space, err := r.client.ComputeSpace(data.SpaceID) + if err != nil { + // we do not fail if there the user doesn't specify an id + // because we are creating one, still log the error + tflog.Debug(ctx, err.Error()) + } + // Do GraphQL request to API to create the resource. - payload, err := r.client.CreateSpace(ctx, data.OrgID.ValueString(), data.SpaceID.ValueString(), data.Name.ValueString()) + payload, err := r.client.CreateSpace(ctx, + data.OrgID.ValueString(), + space.ID(), + data.Name.ValueString(), + ) if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to create space, got error: %s", err)) + resp.Diagnostics. + AddError("Client Error", + fmt.Sprintf("Unable to create space, got error: %s", err), + ) return } @@ -142,12 +151,14 @@ func (r *SpaceResource) Create(ctx context.Context, req resource.CreateRequest, id, ok := payload.Id.(string) if ok { data.SpaceID = types.StringValue(id) + ctx = tflog.SetField(ctx, "space_id", data.SpaceID) } data.SpaceMrn = types.StringValue(string(payload.Mrn)) + ctx = tflog.SetField(ctx, "space_mrn", data.SpaceMrn) // Write logs using the tflog package - tflog.Trace(ctx, "created a space resource") + tflog.Debug(ctx, "Created a space resource") // Save data into Terraform state resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) @@ -177,25 +188,34 @@ func (r *SpaceResource) Update(ctx context.Context, req resource.UpdateRequest, return } - // ensure space id is not changed - var stateSpaceID string - req.State.GetAttribute(ctx, path.Root("id"), &stateSpaceID) + // Compute and validate the space + space, err := r.client.ComputeSpace(data.SpaceID) + if err != nil { + // we do not fail if there the user doesn't specify an id + // because we are creating one, still log the error + tflog.Debug(ctx, err.Error()) + } + ctx = tflog.SetField(ctx, "computed_space_id", space.ID()) + // ensure space id is not changed var planSpaceID string req.Plan.GetAttribute(ctx, path.Root("id"), &planSpaceID) - if stateSpaceID != planSpaceID { + if space.ID() != planSpaceID { resp.Diagnostics.AddError( "Space ID cannot be changed", - "Space ID cannot be changed", + "Note that the Mondoo space can be configured at the resource or provider level.", ) return } + ctx = tflog.SetField(ctx, "space_id_from_plan", planSpaceID) // Do GraphQL request to API to update the resource. - err := r.client.UpdateSpace(ctx, data.SpaceID.ValueString(), data.Name.ValueString()) + tflog.Debug(ctx, "Updating space") + err = r.client.UpdateSpace(ctx, planSpaceID, data.Name.ValueString()) if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to update space, got error: %s", err)) + resp.Diagnostics. + AddError("Client Error", fmt.Sprintf("Unable to update space, got error: %s", err)) return } @@ -216,7 +236,10 @@ func (r *SpaceResource) Delete(ctx context.Context, req resource.DeleteRequest, // Do GraphQL request to API to delete the resource. err := r.client.DeleteSpace(ctx, data.SpaceID.ValueString()) if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to delete space, got error: %s", err)) + resp.Diagnostics. + AddError("Client Error", + fmt.Sprintf("Unable to delete space, got error: %s", err), + ) return } } @@ -225,7 +248,10 @@ func (r *SpaceResource) ImportState(ctx context.Context, req resource.ImportStat mrn := spacePrefix + req.ID spacePayload, err := r.client.GetSpace(ctx, mrn) if err != nil { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to retrieve space, got error: %s", err)) + resp.Diagnostics. + AddError("Client Error", + fmt.Sprintf("Unable to retrieve space, got error: %s", err), + ) return } diff --git a/internal/provider/space_resource_test.go b/internal/provider/space_resource_test.go index a670f05..0f28560 100644 --- a/internal/provider/space_resource_test.go +++ b/internal/provider/space_resource_test.go @@ -26,6 +26,7 @@ func TestAccSpaceResource(t *testing.T) { Config: testAccSpaceResourceConfig(orgID, "one"), Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr("mondoo_space.test", "name", "one"), + resource.TestCheckResourceAttr("mondoo_space.test", "org_id", orgID), ), }, // ImportState testing diff --git a/internal/provider/space_test.go b/internal/provider/space_test.go new file mode 100644 index 0000000..41091a9 --- /dev/null +++ b/internal/provider/space_test.go @@ -0,0 +1,108 @@ +// Copyright (c) Mondoo, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package provider + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSpaceFrom(t *testing.T) { + tests := []struct { + name string + input string + expected Space + }{ + { + name: "Valid MRN", + input: "//captain.api.mondoo.app/spaces/1234", + expected: Space("1234"), + }, + { + name: "Valid MRN with additional segments", + input: "//captain.api.mondoo.app/spaces/1234/resources/5678", + expected: Space("1234"), + }, + { + name: "Space ID only", + input: "5678", + expected: Space("5678"), + }, + { + name: "Empty string input", + input: "", + expected: Space(""), + }, + { + name: "Invalid MRN without space ID segment", + input: "//captain.api.mondoo.app/spaces/", + expected: Space(""), + }, + { + name: "Non-MRN format", + input: "not-an-mrn", + expected: Space("not-an-mrn"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := SpaceFrom(tt.input) + assert.Equal(t, tt.expected, result, "Expected SpaceFrom result to match") + }) + } +} + +func TestSpace_ID(t *testing.T) { + tests := []struct { + name string + input Space + expected string + }{ + { + name: "Valid Space ID", + input: Space("1234"), + expected: "1234", + }, + { + name: "Empty Space", + input: Space(""), + expected: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := tt.input.ID() + assert.Equal(t, tt.expected, result, "Expected ID result to match") + }) + } +} + +func TestSpace_MRN(t *testing.T) { + tests := []struct { + name string + input Space + expected string + }{ + { + name: "Valid Space ID", + input: Space("1234"), + expected: "//captain.api.mondoo.app/spaces/1234", + }, + { + name: "Empty Space", + input: Space(""), + expected: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := tt.input.MRN() + assert.Equal(t, tt.expected, result, "Expected MRN result to match") + }) + } +}