diff --git a/pkg/awsutil/session.go b/pkg/awsutil/session.go index d44421af..d10b9e9a 100644 --- a/pkg/awsutil/session.go +++ b/pkg/awsutil/session.go @@ -15,6 +15,7 @@ import ( "github.com/aws/aws-sdk-go/aws/endpoints" "github.com/aws/aws-sdk-go/aws/request" "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/iottwinmaker" "github.com/aws/aws-sdk-go/service/s3control" "github.com/ekristen/aws-nuke/v3/pkg/config" @@ -242,6 +243,11 @@ func skipGlobalHandler(global bool) func(r *request.Request) { // Rewrite S3 Control ServiceName to proper EndpointsID // https://github.com/rebuy-de/aws-nuke/issues/708 } + if service == iottwinmaker.ServiceName { + service = iottwinmaker.EndpointsID + // IoTTwinMaker have two endpoints, must point on "api" one + // https://docs.aws.amazon.com/iot-twinmaker/latest/guide/endpionts-and-quotas.html + } rs, ok := endpoints.RegionsForService(endpoints.DefaultPartitions(), DefaultAWSPartitionID, service) if !ok { // This means that the service does not exist in the endpoints list. diff --git a/resources/iottwinmaker-component-type.go b/resources/iottwinmaker-component-type.go new file mode 100644 index 00000000..efed1918 --- /dev/null +++ b/resources/iottwinmaker-component-type.go @@ -0,0 +1,140 @@ +package resources + +import ( + "context" + "fmt" + "strings" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/iottwinmaker" + + "github.com/ekristen/libnuke/pkg/registry" + "github.com/ekristen/libnuke/pkg/resource" + "github.com/ekristen/libnuke/pkg/types" + + "github.com/ekristen/aws-nuke/v3/pkg/nuke" +) + +const IoTTwinMakerComponentTypeResource = "IoTTwinMakerComponentType" + +func init() { + registry.Register(®istry.Registration{ + Name: IoTTwinMakerComponentTypeResource, + Scope: nuke.Account, + Lister: &IoTTwinMakerComponentTypeLister{}, + }) +} + +type IoTTwinMakerComponentTypeLister struct{} + +func (l *IoTTwinMakerComponentTypeLister) List(_ context.Context, o interface{}) ([]resource.Resource, error) { + opts := o.(*nuke.ListerOpts) + + svc := iottwinmaker.New(opts.Session) + resources := make([]resource.Resource, 0) + + // Require to have workspaces identifiers to query components + workspaceListResponse, err := ListWorkspacesComponentType(svc) + + if err != nil { + return nil, err + } + + for _, workspaceResponse := range workspaceListResponse { + params := &iottwinmaker.ListComponentTypesInput{ + WorkspaceId: workspaceResponse.WorkspaceId, + MaxResults: aws.Int64(25), + } + + for { + resp, err := svc.ListComponentTypes(params) + if err != nil { + return nil, err + } + + for _, item := range resp.ComponentTypeSummaries { + // We must filter out amazon-owned component types when querying tags, + // because their ARN format causes ListTagsForResource to fail with validation error + tags := make(map[string]*string) + if !strings.Contains(*item.Arn, "AmazonOwnedTypesWorkspace") { + tagResp, err := svc.ListTagsForResource( + &iottwinmaker.ListTagsForResourceInput{ + ResourceARN: item.Arn, + }) + if err != nil { + return nil, err + } + tags = tagResp.Tags + } + + resources = append(resources, &IoTTwinMakerComponentType{ + svc: svc, + ID: item.ComponentTypeId, + arn: item.Arn, + Tags: tags, + WorkspaceID: workspaceResponse.WorkspaceId, + }) + } + + if resp.NextToken == nil { + break + } + + params.NextToken = resp.NextToken + } + } + + return resources, nil +} + +// Utility function to list workspaces +func ListWorkspacesComponentType(svc *iottwinmaker.IoTTwinMaker) ([]*iottwinmaker.WorkspaceSummary, error) { + resources := make([]*iottwinmaker.WorkspaceSummary, 0) + params := &iottwinmaker.ListWorkspacesInput{ + MaxResults: aws.Int64(25), + } + for { + resp, err := svc.ListWorkspaces(params) + if err != nil { + return nil, err + } + resources = append(resources, resp.WorkspaceSummaries...) + if resp.NextToken == nil { + break + } + params.NextToken = resp.NextToken + } + return resources, nil +} + +type IoTTwinMakerComponentType struct { + svc *iottwinmaker.IoTTwinMaker + ID *string + Tags map[string]*string + WorkspaceID *string + arn *string +} + +func (r *IoTTwinMakerComponentType) Filter() error { + if strings.Contains(*r.arn, "AmazonOwnedTypesWorkspace") { + return fmt.Errorf("cannot delete pre-defined component type") + } + return nil +} + +func (r *IoTTwinMakerComponentType) Properties() types.Properties { + return types.NewPropertiesFromStruct(r) +} + +func (r *IoTTwinMakerComponentType) Remove(_ context.Context) error { + _, err := r.svc.DeleteComponentType(&iottwinmaker.DeleteComponentTypeInput{ + ComponentTypeId: r.ID, + WorkspaceId: r.WorkspaceID, + }) + + return err +} + +func (r *IoTTwinMakerComponentType) String() string { + return *r.ID +} diff --git a/resources/iottwinmaker-entity.go b/resources/iottwinmaker-entity.go new file mode 100644 index 00000000..f18f3a22 --- /dev/null +++ b/resources/iottwinmaker-entity.go @@ -0,0 +1,120 @@ +package resources + +import ( + "context" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/iottwinmaker" + + "github.com/ekristen/libnuke/pkg/registry" + "github.com/ekristen/libnuke/pkg/resource" + "github.com/ekristen/libnuke/pkg/types" + + "github.com/ekristen/aws-nuke/v3/pkg/nuke" +) + +const IoTTwinMakerEntityResource = "IoTTwinMakerEntity" + +func init() { + registry.Register(®istry.Registration{ + Name: IoTTwinMakerEntityResource, + Scope: nuke.Account, + Lister: &IoTTwinMakerEntityLister{}, + }) +} + +type IoTTwinMakerEntityLister struct{} + +func (l *IoTTwinMakerEntityLister) List(_ context.Context, o interface{}) ([]resource.Resource, error) { + opts := o.(*nuke.ListerOpts) + + svc := iottwinmaker.New(opts.Session) + resources := make([]resource.Resource, 0) + + // Require to have workspaces identifiers to query entities + workspaceListResponse, err := ListWorkspacesEntities(svc) + + if err != nil { + return nil, err + } + + for _, workspaceResponse := range workspaceListResponse { + params := &iottwinmaker.ListEntitiesInput{ + WorkspaceId: workspaceResponse.WorkspaceId, + MaxResults: aws.Int64(25), + } + + for { + resp, err := svc.ListEntities(params) + if err != nil { + return nil, err + } + + for _, item := range resp.EntitySummaries { + // We must filter out amazon-owned component types when querying tags, + // because their ARN format causes ListTagsForResource to vail with validation error + resources = append(resources, &IoTTwinMakerEntity{ + svc: svc, + ID: item.EntityId, + Name: item.EntityName, + Status: item.Status.State, + WorkspaceID: workspaceResponse.WorkspaceId, + }) + } + + if resp.NextToken == nil { + break + } + + params.NextToken = resp.NextToken + } + } + + return resources, nil +} + +// Utility function to list workspaces +func ListWorkspacesEntities(svc *iottwinmaker.IoTTwinMaker) ([]*iottwinmaker.WorkspaceSummary, error) { + resources := make([]*iottwinmaker.WorkspaceSummary, 0) + params := &iottwinmaker.ListWorkspacesInput{ + MaxResults: aws.Int64(25), + } + for { + resp, err := svc.ListWorkspaces(params) + if err != nil { + return nil, err + } + resources = append(resources, resp.WorkspaceSummaries...) + if resp.NextToken == nil { + break + } + params.NextToken = resp.NextToken + } + return resources, nil +} + +type IoTTwinMakerEntity struct { + svc *iottwinmaker.IoTTwinMaker + ID *string + Name *string + Status *string + WorkspaceID *string +} + +func (r *IoTTwinMakerEntity) Properties() types.Properties { + return types.NewPropertiesFromStruct(r) +} + +func (r *IoTTwinMakerEntity) Remove(_ context.Context) error { + _, err := r.svc.DeleteEntity(&iottwinmaker.DeleteEntityInput{ + EntityId: r.ID, + WorkspaceId: r.WorkspaceID, + IsRecursive: aws.Bool(true), + }) + + return err +} + +func (r *IoTTwinMakerEntity) String() string { + return *r.ID +} diff --git a/resources/iottwinmaker-scene.go b/resources/iottwinmaker-scene.go new file mode 100644 index 00000000..8a368448 --- /dev/null +++ b/resources/iottwinmaker-scene.go @@ -0,0 +1,113 @@ +package resources + +import ( + "context" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/iottwinmaker" + + "github.com/ekristen/libnuke/pkg/registry" + "github.com/ekristen/libnuke/pkg/resource" + "github.com/ekristen/libnuke/pkg/types" + + "github.com/ekristen/aws-nuke/v3/pkg/nuke" +) + +const IoTTwinMakerSceneResource = "IoTTwinMakerScene" + +func init() { + registry.Register(®istry.Registration{ + Name: IoTTwinMakerSceneResource, + Scope: nuke.Account, + Lister: &IoTTwinMakerSceneLister{}, + }) +} + +type IoTTwinMakerSceneLister struct{} + +func (l *IoTTwinMakerSceneLister) List(_ context.Context, o interface{}) ([]resource.Resource, error) { + opts := o.(*nuke.ListerOpts) + + svc := iottwinmaker.New(opts.Session) + resources := make([]resource.Resource, 0) + + // Require to have workspaces identifiers to query scenes + workspaceListResponse, err := ListWorkspacesScene(svc) + + if err != nil { + return nil, err + } + + for _, workspaceResponse := range workspaceListResponse { + params := &iottwinmaker.ListScenesInput{ + WorkspaceId: workspaceResponse.WorkspaceId, + MaxResults: aws.Int64(25), + } + + for { + resp, err := svc.ListScenes(params) + if err != nil { + return nil, err + } + + for _, item := range resp.SceneSummaries { + resources = append(resources, &IoTTwinMakerScene{ + svc: svc, + ID: item.SceneId, + WorkspaceID: workspaceResponse.WorkspaceId, + }) + } + + if resp.NextToken == nil { + break + } + + params.NextToken = resp.NextToken + } + } + + return resources, nil +} + +// Utility function to list workspaces +func ListWorkspacesScene(svc *iottwinmaker.IoTTwinMaker) ([]*iottwinmaker.WorkspaceSummary, error) { + resources := make([]*iottwinmaker.WorkspaceSummary, 0) + params := &iottwinmaker.ListWorkspacesInput{ + MaxResults: aws.Int64(25), + } + for { + resp, err := svc.ListWorkspaces(params) + if err != nil { + return nil, err + } + resources = append(resources, resp.WorkspaceSummaries...) + if resp.NextToken == nil { + break + } + params.NextToken = resp.NextToken + } + return resources, nil +} + +type IoTTwinMakerScene struct { + svc *iottwinmaker.IoTTwinMaker + ID *string + WorkspaceID *string +} + +func (r *IoTTwinMakerScene) Properties() types.Properties { + return types.NewPropertiesFromStruct(r) +} + +func (r *IoTTwinMakerScene) Remove(_ context.Context) error { + _, err := r.svc.DeleteScene(&iottwinmaker.DeleteSceneInput{ + SceneId: r.ID, + WorkspaceId: r.WorkspaceID, + }) + + return err +} + +func (r *IoTTwinMakerScene) String() string { + return *r.ID +} diff --git a/resources/iottwinmaker-sync-job.go b/resources/iottwinmaker-sync-job.go new file mode 100644 index 00000000..39d990cd --- /dev/null +++ b/resources/iottwinmaker-sync-job.go @@ -0,0 +1,115 @@ +package resources + +import ( + "context" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/iottwinmaker" + + "github.com/ekristen/libnuke/pkg/registry" + "github.com/ekristen/libnuke/pkg/resource" + "github.com/ekristen/libnuke/pkg/types" + + "github.com/ekristen/aws-nuke/v3/pkg/nuke" +) + +const IoTTwinMakerSyncJobResource = "IoTTwinMakerSyncJob" + +func init() { + registry.Register(®istry.Registration{ + Name: IoTTwinMakerSyncJobResource, + Scope: nuke.Account, + Lister: &IoTTwinMakerSyncJobLister{}, + }) +} + +type IoTTwinMakerSyncJobLister struct{} + +func (l *IoTTwinMakerSyncJobLister) List(_ context.Context, o interface{}) ([]resource.Resource, error) { + opts := o.(*nuke.ListerOpts) + + svc := iottwinmaker.New(opts.Session) + resources := make([]resource.Resource, 0) + + // Require to have workspaces identifiers to query sync jobs + workspaceListResponse, err := ListWorkspacesSyncJob(svc) + + if err != nil { + return nil, err + } + + for _, workspaceResponse := range workspaceListResponse { + params := &iottwinmaker.ListSyncJobsInput{ + WorkspaceId: workspaceResponse.WorkspaceId, + MaxResults: aws.Int64(25), + } + + for { + resp, err := svc.ListSyncJobs(params) + if err != nil { + return nil, err + } + + for _, item := range resp.SyncJobSummaries { + resources = append(resources, &IoTTwinMakerSyncJob{ + svc: svc, + arn: item.Arn, + syncSource: item.SyncSource, + WorkspaceID: workspaceResponse.WorkspaceId, + }) + } + + if resp.NextToken == nil { + break + } + + params.NextToken = resp.NextToken + } + } + + return resources, nil +} + +// Utility function to list workspaces +func ListWorkspacesSyncJob(svc *iottwinmaker.IoTTwinMaker) ([]*iottwinmaker.WorkspaceSummary, error) { + resources := make([]*iottwinmaker.WorkspaceSummary, 0) + params := &iottwinmaker.ListWorkspacesInput{ + MaxResults: aws.Int64(25), + } + for { + resp, err := svc.ListWorkspaces(params) + if err != nil { + return nil, err + } + resources = append(resources, resp.WorkspaceSummaries...) + if resp.NextToken == nil { + break + } + params.NextToken = resp.NextToken + } + return resources, nil +} + +type IoTTwinMakerSyncJob struct { + svc *iottwinmaker.IoTTwinMaker + arn *string + syncSource *string + WorkspaceID *string +} + +func (r *IoTTwinMakerSyncJob) Properties() types.Properties { + return types.NewPropertiesFromStruct(r) +} + +func (r *IoTTwinMakerSyncJob) Remove(_ context.Context) error { + _, err := r.svc.DeleteSyncJob(&iottwinmaker.DeleteSyncJobInput{ + SyncSource: r.syncSource, + WorkspaceId: r.WorkspaceID, + }) + + return err +} + +func (r *IoTTwinMakerSyncJob) String() string { + return *r.arn +} diff --git a/resources/iottwinmaker-workspace.go b/resources/iottwinmaker-workspace.go new file mode 100644 index 00000000..36247f5c --- /dev/null +++ b/resources/iottwinmaker-workspace.go @@ -0,0 +1,96 @@ +package resources + +import ( + "context" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/iottwinmaker" + + "github.com/ekristen/libnuke/pkg/registry" + "github.com/ekristen/libnuke/pkg/resource" + "github.com/ekristen/libnuke/pkg/types" + + "github.com/ekristen/aws-nuke/v3/pkg/nuke" +) + +const IoTTwinMakerWorkspaceResource = "IoTTwinMakerWorkspace" + +func init() { + registry.Register(®istry.Registration{ + Name: IoTTwinMakerWorkspaceResource, + Scope: nuke.Account, + Lister: &IoTTwinMakerWorkspaceLister{}, + DependsOn: []string{ + IoTTwinMakerComponentTypeResource, + IoTTwinMakerEntityResource, + IoTTwinMakerSceneResource, + IoTTwinMakerSyncJobResource, + }, + }) +} + +type IoTTwinMakerWorkspaceLister struct{} + +func (l *IoTTwinMakerWorkspaceLister) List(_ context.Context, o interface{}) ([]resource.Resource, error) { + opts := o.(*nuke.ListerOpts) + + svc := iottwinmaker.New(opts.Session) + resources := make([]resource.Resource, 0) + + params := &iottwinmaker.ListWorkspacesInput{ + MaxResults: aws.Int64(25), + } + + for { + resp, err := svc.ListWorkspaces(params) + if err != nil { + return nil, err + } + + for _, item := range resp.WorkspaceSummaries { + tagResp, err := svc.ListTagsForResource( + &iottwinmaker.ListTagsForResourceInput{ + ResourceARN: item.Arn, + }) + if err != nil { + return nil, err + } + + resources = append(resources, &IoTTwinMakerWorkspace{ + svc: svc, + ID: item.WorkspaceId, + Tags: tagResp.Tags, + }) + } + + if resp.NextToken == nil { + break + } + + params.NextToken = resp.NextToken + } + + return resources, nil +} + +type IoTTwinMakerWorkspace struct { + svc *iottwinmaker.IoTTwinMaker + ID *string + Tags map[string]*string +} + +func (r *IoTTwinMakerWorkspace) Properties() types.Properties { + return types.NewPropertiesFromStruct(r) +} + +func (r *IoTTwinMakerWorkspace) Remove(_ context.Context) error { + _, err := r.svc.DeleteWorkspace(&iottwinmaker.DeleteWorkspaceInput{ + WorkspaceId: r.ID, + }) + + return err +} + +func (r *IoTTwinMakerWorkspace) String() string { + return *r.ID +}