aws_s3_website_sync 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 6cd1d01e70b5021bee1b491605cd4c72d7df85b472890badff176f4068cdf454
4
+ data.tar.gz: 24054c0ea3833aff89c15282fa5f332becf75a8fde0e56574b379d77e2e41adf
5
+ SHA512:
6
+ metadata.gz: 8ad2ac55bdcf593ee3c3c71bb0ba2d937416b4c6363b0c9ca78ba0c02c5a0df2d951368a29ffbd59e8c0a9c845da54fb21a9c2fbc551e752eb88df9131e371e1
7
+ data.tar.gz: 3a139b7ae10d01762285a6444a89e67a68d98d9b899882147663153cb0bc42705aba6b946d956b8d92f799391c9473d0ae5d8faa7d74c1c7ed0a22c678add5c3
data/README.md ADDED
File without changes
@@ -0,0 +1,117 @@
1
+ module AwsS3WebsiteSync
2
+ class Apply
3
+ # path to changeset file
4
+ def self.run(
5
+ changeset_path:,
6
+ aws_access_key_id:,
7
+ aws_secret_access_key:,
8
+ s3_bucket:,
9
+ distribution_id:,
10
+ caller_reference:,
11
+ aws_default_region:,
12
+ build_dir:
13
+ )
14
+ json = File.read(changeset_path)
15
+ data = JSON.parse(json)
16
+
17
+ puts "---[ Apply ]------------------------------------------------------------"
18
+ puts "ChangeSet: #{File.basename(changeset_path,'.json')}"
19
+ puts ""
20
+ puts "WebSync is performing operations:"
21
+
22
+ items_delete = data.select{|t| t["action"] == "delete" }
23
+ keys_delete = items_delete.map{|t|t["path"]}
24
+
25
+ items_create_or_update = data.select{|t| %{create update}.include?(t["action"]) }
26
+
27
+ s3 = Aws::S3::Resource.new({
28
+ region: aws_default_region,
29
+ credentials: Aws::Credentials.new(
30
+ aws_access_key_id,
31
+ aws_secret_access_key
32
+ )
33
+ })
34
+ bucket = s3.bucket s3_bucket
35
+ AwsS3WebsiteSync::Apply.delete bucket, keys_delete
36
+ AwsS3WebsiteSync::Apply.create_or_update bucket, items_create_or_update, build_dir
37
+ AwsS3WebsiteSync::Apply.invalidate aws_access_key_id, aws_secret_access_key, aws_default_region, distribution_id, caller_reference, data
38
+ end
39
+
40
+ def self.delete bucket, keys
41
+ $logger.info "Apply.delete"
42
+ return if keys.empty?
43
+ puts "\t#{keys}"
44
+ bucket.delete_objects({
45
+ delete: { # required
46
+ objects: keys.map{|t|{key: t}},
47
+ quiet: false,
48
+ }
49
+ })
50
+ end
51
+
52
+ # files: [{key: '', path: ''}]
53
+ def self.create_or_update bucket, files, build_dir
54
+ files.each do |data|
55
+ puts "\t#{data["path"]}"
56
+
57
+ file_path = build_dir + "/" + data["path"]
58
+ file_path = file_path + ".html" if !!(data["path"] =~ /\./) == false
59
+
60
+ file = File.open file_path
61
+ md5 = Digest::MD5.hexdigest file.read
62
+ md5 = Base64.encode64([md5].pack("H*")).strip
63
+
64
+ attrs = {
65
+ key: data["path"],
66
+ body: IO.read(file),
67
+ content_md5: md5
68
+ }
69
+ # If it doee not have an extension assume its an html file
70
+ # and explicty set content type to html.
71
+ if !!(data["path"] =~ /\./) == false
72
+ attrs[:content_type] = 'text/html'
73
+ elsif !!(data["path"] =~ /\.html/)
74
+ attrs[:content_type] = 'text/html'
75
+ elsif !!(data["path"] =~ /\.css/)
76
+ attrs[:content_type] = 'text/css'
77
+ elsif !!(data["path"] =~ /\.js/)
78
+ attrs[:content_type] = 'application/x-javascript'
79
+ elsif !!(data["path"] =~ /\.svg/)
80
+ attrs[:content_type] = "image/svg+xml"
81
+ end
82
+ # TEMP
83
+ puts "Content Type Detected: #{attrs[:content_type]}"
84
+ resp = bucket.put_object(attrs)
85
+ end
86
+ end
87
+
88
+ def self.invalidate aws_access_key_id, aws_secret_access_key, aws_default_region, distribution_id, caller_reference, files
89
+ $logger.info "Apply.invalidate"
90
+ items = files.select{|t| %w{create update delete}.include?(t["action"]) }
91
+ items.map!{|t| "/" + t["path"] }
92
+
93
+ cloudfront = Aws::CloudFront::Client.new(
94
+ region: aws_default_region,
95
+ credentials: Aws::Credentials.new(
96
+ aws_access_key_id,
97
+ aws_secret_access_key
98
+ )
99
+ )
100
+ resp = cloudfront.create_invalidation({
101
+ distribution_id: distribution_id, # required
102
+ invalidation_batch: { # required
103
+ paths: { # required
104
+ quantity: items.count, # required
105
+ items: items
106
+ },
107
+ caller_reference: caller_reference + "-#{Time.now.to_i}" # required
108
+ }
109
+ })
110
+ if resp.is_a?(Seahorse::Client::Response)
111
+ puts "Invalidation #{resp.invalidation.id} has been created. Please wait about 60 seconds for it to finish."
112
+ else
113
+ puts "ERROR"
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,23 @@
1
+ module AwsS3WebsiteSync
2
+ class Color
3
+ def self.cyan str
4
+ "\e[36m#{str}\e[0m"
5
+ end
6
+
7
+ def self.red str
8
+ "\e[31m#{str}\e[0m"
9
+ end
10
+
11
+ def self.green str
12
+ "\e[32m#{str}\e[0m"
13
+ end
14
+
15
+ def self.yellow str
16
+ "\e[33m#{str}\e[0m"
17
+ end
18
+
19
+ def self.grey str
20
+ "\e[37m#{str}\e[0m"
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,37 @@
1
+ module AwsS3WebsiteSync
2
+ class List
3
+ def self.local build_dir
4
+ $logger.info "List.local"
5
+ paths = Dir.glob build_dir + "/**/*"
6
+ paths = paths.reject { |f| File.directory?(f) }
7
+ paths.map! do |path|
8
+ md5 = Digest::MD5.hexdigest File.read(path)
9
+ path = path.sub build_dir + '/', ''
10
+ path =
11
+ if path == 'index.html'
12
+ path
13
+ else
14
+ path.sub('.html','')
15
+ end
16
+ {path: path, md5: md5}
17
+ end
18
+ paths
19
+ end
20
+
21
+ def self.remote aws_access_key_id, aws_secret_access_key, s3_bucket, aws_default_region
22
+ $logger.info "List.remote"
23
+ s3 ||= Aws::S3::Resource.new({
24
+ region: aws_default_region,
25
+ credentials: Aws::Credentials.new(
26
+ aws_access_key_id,
27
+ aws_secret_access_key
28
+ )
29
+ })
30
+ bucket = s3.bucket s3_bucket
31
+ keys = bucket.objects.map do |object_summary|
32
+ {path: object_summary.key, md5: object_summary.etag.gsub('"','')}
33
+ end
34
+ keys
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,69 @@
1
+ module AwsS3WebsiteSync
2
+ class Plan
3
+ def self.run(
4
+ output_changeset_path:,
5
+ build_dir:,
6
+ aws_access_key_id:,
7
+ aws_secret_access_key:,
8
+ s3_bucket:,
9
+ aws_default_region:,
10
+ ignore_files:
11
+ )
12
+ paths = AwsS3WebsiteSync::List.local build_dir
13
+ keys = AwsS3WebsiteSync::List.remote aws_access_key_id, aws_secret_access_key, s3_bucket, aws_default_region
14
+
15
+ # Files we should delete
16
+ diff_delete = AwsS3WebsiteSync::Plan.delete paths, keys
17
+
18
+ # Ignore files we plan to delete for create_or_update
19
+ create_or_update_keys = keys.reject{|t| diff_delete.any?(t[:path]) }
20
+
21
+ # Files we should create or update
22
+ diff_create_or_update = AwsS3WebsiteSync::Plan.create_or_update paths, create_or_update_keys
23
+
24
+ AwsS3WebsiteSync::Plan.create_changeset output_changeset_path, diff_delete, diff_create_or_update, ignore_files
25
+ end
26
+
27
+ def self.create_changeset output_changeset_path, diff_delete_keys, diff_create_or_update, ignore_files
28
+ diff_delete = diff_delete_keys.map{|t| {path: t, action: 'delete'} }
29
+ data = diff_delete + diff_create_or_update
30
+
31
+ data.each do |t|
32
+ if ignore_files.include?(t[:path])
33
+ t[:action] = 'ignore'
34
+ end
35
+ end
36
+
37
+ formatted_data = JSON.pretty_generate data
38
+
39
+ FileUtils.mkdir_p File.dirname(output_changeset_path)
40
+ File.write output_changeset_path, formatted_data
41
+ end
42
+
43
+ # Get the differece between the local build directory and S3 bucket
44
+ # return: [Array] a list of keys that should be deleted
45
+ def self.delete paths, keys
46
+ $logger.info "Plan.delete"
47
+ keys.map{|t|t[:path]} - paths.map{|t|t[:path]}
48
+ end
49
+
50
+ # Get the difference of files changed
51
+ def self.create_or_update paths, keys
52
+ $logger.info "Plan.create_update"
53
+ results = []
54
+ paths.each do |p|
55
+ if k = keys.find{|t|t[:path] == p[:path] }
56
+ # existing entry
57
+ if k[:md5] == p[:md5]
58
+ results.push p.merge(action: 'no_change')
59
+ else
60
+ results.push p.merge(action: 'update')
61
+ end
62
+ else
63
+ results.push p.merge(action: 'create')
64
+ end
65
+ end
66
+ results
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,56 @@
1
+ module AwsS3WebsiteSync
2
+ class Preview
3
+ def self.run changeset_path:, silent:
4
+ silent_actions = silent.split(',')
5
+ json = File.read changeset_path
6
+ data = JSON.parse json
7
+ puts "---[ Plan ]------------------------------------------------------------"
8
+ puts "ChangeSet: #{File.basename(changeset_path,'.json')}"
9
+ puts ""
10
+ puts "WebSync will perform the following operations:"
11
+ puts ""
12
+ data = data.sort_by { |t| t["path"] }
13
+
14
+ summary = {
15
+ ignore: 0,
16
+ delete: 0,
17
+ create: 0,
18
+ update: 0,
19
+ no_change: 0
20
+ }
21
+ data.each do |item|
22
+ action =
23
+ case item['action']
24
+ when 'ignore'
25
+ summary[:ignore] += 1
26
+ AwsS3WebsiteSync::Color.grey item['action']
27
+ when 'delete'
28
+ summary[:delete] += 1
29
+ AwsS3WebsiteSync::Color.red item['action']
30
+ when 'create'
31
+ summary[:create] += 1
32
+ AwsS3WebsiteSync::Color.green item['action']
33
+ when 'update'
34
+ summary[:update] += 1
35
+ AwsS3WebsiteSync::Color.cyan item['action']
36
+ when 'no_change'
37
+ summary[:no_change] += 1
38
+ AwsS3WebsiteSync::Color.yellow item['action']
39
+ end
40
+
41
+ unless silent_actions.include?(item["action"])
42
+ puts "\t#{action} #{item["path"]}"
43
+ end
44
+ end # data.each
45
+ puts "--------------------------------------------------------------------"
46
+ puts [
47
+ AwsS3WebsiteSync::Color.grey("ignore: #{summary[:ignore]}"),
48
+ AwsS3WebsiteSync::Color.red("delete: #{summary[:delete]}"),
49
+ AwsS3WebsiteSync::Color.green("create: #{summary[:create]}"),
50
+ AwsS3WebsiteSync::Color.cyan("update: #{summary[:update]}"),
51
+ AwsS3WebsiteSync::Color.yellow("no_change: #{summary[:no_change]}")
52
+ ].join(' ')
53
+ puts ""
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,70 @@
1
+ module AwsS3WebsiteSync
2
+ class Runner
3
+ def self.run(
4
+ aws_access_key_id:,
5
+ aws_secret_access_key:,
6
+ aws_default_region:,
7
+ s3_bucket:,
8
+ auto_approve:,
9
+ distribution_id:,
10
+ build_dir:,
11
+ output_changset_path:,
12
+ ignore_files:,
13
+ silent:
14
+ )
15
+ $logger.info "Runner.run"
16
+ AwsS3WebsiteSync::Plan.run(
17
+ output_changeset_path: output_changset_path,
18
+ build_dir: build_dir,
19
+ aws_access_key_id: aws_access_key_id,
20
+ aws_secret_access_key: aws_secret_access_key,
21
+ s3_bucket: s3_bucket,
22
+ aws_default_region: aws_default_region,
23
+ ignore_files: ignore_files
24
+ )
25
+ AwsS3WebsiteSync::Preview.run(
26
+ changeset_path: output_changset_path,
27
+ silent: silent
28
+ )
29
+
30
+ json = File.read output_changset_path
31
+ data = JSON.parse json
32
+ items = data.select{|t| %w{create update delete}.include?(t["action"]) }
33
+
34
+ if items.count.zero?
35
+ puts "no changes to apply, quitting....."
36
+ else
37
+ puts "Execute the plan? Type: yes or no"
38
+ if auto_approve == "true"
39
+ AwsS3WebsiteSync::Apply.run(
40
+ changeset_path: output_changset_path,
41
+ build_dir: build_dir,
42
+ aws_access_key_id: aws_access_key_id,
43
+ aws_secret_access_key: aws_secret_access_key,
44
+ s3_bucket: s3_bucket,
45
+ aws_default_region: aws_default_region,
46
+ distribution_id: distribution_id,
47
+ caller_reference: File.basename(output_changset_path,'.json')
48
+ )
49
+ else
50
+ print "> "
51
+ case (STDIN.gets.chomp)
52
+ when 'yes'
53
+ AwsS3WebsiteSync::Apply.run(
54
+ changeset_path: output_changset_path,
55
+ build_dir: build_dir,
56
+ aws_access_key_id: aws_access_key_id,
57
+ aws_secret_access_key: aws_secret_access_key,
58
+ s3_bucket: s3_bucket,
59
+ aws_default_region: aws_default_region,
60
+ distribution_id: distribution_id,
61
+ caller_reference: File.basename(output_changset_path,'.json')
62
+ )
63
+ when 'no'
64
+ puts "quitting...."
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,3 @@
1
+ module AwsS3WebsiteSync
2
+ VERSION = "1.0.1"
3
+ end
@@ -0,0 +1,14 @@
1
+ require 'aws-sdk-cloudfront'
2
+ require 'aws-sdk-s3'
3
+ require 'digest/md5'
4
+ require 'logger'
5
+ require 'fileutils'
6
+
7
+ require_relative 'aws_s3_website_sync/color'
8
+ require_relative 'aws_s3_website_sync/runner'
9
+ require_relative 'aws_s3_website_sync/list'
10
+ require_relative 'aws_s3_website_sync/preview'
11
+ require_relative 'aws_s3_website_sync/plan'
12
+ require_relative 'aws_s3_website_sync/apply'
13
+
14
+ $logger = Logger.new(STDOUT)
metadata ADDED
@@ -0,0 +1,80 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: aws_s3_website_sync
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.1
5
+ platform: ruby
6
+ authors:
7
+ - TeacherSeat
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-10-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: aws-sdk-cloudfront
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: aws-sdk-s3
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: AWS S3 Website Sync
42
+ email:
43
+ - andrew@teachersaet.co
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - README.md
49
+ - lib/aws_s3_website_sync.rb
50
+ - lib/aws_s3_website_sync/apply.rb
51
+ - lib/aws_s3_website_sync/color.rb
52
+ - lib/aws_s3_website_sync/list.rb
53
+ - lib/aws_s3_website_sync/plan.rb
54
+ - lib/aws_s3_website_sync/preview.rb
55
+ - lib/aws_s3_website_sync/runner.rb
56
+ - lib/aws_s3_website_sync/version.rb
57
+ homepage: https://www.teacherseat.com
58
+ licenses:
59
+ - MIT
60
+ metadata: {}
61
+ post_install_message:
62
+ rdoc_options: []
63
+ require_paths:
64
+ - lib
65
+ required_ruby_version: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ required_rubygems_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ requirements: []
76
+ rubygems_version: 3.3.7
77
+ signing_key:
78
+ specification_version: 4
79
+ summary: AWS S3 Website Sync
80
+ test_files: []