bookbindery 4.1.0 → 4.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/bookbinder/cf_command_runner.rb +33 -30
- data/lib/bookbinder/cli.rb +15 -7
- data/lib/bookbinder/code_example_reader.rb +5 -7
- data/lib/bookbinder/commands/bind/directory_preparer.rb +1 -1
- data/lib/bookbinder/commands/build_and_push_tarball.rb +18 -6
- data/lib/bookbinder/commands/collection.rb +16 -21
- data/lib/bookbinder/commands/push_from_local.rb +27 -19
- data/lib/bookbinder/commands/push_to_prod.rb +30 -34
- data/lib/bookbinder/commands/tag.rb +5 -6
- data/lib/bookbinder/commands/update_local_doc_repos.rb +0 -1
- data/lib/bookbinder/config/cf_credentials.rb +1 -14
- data/lib/bookbinder/deploy/app_fetcher.rb +36 -0
- data/lib/bookbinder/deploy/archive.rb +102 -0
- data/lib/bookbinder/deploy/artifact.rb +26 -0
- data/lib/bookbinder/deploy/blue_green_app.rb +29 -0
- data/lib/bookbinder/deploy/cf_routes.rb +32 -0
- data/lib/bookbinder/deploy/deployment.rb +55 -0
- data/lib/bookbinder/deploy/distributor.rb +76 -0
- data/lib/bookbinder/deploy/failure.rb +15 -0
- data/lib/bookbinder/deploy/pusher.rb +34 -0
- data/lib/bookbinder/deploy/success.rb +16 -0
- data/lib/bookbinder/ingest/cloner_factory.rb +4 -4
- data/lib/bookbinder/ingest/local_filesystem_cloner.rb +5 -6
- data/lib/bookbinder/local_file_system_accessor.rb +9 -0
- data/lib/bookbinder/{post_production → postprocessing}/sitemap_writer.rb +7 -2
- data/lib/bookbinder/preprocessing/{copy_to_site_gen_dir.rb → link_to_site_gen_dir.rb} +2 -2
- data/lib/bookbinder/server_director.rb +3 -10
- data/lib/bookbinder/sheller.rb +5 -1
- data/master_middleman/bookbinder_helpers.rb +4 -12
- data/template_app/config.ru +3 -7
- data/template_app/rack_app.rb +23 -0
- metadata +60 -58
- data/lib/bookbinder/app_fetcher.rb +0 -36
- data/lib/bookbinder/archive.rb +0 -102
- data/lib/bookbinder/artifact_namer.rb +0 -22
- data/lib/bookbinder/commands/bookbinder_command.rb +0 -18
- data/lib/bookbinder/distributor.rb +0 -80
- data/lib/bookbinder/pusher.rb +0 -34
- data/lib/bookbinder/time_fetcher.rb +0 -7
- data/lib/bookbinder/values/blue_green_app.rb +0 -27
- data/lib/bookbinder/values/cf_routes.rb +0 -30
@@ -1,20 +1,19 @@
|
|
1
|
-
require_relative '../
|
2
|
-
require_relative '
|
1
|
+
require_relative '../deploy/archive'
|
2
|
+
require_relative '../deploy/deployment'
|
3
|
+
require_relative '../deploy/distributor'
|
3
4
|
require_relative 'naming'
|
4
5
|
|
5
6
|
module Bookbinder
|
6
|
-
class PushToProdValidator
|
7
|
-
MissingRequiredKeyError = Class.new(RuntimeError)
|
8
|
-
end
|
9
|
-
|
10
7
|
module Commands
|
11
8
|
class PushToProd
|
12
9
|
include Commands::Naming
|
13
|
-
|
10
|
+
MissingRequiredKeyError = Class.new(RuntimeError)
|
14
11
|
|
15
|
-
def initialize(logger, configuration_fetcher)
|
12
|
+
def initialize(streams, logger, configuration_fetcher, app_dir)
|
13
|
+
@streams = streams
|
16
14
|
@logger = logger
|
17
15
|
@configuration_fetcher = configuration_fetcher
|
16
|
+
@app_dir = app_dir
|
18
17
|
end
|
19
18
|
|
20
19
|
def usage
|
@@ -22,49 +21,46 @@ module Bookbinder
|
|
22
21
|
"Push latest or <build_#> from your S3 bucket to the production host specified in credentials.yml"]
|
23
22
|
end
|
24
23
|
|
25
|
-
def run(
|
24
|
+
def run((build_number))
|
25
|
+
streams[:warn].puts "Warning: You are pushing to production."
|
26
26
|
validate
|
27
|
-
|
27
|
+
deployment = Deploy::Deployment.new(
|
28
|
+
app_dir: app_dir,
|
29
|
+
build_number: build_number,
|
30
|
+
aws_credentials: credentials[:aws],
|
31
|
+
cf_credentials: credentials[:cloud_foundry],
|
32
|
+
book_repo: config.book_repo,
|
33
|
+
)
|
34
|
+
archive = Deploy::Archive.new(
|
35
|
+
logger: @logger,
|
36
|
+
key: deployment.aws_access_key,
|
37
|
+
secret: deployment.aws_secret_key
|
38
|
+
)
|
39
|
+
archive.download(download_dir: deployment.app_dir,
|
40
|
+
bucket: deployment.green_builds_bucket,
|
41
|
+
build_number: deployment.build_number,
|
42
|
+
namespace: deployment.namespace)
|
43
|
+
Deploy::Distributor.build(streams, archive, deployment).distribute
|
28
44
|
0
|
29
45
|
end
|
30
46
|
|
31
47
|
private
|
32
48
|
|
33
|
-
attr_reader :configuration_fetcher
|
34
|
-
|
35
|
-
def options(arguments)
|
36
|
-
{
|
37
|
-
app_dir: Dir.mktmpdir,
|
38
|
-
build_number: arguments[0],
|
39
|
-
|
40
|
-
aws_credentials: credentials[:aws],
|
41
|
-
cf_credentials: credentials[:cloud_foundry],
|
42
|
-
|
43
|
-
book_repo: config.book_repo,
|
44
|
-
}
|
45
|
-
end
|
49
|
+
attr_reader :app_dir, :configuration_fetcher, :streams
|
46
50
|
|
47
51
|
def credentials
|
48
52
|
configuration_fetcher.fetch_credentials('production')
|
49
53
|
end
|
50
54
|
|
51
55
|
def config
|
52
|
-
|
56
|
+
configuration_fetcher.fetch_config
|
53
57
|
end
|
54
58
|
|
55
59
|
def validate
|
56
|
-
|
57
|
-
|
58
|
-
unless config.has_option?(required_key)
|
59
|
-
missing_keys.push(required_key)
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
if missing_keys.length > 0
|
64
|
-
raise PushToProdValidator::MissingRequiredKeyError.new "Your config.yml is missing required key(s). The require keys for this commands are " + missing_keys.join(", ")
|
60
|
+
unless config.has_option?('cred_repo')
|
61
|
+
raise MissingRequiredKeyError.new "Your config.yml is missing required key(s). The require keys for this commands are cred_repo"
|
65
62
|
end
|
66
63
|
end
|
67
|
-
|
68
64
|
end
|
69
65
|
end
|
70
66
|
end
|
@@ -1,4 +1,3 @@
|
|
1
|
-
require_relative '../deprecated_logger'
|
2
1
|
require_relative '../errors/cli_error'
|
3
2
|
require_relative 'naming'
|
4
3
|
|
@@ -7,8 +6,8 @@ module Bookbinder
|
|
7
6
|
class Tag
|
8
7
|
include Commands::Naming
|
9
8
|
|
10
|
-
def initialize(
|
11
|
-
@
|
9
|
+
def initialize(streams, configuration_fetcher, version_control_system)
|
10
|
+
@streams = streams
|
12
11
|
@configuration_fetcher = configuration_fetcher
|
13
12
|
@version_control_system = version_control_system
|
14
13
|
end
|
@@ -24,14 +23,14 @@ module Bookbinder
|
|
24
23
|
version_control_system.remote_tag(url, tag, 'HEAD')
|
25
24
|
end
|
26
25
|
|
27
|
-
|
28
|
-
|
26
|
+
streams[:success].puts 'Success!'
|
27
|
+
streams[:out].puts "#{config.book_repo} and its sections were tagged with #{tag}"
|
29
28
|
0
|
30
29
|
end
|
31
30
|
|
32
31
|
private
|
33
32
|
|
34
|
-
attr_reader :configuration_fetcher, :version_control_system
|
33
|
+
attr_reader :streams, :configuration_fetcher, :version_control_system
|
35
34
|
|
36
35
|
def urls(config)
|
37
36
|
[config.book_repo_url] + config.sections.map(&:repo_url).uniq
|
@@ -22,25 +22,12 @@ module Bookbinder
|
|
22
22
|
]
|
23
23
|
end
|
24
24
|
|
25
|
-
def download_archive_before_push?
|
26
|
-
production?
|
27
|
-
end
|
28
|
-
|
29
|
-
def push_warning
|
30
|
-
if production?
|
31
|
-
'Warning: You are pushing to CF Docs production. Be careful.'
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
25
|
def routes
|
36
26
|
fetch(host_key) if correctly_formatted_domain_and_routes?(host_key)
|
37
27
|
end
|
38
28
|
|
39
29
|
def flat_routes
|
40
|
-
routes.
|
41
|
-
domain, apps = domain_apps
|
42
|
-
all_routes + apps.map { |app| [domain, app] }
|
43
|
-
end
|
30
|
+
routes.flat_map { |(domain, apps)| apps.map { |app| [domain, app] } }
|
44
31
|
end
|
45
32
|
|
46
33
|
def space
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require_relative 'blue_green_app'
|
2
|
+
require_relative 'cf_routes'
|
3
|
+
|
4
|
+
module Bookbinder
|
5
|
+
module Deploy
|
6
|
+
class AppFetcher
|
7
|
+
def initialize(routes_to_search, cf_command_runner)
|
8
|
+
@routes_to_search = routes_to_search
|
9
|
+
@cf_command_runner = cf_command_runner
|
10
|
+
end
|
11
|
+
|
12
|
+
def fetch_current_app
|
13
|
+
raw_cf_routes = cf_command_runner.cf_routes_output
|
14
|
+
cf_routes = CfRoutes.new(raw_cf_routes)
|
15
|
+
|
16
|
+
existing_hosts = routes_to_search.select do |domain, host|
|
17
|
+
cf_routes.apps_by_host_and_domain.has_key?([host, domain])
|
18
|
+
end
|
19
|
+
|
20
|
+
return nil if existing_hosts.empty?
|
21
|
+
app_groups = existing_hosts.map { |domain, host| apps_for_host(cf_routes, domain, host) }
|
22
|
+
apps_for_existing_routes = app_groups.first
|
23
|
+
apps_for_existing_routes.first
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
attr_reader :routes_to_search, :cf_command_runner
|
29
|
+
|
30
|
+
def apps_for_host(routes_from_cf, domain, host)
|
31
|
+
routes_from_cf.apps_by_host_and_domain.fetch([host, domain], []).
|
32
|
+
map &BlueGreenApp.method(:new)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'fog/aws'
|
2
|
+
require 'tmpdir'
|
3
|
+
require_relative '../deprecated_logger'
|
4
|
+
require_relative 'artifact'
|
5
|
+
require_relative 'success'
|
6
|
+
|
7
|
+
module Bookbinder
|
8
|
+
module Deploy
|
9
|
+
class Archive
|
10
|
+
class FileDoesNotExist < StandardError; end
|
11
|
+
class NoNamespaceGiven < StandardError; end
|
12
|
+
|
13
|
+
def initialize(logger: nil, key: '', secret: '')
|
14
|
+
@logger = logger
|
15
|
+
@aws_key = key
|
16
|
+
@aws_secret_key = secret
|
17
|
+
end
|
18
|
+
|
19
|
+
def create_and_upload_tarball(
|
20
|
+
app_dir: 'final_app',
|
21
|
+
bucket: '',
|
22
|
+
build_number: nil,
|
23
|
+
namespace: nil
|
24
|
+
)
|
25
|
+
tarball_filename, tarball_path = create_tarball(app_dir, build_number, namespace)
|
26
|
+
upload_file(bucket, tarball_filename, tarball_path)
|
27
|
+
Success.new("Green build ##{build_number} has been uploaded to S3 for #{namespace}")
|
28
|
+
end
|
29
|
+
|
30
|
+
def upload_file(bucket, name, source_path)
|
31
|
+
find_or_create_directory(bucket).
|
32
|
+
files.create(key: name,
|
33
|
+
body: File.read(source_path),
|
34
|
+
public: true)
|
35
|
+
end
|
36
|
+
|
37
|
+
def download(download_dir: nil, bucket: nil, build_number: nil, namespace: nil)
|
38
|
+
raise NoNamespaceGiven, 'One must specify a namespace to find files in this bucket' unless namespace
|
39
|
+
|
40
|
+
directory = connection.directories.get bucket
|
41
|
+
build_number ||= latest_build_number_for_namespace(directory, namespace)
|
42
|
+
filename = Artifact.new(namespace, build_number, 'tgz').filename
|
43
|
+
|
44
|
+
s3_file = directory.files.get(filename)
|
45
|
+
raise FileDoesNotExist, "Unable to find tarball on AWS for book '#{namespace}', build number: #{build_number}" unless s3_file
|
46
|
+
|
47
|
+
downloaded_file = File.join(Dir.mktmpdir, 'downloaded.tgz')
|
48
|
+
File.open(downloaded_file, 'wb') { |f| f.write(s3_file.body) }
|
49
|
+
Dir.chdir(download_dir) { `tar xzf #{downloaded_file}` }
|
50
|
+
|
51
|
+
@logger.log "Green build ##{build_number.to_s.green} has been downloaded from S3 and untarred into #{download_dir.to_s.cyan}"
|
52
|
+
end
|
53
|
+
|
54
|
+
def tarball_name_regex(namespace)
|
55
|
+
/^#{namespace}-(\d+_?\d*)\.tgz/
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def find_or_create_directory(name)
|
61
|
+
connection.directories.create(key: name)
|
62
|
+
rescue Excon::Errors::Conflict
|
63
|
+
connection.directories.get(name)
|
64
|
+
end
|
65
|
+
|
66
|
+
def create_tarball(app_dir, build_number, namespace)
|
67
|
+
tarball_filename = Artifact.new(namespace, build_number, 'tgz').filename
|
68
|
+
tarball_path = File.join(Dir.mktmpdir, tarball_filename)
|
69
|
+
|
70
|
+
Dir.chdir(app_dir) { `tar czf #{tarball_path} *` }
|
71
|
+
return tarball_filename, tarball_path
|
72
|
+
end
|
73
|
+
|
74
|
+
def latest_build_number_for_namespace(directory, namespace)
|
75
|
+
all_files = all_files_workaround_for_fog_map_limitation(directory.files)
|
76
|
+
|
77
|
+
all_files_with_namespace = all_files.map do |file|
|
78
|
+
filename = file.key
|
79
|
+
file if filename[tarball_name_regex(namespace)]
|
80
|
+
end.compact
|
81
|
+
|
82
|
+
return nil if all_files_with_namespace.empty?
|
83
|
+
most_recent_file = all_files_with_namespace.sort_by { |file| file.last_modified }.last
|
84
|
+
|
85
|
+
most_recent_filename = most_recent_file.key
|
86
|
+
most_recent_filename[tarball_name_regex(namespace), 1]
|
87
|
+
end
|
88
|
+
|
89
|
+
def connection
|
90
|
+
@connection ||= Fog::Storage.new :provider => 'AWS',
|
91
|
+
:aws_access_key_id => @aws_key,
|
92
|
+
:aws_secret_access_key => @aws_secret_key
|
93
|
+
end
|
94
|
+
|
95
|
+
def all_files_workaround_for_fog_map_limitation(files)
|
96
|
+
all_files = []
|
97
|
+
files.each { |f| all_files << f }
|
98
|
+
all_files
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Bookbinder
|
2
|
+
module Deploy
|
3
|
+
class Artifact
|
4
|
+
attr_reader :namespace
|
5
|
+
|
6
|
+
def initialize(namespace, build_number, extension, path = '.')
|
7
|
+
@namespace = namespace
|
8
|
+
@build_number = build_number
|
9
|
+
@path = path
|
10
|
+
@extension = extension
|
11
|
+
end
|
12
|
+
|
13
|
+
def full_path
|
14
|
+
File.join(path, filename)
|
15
|
+
end
|
16
|
+
|
17
|
+
def filename
|
18
|
+
"#{namespace}-#{build_number}.#{extension}"
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
attr_reader :build_number, :path, :extension
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Bookbinder
|
2
|
+
module Deploy
|
3
|
+
class BlueGreenApp
|
4
|
+
def initialize(name)
|
5
|
+
@name = name.strip
|
6
|
+
end
|
7
|
+
|
8
|
+
def ==(other)
|
9
|
+
to_s == other.to_s
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_s
|
13
|
+
name
|
14
|
+
end
|
15
|
+
|
16
|
+
def with_flipped_name
|
17
|
+
if name.match(/green$/)
|
18
|
+
BlueGreenApp.new(name.sub(/green$/, 'blue'))
|
19
|
+
else
|
20
|
+
BlueGreenApp.new(name.sub(/blue$/, 'green'))
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
attr_reader :name
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Bookbinder
|
2
|
+
module Deploy
|
3
|
+
class CfRoutes
|
4
|
+
def initialize(raw_routes)
|
5
|
+
@raw_routes = raw_routes
|
6
|
+
end
|
7
|
+
|
8
|
+
def apps_by_host_and_domain
|
9
|
+
@apps_by_host_and_domain ||= data(raw_routes).reduce({}) {|acc, row|
|
10
|
+
parsed_row = Hash[headers(raw_routes).zip(row)]
|
11
|
+
acc.merge(parsed_row.values_at('host', 'domain') => parse_apps(parsed_row['apps']))
|
12
|
+
}
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
attr_reader :raw_routes
|
18
|
+
|
19
|
+
def parse_apps(apps)
|
20
|
+
apps.split(',').map(&:strip)
|
21
|
+
end
|
22
|
+
|
23
|
+
def headers(raw)
|
24
|
+
raw.lines[2].split(/\s+/)
|
25
|
+
end
|
26
|
+
|
27
|
+
def data(raw)
|
28
|
+
raw.lines[3..-1].map {|line| line.split(/\s+/, headers(raw).size)}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Bookbinder
|
2
|
+
module Deploy
|
3
|
+
class Deployment
|
4
|
+
attr_reader :app_dir, :build_number, :cf_credentials
|
5
|
+
|
6
|
+
def initialize(app_dir: nil,
|
7
|
+
aws_credentials: nil,
|
8
|
+
book_repo: nil,
|
9
|
+
build_number: nil,
|
10
|
+
cf_credentials: nil)
|
11
|
+
@app_dir = app_dir
|
12
|
+
@aws_credentials = aws_credentials
|
13
|
+
@book_repo = book_repo
|
14
|
+
@build_number = build_number
|
15
|
+
@cf_credentials = cf_credentials
|
16
|
+
end
|
17
|
+
|
18
|
+
def artifact_filename
|
19
|
+
artifact.filename
|
20
|
+
end
|
21
|
+
|
22
|
+
def artifact_full_path
|
23
|
+
artifact.full_path
|
24
|
+
end
|
25
|
+
|
26
|
+
def aws_access_key
|
27
|
+
aws_credentials.access_key
|
28
|
+
end
|
29
|
+
|
30
|
+
def aws_secret_key
|
31
|
+
aws_credentials.secret_key
|
32
|
+
end
|
33
|
+
|
34
|
+
def flat_routes
|
35
|
+
cf_credentials.flat_routes
|
36
|
+
end
|
37
|
+
|
38
|
+
def green_builds_bucket
|
39
|
+
aws_credentials.green_builds_bucket
|
40
|
+
end
|
41
|
+
|
42
|
+
def namespace
|
43
|
+
Ingest::DestinationDirectory.new(book_repo)
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
attr_reader :aws_credentials, :book_repo
|
49
|
+
|
50
|
+
def artifact
|
51
|
+
Artifact.new(namespace, build_number, 'log', '/tmp')
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|