summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKamil Trzcinski <ayufan@ayufan.eu>2015-05-17 13:36:11 +0200
committerValery Sizov <vsv2711@gmail.com>2015-05-22 16:34:26 +0300
commit4785e35d9477219997a0af7fee505558dceb449f (patch)
tree2fa3c8e0d4ca15c68eaa9af4180cca91b35b923f
parent2bab322eab1bba3f38ee01e5dbc5e61dffdbee1a (diff)
downloadgitlab-ci-4785e35d9477219997a0af7fee505558dceb449f.tar.gz
Travis-CI integration
-rw-r--r--.gitignore1
-rw-r--r--Gemfile5
-rw-r--r--Gemfile.lock22
-rw-r--r--app/assets/javascripts/build.coffee8
-rw-r--r--app/assets/stylesheets/sections/builds.scss86
-rw-r--r--app/controllers/projects_controller.rb5
-rw-r--r--app/controllers/variables_controller.rb17
-rw-r--r--app/models/build.rb74
-rw-r--r--app/models/commit.rb98
-rw-r--r--app/models/job.rb41
-rw-r--r--app/models/job_models/shell_job.rb20
-rw-r--r--app/models/job_models/travis_job.rb244
-rw-r--r--app/models/network.rb20
-rw-r--r--app/models/project.rb71
-rw-r--r--app/models/project_services/slack_service.rb3
-rw-r--r--app/models/variable.rb3
-rw-r--r--app/services/create_commit_service.rb12
-rw-r--r--app/services/create_travis_jobs_service.rb112
-rw-r--r--app/views/builds/show.html.haml2
-rw-r--r--app/views/commits/show.html.haml2
-rw-r--r--app/views/jobs/_deploy_job_edit.html.haml2
-rw-r--r--app/views/jobs/_edit.html.haml2
-rw-r--r--app/views/jobs/_list.html.haml17
-rw-r--r--app/views/layouts/_nav_project.html.haml27
-rw-r--r--app/views/projects/_form.html.haml56
-rw-r--r--app/views/variables/index.html.haml43
-rw-r--r--config/application.rb1
-rw-r--r--config/application.yml.example1
-rw-r--r--config/initializers/1_settings.rb1
-rw-r--r--config/routes.rb1
-rw-r--r--config/travis.yml.example20
-rw-r--r--db/migrate/20141206115152_add_type_to_job.rb5
-rw-r--r--db/migrate/20141206120632_add_job_parameters.rb5
-rw-r--r--db/migrate/20150120223924_add_build_method_to_projects.rb5
-rw-r--r--db/migrate/20150121112130_create_variables.rb13
-rw-r--r--db/migrate/20150121151447_add_params_to_builds.rb5
-rw-r--r--db/migrate/20150204001034_add_ssh_key_to_projects.rb7
-rw-r--r--db/migrate/20150306135340_add_origin_ref_to_builds.rb5
-rw-r--r--db/migrate/20150306135342_add_ref_message_to_commits.rb5
-rw-r--r--db/migrate/20150306135343_add_origin_ref_to_commits.rb5
-rw-r--r--db/migrate/20150306135345_migrate_ref_type.rb6
-rw-r--r--lib/ansi2html.rb105
-rw-r--r--lib/extension.rb40
-rw-r--r--spec/models/commit_spec.rb2
-rw-r--r--spec/requests/api/builds_spec.rb2
45 files changed, 1153 insertions, 74 deletions
diff --git a/.gitignore b/.gitignore
index 794c1ec..f85f95a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,6 +8,7 @@ ci.db
config/application.yml
config/database.yml
config/resque.yml
+config/travis.yml
config/unicorn.rb
config/initializers/smtp_settings.rb
coverage/*
diff --git a/Gemfile b/Gemfile
index b70df69..0b4ab4d 100644
--- a/Gemfile
+++ b/Gemfile
@@ -12,6 +12,9 @@ gem 'rails', '4.1.9'
gem 'activerecord-deprecated_finders'
gem 'activerecord-session_store'
gem "nested_form"
+gem 'addressable'
+gem 'travis-build', github: 'travis-ci/travis-build'
+gem 'travis-model-build-config', github: 'ayufan/travis-model-build-config'
# tag runners
gem 'acts-as-taggable-on', '~> 3.4'
@@ -88,6 +91,8 @@ gem "paranoia", "~> 2.0"
# Colored output to console
gem "colored"
+gem 'sshkey'
+gem 'coder'
group :development do
gem 'brakeman', require: false
diff --git a/Gemfile.lock b/Gemfile.lock
index 76bd8fd..5230184 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,3 +1,16 @@
+GIT
+ remote: git://github.com/ayufan/travis-model-build-config.git
+ revision: b2618553985f2968480bd1e5c97a40954cf2989a
+ specs:
+ travis-model-build-config (0.0.2)
+ activesupport (~> 4.0)
+
+GIT
+ remote: git://github.com/travis-ci/travis-build.git
+ revision: 3ef77bcecbdfeacddaa3f5db9fb50e334e6f5b04
+ specs:
+ travis-build (0.0.1)
+
GEM
remote: https://rubygems.org/
specs:
@@ -71,6 +84,7 @@ GEM
timers (~> 1.1.0)
chronic (0.10.2)
cliver (0.3.2)
+ coder (0.4.0)
coderay (1.1.0)
coercible (1.0.0)
descendants_tracker (~> 0.0.1)
@@ -331,6 +345,7 @@ GEM
actionpack (>= 3.0)
activesupport (>= 3.0)
sprockets (>= 2.8, < 4.0)
+ sshkey (1.6.1)
stamp (0.5.0)
state_machine (1.2.0)
temple (0.6.7)
@@ -338,7 +353,7 @@ GEM
tins (~> 0.8)
terminal-table (1.4.5)
thor (0.19.1)
- thread_safe (0.3.4)
+ thread_safe (0.3.5)
tilt (1.4.1)
timers (1.1.0)
tins (0.13.1)
@@ -375,11 +390,13 @@ DEPENDENCIES
activerecord-deprecated_finders
activerecord-session_store
acts-as-taggable-on (~> 3.4)
+ addressable
annotate
bootstrap-sass (~> 3.0)
brakeman
byebug
capybara
+ coder
coffee-rails (~> 4.0.0)
colored
coveralls
@@ -428,8 +445,11 @@ DEPENDENCIES
slack-notifier (~> 1.0.0)
slim
spring-commands-rspec
+ sshkey
stamp
state_machine
+ travis-build!
+ travis-model-build-config!
turbolinks
uglifier (>= 1.0.3)
unicorn (~> 4.8.2)
diff --git a/app/assets/javascripts/build.coffee b/app/assets/javascripts/build.coffee
index b21b34d..450d202 100644
--- a/app/assets/javascripts/build.coffee
+++ b/app/assets/javascripts/build.coffee
@@ -39,3 +39,11 @@ class Build
$("html,body").scrollTop $("#build-trace").height() if "enabled" is $("#autoscroll-button").data("state")
@Build = Build
+
+@Fold_Section = (foldName) ->
+ section = document.getElementById(foldName)
+ if section
+ if section.classList.contains("open")
+ section.classList.remove("open")
+ else
+ section.classList.add("open")
diff --git a/app/assets/stylesheets/sections/builds.scss b/app/assets/stylesheets/sections/builds.scss
index 535d656..28a884a 100644
--- a/app/assets/stylesheets/sections/builds.scss
+++ b/app/assets/stylesheets/sections/builds.scss
@@ -1,16 +1,17 @@
pre.trace {
background: #111111;
color: #fff;
- font-family: $monospace_font;
+ font-family: 'monospace';
white-space: pre;
white-space: pre-wrap; /* css-3 */
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
white-space: -pre-wrap; /* Opera 4-6 */
white-space: -o-pre-wrap; /* Opera 7 */
word-wrap: break-word; /* Internet Explorer 5.5+ */
+ position: relative;
overflow: auto;
- overflow-y: hidden;
font-size: 12px;
+ counter-reset: line-numbering;
.icon-refresh {
font-size: 24px;
@@ -18,6 +19,83 @@ pre.trace {
}
}
+pre.trace p a::before {
+ content: counter(line-numbering);
+ counter-increment: line-numbering;
+ padding-right: 1em;
+}
+
+pre.trace p a {
+ display: inline-block;
+ text-align: right;
+ min-width: 40px;
+ margin-left: -33px;
+ cursor: pointer;
+ text-decoration: none;
+ color: #666;
+}
+
+pre.trace p {
+ position: relative;
+ padding: 0 15px 0 55px;
+ margin: 0;
+ min-height: 16px;
+}
+
+pre.trace .fold {
+ position: relative;
+ height: 16px;
+ overflow: hidden;
+ cursor: pointer;
+}
+
+pre.trace .fold p:first-of-type {
+ background: #333 url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMTAiIHdpZHRoPSIxMCI+CjxwYXRoIGQ9Im0wLjUsMS41LDQuNSw3LDQuNS03eiIgc3Ryb2tlPSIjNTU1IiBzdHJva2Utd2lkdGg9IjAuNSIgZmlsbD0iIzY2NiIvPgo8L3N2Zz4KCg==') no-repeat 8px 3px
+}
+
+pre.trace .fold:not(.open) p:first-of-type {
+ visibility: visible;
+ height: auto;
+ min-height: 16px;
+ background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMTAiIHdpZHRoPSIxMCI+CjxwYXRoIGQ9Ik0yLDksOSw1LDIsMXoiIHN0cm9rZT0iIzU1NSIgc3Ryb2tlLXdpZHRoPSIwLjUiIGZpbGw9IiM2NjYiLz4KPC9zdmc+Cgo=')
+}
+
+pre.trace .fold p:first-of-type {
+ padding-right: 190px;
+}
+
+pre.trace .fold.open {
+ height: auto;
+}
+
+pre.trace .fold .fold-name {
+ position: absolute;
+ z-index: 1;
+ display: block;
+ top: 2px;
+ right: 85px;
+ padding: 0px 7px 0px 7px;
+ line-height: 10px;
+ font-size: 10px;
+ background-color: #666;
+ border-radius: 6px;
+ color: #bbb;
+}
+
+pre.trace .duration {
+ position: absolute;
+ z-index: 1;
+ display: block;
+ top: 2px;
+ right: 12px;
+ padding: 0px 7px 0px 7px;
+ line-height: 10px;
+ font-size: 10px;
+ background-color: #666;
+ border-radius: 6px;
+ color: #bbb;
+}
+
.autoscroll-container {
position: fixed;
bottom: 10px;
@@ -64,3 +142,7 @@ pre.trace {
left: 4px;
}
}
+
+.build-variant {
+ width: 50%;
+}
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index ad5b120..bd38474 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -123,6 +123,9 @@ class ProjectsController < ApplicationController
params.require(:project).permit(:path, :timeout, :timeout_in_minutes, :default_ref, :always_build,
:polling_interval, :public, :ssh_url_to_repo, :allow_git_fetch, :skip_refs, :email_recipients,
:email_add_pusher, :email_only_broken_builds, :coverage_regex, :shared_runners_enabled, :token,
- { jobs_attributes: [:id, :name, :build_branches, :build_tags, :tag_list, :commands, :refs, :_destroy, :job_type] })
+ { jobs_attributes: [:id, :name, :build_branches, :build_tags, :tag_list, :commands, :refs, :_destroy, :job_type] },
+ :build_method, :ssh_key, :ssh_source, :ssh_refs,
+ { variables_attributes: [:id, :key, :value, :public] },
+ )
end
end
diff --git a/app/controllers/variables_controller.rb b/app/controllers/variables_controller.rb
new file mode 100644
index 0000000..6eb908e
--- /dev/null
+++ b/app/controllers/variables_controller.rb
@@ -0,0 +1,17 @@
+class VariablesController < ApplicationController
+ before_filter :authenticate_user!
+ before_filter :project
+ before_filter :authorize_access_project!
+ before_filter :authorize_manage_project!
+
+ layout 'project'
+
+ def index
+ end
+
+ private
+
+ def project
+ @project ||= Project.find(params[:project_id])
+ end
+end
diff --git a/app/models/build.rb b/app/models/build.rb
index c9f1230..7026355 100644
--- a/app/models/build.rb
+++ b/app/models/build.rb
@@ -33,7 +33,7 @@ class Build < ActiveRecord::Base
scope :pending, ->() { where(status: "pending") }
scope :success, ->() { where(status: "success") }
scope :failed, ->() { where(status: "failed") }
- scope :unstarted, ->() { where(runner_id: nil) }
+ scope :unstarted, ->() { where(runner_id: nil).where.not(commands: nil) }
acts_as_taggable
@@ -64,17 +64,21 @@ class Build < ActiveRecord::Base
def retry(build)
new_build = Build.new(status: :pending)
+ new_build.job_id = build.job_id
+ new_build.commit_id = build.commit_id
+ new_build.ref = build.ref
+ new_build.origin_ref = build.origin_ref
+ new_build.project_id = build.project_id
+ new_build.build_method = build.build_method
+ new_build.save
if build.job
- new_build.commands = build.job.commands
+ new_build.commands = build.job.build_commands(build)
new_build.tag_list = build.job.tag_list
else
new_build.commands = build.commands
end
- new_build.job_id = build.job_id
- new_build.commit_id = build.commit_id
- new_build.project_id = build.project_id
new_build.save
new_build
end
@@ -105,15 +109,23 @@ class Build < ActiveRecord::Base
build.update_attributes finished_at: Time.now
project = build.project
+ if build.job
+ build.job.build_end(build)
+ end
+
if project.web_hooks?
WebHookService.new.build_end(build)
end
- if build.commit.success? && !(build.job && build.job.deploy?)
- build.commit.create_deploy_builds(build.ref)
+ if project.can_have_jobs?
+ if build.commit.success? && !(build.job && build.job.deploy?)
+ build.commit.create_deploy_builds(build.ref, build.origin_ref)
+ end
end
- project.execute_services(build)
+ if project.can_have_services?
+ project.execute_services(build)
+ end
if project.coverage_enabled?
build.update_coverage
@@ -130,6 +142,18 @@ class Build < ActiveRecord::Base
delegate :sha, :short_sha, :before_sha, :ref,
to: :commit, prefix: false
+ def build_id
+ project.builds.where("builds.id <= ?", id).count("builds.id")
+ end
+
+ def build_commit_id
+ commit.builds.where("builds.id <= ?", id).count("builds.id")
+ end
+
+ def build_name
+ "#{commit.commit_id}.#{build_commit_id}"
+ end
+
def trace_html
html = Ansi2html::convert(trace) if trace.present?
html ||= ''
@@ -169,6 +193,10 @@ class Build < ActiveRecord::Base
commit.project
end
+ def repo_slug
+ repo_url.split('/').last(2).join('/').gsub(/\.git$/, '')
+ end
+
def project_id
commit.project_id
end
@@ -182,7 +210,15 @@ class Build < ActiveRecord::Base
end
def allow_git_fetch
- project.allow_git_fetch
+ project.can_git_fetch? && project.allow_git_fetch
+ end
+
+ def wait
+ if started_at
+ started_at - created_at
+ else
+ Time.now - created_at
+ end
end
def update_coverage
@@ -213,11 +249,23 @@ class Build < ActiveRecord::Base
end
end
- def for_tag?
- if job && job.build_tags
- true
+ def ref
+ build_ref = read_attribute(:ref)
+
+ if build_ref.present?
+ build_ref
else
- false
+ commit.ref
end
end
+
+ def origin_ref=(value)
+ value = "refs/heads/#{value}" unless value.start_with?("refs/")
+ write_attribute(:origin_ref, value)
+ write_attribute(:ref, value.gsub(/\Arefs\/(tags|heads)\//, ''))
+ end
+
+ def for_tag?
+ origin_ref.present? && origin_ref.start_with?("refs/tags/")
+ end
end
diff --git a/app/models/commit.rb b/app/models/commit.rb
index 6c876bd..80def46 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -19,9 +19,12 @@ class Commit < ActiveRecord::Base
serialize :push_data
- validates_presence_of :ref, :sha, :before_sha, :push_data
+ validates_presence_of :ref, :origin_ref, :sha, :before_sha, :push_data
validate :valid_commit_sha
+ scope :heads, ->() { where("commits.origin_ref LIKE 'refs/heads/%'") }
+ scope :tags, ->() { where("commits.origin_ref LIKE 'refs/tags/%'") }
+
def self.truncate_sha(sha)
sha[0...8]
end
@@ -92,25 +95,30 @@ class Commit < ActiveRecord::Base
recipients.uniq
end
- def create_builds
- project.jobs.where(build_branches: true).active.parallel.map do |job|
- create_build_from_job(job)
+ def create_builds(origin_ref)
+ case project.build_method
+ when 'travis'
+ CreateTravisJobsService.new.execute(self, origin_ref)
+ else
+ project.jobs.where(build_branches: true).active.parallel.map do |job|
+ create_build_from_job(job, origin_ref)
+ end
end
end
- def create_builds_for_tag(ref = '')
- project.jobs.where(build_tags: true).active.parallel.map do |job|
- create_build_from_job(job, ref)
+ def create_builds_for_tag(origin_ref)
+ case project.build_method
+ when 'travis'
+ CreateTravisJobsService.new.execute(self, origin_ref)
+ else
+ project.jobs.where(build_tags: true).active.parallel.map do |job|
+ create_build_from_job(job, origin_ref)
+ end
end
end
- def create_build_from_job(job, ref = '')
- build = builds.new(commands: job.commands)
- build.tag_list = job.tag_list
- build.project_id = project_id
- build.job = job
- build.save
- build
+ def create_build_from_job(job, origin_ref)
+ job.execute(self, origin_ref)
end
def builds_without_retry
@@ -127,10 +135,10 @@ class Commit < ActiveRecord::Base
@retried_builds ||= (builds - builds_without_retry)
end
- def create_deploy_builds(ref)
+ def create_deploy_builds(ref, origin_ref)
project.jobs.deploy.active.each do |job|
if job.run_for_ref?(ref)
- create_build_from_job(job)
+ create_build_from_job(job, origin_ref)
end
end
end
@@ -181,10 +189,25 @@ class Commit < ActiveRecord::Base
@duration ||= builds_without_retry.select(&:duration).sum(&:duration).to_i
end
+ def started_at
+ @started_at ||= builds.order('started_at DESC').first.try(:started_at)
+ end
+
def finished_at
@finished_at ||= builds.order('finished_at DESC').first.try(:finished_at)
end
+ def real_duration
+ case status
+ when 'success'
+ when 'canceled'
+ when 'failed'
+ finished_at - started_at
+ else
+ nil
+ end
+ end
+
def coverage
if project.coverage_enabled? && builds.size > 0
builds.last.coverage
@@ -194,4 +217,47 @@ class Commit < ActiveRecord::Base
def matrix?
builds_without_retry.size > 1
end
+
+ def commit_id
+ project.commits.where("id <= ?", id).count(:id)
+ end
+
+ def head?
+ origin_ref.start_with?('refs/heads/')
+ end
+
+ def tag?
+ origin_ref.start_with?('refs/tags/')
+ end
+
+ def ref_type
+ return 'heads' if head?
+ return 'tags' if tag?
+ 'undefined'
+ end
+
+ def tag_name
+ push_data[:tag_name]
+ end
+
+ def tag_message
+ push_data[:tag_message]
+ end
+
+ def prev_finished_commit
+ prev_commits = project.commits.where('id < ?', id)
+ prev_commits = prev_commits.where(origin_ref: origin_ref) if head?
+ prev_commits = prev_commits.tags if tag?
+ prev_commits.order(id: :desc).limit(10).each do |prev_commit|
+ if prev_commit.success? || prev_commit.failed?
+ return prev_commit
+ end
+ end
+ return nil
+ end
+
+ def prev_status
+ prev_commit = prev_finished_commit
+ prev_commit ? prev_commit.status : 'success'
+ end
end
diff --git a/app/models/job.rb b/app/models/job.rb
index 110c96d..9b99221 100644
--- a/app/models/job.rb
+++ b/app/models/job.rb
@@ -19,6 +19,10 @@
class Job < ActiveRecord::Base
acts_as_paranoid
+ default_value_for :type, 'ShellJob'
+
+ serialize :properties
+
belongs_to :project
has_many :builds
@@ -46,4 +50,41 @@ class Job < ActiveRecord::Base
true
end
end
+
+ after_initialize :initialize_properties
+ def initialize_properties
+ self.properties = {} if properties.nil?
+ end
+
+ def title
+ # implement inside child
+ end
+
+ def execute(commit, ref)
+ # implement inside child
+ end
+
+ def build_end(build)
+
+ end
+
+ def build_commands(build)
+ commands
+ end
+
+ # Provide convenient accessor methods
+ # for each serialized property.
+ def self.prop_accessor(*args)
+ args.each do |arg|
+ class_eval %{
+ def #{arg}
+ properties['#{arg}']
+ end
+
+ def #{arg}=(value)
+ self.properties['#{arg}'] = value
+ end
+ }
+ end
+ end
end
diff --git a/app/models/job_models/shell_job.rb b/app/models/job_models/shell_job.rb
new file mode 100644
index 0000000..82fbf72
--- /dev/null
+++ b/app/models/job_models/shell_job.rb
@@ -0,0 +1,20 @@
+class ShellJob < Job
+ def title
+ 'Shell'
+ end
+
+ def type
+ 'shell'
+ end
+
+ def execute(commit, origin_ref)
+ build = commit.builds.new(commands: commands)
+ build.build_method = 'shell'
+ build.tag_list = tag_list
+ build.project_id = commit.project_id
+ build.job = self
+ build.origin_ref = origin_ref
+ build.save
+ build
+ end
+end \ No newline at end of file
diff --git a/app/models/job_models/travis_job.rb b/app/models/job_models/travis_job.rb
new file mode 100644
index 0000000..0d12a52
--- /dev/null
+++ b/app/models/job_models/travis_job.rb
@@ -0,0 +1,244 @@
+require 'travis/model/build/config'
+require 'travis/model/build/config/matrix'
+require 'travis/build'
+require 'uri'
+
+class TravisJob < Job
+ prop_accessor :config
+ prop_accessor :variables
+ prop_accessor :matrix_config
+
+ after_initialize :init_values
+ before_save :update_values
+
+ SCRIPT_HEADER = <<-eos
+#!/bin/bash
+cat > ~/build.script && chmod +x ~/build.script && exec ~/build.script
+eos
+
+ def title
+ 'Travis'
+ end
+
+ def type
+ 'travis'
+ end
+
+ def init_values
+ self.active = false
+ self.config ||= {}
+ self.variables ||= []
+ self.matrix_config ||= {}
+ end
+
+ def update_values
+ self.name = TravisJob.build_name(matrix_config)
+ end
+
+ def commands
+ nil
+ end
+
+ def build_commands(build)
+ commands
+ end
+
+ def tag_list
+ tag_list = ['travis', config[:language] || 'ruby', config[:os] || 'linux']
+ tag_list << "xcode-#{config[:xcode_version] || 'default'}" if config[:language] == 'objective-c'
+ tag_list
+ end
+
+ def execute(commit, origin_ref)
+ build = commit.builds.new()
+ build.build_method = 'travis'
+ build.tag_list = tag_list
+ build.project_id = commit.project_id
+ build.job = self
+ build.origin_ref = origin_ref
+ build.save
+ build.update_attributes(commands: build_commands(build))
+ build
+ end
+
+ def build_end(build)
+ return unless build.commit
+ return unless build.commit.builds_without_retry.include?(build)
+
+ fast_finish = matrix_config[:fast_finish] if fast_finish
+
+ # cancel all builds
+ if fast_finish
+ build.commit.builds.each do |other_build|
+ other_build.cancel
+ end
+ end
+
+ # notifications
+ execute_notifications(build, config[:notifications])
+ end
+
+ def self.build_name(matrix_config)
+ matrix_config.map { |k,v| v.to_s }.join(", ")
+ end
+
+ def build_commands(build)
+ commit = build.commit
+ data = travis_config.dup
+ data[:config] = config
+ data[:env_vars] = (data[:env_vars] || []) + commit_variables(commit)
+
+ data[:urls] = {
+ }
+ data[:repository] = {
+ source_url: build.repo_url,
+ github_id: build.project.id,
+ slug: build.repo_slug
+ }
+ data[:source] = {
+ id: commit.id.to_s,
+ number: commit.commit_id.to_s
+ }
+ data[:job] = {
+ id: build.id.to_s,
+ number: "#{build.build_name}",
+ branch: build.ref,
+ commit: commit.sha,
+ commit_range: "#{commit.short_before_sha}..#{commit.short_sha}",
+ pull_request: false, # not yet supported
+ secure_env_enabled: true,
+ tag: build.for_tag? ? build.ref : nil
+ }
+
+ if project.ssh_key && (project.ssh_for_ref?(build.ref) || project.ssh_for_ref?(build.origin_ref))
+ url = URI(project.gitlab_url)
+ data[:ssh_key] = {
+ value: project.ssh_key,
+ source: url.host
+ }
+ end
+
+ # THIS DOESN'T WORK
+ # ::Travis::Build::Git.send(:define_method, :checkout) do
+ # disable_interactive_auth
+ # install_ssh_key
+ #
+ # if use_tarball?
+ # download_tarball
+ # else
+ # submodules
+ # end
+ #
+ # rm_key
+ # end
+
+ script = ::Travis::Build.script(data)
+ SCRIPT_HEADER + script.compile(true)
+ end
+
+ private
+
+ def commit_variables(commit)
+ return {} unless commit.project
+ commit.project.variables.map do |variable|
+ {
+ name: variable.key,
+ value: variable.value,
+ public: variable.public
+ }
+ end
+ end
+
+ def execute_notifications(build, notifications)
+ notifications ||= {}
+ email_notifications(build, notifications[:email])
+ slack_notifications(build, notifications[:slack])
+ end
+
+ def should_execute_notification(status, prev_status, config)
+ config ||= {}
+ when_notify = nil
+ case status
+ when 'success'
+ when_notify = 'change'
+ when_notify ||= config[:on_success]
+ when 'failed'
+ when_notify = 'always'
+ when_notify ||= config[:on_failure]
+ else
+ return false
+ end
+
+ case when_notify
+ when 'always'
+ return true
+ when 'never'
+ return false
+ when 'change'
+ return prev_status != status
+ else
+ return false
+ end
+ end
+
+ def email_notifications(build, config)
+ begin
+ recipients = []
+ notification_config = {}
+
+ if config.is_a?(Hash)
+ notification_config = config
+ recipients = config[:recipients]
+ elsif config.is_a?(Array)
+ recipients = config
+ end
+
+ return unless should_execute_notification(build.status, build.commit.prev_status, notification_config)
+
+ mail = MailService.new
+ mail.project = build.project
+ mail.email_add_committer = recipients.empty?
+ mail.email_only_broken_builds = false
+ mail.email_recipients = recipients.join(" ")
+ mail.execute(build)
+ rescue => e
+ NewRelic::Agent.notice_error(e, request_params: config)
+ end
+ end
+
+ def slack_notifications(build, config)
+ begin
+ rooms = []
+ notification_config = {}
+
+ if config.is_a?(Hash)
+ notification_config = config
+ rooms = config[:rooms]
+ elsif config.is_a?(Array)
+ rooms = config
+ elsif config.is_a?(String)
+ rooms = [config]
+ end
+
+ return unless should_execute_notification(build.commit.status, build.commit.prev_status, notification_config)
+
+ rooms.each do |room|
+ account_token, channel = room.split('#', 2)
+ account, token = account_token.split(':', 2)
+
+ slack = SlackService.new
+ slack.project = build.project
+ slack.webhook = "https://#{account}.slack.com/services/hooks/travis?token=#{token}"
+ slack.channel = channel
+ slack.notify_only_broken_builds = false
+ slack.execute(build)
+ end
+ rescue => e
+ NewRelic::Agent.notice_error(e, request_params: config)
+ end
+ end
+
+ def travis_config
+ CreateTravisJobsService.travis_config
+ end
+end \ No newline at end of file
diff --git a/app/models/network.rb b/app/models/network.rb
index 8431f75..093ce0b 100644
--- a/app/models/network.rb
+++ b/app/models/network.rb
@@ -102,6 +102,19 @@ class Network
build_response(response)
end
+ def raw_file_content(project_id, token, sha, filepath)
+ opts = {
+ headers: {"Content-Type" => "application/json"},
+ }
+
+ query = "projects/#{project_id}/repository/blobs/#{sha}?private_token=#{token}&filepath=#{filepath}"
+
+ endpoint = File.join(url, API_PREFIX, query)
+ response = self.class.get(endpoint, opts)
+
+ build_response(response)
+ end
+
private
def url
@@ -116,12 +129,15 @@ class Network
def build_response(response)
case response.code
- when 200
+ when 200, 201
response.parsed_response
- when 401
+ when 400
+ raise response.parsed_response['message']
+ when 401, 403
raise UnauthorizedError
else
nil
end
end
+
end
diff --git a/app/models/project.rb b/app/models/project.rb
index 9b015d2..5deec78 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -34,6 +34,8 @@ class Project < ActiveRecord::Base
has_many :web_hooks, dependent: :destroy
has_many :jobs, dependent: :destroy
has_many :events, dependent: :destroy
+ has_many :variables, dependent: :destroy
+ has_many :travis_jobs, dependent: :destroy
# Project services
has_many :services, dependent: :destroy
@@ -42,6 +44,7 @@ class Project < ActiveRecord::Base
has_one :mail_service, dependent: :destroy
accepts_nested_attributes_for :jobs, allow_destroy: true
+ accepts_nested_attributes_for :variables, allow_destroy: true
#
# Validations
@@ -56,6 +59,7 @@ class Project < ActiveRecord::Base
if: ->(project) { project.always_build.present? }
validate :validate_jobs
+ validate :validate_ssh_key
scope :public_only, ->() { where(public: true) }
@@ -84,6 +88,7 @@ ls -la
ssh_url_to_repo: project.ssh_url_to_repo,
email_add_pusher: GitlabCi.config.gitlab_ci.add_pusher,
email_only_broken_builds: GitlabCi.config.gitlab_ci.all_broken_builds,
+ build_method: GitlabCi.config.gitlab_ci.default_method
}
project = Project.new(params)
@@ -136,6 +141,7 @@ ls -la
def set_default_values
self.token = SecureRandom.hex(15) if self.token.blank?
+ self.ssh_key = SSHKey.generate.private_key if self.ssh_key.blank? and self.ssh_key_was.blank?
end
def tracked_refs
@@ -197,6 +203,8 @@ ls -la
end
def validate_jobs
+ return unless can_have_jobs?
+
remaining_jobs = jobs.reject(&:marked_for_destruction?)
if remaining_jobs.empty?
@@ -247,4 +255,67 @@ ls -la
def setup_finished?
commits.any?
end
+
+ def available_job_names
+ %w(shell travis)
+ end
+
+ def create_job(job_name)
+ self.send "create_#{job_name}_job" if available_job_names.include?(job_name)
+ end
+
+ def can_have_variables?
+ build_method == 'travis'
+ end
+
+ def can_have_services?
+ build_method == 'shell'
+ end
+
+ def can_have_jobs?
+ build_method == 'shell'
+ end
+
+ def can_have_ssh_key?
+ build_method == 'travis'
+ end
+
+ def can_git_fetch?
+ build_method == 'shell'
+ end
+
+ def ssh_pub_key
+ ssh_openssl_pkey.ssh_public_key if ssh_openssl_pkey
+ end
+
+ def ssh_fingerprint
+ ssh_openssl_pkey.fingerprint if ssh_openssl_pkey
+ end
+
+ def validate_ssh_key
+ if ssh_key.blank?
+ self.ssh_key = ssh_key_was if self.ssh_key_changed?
+ return
+ end
+
+ begin
+ SSHKey.new(ssh_key)
+ rescue => e
+ errors.add(:ssh_key, e.to_s)
+ return
+ end
+ end
+
+ def ssh_for_ref?(ref_name)
+ ssh_refs.blank? || ssh_refs.split(",").map{|ref| ref.strip}.any? {|ref| File.fnmatch(ref, ref_name)}
+ end
+
+ private
+
+ def ssh_openssl_pkey
+ begin
+ @pkey ||= SSHKey.new(ssh_key_was) unless ssh_key.blank?
+ rescue
+ end
+ end
end
diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb
index c393bd4..63fe447 100644
--- a/app/models/project_services/slack_service.rb
+++ b/app/models/project_services/slack_service.rb
@@ -19,6 +19,8 @@ class SlackService < Service
default_value_for :notify_only_broken_builds, true
+ attr_accessor :channel
+
def title
'Slack'
end
@@ -75,6 +77,7 @@ class SlackService < Service
fallback: message.fallback,
attachments: message.attachments
)
+ options[:channel] = channel if channel
SlackNotifierWorker.perform_async(webhook, message.pretext, options)
end
diff --git a/app/models/variable.rb b/app/models/variable.rb
new file mode 100644
index 0000000..b6e7741
--- /dev/null
+++ b/app/models/variable.rb
@@ -0,0 +1,3 @@
+class Variable < ActiveRecord::Base
+ belongs_to :project
+end \ No newline at end of file
diff --git a/app/services/create_commit_service.rb b/app/services/create_commit_service.rb
index 646b7c5..5ea81a9 100644
--- a/app/services/create_commit_service.rb
+++ b/app/services/create_commit_service.rb
@@ -33,12 +33,15 @@ class CreateCommitService
unless commit
data = {
ref: ref,
+ origin_ref: origin_ref,
+ ref_message: params[:message],
sha: sha,
before_sha: before_sha,
push_data: {
before: before_sha,
after: sha,
ref: ref,
+ message: params[:message],
user_name: params[:user_name],
user_email: params[:user_email],
repository: params[:repository],
@@ -51,9 +54,14 @@ class CreateCommitService
end
if origin_ref.start_with?('refs/tags/')
- commit.create_builds_for_tag(ref)
+ commit.create_builds_for_tag(origin_ref)
else
- commit.create_builds
+ commit.create_builds(origin_ref)
+ end
+
+ if commit.builds.none?
+ commit.destroy
+ commit = nil
end
if commit.builds.empty?
diff --git a/app/services/create_travis_jobs_service.rb b/app/services/create_travis_jobs_service.rb
new file mode 100644
index 0000000..5f6c222
--- /dev/null
+++ b/app/services/create_travis_jobs_service.rb
@@ -0,0 +1,112 @@
+class CreateTravisJobsService
+ def execute(commit, origin_ref)
+ project = commit.project
+
+ ref = origin_ref.gsub(/\Arefs\/(tags|heads)\//, '')
+ tag = origin_ref.start_with?('refs/tags/')
+
+ build_config_params = load_config(project, ref, commit.sha, tag)
+ raise 'Missing .travis.yml' if build_config_params.nil?
+
+ ActiveRecord::Base.transaction do
+ begin
+ generate_builds(build_config_params, commit.push_data) do |new_data, new_attributes|
+ new_job = find_or_create_job(project, new_attributes)
+ new_job.execute(commit, origin_ref)
+ end
+
+ commit
+ rescue => e
+ raise ActiveRecord::Rollback
+ nil
+ end
+ end
+ end
+
+ def find_job(project, new_attributes)
+ project.travis_jobs.where(name: TravisJob.build_name(new_attributes[:matrix_config])).find do |job|
+ job.variables == new_attributes[:env_vars] &&
+ job.config == new_attributes[:config] &&
+ job.matrix_config == new_attributes[:matrix_config]
+ end
+ end
+
+ def find_or_create_job(project, new_attributes)
+ job = find_job(project, new_attributes)
+
+ unless job
+ job = project.travis_jobs.new()
+ job.variables = new_attributes[:env_vars] || []
+ job.config = new_attributes[:config] || {}
+ job.matrix_config = new_attributes[:matrix_config] || {}
+ job.save
+ end
+
+ job
+ end
+
+ def self.travis_config
+ @@travis_config ||= Extension.deep_symbolize_keys(YAML.load_file("#{Rails.root}/config/travis.yml")[Rails.env])
+ end
+
+ def travis_config
+ CreateTravisJobsService.travis_config
+ end
+
+ private
+
+ def load_config(project, ref, sha, is_tagged = false)
+ variant, version = ref.split('/', 2)
+
+ config_file ||= load_config_file(project, sha, ".release_#{variant}.yml") if is_tagged && version
+ config_file ||= load_config_file(project, sha, ".travis_#{variant}.yml") if version
+ config_file ||= load_config_file(project, sha, '.release.yml') if is_tagged
+ config_file ||= load_config_file(project, sha, '.travis.yml')
+
+ YAML.load(config_file) unless config_file.nil?
+ end
+
+ def load_config_file(project, sha, file_name)
+ network.raw_file_content(
+ project.gitlab_id, GitlabCi.config.gitlab_server.private_token, sha, file_name)
+ end
+
+ def generate_builds(build_config_params, data, &block)
+ raise 'block must be specified' unless block_given?
+
+ build_config = ::Travis::Model::Build::Config.new(build_config_params, default_options).normalize
+ if branches = build_config[:branches]
+ if branches[:only]
+ return unless branches[:only].include? data[:ref]
+ elsif branches[:except]
+ return if branches[:except].include? data[:ref]
+ end
+ end
+
+ # omit gh-pages unless specified in branches.only
+ return if data[:ref] == 'gh-pages' unless build_config[:branches] and build_config[:branches][:only]
+
+ matrix_build = ::Travis::Model::Build::Config::Matrix.new(build_config, default_options)
+ matrix_build.expand.each do |expanded_config|
+ matrix_attributes = expanded_config.select do |key, value|
+ matrix_build.send(:expand_keys).include? key and build_config[key].is_a?(Array)
+ end
+
+ matrix_build_data = {
+ config: expanded_config,
+ matrix_config: matrix_attributes,
+ env_vars: []
+ }
+
+ block.call(data.dup, matrix_build_data)
+ end
+ end
+
+ def network
+ @@network ||= Network.new
+ end
+
+ def default_options
+ {multi_os: true, dist_group_expansion: true}
+ end
+end \ No newline at end of file
diff --git a/app/views/builds/show.html.haml b/app/views/builds/show.html.haml
index afca8ce..dafc8ef 100644
--- a/app/views/builds/show.html.haml
+++ b/app/views/builds/show.html.haml
@@ -2,7 +2,7 @@
= link_to @project.name, @project
@
= @commit.short_sha
- - if current_user && current_user.can_manage_project?(@project.gitlab_id)
+ - if current_user && current_user.can_manage_project?(@project.gitlab_id) && @project.can_have_jobs?
.pull-right
= link_to project_jobs_path(@project), class: "btn btn-default btn-small" do
%i.icon-edit.icon-white
diff --git a/app/views/commits/show.html.haml b/app/views/commits/show.html.haml
index 4464106..de69c71 100644
--- a/app/views/commits/show.html.haml
+++ b/app/views/commits/show.html.haml
@@ -2,7 +2,7 @@
= @project.name
@
#{gitlab_commit_link(@project, @commit.sha)}
- - if current_user && current_user.can_manage_project?(@project.gitlab_id)
+ - if current_user && current_user.can_manage_project?(@project.gitlab_id) && @project.can_have_jobs?
.pull-right
= link_to project_jobs_path(@project), class: "btn btn-default btn-small" do
%i.icon-edit.icon-white
diff --git a/app/views/jobs/_deploy_job_edit.html.haml b/app/views/jobs/_deploy_job_edit.html.haml
index 5765784..c774293 100644
--- a/app/views/jobs/_deploy_job_edit.html.haml
+++ b/app/views/jobs/_deploy_job_edit.html.haml
@@ -8,7 +8,7 @@
%li= msg
= f.fields_for :jobs do |job_form|
- - if job_form.object.job_type == "deploy" || job_form.object.new_record?
+ - if (job_form.object.job_type == "deploy" || job_form.object.new_record?) && job_form.object.active?
= job_form.hidden_field :job_type, value: "deploy"
.form-group
diff --git a/app/views/jobs/_edit.html.haml b/app/views/jobs/_edit.html.haml
index d748b22..319a1e0 100644
--- a/app/views/jobs/_edit.html.haml
+++ b/app/views/jobs/_edit.html.haml
@@ -8,7 +8,7 @@
%li= msg
= f.fields_for :jobs do |job_form|
- - if job_form.object.job_type == "parallel" || job_form.object.new_record?
+ - if (job_form.object.job_type == "parallel" || job_form.object.new_record?) && job_form.object.active?
.form-group
= job_form.label :name, 'Name', class: 'control-label'
.col-sm-10
diff --git a/app/views/jobs/_list.html.haml b/app/views/jobs/_list.html.haml
index 440985b..c84f95a 100644
--- a/app/views/jobs/_list.html.haml
+++ b/app/views/jobs/_list.html.haml
@@ -8,12 +8,13 @@
%tbody
- @project.jobs.each do |job|
- %tr
- %td= job.name
- %td= check_box_tag nil, nil, job.build_branches, disabled: true
- %td= check_box_tag nil, nil, job.build_tags, disabled: true
- %td
- - job.tag_list.each do |tag|
- %span.label.label-primary
- = tag
+ - if job.object.active?
+ %tr
+ %td= job.name
+ %td= check_box_tag nil, nil, job.build_branches, disabled: true
+ %td= check_box_tag nil, nil, job.build_tags, disabled: true
+ %td
+ - job.tag_list.each do |tag|
+ %span.label.label-primary
+ = tag
diff --git a/app/views/layouts/_nav_project.html.haml b/app/views/layouts/_nav_project.html.haml
index 7a8286e..223ae22 100644
--- a/app/views/layouts/_nav_project.html.haml
+++ b/app/views/layouts/_nav_project.html.haml
@@ -8,28 +8,35 @@
= link_to project_charts_path(@project) do
%i.icon-bar-chart
Charts
+ %li
+ %hr
= nav_link path: ['runners#index', 'runners#show'] do
= link_to project_runners_path(@project) do
%i.icon-cog
Runners
- = nav_link path: ['jobs#index', 'jobs#deploy_jobs'] do
- = link_to project_jobs_path(@project) do
- %i.icon-code
- Jobs
+ - if @project.can_have_jobs?
+ = nav_link path: ['jobs#index', 'jobs#deploy_jobs'] do
+ = link_to project_jobs_path(@project) do
+ %i.icon-code
+ Jobs
+ - if @project.can_have_variables?
+ = nav_link path: 'variables#index' do
+ = link_to project_variables_path(@project) do
+ %i.icon-code
+ Variables
+ - if @project.can_have_services?
+ = nav_link path: 'services#index' do
+ = link_to project_services_path(@project) do
+ %i.icon-gear
+ Services
= nav_link path: 'web_hooks#index' do
= link_to project_web_hooks_path(@project) do
%i.icon-link
Web Hooks
- = nav_link path: 'services#index' do
- = link_to project_services_path(@project) do
- %i.icon-gear
- Services
= nav_link path: 'events#index' do
= link_to project_events_path(@project) do
%i.icon-book
Events
- %li
- %hr
= nav_link path: 'projects#edit' do
= link_to edit_project_path(@project) do
%i.icon-cogs
diff --git a/app/views/projects/_form.html.haml b/app/views/projects/_form.html.haml
index af78db9..572b2a9 100644
--- a/app/views/projects/_form.html.haml
+++ b/app/views/projects/_form.html.haml
@@ -11,19 +11,37 @@
%legend Build settings
.form-group
= label_tag nil, class: 'control-label' do
- Get code
+ Build method
.col-sm-10
- %p Get recent application code using the following command:
+ %p Select the way project is built:
.radio
= label_tag do
- = f.radio_button :allow_git_fetch, 'false'
- %strong git clone
- .light Slower but makes sure you have a clean dir before every build
+ = f.radio_button :build_method, 'shell'
+ %strong Shell Jobs
+ .light Scripts you want CI to run on each push to repository
.radio
= label_tag do
- = f.radio_button :allow_git_fetch, 'true'
- %strong git fetch
- .light Faster
+ = f.radio_button :build_method, 'travis'
+ %strong Travis-CI
+ .light Use .travis.yml from project repository
+
+ - if @project.can_git_fetch?
+ .form-group
+ = label_tag nil, class: 'control-label' do
+ Get code
+ .col-sm-10
+ %p Get recent application code using the following command:
+ .radio
+ = label_tag do
+ = f.radio_button :allow_git_fetch, 'false'
+ %strong git clone
+ .light Slower but makes sure you have a clean dir before every build
+ .radio
+ = label_tag do
+ = f.radio_button :allow_git_fetch, 'true'
+ %strong git fetch
+ .light Faster
+
.form-group
= f.label :timeout_in_minutes, 'Timeout', class: 'control-label'
.col-sm-10
@@ -79,6 +97,28 @@
%code \d+\%$
+ - if @project.can_have_ssh_key?
+ %fieldset
+ %legend SSH key
+ .form-group
+ = f.label :ssh_key, "Public key", class: 'control-label'
+ .col-sm-10
+ = text_area_tag :ssh_pub_key, @project.ssh_pub_key, class: 'form-control', rows: 5, readonly: true
+ .form-group
+ = f.label :ssh_key, "Fingerprint", class: 'control-label'
+ .col-sm-10
+ = text_field_tag :ssh_fingerprint, @project.ssh_fingerprint, class: 'form-control', readonly: true
+ .form-group
+ = f.label :ssh_key, "New private key", class: 'control-label'
+ .col-sm-10
+ = f.text_area :ssh_key, value: (@project.ssh_key_changed? ? @project.ssh_key : ''), rows: 5, class: 'form-control'
+ .light Paste new key here in PEM format to replace current one
+ .form-group
+ = f.label :ssh_refs, "Refs", class: 'control-label'
+ .col-sm-10
+ = f.text_field :ssh_refs, class: 'form-control', placeholder: 'branch1, branch2, feature/*'
+ .light
+ You can specify git references to which SSH key will be used CI builds. Accepts strings and glob pattern syntax
%fieldset
%legend Advanced settings
diff --git a/app/views/variables/index.html.haml b/app/views/variables/index.html.haml
new file mode 100644
index 0000000..49730e5
--- /dev/null
+++ b/app/views/variables/index.html.haml
@@ -0,0 +1,43 @@
+%p.slead
+ Define additional environment variables which will be used by project
+
+%h
+
+= nested_form_for @project, html: { class: 'form-horizontal' } do |f|
+ - if @project.errors.any?
+ #error_explanation
+ %p.lead= "#{pluralize(@project.errors.count, "error")} prohibited this project from being saved:"
+ .alert.alert-error
+ %ul
+ - @project.errors.full_messages.each do |msg|
+ %li= msg
+
+ = f.fields_for :variables do |variable_form|
+ .form-group
+ = f.label :key, 'Key', class: 'control-label'
+ .col-sm-10
+ = variable_form.text_field :key, class: 'form-control', placeholder: "PROJECT_VARIABLE"
+
+ .form-group
+ = f.label :commands, 'Value', class: 'control-label'
+ .col-sm-10
+ = variable_form.text_area :value, class: 'form-control', rows: 2, placeholder: ""
+
+ .form-group
+ = f.label :public, 'Public mode', class: 'control-label'
+ .col-sm-10
+ .checkbox
+ = f.label :public do
+ = variable_form.check_box :public
+ %span.light Anyone can see variable value
+
+ = variable_form.link_to_remove "Remove this variable", class: 'btn btn-danger pull-right'
+ %hr
+ %p
+ .clearfix
+ = f.link_to_add "Add a variable", :variables, class: 'btn btn-success pull-right'
+
+ .form-actions
+ // = f.hidden_field :return_to, value: request.original_url
+ = f.submit 'Save changes', class: 'btn btn-save', return_to: request.original_url
+
diff --git a/config/application.rb b/config/application.rb
index c101033..8798235 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -12,6 +12,7 @@ module GitlabCi
# Custom directories with classes and modules you want to be autoloadable.
config.autoload_paths += %W(#{config.root}/lib
+ #{config.root}/app/models/job_models
#{config.root}/app/models/project_services)
# Only load the plugins named here, in the order given (default is alphabetical).
diff --git a/config/application.yml.example b/config/application.yml.example
index 8f64c33..acdb54f 100644
--- a/config/application.yml.example
+++ b/config/application.yml.example
@@ -3,6 +3,7 @@ defaults: &defaults
url: 'https://gitlab.example.com/' # Replace with your gitlab server url
app_id: ''
app_secret: ''
+ private_token: ''
## Gitlab CI settings
gitlab_ci:
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 6021b46..c9fb0c4 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -40,6 +40,7 @@ Settings.gitlab_ci['support_email'] ||= Settings.gitlab_ci.email_from
Settings.gitlab_ci['all_broken_builds'] = true if Settings.gitlab_ci['all_broken_builds'].nil?
Settings.gitlab_ci['add_pusher'] = false if Settings.gitlab_ci['add_pusher'].nil?
Settings.gitlab_ci['url'] ||= Settings.send(:build_gitlab_ci_url)
+Settings.gitlab_ci['default_method'] ||= 'shell' if Settings.gitlab_ci['default_method'].nil?
# Compatibility with old config
Settings['gitlab_server_urls'] ||= Settings['allowed_gitlab_urls']
diff --git a/config/routes.rb b/config/routes.rb
index b92b523..9cbac61 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -68,6 +68,7 @@ Rails.application.routes.draw do
end
resources :events, only: [:index]
+ resources :variables, only: [:index]
end
resource :user_sessions do
diff --git a/config/travis.yml.example b/config/travis.yml.example
new file mode 100644
index 0000000..7ea0432
--- /dev/null
+++ b/config/travis.yml.example
@@ -0,0 +1,20 @@
+defaults: &defaults
+ skip_resolv_updates: true
+ skip_etc_hosts_fix: true
+ paranoid: false
+ hosts:
+ apt_cache: false
+ npm_cache: false
+ env_vars:
+ - name: MY_GLOBAL_VARIABLE
+ value: value
+ public: false
+
+development:
+ <<: *defaults
+
+test:
+ <<: *defaults
+
+production:
+ <<: *defaults
diff --git a/db/migrate/20141206115152_add_type_to_job.rb b/db/migrate/20141206115152_add_type_to_job.rb
new file mode 100644
index 0000000..52ebfae
--- /dev/null
+++ b/db/migrate/20141206115152_add_type_to_job.rb
@@ -0,0 +1,5 @@
+class AddTypeToJob < ActiveRecord::Migration
+ def change
+ add_column :jobs, :type, :string, default: 'ShellJob'
+ end
+end
diff --git a/db/migrate/20141206120632_add_job_parameters.rb b/db/migrate/20141206120632_add_job_parameters.rb
new file mode 100644
index 0000000..3f85e01
--- /dev/null
+++ b/db/migrate/20141206120632_add_job_parameters.rb
@@ -0,0 +1,5 @@
+class AddJobParameters < ActiveRecord::Migration
+ def change
+ add_column :jobs, :properties, :text
+ end
+end
diff --git a/db/migrate/20150120223924_add_build_method_to_projects.rb b/db/migrate/20150120223924_add_build_method_to_projects.rb
new file mode 100644
index 0000000..ec8a320
--- /dev/null
+++ b/db/migrate/20150120223924_add_build_method_to_projects.rb
@@ -0,0 +1,5 @@
+class AddBuildMethodToProjects < ActiveRecord::Migration
+ def change
+ add_column :projects, :build_method, :string, default: 'shell', null: false
+ end
+end
diff --git a/db/migrate/20150121112130_create_variables.rb b/db/migrate/20150121112130_create_variables.rb
new file mode 100644
index 0000000..a2f5415
--- /dev/null
+++ b/db/migrate/20150121112130_create_variables.rb
@@ -0,0 +1,13 @@
+class CreateVariables < ActiveRecord::Migration
+ def change
+ create_table :variables do |t|
+ t.integer :project_id, null: false
+ t.string :key
+ t.text :value
+ t.boolean :public, null: false, default: false
+ end
+
+ add_index :variables, :project_id
+ add_index :variables, [:project_id, :key], unique: true
+ end
+end
diff --git a/db/migrate/20150121151447_add_params_to_builds.rb b/db/migrate/20150121151447_add_params_to_builds.rb
new file mode 100644
index 0000000..e103754
--- /dev/null
+++ b/db/migrate/20150121151447_add_params_to_builds.rb
@@ -0,0 +1,5 @@
+class AddParamsToBuilds < ActiveRecord::Migration
+ def change
+ add_column :builds, :build_method, :string, default: 'shell', null: false
+ end
+end
diff --git a/db/migrate/20150204001034_add_ssh_key_to_projects.rb b/db/migrate/20150204001034_add_ssh_key_to_projects.rb
new file mode 100644
index 0000000..5802d84
--- /dev/null
+++ b/db/migrate/20150204001034_add_ssh_key_to_projects.rb
@@ -0,0 +1,7 @@
+class AddSshKeyToProjects < ActiveRecord::Migration
+ def change
+ add_column :projects, :ssh_key, :text
+ add_column :projects, :ssh_key_source, :text
+ add_column :projects, :ssh_refs, :text
+ end
+end
diff --git a/db/migrate/20150306135340_add_origin_ref_to_builds.rb b/db/migrate/20150306135340_add_origin_ref_to_builds.rb
new file mode 100644
index 0000000..4e69f6f
--- /dev/null
+++ b/db/migrate/20150306135340_add_origin_ref_to_builds.rb
@@ -0,0 +1,5 @@
+class AddOriginRefToBuilds < ActiveRecord::Migration
+ def change
+ add_column :builds, :origin_ref, :string
+ end
+end
diff --git a/db/migrate/20150306135342_add_ref_message_to_commits.rb b/db/migrate/20150306135342_add_ref_message_to_commits.rb
new file mode 100644
index 0000000..64524c6
--- /dev/null
+++ b/db/migrate/20150306135342_add_ref_message_to_commits.rb
@@ -0,0 +1,5 @@
+class AddRefMessageToCommits < ActiveRecord::Migration
+ def change
+ add_column :commits, :ref_message, :text
+ end
+end
diff --git a/db/migrate/20150306135343_add_origin_ref_to_commits.rb b/db/migrate/20150306135343_add_origin_ref_to_commits.rb
new file mode 100644
index 0000000..4bfebf7
--- /dev/null
+++ b/db/migrate/20150306135343_add_origin_ref_to_commits.rb
@@ -0,0 +1,5 @@
+class AddOriginRefToCommits < ActiveRecord::Migration
+ def change
+ add_column :commits, :origin_ref, :string
+ end
+end
diff --git a/db/migrate/20150306135345_migrate_ref_type.rb b/db/migrate/20150306135345_migrate_ref_type.rb
new file mode 100644
index 0000000..f0294fb
--- /dev/null
+++ b/db/migrate/20150306135345_migrate_ref_type.rb
@@ -0,0 +1,6 @@
+class MigrateRefType < ActiveRecord::Migration
+ def change
+ execute "UPDATE builds SET origin_ref=CONCAT('refs/heads/', ref) WHERE origin_ref IS NULL"
+ execute "UPDATE commits SET origin_ref=CONCAT('refs/heads/', ref) WHERE origin_ref IS NULL"
+ end
+end
diff --git a/lib/ansi2html.rb b/lib/ansi2html.rb
index b85c85a..7dac6b0 100644
--- a/lib/ansi2html.rb
+++ b/lib/ansi2html.rb
@@ -85,19 +85,47 @@ module Ansi2html
def convert(ansi)
@out = ""
+ @line = ""
+ @line_clear = false
+ @line_duration = nil
+ @line_fold = nil
@n_open_tags = 0
+ @n_open_folds = []
+ @n_lines = 0
reset()
- s = StringScanner.new(ansi.gsub("<", "&lt;"))
+ s = StringScanner.new(ansi)
while(!s.eos?)
if s.scan(/\e([@-_])(.*?)([@-~])/)
handle_sequence(s)
+ elsif s.scan(/\r?\n/)
+ finish_line()
+ begin_line()
+ elsif s.scan(/\r/)
+ @line_clear = true
+ elsif s.scan(/</)
+ append_text("&lt;")
+ elsif s.scan(/travis_fold:start:([^\r]*)\r/)
+ open_new_fold(s[1])
+ elsif s.scan(/travis_fold:end:([^\r]*)\r/)
+ close_open_fold()
+ elsif s.scan(/travis_time:start:([^\r]*)\r/)
+ unless @line.empty?
+ finish_line()
+ begin_line()
+ end
+ @line_duration = "id-time-#{s[1]}"
+ elsif s.scan(/travis_time:(end|finish):([^:]*):start=([^,]*),finish=([^,]*),duration=([^\r]*)\r/)
+ line_duration = s[5].to_i / 1000.0 / 1000.0 / 1000.0
+ line_duration = beautify_duration(line_duration)
+ @out << "<script>var duration=document.getElementById('id-time-#{s[2]}'); if(duration) { duration.innerHTML = duration.title = '#{line_duration}'; }</script>"
+ @line_duration = nil
else
- @out << s.scan(/./m)
+ append_text(s.scan(/./m))
end
end
- close_open_tags()
+ close_open_folds()
@out
end
@@ -119,7 +147,41 @@ module Ansi2html
end
evaluate_command_stack(commands)
+ append_style()
+ end
+
+ def begin_line()
+ @line = ""
+ @line_duration = nil
+ @n_open_tags = 0
+ append_style()
+ end
+
+ def finish_line()
+ @line_clear = false
+ close_open_tags()
+ @n_lines += 1
+ if @line_fold
+ @out << %{<p onclick="javascript:Fold_Section('#{@line_fold}')">}
+ @line_fold = nil
+ else
+ @out << %{<p>}
+ end
+ @out << %{<a></a>}
+ @out << %{<span id="#{@line_duration}" class="duration" title="duration">duration</span>} unless @line_duration.nil?
+ @out << %{<span id="id-1-#{@n_lines}">#{@line}</span>}
+ @out << %{</p>}
+ end
+
+ def append_text(text)
+ if @line_clear
+ @line = ""
+ @line_clear = false
+ end
+ @line << text
+ end
+ def append_style()
css_classes = []
unless @fg_color.nil?
@@ -151,17 +213,46 @@ module Ansi2html
end
def open_new_tag(css_classes)
- @out << %{<span class="#{css_classes.join(' ')}">}
+ append_text(%{<span class="#{css_classes.join(' ')}">})
@n_open_tags += 1
end
def close_open_tags
while @n_open_tags > 0
- @out << %{</span>}
+ append_text(%{</span>})
@n_open_tags -= 1
end
end
+ def open_new_fold(fold_name)
+ finish_line
+ @line_fold = "fold-start-" + ('a'..'z').to_a.shuffle.first(8).join
+ @out << %{<div id="#{@line_fold}" class="fold-start fold">}
+ @out << %{<span class="fold-name">#{fold_name}</span>}
+ @n_open_folds << @line_fold
+ begin_line
+ end
+
+ def close_open_fold
+ unless @n_open_folds.empty?
+ finish_line
+ @out << "</div>"
+ @n_open_folds.pop
+ begin_line
+ end
+ end
+
+ def close_open_folds
+ finish_line unless @line.empty?
+
+ # close section and make it open
+ while @n_open_folds.size > 0
+ fold_name = @n_open_folds.pop
+ @out << "</div>"
+ @out << "<script>var fold = document.getElementById('#{fold_name}'); if(fold) { fold.classList.add(\"open\"); }</script>"
+ end
+ end
+
def reset
@fg_color = nil
@bg_color = nil
@@ -218,5 +309,9 @@ module Ansi2html
def get_color_class(segments)
[segments].flatten.compact.join('-')
end
+
+ def beautify_duration(duration)
+ "#{duration.round(2)}s"
+ end
end
end
diff --git a/lib/extension.rb b/lib/extension.rb
new file mode 100644
index 0000000..f291697
--- /dev/null
+++ b/lib/extension.rb
@@ -0,0 +1,40 @@
+class Extension
+ def self.deep_merge(hash, other_hash, &block)
+ deep_merge!(hash.dup, other_hash, &block)
+ end
+
+ # Same as +deep_merge+, but modifies +self+.
+ def self.deep_merge!(hash, other_hash, &block)
+ hash.merge!(other_hash) do |k, old, new|
+ if block
+ block.call(k, old, new)
+ elsif old.is_a?(Array) && new.is_a?(Array)
+ old + new
+ elsif old.is_a?(Hash) && new.is_a?(Hash)
+ deep_merge(old, new)
+ else
+ new
+ end
+ end
+ end
+
+
+
+ def self.deep_symbolize_keys(hash)
+ hash.inject({}) { |result, (key, value)|
+ result[(key.to_sym rescue key) || key] = case value
+ when Array
+ value.map { |value| value.is_a?(Hash) ? Extension.deep_symbolize_keys(value) : value }
+ when Hash
+ Extension.deep_symbolize_keys(value)
+ else
+ value
+ end
+ result
+ }
+ end
+
+ def self.deep_symbolize_keys!(hash)
+ hash.replace(hash.deep_symbolize_keys)
+ end
+end
diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb
index 75d5610..e218273 100644
--- a/spec/models/commit_spec.rb
+++ b/spec/models/commit_spec.rb
@@ -135,7 +135,7 @@ describe Commit do
FactoryGirl.create :job, job_type: :deploy, project: project
project.reload
- commit.create_deploy_builds(commit.ref)
+ commit.create_deploy_builds(commit.ref, commit.origin_ref)
commit.builds.reload
commit.builds.size.should == 1
diff --git a/spec/requests/api/builds_spec.rb b/spec/requests/api/builds_spec.rb
index 2960388..4a82318 100644
--- a/spec/requests/api/builds_spec.rb
+++ b/spec/requests/api/builds_spec.rb
@@ -18,7 +18,7 @@ describe API::API do
it "should start a build" do
commit = FactoryGirl.create(:commit, project: project)
job = FactoryGirl.create :job, project: project
- build = commit.create_builds.first
+ build = commit.create_builds(commit.origin_ref).first
post api("/builds/register"), token: runner.token, info: {platform: :darwin}