bub_bot 0.2.1

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: fcd0e50d99abae7189edd2f1f6ae9c7b12631784
4
+ data.tar.gz: ab061acc31daff26688c897240d3808e1ae389fc
5
+ SHA512:
6
+ metadata.gz: 91b56fd6e73b9c1fb7793a289c72a1bccd49eae9e884be248626040198c6933aa81cbff293ebda8ae3dfa553110619f4a3a6424b8e5282684a6da1c02c5ea8e4
7
+ data.tar.gz: a352763574e9182999139871426a202cbc28dab42d83d3ecc94243f48216ec86081327ee131dda544148bd6029ab4081278f204299a889b54b24fd88f3e63a39
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ test_config.yml
11
+ deploy_web.sh
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.3.1
5
+ before_install: gem install bundler -v 1.13.6
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at kevin@kevinkuchta.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [http://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: http://contributor-covenant.org
74
+ [version]: http://contributor-covenant.org/version/1/4/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in bub_bot.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Kevin Kuchta
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,45 @@
1
+ # Dev instructions
2
+
3
+ These are just notes to myself. //TODO: rewrite this section so it's useful to people who are not me.
4
+
5
+ Use ec2tunnel alias to set up a tunnel (it's already rigged up to slack on the ktest team)
6
+ `redis-server --save "" --appendonly no`
7
+ `bundle exec bin/bub_bot test_config.yml`
8
+
9
+ Slack setup:
10
+ - Creat an app
11
+ - Add a bot user
12
+ - Add an event subscription
13
+ - Use the *bot event* for messages.channel
14
+ - Add an incoming webhook
15
+ <!--- Add event subscription-->
16
+ <!--- Enter hostname (verification should happen automatically now)-->
17
+ <!--- Add messages.channel permission-->
18
+ - More notes on this in test_config.yml. Before I ship all this I'll need to test out the slack setup instructions.
19
+
20
+ # Questions
21
+ - What's the syntax for deploying different branches to different targets?
22
+ `bub deploy burrito, kk_web_change to web, kk_core_change to core` maybe? Ideally something simpler.
23
+ - We'll want a shorthand for deploying the same branch to both (`bub deploy burrito kk_small_fixes to both`). Or maybe not.
24
+ - If you leave one off, does it deploy develop, or maybe it doesn't deploy to that branch at all? eg `bub deploy kk_small_fixes to web`
25
+
26
+ # Deploy
27
+ - Deploy implicitly takes the server
28
+ - `bub deploy cannoli kk_some_change` deploys that branch to both servers on cannoli
29
+ - if that branch is found in both core and web, deploy it
30
+ - if it's found in only one, deploy it there and deploy develop in the other
31
+ - `bub deploy cannoli core kk_foo web kk_bar`
32
+ - deploy branches to servers
33
+ - try to accept 'kk_foo to core', 'kk_foo on core', 'core kk_foo'
34
+ - ignore commas and 'and'
35
+ - `bub deploy cannoli core kk_foo`
36
+ - if only one target specified, I guess deploy default branch to the other
37
+ - I suppose we want to always deploy to all systems so it's not in a surprising state
38
+ - Maybe support `none` or `skip` as a branch if we want to explicitly skip deploying to it.
39
+ - `bub deploy core kk_foo`
40
+ - Takes and deploys to the first available server
41
+ - We *could* have this deploy to any server you already have claimed instead, I guess
42
+ - Seems like that could be surprising (in a bad way)
43
+ - Maybe only do that if you have only one server claimed?
44
+ - I dunno, still seems kinda sketch
45
+
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/bub_bot ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bub_bot'
4
+
5
+ BubBot::CLI.new(ARGV).start
data/braindump.md ADDED
@@ -0,0 +1,28 @@
1
+ This is a running log of whatever I was thinking when I stopped work for the day.
2
+
3
+ Brain dump for the next time I pick this up:
4
+ - Ok, `take` works. There's a nice litle parsing system for flexible command parsing.
5
+ - We've got a custom error class that, if thrown, prints the error message to slack too.
6
+ - Next up: rubocop + some cleanup, then start in on the deploy command.
7
+ - Deploy should be largely configured by the config mechanisms
8
+ - Although there should be helpers for common deploy strategies (eg git pushing and/or running a script)
9
+ - Ok, I described the deploy command in the readme. I also put together an example config in test_config.yml for deploys. Now all you have to do is actually implement it.
10
+
11
+
12
+ - Ok, I've written most of the deploy command's parsing. It probably works, but it'll need testing. Also needs more error handling. See the TODOs in the command file.
13
+ - Next up is implementing the git logic.
14
+ - Old bub implemented deploy by just cloning, then pushing, then deleting it all.
15
+ - New bub needs to keep a persistent git repo around, if only so we can get a list of branch names to check commands against.
16
+ - Might need a repo manager class to keep track of the state of those things.
17
+
18
+ I wrote a bunch of git integration stuff, but it turns out you can get a list of remote branches _without_ cloning the rep! So most of my work today isn't needed yet (although it might be for deploying).
19
+ - If I want to, I can do the old clone->deploy->delete cycle for deploys.
20
+ - Alternately, I can try to keep the repo around between deploys to speed things up.
21
+
22
+ So I ended up using most of that git stuff anyway. This is because... git deploy now works! Woo! We're even reusing repos! Todo next:
23
+ - Clean up the deploy command (it needs to support more things, like "take latest" and "claim whatever we're deploying to").
24
+ - Make sure we're only deploying to one repo at a time. We _could_ use nora's solution of just cloning a separate copy of the repo for each deploy, but I'd prefer to be able to reuse repos.
25
+ - Actually, maybe this isn't much of an issue? We only need to keep a lock on a repo while we're getting set up for a deploy. Once git push starts, we don't care that much about what happens to the filesystem.
26
+ - Implement script-based deploys (for web)
27
+ - Implement before/after deploy hooks (eg for turning envs on and off when not in use)
28
+ - Wait, maybe this should be handled separately.
data/bub_bot.gemspec ADDED
@@ -0,0 +1,37 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'bub_bot/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "bub_bot"
8
+ spec.version = BubBot::VERSION
9
+ spec.authors = ["Kevin Kuchta"]
10
+ spec.email = ["kevin@kevinkuchta.com"]
11
+
12
+ spec.summary = %q{A little server reservation and deploy bot for heroku and aptible}
13
+ spec.homepage = "https://github.com/kkuchta/bub_bot"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
17
+ f.match(%r{^(test|spec|features)/})
18
+ end
19
+ spec.bindir = "bin"
20
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
21
+ spec.require_paths = ["lib"]
22
+
23
+ spec.add_dependency "rack", "~> 1.0"
24
+ spec.add_dependency "thin", "~> 1.0"
25
+ spec.add_dependency "git"
26
+ spec.add_dependency "activesupport"
27
+ spec.add_dependency "faraday"
28
+ spec.add_dependency 'actionview'
29
+ spec.add_dependency "redis"
30
+ spec.add_dependency 'slack-ruby-client'
31
+
32
+ spec.add_development_dependency "bundler", "~> 1.13"
33
+ spec.add_development_dependency "rake", "~> 10.0"
34
+ spec.add_development_dependency "rspec", "~> 3.0"
35
+ spec.add_development_dependency "rspec-mocks", "~> 3.5"
36
+ spec.add_development_dependency "pry-byebug"
37
+ end
@@ -0,0 +1,56 @@
1
+ # Ok, slack's apis + integrations are a big mess. This was much simpler a few
2
+ # years ago. Some learnings:
3
+ #
4
+ # - Distributing as a slack app isn't going to work. That seems built around
5
+ # externally-hosted servers. You get the web-api credentials during this
6
+ # oauth-like flow that happens when a team clicks the "add app" button.
7
+ # - Wait! No! In the app config, under "Add features + functionality"
8
+ # > Permissions > Bot User OAuth Access Token, there's a bot user oauth token
9
+ # that seems to work for accessing the web api.
10
+ #
11
+
12
+ redis_host: localhost:6379
13
+
14
+ bot_name: bub
15
+
16
+ # Rack options
17
+ Port: 65222
18
+
19
+ # Bot oauth token
20
+ bot_oauth_token: abcd-123456
21
+
22
+ slack_channel: general
23
+
24
+ # H'ok, so:
25
+ # deploy_targets lists each codebase. Deploy defines how to deploy. Any string
26
+ # in here will get run through erb. Some parameters that will be provided to erb
27
+ # are:
28
+ # - server
29
+ # - branch
30
+ #
31
+ # deploy types are:
32
+ # - script (just some local script to run; should be in git repo?)
33
+ # - git (a git remote to push to)
34
+ # - http (some web callback to hit)
35
+
36
+ deploy_targets:
37
+ core:
38
+ git: 'git@github.com:MyCo/foo.git'
39
+ deploy:
40
+ git: 'git@beta.aptible.com:myco-<%=server %>/foo-rails.git'
41
+ #default_branch: develop - TODO not yet supported
42
+ web:
43
+ git: 'git@github.com:MyCo/web.git'
44
+
45
+ # Script params: repo_dir, branch, server to deploy to
46
+ # (repo should already be on the right branch)
47
+ deploy:
48
+ script: 'deploy_web.sh'
49
+ #default_branch: develop - TODO not yet supported
50
+
51
+ servers:
52
+ #- cannoli
53
+ #- hotdog
54
+ - burrito
55
+ - gyro
56
+
@@ -0,0 +1,42 @@
1
+ require 'yaml'
2
+
3
+ class BubBot::CLI
4
+ DEFAULT_CONFIG_FILENAME = 'bub_bot.yml'
5
+ def initialize(args)
6
+ @args = args
7
+
8
+ @filename = args[0] || DEFAULT_CONFIG_FILENAME
9
+ end
10
+
11
+ def start
12
+ print_usage && return unless check_usage
13
+
14
+ configure_from_file(@filename)
15
+
16
+ BubBot.start
17
+ end
18
+
19
+ private
20
+
21
+ def configure_from_file(filename)
22
+ file_data = YAML.load_file(filename)
23
+
24
+ BubBot.configure do |config|
25
+ BubBot::Configuration::OPTIONS.each do |option_name|
26
+ config.public_send((option_name.to_s + '=').to_sym, file_data[option_name.to_s])
27
+ end
28
+ end
29
+ end
30
+
31
+ def check_usage
32
+ @args.count <= 1
33
+ end
34
+
35
+ def print_usage
36
+ puts <<USAGE
37
+ Usage: bub_bot [config_filename]
38
+
39
+ Default to #{DEFAULT_CONFIG_FILENAME} if config_filename is not provided
40
+ USAGE
41
+ end
42
+ end
@@ -0,0 +1,34 @@
1
+ # TODO: merge with some defaults
2
+ class BubBot::Configuration
3
+ # Any of these options will be passed on to rack, rather than handled by us.
4
+ RACK_OPTIONS = %i(
5
+ Port
6
+ )
7
+
8
+ # These options will be handled by us.
9
+ BUB_BOT_OPTIONS = %i(
10
+ servers
11
+ bot_oauth_token
12
+ bot_name
13
+ deploy_targets
14
+ slack_channel
15
+ redis_host
16
+ )
17
+
18
+ OPTIONS = RACK_OPTIONS + BUB_BOT_OPTIONS
19
+
20
+ attr_accessor *OPTIONS
21
+
22
+ def rack_options_hash
23
+ RACK_OPTIONS.each_with_object({}) do |option_name, options_hash|
24
+ options_hash[option_name] = public_send(option_name)
25
+ end
26
+ end
27
+
28
+ def verify_options
29
+ # TODO: verify that deploy_targets, etc, are formatted correctly and print
30
+ # useful error messages otherwise.
31
+ true
32
+ end
33
+
34
+ end
@@ -0,0 +1,131 @@
1
+ require 'bub_bot/repo'
2
+ require 'bub_bot/redis_connection'
3
+ require 'erb'
4
+
5
+ class BubBot::DeployManager
6
+ def deploy(server, target_name, branch)
7
+ if DeployState[server, target_name].deploying?
8
+ raise RespondableError.new("A deploy to #{target_name} on #{server} is already in progress. Not deploying.")
9
+ end
10
+
11
+ begin
12
+ DeployState[server, target_name].set(true)
13
+ target_config = target(target_name)
14
+
15
+ unless deploy_config = target_config['deploy']
16
+ raise "Missing deploy config for #{target_name}"
17
+ end
18
+
19
+ locals = {
20
+ server: server
21
+ }
22
+
23
+ # Handle each type of deploy here.
24
+ # TODO: maybe handle multiple deploys for each target? Right now the
25
+ # workaround to do that is to just have a script-type deploy that does that.
26
+ if deploy_git_remote = deploy_config['git']
27
+ deploy_git_remote = ERB.new(deploy_git_remote).result(get_binding(locals))
28
+ repo(target_name, server).push(branch, deploy_git_remote)
29
+ elsif deploy_script = deploy_config['script']
30
+ puts "xdeploying web script #{deploy_script}"
31
+ repo = repo(target_name, server)
32
+ puts "Checking out..."
33
+ repo.checkout(branch)
34
+ puts "Pulling..."
35
+ repo.pull
36
+ puts "Running script..."
37
+ success = Kernel.system("./#{deploy_script} #{repo.repo_dir} #{branch} #{server}")
38
+ puts "Success = #{success}"
39
+ unless success
40
+ raise RespondableError.new("Deploy script failed for server #{server} and target #{target_name}")
41
+ end
42
+ elsif deploy_url = deploy_config['http']
43
+ raise RespondableError.new('Sorry, deploys by http request are not supported yet')
44
+ end
45
+
46
+ DeployState[server, target_name].set(false)
47
+ rescue
48
+ DeployState[server, target_name].set(false)
49
+ raise
50
+ end
51
+ end
52
+
53
+ def target_names
54
+ targets.keys - ['all']
55
+ end
56
+
57
+ def branches(target_name)
58
+ Repo.branches(target(target_name)['git'])
59
+ end
60
+
61
+ private
62
+
63
+ def repo(target_name, server)
64
+ @repos ||= {}
65
+ target = target(target_name)
66
+ @repos[target_name + '__' + server] ||= Repo.new(target_name, target['git'], server)
67
+ end
68
+
69
+ # Returns a hash of config data for the target with this name
70
+ def target(target_name)
71
+ targets.find { |name, _| name == target_name }.last
72
+ end
73
+
74
+ def targets
75
+ BubBot.configuration.deploy_targets
76
+ end
77
+
78
+ # Gets a binding object with the given variables defined in it. You'd *think*
79
+ # there'd be a simpler way. Well, ok, there is, but there's no simpler way that
80
+ # doesn't *also* polute the binding with variables from the outer scope.
81
+ def get_binding(variables)
82
+ obj = Class.new {
83
+ attr_accessor *variables.keys
84
+ def get_binding(); binding end
85
+ }.new
86
+ variables.each { |name, value| obj.public_send(:"#{name}=", value) }
87
+ obj.get_binding
88
+ end
89
+ end
90
+
91
+ class DeployState
92
+ ROOT_KEY = 'bub_deploy_status'.freeze
93
+
94
+ def self.[](server, target)
95
+ (@_deploy_states ||= {})[key(server, target)] ||= DeployState.new(server, target)
96
+ end
97
+
98
+ def self.key(server, target)
99
+ "#{server}__#{target}"
100
+ end
101
+
102
+ def initialize(server, target)
103
+ @server = server
104
+ @target = target
105
+ end
106
+
107
+ def key
108
+ self.class.key(@server, @target)
109
+ end
110
+
111
+ def deploying?
112
+ deployed_at = redis.hget(ROOT_KEY, key)
113
+
114
+ # If we have a super-old deployed_at, assume something went wrong in the
115
+ # deploy and we failed to capture that.
116
+ return deployed_at && Time.parse(deployed_at) > 30.minutes.ago
117
+ end
118
+
119
+ def set(is_deploying)
120
+ puts "set deploying to #{is_deploying} for #{key}"
121
+ if is_deploying
122
+ redis.hset(ROOT_KEY, key, Time.now)
123
+ else
124
+ redis.hdel(ROOT_KEY, key)
125
+ end
126
+ end
127
+
128
+ def redis
129
+ BubBot::RedisConnection.instance
130
+ end
131
+ end
@@ -0,0 +1,16 @@
1
+ require 'redis'
2
+ require 'singleton'
3
+
4
+ # Just a simple wrapper around a redis connection. Call .instance on this, then
5
+ # call anything you'd normally call on a Redis on object on this instead. Eg
6
+ # `RedisConnection.instance.hgetall(...)`.
7
+ class BubBot::RedisConnection
8
+ include Singleton
9
+
10
+ def method_missing(method, *args, &block)
11
+ redis.respond_to?(method) ? redis.public_send(method, *args, &block) : super
12
+ end
13
+ def redis
14
+ @redis ||= Redis.new(url: 'redis://' + BubBot.configuration.redis_host)
15
+ end
16
+ end
@@ -0,0 +1,115 @@
1
+ require 'git'
2
+
3
+ class Repo
4
+ # When we create a temproary remote to push to, use this name.
5
+ PUSH_REMOTE_NAME = 'push_remote'
6
+
7
+ def initialize(target, origin_remote, server)
8
+ puts "target = #{target}"
9
+ puts "remote = #{origin_remote}"
10
+ puts "server = #{server}"
11
+ @target = target
12
+ @origin_remote = origin_remote
13
+ @server = server
14
+ end
15
+
16
+ # Gets a list of branch names without actually cloning the repo.
17
+ def self.branches(origin_remote)
18
+ puts 'repo.branches'
19
+ # git ls-remote returns a list of lines like this:
20
+ # ea656e141760b0d8ba49d92506427322120ce945 refs/heads/some-branch-name
21
+ ls_remote_output = `git ls-remote --heads #{origin_remote}`
22
+ ls_remote_output.split("\n").map {|line| line.split('/').last }
23
+ end
24
+
25
+ def git
26
+ return @_git if @_git
27
+
28
+ if Dir.exists?(repo_dir) && Dir.exists?(repo_dir + '/.git')
29
+ puts "Repo exists!"
30
+ @_git = Git.open(repo_dir)
31
+ # TODO: handle other errors beside "dir doesn't exist"
32
+ else
33
+ puts "Cloning repo"
34
+ @_git = Git.clone(@origin_remote, repo_dir_name, path: dir, depth: 1, :log => Logger.new(STDOUT))
35
+ puts 'here'
36
+ end
37
+ @_git
38
+ end
39
+
40
+ # Push is async
41
+ def push(branch, remote, &on_complete)
42
+ # TODO: handle people pushing while another push is still going, since we
43
+ # have some shared state in the form of the filesystem
44
+
45
+ puts 'Pushing'
46
+ git.remotes.find{ |remote| remote.name == 'push_remote' }&.remove
47
+ puts 'Maybe removed old remote'
48
+
49
+ git.add_remote('push_remote', remote)
50
+ puts 'added remote'
51
+
52
+ Kernel.system("cd #{repo_dir}; git remote set-branches --add origin '#{branch}'")
53
+ puts "Pushing #{branch} to #{remote}"
54
+
55
+ puts "about to git fetch origin for branch #{branch}"
56
+ git.fetch('origin', branch: branch)
57
+ puts 'about to finally push'
58
+ git.push(remote, "+origin/#{branch}:master")
59
+ puts 'Finished final push'
60
+ on_complete.call if on_complete
61
+ end
62
+
63
+ def clean
64
+ # TODO: make sure there's no weird changes on the git repo
65
+ git.clean(force: true)
66
+ git.reset_hard
67
+ end
68
+
69
+ def fetch
70
+ git.fetch
71
+ end
72
+
73
+ def checkout(branch_name)
74
+ clean
75
+ cmd("remote set-branches origin '*'")
76
+ fetch
77
+ git.checkout(branch_name)
78
+ end
79
+
80
+ def pull
81
+ git.pull
82
+ end
83
+
84
+ # We name repo dirs after server + name (eg `burrito__core`). This lets
85
+ # multiple deploys to the same server (eg deploying both core and web to burrito
86
+ # at the same time) to work, as well as multiple deploys to the same target
87
+ # (eg deploying core on both burrito and gyro) to work.
88
+ #
89
+ # Note that simultanious deploys to the same target _and_ server (eg two deploys
90
+ # at once to burrito__core) won't work. Both deploys would use the same git
91
+ # working directory and step on eachother's toes. That's fine, though, because
92
+ # they'd _also_ step on eachother's toes in the actual target environment. We
93
+ # should prevent this case in the UI.
94
+ def repo_dir
95
+ "#{dir}/#{repo_dir_name}"
96
+ end
97
+
98
+ def repo_dir_name
99
+ "#{@server}__#{@target}"
100
+ end
101
+
102
+ def dir
103
+ "/tmp/bub_repos"
104
+ end
105
+
106
+ private
107
+
108
+ # The git library doesn't support everything, so sometimes we run arbitrary commands.
109
+ def cmd(command)
110
+ git_command = "git --git-dir=#{repo_dir}/.git --work-tree=#{repo_dir} #{command}"
111
+ puts "Running #{git_command}"
112
+ result = Kernel.system(git_command)
113
+ puts "Result=#{result}"
114
+ end
115
+ end