konstant 0.0.8

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.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.rspec +3 -0
  4. data/.ruby-version +1 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +125 -0
  8. data/Rakefile +7 -0
  9. data/bin/konstant +87 -0
  10. data/data/templates/data_dir/konstant.js +16 -0
  11. data/data/templates/data_dir/projects/.gitkeep +0 -0
  12. data/data/templates/project/build +8 -0
  13. data/data/templates/project/cleanup +5 -0
  14. data/data/templates/project/deploy +4 -0
  15. data/data/templates/project/run.txt +0 -0
  16. data/konstant.gemspec +29 -0
  17. data/lib/konstant/build.rb +86 -0
  18. data/lib/konstant/builder.rb +108 -0
  19. data/lib/konstant/cli.rb +37 -0
  20. data/lib/konstant/project.rb +75 -0
  21. data/lib/konstant/runner.rb +50 -0
  22. data/lib/konstant/scheduler.rb +50 -0
  23. data/lib/konstant/version.rb +3 -0
  24. data/lib/konstant/web.rb +39 -0
  25. data/lib/konstant.rb +81 -0
  26. data/public/assets/angular.min.js +216 -0
  27. data/public/assets/app.js +60 -0
  28. data/public/assets/bootstrap-theme.min.css +5 -0
  29. data/public/assets/bootstrap.min.css +5 -0
  30. data/public/assets/bootstrap.min.js +6 -0
  31. data/public/assets/glyphicons-halflings-regular.eot +0 -0
  32. data/public/assets/glyphicons-halflings-regular.svg +229 -0
  33. data/public/assets/glyphicons-halflings-regular.ttf +0 -0
  34. data/public/assets/glyphicons-halflings-regular.woff +0 -0
  35. data/public/assets/jquery-2.1.1.min.js +4 -0
  36. data/public/assets/style.css +3 -0
  37. data/public/index.html +175 -0
  38. data/spec/file_spec.rb +28 -0
  39. data/spec/fixtures/projects/test_project_01/build +6 -0
  40. data/spec/fixtures/projects/test_project_02/build +3 -0
  41. data/spec/konstant/builder_spec.rb +21 -0
  42. data/spec/konstant/runner_spec.rb +14 -0
  43. data/spec/konstant_spec.rb +23 -0
  44. data/spec/spec_helper.rb +27 -0
  45. metadata +208 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: effe897e9f439446faa514383ab12669c1c876e3
4
+ data.tar.gz: 0098fb81a4dd80eb3f3963f7b2074015e2967bb2
5
+ SHA512:
6
+ metadata.gz: 19a2645f44d5428c8145eb804358ca4c09aff2ee08be8bd730f7a5fa8e95eb2187fb27fd41fb1e2972952a0b34c02ad4d02c16c00928e5f1184273eebeac41ce
7
+ data.tar.gz: aa4fc7a3adbaea16385bfa6b6acfffea7a472b265c64ab024ecf876c750e0dd35690def33c31ab3cc3084e8fa7f81b909b5a4ba46760397e2f73597073631a34
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
15
+
16
+ projects
17
+ konstant.js
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --backtrace
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.0.0-p481
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in konstant.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Moritz Schepp (moritz.schepp@gmail.com)
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,125 @@
1
+ # Konstant
2
+
3
+ A simple continuous integration server with a web UI, that allows
4
+ you to implement a continuous delivery setup.
5
+
6
+ Konstant is built with ruby and comes as a ruby gem.
7
+
8
+ ## Installation
9
+
10
+ Just install the gem like this
11
+
12
+ $ gem install konstant
13
+
14
+ This will provide the `konstant` executable.
15
+
16
+ ## Usage
17
+
18
+ ### Setting up the data directory
19
+
20
+ $ mkdir ci
21
+ $ cd ci
22
+ $ konstant init
23
+
24
+ This will create an empty projects folder and a sample konstant.js config
25
+ file within `ci`. The config file contains reasonable defaults for you to get
26
+ started with.
27
+
28
+
29
+ ### Running the server
30
+
31
+ The konstant server can be instructed to provide the web UI, the actual build
32
+ scheduler or both. The server has to be run from within the directory containing
33
+ `konstant.js`
34
+
35
+ $ cd ci
36
+ $ konstant run -w # provide the web UI (default http://0.0.0.0:9105)
37
+ $ konstant run -s # provide the CI scheduler
38
+ $ konstant run -w -s # provide both
39
+
40
+ run `konstant --help` for some additional options.
41
+
42
+
43
+ ### Adding projects
44
+
45
+ Adding projects is done by adding subdirectories under the `ci/projects` folder.
46
+ Bash scripts within those subdirectories define how a project is to be built,
47
+ deployed and cleaned up. A generator is provided to quickly create new projects:
48
+
49
+ $ cd ci
50
+ $ konstant generate project my_app
51
+
52
+ this will create `ci/projects/my_app` including some sample bash scripts. Modify
53
+ them according to your build scenario.
54
+
55
+
56
+ ### The scripts
57
+
58
+ A build is a sequence of jobs:
59
+
60
+ * `build` is always run
61
+ * `deploy` is only run if `build` succeeded
62
+ * `cleanup` is always run
63
+
64
+ Those jobs are represented by bash scripts within each project directory. Only
65
+ the `build` script is required, the other jobs are simply not executed
66
+ if the files are missing.
67
+
68
+ The scripts are required to have the executable bit set. Apart from that, it is
69
+ recommended to have each script start with `#!/bin/bash -e` so that any
70
+ failed command within the script will prevent the execution of any remaining
71
+ commands.
72
+
73
+ When a project is built, the above jobs are executed while status, stdout and
74
+ stderr are recorded within the directory for that build. Build directories are
75
+ kept under `ci/projects/my_app/builds` and are named according to the timestamp
76
+ when the build was requested.
77
+
78
+
79
+ ### Triggering builds
80
+
81
+ Once the CI scheduler is up and running, building can be triggered in three
82
+ ways:
83
+
84
+ * click a button on the web UI
85
+ * create the file `ci/projects/my_app/run.txt`
86
+ * make an http request to `/projects/my_app/build` (any http verb works)
87
+
88
+ You might find the last two ways useful to automate builds after git pushes.
89
+
90
+
91
+ ### Sample project
92
+
93
+ For example, to test the
94
+ `carrierwave_backgrounder` ruby gem, you could
95
+
96
+ $ cd ci/projects/carrierwave_backgrounder
97
+ $ git clone https://github.com/lardawge/carrierwave_backgrounder.git src
98
+
99
+ And insert the following code into `ci/projects/carrierwave_backgrounder/build`
100
+
101
+ ```bash
102
+ #!/bin/bash -e
103
+
104
+ cd $KONSTANT_PROJECT_ROOT/src
105
+ git pull
106
+
107
+ bundle install --path=$KONSTANT_PROJECT_ROOT/bundle --quiet
108
+
109
+ bundle exec rspec --format progress
110
+ ```
111
+
112
+ Since we don't want to deploy anything after successful builds and there is
113
+ nothing to cleanup, you might want to
114
+
115
+ $ cd ci/projects/carrierwave_backgrounder/
116
+ $ rm deploy cleanup
117
+
118
+
119
+ ## Contributing
120
+
121
+ 1. Fork it ( https://github.com/coneda/konstant/fork )
122
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
123
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
124
+ 4. Push to the branch (`git push origin my-new-feature`)
125
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
7
+
data/bin/konstant ADDED
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
4
+
5
+ require 'konstant'
6
+
7
+ unless Konstant.env == "production"
8
+ require "debugger"
9
+ end
10
+
11
+ cli = Konstant::Cli.new
12
+ cli.parse_options
13
+ command = cli.cli_arguments.first
14
+
15
+ [:INT, :TERM].each do |sig|
16
+ trap sig do
17
+ Konstant.shutdown_handlers.each do |sh|
18
+ sh.call
19
+ end
20
+ end
21
+ end
22
+
23
+ command = "version" if cli.config[:version]
24
+ Konstant.configure "data_dir" => File.expand_path(".")
25
+
26
+ case command
27
+ when "version"
28
+ puts Konstant::VERSION
29
+ when "run"
30
+ Konstant.configure "./konstant.js"
31
+
32
+ require "puma"
33
+ require "rack/handler/puma"
34
+
35
+ threads = []
36
+
37
+ if cli.config[:web]
38
+ threads << Thread.new do
39
+ options = {
40
+ :Host => cli.config[:host],
41
+ :Port => cli.config[:port]
42
+ }
43
+
44
+ app = Konstant::Web
45
+ static = Rack::Static.new(app,
46
+ :urls => ["/assets"],
47
+ :root => "#{Konstant.root}/public",
48
+ :index => "index.html"
49
+ )
50
+
51
+ Rack::Handler::Puma.run static, options do |server|
52
+ Konstant.shutdown_handlers << Proc.new do
53
+ server.stop
54
+ end
55
+ end
56
+ end
57
+ sleep 2
58
+ puts "Web server started"
59
+ end
60
+
61
+ if cli.config[:scheduler]
62
+ threads << Thread.new do
63
+ Konstant::Scheduler.new.run
64
+ end
65
+ puts "Build scheduler started"
66
+ end
67
+
68
+ threads.map{|t| t.join}
69
+ when "config"
70
+ Konstant.configure "./konstant.js"
71
+ p Konstant.config
72
+ when "init"
73
+ system "cp -a #{Konstant.root}/data/templates/data_dir/* ."
74
+ when "generate"
75
+ item = cli.cli_arguments[1]
76
+
77
+ case item
78
+ when "project"
79
+ project_id = cli.cli_arguments[2]
80
+ Konstant.configure "./konstant.js"
81
+ system "cp -a #{Konstant.root}/data/templates/project #{Konstant.config['data_dir']}/projects/#{project_id}"
82
+ else
83
+ puts "Generator doesn't exist: '#{item}'"
84
+ end
85
+ else
86
+ raise "unknown command '#{command}'"
87
+ end
@@ -0,0 +1,16 @@
1
+ {
2
+ /* the amount of builds to keep for each project */
3
+ "builds_to_keep": 50,
4
+
5
+ /* the interval to check for new projects */
6
+ "new_projects_interval": 5,
7
+
8
+ /* the interval to check if a project has to be rebuilt */
9
+ "build_check_interval": 1,
10
+
11
+ /* the sender address for mail notifications */
12
+ "mail_sender": "admin@example.com",
13
+
14
+ /* a list of recipients for notifications on failures and recoveries */
15
+ "notify": []
16
+ }
File without changes
@@ -0,0 +1,8 @@
1
+ #!/bin/bash -e
2
+
3
+ # Some output (stdout)
4
+ echo "Building in project dir $KONSTANT_PROJECT_ROOT ..."
5
+ echo "The build timestamp is $KONSTANT_TIMESTAMP"
6
+
7
+ # Exit with positive status code (build succeeded)
8
+ # exit 0
@@ -0,0 +1,5 @@
1
+ #!/bin/bash -e
2
+
3
+ # Add code to cleanup the build directory after each build. This happens in
4
+ # case of success and failure alike.
5
+ echo "Cleaning up ..."
@@ -0,0 +1,4 @@
1
+ #!/bin/bash -e
2
+
3
+ # Add code to deploy this project to production after a successful build
4
+ echo "Deploying ..."
File without changes
data/konstant.gemspec ADDED
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'konstant/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "konstant"
8
+ spec.version = Konstant::VERSION
9
+ spec.authors = ["Moritz Schepp"]
10
+ spec.email = ["moritz.schepp@gmail.com"]
11
+ spec.summary = "Continous delivery made easy."
12
+ spec.homepage = "https://github.com/coneda/konstant"
13
+ spec.license = "MIT"
14
+
15
+ spec.files = `git ls-files -z`.split("\x0")
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_development_dependency "bundler", "~> 1.7"
21
+ spec.add_development_dependency "rake", "~> 10.0"
22
+ spec.add_development_dependency "rspec"
23
+ spec.add_development_dependency "debugger"
24
+
25
+ spec.add_dependency "json"
26
+ spec.add_dependency "puma"
27
+ spec.add_dependency "mixlib-cli"
28
+ spec.add_dependency "mail"
29
+ end
@@ -0,0 +1,86 @@
1
+ class Konstant::Build
2
+
3
+ def initialize(project, timestamp)
4
+ @project = project
5
+ @timestamp = timestamp
6
+ end
7
+
8
+ attr_reader :project, :timestamp
9
+
10
+ def create
11
+ system "mkdir -p #{path}"
12
+ end
13
+
14
+ def destroy
15
+ system "rm -rf #{path}"
16
+ end
17
+
18
+ def human
19
+ Time.parse(timestamp).strftime("%Y-%m-%d %H:%M:%S")
20
+ end
21
+
22
+ def ok?(task = 'build')
23
+ status(task) == nil ? nil : (status(task) == 0)
24
+ end
25
+
26
+ def failure?
27
+ !ok?
28
+ end
29
+
30
+ def path
31
+ "#{project.path}/builds/#{timestamp}"
32
+ end
33
+
34
+ def status(task = 'build')
35
+ File.read("#{path}/#{task}.status").strip.to_i
36
+ rescue Errno::ENOENT => e
37
+ nil
38
+ end
39
+
40
+ def stdout(task = 'build')
41
+ File.read "#{path}/#{task}.stdout"
42
+ rescue Errno::ENOENT => e
43
+ nil
44
+ end
45
+
46
+ def stderr(task = 'build')
47
+ File.read "#{path}/#{task}.stderr"
48
+ rescue Errno::ENOENT => e
49
+ nil
50
+ end
51
+
52
+ def previous
53
+ timestamps = project.build_timestamps
54
+ index = timestamps.index(timestamp)
55
+ if index <= timestamps.size - 1
56
+ project.builds[index + 1]
57
+ end
58
+ end
59
+
60
+ def as_json(*)
61
+ return {
62
+ "project_id" => project.id,
63
+ "timestamp" => timestamp,
64
+ "human" => human,
65
+ "stdout" => stdout,
66
+ "stderr" => stderr,
67
+ "status" => status,
68
+ "ok" => ok?,
69
+ "deploy" => {
70
+ "stdout" => stdout('deploy'),
71
+ "stderr" => stderr('deploy'),
72
+ "status" => status('deploy')
73
+ },
74
+ "cleanup" => {
75
+ "stdout" => stdout('cleanup'),
76
+ "stderr" => stderr('cleanup'),
77
+ "status" => status('cleanup')
78
+ }
79
+ }
80
+ end
81
+
82
+ def to_json(*args)
83
+ as_json.to_json(*args)
84
+ end
85
+
86
+ end
@@ -0,0 +1,108 @@
1
+ class Konstant::Builder
2
+
3
+ def initialize(project)
4
+ @project = project
5
+ @timestamp = Time.now.strftime("%Y%m%d%H%M%S")
6
+ @new_build = Konstant::Build.new project, timestamp
7
+ end
8
+
9
+ attr_reader :project, :timestamp, :new_build
10
+
11
+ def run
12
+ Konstant.logger.info "building project '#{project.id}'"
13
+
14
+ if build
15
+ if previous_build_failed?
16
+ Konstant.logger.info "project '#{project.id}' recovered"
17
+ notify :recovery
18
+ else
19
+ Konstant.logger.info "project '#{project.id}' built successfully"
20
+ end
21
+
22
+ Konstant.logger.info "deploying project '#{project.id}'"
23
+ unless deploy
24
+ notify :deploy_failed
25
+ end
26
+ Konstant.logger.info "project '#{project.id}' deployed"
27
+ else
28
+ Konstant.logger.info "project '#{project.id}' failed to build"
29
+ notify :failure
30
+ end
31
+
32
+ Konstant.logger.info "cleaning up project '#{project.id}'"
33
+ unless cleanup
34
+ notify :cleanup_failed
35
+ end
36
+ Konstant.logger.info "project '#{project.id}' cleaned up"
37
+
38
+ symlink
39
+ Konstant.logger.info "finished building project '#{project.id}'"
40
+
41
+ cleanup_old_builds
42
+ end
43
+
44
+ def build
45
+ Konstant::Runner.new(new_build, "build").run
46
+ end
47
+
48
+ def deploy
49
+ Konstant::Runner.new(new_build, "deploy").run
50
+ end
51
+
52
+ def cleanup
53
+ Konstant::Runner.new(new_build, "cleanup").run
54
+ end
55
+
56
+ def cleanup_old_builds
57
+ builds = project.builds
58
+ limit = Konstant.config["builds_to_keep"]
59
+
60
+ if builds.size > limit
61
+ Konstant.logger.info "removing #{builds.size - limit} build(s) from project '#{project.id}'"
62
+ builds[limit..-1].each do |build|
63
+ build.destroy
64
+ end
65
+ end
66
+ end
67
+
68
+ def symlink
69
+ system "ln -sfn #{new_build.path} #{project.path}/current"
70
+ end
71
+
72
+ def notify(message)
73
+ unless Konstant.config["notify"].empty?
74
+ Mail.deliver do
75
+ from Konstant.config["mail_sender"]
76
+ to Konstant.config["notify"]
77
+
78
+ case message
79
+ when :failure
80
+ subject "Konstant: Project failed: #{project.id}"
81
+ when :recovery
82
+ subject "Konstant: Project recovered: #{project.id}"
83
+ when :cleanup_failed
84
+ subject "Konstant: Project could not be cleaned up: #{project.id}"
85
+ when :deploy_failed
86
+ subject "Konstant: Project could not be deployed: #{project.id}"
87
+ end
88
+ end
89
+ end
90
+ end
91
+
92
+ def build_status
93
+ new_build.status
94
+ end
95
+
96
+ def build_stdout
97
+ new_build.stdout
98
+ end
99
+
100
+ def build_stderr
101
+ new_build.stderr
102
+ end
103
+
104
+ def previous_build_failed?
105
+ new_build.previous ? new_build.previous.failure? : false
106
+ end
107
+
108
+ end
@@ -0,0 +1,37 @@
1
+ require "mixlib/cli"
2
+
3
+ class Konstant::Cli
4
+
5
+ include Mixlib::CLI
6
+
7
+ option(:web,
8
+ :short => "-w",
9
+ :long => "--web",
10
+ :default => false
11
+ )
12
+
13
+ option(:scheduler,
14
+ :short => "-s",
15
+ :long => "--scheduler",
16
+ :default => false
17
+ )
18
+
19
+ option(:host,
20
+ :short => "-h HOST",
21
+ :long => "--host HOST",
22
+ :default => "0.0.0.0"
23
+ )
24
+
25
+ option(:port,
26
+ :short => "-p PORT",
27
+ :long => "--port PORT",
28
+ :default => "9105"
29
+ )
30
+
31
+ option(:version,
32
+ :short => "-v",
33
+ :long => "--version",
34
+ :default => false
35
+ )
36
+
37
+ end
@@ -0,0 +1,75 @@
1
+ class Konstant::Project
2
+
3
+ def initialize(id)
4
+ @id = id
5
+ end
6
+
7
+ attr_reader :id
8
+
9
+ def self.all
10
+ Dir["#{Konstant.config['data_dir']}/projects/*"].map do |path|
11
+ new path.split('/').last
12
+ end
13
+ end
14
+
15
+ def build!
16
+ system "touch #{path}/run.txt"
17
+ end
18
+
19
+ def building?
20
+ File.exists? "#{path}/running.txt"
21
+ end
22
+
23
+ def path
24
+ "#{Konstant.config['data_dir']}/projects/#{id}"
25
+ end
26
+
27
+ def build_timestamps
28
+ Dir["#{path}/builds/*"].map do |path|
29
+ path.split('/').last
30
+ end.sort.reverse
31
+ end
32
+
33
+ def builds
34
+ build_timestamps.map do |ts|
35
+ Konstant::Build.new self, ts
36
+ end
37
+ end
38
+
39
+ def last_build
40
+ if ts = build_timestamps.first
41
+ Konstant::Build.new self, ts
42
+ end
43
+ end
44
+
45
+ def ok?(task = 'build')
46
+ last_build && last_build.ok?(task)
47
+ end
48
+
49
+ def status(task = 'build')
50
+ last_build ? last_build.status(task) : nil
51
+ end
52
+
53
+ def stdout(task = 'build')
54
+ last_build ? last_build.stdout(task) : nil
55
+ end
56
+
57
+ def stderr(task = 'build')
58
+ last_build ? last_build.stderr(task) : nil
59
+ end
60
+
61
+ def as_json(*)
62
+ return {
63
+ "id" => id,
64
+ "ok" => ok?,
65
+ "deploy_ok" => ok?('deploy'),
66
+ "cleanup_ok" => ok?('cleanup'),
67
+ "building" => building?
68
+ }
69
+ end
70
+
71
+ def to_json(*args)
72
+ as_json.to_json(*args)
73
+ end
74
+
75
+ end