Skip to content

Commit

Permalink
Merge pull request #181 from deploymenttheory/feat_intune_settings_te…
Browse files Browse the repository at this point in the history
…mplates

Feat refactoring resource func patterns and added template resource example
  • Loading branch information
ShocOne authored Nov 17, 2024
2 parents dfda42a + 018e5ac commit 8c5519f
Show file tree
Hide file tree
Showing 76 changed files with 1,091 additions and 362 deletions.
4 changes: 2 additions & 2 deletions internal/provider/resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
graphBetaDeviceAndAppManagementM365AppsInstallationOptions "github.com/deploymenttheory/terraform-provider-microsoft365/internal/resources/device_and_app_management/beta/m365_apps_installation_options"
graphBetaDeviceAndAppManagementMobileAppAssignment "github.com/deploymenttheory/terraform-provider-microsoft365/internal/resources/device_and_app_management/beta/mobile_app_assignment"
graphBetaDeviceAndAppManagementRoleDefinition "github.com/deploymenttheory/terraform-provider-microsoft365/internal/resources/device_and_app_management/beta/role_definition"
graphBetaDeviceAndAppManagementSettingsCatalogV3 "github.com/deploymenttheory/terraform-provider-microsoft365/internal/resources/device_and_app_management/beta/settings_catalog_v3"
graphBetaDeviceAndAppManagementSettingsCatalog "github.com/deploymenttheory/terraform-provider-microsoft365/internal/resources/device_and_app_management/beta/settings_catalog_v3"
graphBetaDeviceAndAppManagementWinGetApp "github.com/deploymenttheory/terraform-provider-microsoft365/internal/resources/device_and_app_management/beta/winget_app"
graphDeviceAndAppManagementCloudPcDeviceImage "github.com/deploymenttheory/terraform-provider-microsoft365/internal/resources/device_and_app_management/v1.0/cloud_pc_device_image"
graphDeviceAndAppManagementCloudPcProvisioningPolicy "github.com/deploymenttheory/terraform-provider-microsoft365/internal/resources/device_and_app_management/v1.0/cloud_pc_provisioning_policy"
Expand Down Expand Up @@ -40,7 +40,7 @@ func (p *M365Provider) Resources(ctx context.Context) []func() resource.Resource
graphBetaDeviceAndAppManagementDeviceManagementScript.NewDeviceManagementScriptResource,
graphBetaDeviceAndAppManagementM365AppsInstallationOptions.NewM365AppsInstallationOptionsResource,
graphBetaDeviceAndAppManagementMobileAppAssignment.NewMobileAppAssignmentResource,
graphBetaDeviceAndAppManagementSettingsCatalogV3.NewSettingsCatalogResource,
graphBetaDeviceAndAppManagementSettingsCatalog.NewSettingsCatalogResource,
graphBetaDeviceAndAppManagementRoleDefinition.NewRoleDefinitionResource,
graphBetaDeviceAndAppManagementWinGetApp.NewWinGetAppResource,
graphBetaIdentityAndAccessConditionalAccessPolicy.NewConditionalAccessPolicyResource,
Expand Down
33 changes: 33 additions & 0 deletions internal/resources/_resource_template/construct.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package graphBetaAssignmentFilter

import (
"context"
"fmt"

"github.com/deploymenttheory/terraform-provider-microsoft365/internal/resources/common/construct"
"github.com/hashicorp/terraform-plugin-log/tflog"

"github.com/microsoftgraph/msgraph-beta-sdk-go/models"
)

// constructResource maps the Terraform schema to the SDK model
func constructResource(ctx context.Context, typeName string, data *AssignmentFilterResourceModel) (*models.DeviceAndAppManagementAssignmentFilter, error) {
tflog.Debug(ctx, fmt.Sprintf("Constructing %s resource from model", ResourceName))

requestBody := models.NewDeviceAndAppManagementAssignmentFilter()

displayName := data.DisplayName.ValueString()
requestBody.SetDisplayName(&displayName)

// add more fields here as needed

if err := construct.DebugLogGraphObject(ctx, fmt.Sprintf("Final JSON to be sent to Graph API for resource %s", ResourceName), requestBody); err != nil {
tflog.Error(ctx, "Failed to debug log object", map[string]interface{}{
"error": err.Error(),
})
}

tflog.Debug(ctx, fmt.Sprintf("Finished constructing %s resource", ResourceName))

return requestBody, nil
}
179 changes: 179 additions & 0 deletions internal/resources/_resource_template/crud.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
package graphVersionResourceTemplate

import (
"context"
"fmt"
"time"

"github.com/deploymenttheory/terraform-provider-microsoft365/internal/resources/common/crud"
"github.com/deploymenttheory/terraform-provider-microsoft365/internal/resources/common/errors"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"
)

// Create handles the Create operation.
func (r *ResourceTemplateResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var plan ResourceTemplateResourceModel

tflog.Debug(ctx, fmt.Sprintf("Starting creation of resource: %s_%s", r.ProviderTypeName, r.TypeName))

resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
if resp.Diagnostics.HasError() {
return
}

ctx, cancel := crud.HandleTimeout(ctx, plan.Timeouts.Create, 30*time.Second, &resp.Diagnostics)
if cancel == nil {
return
}
defer cancel()

requestBody, err := constructResource(ctx, r.TypeName, &plan)
if err != nil {
resp.Diagnostics.AddError(
"Error constructing resource",
fmt.Sprintf("Could not construct resource: %s_%s: %s", r.ProviderTypeName, r.TypeName, err.Error()),
)
return
}

resource, err := r.client.
DeviceManagement().
ResourceTemplates().
Post(ctx, requestBody, nil)

if err != nil {
errors.HandleGraphError(ctx, err, resp, "Create", r.WritePermissions)
return
}

plan.ID = types.StringValue(*resource.GetId())

MapRemoteStateToTerraform(ctx, &plan, resource)

resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
if resp.Diagnostics.HasError() {
return
}

tflog.Debug(ctx, fmt.Sprintf("Finished Create Method: %s_%s", r.ProviderTypeName, r.TypeName))
}

// Read handles the Read operation.
func (r *ResourceTemplateResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
var state ResourceTemplateResourceModel
tflog.Debug(ctx, fmt.Sprintf("Starting Read method for: %s_%s", r.ProviderTypeName, r.TypeName))

resp.Diagnostics.Append(req.State.Get(ctx, &state)...)

if resp.Diagnostics.HasError() {
return
}

tflog.Debug(ctx, fmt.Sprintf("Reading %s_%s with ID: %s", r.ProviderTypeName, r.TypeName, state.ID.ValueString()))

ctx, cancel := crud.HandleTimeout(ctx, state.Timeouts.Read, 30*time.Second, &resp.Diagnostics)
if cancel == nil {
return
}
defer cancel()

resource, err := r.client.
DeviceManagement().
ResourceTemplates().
ByDeviceAndAppManagementResourceTemplateId(state.ID.ValueString()).
Get(ctx, nil)

if err != nil {
errors.HandleGraphError(ctx, err, resp, "Read", r.ReadPermissions)
return
}

MapRemoteStateToTerraform(ctx, &state, resource)

resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}

tflog.Debug(ctx, fmt.Sprintf("Finished Read Method: %s_%s", r.ProviderTypeName, r.TypeName))
}

// Update handles the Update operation.
func (r *ResourceTemplateResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
var plan ResourceTemplateResourceModel

tflog.Debug(ctx, fmt.Sprintf("Starting Update of resource: %s_%s", r.ProviderTypeName, r.TypeName))

resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
if resp.Diagnostics.HasError() {
return
}

ctx, cancel := crud.HandleTimeout(ctx, plan.Timeouts.Update, 30*time.Second, &resp.Diagnostics)
if cancel == nil {
return
}
defer cancel()

requestBody, err := constructResource(ctx, r.TypeName, &plan)
if err != nil {
resp.Diagnostics.AddError(
"Error constructing resource for update method",
fmt.Sprintf("Could not construct resource: %s_%s: %s", r.ProviderTypeName, r.TypeName, err.Error()),
)
return
}

_, err = r.client.
DeviceManagement().
ResourceTemplates().
ByDeviceAndAppManagementResourceTemplateId(plan.ID.ValueString()).
Patch(ctx, requestBody, nil)

if err != nil {
errors.HandleGraphError(ctx, err, resp, "Update", r.ReadPermissions)
return
}

resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
if resp.Diagnostics.HasError() {
return
}

tflog.Debug(ctx, fmt.Sprintf("Finished Update Method: %s_%s", r.ProviderTypeName, r.TypeName))
}

// Delete handles the Delete operation.
func (r *ResourceTemplateResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
var data ResourceTemplateResourceModel

tflog.Debug(ctx, fmt.Sprintf("Starting deletion of resource: %s_%s", r.ProviderTypeName, r.TypeName))

resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}

ctx, cancel := crud.HandleTimeout(ctx, data.Timeouts.Delete, 30*time.Second, &resp.Diagnostics)
if cancel == nil {
return
}
defer cancel()

err := r.client.
DeviceManagement().
ResourceTemplates().
ByDeviceAndAppManagementResourceTemplateId(data.ID.ValueString()).
Delete(ctx, nil)

if err != nil {
errors.HandleGraphError(ctx, err, resp, "Delete", r.ReadPermissions)
return
}

resp.State.RemoveResource(ctx)

tflog.Debug(ctx, fmt.Sprintf("Finished Delete Method: %s_%s", r.ProviderTypeName, r.TypeName))
}
13 changes: 13 additions & 0 deletions internal/resources/_resource_template/model.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// REF: include the graph api docs link for the resource type here
package graphVersionResourceTemplate

import (
"github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts"
"github.com/hashicorp/terraform-plugin-framework/types"
)

type ResourceTemplateResourceModel struct {
ID types.String `tfsdk:"id"`
etc types.String `tfsdk:"etc"`
Timeouts timeouts.Value `tfsdk:"timeouts"`
}
17 changes: 17 additions & 0 deletions internal/resources/_resource_template/modify_plan.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package graphBetaWinGetApp

import (
"context"

"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-log/tflog"
)

// ModifyPlan handles plan modification for diff suppression
func (r *ResourceTemplateResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) {
if req.State.Raw.IsNull() || req.Plan.Raw.IsNull() {
return
}

tflog.Debug(ctx, "Modify Plan Place holder")
}
82 changes: 82 additions & 0 deletions internal/resources/_resource_template/resource.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package graphVersionResourceTemplate

import (
"context"

"github.com/deploymenttheory/terraform-provider-microsoft365/internal/resources/common"
commonschema "github.com/deploymenttheory/terraform-provider-microsoft365/internal/resources/common/schema"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
msgraphbetasdk "github.com/microsoftgraph/msgraph-beta-sdk-go"
)

const (
ResourceName = "graph_apitype_resource_type_resource_name"
)

var (
// Basic resource interface (CRUD operations)
_ resource.Resource = &ResourceTemplateResource{}

// Allows the resource to be configured with the provider client
_ resource.ResourceWithConfigure = &ResourceTemplateResource{}

// Enables import functionality
_ resource.ResourceWithImportState = &ResourceTemplateResource{}

// Enables plan modification/diff suppression
_ resource.ResourceWithModifyPlan = &ResourceTemplateResource{}
)

func NewResourceTemplateResource() resource.Resource {
return &ResourceTemplateResource{
ReadPermissions: []string{
"DeviceManagementConfiguration.Read.All",
},
WritePermissions: []string{
"DeviceManagementConfiguration.ReadWrite.All",
},
}
}

type ResourceTemplateResource struct {
client *msgraphbetasdk.GraphServiceClient
ProviderTypeName string
TypeName string
ReadPermissions []string
WritePermissions []string
}

// Metadata returns the resource type name.
func (r *ResourceTemplateResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_" + ResourceName
}

// Configure sets the client for the resource.
func (r *ResourceTemplateResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
r.client = common.SetGraphBetaClientForResource(ctx, req, resp, r.TypeName)
}

// ImportState imports the resource state.
func (r *ResourceTemplateResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
}

// Function to create the full device management win32 lob app schema
func (r *ResourceTemplateResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
Description: "The resource `resource_name` manages a graph api resource of type `resource_name`",
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Description: "Unique Identifier for the resource.",
Computed: true,
},
"etc": schema.StringAttribute{
Description: "Add schema from here.",
Computed: true,
},
"timeouts": commonschema.Timeouts(ctx),
},
}
}
28 changes: 28 additions & 0 deletions internal/resources/_resource_template/state.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package graphVersionResourceTemplate

import (
"context"

"github.com/deploymenttheory/terraform-provider-microsoft365/internal/resources/common/state"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"
graphmodels "github.com/microsoftgraph/msgraph-beta-sdk-go/models"
)

func MapRemoteStateToTerraform(ctx context.Context, data *ResourceTemplateResourceModel, remoteResource graphmodels.DeviceAndAppManagementAssignmentFilterable) {
if remoteResource == nil {
tflog.Debug(ctx, "Remote resource is nil")
return
}

tflog.Debug(ctx, "Starting to map remote state to Terraform state", map[string]interface{}{
"resourceId": state.StringPtrToString(remoteResource.GetId()),
})

data.ID = types.StringValue(state.StringPtrToString(remoteResource.GetId()))
// add more fields here as needed

tflog.Debug(ctx, "Finished mapping remote state to Terraform state", map[string]interface{}{
"resourceId": data.ID.ValueString(),
})
}
Loading

0 comments on commit 8c5519f

Please sign in to comment.