Class: Hatchet::App
- Inherits:
-
Object
- Object
- Hatchet::App
- Defined in:
- lib/hatchet/app.rb
Defined Under Namespace
Classes: FailedDeploy
Constant Summary collapse
- HATCHET_BUILDPACK_BASE =
(ENV['HATCHET_BUILDPACK_BASE'] || "https://github.com/heroku/heroku-buildpack-ruby.git")
- HATCHET_BUILDPACK_BRANCH =
-> { ENV['HATCHET_BUILDPACK_BRANCH'] || ENV['HEROKU_TEST_RUN_BRANCH'] || Hatchet.git_branch }
- BUILDPACK_URL =
"https://github.com/heroku/heroku-buildpack-ruby.git"
Instance Attribute Summary collapse
-
#directory ⇒ Object
readonly
Returns the value of attribute directory.
-
#name ⇒ Object
readonly
Returns the value of attribute name.
-
#repo_name ⇒ Object
readonly
Returns the value of attribute repo_name.
-
#stack ⇒ Object
readonly
Returns the value of attribute stack.
Class Method Summary collapse
-
.config ⇒ Object
config is read only, should be threadsafe.
- .default_buildpack ⇒ Object
Instance Method Summary collapse
- #add_database(plan_name = 'heroku-postgresql:dev', match_val = "HEROKU_POSTGRESQL_[A-Z]+_URL") ⇒ Object
- #allow_failure? ⇒ Boolean
- #api_key ⇒ Object
- #api_rate_limit ⇒ Object
- #before_deploy(&block) ⇒ Object
- #commit! ⇒ Object
- #config ⇒ Object
- #create_app ⇒ Object
- #create_pipeline ⇒ Object
- #create_source ⇒ Object
-
#debug? ⇒ Boolean
(also: #debugging?)
set debug: true when creating app if you don’t want it to be automatically destroyed, useful for debugging…bad for app limits.
- #delete_pipeline(pipeline_id) ⇒ Object
-
#deploy(&block) ⇒ Object
creates a new app on heroku, “pushes” via anvil or git then yields to self so you can call self.run or self.deployed? Allow deploy failures on CI server by setting ENV.
- #deployed? ⇒ Boolean
- #get_config ⇒ Object
- #get_labs ⇒ Object
- #heroku ⇒ Object
- #in_directory(directory = self.directory) ⇒ Object
-
#initialize(repo_name, stack: "", name: default_name, debug: nil, debugging: nil, allow_failure: false, labs: [], buildpack: nil, buildpacks: nil, buildpack_url: nil, before_deploy: nil, config: {}) ⇒ App
constructor
A new instance of App.
- #lab_is_installed?(lab) ⇒ Boolean
- #not_debugging? ⇒ Boolean (also: #no_debug?)
- #output ⇒ Object
- #pipeline_id ⇒ Object
- #platform_api ⇒ Object
- #push ⇒ Object (also: #push!, #push_with_retry)
- #push_without_retry! ⇒ Object
- #retry_error_message(error, attempt, max_retries) ⇒ Object
-
#run(cmd_type, command = nil, options = {}, &block) ⇒ Object
runs a command on heroku similar to ‘$ heroku run #foo` but programatically and with more control.
- #run_ci(timeout: 300, &block) ⇒ Object
- #set_config(options = {}) ⇒ Object
- #set_lab(lab) ⇒ Object
- #set_labs! ⇒ Object
-
#setup! ⇒ Object
(also: #setup)
creates a new heroku app via the API.
- #source_get_url ⇒ Object
- #teardown! ⇒ Object
- #update_stack(stack_name) ⇒ Object
Constructor Details
#initialize(repo_name, stack: "", name: default_name, debug: nil, debugging: nil, allow_failure: false, labs: [], buildpack: nil, buildpacks: nil, buildpack_url: nil, before_deploy: nil, config: {}) ⇒ App
Returns a new instance of App.
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
# File 'lib/hatchet/app.rb', line 24 def initialize(repo_name, stack: "", name: default_name, debug: nil, debugging: nil, allow_failure: false, labs: [], buildpack: nil, buildpacks: nil, buildpack_url: nil, before_deploy: nil, config: {} ) @repo_name = repo_name @directory = self.config.path_for_name(@repo_name) @name = name @stack = stack @debug = debug || debugging @allow_failure = allow_failure @labs = ([] << labs).flatten.compact @buildpacks = buildpack || buildpacks || buildpack_url || self.class.default_buildpack @buildpacks = Array(@buildpacks) @before_deploy = before_deploy @app_config = config @reaper = Reaper.new(api_rate_limit: api_rate_limit) end |
Instance Attribute Details
#directory ⇒ Object (readonly)
Returns the value of attribute directory.
12 13 14 |
# File 'lib/hatchet/app.rb', line 12 def directory @directory end |
#name ⇒ Object (readonly)
Returns the value of attribute name.
12 13 14 |
# File 'lib/hatchet/app.rb', line 12 def name @name end |
#repo_name ⇒ Object (readonly)
Returns the value of attribute repo_name.
12 13 14 |
# File 'lib/hatchet/app.rb', line 12 def repo_name @repo_name end |
#stack ⇒ Object (readonly)
Returns the value of attribute stack.
12 13 14 |
# File 'lib/hatchet/app.rb', line 12 def stack @stack end |
Class Method Details
.config ⇒ Object
config is read only, should be threadsafe
60 61 62 |
# File 'lib/hatchet/app.rb', line 60 def self.config @config ||= Config.new end |
.default_buildpack ⇒ Object
51 52 53 |
# File 'lib/hatchet/app.rb', line 51 def self.default_buildpack [HATCHET_BUILDPACK_BASE, HATCHET_BUILDPACK_BRANCH.call].join("#") end |
Instance Method Details
#add_database(plan_name = 'heroku-postgresql:dev', match_val = "HEROKU_POSTGRESQL_[A-Z]+_URL") ⇒ Object
98 99 100 101 102 103 104 105 |
# File 'lib/hatchet/app.rb', line 98 def add_database(plan_name = 'heroku-postgresql:dev', match_val = "HEROKU_POSTGRESQL_[A-Z]+_URL") Hatchet::RETRIES.times.retry do # heroku.post_addon(name, plan_name) api_rate_limit.call.addon.create(name, plan: plan_name ) _, value = get_config.detect {|k, v| k.match(/#{match_val}/) } set_config('DATABASE_URL' => value) end end |
#allow_failure? ⇒ Boolean
55 56 57 |
# File 'lib/hatchet/app.rb', line 55 def allow_failure? @allow_failure end |
#api_key ⇒ Object
257 258 259 |
# File 'lib/hatchet/app.rb', line 257 def api_key @api_key ||= ENV['HEROKU_API_KEY'] || bundle_exec {`heroku auth:token`.chomp } end |
#api_rate_limit ⇒ Object
322 323 324 325 |
# File 'lib/hatchet/app.rb', line 322 def api_rate_limit @platform_api ||= PlatformAPI.connect_oauth(api_key, cache: Moneta.new(:Null)) @api_rate_limit ||= ApiRateLimit.new(@platform_api) end |
#before_deploy(&block) ⇒ Object
180 181 182 183 184 185 |
# File 'lib/hatchet/app.rb', line 180 def before_deploy(&block) raise "block required" unless block @before_deploy = block self end |
#commit! ⇒ Object
187 188 189 |
# File 'lib/hatchet/app.rb', line 187 def commit! local_cmd_exec!('git add .; git commit -m next') end |
#config ⇒ Object
64 65 66 |
# File 'lib/hatchet/app.rb', line 64 def config self.class.config end |
#create_app ⇒ Object
144 145 146 147 148 149 150 151 152 153 154 155 156 |
# File 'lib/hatchet/app.rb', line 144 def create_app 3.times.retry do begin # heroku.post_app({ name: name, stack: stack }.delete_if {|k,v| v.nil? }) hash = { name: name, stack: stack } hash.delete_if { |k,v| v.nil? } api_rate_limit.call.app.create(hash) rescue => e @reaper.cycle raise e end end end |
#create_pipeline ⇒ Object
294 295 296 |
# File 'lib/hatchet/app.rb', line 294 def create_pipeline api_rate_limit.call.pipeline.create(name: @name) end |
#create_source ⇒ Object
303 304 305 306 307 308 309 310 |
# File 'lib/hatchet/app.rb', line 303 def create_source @create_source ||= begin result = api_rate_limit.call.source.create @source_get_url = result["source_blob"]["get_url"] @source_put_url = result["source_blob"]["put_url"] @source_put_url end end |
#debug? ⇒ Boolean Also known as: debugging?
set debug: true when creating app if you don’t want it to be automatically destroyed, useful for debugging…bad for app limits. turn on global debug by setting HATCHET_DEBUG=true in the env
129 130 131 |
# File 'lib/hatchet/app.rb', line 129 def debug? @debug || ENV['HATCHET_DEBUG'] || false end |
#delete_pipeline(pipeline_id) ⇒ Object
312 313 314 |
# File 'lib/hatchet/app.rb', line 312 def delete_pipeline(pipeline_id) api_rate_limit.call.pipeline.delete(pipeline_id) end |
#deploy(&block) ⇒ Object
creates a new app on heroku, “pushes” via anvil or git then yields to self so you can call self.run or self.deployed? Allow deploy failures on CI server by setting ENV
221 222 223 224 225 226 227 228 229 |
# File 'lib/hatchet/app.rb', line 221 def deploy(&block) in_directory do self.setup! self.push_with_retry! block.call(self, api_rate_limit.call, output) if block_given? end ensure self.teardown! end |
#deployed? ⇒ Boolean
139 140 141 142 |
# File 'lib/hatchet/app.rb', line 139 def deployed? # !heroku.get_ps(name).body.detect {|ps| ps["process"].include?("web") }.nil? api_rate_limit.call.formation.list(name).detect {|ps| ps["type"] == "web"} end |
#get_config ⇒ Object
75 76 77 78 |
# File 'lib/hatchet/app.rb', line 75 def get_config # heroku.get_config_vars(name).body api_rate_limit.call.config_var.info_for_app(name) end |
#get_labs ⇒ Object
84 85 86 87 |
# File 'lib/hatchet/app.rb', line 84 def get_labs # heroku.get_features(name).body api_rate_limit.call.app_feature.list(name) end |
#heroku ⇒ Object
261 262 263 |
# File 'lib/hatchet/app.rb', line 261 def heroku raise "Not supported, use `platform_api` instead." end |
#in_directory(directory = self.directory) ⇒ Object
204 205 206 207 208 209 210 211 212 213 214 215 |
# File 'lib/hatchet/app.rb', line 204 def in_directory(directory = self.directory) yield directory and return if @already_in_dir Dir.mktmpdir do |tmpdir| FileUtils.cp_r("#{directory}/.", "#{tmpdir}/.") Dir.chdir(tmpdir) do @already_in_dir = true yield directory @already_in_dir = false end end end |
#lab_is_installed?(lab) ⇒ Boolean
80 81 82 |
# File 'lib/hatchet/app.rb', line 80 def lab_is_installed?(lab) get_labs.any? {|hash| hash["name"] == lab } end |
#not_debugging? ⇒ Boolean Also known as: no_debug?
134 135 136 |
# File 'lib/hatchet/app.rb', line 134 def not_debugging? !debug? end |
#output ⇒ Object
253 254 255 |
# File 'lib/hatchet/app.rb', line 253 def output @output end |
#pipeline_id ⇒ Object
290 291 292 |
# File 'lib/hatchet/app.rb', line 290 def pipeline_id @pipeline_id end |
#platform_api ⇒ Object
316 317 318 319 320 |
# File 'lib/hatchet/app.rb', line 316 def platform_api puts "Deprecated: use `api_rate_limit.call` instead of platform_api" api_rate_limit return @platform_api end |
#push ⇒ Object Also known as: push!, push_with_retry
231 232 233 234 235 236 237 238 239 240 241 |
# File 'lib/hatchet/app.rb', line 231 def push max_retries = @allow_failure ? 1 : RETRIES max_retries.times.retry do |attempt| begin @output = self.push_without_retry! rescue StandardError => error puts (error, attempt, max_retries) raise error end end end |
#push_without_retry! ⇒ Object
191 192 193 |
# File 'lib/hatchet/app.rb', line 191 def push_without_retry! raise NotImplementedError end |
#retry_error_message(error, attempt, max_retries) ⇒ Object
246 247 248 249 250 251 |
# File 'lib/hatchet/app.rb', line 246 def (error, attempt, max_retries) attempt += 1 return "" if attempt == max_retries msg = "\nRetrying failed Attempt ##{attempt}/#{max_retries} to push for '#{name}' due to error: \n"<< "#{error.class} #{error.}\n #{error.backtrace.join("\n ")}" end |
#run(cmd_type, command = nil, options = {}, &block) ⇒ Object
runs a command on heroku similar to ‘$ heroku run #foo` but programatically and with more control
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
# File 'lib/hatchet/app.rb', line 109 def run(cmd_type, command = nil, = {}, &block) command = cmd_type.to_s if command.nil? = (.delete(:heroku) || {}).map do |k,v| arg = "--#{k.to_s.shellescape}" arg << "=#{v.to_s.shellescape}" unless v.nil? arg end.join(" ") heroku_command = "heroku run -a #{name} #{} --exit-code -- #{command}" bundle_exec do if block_given? ReplRunner.new(cmd_type, heroku_command, ).run(&block) else `#{heroku_command}` end end end |
#run_ci(timeout: 300, &block) ⇒ Object
265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 |
# File 'lib/hatchet/app.rb', line 265 def run_ci(timeout: 300, &block) Hatchet::RETRIES.times.retry do result = create_pipeline @pipeline_id = result["id"] end # create_app # platform_api.pipeline_coupling.create(app: name, pipeline: @pipeline_id, stage: "development") test_run = TestRun.new( token: api_key, buildpacks: @buildpacks, timeout: timeout, app: self, pipeline: @pipeline_id, api_rate_limit: api_rate_limit ) Hatchet::RETRIES.times.retry do test_run.create_test_run end test_run.wait!(&block) ensure delete_pipeline(@pipeline_id) if @pipeline_id end |
#set_config(options = {}) ⇒ Object
68 69 70 71 72 73 |
# File 'lib/hatchet/app.rb', line 68 def set_config( = {}) .each do |key, value| # heroku.put_config_vars(name, key => value) api_rate_limit.call.config_var.update(name, key => value) end end |
#set_lab(lab) ⇒ Object
93 94 95 96 |
# File 'lib/hatchet/app.rb', line 93 def set_lab(lab) # heroku.post_feature(lab, name) api_rate_limit.call.app_feature.update(name, lab, enabled: true) end |
#set_labs! ⇒ Object
89 90 91 |
# File 'lib/hatchet/app.rb', line 89 def set_labs! @labs.each {|lab| set_lab(lab) } end |
#setup! ⇒ Object Also known as: setup
creates a new heroku app via the API
164 165 166 167 168 169 170 171 172 173 174 175 176 177 |
# File 'lib/hatchet/app.rb', line 164 def setup! return self if @app_is_setup puts "Hatchet setup: #{name.inspect} for #{repo_name.inspect}" create_git_repo! unless is_git_repo? create_app set_labs! buildpack_list = @buildpacks.map { |pack| { buildpack: pack } } api_rate_limit.call.buildpack_installation.update(name, updates: buildpack_list) set_config @app_config call_before_deploy @app_is_setup = true self end |
#source_get_url ⇒ Object
298 299 300 301 |
# File 'lib/hatchet/app.rb', line 298 def source_get_url create_source @source_get_url end |
#teardown! ⇒ Object
195 196 197 198 199 200 201 202 |
# File 'lib/hatchet/app.rb', line 195 def teardown! return false unless @app_is_setup if debugging? puts "Debugging App:#{name}" return false end @reaper.cycle end |
#update_stack(stack_name) ⇒ Object
158 159 160 161 |
# File 'lib/hatchet/app.rb', line 158 def update_stack(stack_name) @stack = stack_name api_rate_limit.call.app.update(name, build_stack: @stack) end |