bub_bot 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
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