class Shelter::CLI::Command::Resource
Resource
subcommand for Shelter
Basic Directory Structure¶ ↑
By default Shelter
is looking for a directory named resources
with a subdirectory name templates
.
In the templates
subdirectory we can define different templates. They have to be a valid CloudFormation .yaml
file.
In the resources
directory we can define different resources. They have to be defined in .yaml
format with a few specific tags in it.
+-- resources | +-- templates | | `-- restricted-s3.yaml | `-- testbucketresource.yaml
You can specify where is your resources
directory in Shelterfile.rb
with resource_directory
.
Templates definition¶ ↑
In our example above, we have only one template named restricted-s3
. This template defined a CloudFormation stack that contains an S3 bucket, an IAM User and a Policy for that specific user which restricts the user to be able to reach only our new S3 Bucket, but nothing else. User we create the IAM User, we create a new AccessKey pair, so we can use the credentials in our infrastucrute. Now, for the simplicity we did not define a KMS key.
As an output we export the newly created AccessKeyID
and AccessKeySecret
pair.
We have three template parameters:
-
S3 Bucket name
-
Client name for tagging our S3 Bucket (reason: billing)
-
Project name for tagging our S3 Bucket (reason: billing)
resources/templates/restricted-s3.yaml:
--- AWSTemplateFormatVersion: "2010-09-09" Parameters: BucketName: Type: String Description: Created S3 bucket Client: Type: String Description: Name of the client for tagging Project: Type: String Description: Project tag Resources: Bucket: Type: AWS::S3::Bucket Properties: BucketName: !Ref BucketName Tags: - { Key: "project", Value: !Ref Project } - { Key: "client", Value: !Ref Client } S3Policy: Type: "AWS::IAM::Policy" Properties: PolicyName: !Join ["-", ["s3", !Ref BucketName]] Users: - !Ref NewUser PolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Action: - "s3:PutObject" - "s3:GetObjectAcl" - "s3:GetObject" - "s3:GetObjectTorrent" - "s3:GetBucketTagging" - "s3:GetObjectTagging" - "s3:ListBucket" - "s3:PutObjectTagging" - "s3:DeleteObject" Resource: - !GetAtt [Bucket, Arn] - !Join ["", [!GetAtt [Bucket, Arn], "/*"]] NewUser: Type: "AWS::IAM::User" Properties: UserName: !Join ["-", ["s3", !Ref BucketName, "user"]] Path: / AccessKey: Type: AWS::IAM::AccessKey Properties: UserName: !Ref NewUser Outputs: AccessKeyID: Value: !Ref AccessKey SecretKeyID: Value: !GetAtt AccessKey.SecretAccessKey
Resource
definition¶ ↑
Now we have a template named restricted-s3
. We can start creating resources with this template. For now we can create a backup user and S3 bucket, so all of our servers with a specific label can call AWS API to make some backup on our specific S3 bucket.
Let's create a file under resources
:
--- name: testbucketresource template: restricted-s3 capabilities: - CAPABILITY_NAMED_IAM tags: random: yes project: test client: cheppers extra: something parameters: BucketName: my-testresource Client: cheppers Project: test
Here we go. Basically all of the keys in this file are required, or if we don't define them, they will be empty (like tags).
Name¶ ↑
This will be the name of our resource. It will be stack name as well, but prefixed with the res-
string. In this case res-testbucketresource
.
Template¶ ↑
This value defines which one of our template we want to use. In this case we want to use our only one restricted-s3
which is defined in resources/templates/restricted-s3.yaml
.
Capabilities¶ ↑
AWS CloudFormation capabilities. For more details check. [AWS API Documentation](docs.aws.amazon.com/AWSCloudFormation/latest/APIReference/API_CreateStack.html). Now we want to manage IAM resources, so we need CAPABILITY_IAM
in general, but now we give them custom names so we need CAPABILITY_NAMED_IAM
.
Tags¶ ↑
This is a simple key-value list. Our CloudFormation stack will be tagged with these tags.
Parameters¶ ↑
This is a simple ket-value list. That's how we can define parameters for our CloudFormation template.
Public Instance Methods
With create
, we can create a specific resource.
$ bundle exec shelter resource create testbucketresource Waiting for 'stack_create_complete' on 'res-testbucketresource'...
# File lib/cli/command/resource.rb 262 def create(resource_name) 263 res = read_resource(resource_name) 264 cf_client.create_stack( 265 stack_name: res['name'], capabilities: res['capabilities'], 266 template_body: read_template(res['template']), 267 tags: res['tags'], parameters: res['parameters'] 268 ) 269 wait_until(:stack_create_complete, res['name']) 270 end
With delete
we can delete the whole stack.
$ bundle exec shelter resource delete testbucketresource Waiting for 'stack_delete_complete' on 'res-testbucketresource'...
# File lib/cli/command/resource.rb 251 def delete(resource_name) 252 resource = read_resource(resource_name) 253 cf_client.delete_stack(stack_name: resource['name']) 254 wait_until(:stack_delete_complete, resource['name']) 255 end
With list
we can check our resource inventory.
$ bundle exec shelter resource list testbucketresource
# File lib/cli/command/resource.rb 176 def list 177 Dir.glob("#{App.config.resource_directory}/*.yaml").each do |res| 178 puts File.basename(res, '.yaml') 179 end 180 end
If we defined Outputs in our template, we can easily list them all with output
command
$ bundle exec shelter resource output testbucketresource AccessKeyID: AKIXXXXXXXXXXXXXXXXX SecretKeyID: 3cXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXuI
# File lib/cli/command/resource.rb 219 def output(resource_name) 220 resource = read_resource(resource_name) 221 222 stack = cf_client.describe_stacks( 223 stack_name: resource['name'] 224 ).stacks.first 225 226 stack.outputs.each { |out| display_stack_output(out) } 227 end
With status
we can ask for the stack status.
$ bundle exec shelter resource status testbucketresource Resource ID: AKIXXXXXXXXXXXXXXXXX Resource Type: AWS::IAM::AccessKey Resource Status: CREATE_COMPLETE Resource ID: cheppers-testresource Resource Type: AWS::S3::Bucket Resource Status: CREATE_COMPLETE Resource ID: s3-cheppers-testresource-user Resource Type: AWS::IAM::User Resource Status: CREATE_COMPLETE Resource ID: res-t-S3Po-W66XXXXXXXXX Resource Type: AWS::IAM::Policy Resource Status: CREATE_COMPLETE
# File lib/cli/command/resource.rb 198 def status(resource_name) 199 resource = read_resource(resource_name) 200 201 stack = cf_client.describe_stacks( 202 stack_name: resource['name'] 203 ).stacks.first 204 205 cf_client.describe_stack_resources( 206 stack_name: stack.stack_name 207 ).stack_resources.each { |r| display_stack_resource(r) } 208 rescue Aws::CloudFormation::Errors::ValidationError 209 puts "#{resource_name} does not exist" 210 end
With update
, we can update a specific resource.
$ bundle exec shelter resource update testbucketresource Waiting for 'stack_update_complete' on 'res-testbucketresource'...
# File lib/cli/command/resource.rb 234 def update(resource_name) 235 res = read_resource(resource_name) 236 cf_client.update_stack( 237 stack_name: res['name'], capabilities: res['capabilities'], 238 template_body: read_template(res['template']), 239 tags: res['tags'], parameters: res['parameters'] 240 ) 241 wait_until(:stack_update_complete, resource['name']) 242 rescue Aws::CloudFormation::Errors::ValidationError => e 243 puts e.message 244 end
Private Instance Methods
Reads and validates a resource description file.
If mandatory fields are not defined, it will rais an error. For every other fields, it just fills with default value.
# File lib/cli/command/resource.rb 281 def read_resource(name) 282 res = YAML.load_file( 283 "#{App.config.resource_directory}/#{name}.yaml" 284 ) 285 raise 'No name specified...' if res['name'].nil? 286 287 res['name'] = "res-#{res['name']}" 288 res['capabilities'] ||= [] 289 res['tags'] = stack_meta(res['tags'] || {}) 290 res['parameters'] = stack_params(res['parameters'] || {}) 291 res 292 end
It's simply reads a speciifc template file content
# File lib/cli/command/resource.rb 300 def read_template(name) 301 File.open("#{resource_template_dir}/#{name}.yaml").read 302 end
Easier to reference on templates directory
# File lib/cli/command/resource.rb 295 def resource_template_dir 296 "#{App.config.resource_directory}/templates" 297 end