bookbindery 4.1.0 → 4.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/lib/bookbinder/cf_command_runner.rb +33 -30
  3. data/lib/bookbinder/cli.rb +15 -7
  4. data/lib/bookbinder/code_example_reader.rb +5 -7
  5. data/lib/bookbinder/commands/bind/directory_preparer.rb +1 -1
  6. data/lib/bookbinder/commands/build_and_push_tarball.rb +18 -6
  7. data/lib/bookbinder/commands/collection.rb +16 -21
  8. data/lib/bookbinder/commands/push_from_local.rb +27 -19
  9. data/lib/bookbinder/commands/push_to_prod.rb +30 -34
  10. data/lib/bookbinder/commands/tag.rb +5 -6
  11. data/lib/bookbinder/commands/update_local_doc_repos.rb +0 -1
  12. data/lib/bookbinder/config/cf_credentials.rb +1 -14
  13. data/lib/bookbinder/deploy/app_fetcher.rb +36 -0
  14. data/lib/bookbinder/deploy/archive.rb +102 -0
  15. data/lib/bookbinder/deploy/artifact.rb +26 -0
  16. data/lib/bookbinder/deploy/blue_green_app.rb +29 -0
  17. data/lib/bookbinder/deploy/cf_routes.rb +32 -0
  18. data/lib/bookbinder/deploy/deployment.rb +55 -0
  19. data/lib/bookbinder/deploy/distributor.rb +76 -0
  20. data/lib/bookbinder/deploy/failure.rb +15 -0
  21. data/lib/bookbinder/deploy/pusher.rb +34 -0
  22. data/lib/bookbinder/deploy/success.rb +16 -0
  23. data/lib/bookbinder/ingest/cloner_factory.rb +4 -4
  24. data/lib/bookbinder/ingest/local_filesystem_cloner.rb +5 -6
  25. data/lib/bookbinder/local_file_system_accessor.rb +9 -0
  26. data/lib/bookbinder/{post_production → postprocessing}/sitemap_writer.rb +7 -2
  27. data/lib/bookbinder/preprocessing/{copy_to_site_gen_dir.rb → link_to_site_gen_dir.rb} +2 -2
  28. data/lib/bookbinder/server_director.rb +3 -10
  29. data/lib/bookbinder/sheller.rb +5 -1
  30. data/master_middleman/bookbinder_helpers.rb +4 -12
  31. data/template_app/config.ru +3 -7
  32. data/template_app/rack_app.rb +23 -0
  33. metadata +60 -58
  34. data/lib/bookbinder/app_fetcher.rb +0 -36
  35. data/lib/bookbinder/archive.rb +0 -102
  36. data/lib/bookbinder/artifact_namer.rb +0 -22
  37. data/lib/bookbinder/commands/bookbinder_command.rb +0 -18
  38. data/lib/bookbinder/distributor.rb +0 -80
  39. data/lib/bookbinder/pusher.rb +0 -34
  40. data/lib/bookbinder/time_fetcher.rb +0 -7
  41. data/lib/bookbinder/values/blue_green_app.rb +0 -27
  42. data/lib/bookbinder/values/cf_routes.rb +0 -30
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9f6a8e4eb39aa0fd160b3172ba64ea6b9235cf00
4
- data.tar.gz: 336570ec41b4e7ba131acab5565a9c210e122227
3
+ metadata.gz: f7877178e76465b0d9ad6b4f1564da1bba88d6b2
4
+ data.tar.gz: bf482ea5acec272fcb583378bdb526bc3f5a17e1
5
5
  SHA512:
6
- metadata.gz: e6b3d8dbd2a9b312574024ff56ed8669de906b7290b1de9a5de3e2bb1ea372fc2b6d11bdf7f7f8239153dac485135e4c8993ed814ca3ef6ebe5d62e1ae815848
7
- data.tar.gz: a6bcdfcc72d5a57a090a88bc60cddcd74d2f0bb5715b34db798310104181843c25feb50089aaf5d2e7b6f06169226c02dfb2e36a40f80ae0aced02a663c622bf
6
+ metadata.gz: 4971a06ce3b96ed50633d2ac3d2da19d0d31da0a7bdea5c4d5474e4e4fc58a4dbf687abf1711bb07988651a55fdb60db4e8e9d511cac3bcfcb47f3cb60f129bc
7
+ data.tar.gz: 1c90657d87387b98c1158ab14a7ebb5554ea72db268773adea9ac480b6c2eedcb85c6464cb58ae004d27e812b30303cd1d65366b7300b5320530197655c1c840
@@ -1,10 +1,9 @@
1
- require 'open3'
2
- require_relative 'values/blue_green_app'
1
+ require_relative 'deploy/blue_green_app'
3
2
 
4
3
  module Bookbinder
5
4
  class CfCommandRunner
6
- def initialize(logger, sheller, cf_credentials, trace_file)
7
- @logger = logger
5
+ def initialize(streams, sheller, cf_credentials, trace_file)
6
+ @streams = streams
8
7
  @sheller = sheller
9
8
  @creds = cf_credentials
10
9
  @trace_file = trace_file
@@ -18,37 +17,39 @@ module Bookbinder
18
17
  space = creds.space
19
18
  creds_string = (username && password) ? "-u '#{username}' -p '#{password}'" : ''
20
19
 
21
- success = Kernel.system("#{cf_binary_path} login #{creds_string} -a '#{api_endpoint}' -o '#{organization}' -s '#{space}'")
22
- raise "Could not log in to #{creds.api_endpoint}" unless success
20
+ result = sheller.run_command(
21
+ "#{cf_binary_path} login #{creds_string} -a '#{api_endpoint}' -o '#{organization}' -s '#{space}'",
22
+ streams
23
+ )
24
+ raise "Could not log in to #{creds.api_endpoint}" unless result.success?
23
25
  end
24
26
 
25
27
  def cf_routes_output
26
- output, status = Open3.capture2("CF_COLOR=false #{cf_binary_path} routes")
27
- raise 'failure executing cf routes' unless status.success?
28
- output
28
+ sheller.get_stdout("CF_COLOR=false #{cf_binary_path} routes").tap do |output|
29
+ raise 'failure executing cf routes' if output == ''
30
+ end
29
31
  end
30
32
 
31
33
  def new_app
32
- BlueGreenApp.new([creds.app_name, 'blue'].join('-'))
34
+ Deploy::BlueGreenApp.new([creds.app_name, 'blue'].join('-'))
33
35
  end
34
36
 
35
37
  def start(deploy_target_app)
36
38
  # Theoretically we shouldn't need this (and corresponding "stop" below), but we've seen CF pull files from both
37
39
  # green and blue when a DNS redirect points to HOST.cfapps.io
38
40
  # Also, shutting down the unused app saves $$
39
- sheller.run_command("#{cf_binary_path} start #{deploy_target_app}",
40
- out: $stdout,
41
- err: $stderr)
41
+ sheller.run_command("#{cf_binary_path} start #{deploy_target_app}", streams)
42
42
  end
43
43
 
44
44
  def push(deploy_target_app)
45
45
  # Currently --no-routes is used to blow away all existing routes from a newly deployed app.
46
46
  # The routes will then be recreated from the creds repo.
47
- status = sheller.run_command(environment_variables,
48
- "#{cf_binary_path} push #{deploy_target_app} -s cflinuxfs2 --no-route -m 256M -i 3",
49
- out: $stdout,
50
- err: $stderr)
51
- raise "Could not deploy app to #{deploy_target_app}" unless status.success?
47
+ result = sheller.run_command(
48
+ environment_variables,
49
+ "#{cf_binary_path} push #{deploy_target_app} -s cflinuxfs2 --no-route -m 256M -i 3",
50
+ streams
51
+ )
52
+ raise "Could not deploy app to #{deploy_target_app}" unless result.success?
52
53
  end
53
54
 
54
55
  def unmap_routes(app)
@@ -73,9 +74,9 @@ module Bookbinder
73
74
 
74
75
  def takedown_old_target_app(app)
75
76
  # Routers flush every 10 seconds (but not guaranteed), so wait a bit longer than that.
76
- @logger.log "waiting 15 seconds for routes to remap...\n\n"
77
+ streams[:out].puts "waiting 15 seconds for routes to remap...\n\n"
77
78
  (1..15).to_a.reverse.each do |seconds|
78
- @logger.log_print "\r\r#{seconds}... "
79
+ streams[:out] << "\r\r#{seconds}... "
79
80
  Kernel.sleep 1
80
81
  end
81
82
  stop(app)
@@ -84,33 +85,35 @@ module Bookbinder
84
85
 
85
86
  private
86
87
 
87
- attr_reader :creds, :sheller
88
+ attr_reader :creds, :sheller, :streams
88
89
 
89
90
  def stop(app)
90
- success = Kernel.system("#{cf_binary_path} stop #{app}")
91
- raise "Failed to stop application #{app}" unless success
91
+ result = sheller.run_command("#{cf_binary_path} stop #{app}", streams)
92
+ raise "Failed to stop application #{app}" unless result.success?
92
93
  end
93
94
 
94
95
  def map_route(deploy_target_app, domain, host)
95
96
  map_route_command = "#{cf_binary_path} map-route #{deploy_target_app} #{domain}"
96
97
  map_route_command += " -n #{host}" unless host.empty?
97
98
 
98
- success = Kernel.system(map_route_command)
99
- raise "Deployed app to #{deploy_target_app} but failed to map hostname #{host}.#{domain} to it." unless success
99
+ result = sheller.run_command(map_route_command, streams)
100
+ raise "Deployed app to #{deploy_target_app} but failed to map hostname #{host}.#{domain} to it." unless result.success?
100
101
  end
101
102
 
102
103
  def unmap_route(deploy_target_app, domain, host)
103
104
  unmap_route_command = "#{cf_binary_path} unmap-route #{deploy_target_app} #{domain}"
104
105
  unmap_route_command += " -n #{host}" unless host.empty?
105
106
 
106
- success = Kernel.system(unmap_route_command)
107
- raise "Failed to unmap route #{host} on #{deploy_target_app}." unless success
107
+ result = sheller.run_command(unmap_route_command, streams)
108
+ raise "Failed to unmap route #{host} on #{deploy_target_app}." unless result.success?
108
109
  end
109
110
 
110
111
  def cf_binary_path
111
- @cf_binary_path ||= `which cf`.chomp!
112
- raise "CF CLI could not be found in your PATH. Please make sure cf cli is in your PATH." if @cf_binary_path.nil?
113
- @cf_binary_path
112
+ @cf_binary_path ||= sheller.get_stdout("which cf").tap do |path|
113
+ if path == ''
114
+ raise "CF CLI could not be found in your PATH. Please make sure cf cli is in your PATH."
115
+ end
116
+ end
114
117
  end
115
118
 
116
119
  def environment_variables
@@ -1,8 +1,8 @@
1
- require_relative 'colorizer'
2
1
  require_relative 'command_runner'
3
2
  require_relative 'command_validator'
4
3
  require_relative 'commands/collection'
5
4
  require_relative 'config/cf_credentials'
5
+ require_relative 'streams/colorized_stream'
6
6
  require_relative 'terminal'
7
7
 
8
8
  module Bookbinder
@@ -17,7 +17,7 @@ module Bookbinder
17
17
  logger = DeprecatedLogger.new
18
18
  commands = Commands::Collection.new(
19
19
  logger,
20
- {out: $stdout, err: $stderr},
20
+ colorized_streams,
21
21
  version_control_system
22
22
  )
23
23
 
@@ -39,19 +39,19 @@ module Bookbinder
39
39
  command_runner.run command_name, command_arguments
40
40
 
41
41
  rescue Config::RemoteBindConfiguration::VersionUnsupportedError => e
42
- logger.error "config.yml at version '#{e.message}' has an unsupported API."
42
+ colorized_streams[:err].puts "config.yml at version '#{e.message}' has an unsupported API."
43
43
  1
44
44
  rescue Config::CfCredentials::CredentialKeyError => e
45
- logger.error "#{e.message}, in credentials.yml"
45
+ colorized_streams[:err].puts "#{e.message}, in credentials.yml"
46
46
  1
47
47
  rescue KeyError => e
48
- logger.error "#{e.message} from your configuration."
48
+ colorized_streams[:err].puts "#{e.message} from your configuration."
49
49
  1
50
50
  rescue CliError::UnknownCommand => e
51
- logger.log e.message
51
+ colorized_streams[:out].puts e.message
52
52
  1
53
53
  rescue RuntimeError => e
54
- logger.error e.message
54
+ colorized_streams[:err].puts e.message
55
55
  1
56
56
  end
57
57
  end
@@ -60,5 +60,13 @@ module Bookbinder
60
60
 
61
61
  attr_reader :version_control_system
62
62
 
63
+ def colorized_streams
64
+ {
65
+ err: Streams::ColorizedStream.new(Colorizer::Colors.red, $stderr),
66
+ out: $stdout,
67
+ success: Streams::ColorizedStream.new(Colorizer::Colors.green, $stdout),
68
+ warn: Streams::ColorizedStream.new(Colorizer::Colors.yellow, $stdout),
69
+ }
70
+ end
63
71
  end
64
72
  end
@@ -1,15 +1,13 @@
1
- require_relative '../../lib/bookbinder/deprecated_logger'
2
-
3
1
  module Bookbinder
4
2
  class CodeExampleReader
5
3
  class InvalidSnippet < StandardError
6
4
  def initialize(repo, marker)
7
- super "Error with marker #{marker.cyan} #{'in'.red} #{repo.cyan}#{'.'.red}"
5
+ super "Error with marker #{marker} in #{repo}."
8
6
  end
9
7
  end
10
8
 
11
- def initialize(logger)
12
- @logger = logger
9
+ def initialize(out: out)
10
+ @out = out
13
11
  end
14
12
 
15
13
  def get_snippet_and_language_at(marker, working_copy)
@@ -19,7 +17,7 @@ module Bookbinder
19
17
  elsif snippet.available?
20
18
  [snippet.content, snippet.language]
21
19
  else
22
- logger.log ' skipping (not found) '.magenta + working_copy.full_name
20
+ out << " skipping (not found) #{working_copy.full_name}"
23
21
  ''
24
22
  end
25
23
  end
@@ -73,6 +71,6 @@ module Bookbinder
73
71
 
74
72
  private
75
73
 
76
- attr_reader :logger
74
+ attr_reader :out
77
75
  end
78
76
  end
@@ -15,7 +15,7 @@ module Bookbinder
15
15
 
16
16
  def prepare_directories(config, gem_root, output_locations, layout_repo_dir)
17
17
  file_system_accessor.remove_directory(output_locations.output_dir)
18
- file_system_accessor.remove_directory(output_locations.final_app_dir)
18
+ file_system_accessor.empty_directory(output_locations.final_app_dir)
19
19
 
20
20
  copy_directory_from_gem(gem_root, 'template_app', output_locations.final_app_dir)
21
21
  copy_directory_from_gem(gem_root, 'master_middleman', output_locations.site_generator_home)
@@ -1,11 +1,15 @@
1
- require_relative '../archive'
1
+ require_relative '../deploy/archive'
2
2
  require_relative '../ingest/destination_directory'
3
- require_relative 'bookbinder_command'
4
3
  require_relative 'naming'
5
4
 
6
5
  module Bookbinder
7
6
  module Commands
8
- class BuildAndPushTarball < BookbinderCommand
7
+ class BuildAndPushTarball
8
+ def initialize(streams, configuration_fetcher)
9
+ @success = streams[:success]
10
+ @configuration_fetcher = configuration_fetcher
11
+ end
12
+
9
13
  include Commands::Naming
10
14
 
11
15
  def usage
@@ -16,11 +20,19 @@ module Bookbinder
16
20
  def run(_)
17
21
  config = configuration_fetcher.fetch_config
18
22
  aws_credentials = configuration_fetcher.fetch_credentials[:aws]
19
- archive = Archive.new(logger: @logger, key: aws_credentials.access_key, secret: aws_credentials.secret_key)
20
- archive.create_and_upload_tarball(build_number: ENV['BUILD_NUMBER'], bucket: aws_credentials.green_builds_bucket,
21
- namespace: Ingest::DestinationDirectory.new(config.book_repo))
23
+ archive = Deploy::Archive.new(key: aws_credentials.access_key, secret: aws_credentials.secret_key)
24
+ result = archive.create_and_upload_tarball(
25
+ build_number: ENV.fetch('BUILD_NUMBER', Time.now.strftime("%Y%m%d_%H%M")),
26
+ bucket: aws_credentials.green_builds_bucket,
27
+ namespace: Ingest::DestinationDirectory.new(config.book_repo)
28
+ )
29
+ success.puts(result.reason)
22
30
  0
23
31
  end
32
+
33
+ private
34
+
35
+ attr_reader :configuration_fetcher, :success
24
36
  end
25
37
  end
26
38
  end
@@ -15,9 +15,9 @@ require_relative '../ingest/cloner_factory'
15
15
  require_relative '../ingest/section_repository_factory'
16
16
  require_relative '../local_file_system_accessor'
17
17
  require_relative '../middleman_runner'
18
- require_relative '../post_production/sitemap_writer'
19
- require_relative '../preprocessing/copy_to_site_gen_dir'
18
+ require_relative '../postprocessing/sitemap_writer'
20
19
  require_relative '../preprocessing/dita_preprocessor'
20
+ require_relative '../preprocessing/link_to_site_gen_dir'
21
21
  require_relative '../preprocessing/preprocessor'
22
22
  require_relative '../sheller'
23
23
  require_relative '../subnav_formatter'
@@ -28,9 +28,9 @@ module Bookbinder
28
28
  class Collection
29
29
  include Enumerable
30
30
 
31
- def initialize(logger, base_streams, version_control_system)
31
+ def initialize(logger, streams, version_control_system)
32
32
  @logger = logger
33
- @base_streams = base_streams
33
+ @streams = streams
34
34
  @version_control_system = version_control_system
35
35
  end
36
36
 
@@ -47,7 +47,7 @@ module Bookbinder
47
47
 
48
48
  private
49
49
 
50
- attr_reader :logger, :base_streams, :version_control_system
50
+ attr_reader :logger, :streams, :version_control_system
51
51
 
52
52
  def list
53
53
  standard_commands + flags
@@ -63,17 +63,17 @@ module Bookbinder
63
63
  local_file_system_accessor,
64
64
  Sheller.new,
65
65
  Dir.pwd,
66
- colored_streams
66
+ streams
67
67
  ),
68
68
  build_and_push_tarball,
69
69
  bind,
70
- Commands::PushFromLocal.new(logger, configuration_fetcher, 'acceptance'),
70
+ Commands::PushFromLocal.new(streams, logger, configuration_fetcher, 'acceptance'),
71
71
  push_local_to_staging,
72
- Commands::PushToProd.new(logger, configuration_fetcher),
72
+ Commands::PushToProd.new(streams, logger, configuration_fetcher, Dir.mktmpdir),
73
73
  Commands::RunPublishCI.new(bind, push_local_to_staging, build_and_push_tarball),
74
- Commands::Tag.new(logger, configuration_fetcher, version_control_system),
74
+ Commands::Tag.new(streams, configuration_fetcher, version_control_system),
75
75
  Commands::UpdateLocalDocRepos.new(
76
- colored_streams,
76
+ streams,
77
77
  configuration_fetcher,
78
78
  version_control_system
79
79
  ),
@@ -86,13 +86,13 @@ module Bookbinder
86
86
 
87
87
  def bind
88
88
  @bind ||= Commands::Bind.new(
89
- colored_streams,
89
+ streams,
90
90
  OutputLocations.new(final_app_dir: final_app_directory, context_dir: File.absolute_path('.')),
91
91
  Config::BindConfigFactory.new(version_control_system, configuration_fetcher),
92
92
  Config::ArchiveMenuConfiguration.new(loader: config_loader, config_filename: 'bookbinder.yml'),
93
93
  local_file_system_accessor,
94
94
  MiddlemanRunner.new(logger, version_control_system),
95
- PostProduction::SitemapWriter.build(logger, final_app_directory, sitemap_port),
95
+ Postprocessing::SitemapWriter.build(logger, final_app_directory, sitemap_port),
96
96
  Preprocessing::Preprocessor.new(
97
97
  Preprocessing::DitaPreprocessor.new(
98
98
  DitaHtmlToMiddlemanFormatter.new(local_file_system_accessor, subnav_formatter, html_document_manipulator),
@@ -100,9 +100,9 @@ module Bookbinder
100
100
  DitaCommandCreator.new(ENV['PATH_TO_DITA_OT_LIBRARY']),
101
101
  Sheller.new
102
102
  ),
103
- Preprocessing::CopyToSiteGenDir.new(local_file_system_accessor),
103
+ Preprocessing::LinkToSiteGenDir.new(local_file_system_accessor),
104
104
  ),
105
- Ingest::ClonerFactory.new(logger, local_file_system_accessor, version_control_system),
105
+ Ingest::ClonerFactory.new(streams, local_file_system_accessor, version_control_system),
106
106
  Ingest::SectionRepositoryFactory.new(logger),
107
107
  Commands::BindComponents::DirectoryPreparer.new(logger, local_file_system_accessor, version_control_system)
108
108
  )
@@ -110,6 +110,7 @@ module Bookbinder
110
110
 
111
111
  def push_local_to_staging
112
112
  @push_local_to_staging ||= Commands::PushFromLocal.new(
113
+ streams,
113
114
  logger,
114
115
  configuration_fetcher,
115
116
  'staging'
@@ -118,17 +119,11 @@ module Bookbinder
118
119
 
119
120
  def build_and_push_tarball
120
121
  @build_and_push_tarball ||= Commands::BuildAndPushTarball.new(
121
- logger,
122
+ streams,
122
123
  configuration_fetcher
123
124
  )
124
125
  end
125
126
 
126
- def colored_streams
127
- { out: base_streams[:out],
128
- success: Streams::ColorizedStream.new(Colorizer::Colors.green, base_streams[:out]),
129
- err: Streams::ColorizedStream.new(Colorizer::Colors.red, base_streams[:err]) }
130
- end
131
-
132
127
  def configuration_fetcher
133
128
  @configuration_fetcher ||= Config::Fetcher.new(
134
129
  Config::Validator.new(local_file_system_accessor),
@@ -1,5 +1,5 @@
1
- require_relative '../distributor'
2
- require_relative 'bookbinder_command'
1
+ require_relative '../deploy/deployment'
2
+ require_relative '../deploy/distributor'
3
3
  require_relative 'naming'
4
4
 
5
5
  module Bookbinder
@@ -11,7 +11,8 @@ module Bookbinder
11
11
  REQUIRED_AWS_KEYS = %w(access_key secret_key green_builds_bucket)
12
12
  REQUIRED_CF_KEYS = %w(username password api_endpoint organization app_name)
13
13
 
14
- def initialize(logger, configuration_fetcher, environment)
14
+ def initialize(streams, logger, configuration_fetcher, environment)
15
+ @streams = streams
15
16
  @logger = logger
16
17
  @configuration_fetcher = configuration_fetcher
17
18
  @environment = environment
@@ -24,33 +25,40 @@ module Bookbinder
24
25
 
25
26
  def run(_)
26
27
  validate
27
- Distributor.build(@logger, options).distribute
28
+ deployment = Deploy::Deployment.new(
29
+ app_dir: './final_app',
30
+ aws_credentials: credentials[:aws],
31
+ book_repo: config.book_repo,
32
+ build_number: ENV['BUILD_NUMBER'],
33
+ cf_credentials: credentials[:cloud_foundry]
34
+ )
35
+ archive = Deploy::Archive.new(
36
+ logger: @logger,
37
+ key: deployment.aws_access_key,
38
+ secret: deployment.aws_secret_key
39
+ )
40
+ Deploy::Distributor.build(
41
+ streams,
42
+ archive,
43
+ deployment
44
+ ).distribute
28
45
  0
29
46
  end
30
47
 
31
48
  private
32
49
 
33
- attr_reader :configuration_fetcher, :environment
50
+ attr_reader :configuration_fetcher, :environment, :streams
34
51
 
35
52
  def config
36
- @config ||= configuration_fetcher.fetch_config
53
+ configuration_fetcher.fetch_config
37
54
  end
38
55
 
39
- def command_name
40
- "push_local_to_#{environment}"
56
+ def credentials
57
+ configuration_fetcher.fetch_credentials(environment)
41
58
  end
42
59
 
43
- def options
44
- credentials = configuration_fetcher.fetch_credentials(environment)
45
- {
46
- app_dir: './final_app',
47
- build_number: ENV['BUILD_NUMBER'],
48
-
49
- aws_credentials: credentials[:aws],
50
- cf_credentials: credentials[:cloud_foundry],
51
-
52
- book_repo: config.book_repo,
53
- }
60
+ def command_name
61
+ "push_local_to_#{environment}"
54
62
  end
55
63
 
56
64
  def error_message