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 +7 -0
- data/README.md +0 -0
- data/lib/aws_s3_website_sync/apply.rb +117 -0
- data/lib/aws_s3_website_sync/color.rb +23 -0
- data/lib/aws_s3_website_sync/list.rb +37 -0
- data/lib/aws_s3_website_sync/plan.rb +69 -0
- data/lib/aws_s3_website_sync/preview.rb +56 -0
- data/lib/aws_s3_website_sync/runner.rb +70 -0
- data/lib/aws_s3_website_sync/version.rb +3 -0
- data/lib/aws_s3_website_sync.rb +14 -0
- metadata +80 -0
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,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: []
|