bcome 1.3.3 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (183) hide show
  1. checksums.yaml +5 -5
  2. data/bin/bcome +13 -8
  3. data/lib/bcome.rb +14 -11
  4. data/lib/objects/bcome/version.rb +19 -1
  5. data/lib/objects/bootup.rb +16 -7
  6. data/lib/objects/command/local.rb +2 -0
  7. data/lib/objects/config_factory.rb +3 -0
  8. data/lib/objects/driver/base.rb +52 -6
  9. data/lib/objects/driver/bucket.rb +6 -4
  10. data/lib/objects/driver/ec2.rb +45 -5
  11. data/lib/objects/driver/gcp.rb +168 -0
  12. data/lib/objects/driver/gcp/authentication/api_key.rb +6 -0
  13. data/lib/objects/driver/gcp/authentication/base.rb +36 -0
  14. data/lib/objects/driver/gcp/authentication/oauth.rb +96 -0
  15. data/lib/objects/driver/gcp/authentication/oauth_client_config.rb +22 -0
  16. data/lib/objects/driver/gcp/authentication/oauth_session_store.rb +22 -0
  17. data/lib/objects/driver/gcp/authentication/service_account.rb +62 -0
  18. data/lib/objects/driver/gcp/authentication/signet/service_account.rb +27 -0
  19. data/lib/objects/driver/gcp/authentication/utilities.rb +42 -0
  20. data/lib/objects/encryptor.rb +109 -24
  21. data/lib/objects/exception/argument_error_invoking_method_from_command_line.rb +8 -4
  22. data/lib/objects/exception/base.rb +21 -10
  23. data/lib/objects/exception/can_only_subselect_on_inventory.rb +8 -4
  24. data/lib/objects/exception/cannot_authenticate_to_gcp.rb +11 -0
  25. data/lib/objects/exception/cannot_find_internal_registry_klass.rb +8 -4
  26. data/lib/objects/exception/cannot_find_inventory.rb +11 -0
  27. data/lib/objects/exception/cannot_find_subselection_parent.rb +8 -4
  28. data/lib/objects/exception/cant_find_key_in_cloud_tags.rb +8 -4
  29. data/lib/objects/exception/cant_find_key_in_metadata.rb +8 -4
  30. data/lib/objects/exception/cant_find_proxy_host_by_identifier.rb +8 -4
  31. data/lib/objects/exception/cant_find_proxy_host_by_namespace.rb +8 -4
  32. data/lib/objects/exception/could_not_initiate_ssh_connection.rb +8 -4
  33. data/lib/objects/exception/could_not_initiate_ssh_connection_through_backend_proxy.rb +8 -4
  34. data/lib/objects/exception/could_not_retrieve_terraform_output.rb +11 -0
  35. data/lib/objects/exception/deprecation_warning.rb +9 -7
  36. data/lib/objects/exception/duplicate_command_line_argument_key.rb +8 -4
  37. data/lib/objects/exception/ec2_driver_missing_authorization_keys.rb +11 -0
  38. data/lib/objects/exception/ec2_driver_missing_provisioning_region.rb +8 -4
  39. data/lib/objects/exception/empty_namespace_tree.rb +11 -0
  40. data/lib/objects/exception/failed_to_run_local_command.rb +8 -4
  41. data/lib/objects/exception/gcp_auth_service_account_missing_credentials.rb +11 -0
  42. data/lib/objects/exception/generic.rb +11 -0
  43. data/lib/objects/exception/interactive_session_halt.rb +6 -2
  44. data/lib/objects/exception/invalid_bcome_breadcrumb.rb +8 -4
  45. data/lib/objects/exception/invalid_breadcrumb.rb +8 -4
  46. data/lib/objects/exception/invalid_context_command.rb +8 -4
  47. data/lib/objects/exception/invalid_gcp_authentication_scheme.rb +11 -0
  48. data/lib/objects/exception/invalid_identifier.rb +8 -4
  49. data/lib/objects/exception/invalid_machines_cache_config.rb +8 -4
  50. data/lib/objects/exception/invalid_matcher_query.rb +8 -4
  51. data/lib/objects/exception/invalid_meta_data_config.rb +8 -4
  52. data/lib/objects/exception/invalid_metadata_encryption_key.rb +8 -4
  53. data/lib/objects/exception/invalid_network_config.rb +8 -4
  54. data/lib/objects/exception/invalid_network_driver_type.rb +8 -4
  55. data/lib/objects/exception/invalid_port_forward_request.rb +11 -0
  56. data/lib/objects/exception/invalid_proxy_config.rb +8 -4
  57. data/lib/objects/exception/invalid_regexp_matcher_in_registry.rb +8 -4
  58. data/lib/objects/exception/invalid_registry_arguments_type.rb +8 -4
  59. data/lib/objects/exception/invalid_registry_command_name_length.rb +8 -4
  60. data/lib/objects/exception/invalid_registry_data_config.rb +8 -4
  61. data/lib/objects/exception/invalid_restriction_key_in_registry.rb +8 -4
  62. data/lib/objects/exception/invalid_ssh_config.rb +8 -4
  63. data/lib/objects/exception/inventories_cannot_have_subviews.rb +8 -4
  64. data/lib/objects/exception/malformed_command_line_arguments.rb +8 -4
  65. data/lib/objects/exception/method_invocation_requires_parameter.rb +8 -4
  66. data/lib/objects/exception/method_name_conflict_in_registry.rb +8 -4
  67. data/lib/objects/exception/missing_argument_for_registry_command.rb +8 -4
  68. data/lib/objects/exception/missing_description_on_view.rb +8 -4
  69. data/lib/objects/exception/missing_execute_on_registry_object.rb +8 -4
  70. data/lib/objects/exception/missing_gcp_authentication_scheme.rb +11 -0
  71. data/lib/objects/exception/missing_gcp_service_account_credentials_filename.rb +11 -0
  72. data/lib/objects/exception/missing_gcp_service_scopes.rb +11 -0
  73. data/lib/objects/exception/missing_identifier_on_view.rb +8 -4
  74. data/lib/objects/exception/missing_inventory_contributors.rb +11 -0
  75. data/lib/objects/exception/missing_ip_address_on_server.rb +8 -4
  76. data/lib/objects/exception/missing_network_config.rb +8 -4
  77. data/lib/objects/exception/missing_or_invalid_client_secrets.rb +11 -0
  78. data/lib/objects/exception/missing_params_for_rsync.rb +8 -4
  79. data/lib/objects/exception/missing_params_for_scp.rb +8 -4
  80. data/lib/objects/exception/missing_subselection_key.rb +8 -4
  81. data/lib/objects/exception/missing_type_on_view.rb +8 -4
  82. data/lib/objects/exception/no_node_found_for_breadcrumb.rb +8 -4
  83. data/lib/objects/exception/no_node_named_by_identifier.rb +8 -4
  84. data/lib/objects/exception/node_identifiers_must_be_unique.rb +8 -4
  85. data/lib/objects/exception/orchestration_script_does_not_exist.rb +8 -4
  86. data/lib/objects/exception/proxy_host_node_does_not_have_public_ip_address.rb +8 -4
  87. data/lib/objects/exception/unknown_dynamic_server_type.rb +11 -0
  88. data/lib/objects/exception/unknown_method_for_namespace.rb +8 -4
  89. data/lib/objects/exception/user_orchestration_error.rb +11 -0
  90. data/lib/objects/initialization/factory.rb +36 -0
  91. data/lib/objects/initialization/structure.rb +18 -0
  92. data/lib/objects/initialization/utils.rb +20 -0
  93. data/lib/objects/interactive/session.rb +4 -1
  94. data/lib/objects/interactive/session_item/base.rb +2 -0
  95. data/lib/objects/interactive/session_item/capture_input.rb +2 -0
  96. data/lib/objects/interactive/session_item/transparent_ssh.rb +29 -23
  97. data/lib/objects/loading_bar/handler.rb +80 -0
  98. data/lib/objects/loading_bar/indicator/base.rb +65 -0
  99. data/lib/objects/loading_bar/indicator/basic.rb +34 -0
  100. data/lib/objects/loading_bar/indicator/progress.rb +26 -0
  101. data/lib/objects/loading_bar/pid_bucket.rb +27 -0
  102. data/lib/objects/modules/context.rb +13 -9
  103. data/lib/objects/modules/draw.rb +49 -0
  104. data/lib/objects/modules/registry_management.rb +16 -10
  105. data/lib/objects/modules/tree.rb +157 -0
  106. data/lib/objects/modules/ui_output.rb +10 -6
  107. data/lib/objects/modules/workspace_commands.rb +131 -157
  108. data/lib/objects/modules/workspace_menu.rb +193 -123
  109. data/lib/objects/node/attributes.rb +17 -19
  110. data/lib/objects/node/base.rb +136 -74
  111. data/lib/objects/node/cache_handler.rb +3 -1
  112. data/lib/objects/node/collection.rb +10 -9
  113. data/lib/objects/node/factory.rb +47 -36
  114. data/lib/objects/node/inventory/base.rb +106 -100
  115. data/lib/objects/node/inventory/defined.rb +113 -89
  116. data/lib/objects/node/inventory/merge.rb +51 -0
  117. data/lib/objects/node/inventory/subselect.rb +66 -46
  118. data/lib/objects/node/kube/base.rb +51 -0
  119. data/lib/objects/node/kube/container.rb +9 -0
  120. data/lib/objects/node/kube/estate.rb +19 -0
  121. data/lib/objects/node/kube/namespace.rb +24 -0
  122. data/lib/objects/node/kube/pod.rb +24 -0
  123. data/lib/objects/node/kube_wrap.rb +26 -0
  124. data/lib/objects/node/meta/base.rb +8 -1
  125. data/lib/objects/node/meta/cloud.rb +2 -0
  126. data/lib/objects/node/meta/local.rb +2 -0
  127. data/lib/objects/node/meta_data_factory.rb +4 -2
  128. data/lib/objects/node/meta_data_loader.rb +28 -29
  129. data/lib/objects/node/resources/base.rb +5 -1
  130. data/lib/objects/node/resources/inventory.rb +26 -5
  131. data/lib/objects/node/resources/merged.rb +47 -0
  132. data/lib/objects/node/resources/sub_inventory.rb +12 -8
  133. data/lib/objects/node/server/base.rb +106 -71
  134. data/lib/objects/node/server/dynamic/base.rb +23 -0
  135. data/lib/objects/node/server/{dynamic.rb → dynamic/ec2.rb} +13 -13
  136. data/lib/objects/node/server/dynamic/gcp.rb +46 -0
  137. data/lib/objects/node/server/static.rb +34 -10
  138. data/lib/objects/orchestration/base.rb +17 -1
  139. data/lib/objects/orchestration/interactive_terraform.rb +59 -30
  140. data/lib/objects/orchestrator.rb +22 -0
  141. data/lib/objects/parser/bread_crumb.rb +3 -1
  142. data/lib/objects/registry/arguments/base.rb +3 -1
  143. data/lib/objects/registry/arguments/command_line.rb +6 -1
  144. data/lib/objects/registry/arguments/console.rb +4 -1
  145. data/lib/objects/registry/command/base.rb +3 -0
  146. data/lib/objects/registry/command/external.rb +9 -3
  147. data/lib/objects/registry/command/group.rb +11 -4
  148. data/lib/objects/registry/command/internal.rb +3 -1
  149. data/lib/objects/registry/command/shortcut.rb +17 -9
  150. data/lib/objects/registry/command_list.rb +2 -0
  151. data/lib/objects/registry/loader.rb +13 -10
  152. data/lib/objects/ssh/bootstrap.rb +3 -1
  153. data/lib/objects/ssh/command.rb +9 -8
  154. data/lib/objects/ssh/command_exec.rb +16 -10
  155. data/lib/objects/ssh/connection_wrangler.rb +122 -0
  156. data/lib/objects/ssh/connector.rb +108 -0
  157. data/lib/objects/ssh/driver.rb +28 -242
  158. data/lib/objects/ssh/driver_concerns/command_strings.rb +17 -0
  159. data/lib/objects/ssh/driver_concerns/connection.rb +70 -0
  160. data/lib/objects/ssh/driver_concerns/functions.rb +89 -0
  161. data/lib/objects/ssh/driver_concerns/user.rb +32 -0
  162. data/lib/objects/ssh/proxy_chain.rb +19 -0
  163. data/lib/objects/ssh/proxy_chain_link.rb +26 -0
  164. data/lib/objects/ssh/proxy_hop.rb +130 -0
  165. data/lib/objects/ssh/script_exec.rb +12 -11
  166. data/lib/objects/ssh/tunnel/local_port_forward.rb +5 -6
  167. data/lib/objects/ssh/tunnel_keeper.rb +21 -0
  168. data/lib/objects/ssh/window.rb +31 -0
  169. data/lib/objects/startup.rb +58 -0
  170. data/lib/objects/system/local.rb +3 -0
  171. data/lib/objects/terraform/output.rb +45 -0
  172. data/lib/objects/workspace.rb +13 -14
  173. data/patches/irb.rb +63 -6
  174. data/patches/string-encrypt.rb +20 -23
  175. data/patches/string.rb +18 -1
  176. data/patches/string_stylesheet.rb +2 -0
  177. metadata +157 -33
  178. data/lib/objects/driver/static.rb +0 -4
  179. data/lib/objects/progress_bar.rb +0 -30
  180. data/lib/objects/ssh/connection_handler.rb +0 -101
  181. data/lib/objects/ssh/proxy_data.rb +0 -56
  182. data/lib/objects/terraform/parser.rb +0 -23
  183. data/lib/objects/terraform/state.rb +0 -40
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bcome::Driver::Gcp::Authentication
4
+ class ApiKey
5
+ end
6
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bcome::Driver::Gcp::Authentication
4
+ class Base
5
+ include ::Bcome::LoadingBar::Handler
6
+
7
+ ## Overrides
8
+ def authorized?
9
+ raise 'Should be overidden'
10
+ end
11
+
12
+ ## Loading bar -
13
+
14
+ def loader_title
15
+ 'Authenticating' + "\s#{@driver.pretty_provider_name.bc_blue.bold}\s#{@driver.pretty_resource_location.underline}".bc_green
16
+ end
17
+
18
+ ## Credential helpers --
19
+
20
+ def credential_directory
21
+ '.gauth'
22
+ end
23
+
24
+ def full_path_to_credential_file
25
+ "#{credential_directory}/#{credential_file}"
26
+ end
27
+
28
+ def credential_file
29
+ "#{@node.keyed_namespace}:#{credential_file_suffix}"
30
+ end
31
+
32
+ def ensure_credential_directory
33
+ `mkdir -p #{credential_directory}`
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'google/api_client/auth/storage'
4
+ require 'google/api_client/auth/storages/file_store'
5
+ require 'google/api_client/client_secrets'
6
+
7
+ module Bcome::Driver::Gcp::Authentication
8
+ class Oauth < Base
9
+ include Utilities
10
+
11
+ credential_directory = '.gauth'
12
+
13
+ attr_reader :scopes, :secrets_filename, :service, :client_config
14
+
15
+ def initialize(driver, service, client_config, node)
16
+ @service = service
17
+ @scopes = client_config.scopes
18
+ @node = node
19
+ @driver = driver
20
+ @client_config = client_config
21
+ @secrets_filename = client_config.secrets_filename
22
+ @path_to_secrets = "#{credential_directory}/#{@secrets_filename}"
23
+
24
+ raise ::Bcome::Exception::Generic, "Missing OAuth 2.0 client secrets file from GCP network configuration. Cannot find '#{@path_to_secrets}'" unless File.exist?(@path_to_secrets) && File.file?(@path_to_secrets)
25
+
26
+ # All credentials are held in .gauth
27
+ ensure_credential_directory
28
+ end
29
+
30
+ def authorized?
31
+ storage && !@storage.authorization.nil?
32
+ end
33
+
34
+ def credential_file_suffix
35
+ 'oauth2.json'
36
+ end
37
+
38
+ def authorize!
39
+ @service.authorization = storage.authorize
40
+ end
41
+
42
+ def client_secrets
43
+ @client_secrets ||= load_client_secrets
44
+ end
45
+
46
+ def load_client_secrets
47
+ ::Google::APIClient::ClientSecrets.load(@path_to_secrets)
48
+ rescue Exception => e
49
+ raise ::Bcome::Exception::MissingOrInvalidClientSecrets, "#{@path_to_secrets}. Gcp exception: #{e.class} #{e.message}"
50
+ end
51
+
52
+ def storage
53
+ @storage ||= ::Google::APIClient::Storage.new(Google::APIClient::FileStore.new(full_path_to_credential_file))
54
+ end
55
+
56
+ def credential_file
57
+ # If an authorization has the same scopes & secrets file, it is the same authorization. Hence we store the resulting oauth2 access credentials as the same file. This allows
58
+ # re-use of authorizations and prevents multiple oauth loops.
59
+ "#{@client_config.checksum}:#{credential_file_suffix}"
60
+ end
61
+
62
+ def do!
63
+ authorize!
64
+ if @storage.authorization.nil?
65
+ # Total bloat from google here. Thanks google... requiring at last possible moment.
66
+ require 'google/api_client/auth/installed_app'
67
+
68
+ wrap_indicator type: :basic, title: loader_title, completed_title: '' do
69
+ flow = Google::APIClient::InstalledAppFlow.new(
70
+ client_id: client_secrets.client_id,
71
+ client_secret: client_secrets.client_secret,
72
+ scope: @scopes
73
+ )
74
+
75
+ ## Override the redirected-to screen so that clearer instruction can be given
76
+ flow.class.send(:remove_const,'RESPONSE_BODY') if flow.class.const_defined?('RESPONSE_BODY')
77
+ flow.class.send(:const_set,'RESPONSE_BODY', oauth_redirect_html)
78
+
79
+ begin
80
+ @service.authorization = flow.authorize(storage)
81
+ signal_success
82
+ rescue ArgumentError => e
83
+ signal_failure
84
+ raise ::Bcome::Exception::MissingOrInvalidClientSecrets, "#{@path_to_secrets}. Gcp exception: #{e.class} #{e.message}"
85
+ end
86
+ end
87
+ end
88
+
89
+ @service
90
+ end
91
+
92
+ def notify_success
93
+ print "[\s" + "Credentials file written to\s" + full_path_to_credential_file + "\s]" + "\n"
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'digest/md5'
4
+
5
+ module Bcome::Driver::Gcp::Authentication
6
+ class OauthClientConfig
7
+ attr_reader :scopes, :secrets_filename
8
+
9
+ def initialize(scopes, secrets_filename)
10
+ @scopes = scopes
11
+ @secrets_filename = secrets_filename
12
+ end
13
+
14
+ def ==(other)
15
+ checksum == other.checksum
16
+ end
17
+
18
+ def checksum
19
+ @checksum ||= ::Digest::MD5.hexdigest(Marshal.dump("#{@scopes}-#{@secrets_filename}"))
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bcome::Driver::Gcp::Authentication
4
+ class OauthSessionStore
5
+ include Singleton
6
+
7
+ def initialize
8
+ @oauth_sessions = []
9
+ end
10
+
11
+ def in_memory_session_for(oauth_client_config)
12
+ existing_session = @oauth_sessions.detect do |session|
13
+ session.client_config == oauth_client_config
14
+ end
15
+ existing_session
16
+ end
17
+
18
+ def <<(session)
19
+ @oauth_sessions << session
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bcome::Driver::Gcp::Authentication
4
+ class ServiceAccount < Base
5
+ def initialize(service, scopes, node, credentials_file_name, driver)
6
+ @service = service
7
+ @scopes = scopes
8
+ @node = node
9
+ @driver = driver
10
+ @credentials_file_name = credentials_file_name
11
+ ensure_credential_directory
12
+ ensure_credentials_file
13
+ end
14
+
15
+ def do!
16
+ @service.authorization = service_account
17
+ end
18
+
19
+ def authorized?
20
+ !@service.authorization.nil?
21
+ end
22
+
23
+ def service_account
24
+ @service_account ||= ::Bcome::Driver::Gcp::Authentication::SignetServiceAccountClient.new(@scopes, credentials_file_path)
25
+ end
26
+
27
+ def credentials_file_path
28
+ has_namespaced_keyed_filename? ? namespaced_keyed_filename : defined_credentials_files
29
+ end
30
+
31
+ def ensure_credentials_file
32
+ return if has_namespaced_keyed_filename?
33
+ raise ::Bcome::Exception::MissingGcpServiceAccountCredentialsFilename unless @credentials_file_name
34
+ end
35
+
36
+ ## New implementation - we take a defined file name for the service account credentials
37
+ ## Clean & may be re-used
38
+ def defined_credentials_files
39
+ "#{credential_directory}/#{@credentials_file_name}"
40
+ end
41
+
42
+ def has_namespaced_keyed_filename?
43
+ @has_namespaced_keyed_filename ||= File.exist?(namespaced_keyed_filename)
44
+ end
45
+
46
+ ## Older implementation - we infer the credentials file from the namespace
47
+ ## Retained to provide backwards compatibility
48
+ def namespaced_keyed_filename
49
+ full_path_to_credential_file
50
+ end
51
+
52
+ def credential_file_suffix
53
+ 'service-account.json'
54
+ end
55
+ #######################################
56
+
57
+ def authorize!
58
+ storage.authorize
59
+ @service.authorization = storage.authorization
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bcome::Driver::Gcp::Authentication
4
+ class SignetServiceAccountClient < Signet::OAuth2::Client
5
+ def initialize(scopes, service_account_json_path)
6
+ @scopes = scopes
7
+ @service_account_json_path = service_account_json_path
8
+ raise ::Bcome::Exception::GcpAuthServiceAccountMissingCredentials, @service_account_json_path unless File.exist?(@service_account_json_path)
9
+ end
10
+
11
+ def fetch_access_token(_options = {})
12
+ token = authorizer.fetch_access_token!
13
+ token
14
+ end
15
+
16
+ def authorize
17
+ @token ||= fetch_access_token
18
+ end
19
+
20
+ def authorizer
21
+ authorizer = Google::Auth::ServiceAccountCredentials.make_creds(
22
+ json_key_io: File.open(@service_account_json_path),
23
+ scope: @scopes
24
+ )
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,42 @@
1
+ module Bcome::Driver::Gcp::Authentication::Utilities
2
+
3
+ def oauth_redirect_html
4
+ ## [GR] Style rules: Explicitly no assets to be pulled from bcome remote (no tracking). Inline styles only.
5
+ ## Made an exception for the google font, as the user is already oauthing against google in any case.
6
+ <<-HTML
7
+ <html>
8
+ <head>
9
+ <script>
10
+ function closeWindow() {
11
+ window.open('', '_self', '');
12
+ window.close();
13
+ }
14
+ setTimeout(closeWindow, 10);
15
+ </script>
16
+ </head>
17
+ <style>
18
+ @import url("https://fonts.googleapis.com/css2?family=Catamaran:wght@200;500&display=swap");
19
+
20
+ body {
21
+ font-family: 'Catamaran', sans-serif;
22
+ font-weight: 200;
23
+ color: #3E4E60;
24
+ }
25
+ </style>
26
+ <body>#{oauth_redirect_body}</body>
27
+ </html>
28
+ HTML
29
+ end
30
+
31
+ def oauth_redirect_body
32
+ <<-HTML
33
+ <p>
34
+ OAuth redirection for namespace <strong>#{@node.keyed_namespace}</strong> complete.
35
+ </p>
36
+ <p>
37
+ You may close this window and return to the Bcome Console.
38
+ </p>
39
+ HTML
40
+ end
41
+
42
+ end
@@ -1,8 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'diffy'
4
+
1
5
  module Bcome
2
6
  class Encryptor
3
-
4
- UNENC_SIGNIFIER = "".freeze
5
- ENC_SIGNIFIER = "enc".freeze
7
+ UNENC_SIGNIFIER = ''
8
+ ENC_SIGNIFIER = 'enc'
9
+ AFFIRMATIVE = 'yes'
6
10
 
7
11
  include Singleton
8
12
 
@@ -19,13 +23,23 @@ module Bcome
19
23
  else
20
24
  puts "\nNo unencrypted files to encrypt.\n".warning
21
25
  end
22
- return
26
+ nil
23
27
  end
24
28
 
25
29
  def prompt_for_key
26
- print "Please enter an encryption key (and if your data is already encrypted, you must provide the same key): ".informational
30
+ puts "\n"
31
+ print 'Please enter an encryption key (and if your data is already encrypted, you must provide the same key): '.informational
27
32
  @key = STDIN.noecho(&:gets).chomp
33
+ # puts "\n"
34
+ end
35
+
36
+ def prompt_to_overwrite
37
+ valid_answers = [AFFIRMATIVE, 'no']
28
38
  puts "\n"
39
+ print "Do you want to continue with unpacking this file? Your local changes would be overwritten [#{valid_answers.join(',')}]\s"
40
+ answer = STDIN.gets.chomp
41
+ prompt_to_overwrite unless valid_answers.include?(answer)
42
+ answer
29
43
  end
30
44
 
31
45
  def has_encrypted_files?
@@ -46,45 +60,117 @@ module Bcome
46
60
 
47
61
  def unpack
48
62
  prompt_for_key
49
- toggle_packed_files(all_encrypted_filenames,:decrypt)
50
- return
63
+ toggle_packed_files(all_encrypted_filenames, :decrypt)
64
+ nil
65
+ end
66
+
67
+ def decrypt_file_data(filename)
68
+ raw_contents = File.read(filename)
69
+ raw_contents.send(:decrypt, @key)
70
+ end
71
+
72
+ def enc_file_diff(filename)
73
+ # Get decrypted file data
74
+ decrypted_data_for_filename = decrypt_file_data(filename)
75
+
76
+ # Get unpacked file data
77
+ opposing_filename = opposing_file_for_filename(filename)
78
+ return nil unless File.exist?(opposing_filename)
79
+
80
+ unpacked_file_data = File.read(opposing_filename)
81
+
82
+ # there are no differences
83
+ return nil if decrypted_data_for_filename.eql?(unpacked_file_data)
84
+
85
+ get_diffs(unpacked_file_data, decrypted_data_for_filename)
86
+ end
87
+
88
+ def opposing_file_for_filename(filename)
89
+ filename =~ %r{#{path_to_metadata}/(.+)\.enc}
90
+ "#{path_to_metadata}/#{Regexp.last_match(1)}"
91
+ end
92
+
93
+ def get_diffs(file_one, file_two)
94
+ diffy = ::Diffy::SplitDiff.new(file_one, file_two)
95
+ left_diffs = diffy.left.split("\n").each_with_index.collect { |l, index| "#{index + 1}:\s#{l}" }
96
+ right_diffs = diffy.right.split("\n").each_with_index.collect { |l, index| "#{index + 1}:\s#{l}" }
97
+
98
+ diffed_lines = (left_diffs + right_diffs).select { |line| line =~ /^[0-9]+:\s[+-](.+)$/ }
99
+ return nil if diffed_lines.empty?
100
+
101
+ diffed_lines.collect do |line|
102
+ line =~ /^[0-9]+:\s\+(.+)$/ ? line.bc_green : line.bc_red
103
+ end.join("\n")
104
+ end
105
+
106
+ def diff
107
+ prompt_for_key
108
+ puts "\n"
109
+ all_encrypted_filenames.each do |filename|
110
+ opposing_file = opposing_file_for_filename(filename)
111
+ if File.exist?(opposing_file)
112
+ if diffs = enc_file_diff(filename)
113
+ puts "\n[+/-]\s".warning + filename + "\sis different to your local unpacked version\n\n"
114
+ puts diffs + "\n\n"
115
+ else
116
+ puts filename.to_s.informational + "\s- no differences".bc_green
117
+ end
118
+ else
119
+ puts filename.to_s.informational + "\s- new file".warning
120
+ end
121
+ end
122
+ puts "\n"
51
123
  end
52
124
 
53
125
  def toggle_packed_files(filenames, packer_method)
54
- raise "Missing encryption key. Please set an encryption key" unless @key
126
+ raise 'Missing encryption key. Please set an encryption key' unless @key
127
+
55
128
  filenames.each do |filename|
56
129
  # Get raw
57
130
  raw_contents = File.read(filename)
58
-
131
+
59
132
  if packer_method == :decrypt
60
- filename =~ /#{path_to_metadata}\/(.+)\.enc/
61
- opposing_filename = $1
62
- action = "Unpacking"
133
+ filename =~ %r{#{path_to_metadata}/(.+)\.enc}
134
+ opposing_filename = Regexp.last_match(1)
135
+ action = 'Unpacking'
136
+
137
+ # Skip unpacking a file if there are local modifications that the user does not want to lose.
138
+ if diffs = enc_file_diff(filename)
139
+ puts "\n[+/-]\s".warning + filename + "\sis different to your local unpacked version\n\n"
140
+ puts diffs
141
+
142
+ if prompt_to_overwrite != AFFIRMATIVE
143
+ puts "\n\nskipping\s".warning + filename + "\n"
144
+ next
145
+ end
146
+ puts "\n"
147
+ end
63
148
  else
64
- filename =~ /#{path_to_metadata}\/(.*)/
65
- opposing_filename = "#{$1}.enc"
66
- action = "Packing"
67
- end
149
+ filename =~ %r{#{path_to_metadata}/(.*)}
150
+ opposing_filename = "#{Regexp.last_match(1)}.enc"
151
+ action = 'Packing'
152
+ end
68
153
 
69
154
  # Write encrypted/decryption action
70
155
  enc_decrypt_result = raw_contents.send(packer_method, @key)
156
+ print "\n\n"
71
157
  puts "#{action}\s".informational + filename + "\sto\s".informational + "#{path_to_metadata}/" + opposing_filename
72
158
  write_file(opposing_filename, enc_decrypt_result)
73
159
  end
74
160
  puts "\ndone".informational
75
- end
76
-
161
+ end
162
+
77
163
  def path_to_metadata
78
- "bcome/metadata"
164
+ 'bcome/metadata'
79
165
  end
80
166
 
81
167
  def write_file(filename, contents)
82
168
  filepath = "#{path_to_metadata}/#{filename}"
83
- File.open("#{filepath}", 'w') { |f| f.write(contents) }
169
+ File.open(filepath.to_s, 'w') { |f| f.write(contents) }
84
170
  end
85
-
171
+
86
172
  def all_unencrypted_filenames
87
- Dir["#{metadata_path}/*"].reject {|f| f =~ /\.enc/}
173
+ Dir["#{metadata_path}/*"].reject { |f| f =~ /\.enc/ }
88
174
  end
89
175
 
90
176
  def all_encrypted_filenames
@@ -92,8 +178,7 @@ module Bcome
92
178
  end
93
179
 
94
180
  def metadata_path
95
- "bcome/metadata"
181
+ 'bcome/metadata'
96
182
  end
97
-
98
183
  end
99
184
  end