cfn-backup 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f9b485ca70d3a19a3c50e4208ed8deea0147308314c053623bbbfd3cdf0fe286
4
+ data.tar.gz: aeeb9d3a8d2ff548927ebb813a44bc1d10e59ca1a2c7f1ce72a1b628402bdce3
5
+ SHA512:
6
+ metadata.gz: bca91db583fc25254df3d1d223f094b074aee87dc89cd0d4f3f69c4d7e4fc88b53444f068a2993e559f9808c108a55fded765452e4e34548ef8a08fb458639d0
7
+ data.tar.gz: ed7a2f7b33c002e0ec9df1fd7aa6cad73e34e916944ab2b67bd73a44a9c00969c9fe95ecf1a333929918d9bbdacd9b9b56aea36bf8e310b9f897f1c6a7695a1e
@@ -0,0 +1,32 @@
1
+ name: Publish to RubyGems
2
+
3
+ on:
4
+ pull_request:
5
+ branches:
6
+ - master
7
+ push:
8
+ branches:
9
+ - master
10
+
11
+ jobs:
12
+ build:
13
+ name: Build + Publish
14
+ runs-on: ubuntu-latest
15
+
16
+ steps:
17
+ - uses: actions/checkout@master
18
+ - name: Set up Ruby 2.6
19
+ uses: actions/setup-ruby@v1
20
+ with:
21
+ version: 2.6.x
22
+
23
+ - name: Publish to RubyGems
24
+ run: |
25
+ mkdir -p $HOME/.gem
26
+ touch $HOME/.gem/credentials
27
+ chmod 0600 $HOME/.gem/credentials
28
+ printf -- "---\n:rubygems_api_key: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
29
+ gem build *.gemspec
30
+ gem push *.gem
31
+ env:
32
+ GEM_HOST_API_KEY: ${{secrets.RUBYGEMS_AUTH_TOKEN}}
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ output/
11
+ *.gem
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in cfn-backup.gemspec
4
+ gemspec
@@ -0,0 +1,63 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ cfn-backup (0.1.0)
5
+ cfhighlander (~> 0.10.3, < 1)
6
+ cfndsl (~> 0.17.2, < 1)
7
+ thor (~> 0.20)
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ aws-eventstream (1.0.3)
13
+ aws-partitions (1.260.0)
14
+ aws-sdk-cloudformation (1.29.0)
15
+ aws-sdk-core (~> 3, >= 3.71.0)
16
+ aws-sigv4 (~> 1.1)
17
+ aws-sdk-core (3.86.0)
18
+ aws-eventstream (~> 1.0, >= 1.0.2)
19
+ aws-partitions (~> 1, >= 1.239.0)
20
+ aws-sigv4 (~> 1.1)
21
+ jmespath (~> 1.0)
22
+ aws-sdk-ec2 (1.129.0)
23
+ aws-sdk-core (~> 3, >= 3.71.0)
24
+ aws-sigv4 (~> 1.1)
25
+ aws-sdk-kms (1.27.0)
26
+ aws-sdk-core (~> 3, >= 3.71.0)
27
+ aws-sigv4 (~> 1.1)
28
+ aws-sdk-s3 (1.60.1)
29
+ aws-sdk-core (~> 3, >= 3.83.0)
30
+ aws-sdk-kms (~> 1)
31
+ aws-sigv4 (~> 1.1)
32
+ aws-sigv4 (1.1.0)
33
+ aws-eventstream (~> 1.0, >= 1.0.2)
34
+ cfhighlander (0.10.7)
35
+ aws-sdk-cloudformation (~> 1, < 2)
36
+ aws-sdk-core (~> 3, < 4)
37
+ aws-sdk-ec2 (~> 1, < 2)
38
+ aws-sdk-s3 (~> 1, < 2)
39
+ cfndsl (= 0.17.2)
40
+ duplicate (~> 1.1)
41
+ git (~> 1.4, < 2)
42
+ highline (>= 1.7.10, < 1.8)
43
+ rubyzip (>= 2.0.0, < 3)
44
+ thor (~> 0.20, < 1)
45
+ cfndsl (0.17.2)
46
+ duplicate (1.1.1)
47
+ git (1.5.0)
48
+ highline (1.7.10)
49
+ jmespath (1.4.0)
50
+ rake (10.5.0)
51
+ rubyzip (2.0.0)
52
+ thor (0.20.3)
53
+
54
+ PLATFORMS
55
+ ruby
56
+
57
+ DEPENDENCIES
58
+ bundler (~> 2.0)
59
+ cfn-backup!
60
+ rake (~> 10.0)
61
+
62
+ BUNDLED WITH
63
+ 2.1.2
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2019 lohgannash
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,120 @@
1
+ # CfnBackup
2
+
3
+ Generate & manage configuration for the use of [AWS Backup](https://aws.amazon.com/backup/) to manage backup & retention of your resources. Deployed using CloudFormation.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'cfn-backup'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install cfn-backup
20
+
21
+ Setup your [AWS credentials](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html) by either setting a profile or exporting them as environment variables.
22
+
23
+ ## Usage
24
+
25
+ ```bash
26
+ Commands:
27
+ cfn-vpn --version, -v # Print the version
28
+ cfn-vpn generate --stack-name --config # Generate the CloudFormation templates
29
+ cfn-vpn publish --stack-name --config --source-bucket # Generate & publish the CloudFormation templates to S3
30
+ cfn-vpn help [COMMAND] # Describe available commands or one specific command
31
+ ```
32
+
33
+ Global Options
34
+
35
+ ```bash
36
+ p, [--profile=PROFILE] # AWS Profile
37
+ r, [--region=REGION] # AWS Region
38
+ # Default: ENV['AWS_REGION']
39
+ [--verbose], [--no-verbose] # Set log level to debug
40
+ ```
41
+
42
+ ## How It Works
43
+
44
+ Once you have decided upon using the default configuration or you are instead providing your custom configuration, use the generate command to verify the CloudFormation is valid. Then, run the publish command, passing your source bucket to deploy the templates to S3. Ensure you have your AWS credentials and region set up either as environment variables or using the `--profile` flag.
45
+
46
+ Once published, you will be given the S3 URL to the master template. Launch this in CloudFormation, and this will create the stack and the nessecary resources.
47
+
48
+ This will create the following:
49
+
50
+ * Backup Vault - A vault to store the backups in
51
+ * Backup Plan - A single backup plan containing the rules and resource selection
52
+ * Backup Rules
53
+ * Daily Rule
54
+ * Weekly Rule
55
+ * Monthly Rule
56
+ * Yearly Rule
57
+ * Backup Selection - A single backup selection covering the tag key-value pair specified in your default or custom config
58
+
59
+ Simply ensure any resources you wish to be backed up have the tagged applied correctly and they will be included in the next backup job. You can add/remove tags to resources at any time without altering the CloudFormation to ensure new resources will be picked up.
60
+
61
+ The following resources are currently supported by AWS Backup:
62
+ * EFS File Systems
63
+ * DynamoDB Tables
64
+ * EBS Volumes (Tagging EC2 Instances is supported, doing this will backup all volumes attached)
65
+ * RDS Instances
66
+ * Storage Gateway
67
+
68
+ ## Custom Configuration
69
+
70
+ You can create a custom config file to override the global defaults by providing the path to a YAML file using the `--config` flag.
71
+ This will perform a deep merge on the global config, meaning you only need to provide the values you want to override in your custom config. The global config file looks like this:
72
+
73
+ ```yaml
74
+ # Determines what tag key/value the backup selection will look for on resources
75
+ tag_key: cfnbackup:enabled
76
+ tag_value: true
77
+
78
+ # The default retention values (in days). Follows the Grandfather-father-son backup
79
+ daily_retention: 14 # 14 Days
80
+ weekly_retention: 56 # 8 Weeks
81
+ monthly_retention: 365 # 12 Months
82
+ yearly_retention: 3652 # 10 Years
83
+
84
+ # The default cron expressions for each rule (Space-seperated, AWS friendly)
85
+ daily_cron: 0 0 * * ? * # At 12:00 AM UTC, every day
86
+ weekly_cron: 0 0 ? * 1 * # At 12:00 AM UTC, only on Sunday
87
+ monthly_cron: 0 0 1 * ? * # At 12:00 AM UTC, on day 1 of the month
88
+ yearly_cron: 0 0 1 1 ? * # At 12:00 AM UTC, on day 1 of the month, only in January
89
+ ```
90
+
91
+ ## Custom Rules
92
+
93
+ If there are additional rules outside of the default Daily-Weekly-Monthly-Yearly plan, you can define these within your custom configuration file. You must define all properties specified in the example below:
94
+
95
+ ```yaml
96
+ custom_rules:
97
+ hourly: # Alphanumeric name describing the rule
98
+ cron: 0 0/1 * * ? * # Space-seperated AWS cron expression
99
+ retention: 3 # Number in days to retain backups taken by this rule
100
+ tag_key: cfnbackup:custom # A tag key that the service will look for on resources
101
+ tag_value: hourly # The tag value that must match on the provided tag key
102
+ ```
103
+
104
+ This will create a new Backup Plan, Backup Rule and Backup Selection per custom rule defined here. None of the regular configuration defaults or custom configuration defined in the previous section will have any impact on custom rules.
105
+
106
+ **NOTE:** AWS Backup has a limitation where the minimum interval between jobs for a backup rule is 60 minutes. This means the lowest interval rule you can currently create will allow you to create *hourly* backups.
107
+
108
+ ## AWS Backup Pricing
109
+
110
+ With AWS Backup, you pay only for the amount of backup storage you use and the amount of backup data you restore in the month. There is no minimum fee and there are no set-up charges. Deploying the CloudFormation is free, and so are the AWS Backup resources.
111
+
112
+ Pricing for backup storage and restore differentiates between regions and resources. Please see the [AWS Backup pricing guide](https://aws.amazon.com/backup/pricing/) for a detailed breakdown per region for each resource.
113
+
114
+ ## Contributing
115
+
116
+ Bug reports and pull requests are welcome on GitHub at https://github.com/base2services/cfn-backup.
117
+
118
+ ## License
119
+
120
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+ task :default => :spec
@@ -0,0 +1,42 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "cfnbackup/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "cfn-backup"
8
+ spec.version = CfnBackup::VERSION
9
+ spec.authors = ["lohgannash"]
10
+ spec.email = ["lohgannash@gmail.com"]
11
+
12
+ spec.summary = %q{Generates templates and configuration for AWS Backup via CloudFormation}
13
+ spec.description = %q{Geneate templates and configuration for AWS Backup via CloudFormation}
14
+ spec.homepage = "https://github.com/base2services/cfn-backup"
15
+ spec.license = "MIT"
16
+
17
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
18
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
19
+ if spec.respond_to?(:metadata)
20
+ spec.metadata["homepage_uri"] = spec.homepage
21
+ spec.metadata["source_code_uri"] = "https://github.com/base2services/cfn-backup"
22
+ else
23
+ raise "RubyGems 2.0 or newer is required to protect against " \
24
+ "public gem pushes."
25
+ end
26
+
27
+ # Specify which files should be added to the gem when it is released.
28
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
29
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
30
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
31
+ end
32
+ spec.bindir = "exe"
33
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
34
+ spec.require_paths = ["lib", "components"]
35
+
36
+ spec.add_dependency "thor", "~> 0.20"
37
+ spec.add_dependency 'cfhighlander', '~> 0.10.3', '<1'
38
+ spec.add_dependency 'cfndsl', '~> 0.17.2', '<1'
39
+
40
+ spec.add_development_dependency "bundler", "~> 2.0"
41
+ spec.add_development_dependency "rake", "~> 10.0"
42
+ end
@@ -0,0 +1,17 @@
1
+ CfhighlanderTemplate do
2
+
3
+ Parameters do
4
+ ComponentParam 'StackName'
5
+ ComponentParam 'TagKey'
6
+ ComponentParam 'TagValue'
7
+ ComponentParam 'DailyRetention'
8
+ ComponentParam 'WeeklyRetention'
9
+ ComponentParam 'MonthlyRetention'
10
+ ComponentParam 'YearlyRetention'
11
+ ComponentParam 'DailyCron'
12
+ ComponentParam 'WeeklyCron'
13
+ ComponentParam 'MonthlyCron'
14
+ ComponentParam 'YearlyCron'
15
+ end
16
+
17
+ end
@@ -0,0 +1,128 @@
1
+ CloudFormation do
2
+
3
+ Backup_BackupVault(:BackupVault) do
4
+ BackupVaultName FnSub("${StackName}-BackupVault")
5
+ end
6
+
7
+ Backup_BackupPlan(:BackupPlan) do
8
+ DependsOn :BackupVault
9
+ BackupPlan {
10
+ BackupPlanName FnSub("${StackName}-Plan")
11
+ BackupPlanRule [
12
+ {
13
+ RuleName: FnSub("${StackName}-DailyRule"),
14
+ StartWindowMinutes: 60,
15
+ TargetBackupVault: FnSub("${StackName}-BackupVault"),
16
+ ScheduleExpression: FnSub("cron(${DailyCron})"),
17
+ Lifecycle: {
18
+ DeleteAfterDays: Ref("DailyRetention")
19
+ },
20
+ RecoveryPointTags: {
21
+ "cfnbackup:type": "daily"
22
+ }
23
+ },
24
+ {
25
+ RuleName: FnSub("${StackName}-WeeklyRule"),
26
+ StartWindowMinutes: 60,
27
+ TargetBackupVault: FnSub("${StackName}-BackupVault"),
28
+ ScheduleExpression: FnSub("cron(${WeeklyCron})"),
29
+ Lifecycle: {
30
+ DeleteAfterDays: Ref("WeeklyRetention")
31
+ },
32
+ RecoveryPointTags: {
33
+ "cfnbackup:type": "weekly"
34
+ }
35
+ },
36
+ {
37
+ RuleName: FnSub("${StackName}-MonthlyRule"),
38
+ StartWindowMinutes: 60,
39
+ TargetBackupVault: FnSub("${StackName}-BackupVault"),
40
+ ScheduleExpression: FnSub("cron(${MonthlyCron})"),
41
+ Lifecycle: {
42
+ DeleteAfterDays: Ref("MonthlyRetention")
43
+ },
44
+ RecoveryPointTags: {
45
+ "cfnbackup:type": "monthly"
46
+ }
47
+ },
48
+ {
49
+ RuleName: FnSub("${StackName}-YearlyRule"),
50
+ StartWindowMinutes: 60,
51
+ TargetBackupVault: FnSub("${StackName}-BackupVault"),
52
+ ScheduleExpression: FnSub("cron(${YearlyCron})"),
53
+ Lifecycle: {
54
+ DeleteAfterDays: Ref("YearlyRetention")
55
+ },
56
+ RecoveryPointTags: {
57
+ "cfnbackup:type": "yearly"
58
+ }
59
+ }
60
+ ]
61
+ }
62
+ end
63
+
64
+ Backup_BackupSelection(:BackupSelection) do
65
+ DependsOn :BackupPlan
66
+ BackupPlanId FnGetAtt(:BackupPlan, :BackupPlanId)
67
+ BackupSelection {
68
+ IamRoleArn FnSub("arn:aws:iam::${AWS::AccountId}:role/service-role/AWSBackupDefaultServiceRole")
69
+ ListOfTags [
70
+ {
71
+ ConditionKey: FnSub("${TagKey}"),
72
+ ConditionType: "STRINGEQUALS",
73
+ ConditionValue: FnSub("${TagValue}")
74
+ }
75
+ ]
76
+ SelectionName FnSub("${StackName}-Selection")
77
+ }
78
+ end
79
+
80
+ custom_rules = external_parameters.fetch(:custom_rules, [])
81
+ custom_rules.each do |rule|
82
+
83
+ rule_name = rule[0]
84
+ rule_cron = rule[1]['cron']
85
+ rule_retention = rule[1]['retention']
86
+ rule_tag_key = rule[1]['tag_key']
87
+ rule_tag_value = rule[1]['tag_value']
88
+
89
+ Backup_BackupPlan("#{rule_name}BackupPlan") do
90
+ DependsOn :BackupVault
91
+ BackupPlan {
92
+ BackupPlanName FnSub("${StackName}-#{rule_name}Plan")
93
+ BackupPlanRule [
94
+ {
95
+ RuleName: FnSub("${StackName}-#{rule_name}Rule"),
96
+ StartWindowMinutes: 60,
97
+ TargetBackupVault: FnSub("${StackName}-BackupVault"),
98
+ ScheduleExpression: FnSub("cron(#{rule_cron})"),
99
+ Lifecycle: {
100
+ DeleteAfterDays: FnSub("#{rule_retention}")
101
+ },
102
+ RecoveryPointTags: {
103
+ "cfnbackup:type": "#{rule_name}"
104
+ }
105
+ }
106
+ ]
107
+ }
108
+ end
109
+
110
+ Backup_BackupSelection("#{rule_name}BackupSelection") do
111
+ DependsOn "#{rule_name}BackupPlan"
112
+ BackupPlanId FnGetAtt("#{rule_name}BackupPlan", :BackupPlanId)
113
+ BackupSelection {
114
+ IamRoleArn FnSub("arn:aws:iam::${AWS::AccountId}:role/service-role/AWSBackupDefaultServiceRole")
115
+ ListOfTags [
116
+ {
117
+ ConditionKey: FnSub("#{rule_tag_key}"),
118
+ ConditionType: "STRINGEQUALS",
119
+ ConditionValue: FnSub("#{rule_tag_value}")
120
+ }
121
+ ]
122
+ SelectionName FnSub("${StackName}-#{rule_name}Selection")
123
+ }
124
+ end
125
+
126
+ end
127
+
128
+ end
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "cfnbackup"
4
+
5
+ CfnBackup::Cli.start( ARGV )
@@ -0,0 +1,24 @@
1
+ require "thor"
2
+ require "cfnbackup/version"
3
+ require "cfnbackup/generate"
4
+ require "cfnbackup/publish"
5
+
6
+ module CfnBackup
7
+ class Cli < Thor
8
+
9
+ map %w[--version -v] => :__print_version
10
+ desc "--version, -v", "Print the version"
11
+
12
+ def __print_version
13
+ puts CfnBackup::VERSION
14
+ end
15
+
16
+ register CfnBackup::Generate, 'generate', 'generate', 'Generates a CloudFormation template'
17
+ tasks['generate'].options = CfnBackup::Generate.class_options
18
+
19
+ register CfnBackup::Publish, 'publish', 'publish', 'Generates, validates and publishes the CloudFormation to S3'
20
+ tasks['publish'].options = CfnBackup::Publish.class_options
21
+
22
+ end
23
+
24
+ end
@@ -0,0 +1,59 @@
1
+ require 'cfhighlander.publisher'
2
+ require 'cfhighlander.factory'
3
+ require 'cfhighlander.validator'
4
+
5
+ require 'cfnbackup/version'
6
+
7
+ module CfnBackup
8
+ class CfHighlander
9
+
10
+ def initialize(region, name, config, output_dir)
11
+ @component_name = name
12
+ @region = region
13
+ @config = config
14
+ @cfn_output_format = 'yaml'
15
+ @output_dir = output_dir
16
+ ENV['CFHIGHLANDER_WORKDIR'] = output_dir
17
+ end
18
+
19
+ def render()
20
+ component = load_component(@component_name)
21
+ Log.logger.debug("Compiling component, saving generated templates to #{@output_dir}")
22
+ compiled = compile_component(component)
23
+ validate_component(component,compiled.cfn_template_paths)
24
+ cfn_template_paths = compiled.cfn_template_paths
25
+ return compiled
26
+ end
27
+
28
+ def publish(cf_compiler)
29
+ publisher = Cfhighlander::Publisher::ComponentPublisher.new(cf_compiler.component, false, @cfn_output_format)
30
+ Log.logger.debug("Publishing compiled templates to S3")
31
+ publisher.publishFiles(cf_compiler.cfn_template_paths)
32
+ Log.logger.debug("Master template URL: #{publisher.getTemplateUrl}")
33
+ return publisher.getTemplateUrl
34
+ end
35
+
36
+ private
37
+
38
+ def load_component(component_name)
39
+ factory = Cfhighlander::Factory::ComponentFactory.new
40
+ component = factory.loadComponentFromTemplate(component_name)
41
+ component.config = @config
42
+ component.version = CfnBackup::VERSION
43
+ component.load()
44
+ return component
45
+ end
46
+
47
+ def compile_component(component)
48
+ component_compiler = Cfhighlander::Compiler::ComponentCompiler.new(component)
49
+ component_compiler.compileCloudFormation(@cfn_output_format)
50
+ return component_compiler
51
+ end
52
+
53
+ def validate_component(component,template_paths)
54
+ component_validator = Cfhighlander::Cloudformation::Validator.new(component)
55
+ component_validator.validate(template_paths, @cfn_output_format)
56
+ end
57
+
58
+ end
59
+ end
@@ -0,0 +1,85 @@
1
+ require 'thor'
2
+ require 'fileutils'
3
+ require 'cfnbackup/log'
4
+ require 'cfnbackup/utils'
5
+ require 'cfnbackup/cfhighlander'
6
+
7
+ module CfnBackup
8
+ class Generate < Thor::Group
9
+ include Thor::Actions
10
+ include Thor::Shell
11
+ include CfnBackup::Log
12
+
13
+ class_option :profile, aliases: :p, desc: 'AWS Profile'
14
+ class_option :region, aliases: :r, default: ENV['AWS_REGION'], desc: 'AWS Region'
15
+ class_option :verbose, desc: 'Enable DEBUG logging', type: :boolean
16
+
17
+ class_option :config, desc: 'Provide a path to a config file to override defaults' # Optional
18
+ class_option :stack_name, desc: 'Override the default stack name for the AWS Backup stack' # Optional
19
+
20
+ def self.source_root
21
+ File.dirname(__FILE__)
22
+ end
23
+
24
+ def set_loglevel
25
+ Log.logger.level = Logger::DEBUG if @options['verbose']
26
+ Log.logger.debug("Log level set to DEBUG")
27
+ end
28
+
29
+ # Sets the stack name to be used in template name & resource names. Defaults to cfnbackup if none provided
30
+ def set_stack_name
31
+ if @options['stack_name']
32
+ @stack_name = @options['stack_name']
33
+ Log.logger.debug("Stack name provided, set to #{@stack_name}")
34
+ else
35
+ @stack_name = "cfnbackup"
36
+ Log.logger.debug("Using default stack name #{@stack_name}")
37
+ end
38
+ end
39
+
40
+ # Creates the build dir based on the stack name
41
+ def create_build_directory
42
+ @build_dir = "output/#{@stack_name}"
43
+ Log.logger.debug("Creating output directory #{@build_dir}")
44
+ FileUtils.mkdir_p(@build_dir)
45
+ end
46
+
47
+ def initialize_config
48
+ Log.logger.debug("Initialising config, loading global config file")
49
+ # Load the global config file (should always be present in the hardcoded path)
50
+ global_config_path = File.join(File.dirname(__FILE__), '../config/global_config.yml')
51
+ global_config = YAML.load(File.read(global_config_path))
52
+ # Check if a custom config file has been provided with the --config flag
53
+ if @options['config']
54
+ Log.logger.debug("Custom config file path provided, attempting to load")
55
+ # Check if the file/path provided is a valid file and attempt to load it using the YAML object.
56
+ if File.file?(@options['config'])
57
+ custom_config = YAML.load(File.read(@options['config']))
58
+ Log.logger.debug("Custom config file loaded, deep merging with global config")
59
+ # Peform a deep merge on the loaded global config and the custom config
60
+ @config = CfnBackup::Utils.deep_merge(global_config, custom_config)
61
+ else
62
+ abort("Could not find or load file #{@options['config']}")
63
+ end
64
+ else
65
+ # If no custom config was provided no further action is needed
66
+ Log.logger.debug("No custom config file provided, using all default values")
67
+ @config = global_config
68
+ end
69
+ @config['stack_name'] = @stack_name
70
+ end
71
+
72
+ def generate_cloudformation
73
+ Log.logger.debug("Populating CfHighlander file from template")
74
+ # Inject the initalised config list into the text template which will use these to populate parameters
75
+ template('templates/cfnbackup.cfhighlander.rb.tt', "#{@build_dir}/#{@stack_name}.cfhighlander.rb", @config, force: true)
76
+ Log.logger.debug("Generating CloudFormation template from #{@build_dir}/#{@stack_name}.cfhighlander.rb")
77
+ # Initalise the CfHighlander object and run a render, this will compile and validate the component, outputting cloudformation
78
+ cfhl = CfnBackup::CfHighlander.new(@options['region'], @stack_name, @config, @build_dir)
79
+ template_path = cfhl.render()
80
+ Log.logger.debug("CloudFormation template generated and validated")
81
+ end
82
+
83
+ end
84
+
85
+ end
@@ -0,0 +1,38 @@
1
+ require 'logger'
2
+
3
+ module CfnBackup
4
+ module Log
5
+
6
+ def self.colors
7
+ @colors ||= {
8
+ ERROR: 31, # Red
9
+ WARN: 33, # Yellow
10
+ DEBUG: 32, # Green
11
+ INFO: 0
12
+ }
13
+ end
14
+
15
+ def self.logger
16
+ if @logger.nil?
17
+ @logger = Logger.new(STDOUT)
18
+ @logger.level = Logger::INFO
19
+ @logger.formatter = proc do |severity, datetime, progname, msg|
20
+ "\e[#{colors[severity.to_sym]}m#{severity}: - #{msg}\e[0m\n"
21
+ end
22
+ end
23
+ @logger
24
+ end
25
+
26
+ def self.logger=(logger)
27
+ @logger = logger
28
+ end
29
+
30
+ levels = %w(debug info warn error fatal)
31
+ levels.each do |level|
32
+ define_method("#{level.to_sym}") do |msg|
33
+ self.logger.send(level, msg)
34
+ end
35
+ end
36
+
37
+ end
38
+ end
@@ -0,0 +1,104 @@
1
+ require 'thor'
2
+ require 'fileutils'
3
+ require 'cfnbackup/log'
4
+ require 'cfnbackup/generate'
5
+ require 'cfnbackup/utils'
6
+ require 'cfnbackup/cfhighlander'
7
+
8
+ module CfnBackup
9
+ class Publish < Thor::Group
10
+ include Thor::Actions
11
+ include Thor::Shell
12
+ include CfnBackup::Log
13
+
14
+ class_option :profile, aliases: :p, desc: 'AWS Profile'
15
+ class_option :region, aliases: :r, default: ENV['AWS_REGION'], desc: 'AWS Region'
16
+ class_option :verbose, desc: 'Enable DEBUG logging', type: :boolean
17
+
18
+ class_option :config, desc: 'Provide a path to a config file to override defaults' # Optional
19
+ class_option :stack_name, desc: 'Override the default stack name for the AWS Backup stack' # Optional
20
+ class_option :source_bucket, desc: 'Source bucket to upload template files to' # Required
21
+
22
+ def self.source_root
23
+ File.dirname(__FILE__)
24
+ end
25
+
26
+ def set_loglevel
27
+ Log.logger.level = Logger::DEBUG if @options['verbose']
28
+ Log.logger.debug("Log level set to DEBUG")
29
+ end
30
+
31
+ # Sets the stack name to be used in template name & resource names. Defaults to cfnbackup if none provided
32
+ def set_stack_name
33
+ if @options['stack_name']
34
+ @stack_name = @options['stack_name']
35
+ Log.logger.debug("Stack name provided, set to #{@stack_name}")
36
+ else
37
+ @stack_name = "cfnbackup"
38
+ Log.logger.debug("Using default stack name #{@stack_name}")
39
+ end
40
+ end
41
+
42
+ # Enforces source bucket to be provided and sets it if it is
43
+ def set_source_bucket
44
+ if !@options['source_bucket']
45
+ Log.logger.debug("No source bucket provided")
46
+ abort("Set source S3 bucket with --source-bucket flag")
47
+ else
48
+ Log.logger.debug("Setting source bucket to #{@options['source_bucket']}")
49
+ @source_bucket = @options['source_bucket']
50
+ end
51
+ end
52
+
53
+ # Creates the build dir based on the stack name
54
+ def create_build_directory
55
+ @build_dir = "output/#{@stack_name}"
56
+ Log.logger.debug("Creating output directory #{@build_dir}")
57
+ FileUtils.mkdir_p(@build_dir)
58
+ end
59
+
60
+ def initialize_config
61
+ Log.logger.debug("Initialising config, loading global config file")
62
+ # Load the global config file (should always be present in the hardcoded path)
63
+ global_config_path = File.join(File.dirname(__FILE__), '../config/global_config.yml')
64
+ global_config = YAML.load(File.read(global_config_path))
65
+ # Check if a custom config file has been provided with the --config flag
66
+ if @options['config']
67
+ Log.logger.debug("Custom config file path provided, attempting to load")
68
+ # Check if the file/path provided is a valid file and attempt to load it using the YAML object.
69
+ if File.file?(@options['config'])
70
+ custom_config = YAML.load(File.read(@options['config']))
71
+ Log.logger.debug("Custom config file loaded, deep merging with global config")
72
+ # Peform a deep merge on the loaded global config and the custom config
73
+ @config = CfnBackup::Utils.deep_merge(global_config, custom_config)
74
+ else
75
+ abort("Could not find or load file #{@options['config']}")
76
+ end
77
+ else
78
+ # If no custom config was provided no further action is needed
79
+ Log.logger.debug("No custom config file provided, using all default values")
80
+ @config = global_config
81
+ end
82
+ # Load the stack name and source bucket taken from ARGS/Defaults and insert into the final config
83
+ @config['stack_name'] = @stack_name
84
+ @config['source_bucket'] = @source_bucket
85
+ end
86
+
87
+ def publish_cloudformation
88
+ Log.logger.debug("Populating CfHighlander file from template")
89
+ # Inject the initalised config list into the text template which will use these to populate parameters
90
+ template('templates/cfnbackup.cfhighlander.rb.tt', "#{@build_dir}/#{@stack_name}.cfhighlander.rb", @config, force: true)
91
+ Log.logger.debug("Generating CloudFormation template from #{@build_dir}/#{@stack_name}.cfhighlander.rb")
92
+ # Initalise the CfHighlander object and run a render, this will compile and validate the component, outputting cloudformation
93
+ cfhl = CfnBackup::CfHighlander.new(@options['region'], @stack_name, nil, @build_dir)
94
+ compiler = cfhl.render()
95
+ Log.logger.debug("CloudFormation template generated and validated")
96
+ # Publishes the compiled cloudformation to S3 using the source bucket provided, outputting the master stack S3 path
97
+ @template_url = cfhl.publish(compiler)
98
+ say("\n--------- Master Template URL ---------")
99
+ say("#{@template_url}")
100
+ say("---------------------------------------")
101
+ end
102
+
103
+ end
104
+ end
@@ -0,0 +1,33 @@
1
+ CfhighlanderTemplate do
2
+
3
+ ComponentDistribution "s3://<%= @config['source_bucket'] %>/cfnbackup/cloudformation/<%= @config['stack_name'] %>"
4
+
5
+ Parameters do
6
+ ComponentParam 'StackName', '<%= @config['stack_name'] %>'
7
+ ComponentParam 'TagKey', '<%= @config['tag_key'] %>'
8
+ ComponentParam 'TagValue', '<%= @config['tag_value'] %>'
9
+ ComponentParam 'DailyRetention', '<%= @config['daily_retention'] %>'
10
+ ComponentParam 'WeeklyRetention', '<%= @config['weekly_retention'] %>'
11
+ ComponentParam 'MonthlyRetention', '<%= @config['monthly_retention'] %>'
12
+ ComponentParam 'YearlyRetention', '<%= @config['yearly_retention'] %>'
13
+ ComponentParam 'DailyCron', '<%= @config['daily_cron'] %>'
14
+ ComponentParam 'WeeklyCron', '<%= @config['weekly_cron'] %>'
15
+ ComponentParam 'MonthlyCron', '<%= @config['monthly_cron'] %>'
16
+ ComponentParam 'YearlyCron', '<%= @config['yearly_cron'] %>'
17
+ end
18
+
19
+ Component template: 'backup', name: 'backup', render: 'inline', config: <%= @config %> do
20
+ parameter name: 'StackName', value: Ref('StackName')
21
+ parameter name: 'TagKey', value: Ref('TagKey')
22
+ parameter name: 'TagValue', value: Ref('TagValue')
23
+ parameter name: 'DailyRetention', value: Ref('DailyRetention')
24
+ parameter name: 'WeeklyRetention', value: Ref('WeeklyRetention')
25
+ parameter name: 'MonthlyRetention', value: Ref('MonthlyRetention')
26
+ parameter name: 'YearlyRetention', value: Ref('YearlyRetention')
27
+ parameter name: 'DailyCron', value: Ref('DailyCron')
28
+ parameter name: 'WeeklyCron', value: Ref('WeeklyCron')
29
+ parameter name: 'MonthlyCron', value: Ref('MonthlyCron')
30
+ parameter name: 'YearlyCron', value: Ref('YearlyCron')
31
+ end
32
+
33
+ end
@@ -0,0 +1,44 @@
1
+ require 'thor'
2
+ require 'cfnbackup/log'
3
+
4
+ module CfnBackup
5
+ class Utils
6
+ include CfnBackup::Log
7
+
8
+ def self.deep_merge(global, custom)
9
+ # Iterate through each key in the custom yaml file
10
+ custom.each do |k,v|
11
+ Log.logger.debug("Checking global config for #{k}:#{v}")
12
+ # Check if the key exists in the global config
13
+ if global.key? k
14
+ # Check if the current key is a hash
15
+ if global[k].class == Hash && custom[k].class == Hash
16
+ # If it is, we will need to run the deep merge on this again to account for nested config
17
+ Log.logger.debug("Key #{k} is a Hash present in both templates, running merge recursively")
18
+ global[k] = deep_merge(global[k], v)
19
+ else
20
+ # Once (if any) recursion is complete, override the global value for the current key with the custom value
21
+ Log.logger.debug("Overriding defaults with key-value pair #{k}:#{v}")
22
+ global[k] = v
23
+ end
24
+ else
25
+ # If it doesn't exist in the global config, merge it in
26
+ Log.logger.debug("Key-value pair #{k}:#{v} not found in original config, appending")
27
+ global[k] = v
28
+ end
29
+ end
30
+ return global
31
+ end
32
+
33
+ end
34
+ end
35
+
36
+ class Hash
37
+ def without(*keys)
38
+ dup.without!(*keys)
39
+ end
40
+
41
+ def without!(*keys)
42
+ reject! { |key| keys.include?(key) }
43
+ end
44
+ end
@@ -0,0 +1,3 @@
1
+ module CfnBackup
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,16 @@
1
+
2
+ # Determines what tag key/value the backup selection will look for on resources
3
+ tag_key: cfnbackup:enabled
4
+ tag_value: true
5
+
6
+ # The default retention values (in days). Follows the Grandfather-father-son backup
7
+ daily_retention: 14
8
+ weekly_retention: 56
9
+ monthly_retention: 365
10
+ yearly_retention: 3652
11
+
12
+ # The default cron expressions for each rule
13
+ daily_cron: 0 0 * * ? *
14
+ weekly_cron: 0 0 ? * 2 *
15
+ monthly_cron: 0 0 1 * ? *
16
+ yearly_cron: 0 0 1 1 ? *
metadata ADDED
@@ -0,0 +1,149 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cfn-backup
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - lohgannash
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-01-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: thor
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.20'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.20'
27
+ - !ruby/object:Gem::Dependency
28
+ name: cfhighlander
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.10.3
34
+ - - "<"
35
+ - !ruby/object:Gem::Version
36
+ version: '1'
37
+ type: :runtime
38
+ prerelease: false
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - "~>"
42
+ - !ruby/object:Gem::Version
43
+ version: 0.10.3
44
+ - - "<"
45
+ - !ruby/object:Gem::Version
46
+ version: '1'
47
+ - !ruby/object:Gem::Dependency
48
+ name: cfndsl
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: 0.17.2
54
+ - - "<"
55
+ - !ruby/object:Gem::Version
56
+ version: '1'
57
+ type: :runtime
58
+ prerelease: false
59
+ version_requirements: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - "~>"
62
+ - !ruby/object:Gem::Version
63
+ version: 0.17.2
64
+ - - "<"
65
+ - !ruby/object:Gem::Version
66
+ version: '1'
67
+ - !ruby/object:Gem::Dependency
68
+ name: bundler
69
+ requirement: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - "~>"
72
+ - !ruby/object:Gem::Version
73
+ version: '2.0'
74
+ type: :development
75
+ prerelease: false
76
+ version_requirements: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - "~>"
79
+ - !ruby/object:Gem::Version
80
+ version: '2.0'
81
+ - !ruby/object:Gem::Dependency
82
+ name: rake
83
+ requirement: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - "~>"
86
+ - !ruby/object:Gem::Version
87
+ version: '10.0'
88
+ type: :development
89
+ prerelease: false
90
+ version_requirements: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - "~>"
93
+ - !ruby/object:Gem::Version
94
+ version: '10.0'
95
+ description: Geneate templates and configuration for AWS Backup via CloudFormation
96
+ email:
97
+ - lohgannash@gmail.com
98
+ executables:
99
+ - cfn-backup
100
+ extensions: []
101
+ extra_rdoc_files: []
102
+ files:
103
+ - ".github/workflows/publish-gem.yml"
104
+ - ".gitignore"
105
+ - Gemfile
106
+ - Gemfile.lock
107
+ - LICENSE.txt
108
+ - README.md
109
+ - Rakefile
110
+ - cfn-backup.gemspec
111
+ - components/backup/backup.cfhighlander.rb
112
+ - components/backup/backup.cfndsl.rb
113
+ - exe/cfn-backup
114
+ - lib/cfnbackup.rb
115
+ - lib/cfnbackup/cfhighlander.rb
116
+ - lib/cfnbackup/generate.rb
117
+ - lib/cfnbackup/log.rb
118
+ - lib/cfnbackup/publish.rb
119
+ - lib/cfnbackup/templates/cfnbackup.cfhighlander.rb.tt
120
+ - lib/cfnbackup/utils.rb
121
+ - lib/cfnbackup/version.rb
122
+ - lib/config/global_config.yml
123
+ homepage: https://github.com/base2services/cfn-backup
124
+ licenses:
125
+ - MIT
126
+ metadata:
127
+ homepage_uri: https://github.com/base2services/cfn-backup
128
+ source_code_uri: https://github.com/base2services/cfn-backup
129
+ post_install_message:
130
+ rdoc_options: []
131
+ require_paths:
132
+ - lib
133
+ - components
134
+ required_ruby_version: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ required_rubygems_version: !ruby/object:Gem::Requirement
140
+ requirements:
141
+ - - ">="
142
+ - !ruby/object:Gem::Version
143
+ version: '0'
144
+ requirements: []
145
+ rubygems_version: 3.0.3
146
+ signing_key:
147
+ specification_version: 4
148
+ summary: Generates templates and configuration for AWS Backup via CloudFormation
149
+ test_files: []