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 +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: []
|