bard 2.0.0.beta → 2.0.0

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.
Files changed (174) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +6 -1
  3. data/CLAUDE.md +76 -0
  4. data/MIGRATION_GUIDE.md +24 -9
  5. data/PLUGINS.md +99 -0
  6. data/README.md +14 -6
  7. data/Rakefile +3 -1
  8. data/bard.gemspec +2 -1
  9. data/cucumber.yml +1 -0
  10. data/features/ci.feature +63 -0
  11. data/features/data.feature +13 -0
  12. data/features/deploy.feature +14 -0
  13. data/features/deploy_git_workflow.feature +89 -0
  14. data/features/run.feature +14 -0
  15. data/features/step_definitions/bard_steps.rb +136 -0
  16. data/features/support/bard-coverage +16 -0
  17. data/features/support/env.rb +14 -39
  18. data/features/support/test_server.rb +216 -0
  19. data/lib/bard/cli.rb +14 -31
  20. data/lib/bard/command.rb +10 -69
  21. data/lib/bard/config.rb +40 -183
  22. data/lib/bard/copy.rb +28 -103
  23. data/lib/bard/plugins/data.rb +56 -0
  24. data/lib/bard/{ci → plugins/deploy/ci}/github_actions.rb +3 -4
  25. data/lib/bard/plugins/deploy/ci/jenkins.rb +176 -0
  26. data/lib/bard/{ci → plugins/deploy/ci}/local.rb +7 -7
  27. data/lib/bard/{ci → plugins/deploy/ci}/runner.rb +38 -4
  28. data/lib/bard/plugins/deploy/ci.rb +38 -0
  29. data/lib/bard/plugins/deploy/ssh_strategy.rb +27 -0
  30. data/lib/bard/{deploy_strategy.rb → plugins/deploy/strategy.rb} +1 -1
  31. data/lib/bard/plugins/deploy.rb +240 -0
  32. data/lib/bard/{git.rb → plugins/git.rb} +6 -3
  33. data/lib/bard/{github.rb → plugins/github.rb} +4 -6
  34. data/lib/bard/{deploy_strategy/github_pages.rb → plugins/github_pages/strategy.rb} +13 -6
  35. data/lib/bard/plugins/github_pages.rb +30 -0
  36. data/lib/bard/plugins/hurt.rb +13 -0
  37. data/{install_files → lib/bard/plugins/install}/.github/dependabot.yml +2 -1
  38. data/{install_files → lib/bard/plugins/install}/.github/workflows/cache-ci.yml +1 -1
  39. data/{install_files → lib/bard/plugins/install}/.github/workflows/ci.yml +2 -2
  40. data/lib/bard/plugins/install.rb +9 -0
  41. data/lib/bard/plugins/open.rb +20 -0
  42. data/lib/bard/{ping.rb → plugins/ping/check.rb} +4 -4
  43. data/lib/bard/plugins/ping/target_methods.rb +23 -0
  44. data/lib/bard/plugins/ping.rb +10 -0
  45. data/lib/bard/plugins/run.rb +19 -0
  46. data/lib/bard/plugins/setup.rb +54 -0
  47. data/lib/bard/plugins/ssh/connection.rb +75 -0
  48. data/lib/bard/plugins/ssh/copy.rb +95 -0
  49. data/lib/bard/{ssh_server.rb → plugins/ssh/server.rb} +17 -42
  50. data/lib/bard/plugins/ssh/target_methods.rb +20 -0
  51. data/lib/bard/plugins/ssh.rb +10 -0
  52. data/lib/bard/plugins/url/target_methods.rb +23 -0
  53. data/lib/bard/plugins/url.rb +1 -0
  54. data/lib/bard/plugins/vim.rb +6 -0
  55. data/lib/bard/retryable.rb +25 -0
  56. data/lib/bard/secrets.rb +10 -0
  57. data/lib/bard/target.rb +27 -185
  58. data/lib/bard/version.rb +1 -1
  59. data/lib/bard.rb +1 -3
  60. data/spec/acceptance/docker/Dockerfile +3 -2
  61. data/spec/bard/capability_spec.rb +8 -50
  62. data/spec/bard/ci/github_actions_spec.rb +117 -14
  63. data/spec/bard/ci/jenkins_spec.rb +139 -0
  64. data/spec/bard/ci/runner_spec.rb +61 -0
  65. data/spec/bard/ci_spec.rb +1 -1
  66. data/spec/bard/cli/ci_spec.rb +34 -27
  67. data/spec/bard/cli/data_spec.rb +7 -26
  68. data/spec/bard/cli/deploy_spec.rb +87 -46
  69. data/spec/bard/cli/hurt_spec.rb +3 -9
  70. data/spec/bard/cli/install_spec.rb +5 -11
  71. data/spec/bard/cli/master_key_spec.rb +5 -19
  72. data/spec/bard/cli/open_spec.rb +14 -30
  73. data/spec/bard/cli/ping_spec.rb +8 -23
  74. data/spec/bard/cli/run_spec.rb +27 -21
  75. data/spec/bard/cli/setup_spec.rb +10 -27
  76. data/spec/bard/cli/ssh_spec.rb +10 -25
  77. data/spec/bard/cli/stage_spec.rb +28 -23
  78. data/spec/bard/cli/vim_spec.rb +3 -9
  79. data/spec/bard/command_spec.rb +1 -8
  80. data/spec/bard/config_spec.rb +78 -98
  81. data/spec/bard/copy_spec.rb +54 -18
  82. data/spec/bard/deploy_strategy/ssh_spec.rb +65 -7
  83. data/spec/bard/deploy_strategy_spec.rb +1 -1
  84. data/spec/bard/dynamic_dsl_spec.rb +18 -98
  85. data/spec/bard/git_spec.rb +9 -5
  86. data/spec/bard/github_spec.rb +2 -2
  87. data/spec/bard/ping_spec.rb +5 -5
  88. data/spec/bard/ssh_copy_spec.rb +44 -0
  89. data/spec/bard/ssh_server_spec.rb +8 -101
  90. data/spec/bard/target_spec.rb +66 -109
  91. data/spec/spec_helper.rb +6 -1
  92. metadata +79 -143
  93. data/README.rdoc +0 -15
  94. data/features/bard_check.feature +0 -94
  95. data/features/bard_deploy.feature +0 -18
  96. data/features/bard_pull.feature +0 -112
  97. data/features/bard_push.feature +0 -112
  98. data/features/podman_testcontainers.feature +0 -16
  99. data/features/step_definitions/check_steps.rb +0 -47
  100. data/features/step_definitions/git_steps.rb +0 -73
  101. data/features/step_definitions/global_steps.rb +0 -56
  102. data/features/step_definitions/podman_steps.rb +0 -23
  103. data/features/step_definitions/rails_steps.rb +0 -44
  104. data/features/step_definitions/submodule_steps.rb +0 -110
  105. data/features/support/grit_ext.rb +0 -13
  106. data/features/support/io.rb +0 -32
  107. data/features/support/podman.rb +0 -153
  108. data/lib/bard/ci/jenkins.rb +0 -105
  109. data/lib/bard/ci/retryable.rb +0 -27
  110. data/lib/bard/ci.rb +0 -50
  111. data/lib/bard/cli/ci.rb +0 -66
  112. data/lib/bard/cli/command.rb +0 -26
  113. data/lib/bard/cli/data.rb +0 -45
  114. data/lib/bard/cli/deploy.rb +0 -85
  115. data/lib/bard/cli/hurt.rb +0 -20
  116. data/lib/bard/cli/install.rb +0 -16
  117. data/lib/bard/cli/master_key.rb +0 -17
  118. data/lib/bard/cli/new.rb +0 -101
  119. data/lib/bard/cli/new_rails_template.rb +0 -197
  120. data/lib/bard/cli/open.rb +0 -22
  121. data/lib/bard/cli/ping.rb +0 -18
  122. data/lib/bard/cli/provision.rb +0 -34
  123. data/lib/bard/cli/run.rb +0 -24
  124. data/lib/bard/cli/setup.rb +0 -56
  125. data/lib/bard/cli/ssh.rb +0 -14
  126. data/lib/bard/cli/stage.rb +0 -27
  127. data/lib/bard/cli/vim.rb +0 -13
  128. data/lib/bard/default_config.rb +0 -35
  129. data/lib/bard/deploy_strategy/ssh.rb +0 -19
  130. data/lib/bard/github_pages.rb +0 -134
  131. data/lib/bard/provision/app.rb +0 -10
  132. data/lib/bard/provision/apt.rb +0 -16
  133. data/lib/bard/provision/authorizedkeys.rb +0 -25
  134. data/lib/bard/provision/data.rb +0 -27
  135. data/lib/bard/provision/deploy.rb +0 -10
  136. data/lib/bard/provision/http.rb +0 -16
  137. data/lib/bard/provision/logrotation.rb +0 -30
  138. data/lib/bard/provision/masterkey.rb +0 -18
  139. data/lib/bard/provision/mysql.rb +0 -22
  140. data/lib/bard/provision/passenger.rb +0 -37
  141. data/lib/bard/provision/repo.rb +0 -72
  142. data/lib/bard/provision/rvm.rb +0 -22
  143. data/lib/bard/provision/ssh.rb +0 -72
  144. data/lib/bard/provision/swapfile.rb +0 -21
  145. data/lib/bard/provision/user.rb +0 -42
  146. data/lib/bard/provision.rb +0 -16
  147. data/lib/bard/server.rb +0 -117
  148. data/spec/bard/cli/command_spec.rb +0 -50
  149. data/spec/bard/cli/new_spec.rb +0 -73
  150. data/spec/bard/cli/provision_spec.rb +0 -42
  151. data/spec/bard/github_pages_spec.rb +0 -143
  152. data/spec/bard/provision/app_spec.rb +0 -33
  153. data/spec/bard/provision/apt_spec.rb +0 -39
  154. data/spec/bard/provision/authorizedkeys_spec.rb +0 -40
  155. data/spec/bard/provision/data_spec.rb +0 -54
  156. data/spec/bard/provision/deploy_spec.rb +0 -33
  157. data/spec/bard/provision/http_spec.rb +0 -57
  158. data/spec/bard/provision/logrotation_spec.rb +0 -34
  159. data/spec/bard/provision/masterkey_spec.rb +0 -63
  160. data/spec/bard/provision/mysql_spec.rb +0 -55
  161. data/spec/bard/provision/passenger_spec.rb +0 -81
  162. data/spec/bard/provision/repo_spec.rb +0 -208
  163. data/spec/bard/provision/rvm_spec.rb +0 -49
  164. data/spec/bard/provision/ssh_spec.rb +0 -229
  165. data/spec/bard/provision/swapfile_spec.rb +0 -32
  166. data/spec/bard/provision/user_spec.rb +0 -103
  167. data/spec/bard/provision_spec.rb +0 -28
  168. data/spec/bard/server_spec.rb +0 -127
  169. /data/lib/bard/{ci → plugins/deploy/ci}/state.rb +0 -0
  170. /data/{install_files → lib/bard/plugins/install}/apt_dependencies.rb +0 -0
  171. /data/{install_files → lib/bard/plugins/install}/ci +0 -0
  172. /data/{install_files → lib/bard/plugins/install}/setup +0 -0
  173. /data/{install_files → lib/bard/plugins/install}/specified_bundler.rb +0 -0
  174. /data/{install_files → lib/bard/plugins/install}/specified_ruby.rb +0 -0
data/lib/bard/config.rb CHANGED
@@ -1,222 +1,79 @@
1
- require "bard/server"
2
1
  require "bard/target"
2
+ require "bard/plugins/ssh/target_methods"
3
+ require "bard/plugins/ping/target_methods"
3
4
 
4
5
  module Bard
5
6
  class Config
6
- def self.current(working_directory: Dir.getwd)
7
- project_name = File.basename(working_directory)
8
- path = File.join(working_directory, "bard.rb")
9
- new(project_name, path: path)
7
+ def self.current
8
+ new(detect_project_name, path: "bard.rb")
9
+ end
10
+
11
+ def self.detect_project_name
12
+ git_common_dir = `git rev-parse --git-common-dir 2>/dev/null`.chomp
13
+ dirname = if $?.success? && !git_common_dir.empty?
14
+ File.dirname(File.expand_path(git_common_dir))
15
+ else
16
+ Dir.getwd
17
+ end
18
+ File.basename(dirname)
10
19
  end
11
20
 
12
21
  attr_reader :project_name, :targets
13
22
 
14
23
  def initialize(project_name = nil, path: nil, source: nil)
15
- # Support both positional and keyword argument for project_name
16
24
  @project_name = project_name
17
- @servers = {} # Unified hash for both Server and Target instances
18
- @data_paths = []
19
- @backup = nil
20
- @ci_system = nil
21
-
22
- # Load default configuration (creates Server instances for backward compat)
25
+ @targets = {}
23
26
  load_defaults if project_name
24
27
 
25
- # Load user configuration
26
28
  if path && File.exist?(path)
27
29
  source = File.read(path)
28
30
  end
29
31
  if source
30
- instance_eval(source)
32
+ instance_eval(source, path)
31
33
  end
32
34
  end
33
35
 
34
- # Backward compatible accessor
35
- def servers
36
- @servers
37
- end
38
-
39
- # New v2.0 accessor (same as servers)
40
- def targets
41
- @servers
36
+ def remove_target(key)
37
+ @targets.delete(key.to_sym)
42
38
  end
43
39
 
44
- # Old v1.x API - creates Server instances
45
- def server(key, &block)
46
- key = key.to_sym
47
- @servers[key] = Server.define(project_name, key, &block)
48
- end
49
-
50
- # New v2.0 API - creates Target instances
51
40
  def target(key, &block)
52
41
  key = key.to_sym
53
- @servers[key] ||= Target.new(key, self)
54
- @servers[key].instance_eval(&block) if block
55
- @servers[key]
56
- end
57
-
58
- # Get a server/target by key
59
- def [](key)
60
- key = key.to_sym
61
- # Fallback to staging if production not defined
62
- if @servers[key].nil? && key == :production
63
- key = :staging
42
+ unless @targets[key].is_a?(Target)
43
+ @targets[key] = Target.new(key, self)
64
44
  end
65
- @servers[key]
45
+ @targets[key].instance_eval(&block) if block
46
+ @targets[key]
66
47
  end
67
48
 
68
- # Data paths configuration
69
- def data(*paths)
70
- if paths.empty?
71
- @data_paths
72
- else
73
- @data_paths = paths
74
- end
75
- end
76
-
77
- def data_paths
78
- @data_paths
79
- end
80
-
81
- def backup(value = nil, &block)
82
- if block_given?
83
- @backup = BackupConfig.new(&block)
84
- elsif value == false
85
- @backup = BackupConfig.new { disabled }
86
- elsif value.nil? # Getter
87
- @backup ||= BackupConfig.new { bard }
88
- else
89
- raise ArgumentError, "backup accepts false or a block"
90
- end
49
+ def [](key)
50
+ @targets[key.to_sym]
91
51
  end
92
52
 
93
- def backup_enabled?
94
- backup == true
95
- end
53
+ private
96
54
 
97
- def github_pages url
98
- urls = []
99
- uri = url.start_with?("http") ? URI.parse(url) : URI.parse("https://#{url}")
100
- hostname = uri.hostname.sub(/^www\./, '')
101
- urls = [hostname]
102
- if hostname.count(".") < 2
103
- urls << "www.#{hostname}"
104
- end
55
+ def load_defaults
56
+ target :local
105
57
 
106
- target :production do
107
- github_pages url
108
- ssh false
109
- ping(*urls) if urls.any?
58
+ target :gubs do
59
+ ssh "botandrose@cloud.hackett.world:22022",
60
+ path: "Sites/#{config.project_name}"
61
+ url false
110
62
  end
111
63
 
112
- backup false
113
- end
114
-
115
- # CI configuration
116
- def ci(system = nil)
117
- if system.nil?
118
- @ci_system
119
- else
120
- @ci_system = system
64
+ target :ci do
65
+ ssh "jenkins@staging.botandrose.com:22022",
66
+ path: "jobs/#{config.project_name}/workspace"
67
+ url false
121
68
  end
122
- end
123
-
124
- def ci_system
125
- @ci_system
126
- end
127
69
 
128
- def ci_instance(branch)
129
- return nil if @ci_system == false
130
-
131
- require "bard/ci"
132
-
133
- # Use the existing CI class which handles auto-detection
134
- case @ci_system
135
- when :local
136
- CI.new(project_name, branch, local: true)
137
- when :github_actions, :jenkins, nil
138
- # CI class auto-detects between github_actions and jenkins
139
- CI.new(project_name, branch)
140
- when false
141
- nil
142
- else
143
- CI.new(project_name, branch)
70
+ staging_defaults = proc do
71
+ ssh "www@staging.botandrose.com:22022"
72
+ url "#{config.project_name}.botandrose.com"
144
73
  end
145
- end
146
-
147
- private
148
-
149
- # Load default server configurations (v1.x compatible)
150
- def load_defaults
151
- @servers[:local] = Server.new(
152
- project_name,
153
- :local,
154
- false,
155
- "./",
156
- ["#{project_name}.local"],
157
- )
158
- @servers[:gubs] = Server.new(
159
- project_name,
160
- :gubs,
161
- "botandrose@cloud.hackett.world:22022",
162
- "Sites/#{project_name}",
163
- false,
164
- )
165
- @servers[:ci] = Server.new(
166
- project_name,
167
- :ci,
168
- "jenkins@staging.botandrose.com:22022",
169
- "jobs/#{project_name}/workspace",
170
- false,
171
- )
172
- @servers[:staging] = Server.new(
173
- project_name,
174
- :staging,
175
- "www@staging.botandrose.com:22022",
176
- project_name,
177
- ["#{project_name}.botandrose.com"],
178
- )
179
- end
180
- end
181
-
182
- class BackupConfig
183
- attr_reader :destinations
184
-
185
- def initialize(&block)
186
- @destinations = []
187
- instance_eval(&block) if block_given?
188
- end
189
-
190
- def bard
191
- @bard = true
192
- end
193
-
194
- def bard?
195
- !!@bard
196
- end
197
-
198
- def disabled
199
- @disabled = true
200
- end
201
-
202
- def disabled?
203
- !!@disabled
204
- end
205
-
206
- def enabled?
207
- !disabled?
208
- end
209
-
210
- def s3(name, **kwargs)
211
- @destinations << {
212
- name: name,
213
- type: :s3,
214
- **kwargs,
215
- }
216
- end
217
74
 
218
- def self_managed?
219
- @destinations.any?
75
+ target :staging, &staging_defaults
76
+ target :production, &staging_defaults
220
77
  end
221
78
  end
222
79
  end
data/lib/bard/copy.rb CHANGED
@@ -1,120 +1,45 @@
1
- require "uri"
2
- require "bard/command"
3
-
4
1
  module Bard
5
- class Copy < Struct.new(:path, :from, :to, :verbose)
6
- def self.file path, from:, to:, verbose: false
7
- new(path, from, to, verbose).scp
8
- end
9
-
10
- def self.dir path, from:, to:, verbose: false
11
- new(path, from, to, verbose).rsync
12
- end
2
+ class Copy
3
+ @handlers = []
13
4
 
14
- def scp
15
- if from.key == :local
16
- scp_using_local :to, to
17
- elsif to.key == :local
18
- scp_using_local :from, from
19
- else
20
- scp_as_mediator
5
+ class << self
6
+ def inherited(subclass)
7
+ super
8
+ @handlers.unshift(subclass)
21
9
  end
22
- end
23
-
24
- def scp_using_local direction, target_or_server
25
- # Support both new Target (with server attribute) and old Server
26
- ssh_server = target_or_server.respond_to?(:server) ? target_or_server.server : target_or_server
27
-
28
- gateway = ssh_server.gateway ? "-oProxyCommand='ssh #{ssh_server.gateway} -W %h:%p'" : ""
29
10
 
30
- ssh_key = ssh_server.ssh_key ? "-i #{ssh_server.ssh_key}" : ""
31
-
32
- from_and_to = [path, target_or_server.scp_uri(path)]
33
- from_and_to.reverse! if direction == :from
34
-
35
- command = ["scp", gateway, ssh_key, *from_and_to].join(" ")
36
- Bard::Command.run! command, verbose: verbose
37
- end
38
-
39
- def scp_as_mediator
40
- from_server = from.respond_to?(:server) ? from.server : from
41
- to_server = to.respond_to?(:server) ? to.server : to
42
-
43
- raise NotImplementedError if from_server.gateway || to_server.gateway || from_server.ssh_key || to_server.ssh_key
44
- command = "scp -o ForwardAgent=yes #{from.scp_uri(path)} #{to.scp_uri(path)}"
45
- Bard::Command.run! command, verbose: verbose
46
- end
47
-
48
- def rsync
49
- if from.key == :local
50
- rsync_using_local :to, to
51
- elsif to.key == :local
52
- rsync_using_local :from, from
53
- else
54
- rsync_as_mediator
11
+ def file(path, from:, to:, verbose: false)
12
+ handler_for!(from, to).new(path, from, to, verbose).file
55
13
  end
56
- end
57
-
58
- def rsync_using_local direction, target_or_server
59
- # Support both new Target (with server attribute) and old Server
60
- ssh_server = target_or_server.respond_to?(:server) ? target_or_server.server : target_or_server
61
14
 
62
- # Get ssh_uri - it might be a URI object (old Server), string (new SSHServer), or mock
63
- ssh_uri_value = ssh_server.respond_to?(:ssh_uri) ? ssh_server.ssh_uri : nil
64
- if ssh_uri_value.respond_to?(:port)
65
- # Already a URI-like object (old Server or mock)
66
- ssh_uri = ssh_uri_value
67
- elsif ssh_uri_value.is_a?(String)
68
- # String from new SSHServer
69
- ssh_uri = URI("ssh://#{ssh_uri_value}")
70
- else
71
- # Fallback
72
- ssh_uri = ssh_uri_value
15
+ def dir(path, from:, to:, verbose: false)
16
+ handler_for!(from, to).new(path, from, to, verbose).dir
73
17
  end
74
18
 
75
- gateway = ssh_server.gateway ? "-oProxyCommand=\"ssh #{ssh_server.gateway} -W %h:%p\"" : ""
19
+ private
76
20
 
77
- ssh_key = ssh_server.ssh_key ? "-i #{ssh_server.ssh_key}" : ""
78
- ssh = "-e'ssh #{gateway} -p#{ssh_uri.port || 22}'"
79
-
80
- from_and_to = ["./#{path}", target_or_server.rsync_uri(path)]
81
- from_and_to.reverse! if direction == :from
82
- from_and_to[-1].sub! %r(/[^/]+$), '/'
83
-
84
- command = "rsync #{ssh} --delete --info=progress2 -az #{from_and_to.join(" ")}"
85
- Bard::Command.run! command, verbose: verbose
21
+ def handler_for!(from, to)
22
+ handler = @handlers.find { |h| h.can_handle?(from, to) }
23
+ raise "No copy handler for #{from.key} -> #{to.key}" unless handler
24
+ handler
25
+ end
86
26
  end
87
27
 
88
- def rsync_as_mediator
89
- from_server = from.respond_to?(:server) ? from.server : from
90
- to_server = to.respond_to?(:server) ? to.server : to
28
+ attr_reader :path, :from, :to, :verbose
91
29
 
92
- raise NotImplementedError if from_server.gateway || to_server.gateway || from_server.ssh_key || to_server.ssh_key
93
-
94
- # Get ssh_uri - it might be a URI object (old Server), string (new SSHServer), or mock
95
- from_uri_value = from_server.respond_to?(:ssh_uri) ? from_server.ssh_uri : nil
96
- if from_uri_value.respond_to?(:port)
97
- from_uri = from_uri_value
98
- elsif from_uri_value.is_a?(String)
99
- from_uri = URI("ssh://#{from_uri_value}")
100
- else
101
- from_uri = from_uri_value
102
- end
103
-
104
- to_uri_value = to_server.respond_to?(:ssh_uri) ? to_server.ssh_uri : nil
105
- if to_uri_value.respond_to?(:port)
106
- to_uri = to_uri_value
107
- elsif to_uri_value.is_a?(String)
108
- to_uri = URI("ssh://#{to_uri_value}")
109
- else
110
- to_uri = to_uri_value
111
- end
30
+ def initialize(path, from, to, verbose)
31
+ @path = path
32
+ @from = from
33
+ @to = to
34
+ @verbose = verbose
35
+ end
112
36
 
113
- from_str = "-p#{from_uri.port || 22} #{from_uri.user}@#{from_uri.host}"
114
- to_str = to.rsync_uri(path).sub(%r(/[^/]+$), '/')
37
+ def file
38
+ raise NotImplementedError
39
+ end
115
40
 
116
- command = %(ssh -A #{from_str} 'rsync -e \"ssh -A -p#{to_uri.port || 22} -o StrictHostKeyChecking=no\" --delete --info=progress2 -az #{from.path}/#{path} #{to_str}')
117
- Bard::Command.run! command, verbose: verbose
41
+ def dir
42
+ raise NotImplementedError
118
43
  end
119
44
  end
120
45
  end
@@ -0,0 +1,56 @@
1
+ require "bard/command"
2
+ require "bard/copy"
3
+ require "bard/plugins/ssh"
4
+ require "bard/plugins/url"
5
+
6
+ class Bard::CLI
7
+ option :from, default: "production"
8
+ option :to, default: "local"
9
+ desc "data --from=production --to=local", "copy database and assets from from to to"
10
+ def data
11
+ from = config[options[:from]]
12
+ to = config[options[:to]]
13
+
14
+ from.require_capability!(:ssh) unless from.key == :local
15
+ to.require_capability!(:ssh) unless to.key == :local
16
+
17
+ if to.key == :production
18
+ url = to.url
19
+ puts yellow "WARNING: You are about to push data to production, overwriting everything that is there!"
20
+ answer = ask("If you really want to do this, please type in the full HTTPS url of the production server:")
21
+ if answer != url
22
+ puts red("!!! ") + "Failed! We expected #{url}. Is this really where you want to overwrite all the data?"
23
+ exit 1
24
+ end
25
+ end
26
+
27
+ puts "Dumping #{from.key} database to file..."
28
+ from.run! "bin/rake db:dump"
29
+
30
+ puts "Transfering file from #{from.key} to #{to.key}..."
31
+ Bard::Copy.file "db/data.sql.gz", from: from, to: to, verbose: true
32
+
33
+ puts "Loading file into #{to.key} database..."
34
+ to.run! "bin/rake db:load"
35
+
36
+ config.data.each do |path|
37
+ puts "Synchronizing files in #{path}..."
38
+ Bard::Copy.dir path, from: from, to: to, verbose: true
39
+ end
40
+ rescue Bard::Command::Error => e
41
+ puts red("!!! ") + "Running command failed: #{yellow(e.message)}"
42
+ exit 1
43
+ end
44
+ end
45
+
46
+ require "bard/config"
47
+
48
+ class Bard::Config
49
+ def data(*paths)
50
+ if paths.empty?
51
+ @data_paths ||= []
52
+ else
53
+ @data_paths = paths
54
+ end
55
+ end
56
+ end
@@ -1,13 +1,12 @@
1
1
  require "time"
2
- require "bard/github"
3
- require "bard/ci/runner"
2
+ require "bard/plugins/github"
3
+ require "bard/plugins/deploy/ci/runner"
4
4
 
5
5
  module Bard
6
6
  class CI
7
7
  class GithubActions < Runner
8
-
9
8
  def exists?
10
- true
9
+ File.exist?(".github/workflows/ci.yml")
11
10
  end
12
11
 
13
12
  def console
@@ -0,0 +1,176 @@
1
+ require "json"
2
+ require "bard/plugins/deploy/ci/runner"
3
+ require "bard/secrets"
4
+
5
+ module Bard
6
+ class CI
7
+ class Jenkins < Runner
8
+ def exists?
9
+ `curl -s -I #{ci_host}/` =~ /\b200 OK\b/ or create!
10
+ end
11
+
12
+ private def create!
13
+ git_url = `git remote get-url origin`.strip
14
+ config = JOB_CONFIG_XML.sub("GIT_URL", git_url)
15
+ if File.exist?("config/master.key")
16
+ master_key = File.read("config/master.key").strip
17
+ master_key_step = <<~XML.chomp
18
+ <hudson.tasks.Shell>
19
+ <command>echo #{master_key} > config/master.key</command>
20
+ </hudson.tasks.Shell>
21
+
22
+ XML
23
+ config = config.sub("<builders>\n", "<builders>\n #{master_key_step}")
24
+ end
25
+ `curl -s -X POST "http://#{auth}@ci.botandrose.com/createItem?name=#{project_name}" -H "Content-Type: application/xml" -d '#{config}'`
26
+ end
27
+
28
+ def console
29
+ raw = `curl -s #{ci_host}/lastBuild/console`
30
+ raw[%r{<pre.*?>(.+)</pre>}m, 1]
31
+ end
32
+
33
+ attr_accessor :last_response
34
+
35
+ protected
36
+
37
+ def wait_until_started
38
+ sleep(2) until started?
39
+ end
40
+
41
+ def start
42
+ command = "curl -s -I -X POST -L '#{ci_host}/buildWithParameters?GIT_REF=#{branch}'"
43
+ output = `#{command}`
44
+ @queueId = output[%r{Location: .+/queue/item/(\d+)/}, 1].to_i
45
+ end
46
+
47
+ def building?
48
+ retry_with_backoff do
49
+ self.last_response = `curl -s #{ci_host}/#{job_id}/api/json?tree=building,result`
50
+ raise "Blank response from CI" if last_response.empty?
51
+ end
52
+ last_response.include? '"building":true'
53
+ end
54
+
55
+ def success?
56
+ last_response.include? '"result":"SUCCESS"'
57
+ end
58
+
59
+ def get_state_data
60
+ {
61
+ "project_name" => project_name,
62
+ "branch" => branch,
63
+ "queue_id" => @queueId,
64
+ "job_id" => @job_id,
65
+ "start_time" => @start_time,
66
+ "last_time_elapsed" => @last_time_elapsed
67
+ }
68
+ end
69
+
70
+ def restore_state(data)
71
+ @queueId = data["queue_id"]
72
+ @job_id = data["job_id"]
73
+ @start_time = data["start_time"]
74
+ @last_time_elapsed = data["last_time_elapsed"]
75
+ end
76
+
77
+ private
78
+
79
+ def get_last_time_elapsed
80
+ response = retry_with_backoff do
81
+ response = `curl -s #{ci_host}/lastStableBuild/api/xml`
82
+ raise "Blank response from CI" if response.empty?
83
+ response
84
+ end
85
+ response.match(/<duration>(\d+)<\/duration>/)
86
+ $1 ? $1.to_i / 1000 : nil
87
+ rescue => e
88
+ puts " Warning: Could not get last build duration: #{e.message}"
89
+ nil
90
+ end
91
+
92
+ def auth
93
+ @auth ||= "#{Bard::Secrets.fetch("jenkins-user")}:#{Bard::Secrets.fetch("jenkins-token")}"
94
+ end
95
+
96
+ def ci_host
97
+ "http://#{auth}@ci.botandrose.com/job/#{project_name}"
98
+ end
99
+
100
+ def started?
101
+ retry_with_backoff do
102
+ command = "curl -s -g '#{ci_host}/api/json?depth=1&tree=builds[queueId,number]'"
103
+ output = `#{command}`
104
+ raise "Blank response from CI" if output.empty?
105
+ builds = JSON.parse(output)["builds"]
106
+ raise "Build not found in builds list" if builds.empty?
107
+ builds.first["queueId"] == @queueId
108
+ end
109
+ end
110
+
111
+ def job_id
112
+ @job_id ||= begin
113
+ retry_with_backoff do
114
+ output = `curl -s -g '#{ci_host}/api/json?depth=1&tree=builds[queueId,number]'`
115
+ raise "Blank response from CI" if output.empty?
116
+ builds = JSON.parse(output)["builds"]
117
+ build = builds.find { |b| b["queueId"] == @queueId }
118
+ build["number"]
119
+ end
120
+ end
121
+ end
122
+ JOB_CONFIG_XML = <<~XML
123
+ <?xml version="1.0" encoding="UTF-8"?>
124
+ <project>
125
+ <actions/>
126
+ <description></description>
127
+ <keepDependencies>false</keepDependencies>
128
+ <properties>
129
+ <hudson.model.ParametersDefinitionProperty>
130
+ <parameterDefinitions>
131
+ <hudson.model.StringParameterDefinition>
132
+ <name>GIT_REF</name>
133
+ <description></description>
134
+ <defaultValue>master</defaultValue>
135
+ </hudson.model.StringParameterDefinition>
136
+ </parameterDefinitions>
137
+ </hudson.model.ParametersDefinitionProperty>
138
+ </properties>
139
+ <scm class="hudson.plugins.git.GitSCM" plugin="git@3.3.0">
140
+ <configVersion>2</configVersion>
141
+ <userRemoteConfigs>
142
+ <hudson.plugins.git.UserRemoteConfig>
143
+ <url>GIT_URL</url>
144
+ </hudson.plugins.git.UserRemoteConfig>
145
+ </userRemoteConfigs>
146
+ <branches>
147
+ <hudson.plugins.git.BranchSpec>
148
+ <name>$GIT_REF</name>
149
+ </hudson.plugins.git.BranchSpec>
150
+ </branches>
151
+ <doGenerateSubmoduleConfigurations>false</doGenerateSubmoduleConfigurations>
152
+ <submoduleCfg class="list"/>
153
+ <extensions/>
154
+ </scm>
155
+ <canRoam>true</canRoam>
156
+ <disabled>false</disabled>
157
+ <blockBuildWhenDownstreamBuilding>false</blockBuildWhenDownstreamBuilding>
158
+ <blockBuildWhenUpstreamBuilding>false</blockBuildWhenUpstreamBuilding>
159
+ <triggers/>
160
+ <concurrentBuild>false</concurrentBuild>
161
+ <builders>
162
+ <hudson.tasks.Shell>
163
+ <command>bash -l -c bin/setup</command>
164
+ </hudson.tasks.Shell>
165
+ <hudson.tasks.Shell>
166
+ <command>bash -l -c bin/ci</command>
167
+ </hudson.tasks.Shell>
168
+ </builders>
169
+ <publishers/>
170
+ <buildWrappers/>
171
+ </project>
172
+ XML
173
+ end
174
+ end
175
+ end
176
+
@@ -1,10 +1,9 @@
1
- require "open3"
2
- require "bard/ci/runner"
1
+ require "tempfile"
2
+ require "bard/plugins/deploy/ci/runner"
3
3
 
4
4
  module Bard
5
5
  class CI
6
6
  class Local < Runner
7
-
8
7
  def exists?
9
8
  true
10
9
  end
@@ -16,7 +15,8 @@ module Bard
16
15
  protected
17
16
 
18
17
  def start
19
- @stdin, @stdout_and_stderr, @wait_thread = Open3.popen2e("CLEAN=true bin/rake ci")
18
+ @output_file = Tempfile.new("bard-ci")
19
+ @wait_thread = Process.detach(spawn("CLEAN=true bin/rake ci", [:out, :err] => @output_file))
20
20
  end
21
21
 
22
22
  def building?
@@ -47,9 +47,9 @@ module Bard
47
47
  sleep(2)
48
48
  end
49
49
 
50
- @stdin.close
51
- @console = @stdout_and_stderr.read
52
- @stdout_and_stderr.close
50
+ @output_file.rewind
51
+ @console = @output_file.read
52
+ @output_file.close!
53
53
  end
54
54
  end
55
55
  end