diff --git a/docs/data-sources/organization.md b/docs/data-sources/organization.md new file mode 100644 index 0000000..5db1cc4 --- /dev/null +++ b/docs/data-sources/organization.md @@ -0,0 +1,46 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "mondoo_organization Data Source - terraform-provider-mondoo" +subcategory: "" +description: |- + Organization data source +--- + +# mondoo_organization (Data Source) + +Organization data source + +## Example Usage + +```terraform +terraform { + required_providers { + mondoo = { + source = "mondoohq/mondoo" + } + } +} + +provider "mondoo" { +} + +data "mondoo_organization" "org" { + id = "reverent-ride-275852" +} + +output "org_mrn" { + value = data.mondoo_organization.org.mrn +} +``` + + +## Schema + +### Optional + +- `id` (String) Organization ID +- `mrn` (String) Organization MRN + +### Read-Only + +- `name` (String) Organization name diff --git a/docs/data-sources/space.md b/docs/data-sources/space.md new file mode 100644 index 0000000..5d003a2 --- /dev/null +++ b/docs/data-sources/space.md @@ -0,0 +1,59 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "mondoo_space Data Source - terraform-provider-mondoo" +subcategory: "" +description: |- + Space data source +--- + +# mondoo_space (Data Source) + +Space data source + +## Example Usage + +```terraform +terraform { + required_providers { + mondoo = { + source = "mondoohq/mondoo" + } + } +} + +provider "mondoo" { +} + +data "mondoo_organization" "org" { + id = "reverent-ride-275852" +} + +resource "mondoo_space" "test" { + org_id = mondoo_organization.org.id + name = "test-space" +} + +data "mondoo_space" "space" { + id = mondoo_space.test.id + + depends_on = [ + mondoo_space.test + ] +} + +output "space_name" { + value = data.mondoo_space.name +} +``` + + +## Schema + +### Optional + +- `id` (String) Space ID +- `mrn` (String) Space MRN + +### Read-Only + +- `name` (String) Space name diff --git a/docs/resources/scim_group_mapping.md b/docs/resources/scim_group_mapping.md index 71a61fb..13802f7 100644 --- a/docs/resources/scim_group_mapping.md +++ b/docs/resources/scim_group_mapping.md @@ -24,18 +24,22 @@ terraform { provider "mondoo" { } +data "mondoo_organization" "org" { + id = "reverent-ride-275852" +} + resource "mondoo_space" "my_space_1" { name = "My Space 1" - org_id = "your-org-1234567" + org_id = data.mondoo_organization.org.id } resource "mondoo_scim_group_mapping" "MondooAdmin" { - org_id = "your-org-1234567" + org_id = data.mondoo_organization.org.id group = "MondooAdmin" mappings = [ # Give admin group access to the organization { - org_mrn : "//captain.api.mondoo.app/organizations/your-org-1234567", + org_mrn : data.mondoo_organization.org.mrn, iam_role : "//iam.api.mondoo.app/roles/editor" }, # Give admin group access to the space @@ -49,6 +53,10 @@ resource "mondoo_scim_group_mapping" "MondooAdmin" { mondoo_space.my_space_1 ] } + +output "org_mrn" { + value = data.mondoo_organization.org.mrn +} ``` diff --git a/examples/data-sources/mondoo_organization/data-source.tf b/examples/data-sources/mondoo_organization/data-source.tf new file mode 100644 index 0000000..093249d --- /dev/null +++ b/examples/data-sources/mondoo_organization/data-source.tf @@ -0,0 +1,18 @@ +terraform { + required_providers { + mondoo = { + source = "mondoohq/mondoo" + } + } +} + +provider "mondoo" { +} + +data "mondoo_organization" "org" { + id = "reverent-ride-275852" +} + +output "org_mrn" { + value = data.mondoo_organization.org.mrn +} \ No newline at end of file diff --git a/examples/data-sources/mondoo_space/data-source.tf b/examples/data-sources/mondoo_space/data-source.tf new file mode 100644 index 0000000..f8ebc74 --- /dev/null +++ b/examples/data-sources/mondoo_space/data-source.tf @@ -0,0 +1,31 @@ +terraform { + required_providers { + mondoo = { + source = "mondoohq/mondoo" + } + } +} + +provider "mondoo" { +} + +data "mondoo_organization" "org" { + id = "reverent-ride-275852" +} + +resource "mondoo_space" "test" { + org_id = mondoo_organization.org.id + name = "test-space" +} + +data "mondoo_space" "space" { + id = mondoo_space.test.id + + depends_on = [ + mondoo_space.test + ] +} + +output "space_name" { + value = data.mondoo_space.name +} \ No newline at end of file diff --git a/examples/resources/mondoo_scim_group_mapping/resource.tf b/examples/resources/mondoo_scim_group_mapping/resource.tf index 0a2116a..6a1bd0f 100644 --- a/examples/resources/mondoo_scim_group_mapping/resource.tf +++ b/examples/resources/mondoo_scim_group_mapping/resource.tf @@ -9,18 +9,22 @@ terraform { provider "mondoo" { } +data "mondoo_organization" "org" { + id = "reverent-ride-275852" +} + resource "mondoo_space" "my_space_1" { name = "My Space 1" - org_id = "your-org-1234567" + org_id = data.mondoo_organization.org.id } resource "mondoo_scim_group_mapping" "MondooAdmin" { - org_id = "your-org-1234567" + org_id = data.mondoo_organization.org.id group = "MondooAdmin" mappings = [ # Give admin group access to the organization { - org_mrn : "//captain.api.mondoo.app/organizations/your-org-1234567", + org_mrn : data.mondoo_organization.org.mrn, iam_role : "//iam.api.mondoo.app/roles/editor" }, # Give admin group access to the space @@ -34,3 +38,7 @@ resource "mondoo_scim_group_mapping" "MondooAdmin" { mondoo_space.my_space_1 ] } + +output "org_mrn" { + value = data.mondoo_organization.org.mrn +} \ No newline at end of file diff --git a/internal/provider/gql.go b/internal/provider/gql.go index ad38303..7e11650 100644 --- a/internal/provider/gql.go +++ b/internal/provider/gql.go @@ -110,6 +110,28 @@ func (r *ExtendedGqlClient) GetSpace(ctx context.Context, mrn string) (spacePayl return q.Space, nil } +type orgPayload struct { + Id string + Mrn string + Name string +} + +func (r *ExtendedGqlClient) GetOrganization(ctx context.Context, mrn string) (orgPayload, error) { + var q struct { + Organization orgPayload `graphql:"organization(mrn: $mrn)"` + } + variables := map[string]interface{}{ + "mrn": mondoov1.String(mrn), + } + + err := r.Query(ctx, &q, variables) + if err != nil { + return orgPayload{}, err + } + + return q.Organization, nil +} + type setCustomPolicyPayload struct { PolicyMrns []mondoov1.String } diff --git a/internal/provider/organization_data_source.go b/internal/provider/organization_data_source.go new file mode 100644 index 0000000..deddc08 --- /dev/null +++ b/internal/provider/organization_data_source.go @@ -0,0 +1,117 @@ +// Copyright (c) Mondoo, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package provider + +import ( + "context" + "fmt" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" + mondoov1 "go.mondoo.com/mondoo-go" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ datasource.DataSource = &OrganizationDataSource{} + +func NewOrganizationDataSource() datasource.DataSource { + return &OrganizationDataSource{} +} + +// OrganizationDataSource defines the data source implementation. +type OrganizationDataSource struct { + client *ExtendedGqlClient +} + +// OrganizationDataSourceModel describes the data source data model. +type OrganizationDataSourceModel struct { + OrgID types.String `tfsdk:"id"` + OrgMrn types.String `tfsdk:"mrn"` + Name types.String `tfsdk:"name"` +} + +func (d *OrganizationDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_organization" +} + +func (d *OrganizationDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Organization data source", + + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: "Organization ID", + Computed: true, + Optional: true, + }, + "mrn": schema.StringAttribute{ + MarkdownDescription: "Organization MRN", + Computed: true, + Optional: true, + }, + "name": schema.StringAttribute{ + MarkdownDescription: "Organization name", + Computed: true, + }, + }, + } +} + +func (d *OrganizationDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*mondoov1.Client) + + if !ok { + resp.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + fmt.Sprintf("Expected *mondoov1.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + + return + } + + d.client = &ExtendedGqlClient{client} +} + +func (d *OrganizationDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data OrganizationDataSourceModel + + // Read Terraform configuration data into the model + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // we fetch the organization id from the service account + orgMrn := "" + if data.OrgMrn.ValueString() != "" { + orgMrn = data.OrgMrn.ValueString() + } else if data.OrgID.ValueString() != "" { + orgMrn = orgPrefix + data.OrgID.ValueString() + } + if orgMrn == "" { + resp.Diagnostics.AddError("Invalid Configuration", "Either `id` or `mrn` must be set") + return + } + + payload, err := d.client.GetOrganization(ctx, orgMrn) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to fetch organization, got error: %s", err)) + return + } + + // For the purposes of this example code, hardcoding a response value to + // save into the Terraform state. + data.OrgID = types.StringValue(payload.Id) + data.OrgMrn = types.StringValue(payload.Mrn) + data.Name = types.StringValue(payload.Name) + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} diff --git a/internal/provider/organization_data_source_test.go b/internal/provider/organization_data_source_test.go new file mode 100644 index 0000000..ba575a2 --- /dev/null +++ b/internal/provider/organization_data_source_test.go @@ -0,0 +1,44 @@ +// Copyright (c) Mondoo, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package provider + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccOrganizationDataSource(t *testing.T) { + orgID, err := getOrgId() + if err != nil { + t.Fatal(err) + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Read testing + { + Config: testAccOrganizationDataSourceConfig(orgID), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("data.mondoo_organization.org", "id", orgID), + ), + }, + }, + }) +} + +func testAccOrganizationDataSourceConfig(orgId string) string { + return fmt.Sprintf(` +data "mondoo_organization" "org"{ + id = %[1]q +} + +output "org_id" { + value = data.mondoo_organization.org.id +} +`, orgId) +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 658bec9..e487306 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -168,7 +168,10 @@ func (p *MondooProvider) Resources(ctx context.Context) []func() resource.Resour } func (p *MondooProvider) DataSources(ctx context.Context) []func() datasource.DataSource { - return []func() datasource.DataSource{} + return []func() datasource.DataSource{ + NewOrganizationDataSource, + NewSpaceDataSource, + } } func New(version string) func() provider.Provider { diff --git a/internal/provider/space_data_source.go b/internal/provider/space_data_source.go new file mode 100644 index 0000000..822a21a --- /dev/null +++ b/internal/provider/space_data_source.go @@ -0,0 +1,117 @@ +// Copyright (c) Mondoo, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package provider + +import ( + "context" + "fmt" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" + mondoov1 "go.mondoo.com/mondoo-go" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ datasource.DataSource = &SpaceDataSource{} + +func NewSpaceDataSource() datasource.DataSource { + return &SpaceDataSource{} +} + +// SpaceDataSource defines the data source implementation. +type SpaceDataSource struct { + client *ExtendedGqlClient +} + +// SpaceDataSourceModel describes the data source data model. +type SpaceDataSourceModel struct { + SpaceID types.String `tfsdk:"id"` + SpaceMrn types.String `tfsdk:"mrn"` + Name types.String `tfsdk:"name"` +} + +func (d *SpaceDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_space" +} + +func (d *SpaceDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Space data source", + + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: "Space ID", + Computed: true, + Optional: true, + }, + "mrn": schema.StringAttribute{ + MarkdownDescription: "Space MRN", + Computed: true, + Optional: true, + }, + "name": schema.StringAttribute{ + MarkdownDescription: "Space name", + Computed: true, + }, + }, + } +} + +func (d *SpaceDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*mondoov1.Client) + + if !ok { + resp.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + fmt.Sprintf("Expected *mondoov1.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + + return + } + + d.client = &ExtendedGqlClient{client} +} + +func (d *SpaceDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data SpaceDataSourceModel + + // Read Terraform configuration data into the model + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // we fetch the organization id from the service account + spaceMrn := "" + if data.SpaceMrn.ValueString() != "" { + spaceMrn = data.SpaceMrn.ValueString() + } else if data.SpaceMrn.ValueString() != "" { + spaceMrn = orgPrefix + data.SpaceID.ValueString() + } + if spaceMrn == "" { + resp.Diagnostics.AddError("Invalid Configuration", "Either `id` or `mrn` must be set") + return + } + + payload, err := d.client.GetSpace(ctx, spaceMrn) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to fetch organization, got error: %s", err)) + return + } + + // For the purposes of this example code, hardcoding a response value to + // save into the Terraform state. + data.SpaceID = types.StringValue(payload.Id) + data.SpaceMrn = types.StringValue(payload.Mrn) + data.Name = types.StringValue(payload.Name) + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} diff --git a/internal/provider/space_data_source_test.go b/internal/provider/space_data_source_test.go new file mode 100644 index 0000000..5325424 --- /dev/null +++ b/internal/provider/space_data_source_test.go @@ -0,0 +1,53 @@ +// Copyright (c) Mondoo, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package provider + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccSpaceDataSource(t *testing.T) { + orgID, err := getOrgId() + if err != nil { + t.Fatal(err) + } + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Read testing + { + Config: testAccSpaceDataSourceConfig(orgID), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("data.mondoo_space.space", "name", "test-space"), + ), + }, + }, + }) +} + +func testAccSpaceDataSourceConfig(orgId string) string { + return fmt.Sprintf(` + +resource "mondoo_space" "test" { + org_id = %[1]q + name = "test-space" +} + +data "mondoo_space" "space"{ + mrn = mondoo_space.test.mrn + + depends_on = [ + mondoo_space.test + ] +} + +output "space_name" { + value = data.mondoo_space.space.name +} +`, orgId) +}