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 +7 -0
- data/.gitignore +11 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +45 -0
- data/Rakefile +6 -0
- data/bin/bub_bot +5 -0
- data/braindump.md +28 -0
- data/bub_bot.gemspec +37 -0
- data/example_config.yml +56 -0
- data/lib/bub_bot/cli.rb +42 -0
- data/lib/bub_bot/configuration.rb +34 -0
- data/lib/bub_bot/deploy_manager.rb +131 -0
- data/lib/bub_bot/redis_connection.rb +16 -0
- data/lib/bub_bot/repo.rb +115 -0
- data/lib/bub_bot/server_manager.rb +76 -0
- data/lib/bub_bot/slack/client.rb +15 -0
- data/lib/bub_bot/slack/command.rb +100 -0
- data/lib/bub_bot/slack/command_parser.rb +37 -0
- data/lib/bub_bot/slack/commands/claim.rb +89 -0
- data/lib/bub_bot/slack/commands/deploy.rb +123 -0
- data/lib/bub_bot/slack/commands/echo.rb +5 -0
- data/lib/bub_bot/slack/commands/list.rb +26 -0
- data/lib/bub_bot/slack/commands/release.rb +26 -0
- data/lib/bub_bot/slack/response.rb +21 -0
- data/lib/bub_bot/version.rb +3 -0
- data/lib/bub_bot/web_server.rb +92 -0
- data/lib/bub_bot.rb +54 -0
- metadata +257 -0
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
data/.rspec
ADDED
data/.travis.yml
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -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
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
data/bin/bub_bot
ADDED
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
|
data/example_config.yml
ADDED
@@ -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
|
+
|
data/lib/bub_bot/cli.rb
ADDED
@@ -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
|
data/lib/bub_bot/repo.rb
ADDED
@@ -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
|