aws_s3_website_sync 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: []