From 38d1f87c94565bb7a91850515613b055986a6b67 Mon Sep 17 00:00:00 2001 From: Kevin DeJong Date: Wed, 5 Apr 2023 10:19:57 -0700 Subject: [PATCH] Add resource Time::Offset (#188) * Add Time::Offset resource --- release/awscommunity/cicd.yml | 95 +++++++ resources/Time_Offset/.gitignore | 17 ++ resources/Time_Offset/.rpdk-config | 21 ++ resources/Time_Offset/Makefile | 11 + resources/Time_Offset/README.md | 74 ++++++ .../Time_Offset/awscommunity-time-offset.json | 147 +++++++++++ resources/Time_Offset/cmd/main.go | 85 ++++++ resources/Time_Offset/cmd/resource/config.go | 19 ++ resources/Time_Offset/cmd/resource/model.go | 24 ++ .../Time_Offset/cmd/resource/resource.go | 247 ++++++++++++++++++ resources/Time_Offset/docs/README.md | 174 ++++++++++++ resources/Time_Offset/go.mod | 16 ++ resources/Time_Offset/go.sum | 58 ++++ .../Time_Offset/inputs/inputs_1_create.json | 10 + .../Time_Offset/inputs/inputs_1_invalid.json | 3 + .../Time_Offset/inputs/inputs_1_update.json | 10 + .../Time_Offset/inputs/inputs_2_create.json | 10 + .../Time_Offset/inputs/inputs_2_update.json | 10 + .../Time_Offset/inputs/inputs_3_create.json | 10 + .../Time_Offset/inputs/inputs_3_update.json | 10 + .../Time_Offset/inputs/inputs_4_create.json | 9 + .../Time_Offset/inputs/inputs_4_update.json | 9 + resources/Time_Offset/makebuild | 7 + resources/Time_Offset/resource-role-prod.yaml | 33 +++ resources/Time_Offset/resource-role.yaml | 40 +++ resources/Time_Offset/template.yml | 27 ++ resources/Time_Offset/test/integ.yml | 7 + resources/Time_Offset/test/setup.yml | 13 + resources/alpha-buildspec-go.yml | 10 +- resources/beta-buildspec-go.yml | 9 +- resources/prod-buildspec-go.yml | 2 - scripts/resourceName.py | 15 -- 32 files changed, 1206 insertions(+), 26 deletions(-) create mode 100644 resources/Time_Offset/.gitignore create mode 100644 resources/Time_Offset/.rpdk-config create mode 100644 resources/Time_Offset/Makefile create mode 100644 resources/Time_Offset/README.md create mode 100644 resources/Time_Offset/awscommunity-time-offset.json create mode 100644 resources/Time_Offset/cmd/main.go create mode 100644 resources/Time_Offset/cmd/resource/config.go create mode 100644 resources/Time_Offset/cmd/resource/model.go create mode 100644 resources/Time_Offset/cmd/resource/resource.go create mode 100644 resources/Time_Offset/docs/README.md create mode 100644 resources/Time_Offset/go.mod create mode 100644 resources/Time_Offset/go.sum create mode 100644 resources/Time_Offset/inputs/inputs_1_create.json create mode 100644 resources/Time_Offset/inputs/inputs_1_invalid.json create mode 100644 resources/Time_Offset/inputs/inputs_1_update.json create mode 100644 resources/Time_Offset/inputs/inputs_2_create.json create mode 100644 resources/Time_Offset/inputs/inputs_2_update.json create mode 100644 resources/Time_Offset/inputs/inputs_3_create.json create mode 100644 resources/Time_Offset/inputs/inputs_3_update.json create mode 100644 resources/Time_Offset/inputs/inputs_4_create.json create mode 100644 resources/Time_Offset/inputs/inputs_4_update.json create mode 100644 resources/Time_Offset/makebuild create mode 100644 resources/Time_Offset/resource-role-prod.yaml create mode 100644 resources/Time_Offset/resource-role.yaml create mode 100644 resources/Time_Offset/template.yml create mode 100644 resources/Time_Offset/test/integ.yml create mode 100644 resources/Time_Offset/test/setup.yml delete mode 100644 scripts/resourceName.py diff --git a/release/awscommunity/cicd.yml b/release/awscommunity/cicd.yml index 18092fed..713d101c 100644 --- a/release/awscommunity/cicd.yml +++ b/release/awscommunity/cicd.yml @@ -192,6 +192,19 @@ Resources: ManagedPolicyArns: - Fn::ImportValue: !Sub "cep-${Env}-common-build-project-policy" + TimeOffsetBuildProjectRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Statement: + - Action: sts:AssumeRole + Effect: Allow + Principal: + Service: codebuild.amazonaws.com + Version: '2012-10-17' + ManagedPolicyArns: + - Fn::ImportValue: !Sub "cep-${Env}-common-build-project-policy" + CloudFrontWebAclAssociationBuildProjectRole: Type: AWS::IAM::Role Properties: @@ -456,6 +469,40 @@ Resources: Roles: - !Ref TimeSleepBuildProjectRole + TimeOffsetBuildProjectRolePolicy: + Type: AWS::IAM::Policy + Properties: + PolicyDocument: + Statement: + - Action: + - iam:CreateRole + - iam:DeleteRole + - iam:GetRole + - dynamodb:* + Effect: Allow + Resource: "*" + - Action: + - codebuild:StartBuild + - codebuild:BatchGetBuilds + - codebuild:StopBuild + - codebuild:RetryBuild + - codebuild:StartBuildBatch + - codebuild:RetryBuildBatch + - codebuild:StopBuildBatch + Effect: Allow + Resource: + - !GetAtt TimeOffsetBuildProject.Arn + - Action: + - ssm:DeleteParameter + - ssm:GetParameter + - ssm:PutParameter + Effect: Allow + Resource: "*" + Version: '2012-10-17' + PolicyName: delete-bucket-contents-build-project-policy + Roles: + - !Ref TimeOffsetBuildProjectRole + CloudFrontWebAclAssociationBuildProjectPolicy: Type: AWS::IAM::Policy Properties: @@ -719,6 +766,28 @@ Resources: BuildSpec: !Sub "resources/${Env}-buildspec-go.yml" TimeoutInMinutes: 480 + TimeOffsetBuildProject: + Type: AWS::CodeBuild::Project + Properties: + Name: !Sub "${PrefixLower}-${Env}-time-offset" + Artifacts: + Type: CODEPIPELINE + Environment: + ComputeType: BUILD_GENERAL1_LARGE + Image: !Sub "${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/cep-cicd:latest" + ImagePullCredentialsType: SERVICE_ROLE + PrivilegedMode: true + Type: LINUX_CONTAINER + EnvironmentVariables: + - Name: RESOURCE_PATH + Type: PLAINTEXT + Value: "placeholder-for-path-to-resource" + ServiceRole: !GetAtt TimeOffsetBuildProjectRole.Arn + Source: + Type: CODEPIPELINE + BuildSpec: !Sub "resources/${Env}-buildspec-go.yml" + TimeoutInMinutes: 480 + S3BucketVersioningEnabledBuildProject: Type: AWS::CodeBuild::Project Properties: @@ -992,6 +1061,7 @@ Resources: - !GetAtt S3PublicAccessControlsRestrictedBuildProject.Arn - !GetAtt TimeStaticBuildProject.Arn - !GetAtt TimeSleepBuildProject.Arn + - !GetAtt TimeOffsetBuildProject.Arn - !GetAtt CloudFrontLoggingEnabledBuildProject.Arn - !GetAtt CloudFrontWebAclAssociationBuildProject.Arn - !GetAtt S3BucketModuleBuildProject.Arn @@ -1058,6 +1128,7 @@ Resources: - !GetAtt S3PublicAccessControlsRestrictedBuildProjectRole.Arn - !GetAtt TimeStaticBuildProjectRole.Arn - !GetAtt TimeSleepBuildProjectRole.Arn + - !GetAtt TimeOffsetBuildProjectRole.Arn - !GetAtt AlternateContactBuildProjectRole.Arn - !GetAtt CloudFrontLoggingEnabledBuildProjectRole.Arn - !GetAtt CloudFrontWebAclAssociationBuildProjectRole.Arn @@ -1240,6 +1311,30 @@ Resources: } ] RunOrder: 1 + - Name: TimeOffset + InputArtifacts: + - Name: extensions-source + ActionTypeId: + Category: Build + Owner: AWS + Provider: CodeBuild + Version: 1 + Configuration: + ProjectName: !Ref TimeOffsetBuildProject + # BatchEnabled: + # !If + # - IsProd + # - true + # - false + EnvironmentVariables: |- + [ + { + "name": "RESOURCE_PATH", + "type": "PLAINTEXT", + "value": "resources/Time_Offset" + } + ] + RunOrder: 1 - Name: Account_AlternateContact InputArtifacts: - Name: extensions-source diff --git a/resources/Time_Offset/.gitignore b/resources/Time_Offset/.gitignore new file mode 100644 index 00000000..5c7bffc7 --- /dev/null +++ b/resources/Time_Offset/.gitignore @@ -0,0 +1,17 @@ +# macOS +.DS_Store +._* + +# our logs +rpdk.log* + +#compiled file +bin/ + +#vender +vender/ + +# contains credentials +sam-tests/ +.hypothesis/ +/awscommunity-time-offset.zip diff --git a/resources/Time_Offset/.rpdk-config b/resources/Time_Offset/.rpdk-config new file mode 100644 index 00000000..c21cbf36 --- /dev/null +++ b/resources/Time_Offset/.rpdk-config @@ -0,0 +1,21 @@ +{ + "artifact_type": "RESOURCE", + "typeName": "AwsCommunity::Time::Offset", + "language": "go", + "runtime": "go1.x", + "entrypoint": "handler", + "testEntrypoint": "handler", + "settings": { + "version": false, + "subparser_name": null, + "verbose": 0, + "force": false, + "type_name": null, + "artifact_type": null, + "endpoint_url": null, + "region": null, + "target_schemas": [], + "import_path": "github.com/aws-cloudformation/awscommunity-registry-extensions/resources/Time_Offset", + "protocolVersion": "2.0.0" + } +} diff --git a/resources/Time_Offset/Makefile b/resources/Time_Offset/Makefile new file mode 100644 index 00000000..bc13ba53 --- /dev/null +++ b/resources/Time_Offset/Makefile @@ -0,0 +1,11 @@ +.PHONY: build test clean + +build: + make -f makebuild # this runs build steps required by the cfn cli + +test: + cfn generate + env GOOS=linux go build -ldflags="-s -w" -o bin/handler cmd/main.go + +clean: + rm -rf bin diff --git a/resources/Time_Offset/README.md b/resources/Time_Offset/README.md new file mode 100644 index 00000000..fb6a57e7 --- /dev/null +++ b/resources/Time_Offset/README.md @@ -0,0 +1,74 @@ +# AwsCommunity::Time::Offset + +Creates a time based resource with an offset. + +## Example + +```yaml +Resources: + StarTime: + Type: AwsCommunity::Time::Offset + Properties: + OffsetHours: 1 + EndTime: + Type: AwsCommunity::Time::Offset + Properties: + OffsetYears: 5 + DailyNotice: + Type: AWS::Pinpoint::Campaign + Properties: + ... + Schedule: + EndTime: !GetAtt EndTime.Utc + Frequency: "DAILY" + IsLocalTime: true + StartTime: !GetAtt StartTime.Utc + TimeZone: "UTC-07" +``` + +## Properties +| Properties | Type | Description | +| ------------- | ------------- | ------------- | +| **Time** | string | Utc time to do the offset from. If none is provided now will be used. +| **OffsetYears** | integer | Number of years to offset the base timestamp. +| **OffsetMonths** | integer | Number of months to offset the base timestamp. +| **OffsetDays** | integer | Number of days to offset the base timestamp. +| **OffsetHours** | integer | Number of hours to offset the base timestamp. +| **OffsetMinutes** | integer | Number of minutes to offset the base timestamp. +| **OffsetSeconds** | integer | Number of seconds to offset the base timestamp. + +## Attributes +The following properties you can use in a `GetAtt` + +| Attribute | Type | Description | +| ------------- | ------------- | ------------- | +| **Id** | string | Unique ID that identifies the resource. +| **Day** | integer | Day returns the day of the month. +| **Hour** | integer | Hour returns the hour within the day, in the range [0, 23]. +| **Minute** | integer | Minute returns the minute offset within the hour, in the range [0, 59]. +| **Month** | integer | Month returns the month of the year. +| **Second** | integer | Second returns the second offset within the minute, in the range [0, 59]. +| **Unix** | integer | Unix returns a Unix time, the number of seconds elapsed since January 1, 1970 UTC. +| **Utc** | string | The time represented in UTC time +| **Year** | integer | Year returns the year. + +## Development + +Open two tabs in your terminal. + +Run SAM +```sh +sam local start-lambda +``` + +In another tab, run cfn test: + +```sh +cd resources/Time_Offset +cfn test +``` + +## Notes + +### SSM Parameter +Since this extension does not represent an actual resource, we have to create a Systems Manager Parameter Store entry to track its state for the purposes of satisfying the CloudFormation registry contract. We store a key in parameter store at `/CloudFormation/AwsCommunity/Time/Offset/unique-identifier`. diff --git a/resources/Time_Offset/awscommunity-time-offset.json b/resources/Time_Offset/awscommunity-time-offset.json new file mode 100644 index 00000000..8a08974b --- /dev/null +++ b/resources/Time_Offset/awscommunity-time-offset.json @@ -0,0 +1,147 @@ +{ + "typeName": "AwsCommunity::Time::Offset", + "description": "Creates a time based resource with an offset from the provided time or now.", + "sourceUrl": "https://github.com/aws-cloudformation/community-registry-extensions.git", + "definitions": {}, + "properties": { + "Id": { + "description": "Id is a unique identifier that is auto generated.", + "type": "string" + }, + "Time": { + "description": "Optional parameter to represent the time or default is now.", + "type": "string", + "pattern": "^\\d{4}-[0-1][0-3]-[0-3]\\d{1}T[0-2]\\d{1}:[0-5]\\d{1}:[0-5]\\d{1}Z$" + }, + "OffsetDays": { + "description": "Number of days to offset the base timestamp.", + "type": "integer" + }, + "OffsetHours": { + "description": "Number of hours to offset the base timestamp.", + "type": "integer" + }, + "OffsetMinutes": { + "description": "Number of minutes to offset the base timestamp.", + "type": "integer" + }, + "OffsetMonths": { + "description": "Number of months to offset the base timestamp.", + "type": "integer" + }, + "OffsetSeconds": { + "description": "Number of seconds to offset the base timestamp.", + "type": "integer" + }, + "OffsetYears": { + "description": "Number of years to offset the base timestamp.", + "type": "integer" + }, + "Utc": { + "description": "UTC returns the time in UTC format.", + "type": "string" + }, + "Day": { + "description": "Day returns the day of the month.", + "type": "string" + }, + "Hour": { + "description": "Hour returns the hour within the day, in the range [0, 23].", + "type": "string" + }, + "Minute": { + "description": "Minute returns the minute offset within the hour, in the range [0, 59].", + "type": "string" + }, + "Month": { + "description": "Month returns the month of the year.", + "type": "string" + }, + "Second": { + "description": "Second returns the second offset within the minute, in the range [0, 59].", + "type": "string" + }, + "Unix": { + "description": "Unix returns a Unix time, the number of seconds elapsed since January 1, 1970 UTC.", + "type": "string" + }, + "Year": { + "description": "Year returns the year.", + "type": "string" + }, + "Triggers": { + "description": "A value to represent when an update to the time should occur.", + "type": "array", + "items": { + "type": "string" + }, + "insertionOrder": true + } + }, + "additionalProperties": false, + "anyOf": [ + { + "required": ["OffsetDays"] + }, + { + "required": ["OffsetHours"] + }, + { + "required": ["OffsetMinutes"] + }, + { + "required": ["OffsetMonths"] + }, + { + "required": ["OffsetSeconds"] + }, + { + "required": ["OffsetYears"] + } + ], + "readOnlyProperties": [ + "/properties/Id", + "/properties/Utc", + "/properties/Day", + "/properties/Hour", + "/properties/Minute", + "/properties/Month", + "/properties/Second", + "/properties/Unix", + "/properties/Year" + ], + "createOnlyProperties": [ + "/properties/Time", + "/properties/OffsetDays", + "/properties/OffsetHours", + "/properties/OffsetMinutes", + "/properties/OffsetMonths", + "/properties/OffsetSeconds", + "/properties/OffsetYears" + ], + "primaryIdentifier": [ + "/properties/Id" + ], + "handlers": { + "create": { + "permissions": [ + "ssm:PutParameter" + ] + }, + "delete": { + "permissions": [ + "ssm:DeleteParameter" + ] + }, + "read": { + "permissions": [ + "ssm:GetParameter" + ] + }, + "update": { + "permissions": [ + "ssm:GetParameter" + ] + } + } +} \ No newline at end of file diff --git a/resources/Time_Offset/cmd/main.go b/resources/Time_Offset/cmd/main.go new file mode 100644 index 00000000..a753c34f --- /dev/null +++ b/resources/Time_Offset/cmd/main.go @@ -0,0 +1,85 @@ +// Code generated by 'cfn generate', changes will be undone by the next invocation. DO NOT EDIT. +package main + +import ( + "errors" + "fmt" + "log" + + "github.com/aws-cloudformation/awscommunity-registry-extensions/resources/Time_Offset/cmd/resource" + "github.com/aws-cloudformation/cloudformation-cli-go-plugin/cfn" + "github.com/aws-cloudformation/cloudformation-cli-go-plugin/cfn/handler" +) + +// Handler is a container for the CRUDL actions exported by resources +type Handler struct{} + +// Create wraps the related Create function exposed by the resource code +func (r *Handler) Create(req handler.Request) handler.ProgressEvent { + return wrap(req, resource.Create) +} + +// Read wraps the related Read function exposed by the resource code +func (r *Handler) Read(req handler.Request) handler.ProgressEvent { + return wrap(req, resource.Read) +} + +// Update wraps the related Update function exposed by the resource code +func (r *Handler) Update(req handler.Request) handler.ProgressEvent { + return wrap(req, resource.Update) +} + +// Delete wraps the related Delete function exposed by the resource code +func (r *Handler) Delete(req handler.Request) handler.ProgressEvent { + return wrap(req, resource.Delete) +} + +// List wraps the related List function exposed by the resource code +func (r *Handler) List(req handler.Request) handler.ProgressEvent { + return wrap(req, resource.List) +} + +// main is the entry point of the application. +func main() { + cfn.Start(&Handler{}) +} + +type handlerFunc func(handler.Request, *resource.Model, *resource.Model) (handler.ProgressEvent, error) + +func wrap(req handler.Request, f handlerFunc) (response handler.ProgressEvent) { + defer func() { + // Catch any panics and return a failed ProgressEvent + if r := recover(); r != nil { + err, ok := r.(error) + if !ok { + err = errors.New(fmt.Sprint(r)) + } + + log.Printf("Trapped error in handler: %v", err) + + response = handler.NewFailedEvent(err) + } + }() + + // Populate the previous model + prevModel := &resource.Model{} + if err := req.UnmarshalPrevious(prevModel); err != nil { + log.Printf("Error unmarshaling prev model: %v", err) + return handler.NewFailedEvent(err) + } + + // Populate the current model + currentModel := &resource.Model{} + if err := req.Unmarshal(currentModel); err != nil { + log.Printf("Error unmarshaling model: %v", err) + return handler.NewFailedEvent(err) + } + + response, err := f(req, prevModel, currentModel) + if err != nil { + log.Printf("Error returned from handler function: %v", err) + return handler.NewFailedEvent(err) + } + + return response +} diff --git a/resources/Time_Offset/cmd/resource/config.go b/resources/Time_Offset/cmd/resource/config.go new file mode 100644 index 00000000..4d9eb783 --- /dev/null +++ b/resources/Time_Offset/cmd/resource/config.go @@ -0,0 +1,19 @@ +// Code generated by 'cfn generate', changes will be undone by the next invocation. DO NOT EDIT. +// Updates to this type are made my editing the schema file and executing the 'generate' command. +package resource + +import "github.com/aws-cloudformation/cloudformation-cli-go-plugin/cfn/handler" + +// TypeConfiguration is autogenerated from the json schema +type TypeConfiguration struct { +} + +// Configuration returns a resource's configuration. +func Configuration(req handler.Request) (*TypeConfiguration, error) { + // Populate the type configuration + typeConfig := &TypeConfiguration{} + if err := req.UnmarshalTypeConfig(typeConfig); err != nil { + return typeConfig, err + } + return typeConfig, nil +} diff --git a/resources/Time_Offset/cmd/resource/model.go b/resources/Time_Offset/cmd/resource/model.go new file mode 100644 index 00000000..99eeb3a2 --- /dev/null +++ b/resources/Time_Offset/cmd/resource/model.go @@ -0,0 +1,24 @@ +// Code generated by 'cfn generate', changes will be undone by the next invocation. DO NOT EDIT. +// Updates to this type are made my editing the schema file and executing the 'generate' command. +package resource + +// Model is autogenerated from the json schema +type Model struct { + Id *string `json:",omitempty"` + Time *string `json:",omitempty"` + OffsetDays *int `json:",omitempty"` + OffsetHours *int `json:",omitempty"` + OffsetMinutes *int `json:",omitempty"` + OffsetMonths *int `json:",omitempty"` + OffsetSeconds *int `json:",omitempty"` + OffsetYears *int `json:",omitempty"` + Utc *string `json:",omitempty"` + Day *string `json:",omitempty"` + Hour *string `json:",omitempty"` + Minute *string `json:",omitempty"` + Month *string `json:",omitempty"` + Second *string `json:",omitempty"` + Unix *string `json:",omitempty"` + Year *string `json:",omitempty"` + Triggers []string `json:",omitempty"` +} diff --git a/resources/Time_Offset/cmd/resource/resource.go b/resources/Time_Offset/cmd/resource/resource.go new file mode 100644 index 00000000..92fff34c --- /dev/null +++ b/resources/Time_Offset/cmd/resource/resource.go @@ -0,0 +1,247 @@ +package resource + +import ( + "errors" + "fmt" + "strings" + "time" + + "github.com/aws-cloudformation/cloudformation-cli-go-plugin/cfn/handler" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/cloudformation" + "github.com/aws/aws-sdk-go/service/ssm" + "github.com/google/uuid" +) + +func buildSsmParameterString(model *Model) string { + resourceType := "AwsCommunity::Time::Offset" + resourceTypeSplits := strings.Split(resourceType, "::") + return fmt.Sprintf("/CloudFormation/%s/%s/%s/%s", resourceTypeSplits[0], resourceTypeSplits[1], resourceTypeSplits[2], *model.Id) +} + +// Create handles the Create event from the Cloudformation service. +func Create(req handler.Request, _ *Model, currentModel *Model) (handler.ProgressEvent, error) { + + // If Time isn't specified we use now + if currentModel.Time == nil { + now := timeToString(time.Now()) + currentModel.Time = &now + } + id := uuid.New().String() + currentModel.Id = &id + + // determine the offset + t, err := updateTimeWithOffset(currentModel) + if err != nil { + return handler.ProgressEvent{ + OperationStatus: handler.Failed, + Message: err.Error(), + HandlerErrorCode: cloudformation.HandlerErrorCodeAlreadyExists, + ResourceModel: nil, + }, nil + } + + // convert a timestamp into the model so we have all the attributes + timeToModel(currentModel, t) + + // Save the unique identifier in SSM if it exists its a duplicate + ssmParameter := buildSsmParameterString(currentModel) + svc := ssm.New(req.Session) + _, err = svc.PutParameter(&ssm.PutParameterInput{ + Name: &ssmParameter, + Value: &id, + Overwrite: aws.Bool(false), + Tier: aws.String("Standard"), + Type: aws.String("String"), + }) + if err != nil { + return handler.ProgressEvent{ + OperationStatus: handler.Failed, + Message: err.Error(), + HandlerErrorCode: cloudformation.HandlerErrorCodeAlreadyExists, + ResourceModel: nil, + }, nil + } + + response := handler.ProgressEvent{ + OperationStatus: handler.Success, + Message: "Create complete", + ResourceModel: currentModel, + } + return response, nil +} + +// Read handles the Read event from the Cloudformation service. +func Read(req handler.Request, prevModel *Model, currentModel *Model) (handler.ProgressEvent, error) { + + // See if the SSM parameter exists to determine if the resource exists + ssmParameter := buildSsmParameterString(currentModel) + svc := ssm.New(req.Session) + _, err := svc.GetParameter(&ssm.GetParameterInput{ + Name: &ssmParameter, + }) + if err != nil { + return handler.ProgressEvent{ + OperationStatus: handler.Failed, + HandlerErrorCode: cloudformation.HandlerErrorCodeNotFound, + Message: "Resource not found", + }, nil + } + + // determine the offset + t, err := updateTimeWithOffset(currentModel) + if err != nil { + return handler.ProgressEvent{ + OperationStatus: handler.Failed, + Message: err.Error(), + HandlerErrorCode: cloudformation.HandlerErrorCodeAlreadyExists, + ResourceModel: nil, + }, nil + } + + // convert a timestamp into the model so we have all the attributes + timeToModel(currentModel, t) + + response := handler.ProgressEvent{ + OperationStatus: handler.Success, + Message: "Read complete", + ResourceModel: currentModel, + } + + return response, nil +} + +// Update handles the Update event from the Cloudformation service. +func Update(req handler.Request, prevModel *Model, currentModel *Model) (handler.ProgressEvent, error) { + + // Validate teh Id exists already + ssmParameter := buildSsmParameterString(currentModel) + svc := ssm.New(req.Session) + _, err := svc.GetParameter(&ssm.GetParameterInput{ + Name: &ssmParameter, + }) + if err != nil { + return handler.ProgressEvent{ + OperationStatus: handler.Failed, + Message: err.Error(), + HandlerErrorCode: cloudformation.HandlerErrorCodeNotFound, + ResourceModel: nil, + }, nil + } + + // If Time isn't specified we use now + if currentModel.Time == nil { + now := timeToString(time.Now()) + currentModel.Time = &now + } + + // determine the offset + t, err := updateTimeWithOffset(currentModel) + if err != nil { + return handler.ProgressEvent{ + OperationStatus: handler.Failed, + Message: err.Error(), + HandlerErrorCode: cloudformation.HandlerErrorCodeAlreadyExists, + ResourceModel: nil, + }, nil + } + + // convert a timestamp into the model so we have all the attributes + timeToModel(currentModel, t) + + response := handler.ProgressEvent{ + OperationStatus: handler.Success, + Message: "Create complete", + ResourceModel: currentModel, + } + return response, nil +} + +// Delete handles the Delete event from the Cloudformation service. +func Delete(req handler.Request, prevModel *Model, currentModel *Model) (handler.ProgressEvent, error) { + + // See if the SSM parameter exists to determine if the resource exists + ssmParameter := buildSsmParameterString(currentModel) + svc := ssm.New(req.Session) + _, err := svc.DeleteParameter(&ssm.DeleteParameterInput{ + Name: &ssmParameter, + }) + if err != nil { + return handler.ProgressEvent{ + OperationStatus: handler.Failed, + HandlerErrorCode: cloudformation.HandlerErrorCodeNotFound, + Message: "Resource not found", + }, nil + } + + response := handler.ProgressEvent{ + OperationStatus: handler.Success, + Message: "Delete complete", + ResourceModel: nil, + } + + return response, nil +} + +// List handles the List event from the Cloudformation service. +func List(req handler.Request, prevModel *Model, currentModel *Model) (handler.ProgressEvent, error) { + return handler.ProgressEvent{}, errors.New("this resource type does not support list") +} + +// Convert time to a string +func timeToString(t time.Time) string { + return t.Format(time.RFC3339) +} + +func updateTimeWithOffset(m *Model) (*time.Time, error) { + + t, err := time.Parse(time.RFC3339, *m.Time) + if err != nil { + return nil, err + } + + if m.OffsetDays != nil { + t = t.AddDate(0, 0, *m.OffsetDays) + } + if m.OffsetMonths != nil { + t = t.AddDate(0, *m.OffsetMonths, 0) + } + if m.OffsetYears != nil { + t = t.AddDate(*m.OffsetYears, 0, 0) + } + if m.OffsetHours != nil { + t = t.Add(time.Hour * time.Duration(*m.OffsetHours)) + } + if m.OffsetMinutes != nil { + t = t.Add(time.Minute * time.Duration(*m.OffsetMinutes)) + } + if m.OffsetSeconds != nil { + t = t.Add(time.Second * time.Duration(*m.OffsetSeconds)) + } + + return &t, nil + +} + +// Convert time into a model +func timeToModel(m *Model, t *time.Time) { + + utc := t.Format(time.RFC3339) + day := fmt.Sprintf("%02d", t.Day()) + hour := fmt.Sprintf("%02d", t.Hour()) + minute := fmt.Sprintf("%02d", t.Minute()) + month := fmt.Sprintf("%02d", int(t.Month())) + second := fmt.Sprintf("%02d", t.Second()) + unix := fmt.Sprintf("%02d", int(t.Unix())) + year := fmt.Sprintf("%02d", t.Year()) + + m.Utc = &utc + m.Day = &day + m.Hour = &hour + m.Minute = &minute + m.Month = &month + m.Second = &second + m.Unix = &unix + m.Year = &year + +} diff --git a/resources/Time_Offset/docs/README.md b/resources/Time_Offset/docs/README.md new file mode 100644 index 00000000..e97d3ad6 --- /dev/null +++ b/resources/Time_Offset/docs/README.md @@ -0,0 +1,174 @@ +# AwsCommunity::Time::Offset + +Creates a time based resource with an offset from the provided time or now. + +## Syntax + +To declare this entity in your AWS CloudFormation template, use the following syntax: + +### JSON + +
+{
+    "Type" : "AwsCommunity::Time::Offset",
+    "Properties" : {
+        "Time" : String,
+        "OffsetDays" : Integer,
+        "OffsetHours" : Integer,
+        "OffsetMinutes" : Integer,
+        "OffsetMonths" : Integer,
+        "OffsetSeconds" : Integer,
+        "OffsetYears" : Integer,
+        "Triggers" : [ String, ... ]
+    }
+}
+
+ +### YAML + +
+Type: AwsCommunity::Time::Offset
+Properties:
+    Time: String
+    OffsetDays: Integer
+    OffsetHours: Integer
+    OffsetMinutes: Integer
+    OffsetMonths: Integer
+    OffsetSeconds: Integer
+    OffsetYears: Integer
+    Triggers: 
+      - String
+
+ +## Properties + +#### Time + +Optional parameter to represent the time or default is now. + +_Required_: No + +_Type_: String + +_Pattern_: ^\d{4}-[0-1][0-3]-[0-3]\d{1}T[0-2]\d{1}:[0-5]\d{1}:[0-5]\d{1}Z$ + +_Update requires_: [Replacement](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-replacement) + +#### OffsetDays + +Number of days to offset the base timestamp. + +_Required_: No + +_Type_: Integer + +_Update requires_: [Replacement](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-replacement) + +#### OffsetHours + +Number of hours to offset the base timestamp. + +_Required_: No + +_Type_: Integer + +_Update requires_: [Replacement](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-replacement) + +#### OffsetMinutes + +Number of minutes to offset the base timestamp. + +_Required_: No + +_Type_: Integer + +_Update requires_: [Replacement](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-replacement) + +#### OffsetMonths + +Number of months to offset the base timestamp. + +_Required_: No + +_Type_: Integer + +_Update requires_: [Replacement](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-replacement) + +#### OffsetSeconds + +Number of seconds to offset the base timestamp. + +_Required_: No + +_Type_: Integer + +_Update requires_: [Replacement](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-replacement) + +#### OffsetYears + +Number of years to offset the base timestamp. + +_Required_: No + +_Type_: Integer + +_Update requires_: [Replacement](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-replacement) + +#### Triggers + +A value to represent when an update to the time should occur. + +_Required_: No + +_Type_: List of String + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + +## Return Values + +### Ref + +When you pass the logical ID of this resource to the intrinsic `Ref` function, Ref returns the Id. + +### Fn::GetAtt + +The `Fn::GetAtt` intrinsic function returns a value for a specified attribute of this type. The following are the available attributes and sample return values. + +For more information about using the `Fn::GetAtt` intrinsic function, see [Fn::GetAtt](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-getatt.html). + +#### Id + +Id is a unique identifier that is auto generated. + +#### Utc + +UTC returns the time in UTC format. + +#### Day + +Day returns the day of the month. + +#### Hour + +Hour returns the hour within the day, in the range [0, 23]. + +#### Minute + +Minute returns the minute offset within the hour, in the range [0, 59]. + +#### Month + +Month returns the month of the year. + +#### Second + +Second returns the second offset within the minute, in the range [0, 59]. + +#### Unix + +Unix returns a Unix time, the number of seconds elapsed since January 1, 1970 UTC. + +#### Year + +Year returns the year. + diff --git a/resources/Time_Offset/go.mod b/resources/Time_Offset/go.mod new file mode 100644 index 00000000..893dc18c --- /dev/null +++ b/resources/Time_Offset/go.mod @@ -0,0 +1,16 @@ +module github.com/aws-cloudformation/awscommunity-registry-extensions/resources/Time_Offset + +go 1.19 + +require ( + github.com/aws-cloudformation/cloudformation-cli-go-plugin v1.2.0 + github.com/aws/aws-sdk-go v1.44.235 + github.com/google/uuid v1.3.0 +) + +require ( + github.com/aws/aws-lambda-go v1.37.0 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/segmentio/ksuid v1.0.4 // indirect + gopkg.in/validator.v2 v2.0.1 // indirect +) diff --git a/resources/Time_Offset/go.sum b/resources/Time_Offset/go.sum new file mode 100644 index 00000000..99f83d04 --- /dev/null +++ b/resources/Time_Offset/go.sum @@ -0,0 +1,58 @@ +github.com/aws-cloudformation/cloudformation-cli-go-plugin v1.2.0 h1:NHNKs4hOKBz9kufu2Ylce+P20x6mSxS2ryrYoW6AlX8= +github.com/aws-cloudformation/cloudformation-cli-go-plugin v1.2.0/go.mod h1:u3nqs3hHrn8D51m7+N+6ya7Sksyd6OG3xK3RpXdRb1g= +github.com/aws/aws-lambda-go v1.37.0 h1:WXkQ/xhIcXZZ2P5ZBEw+bbAKeCEcb5NtiYpSwVVzIXg= +github.com/aws/aws-lambda-go v1.37.0/go.mod h1:jwFe2KmMsHmffA1X2R09hH6lFzJQxzI8qK17ewzbQMM= +github.com/aws/aws-sdk-go v1.44.235 h1:5MS1ZW1Pr27mmHFqqjuXYwGMlNTW/g6DqU5ekamPMeU= +github.com/aws/aws-sdk-go v1.44.235/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c= +github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/validator.v2 v2.0.1 h1:xF0KWyGWXm/LM2G1TrEjqOu4pa6coO9AlWSf3msVfDY= +gopkg.in/validator.v2 v2.0.1/go.mod h1:lIUZBlB3Im4s/eYp39Ry/wkR02yOPhZ9IwIRBjuPuG8= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/resources/Time_Offset/inputs/inputs_1_create.json b/resources/Time_Offset/inputs/inputs_1_create.json new file mode 100644 index 00000000..99c4a9e5 --- /dev/null +++ b/resources/Time_Offset/inputs/inputs_1_create.json @@ -0,0 +1,10 @@ +{ + "Time": "2023-04-04T18:13:32+00:00", + "OffsetYears": "0", + "OffsetMonths": "0", + "OffsetDays": "1", + "OffsetHours": "0", + "OffsetMinutes": "0", + "OffsetSeconds": "0", + "Triggers": ["A"] +} diff --git a/resources/Time_Offset/inputs/inputs_1_invalid.json b/resources/Time_Offset/inputs/inputs_1_invalid.json new file mode 100644 index 00000000..4d78d253 --- /dev/null +++ b/resources/Time_Offset/inputs/inputs_1_invalid.json @@ -0,0 +1,3 @@ +{ + "Time": "2023-04-04T18:13:32+00:00" +} diff --git a/resources/Time_Offset/inputs/inputs_1_update.json b/resources/Time_Offset/inputs/inputs_1_update.json new file mode 100644 index 00000000..45186130 --- /dev/null +++ b/resources/Time_Offset/inputs/inputs_1_update.json @@ -0,0 +1,10 @@ +{ + "Time": "2023-04-04T18:13:32+00:00", + "OffsetYears": "0", + "OffsetMonths": "0", + "OffsetDays": "1", + "OffsetHours": "0", + "OffsetMinutes": "0", + "OffsetSeconds": "0", + "Triggers": ["B"] +} diff --git a/resources/Time_Offset/inputs/inputs_2_create.json b/resources/Time_Offset/inputs/inputs_2_create.json new file mode 100644 index 00000000..878a05b4 --- /dev/null +++ b/resources/Time_Offset/inputs/inputs_2_create.json @@ -0,0 +1,10 @@ +{ + "Time": "2023-04-04T18:13:32+00:00", + "OffsetYears": "0", + "OffsetMonths": "0", + "OffsetDays": "0", + "OffsetHours": "0", + "OffsetMinutes": "1", + "OffsetSeconds": "0", + "Triggers": ["A"] +} diff --git a/resources/Time_Offset/inputs/inputs_2_update.json b/resources/Time_Offset/inputs/inputs_2_update.json new file mode 100644 index 00000000..11d6f35d --- /dev/null +++ b/resources/Time_Offset/inputs/inputs_2_update.json @@ -0,0 +1,10 @@ +{ + "Time": "2023-04-04T18:13:32+00:00", + "OffsetYears": "0", + "OffsetMonths": "0", + "OffsetDays": "0", + "OffsetHours": "0", + "OffsetMinutes": "1", + "OffsetSeconds": "0", + "Triggers": ["B"] +} diff --git a/resources/Time_Offset/inputs/inputs_3_create.json b/resources/Time_Offset/inputs/inputs_3_create.json new file mode 100644 index 00000000..b495de44 --- /dev/null +++ b/resources/Time_Offset/inputs/inputs_3_create.json @@ -0,0 +1,10 @@ +{ + "Time": "2023-04-04T18:13:32+00:00", + "OffsetYears": "10", + "OffsetMonths": "0", + "OffsetDays": "0", + "OffsetHours": "0", + "OffsetMinutes": "0", + "OffsetSeconds": "0", + "Triggers": ["A"] +} diff --git a/resources/Time_Offset/inputs/inputs_3_update.json b/resources/Time_Offset/inputs/inputs_3_update.json new file mode 100644 index 00000000..b495de44 --- /dev/null +++ b/resources/Time_Offset/inputs/inputs_3_update.json @@ -0,0 +1,10 @@ +{ + "Time": "2023-04-04T18:13:32+00:00", + "OffsetYears": "10", + "OffsetMonths": "0", + "OffsetDays": "0", + "OffsetHours": "0", + "OffsetMinutes": "0", + "OffsetSeconds": "0", + "Triggers": ["A"] +} diff --git a/resources/Time_Offset/inputs/inputs_4_create.json b/resources/Time_Offset/inputs/inputs_4_create.json new file mode 100644 index 00000000..e07acc73 --- /dev/null +++ b/resources/Time_Offset/inputs/inputs_4_create.json @@ -0,0 +1,9 @@ +{ + "OffsetYears": "0", + "OffsetMonths": "0", + "OffsetDays": "1", + "OffsetHours": "0", + "OffsetMinutes": "0", + "OffsetSeconds": "0", + "Triggers": ["A"] +} diff --git a/resources/Time_Offset/inputs/inputs_4_update.json b/resources/Time_Offset/inputs/inputs_4_update.json new file mode 100644 index 00000000..a655ad66 --- /dev/null +++ b/resources/Time_Offset/inputs/inputs_4_update.json @@ -0,0 +1,9 @@ +{ + "OffsetYears": "0", + "OffsetMonths": "0", + "OffsetDays": "1", + "OffsetHours": "0", + "OffsetMinutes": "0", + "OffsetSeconds": "0", + "Triggers": ["B"] +} diff --git a/resources/Time_Offset/makebuild b/resources/Time_Offset/makebuild new file mode 100644 index 00000000..390b673b --- /dev/null +++ b/resources/Time_Offset/makebuild @@ -0,0 +1,7 @@ +# This file is autogenerated, do not edit; +# changes will be undone by the next 'generate' command. + +.PHONY: build +build: + cfn generate + env GOARCH=amd64 GOOS=linux go build -ldflags="-s -w" -tags="$(TAGS)" -o bin/handler cmd/main.go diff --git a/resources/Time_Offset/resource-role-prod.yaml b/resources/Time_Offset/resource-role-prod.yaml new file mode 100644 index 00000000..852f2007 --- /dev/null +++ b/resources/Time_Offset/resource-role-prod.yaml @@ -0,0 +1,33 @@ +AWSTemplateFormatVersion: "2010-09-09" +Description: > + This CloudFormation template creates a role assumed by CloudFormation + during CRUDL operations to mutate resources on behalf of the customer. + +Resources: + ExecutionRole: + Type: AWS::IAM::Role + Properties: + MaxSessionDuration: 8400 + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + Service: resources.cloudformation.amazonaws.com + Action: sts:AssumeRole + Path: "/" + Policies: + - PolicyName: ResourceTypePolicy + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - "ssm:DeleteParameter" + - "ssm:GetParameter" + - "ssm:PutParameter" + Resource: "*" +Outputs: + ExecutionRoleArn: + Value: + Fn::GetAtt: ExecutionRole.Arn diff --git a/resources/Time_Offset/resource-role.yaml b/resources/Time_Offset/resource-role.yaml new file mode 100644 index 00000000..0e70d2cc --- /dev/null +++ b/resources/Time_Offset/resource-role.yaml @@ -0,0 +1,40 @@ +AWSTemplateFormatVersion: "2010-09-09" +Description: > + This CloudFormation template creates a role assumed by CloudFormation + during CRUDL operations to mutate resources on behalf of the customer. + +Resources: + ExecutionRole: + Type: AWS::IAM::Role + Properties: + MaxSessionDuration: 8400 + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + Service: resources.cloudformation.amazonaws.com + Action: sts:AssumeRole + Condition: + StringEquals: + aws:SourceAccount: + Ref: AWS::AccountId + StringLike: + aws:SourceArn: + Fn::Sub: arn:${AWS::Partition}:cloudformation:${AWS::Region}:${AWS::AccountId}:type/resource/AwsCommunity-Time-Offset/* + Path: "/" + Policies: + - PolicyName: ResourceTypePolicy + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - "ssm:DeleteParameter" + - "ssm:GetParameter" + - "ssm:PutParameter" + Resource: "*" +Outputs: + ExecutionRoleArn: + Value: + Fn::GetAtt: ExecutionRole.Arn diff --git a/resources/Time_Offset/template.yml b/resources/Time_Offset/template.yml new file mode 100644 index 00000000..2b361008 --- /dev/null +++ b/resources/Time_Offset/template.yml @@ -0,0 +1,27 @@ +AWSTemplateFormatVersion: "2010-09-09" +Transform: AWS::Serverless-2016-10-31 +Description: AWS SAM template for the AwsCommunity::Time::Offset resource type + +Globals: + Function: + Timeout: 180 # docker start-up times can be long for SAM CLI + MemorySize: 256 + +Resources: + TypeFunction: + Type: AWS::Serverless::Function + Properties: + Handler: handler + Runtime: go1.x + CodeUri: bin/ + + TestEntrypoint: + Type: AWS::Serverless::Function + Properties: + Handler: handler + Runtime: go1.x + CodeUri: bin/ + Environment: + Variables: + MODE: Test + diff --git a/resources/Time_Offset/test/integ.yml b/resources/Time_Offset/test/integ.yml new file mode 100644 index 00000000..f9b989ef --- /dev/null +++ b/resources/Time_Offset/test/integ.yml @@ -0,0 +1,7 @@ +Resources: + Time: + Type: AwsCommunity::Time::Offset + Properties: + Time: "2023-03-31T18:51:56Z" + OffsetDays: 1 + diff --git a/resources/Time_Offset/test/setup.yml b/resources/Time_Offset/test/setup.yml new file mode 100644 index 00000000..86489a31 --- /dev/null +++ b/resources/Time_Offset/test/setup.yml @@ -0,0 +1,13 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: 'CloudFormation exports' + +Conditions: + HasNot: !Equals [ 'true', 'false' ] + +# dummy (null) resource, never created +Resources: + NullResource: + Type: 'AWS::CloudFormation::CustomResource' + Condition: HasNot + Properties: + ServiceToken: "null" \ No newline at end of file diff --git a/resources/alpha-buildspec-go.yml b/resources/alpha-buildspec-go.yml index f68c7c97..4b6440d4 100644 --- a/resources/alpha-buildspec-go.yml +++ b/resources/alpha-buildspec-go.yml @@ -8,12 +8,12 @@ phases: - echo Entered the install phase... - echo About to build $RESOURCE_PATH - export PATH="/usr/local/bin:$PATH" - - ENTRY_PATH=$(python scripts/resourceName.py $RESOURCE_PATH/.rpdk-config) + - TYPE_NAME=$(python scripts/type_name.py $RESOURCE_PATH/.rpdk-config) + - TYPE_CLEAN_NAME="$(echo $TYPE_NAME | sed s/::/-/g | tr ‘[:upper:]’ ‘[:lower:]’)" - /usr/local/bin/dockerd-entrypoint.sh - cat /var/log/docker.log - - echo ENTRY_PATH is $ENTRY_PATH - - BUILD_FILE_NAME=$(cat ENTRY_PATH | sed s/_/-/g) - - BUILD_FILE_NAME="${BUILD_FILE_NAME}.zip" + - echo TYPE_CLEAN_NAME is $TYPE_CLEAN_NAME + - BUILD_FILE_NAME="${TYPE_CLEAN_NAME}.zip" - pip install git+https://github.com/aws-cloudformation/cloudformation-cli-go-plugin.git@master - cd $RESOURCE_PATH - ls /usr/local/bin @@ -27,7 +27,7 @@ phases: - echo Entered the build phase... - cfn validate - cfn generate - - SETUP_STACK_NAME="setup-$(echo $ENTRY_PATH | sed s/_/-/g)" + - SETUP_STACK_NAME="setup-$(echo $TYPE_CLEAN_NAME | sed s/_/-/g)" - rain deploy test/setup.yml $SETUP_STACK_NAME -y - make - ls -ltra diff --git a/resources/beta-buildspec-go.yml b/resources/beta-buildspec-go.yml index 732052ac..1c657815 100644 --- a/resources/beta-buildspec-go.yml +++ b/resources/beta-buildspec-go.yml @@ -8,9 +8,10 @@ phases: - echo Entered the install phase... - echo About to build $RESOURCE_PATH - export PATH="/usr/local/bin:$PATH" - - ENTRY_PATH=$(python scripts/resourceName.py $RESOURCE_PATH/.rpdk-config) - - echo ENTRY_PATH is $ENTRY_PATH - - BUILD_FILE_NAME=$(cat ENTRY_PATH | sed s/_/-/g) + - TYPE_NAME=$(python scripts/type_name.py $RESOURCE_PATH/.rpdk-config) + - TYPE_CLEAN_NAME="$(echo $TYPE_NAME | sed s/::/-/g | tr ‘[:upper:]’ ‘[:lower:]’)" + - echo TYPE_CLEAN_NAME is $TYPE_CLEAN_NAME + - BUILD_FILE_NAME=$(cat TYPE_CLEAN_NAME | sed s/_/-/g) - BUILD_FILE_NAME="${BUILD_FILE_NAME}.zip" - pip install git+https://github.com/aws-cloudformation/cloudformation-cli-python-plugin.git@master - cd $RESOURCE_PATH @@ -25,7 +26,7 @@ phases: - ../../release/deregister-all.sh us-east-1 RESOURCE - make - cfn submit --set-default - - INTEG_STACK_NAME="integ-$(echo $ENTRY_PATH | sed s/_/-/g)" + - INTEG_STACK_NAME="integ-$(echo $TYPE_CLEAN_NAME | sed s/_/-/g)" - rain deploy test/integ.yml $INTEG_STACK_NAME -y finally: - cat rpdk.log diff --git a/resources/prod-buildspec-go.yml b/resources/prod-buildspec-go.yml index d4bd3720..12eddf17 100644 --- a/resources/prod-buildspec-go.yml +++ b/resources/prod-buildspec-go.yml @@ -10,8 +10,6 @@ phases: - export PATH="/usr/local/bin:$PATH" - /usr/local/bin/dockerd-entrypoint.sh - cat /var/log/docker.log - - ENTRY_PATH=$(python scripts/resourceName.py $RESOURCE_PATH/.rpdk-config) - - echo ENTRY_PATH is $ENTRY_PATH - TYPE_NAME=$(python scripts/type_name.py $RESOURCE_PATH/.rpdk-config) - echo TYPE_NAME is $TYPE_NAME - cd $RESOURCE_PATH diff --git a/scripts/resourceName.py b/scripts/resourceName.py deleted file mode 100644 index d8155a0b..00000000 --- a/scripts/resourceName.py +++ /dev/null @@ -1,15 +0,0 @@ -"Gets the name of the folder with the handler code" - -import json -import sys - -def main(rpdk_path): - "Get the source folder from rpdk config" - - with open(rpdk_path) as f: - obj = json.load(f) - entrypoint = obj["typeName"] - print(entrypoint.replace("::", "_").lower()) - -if __name__ == "__main__": - main(sys.argv[1])