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"
- SkipDefaultOption =
Object.new
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.
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
# File 'lib/hatchet/app.rb', line 26 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
62 63 64 |
# File 'lib/hatchet/app.rb', line 62 def self.config @config ||= Config.new end |
.default_buildpack ⇒ Object
53 54 55 |
# File 'lib/hatchet/app.rb', line 53 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
100 101 102 103 104 105 106 107 |
# File 'lib/hatchet/app.rb', line 100 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
57 58 59 |
# File 'lib/hatchet/app.rb', line 57 def allow_failure? @allow_failure end |
#api_key ⇒ Object
261 262 263 |
# File 'lib/hatchet/app.rb', line 261 def api_key @api_key ||= ENV['HEROKU_API_KEY'] || bundle_exec {`heroku auth:token`.chomp } end |
#api_rate_limit ⇒ Object
326 327 328 329 |
# File 'lib/hatchet/app.rb', line 326 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
184 185 186 187 188 189 |
# File 'lib/hatchet/app.rb', line 184 def before_deploy(&block) raise "block required" unless block @before_deploy = block self end |
#commit! ⇒ Object
191 192 193 |
# File 'lib/hatchet/app.rb', line 191 def commit! local_cmd_exec!('git add .; git commit -m next') end |
#config ⇒ Object
66 67 68 |
# File 'lib/hatchet/app.rb', line 66 def config self.class.config end |
#create_app ⇒ Object
148 149 150 151 152 153 154 155 156 157 158 159 160 |
# File 'lib/hatchet/app.rb', line 148 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
298 299 300 |
# File 'lib/hatchet/app.rb', line 298 def create_pipeline api_rate_limit.call.pipeline.create(name: @name) end |
#create_source ⇒ Object
307 308 309 310 311 312 313 314 |
# File 'lib/hatchet/app.rb', line 307 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
133 134 135 |
# File 'lib/hatchet/app.rb', line 133 def debug? @debug || ENV['HATCHET_DEBUG'] || false end |
#delete_pipeline(pipeline_id) ⇒ Object
316 317 318 |
# File 'lib/hatchet/app.rb', line 316 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
225 226 227 228 229 230 231 232 233 |
# File 'lib/hatchet/app.rb', line 225 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
143 144 145 146 |
# File 'lib/hatchet/app.rb', line 143 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
77 78 79 80 |
# File 'lib/hatchet/app.rb', line 77 def get_config # heroku.get_config_vars(name).body api_rate_limit.call.config_var.info_for_app(name) end |
#get_labs ⇒ Object
86 87 88 89 |
# File 'lib/hatchet/app.rb', line 86 def get_labs # heroku.get_features(name).body api_rate_limit.call.app_feature.list(name) end |
#heroku ⇒ Object
265 266 267 |
# File 'lib/hatchet/app.rb', line 265 def heroku raise "Not supported, use `platform_api` instead." end |
#in_directory(directory = self.directory) ⇒ Object
208 209 210 211 212 213 214 215 216 217 218 219 |
# File 'lib/hatchet/app.rb', line 208 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
82 83 84 |
# File 'lib/hatchet/app.rb', line 82 def lab_is_installed?(lab) get_labs.any? {|hash| hash["name"] == lab } end |
#not_debugging? ⇒ Boolean Also known as: no_debug?
138 139 140 |
# File 'lib/hatchet/app.rb', line 138 def not_debugging? !debug? end |
#output ⇒ Object
257 258 259 |
# File 'lib/hatchet/app.rb', line 257 def output @output end |
#pipeline_id ⇒ Object
294 295 296 |
# File 'lib/hatchet/app.rb', line 294 def pipeline_id @pipeline_id end |
#platform_api ⇒ Object
320 321 322 323 324 |
# File 'lib/hatchet/app.rb', line 320 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
235 236 237 238 239 240 241 242 243 244 245 |
# File 'lib/hatchet/app.rb', line 235 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
195 196 197 |
# File 'lib/hatchet/app.rb', line 195 def push_without_retry! raise NotImplementedError end |
#retry_error_message(error, attempt, max_retries) ⇒ Object
250 251 252 253 254 255 |
# File 'lib/hatchet/app.rb', line 250 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
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 |
# File 'lib/hatchet/app.rb', line 111 def run(cmd_type, command = nil, = {}, &block) command = cmd_type.to_s if command.nil? = { "app" => name, "exit-code" => nil } = (.merge(.delete(:heroku) || {})).map do |k,v| next if v == Hatchet::App::SkipDefaultOption # for forcefully removing e.g. --exit-code, a user can pass this arg = "--#{k.to_s.shellescape}" arg << "=#{v.to_s.shellescape}" unless v.nil? # nil means we include the option without an argument arg end.join(" ") heroku_command = "heroku run #{} -- #{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
269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 |
# File 'lib/hatchet/app.rb', line 269 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
70 71 72 73 74 75 |
# File 'lib/hatchet/app.rb', line 70 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
95 96 97 98 |
# File 'lib/hatchet/app.rb', line 95 def set_lab(lab) # heroku.post_feature(lab, name) api_rate_limit.call.app_feature.update(name, lab, enabled: true) end |
#set_labs! ⇒ Object
91 92 93 |
# File 'lib/hatchet/app.rb', line 91 def set_labs! @labs.each {|lab| set_lab(lab) } end |
#setup! ⇒ Object Also known as: setup
creates a new heroku app via the API
168 169 170 171 172 173 174 175 176 177 178 179 180 181 |
# File 'lib/hatchet/app.rb', line 168 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
302 303 304 305 |
# File 'lib/hatchet/app.rb', line 302 def source_get_url create_source @source_get_url end |
#teardown! ⇒ Object
199 200 201 202 203 204 205 206 |
# File 'lib/hatchet/app.rb', line 199 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
162 163 164 165 |
# File 'lib/hatchet/app.rb', line 162 def update_stack(stack_name) @stack = stack_name api_rate_limit.call.app.update(name, build_stack: @stack) end |