diff --git a/internal/resources/devicemanagement/beta/assignmentFilters/helpers.go b/internal/resources/devicemanagement/beta/assignmentFilters/helpers.go index 3458e456..52783794 100644 --- a/internal/resources/devicemanagement/beta/assignmentFilters/helpers.go +++ b/internal/resources/devicemanagement/beta/assignmentFilters/helpers.go @@ -1,42 +1,42 @@ package assignmentFilter -import ( - "fmt" +// import ( +// "fmt" - "github.com/microsoftgraph/msgraph-beta-sdk-go/models" -) +// "github.com/microsoftgraph/msgraph-beta-sdk-go/models" +// ) -// StringToDevicePlatformType converts a string to DevicePlatformType. -func StringToDevicePlatformType(platformStr string, supportedPlatformTypes map[string]models.DevicePlatformType) (*models.DevicePlatformType, error) { - platform, exists := supportedPlatformTypes[platformStr] - if !exists { - return nil, fmt.Errorf("unsupported platform type: %s", platformStr) - } - return &platform, nil -} +// // StringToDevicePlatformType converts a string to DevicePlatformType. +// func StringToDevicePlatformType(platformStr string, supportedPlatformTypes map[string]models.DevicePlatformType) (*models.DevicePlatformType, error) { +// platform, exists := supportedPlatformTypes[platformStr] +// if !exists { +// return nil, fmt.Errorf("unsupported platform type: %s", platformStr) +// } +// return &platform, nil +// } -// DevicePlatformTypeToString converts a DevicePlatformType to its string representation. -func DevicePlatformTypeToString(platform *models.DevicePlatformType) (string, error) { - if platform == nil { - return "", fmt.Errorf("platform is nil") - } - return platform.String(), nil -} +// // DevicePlatformTypeToString converts a DevicePlatformType to its string representation. +// func DevicePlatformTypeToString(platform *models.DevicePlatformType) (string, error) { +// if platform == nil { +// return "", fmt.Errorf("platform is nil") +// } +// return platform.String(), nil +// } -// supportedPlatformTypes is a map of string representations to their corresponding platform types. -var supportedPlatformTypes = map[string]models.DevicePlatformType{ - "android": models.ANDROID_DEVICEPLATFORMTYPE, - "androidForWork": models.ANDROIDFORWORK_DEVICEPLATFORMTYPE, - "iOS": models.IOS_DEVICEPLATFORMTYPE, - "macOS": models.MACOS_DEVICEPLATFORMTYPE, - "windowsPhone81": models.WINDOWSPHONE81_DEVICEPLATFORMTYPE, - "windows81AndLater": models.WINDOWS81ANDLATER_DEVICEPLATFORMTYPE, - "windows10AndLater": models.WINDOWS10ANDLATER_DEVICEPLATFORMTYPE, - "androidWorkProfile": models.ANDROIDWORKPROFILE_DEVICEPLATFORMTYPE, - "unknown": models.UNKNOWN_DEVICEPLATFORMTYPE, - "androidAOSP": models.ANDROIDAOSP_DEVICEPLATFORMTYPE, - "androidMobileApplicationManagement": models.ANDROIDMOBILEAPPLICATIONMANAGEMENT_DEVICEPLATFORMTYPE, - "iOSMobileApplicationManagement": models.IOSMOBILEAPPLICATIONMANAGEMENT_DEVICEPLATFORMTYPE, - "unknownFutureValue": models.UNKNOWNFUTUREVALUE_DEVICEPLATFORMTYPE, - "windowsMobileApplicationManagement": models.WINDOWSMOBILEAPPLICATIONMANAGEMENT_DEVICEPLATFORMTYPE, -} +// // supportedPlatformTypes is a map of string representations to their corresponding platform types. +// var supportedPlatformTypes = map[string]models.DevicePlatformType{ +// "android": models.ANDROID_DEVICEPLATFORMTYPE, +// "androidForWork": models.ANDROIDFORWORK_DEVICEPLATFORMTYPE, +// "iOS": models.IOS_DEVICEPLATFORMTYPE, +// "macOS": models.MACOS_DEVICEPLATFORMTYPE, +// "windowsPhone81": models.WINDOWSPHONE81_DEVICEPLATFORMTYPE, +// "windows81AndLater": models.WINDOWS81ANDLATER_DEVICEPLATFORMTYPE, +// "windows10AndLater": models.WINDOWS10ANDLATER_DEVICEPLATFORMTYPE, +// "androidWorkProfile": models.ANDROIDWORKPROFILE_DEVICEPLATFORMTYPE, +// "unknown": models.UNKNOWN_DEVICEPLATFORMTYPE, +// "androidAOSP": models.ANDROIDAOSP_DEVICEPLATFORMTYPE, +// "androidMobileApplicationManagement": models.ANDROIDMOBILEAPPLICATIONMANAGEMENT_DEVICEPLATFORMTYPE, +// "iOSMobileApplicationManagement": models.IOSMOBILEAPPLICATIONMANAGEMENT_DEVICEPLATFORMTYPE, +// "unknownFutureValue": models.UNKNOWNFUTUREVALUE_DEVICEPLATFORMTYPE, +// "windowsMobileApplicationManagement": models.WINDOWSMOBILEAPPLICATIONMANAGEMENT_DEVICEPLATFORMTYPE, +// } diff --git a/internal/resources/devicemanagement/beta/assignmentFilters/object.go b/internal/resources/devicemanagement/beta/assignmentFilters/object.go new file mode 100644 index 00000000..34953b36 --- /dev/null +++ b/internal/resources/devicemanagement/beta/assignmentFilters/object.go @@ -0,0 +1,84 @@ +package assignmentFilter + +import ( + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/microsoftgraph/msgraph-beta-sdk-go/models" +) + +// constructResource maps the Terraform schema to the SDK model +func constructResource(data *AssignmentFilterResourceModel) (*models.DeviceAndAppManagementAssignmentFilter, error) { + requestBody := models.NewDeviceAndAppManagementAssignmentFilter() + + // Set DisplayName + displayName := data.DisplayName.ValueString() + requestBody.SetDisplayName(&displayName) + + // Set Description + if !data.Description.IsNull() { + description := data.Description.ValueString() + requestBody.SetDescription(&description) + } + + // Set Platform + if !data.Platform.IsNull() { + platformStr := data.Platform.ValueString() + platform, err := models.ParseDevicePlatformType(platformStr) + if err != nil { + return nil, fmt.Errorf("invalid platform: %s", err) + } + if platform != nil { + requestBody.SetPlatform(platform.(*models.DevicePlatformType)) + } + } + + // Set Rule + rule := data.Rule.ValueString() + requestBody.SetRule(&rule) + + // Set AssignmentFilterManagementType + if !data.AssignmentFilterManagementType.IsNull() { + assignmentFilterManagementTypeStr := data.AssignmentFilterManagementType.ValueString() + assignmentFilterManagementType, err := models.ParseAssignmentFilterManagementType(assignmentFilterManagementTypeStr) + if err != nil { + return nil, fmt.Errorf("invalid assignment filter management type: %s", err) + } + if assignmentFilterManagementType != nil { + requestBody.SetAssignmentFilterManagementType(assignmentFilterManagementType.(*models.AssignmentFilterManagementType)) + } + } + + // Set RoleScopeTags + if !data.RoleScopeTags.IsNull() { + var roleScopeTags []string + for _, tag := range data.RoleScopeTags.Elements() { + roleScopeTags = append(roleScopeTags, tag.(types.String).Value) + } + requestBody.SetRoleScopeTags(roleScopeTags) + } + + // Set Payloads + if !data.Payloads.IsNull() { + var payloads []models.PayloadByFilterable + for _, payloadElement := range data.Payloads.Elements() { + payload := payloadElement.(types.Object) + payloadID := payload.Attributes["payload_id"].(types.String).ValueString() + payloadType := payload.Attributes["payload_type"].(types.String).ValueString() + groupID := payload.Attributes["group_id"].(types.String).ValueString() + assignmentFilterType := payload.Attributes["assignment_filter_type"].(types.String).ValueString() + + payloadModel := models.NewPayloadCompatibleAssignmentFilter() + payloadModel.SetPayloadId(&payloadID) + payloadModel.SetPayloadType(&payloadType) + payloadModel.SetGroupId(&groupID) + payloadModel.SetAssignmentFilterType(&assignmentFilterType) + + payloads = append(payloads, payloadModel) + } + requestBody.SetPayloads(payloads) + } + + return requestBody, nil +} diff --git a/internal/resources/devicemanagement/beta/assignmentFilters/resource.go b/internal/resources/devicemanagement/beta/assignmentFilters/resource.go index 2fb8dfcc..d0d2f468 100644 --- a/internal/resources/devicemanagement/beta/assignmentFilters/resource.go +++ b/internal/resources/devicemanagement/beta/assignmentFilters/resource.go @@ -1,3 +1,4 @@ +// REF: https://learn.microsoft.com/en-us/graph/api/resources/intune-policyset-deviceandappmanagementassignmentfilter?view=graph-rest-beta package assignmentFilter import ( @@ -10,24 +11,38 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" msgraphbetasdk "github.com/microsoftgraph/msgraph-beta-sdk-go" - "github.com/microsoftgraph/msgraph-beta-sdk-go/models" ) var _ resource.Resource = &AssignmentFilterResource{} var _ resource.ResourceWithImportState = &AssignmentFilterResource{} +func NewUserResource() resource.Resource { + return &AssignmentFilterResource{ + ProviderTypeName: "microsoft365", + TypeName: "_device_and_app_management_assignment_filter", + } +} + type AssignmentFilterResource struct { - client *msgraphbetasdk.GraphServiceClient + client *msgraphbetasdk.GraphServiceClient + ProviderTypeName string + TypeName string } type AssignmentFilterResourceModel struct { - ID types.String `tfsdk:"id"` - DisplayName types.String `tfsdk:"display_name"` - Description types.String `tfsdk:"description"` - Platform types.String `tfsdk:"platform"` - Rule types.String `tfsdk:"rule"` + ID types.String `tfsdk:"id"` + DisplayName types.String `tfsdk:"display_name"` + Description types.String `tfsdk:"description"` + Platform types.String `tfsdk:"platform"` + Rule types.String `tfsdk:"rule"` + AssignmentFilterManagementType types.String `tfsdk:"assignment_filter_management_type"` + CreatedDateTime types.String `tfsdk:"created_date_time"` + LastModifiedDateTime types.String `tfsdk:"last_modified_date_time"` + RoleScopeTags types.List `tfsdk:"role_scope_tags"` + Payloads types.List `tfsdk:"payloads"` } // Metadata returns the resource type name. @@ -54,18 +69,63 @@ func (r *AssignmentFilterResource) Schema(ctx context.Context, req resource.Sche }, "description": schema.StringAttribute{ Optional: true, - Description: "The description of the assignment filter.", + Description: "The optional description of the assignment filter.", }, "platform": schema.StringAttribute{ Required: true, - Description: "The platform for the assignment filter.", + Description: fmt.Sprintf("The Intune device management type (platform) for the assignment filter. Supported types: %v", getAllPlatformStrings()), Validators: []validator.String{ platformValidator{}, }, }, "rule": schema.StringAttribute{ Required: true, - Description: "The rule for the assignment filter.", + Description: "Rule definition of the assignment filter.", + }, + "assignment_filter_management_type": schema.StringAttribute{ + Optional: true, + Description: fmt.Sprintf("Indicates filter is applied to either 'devices' or 'apps' management type. Possible values are: %v. Default filter will be applied to 'devices'.", getAllManagementTypeStrings()), + Validators: []validator.String{ + assignmentFilterManagementTypeValidator{}, + }, + }, + + "created_date_time": schema.StringAttribute{ + Computed: true, + Description: "The creation time of the assignment filter.", + }, + "last_modified_date_time": schema.StringAttribute{ + Computed: true, + Description: "Last modified time of the assignment filter.", + }, + "role_scope_tags": schema.ListAttribute{ + Optional: true, + Description: "Indicates role scope tags assigned for the assignment filter.", + ElementType: types.StringType, + }, + "payloads": schema.ListNestedAttribute{ + Optional: true, + Description: "Indicates associated assignments for a specific filter.", + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "payload_id": schema.StringAttribute{ + Required: true, + Description: "The ID of the payload.", + }, + "payload_type": schema.StringAttribute{ + Required: true, + Description: "The type of the payload.", + }, + "group_id": schema.StringAttribute{ + Required: true, + Description: "The group ID associated with the payload.", + }, + "assignment_filter_type": schema.StringAttribute{ + Required: true, + Description: "The assignment filter type.", + }, + }, + }, }, }, } @@ -73,38 +133,27 @@ func (r *AssignmentFilterResource) Schema(ctx context.Context, req resource.Sche // Create handles the Create operation. func (r *AssignmentFilterResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { - ctx, cancel := context.WithTimeout(ctx, 2*time.Minute) + ctx, cancel := context.WithTimeout(ctx, 1*time.Minute) defer cancel() var data AssignmentFilterResourceModel + + tflog.Debug(ctx, fmt.Sprintf("Starting creation of resource: %s_%s", r.ProviderTypeName, r.TypeName)) + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) if resp.Diagnostics.HasError() { return } - requestBody := models.NewDeviceAndAppManagementAssignmentFilter() - displayName := data.DisplayName.ValueString() - requestBody.SetDisplayName(&displayName) - - description := data.Description.ValueString() - requestBody.SetDescription(&description) + requestBody, err := constructResource(&data) - platformStr := data.Platform.ValueString() - platform, err := StringToDevicePlatformType(platformStr, supportedPlatformTypes) if err != nil { resp.Diagnostics.AddError( - "Error creating assignment filter", - fmt.Sprintf("Invalid platform: %s", err.Error()), + "Error constructing assignment filter", + fmt.Sprintf("Could not construct resource: %s_%s: %s", r.ProviderTypeName, r.TypeName, err.Error()), ) return } - requestBody.SetPlatform(platform) - - rule := data.Rule.ValueString() - requestBody.SetRule(&rule) - - roleScopeTags := []string{"0"} - requestBody.SetRoleScopeTags(roleScopeTags) assignmentFilter, err := r.client.DeviceManagement().AssignmentFilters().Post(ctx, requestBody, nil) if err != nil { @@ -116,15 +165,20 @@ func (r *AssignmentFilterResource) Create(ctx context.Context, req resource.Crea } data.ID = types.StringValue(*assignmentFilter.GetId()) + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) + + tflog.Debug(ctx, fmt.Sprintf("Finished creation of resource: %s_%s", r.ProviderTypeName, r.TypeName)) } -// Read handles the Read operation. func (r *AssignmentFilterResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { ctx, cancel := context.WithTimeout(ctx, 1*time.Minute) defer cancel() var data AssignmentFilterResourceModel + + tflog.Debug(ctx, fmt.Sprintf("Starting read of resource: %s_%s", r.ProviderTypeName, r.TypeName)) + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) if resp.Diagnostics.HasError() { return @@ -139,19 +193,13 @@ func (r *AssignmentFilterResource) Read(ctx context.Context, req resource.ReadRe return } - data.DisplayName = types.StringValue(*filter.GetDisplayName()) - data.Description = types.StringValue(*filter.GetDescription()) - platformStr, err := DevicePlatformTypeToString(filter.GetPlatform()) - if err != nil { - resp.Diagnostics.AddError( - "Error reading assignment filter", - fmt.Sprintf("Could not convert platform: %s", err.Error()), - ) - return - } - data.Platform = types.StringValue(platformStr) - data.Rule = types.StringValue(*filter.GetRule()) + setTerraformState(&data, filter, resp) + + tflog.Debug(ctx, fmt.Sprintf("READ: %s_environment with id %s", r.ProviderTypeName, data.ID.ValueString())) + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) + + tflog.Debug(ctx, fmt.Sprintf("Finished read of resource: %s_%s", r.ProviderTypeName, r.TypeName)) } // Update handles the Update operation. @@ -160,34 +208,23 @@ func (r *AssignmentFilterResource) Update(ctx context.Context, req resource.Upda defer cancel() var data AssignmentFilterResourceModel + + tflog.Debug(ctx, fmt.Sprintf("Starting Update of resource: %s_%s", r.ProviderTypeName, r.TypeName)) + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) if resp.Diagnostics.HasError() { return } - requestBody := models.NewDeviceAndAppManagementAssignmentFilter() - displayName := data.DisplayName.ValueString() - requestBody.SetDisplayName(&displayName) - - description := data.Description.ValueString() - requestBody.SetDescription(&description) + requestBody, err := constructResource(&data) - platformStr := data.Platform.ValueString() - platform, err := StringToDevicePlatformType(platformStr, supportedPlatformTypes) if err != nil { resp.Diagnostics.AddError( - "Error creating assignment filter", - fmt.Sprintf("Invalid platform: %s", err.Error()), + "Error constructing assignment filter", + fmt.Sprintf("Could not construct resource: %s_%s: %s", r.ProviderTypeName, r.TypeName, err.Error()), ) return } - requestBody.SetPlatform(platform) - - rule := data.Rule.ValueString() - requestBody.SetRule(&rule) - - roleScopeTags := []string{"0"} // Adjust if necessary - requestBody.SetRoleScopeTags(roleScopeTags) _, err = r.client.DeviceManagement().AssignmentFilters().ByDeviceAndAppManagementAssignmentFilterId(data.ID.ValueString()).Patch(ctx, requestBody, nil) if err != nil { @@ -199,6 +236,8 @@ func (r *AssignmentFilterResource) Update(ctx context.Context, req resource.Upda } resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) + + tflog.Debug(ctx, fmt.Sprintf("Finished Update of resource: %s_%s", r.ProviderTypeName, r.TypeName)) } // Delete handles the Delete operation. @@ -207,6 +246,9 @@ func (r *AssignmentFilterResource) Delete(ctx context.Context, req resource.Dele defer cancel() var data AssignmentFilterResourceModel + + 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 @@ -214,12 +256,11 @@ func (r *AssignmentFilterResource) Delete(ctx context.Context, req resource.Dele err := r.client.DeviceManagement().AssignmentFilters().ByDeviceAndAppManagementAssignmentFilterId(data.ID.ValueString()).Delete(ctx, nil) if err != nil { - resp.Diagnostics.AddError( - "Error deleting assignment filter", - fmt.Sprintf("Could not delete assignment filter: %s", err.Error()), - ) + resp.Diagnostics.AddError(fmt.Sprintf("Client error when deleting %s_%s", r.ProviderTypeName, r.TypeName), err.Error()) return } + tflog.Debug(ctx, fmt.Sprintf("Completed deletion of resource: %s_%s", r.ProviderTypeName, r.TypeName)) + resp.State.RemoveResource(ctx) } diff --git a/internal/resources/devicemanagement/beta/assignmentFilters/resource_model.json b/internal/resources/devicemanagement/beta/assignmentFilters/resource_model.json new file mode 100644 index 00000000..13f1ad53 --- /dev/null +++ b/internal/resources/devicemanagement/beta/assignmentFilters/resource_model.json @@ -0,0 +1,23 @@ +{ + "@odata.type": "#microsoft.graph.deviceAndAppManagementAssignmentFilter", + "id": "String (identifier)", + "createdDateTime": "String (timestamp)", + "lastModifiedDateTime": "String (timestamp)", + "displayName": "String", + "description": "String", + "platform": "String", + "rule": "String", + "roleScopeTags": [ + "String" + ], + "payloads": [ + { + "@odata.type": "microsoft.graph.payloadByFilter", + "payloadId": "String", + "payloadType": "String", + "groupId": "String", + "assignmentFilterType": "String" + } + ], + "assignmentFilterManagementType": "String" +} \ No newline at end of file diff --git a/internal/resources/devicemanagement/beta/assignmentFilters/state.go b/internal/resources/devicemanagement/beta/assignmentFilters/state.go new file mode 100644 index 00000000..399f1450 --- /dev/null +++ b/internal/resources/devicemanagement/beta/assignmentFilters/state.go @@ -0,0 +1,33 @@ +package assignmentFilter + +import ( + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/microsoftgraph/msgraph-beta-sdk-go/models" +) + +func setTerraformState(data *AssignmentFilterResourceModel, filter models.DeviceAndAppManagementAssignmentFilterable, resp *resource.ReadResponse) { + data.DisplayName = types.StringValue(*filter.GetDisplayName()) + data.Description = types.StringValue(*filter.GetDescription()) + data.Platform = types.StringValue(filter.GetPlatform().String()) + data.Rule = types.StringValue(*filter.GetRule()) + data.AssignmentFilterManagementType = types.StringValue(filter.GetAssignmentFilterManagementType().String()) + data.CreatedDateTime = types.StringValue(filter.GetCreatedDateTime().String()) + data.LastModifiedDateTime = types.StringValue(filter.GetLastModifiedDateTime().String()) + + // Set RoleScopeTags + roleScopeTags := filter.GetRoleScopeTags() + if roleScopeTags != nil { + tagList := make([]attr.Value, len(roleScopeTags)) + for i, tag := range roleScopeTags { + tagList[i] = types.StringValue(tag) + } + roleScopeTagsList := types.ListValueMust(types.StringType, tagList) + data.RoleScopeTags = roleScopeTagsList + } else { + roleScopeTagsList := types.ListValueMust(types.StringType, []attr.Value{}) + data.RoleScopeTags = roleScopeTagsList + } + +} diff --git a/internal/resources/devicemanagement/beta/assignmentFilters/validators.go b/internal/resources/devicemanagement/beta/assignmentFilters/validators.go index 8deaa4a1..01206867 100644 --- a/internal/resources/devicemanagement/beta/assignmentFilters/validators.go +++ b/internal/resources/devicemanagement/beta/assignmentFilters/validators.go @@ -61,3 +61,41 @@ func getAllPlatformStrings() []string { } return platformStrings } + +// assignmentFilterManagementTypeValidator is the custom validator type +type assignmentFilterManagementTypeValidator struct{} + +// ValidateString performs the validation. +func (v assignmentFilterManagementTypeValidator) ValidateString(ctx context.Context, req validator.StringRequest, resp *validator.StringResponse) { + if req.ConfigValue.IsUnknown() || req.ConfigValue.IsNull() { + return + } + + validTypes := getAllManagementTypeStrings() + value := req.ConfigValue.ValueString() + for _, validType := range validTypes { + if value == validType { + return + } + } + + resp.Diagnostics.AddError( + "Invalid Assignment Filter Management Type", + fmt.Sprintf("The management type '%s' is not valid. Supported types: %v", value, validTypes), + ) +} + +// Description describes the validation in plain text. +func (v assignmentFilterManagementTypeValidator) Description(ctx context.Context) string { + return "must be a valid assignment filter management type" +} + +// MarkdownDescription describes the validation in Markdown. +func (v assignmentFilterManagementTypeValidator) MarkdownDescription(ctx context.Context) string { + return "must be a valid assignment filter management type" +} + +// getAllManagementTypeStrings returns all the valid management type strings +func getAllManagementTypeStrings() []string { + return []string{"devices", "apps", "unknownFutureValue"} +}