diff options
author | Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com> | 2015-06-03 16:20:53 +0200 |
---|---|---|
committer | Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com> | 2015-06-03 16:20:53 +0200 |
commit | c26af87bb4d07601b2aa8493fe03318d4eac939f (patch) | |
tree | a3824925aa0e218739142b809337a9d582ec173d | |
parent | 123c8e06543c4a7c905d1a248dea31f275394846 (diff) | |
parent | c2c9236cde807e98ff9571f8d23ac4def75eb9ba (diff) | |
download | gitlab-ci-c26af87bb4d07601b2aa8493fe03318d4eac939f.tar.gz |
Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ci
70 files changed, 822 insertions, 1147 deletions
@@ -4,6 +4,7 @@ v7.12.0 - Fix pagination on dashboard - Remove ID column from runners list in the admin area - Increase default timeout for builds to 60 minutes + - Using .gitlab-ci.yml file instead of jobs v7.11.0 - Deploy Jobs API calls diff --git a/app/controllers/jobs_controller.rb b/app/controllers/jobs_controller.rb deleted file mode 100644 index 7d029aa..0000000 --- a/app/controllers/jobs_controller.rb +++ /dev/null @@ -1,20 +0,0 @@ -class JobsController < ApplicationController - before_filter :authenticate_user! - before_filter :project - before_filter :authorize_access_project! - before_filter :authorize_manage_project! - - layout 'project' - - def index - end - - def deploy_jobs - end - - private - - def project - @project ||= Project.find(params[:project_id]) - end -end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index ad5b120..2ddd1aa 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -3,9 +3,9 @@ class ProjectsController < ApplicationController before_filter :authenticate_user!, except: [:build, :badge, :index, :show] before_filter :authenticate_public_page!, only: :show - before_filter :project, only: [:build, :integration, :show, :badge, :edit, :update, :destroy, :toggle_shared_runners] + before_filter :project, only: [:build, :integration, :show, :badge, :edit, :update, :destroy, :toggle_shared_runners, :dumped_yaml] before_filter :authorize_access_project!, except: [:build, :gitlab, :badge, :index, :show, :new, :create] - before_filter :authorize_manage_project!, only: [:edit, :integration, :update, :destroy, :toggle_shared_runners] + before_filter :authorize_manage_project!, only: [:edit, :integration, :update, :destroy, :toggle_shared_runners, :dumped_yaml] before_filter :authenticate_token!, only: [:build] before_filter :no_cache, only: [:badge] protect_from_forgery except: :build @@ -49,11 +49,13 @@ class ProjectsController < ApplicationController end def create - unless current_user.can_manage_project?(YAML.load(params["project"])[:id]) + project_data = OpenStruct.new(JSON.parse(params["project"])) + + unless current_user.can_manage_project?(project_data.id) return redirect_to root_path, alert: 'You have to have at least master role to enable CI for this project' end - @project = CreateProjectService.new.execute(current_user, params[:project], project_url(":project_id")) + @project = CreateProjectService.new.execute(current_user, project_data, project_url(":project_id")) if @project.persisted? redirect_to project_path(@project, show_guide: true), notice: 'Project was successfully created.' @@ -107,6 +109,10 @@ class ProjectsController < ApplicationController redirect_to :back end + def dumped_yaml + send_data @project.generated_yaml_config, filename: '.gitlab-ci.yml' + end + protected def project @@ -121,8 +127,7 @@ class ProjectsController < ApplicationController def project_params 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] }) + :polling_interval, :public, :ssh_url_to_repo, :allow_git_fetch, :email_recipients, + :email_add_pusher, :email_only_broken_builds, :coverage_regex, :shared_runners_enabled, :token) end end diff --git a/app/models/build.rb b/app/models/build.rb index c9f1230..7305b28 100644 --- a/app/models/build.rb +++ b/app/models/build.rb @@ -14,7 +14,6 @@ # commit_id :integer # coverage :float # commands :text -# job_id :integer # class Build < ActiveRecord::Base @@ -23,7 +22,6 @@ class Build < ActiveRecord::Base belongs_to :commit belongs_to :project belongs_to :runner - belongs_to :job, -> { with_deleted } validates :commit, presence: true validates :status, presence: true @@ -64,15 +62,8 @@ class Build < ActiveRecord::Base def retry(build) new_build = Build.new(status: :pending) - - if build.job - new_build.commands = build.job.commands - new_build.tag_list = build.job.tag_list - else - new_build.commands = build.commands - end - - new_build.job_id = build.job_id + new_build.commands = build.commands + new_build.tag_list = build.tag_list new_build.commit_id = build.commit_id new_build.project_id = build.project_id new_build.save @@ -109,8 +100,8 @@ class Build < ActiveRecord::Base WebHookService.new.build_end(build) end - if build.commit.success? && !(build.job && build.job.deploy?) - build.commit.create_deploy_builds(build.ref) + if build.commit.success? && !build.deploy? + build.commit.create_deploy_builds end project.execute_services(build) @@ -206,18 +197,4 @@ class Build < ActiveRecord::Base # so we just silentrly ignore error for now end end - - def job_name - if job - job.name - end - end - - def for_tag? - if job && job.build_tags - true - else - false - end - end end diff --git a/app/models/commit.rb b/app/models/commit.rb index 6c876bd..3bc38b6 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -15,7 +15,6 @@ class Commit < ActiveRecord::Base belongs_to :project has_many :builds, dependent: :destroy - has_many :jobs, through: :builds serialize :push_data @@ -93,31 +92,19 @@ class Commit < ActiveRecord::Base end def create_builds - project.jobs.where(build_branches: true).active.parallel.map do |job| - create_build_from_job(job) - 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) + filter_param = tag? ? :tags : :branches + config_processor.builds.each do |build_attrs| + if build_attrs[filter_param] + builds.create!({ project: project }.merge(build_attrs.extract!(:name, :commands, :tag_list))) + 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 - end - def builds_without_retry @builds_without_retry ||= begin - grouped_builds = builds.group_by(&:job) - grouped_builds.map do |job, builds| + grouped_builds = builds.group_by(&:name) + grouped_builds.map do |name, builds| builds.sort_by(&:id).last end end @@ -127,11 +114,9 @@ class Commit < ActiveRecord::Base @retried_builds ||= (builds - builds_without_retry) end - def create_deploy_builds(ref) - project.jobs.deploy.active.each do |job| - if job.run_for_ref?(ref) - create_build_from_job(job) - end + def create_deploy_builds + config_processor.deploy_builds_for_ref(ref).each do |build_attrs| + builds.create!({ project: project }.merge(build_attrs)) end end @@ -194,4 +179,8 @@ class Commit < ActiveRecord::Base def matrix? builds_without_retry.size > 1 end + + def config_processor + @config_processor ||= GitlabCiYamlProcessor.new(push_data[:ci_yaml_file]) + end end diff --git a/app/models/job.rb b/app/models/job.rb deleted file mode 100644 index 110c96d..0000000 --- a/app/models/job.rb +++ /dev/null @@ -1,49 +0,0 @@ -# == Schema Information -# -# Table name: jobs -# -# id :integer not null, primary key -# project_id :integer not null -# commands :text -# active :boolean default(TRUE), not null -# created_at :datetime -# updated_at :datetime -# name :string(255) -# build_branches :boolean default(TRUE), not null -# build_tags :boolean default(FALSE), not null -# job_type :string(255) default("parallel") -# refs :string(255) -# deleted_at :datetime -# - -class Job < ActiveRecord::Base - acts_as_paranoid - - belongs_to :project - has_many :builds - - acts_as_taggable - - scope :active, ->() { where(active: true) } - scope :archived, ->() { where(active: false) } - scope :parallel, ->(){ where(job_type: "parallel") } - scope :deploy, ->(){ where(job_type: "deploy") } - - validate :refs, length: { maximum: 255 } - - def deploy? - job_type == "deploy" - end - - def run_for_ref?(ref) - if refs.present? - refs.split(",").map(&:strip).each do |refs_val| - return true if File.fnmatch(refs_val, ref) - end - - false - else - true - end - end -end diff --git a/app/models/project.rb b/app/models/project.rb index 9b015d2..7aceeca 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -32,7 +32,6 @@ class Project < ActiveRecord::Base has_many :runner_projects, dependent: :destroy has_many :runners, through: :runner_projects has_many :web_hooks, dependent: :destroy - has_many :jobs, dependent: :destroy has_many :events, dependent: :destroy # Project services @@ -41,8 +40,6 @@ class Project < ActiveRecord::Base has_one :slack_service, dependent: :destroy has_one :mail_service, dependent: :destroy - accepts_nested_attributes_for :jobs, allow_destroy: true - # # Validations # @@ -55,8 +52,6 @@ class Project < ActiveRecord::Base presence: true, if: ->(project) { project.always_build.present? } - validate :validate_jobs - scope :public_only, ->() { where(public: true) } before_validation :set_default_values @@ -69,13 +64,7 @@ ls -la eos end - def parse(project_params) - project = if project_params.is_a?(String) - YAML.load(project_params) - else - project_params - end - + def parse(project) params = { name: project.name_with_namespace, gitlab_id: project.id, @@ -171,39 +160,10 @@ ls -la self.timeout = value.to_i * 60 end - def skip_ref?(ref_name) - if skip_refs.present? - skip_refs.delete(" ").split(",").each do |ref| - return true if File.fnmatch(ref, ref_name) - end - - false - else - false - end - end - - def create_commit_for_tag?(tag) - jobs.where(build_tags: true).active.parallel.any? || - jobs.active.deploy.any?{ |job| job.run_for_ref?(tag)} - end - def coverage_enabled? coverage_regex.present? end - def build_default_job - jobs.build(commands: Project.base_build_script) - end - - def validate_jobs - remaining_jobs = jobs.reject(&:marked_for_destruction?) - - if remaining_jobs.empty? - errors.add(:jobs, "At least one foo") - end - end - # Build a clone-able repo url # using http and basic auth def repo_url_with_auth diff --git a/app/models/project_services/hip_chat_message.rb b/app/models/project_services/hip_chat_message.rb index a5e4aee..db0d58e 100644 --- a/app/models/project_services/hip_chat_message.rb +++ b/app/models/project_services/hip_chat_message.rb @@ -8,12 +8,14 @@ class HipChatMessage def to_s lines = Array.new lines.push("<a href=\"#{RoutesHelper.project_url(project)}\">#{project.name}</a> - ") + if commit.matrix? lines.push("<a href=\"#{RoutesHelper.project_ref_commit_url(project, commit.ref, commit.sha)}\">Commit ##{commit.id}</a></br>") else first_build = commit.builds_without_retry.first - lines.push("<a href=\"#{RoutesHelper.project_build_url(project, first_build)}\">Build '#{first_build.job_name}' ##{first_build.id}</a></br>") + lines.push("<a href=\"#{RoutesHelper.project_build_url(project, first_build)}\">Build '#{first_build.name}' ##{first_build.id}</a></br>") end + lines.push("#{commit.short_sha} #{commit.git_author_name} - #{commit.git_commit_message}</br>") lines.push("#{humanized_status(commit_status)} in #{commit.duration} second(s).") lines.join('') diff --git a/app/models/project_services/slack_message.rb b/app/models/project_services/slack_message.rb index c95499e..15d6ee3 100644 --- a/app/models/project_services/slack_message.rb +++ b/app/models/project_services/slack_message.rb @@ -24,7 +24,7 @@ class SlackMessage commit.builds_without_retry.each do |build| next unless build.failed? fields << { - title: build.job_name, + title: build.name, value: "Build <#{RoutesHelper.project_build_url(project, build)}|\##{build.id}> failed in #{build.duration.to_i} second(s)." } end diff --git a/app/services/create_commit_service.rb b/app/services/create_commit_service.rb index 646b7c5..bcd7a63 100644 --- a/app/services/create_commit_service.rb +++ b/app/services/create_commit_service.rb @@ -3,6 +3,8 @@ class CreateCommitService before_sha = params[:before] sha = params[:checkout_sha] || params[:after] origin_ref = params[:ref] + yaml_config = params[:ci_yaml_file] || project.generated_yaml_config + config_processor = build_config_processor(yaml_config) unless origin_ref && sha.present? return false @@ -19,11 +21,11 @@ class CreateCommitService return false end - if origin_ref.start_with?('refs/tags/') && !project.create_commit_for_tag?(ref) + if origin_ref.start_with?('refs/tags/') && !config_processor.create_commit_for_tag?(ref) return false end - if project.skip_ref?(ref) + if config_processor.skip_ref?(ref) return false end @@ -34,6 +36,7 @@ class CreateCommitService data = { ref: ref, sha: sha, + tag: origin_ref.start_with?('refs/tags/'), before_sha: before_sha, push_data: { before: before_sha, @@ -43,23 +46,26 @@ class CreateCommitService user_email: params[:user_email], repository: params[:repository], commits: params[:commits], - total_commits_count: params[:total_commits_count] + total_commits_count: params[:total_commits_count], + ci_yaml_file: yaml_config } } commit = project.commits.create(data) end - if origin_ref.start_with?('refs/tags/') - commit.create_builds_for_tag(ref) - else - commit.create_builds - end + commit.create_builds if commit.builds.empty? - commit.create_deploy_builds(ref) + commit.create_deploy_builds end commit end + + private + + def build_config_processor(config_data) + @builds_config ||= GitlabCiYamlProcessor.new(config_data) + end end diff --git a/app/services/create_project_service.rb b/app/services/create_project_service.rb index 63cbd86..0ffa059 100644 --- a/app/services/create_project_service.rb +++ b/app/services/create_project_service.rb @@ -5,7 +5,6 @@ class CreateProjectService @project = Project.parse(params) Project.transaction do - @project.build_default_job @project.save! opts = { @@ -19,11 +18,6 @@ class CreateProjectService end if forked_project - # Copy jobs - @project.jobs = forked_project.jobs.map do |job| - Job.new(job.attributes.except("id")) - end - # Copy settings settings = forked_project.attributes.select do |attr_name, value| ["public", "shared_runners_enabled", "allow_git_fetch"].include? attr_name diff --git a/app/services/web_hook_service.rb b/app/services/web_hook_service.rb index 0918be8..4de268a 100644 --- a/app/services/web_hook_service.rb +++ b/app/services/web_hook_service.rb @@ -18,7 +18,7 @@ class WebHookService data = {} data.merge!({ build_id: build.id, - build_name: build.job_name, + build_name: build.name, build_status: build.status, build_started_at: build.started_at, build_finished_at: build.finished_at, diff --git a/app/views/admin/runners/show.html.haml b/app/views/admin/runners/show.html.haml index 5062400..6d80f6b 100644 --- a/app/views/admin/runners/show.html.haml +++ b/app/views/admin/runners/show.html.haml @@ -9,7 +9,7 @@ %span.runner-state.runner-state-specific Specific - + - if @runner.shared? .bs-callout.bs-callout-success @@ -38,7 +38,7 @@ Tags .col-sm-10 = f.text_field :tag_list, class: 'form-control' - .help-block You can setup jobs to only use runners with specific tags + .help-block You can setup builds to only use runners with specific tags .form-actions = f.submit 'Save', class: 'btn btn-save' diff --git a/app/views/builds/_build.html.haml b/app/views/builds/_build.html.haml index 1c93576..405ffc0 100644 --- a/app/views/builds/_build.html.haml +++ b/app/views/builds/_build.html.haml @@ -7,17 +7,16 @@ %strong Build ##{build.id} %td - - if build.job - - if build.for_tag? - Tag - · - #{build.ref} - - else - Commit - · - #{build.short_sha} + - if build.commit.tag? + Tag + · + #{build.ref} + - else + Commit + · + #{build.short_sha} %td - = build.job_name + = build.name - if build.tags.any? .pull-right - build.tag_list.each do |tag| diff --git a/app/views/builds/show.html.haml b/app/views/builds/show.html.haml index afca8ce..26925fb 100644 --- a/app/views/builds/show.html.haml +++ b/app/views/builds/show.html.haml @@ -2,12 +2,7 @@ = link_to @project.name, @project @ = @commit.short_sha - - if current_user && current_user.can_manage_project?(@project.gitlab_id) - .pull-right - = link_to project_jobs_path(@project), class: "btn btn-default btn-small" do - %i.icon-edit.icon-white - Edit Job - + %p = link_to project_ref_commit_path(@project, @commit.ref, @commit.sha) do ← Back to project commit @@ -21,9 +16,9 @@ %i{class: build_icon_css_class(build)} %span Build ##{build.id} - - if build.job_name + - if build.name · - = build.job_name + = build.name - unless @commit.builds_without_retry.include?(@build) %li.active @@ -37,7 +32,7 @@ .col-md-9 .build-head.alert{class: build_status_alert_class(@build)} %h4 - - if @build.for_tag? + - if @build.commit.tag? Build for tag %code #{@build.ref} - else @@ -150,8 +145,8 @@ = link_to build_url(build) do %span ##{build.id} %td - - if build.job_name - = build.job_name + - if build.name + = build.name %td.status= build.status diff --git a/app/views/commits/show.html.haml b/app/views/commits/show.html.haml index 4464106..cb8b866 100644 --- a/app/views/commits/show.html.haml +++ b/app/views/commits/show.html.haml @@ -2,12 +2,6 @@ = @project.name @ #{gitlab_commit_link(@project, @commit.sha)} - - if current_user && current_user.can_manage_project?(@project.gitlab_id) - .pull-right - = link_to project_jobs_path(@project), class: "btn btn-default btn-small" do - %i.icon-edit.icon-white - Edit Job - %p = link_to project_path(@project) do ← Back to project commits diff --git a/app/views/jobs/_deploy_job_edit.html.haml b/app/views/jobs/_deploy_job_edit.html.haml deleted file mode 100644 index 5765784..0000000 --- a/app/views/jobs/_deploy_job_edit.html.haml +++ /dev/null @@ -1,54 +0,0 @@ -= 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 :jobs do |job_form| - - if job_form.object.job_type == "deploy" || job_form.object.new_record? - - = job_form.hidden_field :job_type, value: "deploy" - .form-group - = job_form.label :name, 'Name', class: 'control-label' - .col-sm-10 - = job_form.text_field :name, class: 'form-control', placeholder: "Staging" - .form-group - = job_form.label :tag_list, class: 'control-label' do - Tags - .col-sm-10 - = job_form.text_field :tag_list, class: 'form-control' - .help-block - This job will only use runners that contain all these tags. - Leave blank if you want this job to use any runner - - .form-group - = job_form.label :refs, class: 'control-label' do - Refs - .col-sm-10 - = job_form.text_field :refs, class: 'form-control', placeholder: "master, staging, feature/*, tags/testing*" - .help-block - Run only when the above git refs strings match the branch or tag that was pushed. - %br - Accepts strings and glob pattern syntax - - .form-group - = job_form.label :commands, 'Script', class: 'control-label' - .col-sm-10 - = job_form.text_area :commands, class: 'form-control', rows: 10, placeholder: "bundle exec rake spec" - %p.light - All lines will be concatenated into one file and executed. - %br - If you change the working directory or the environment this will affect all following lines. - %br - = link_to("Example job scripts", "https://gitlab.com/gitlab-org/gitlab-ci/tree/master/doc/examples") - = job_form.link_to_remove "Remove this job", class: 'btn btn-danger pull-right' - %hr - .clearfix - = f.link_to_add "Add a job", :jobs, class: 'btn btn-save pull-right' - - .form-actions - = f.submit 'Save changes', class: 'btn btn-success' - diff --git a/app/views/jobs/_edit.html.haml b/app/views/jobs/_edit.html.haml deleted file mode 100644 index d748b22..0000000 --- a/app/views/jobs/_edit.html.haml +++ /dev/null @@ -1,51 +0,0 @@ -= 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 :jobs do |job_form| - - if job_form.object.job_type == "parallel" || job_form.object.new_record? - .form-group - = job_form.label :name, 'Name', class: 'control-label' - .col-sm-10 - = job_form.text_field :name, class: 'form-control', placeholder: "Ex. cucumber" - .form-group - = job_form.label :build_branches, 'Trigger', class: 'control-label' - .col-sm-10 - .checkbox - = job_form.label :build_branches, 'Builds commits', class: '' - = job_form.check_box :build_branches - .checkbox - = job_form.label :build_tags, 'Build tags', class: '' - = job_form.check_box :build_tags - .form-group - = job_form.label :tag_list, class: 'control-label' do - Tags - .col-sm-10 - = job_form.text_field :tag_list, class: 'form-control' - .help-block - This job will only use runners that contain all these tags. - Leave blank if you want this job to use any runner - - .form-group - = job_form.label :commands, 'Script', class: 'control-label' - .col-sm-10 - = job_form.text_area :commands, class: 'form-control', rows: 10, placeholder: "bundle exec rake spec" - %p.light - All lines will be concatenated into one file and executed. - %br - If you change the working directory or the environment this will affect all following lines. - %br - = link_to("Example job scripts", "https://gitlab.com/gitlab-org/gitlab-ci/tree/master/doc/examples") - = job_form.link_to_remove "Remove this job", class: 'btn btn-danger pull-right' - %hr - .clearfix - = f.link_to_add "Add a job", :jobs, class: 'btn btn-save pull-right' - - .form-actions - = f.submit 'Save changes', class: 'btn btn-success' - diff --git a/app/views/jobs/_list.html.haml b/app/views/jobs/_list.html.haml deleted file mode 100644 index 440985b..0000000 --- a/app/views/jobs/_list.html.haml +++ /dev/null @@ -1,19 +0,0 @@ -%table.table - %thead - %tr - %th Name - %th Build commits - %th Build tags - %th Tags - - %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 - diff --git a/app/views/jobs/deploy_jobs.html.haml b/app/views/jobs/deploy_jobs.html.haml deleted file mode 100644 index a602b72..0000000 --- a/app/views/jobs/deploy_jobs.html.haml +++ /dev/null @@ -1,12 +0,0 @@ -%ul.nav.nav-tabs.append-bottom-20 - %li - = link_to 'Test (run in parallel)', project_jobs_path(@project) - %li{class: "active"} - = link_to 'Deploy (run on success)', deploy_jobs_project_jobs_path(@project) - - -%p.slead - Deploy jobs are scripts you want CI to run on succeeding all parallel builds - - -= render 'deploy_job_edit' diff --git a/app/views/jobs/index.html.haml b/app/views/jobs/index.html.haml deleted file mode 100644 index f1b82af..0000000 --- a/app/views/jobs/index.html.haml +++ /dev/null @@ -1,24 +0,0 @@ -%ul.nav.nav-tabs.append-bottom-20 - %li{class: "active"} - = link_to 'Test (run in parallel)', project_jobs_path(@project) - %li - = link_to 'Deploy (run on success)', deploy_jobs_project_jobs_path(@project) - - - -.btn-group.pull-right - = link_to project_jobs_path(@project), class: "btn #{'active' unless params[:list]}" do - %i.icon-edit - Edit - = link_to project_jobs_path(@project, list: 'true'), class: "btn #{'active' if params[:list]}" do - %i.icon-list - List -%p.slead - Jobs are scripts you want CI to run on each push to repository - -%hr - -- if params[:list] - = render 'list' -- else - = render 'edit' diff --git a/app/views/layouts/_nav_project.html.haml b/app/views/layouts/_nav_project.html.haml index 7a8286e..3097e41 100644 --- a/app/views/layouts/_nav_project.html.haml +++ b/app/views/layouts/_nav_project.html.haml @@ -12,10 +12,6 @@ = 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 = nav_link path: 'web_hooks#index' do = link_to project_web_hooks_path(@project) do %i.icon-link diff --git a/app/views/projects/_form.html.haml b/app/views/projects/_form.html.haml index af78db9..7f3cc56 100644 --- a/app/views/projects/_form.html.haml +++ b/app/views/projects/_form.html.haml @@ -86,13 +86,7 @@ = f.label :token, "CI token", class: 'control-label' .col-sm-10 = f.text_field :token, class: 'form-control', placeholder: 'xEeFCaDAB89' - .form-group - = f.label :skip_refs, "Skip refs", class: 'control-label' - .col-sm-10 - = f.text_field :skip_refs, class: 'form-control', placeholder: 'branch1, branch2, feature/*' - .light - You can specify git references to skip CI builds. Accepts strings and glob pattern syntax - + .form-actions = f.submit 'Save changes', class: 'btn btn-save' = link_to 'Cancel', projects_path, class: 'btn' diff --git a/app/views/projects/_gl_projects.html.haml b/app/views/projects/_gl_projects.html.haml index 15cd3f0..479ffb4 100644 --- a/app/views/projects/_gl_projects.html.haml +++ b/app/views/projects/_gl_projects.html.haml @@ -11,5 +11,5 @@ Added - else = form_tag projects_path do - = hidden_field_tag :project, project.to_yaml + = hidden_field_tag :project, project.to_h.to_json = submit_tag 'Add project to CI', class: 'btn btn-default btn-small'
\ No newline at end of file diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index bcc5832..ab28b46 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -1 +1,7 @@ +- if @project.generated_yaml_config + %p.alert.alert-danger + CI Jobs are deprecated now, you can #{link_to "download yaml", dumped_yaml_project_path(@project)} + file which is based on your old jobs. + Put this file to the root of your project and name it .gitlab-ci.yml + = render 'form' diff --git a/app/views/runners/edit.html.haml b/app/views/runners/edit.html.haml index fd0e7d7..5c425ae 100644 --- a/app/views/runners/edit.html.haml +++ b/app/views/runners/edit.html.haml @@ -6,7 +6,7 @@ .col-sm-10 .checkbox = f.check_box :active - %span.light Paused runners don't accept new jobs + %span.light Paused runners don't accept new builds .form-group = label_tag :token, class: 'control-label' do Token diff --git a/app/views/shared/_guide.html.haml b/app/views/shared/_guide.html.haml index 61261e5..0b47f7e 100644 --- a/app/views/shared/_guide.html.haml +++ b/app/views/shared/_guide.html.haml @@ -6,8 +6,7 @@ Add at least one runner to the project. Go to #{link_to 'Runners page', project_runners_path(@project), target: :blank} for instructions. %li - Setup at least one Job with a build script. - Go to #{link_to 'Jobs page', project_jobs_path(@project), target: :blank} for instructions. + Put the .gitlab-ci.yml in the root of your repository. Examples can be found in #{link_to "Configuraton of your builds with .gitlab-ci.yaml", "http://doc.gitlab.com/ci/builds_configuration/README.html", target: :blank} %li Visit #{link_to 'GitLab project settings', @project.gitlab_url + "/services/gitlab_ci/edit", target: :blank} and press the "Test settings" button. diff --git a/config/routes.rb b/config/routes.rb index b92b523..593797a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -20,6 +20,7 @@ Rails.application.routes.draw do get :integration post :build post :toggle_shared_runners + get :dumped_yaml end resources :services, only: [:index, :edit, :update] do @@ -61,12 +62,6 @@ Rails.application.routes.draw do resources :runner_projects, only: [:create, :destroy] - resources :jobs, only: [:index] do - collection do - get :deploy_jobs - end - end - resources :events, only: [:index] end diff --git a/db/migrate/20150528011001_add_fields_to_builds.rb b/db/migrate/20150528011001_add_fields_to_builds.rb new file mode 100644 index 0000000..394dd8b --- /dev/null +++ b/db/migrate/20150528011001_add_fields_to_builds.rb @@ -0,0 +1,6 @@ +class AddFieldsToBuilds < ActiveRecord::Migration + def change + add_column :builds, :name, :string + add_column :builds, :deploy, :boolean, default: false + end +end diff --git a/db/migrate/20150528011012_remove_unused_fields.rb b/db/migrate/20150528011012_remove_unused_fields.rb new file mode 100644 index 0000000..c8f5e89 --- /dev/null +++ b/db/migrate/20150528011012_remove_unused_fields.rb @@ -0,0 +1,5 @@ +class RemoveUnusedFields < ActiveRecord::Migration + def change + remove_column :builds, :job_id, :integer + end +end diff --git a/db/migrate/20150529012113_add_tag_to_commits.rb b/db/migrate/20150529012113_add_tag_to_commits.rb new file mode 100644 index 0000000..a8f7207 --- /dev/null +++ b/db/migrate/20150529012113_add_tag_to_commits.rb @@ -0,0 +1,5 @@ +class AddTagToCommits < ActiveRecord::Migration + def change + add_column :commits, :tag, :boolean, default: false + end +end diff --git a/db/migrate/20150601043220_add_yaml_to_projects.rb b/db/migrate/20150601043220_add_yaml_to_projects.rb new file mode 100644 index 0000000..f741842 --- /dev/null +++ b/db/migrate/20150601043220_add_yaml_to_projects.rb @@ -0,0 +1,9 @@ +class AddYamlToProjects < ActiveRecord::Migration + def up + add_column :projects, :generated_yaml_config, :text + end + + def down + remove_column :projects, :generated_yaml_config + end +end diff --git a/db/migrate/20150601043222_migrate_jobs_to_yaml.rb b/db/migrate/20150601043222_migrate_jobs_to_yaml.rb new file mode 100644 index 0000000..01a69d7 --- /dev/null +++ b/db/migrate/20150601043222_migrate_jobs_to_yaml.rb @@ -0,0 +1,68 @@ +# Migration tested on MySQL and PostgreSQL. +# Can be performed online without errors. +# This migration will loop through all projects and jobs, so it can take some time. + +class MigrateJobsToYaml < ActiveRecord::Migration + def up + select_all("SELECT * FROM projects").each do |project| + config = {jobs: [], deploy_jobs: []} + + concatenate_expression = if ActiveRecord::Base.connection.adapter_name == 'PostgreSQL' + "string_agg(tags.name, ',')" + else + "GROUP_CONCAT(tags.name SEPARATOR ',')" + end + + sql = "SELECT j.*, #{concatenate_expression} tags + FROM jobs j + LEFT JOIN taggings tgs ON j.id = tgs.taggable_id AND tgs.taggable_type = 'Job' + LEFT JOIN tags ON tgs.tag_id = tags.id + WHERE project_id = #{project['id']} + AND active = true + AND job_type = 'parallel' + GROUP BY j.id" + + # Create Jobs + select_all(sql).each do |job| + config[:jobs] << { + script: job["commands"] && job["commands"].split("\n").map(&:strip), + name: job["name"], + branches: parse_boolean_value(job["build_branches"]), + tags: parse_boolean_value(job["build_tags"]), + runner: job["tags"] + } + end + + # Create Deploy Jobs + select_all(sql.sub("parallel", 'deploy')).each do |job| + config[:deploy_jobs] << { + script: job["commands"] && job["commands"].split("\n").map(&:strip), + name: job["name"], + refs: job["refs"], + runner: job["tags"] + } + end + + config[:skip_refs] = project["skip_refs"] + + yaml_config = YAML.dump(config.deep_stringify_keys) + + yaml_config.sub!("---", "# This file is generated by GitLab CI") + + # Convert array of scripts to multiline string + yaml_config.gsub!(" -", " ").gsub!("script:", "script: |") + + execute("UPDATE projects SET generated_yaml_config = '#{quote_string(yaml_config)}' WHERE projects.id = #{project["id"]}") + end + end + + def down + + end + + private + + def parse_boolean_value(value) + [ true, 1, '1', 't', 'T', 'true', 'TRUE', 'on', 'ON' ].include?(value) + end +end diff --git a/db/schema.rb b/db/schema.rb index 1e4f9cd..0d9482e 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -28,7 +28,8 @@ ActiveRecord::Schema.define(version: 20150602000240) do t.integer "commit_id" t.float "coverage" t.text "commands" - t.integer "job_id" + t.string "name" + t.boolean "deploy", default: false end add_index "builds", ["commit_id"], name: "index_builds_on_commit_id", using: :btree @@ -44,6 +45,7 @@ ActiveRecord::Schema.define(version: 20150602000240) do t.text "push_data" t.datetime "created_at" t.datetime "updated_at" + t.boolean "tag", default: false end add_index "commits", ["project_id", "sha"], name: "index_commits_on_project_id_and_sha", using: :btree @@ -100,6 +102,7 @@ ActiveRecord::Schema.define(version: 20150602000240) do t.string "skip_refs" t.string "coverage_regex" t.boolean "shared_runners_enabled", default: false + t.text "generated_yaml_config" end create_table "runner_projects", force: true do |t| diff --git a/doc/README.md b/doc/README.md index a300654..8307276 100644 --- a/doc/README.md +++ b/doc/README.md @@ -4,8 +4,8 @@ + [Examples](examples/README.md) + [Install](install/installation.md) + [Update](update/README.md) -+ [Jobs](jobs/README.md) + [Runners](runners/README.md) ++ [Builds Configuration](builds_configuration/README.md) + [Permissions](permissions/README.md) User permissions + [Rake Tasks](raketasks/README.md) Backup and restore take tasks + [Migrating to packaged CI](migration_to_omnibus/README.md) diff --git a/doc/api/commits.md b/doc/api/commits.md index b348e23..0015a62 100644 --- a/doc/api/commits.md +++ b/doc/api/commits.md @@ -46,9 +46,7 @@ Returns: "before_sha": "96906f2bceb04c7323f8514aa5ad8cb1313e2898", "runner_id": 1, "coverage": null, - "commit_id": 3, - "commands": "git submodule update --init\nls -la\n", - "job_id": 3 + "commit_id": 3 }] }] ``` @@ -97,9 +95,7 @@ Returns: "before_sha": "96906f2bceb04c7323f8514aa5ad8cb1313e2898", "runner_id": 1, "coverage": null, - "commit_id": 3, - "commands": "git submodule update --init\nls -la\n", - "job_id": 3 + "commit_id": 3 }] } ``` diff --git a/doc/api/projects.md b/doc/api/projects.md index bdc9862..c24d48f 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -151,61 +151,4 @@ via authorized user). Parameters: * `id` (required) - The ID of the Gitlab CI project - * `runner_id` (required) - The ID of the Gitlab CI runner - -### List All Jobs for a Project - -List the jobs associated to a Gitlab CI Project (only via -authorized user). - - GET /projects/:id/jobs - -Parameters: - - * `id` (required) - The ID of the Gitlab CI project - -### Add a parallel Job to a Project - -Adds a Job to a Gitlab CI Project (only via -authorized user). - - POST /projects/:id/jobs - -Parameters: - - * `id` (required) - The ID of the Gitlab CI project - * `name` (required) - The name of the Job to add - * `commands` (required) - The script commands of the job - * `active` (optional) - The command is active of not - * `build_branches` (optional) - Trigger commit builds - * `build_tags` (optional) - Trigger tag builds - * `tags` (optional) - The tags associated with this job - -### Add a deploy Job to a Project - -Adds a deploy Job to a Gitlab CI Project (only via -authorized user). - - POST /projects/:id/deploy_jobs - -Parameters: - - * `id` (required) - The ID of the Gitlab CI project - * `name` (required) - The name of the Job to add - * `commands` (required) - The script commands of the job - * `active` (optional) - The command is active of not - * `refs` (optional) - The list of refs - * `tags` (optional) - The tags associated with this job - -### Remove a Job from a Project - -Removes a Job from a Gitlab CI Project (only -via authorized user). - - DELETE /projects/:id/jobs/:job_id - -Parameters: - - * `id` (required) - The ID of the Gitlab CI project - * `job_id` (required) - The ID of the Job - + * `runner_id` (required) - The ID of the Gitlab CI runner
\ No newline at end of file diff --git a/doc/builds_configuration/README.md b/doc/builds_configuration/README.md new file mode 100644 index 0000000..9e761bf --- /dev/null +++ b/doc/builds_configuration/README.md @@ -0,0 +1,80 @@ +## Configuraton of your builds with .gitlab-ci.yaml + +From version 7.12, GitLab CI uses a .gitlab-ci.yml file for the configuration of your builds. It is place in the root of your repository and contains four main sections: skep_refs, before_script, jobs and deploy_jobs. Here is an example of how it looks: + +``` +skip_refs: staging +before_script: | + export PATH=$HOME/bin:/usr/local/bin:/usr/bin:/bin + gem install bundler + cp config/database.yml.mysql config/database.yml + bundle install --without postgres production --jobs $(nproc) + bundle exec rake db:create RAILS_ENV=test +jobs: +- script: "bundle exec rspec" + name: Rspec + runner: mysql,ruby +- "bundle exec cucumber" # or even so +deploy_jobs: +- "bundle exec cap deploy" + +``` + +Let's have a close look at each section. + +### skip_refs +This parameter defines the ref or list of refs to skip. You can use glob pattern syntax as well. Example: "staging,feature-*" + +### jobs +Here you can specify parameters of your builds. There are serveral ways you can configure it. Using hash: +``` +jobs: +- script: "bundle exec rspec" # (required) - commands to run + name: Rspec # (optional) - name of build + runner: mysql,ruby # (optional) - runner tags, only runners which have these tags will be used + branches: true # (optional) - make builds for regular branches + tags: true # (optional) - make builds for tags +``` +`script` can also cantain several commands using YAML multiline string: +``` +- script: | + bundle updata + bundle exec rspec +``` +you can also fill commands like an array: +``` +- script: + - bundle update + - bundle exec rspec +``` +And there is one more way to specify build configuration, using a string: +``` +jobs: +- bundle exec rspec +``` +In this way, the name of the build will be taken from command line. + +## deploy_jobs +Deploy Jobs that will be run when all other jobs have succeeded. Define them using a hash: + +``` +deploy_jobs: +- script: | # (required) - command + bundle update + bundle exec cap deploy + name: Deploy # (optional) - name + refs: deploy # (optional) - run only when the above git refs strings match the branch or tag that was pushed. + runner: ruby,deploy # (optional) - runner tags, only runners which have these tags will be used +``` + +`script` can be a multiline script or array like for regular jobs. + +You can also define deploy jobs with a string: + +``` +deploy_jobs: +- "bundle exec cap deploy" +``` + +## before_script +`before_script` is used to define the command that should be ran before all builds, including deploy builds. This can be an array or a multiline string.
\ No newline at end of file diff --git a/doc/install/installation.md b/doc/install/installation.md index 614fbee..ef78f2d 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -73,7 +73,7 @@ You can use either MySQL or PostgreSQL. mysql> CREATE USER 'gitlab_ci'@'localhost' IDENTIFIED BY '$password'; # Grant proper permissions to the MySQL User - mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER ON `gitlab_ci_production`.* TO 'gitlab_ci'@'localhost'; + mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER, LOCK TABLES ON `gitlab_ci_production`.* TO 'gitlab_ci'@'localhost'; # Logout MYSQL mysql> exit; diff --git a/doc/jobs/README.md b/doc/jobs/README.md deleted file mode 100644 index ab98e0e..0000000 --- a/doc/jobs/README.md +++ /dev/null @@ -1,60 +0,0 @@ -# Jobs - -Jobs are user-created shell scripts. On each push to GitLab the CI creates builds for each job. -Every build is served by runners on which the shell scripts from these jobs are ran. -There are two types of jobs: test jobs (ran in parallel) and deploy jobs (ran on success). - -### Test job (run in parallel) - -![Jobs](job.png) - -These kind of jobs run in parallel and can be useful for test suites. -For example, to save time you can run one part of your test suite in one build -and a second part in another build. - -Fields: - -`name` - an arbitrary name of a job - -`builds commit` (checkbox) - check this if you want to create a build on -pushes of regular commits and branches - -`build tag` (checkbox) - check this if you want to start a build on each tag pushed - -_For example, for GitLab we created a job for building packages. We want packages to be built when we push -new tags. So what we did is disable `builds commit` and we enabled `build tag`._ - -`tags` - the list of tags (ex. "ruby mysql silenium"), only runners that contain the same set of tags can perform this build. - -Script - your shell script to run. Example for rails projects: - -``` -export PATH=~/bin:/usr/local/bin:/usr/bin:/bin -gem install bundler -cp config/database.yml.mysql config/database.yml -cp config/application.yml.example config/application.yml -bundle -RAILS_ENV=test bundle exec rake db:setup -RAILS_ENV=test bundle exec rake spec -``` - - -### Deploy jobs (ran on success) - -![Deploy jobs](deploy_job.png) - -This type of jobs runs after all test jobs pass. -It is especially useful for deploying applications. -For example, if you want to make sure that whole test suite passes before each deploy. - -Fields: - -`name` - an arbitrary name of the deploy job - -`tags` - Just like test jobs, you probably have a specific runner in mind -that can deploy your code, as this runner needs special permissions, for instance. -Here you can set the tags for the runners that are allowed to run the deploy job. - -`refs` - Here you can specify git refs that should trigger a deploy job - -`script` - The actual shell script to run. diff --git a/doc/jobs/deploy_job.png b/doc/jobs/deploy_job.png Binary files differdeleted file mode 100644 index f940710..0000000 --- a/doc/jobs/deploy_job.png +++ /dev/null diff --git a/doc/jobs/job.png b/doc/jobs/job.png Binary files differdeleted file mode 100644 index 760d107..0000000 --- a/doc/jobs/job.png +++ /dev/null diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md index efa95ef..5c54960 100644 --- a/doc/raketasks/backup_restore.md +++ b/doc/raketasks/backup_restore.md @@ -218,3 +218,11 @@ gitlab_ci['backup_keep_time'] = 604800 NOTE: This cron job does not [backup your omnibus-gitlab configuration](#backup-and-restore-omnibus-gitlab-configuration). +## Known issues + +If you’ve been using GitLab CI since 7.11 or before using MySQL and the official installation guide, you will probably get the following error while making a backup: `Dumping MySQL database gitlab_ci_production ... mysqldump: Got error: 1044: Access denied for user 'gitlab_ci'@'localhost' to database 'gitlab_ci_production' when using LOCK TABLES` .This can be resolved by adding a LOCK TABLES permission to the gitlab_ci MySQL user. Add this permission with: +``` +$ mysql -u root -p +mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER, LOCK TABLES ON `gitlab_ci_production`.* TO 'gitlab_ci'@'localhost'; +``` + diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 7417ef5..2089e23 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -29,11 +29,6 @@ module API expose :id, :project_id, :url end - class Job < Grape::Entity - expose :id, :project_id, :commands, :active, :name, :build_branches, - :build_tags, :tags, :job_type, :tag_list - end - class DeployJob < Grape::Entity expose :id, :project_id, :commands, :active, :name, :refs, :tags, :job_type, :refs, :tag_list diff --git a/lib/api/projects.rb b/lib/api/projects.rb index ea2ceeb..3ca099a 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -28,116 +28,6 @@ module API end end - # Retrieve all jobs for a project - # - # Parameters - # id (required) - The ID of a project - # Example Request - # GET /projects/:id/jobs - get ":id/jobs" do - project = Project.find(params[:id]) - - unauthorized! unless current_user.can_manage_project?(project.gitlab_id) - - project.jobs - end - - # Add a new job to a project - # - # Parameters - # id (required) - The ID of a project - # name (required) - The job name - # commands (required) - The command line script for the job - # active (optional) - The command is active of not - # build_branches (optional) - Trigger commit builds - # build_tags (optional) - Trigger tag builds - # tags (optional) - The tags associated with this job - # Example Request - # POST /projects/:id/jobs - post ":id/jobs" do - required_attributes! [:name, :commands] - - project = Project.find(params[:id]) - - unauthorized! unless current_user.can_manage_project?(project.gitlab_id) - - job_params = - { - name: params[:name], - commands: params[:commands], - } - - job_params[:active] = params[:active] unless params[:active].nil? - job_params[:build_branches] = params[:build_branches] unless params[:build_branches].nil? - job_params[:build_tags] = params[:build_tags] unless params[:build_tags].nil? - job_params[:tag_list] = params[:tags] unless params[:tags].nil? - - job = project.jobs.new(job_params) - if job.save - present job, with: Entities::Job - else - errors = job.errors.full_messages.join(", ") - render_api_error!(errors, 400) - end - end - - # Add a new deploy job to a project - # - # Parameters - # id (required) - The ID of a project - # name (required) - The job name - # commands (required) - The command line script for the job - # active (optional) - The command is active of not - # refs (optional) - The list of refs - # tags (optional) - The tags associated with this job - # Example Request - # POST /projects/:id/deploy_jobs - post ":id/deploy_jobs" do - required_attributes! [:name, :commands] - - project = Project.find(params[:id]) - - unauthorized! unless current_user.can_manage_project?(project.gitlab_id) - - job_params = - { - name: params[:name], - commands: params[:commands], - job_type: "deploy" - } - - job_params[:active] = params[:active] unless params[:active].nil? - job_params[:refs] = params[:refs] unless params[:refs].nil? - job_params[:tag_list] = params[:tags] unless params[:tags].nil? - - job = project.jobs.new(job_params) - if job.save - present job, with: Entities::DeployJob - else - errors = job.errors.full_messages.join(", ") - render_api_error!(errors, 400) - end - end - - # Delete a job for a project - # - # Parameters - # id (required) - The ID of a project - # job_id (required) - The ID of the job to delete - # Example Request - # DELETE /projects/:id/jobs/:job_id - delete ":id/jobs/:job_id" do - required_attributes! [:job_id] - - project = Project.find(params[:id]) - - unauthorized! unless current_user.can_manage_project?(project.gitlab_id) - - job = project.jobs.find(params[:job_id]) - - job.destroy - end - # Retrieve all Gitlab CI projects that the user has access to # # Example Request: @@ -204,7 +94,6 @@ module API project = Project.new(filtered_params) project.build_missing_services - project.build_default_job if project.save present project, with: Entities::Project diff --git a/lib/gitlab_ci_yaml_processor.rb b/lib/gitlab_ci_yaml_processor.rb new file mode 100644 index 0000000..3368232 --- /dev/null +++ b/lib/gitlab_ci_yaml_processor.rb @@ -0,0 +1,106 @@ +class GitlabCiYamlProcessor + attr_reader :before_script, :skip_refs + + def initialize(config) + @config = YAML.load(config).deep_symbolize_keys + @skip_refs = @config[:skip_refs] || "" + @before_script = @config[:before_script] || [] + @jobs = @config[:jobs] || [] + @deploy_jobs = @config[:deploy_jobs] || [] + end + + def builds + normalized_jobs.map do |job| + { + name: job[:name], + commands: "#{normalized_script(@before_script)}\n#{job[:script]}", + tag_list: job[:runner], + branches: job[:branches], + tags: job[:tag] + } + end + end + + def deploy_builds + normalized_deploy_jobs.map do |job| + { + name: job[:name], + commands: "#{normalized_script(@before_script)}\n#{job[:script]}", + deploy: true, + refs: job[:refs], + tag_list: job[:runner] + } + end + end + + def create_commit_for_tag?(ref) + normalized_jobs.any?{|job| job[:tags] == true} || + normalized_deploy_jobs.any?{|job| job[:refs].empty? || refs_matches?(job[:refs], ref)} + end + + def deploy_builds_for_ref(ref) + deploy_builds.select do |build_attrs| + refs = build_attrs.delete(:refs) + refs.empty? || refs_matches?(refs, ref) + end + end + + def skip_ref?(ref_name) + @skip_refs.split(",").each do |ref| + return true if File.fnmatch(ref, ref_name) + end + + false + end + + private + + # refs - list of refs. Glob syntax is supported. Ex. ["feature*", "bug"] + # ref - ref that should be checked + def refs_matches?(refs, ref) + refs.each do |ref_pattern| + return true if File.fnmatch(ref_pattern, ref) + end + + false + end + + def normalized_jobs + @jobs.map do |job| + if job.is_a?(String) + { script: job, runner: "", name: job[0..10], branches: true, tags: true } + else + { + script: normalized_script(job[:script]), + runner: job[:runner] || "", + name: job[:name] || job[:script][0..10], + branches: job[:branches].nil? ? true : job[:branches], + tags: job[:tags].nil? ? true : job[:tags] + } + end + end + end + + def normalized_deploy_jobs + @deploy_jobs.map do |job| + if job.is_a?(String) + { script: job, runner: "", refs: [], name: job[0..10].strip } + else + { + script: normalized_script(job[:script]), + refs: (job[:refs] || "").split(",").map(&:strip), + name: job[:name] || job[:script][0..10].strip, + runner: job[:runner] || "", + } + end + end + end + + def normalized_script(script) + if script.is_a? Array + script.map(&:strip).join("\n") + else + script.strip + end + end +end diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index 17f6417..455a213 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -11,7 +11,8 @@ describe ProjectsController do ref: 'master', before: '2aa371379db71ac89ae20843fcff3b3477cf1a1d', after: '1c8a9df454ef68c22c2a33cca8232bb50849e5c5', - token: @project.token + token: @project.token, + ci_yaml_file: gitlab_ci_yaml expect(response).to be_success @@ -23,14 +24,15 @@ describe ProjectsController do ref: 'master', before: '2aa371379db71ac89ae20843fcff3b3477cf1a1d', after: '0000000000000000000000000000000000000000', - token: @project.token + token: @project.token, + ci_yaml_file: gitlab_ci_yaml expect(response).not_to be_success expect(response.code).to eq('400') end it 'should respond 400 if some params missed' do - post :build, id: @project.id, token: @project.token + post :build, id: @project.id, token: @project.token, ci_yaml_file: gitlab_ci_yaml expect(response).not_to be_success expect(response.code).to eq('400') end @@ -43,7 +45,7 @@ describe ProjectsController do end describe "POST /projects" do - let(:project_dump) { File.read(Rails.root.join('spec/support/gitlab_stubs/raw_project.yml')) } + let(:project_dump) { YAML.load File.read(Rails.root.join('spec/support/gitlab_stubs/raw_project.yml')) } let(:gitlab_url) { GitlabCi.config.gitlab_server.url } let (:user_data) do @@ -61,7 +63,7 @@ describe ProjectsController do Network.any_instance.stub(:enable_ci).and_return(true) Network.any_instance.stub(:project_hooks).and_return(true) - post :create, { project: project_dump }.with_indifferent_access + post :create, { project: JSON.dump(project_dump.to_h) }.with_indifferent_access expect(response.code).to eq('302') expect(assigns(:project)).not_to be_a_new(Project) @@ -72,7 +74,7 @@ describe ProjectsController do allow(controller).to receive(:current_user) { user } User.any_instance.stub(:can_manage_project?).and_return(false) - post :create, { project: project_dump }.with_indifferent_access + post :create, { project: JSON.dump(project_dump.to_h) }.with_indifferent_access expect(response.code).to eq('302') expect(flash[:alert]).to include("You have to have at least master role to enable CI for this project") diff --git a/spec/factories/builds.rb b/spec/factories/builds.rb index 2aa6c03..724cd5f 100644 --- a/spec/factories/builds.rb +++ b/spec/factories/builds.rb @@ -14,7 +14,6 @@ # commit_id :integer # coverage :float # commands :text -# job_id :integer # # Read about factories at https://github.com/thoughtbot/factory_girl diff --git a/spec/factories/commits.rb b/spec/factories/commits.rb index 3674643..4779f37 100644 --- a/spec/factories/commits.rb +++ b/spec/factories/commits.rb @@ -13,7 +13,6 @@ # # Read about factories at https://github.com/thoughtbot/factory_girl - FactoryGirl.define do factory :commit do ref 'master' @@ -44,7 +43,8 @@ FactoryGirl.define do } } ], - total_commits_count: 1 + total_commits_count: 1, + ci_yaml_file: File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')) } end end diff --git a/spec/factories/job.rb b/spec/factories/job.rb deleted file mode 100644 index 9a00a13..0000000 --- a/spec/factories/job.rb +++ /dev/null @@ -1,10 +0,0 @@ -FactoryGirl.define do - factory :job do - name 'rspec' - commands 'bundle exec rspec spec' - - factory :deploy_job do - job_type :deploy - end - end -end diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb index 6df53f2..f571587 100644 --- a/spec/factories/projects.rb +++ b/spec/factories/projects.rb @@ -51,9 +51,5 @@ FactoryGirl.define do factory :public_project do public true end - - before :create do |project| - project.build_default_job - end end end diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb index 2108d2c..deaf729 100644 --- a/spec/features/commits_spec.rb +++ b/spec/features/commits_spec.rb @@ -6,8 +6,7 @@ describe "Commits" do login_as :user @project = FactoryGirl.create :project @commit = FactoryGirl.create :commit, project: @project - @job = FactoryGirl.create :job, project: @project - @build = FactoryGirl.create :build, commit: @commit, job: @job + @build = FactoryGirl.create :build, commit: @commit end describe "GET /:project/commits/:sha" do @@ -25,8 +24,7 @@ describe "Commits" do before do @project = FactoryGirl.create :public_project @commit = FactoryGirl.create :commit, project: @project - @job = FactoryGirl.create :job, project: @project - @build = FactoryGirl.create :build, commit: @commit, job: @job + @build = FactoryGirl.create :build, commit: @commit end describe "GET /:project/commits/:sha" do diff --git a/spec/features/jobs_spec.rb b/spec/features/jobs_spec.rb deleted file mode 100644 index 6945a8f..0000000 --- a/spec/features/jobs_spec.rb +++ /dev/null @@ -1,67 +0,0 @@ -require 'spec_helper' - -describe "Jobs" do - before do - login_as :user - @project = FactoryGirl.create :project - end - - describe "GET /projects/:id/jobs" do - before do - visit project_jobs_path(@project) - end - - it { page.should have_content @project.name } - it { page.should have_link 'Add a job' } - - describe 'change job script' do - it "updates job" do - fill_in 'Script', with: 'pwd' - fill_in 'Name', with: 'New Job' - fill_in 'Tags', with: 'Tags' - check "Builds commits" - check "Build tags" - - click_button 'Save changes' - - page.should have_content 'successfully updated' - - find_field('Script').value.should eq 'pwd' - find_field('Name').value.should eq 'New Job' - find_field('Tags').value.should eq 'Tags' - find_field('Builds commits').should be_checked - find_field('Build tags').should be_checked - end - end - end - - describe "GET /projects/:id/jobs/deploy_jobs" do - before do - visit deploy_jobs_project_jobs_path(@project) - end - - it { page.should have_content @project.name } - it { page.should have_link 'Add a job' } - it { page.should have_content 'Deploy jobs are scripts you want CI to run on succeeding all parallel builds' } - - describe 'change job script', js: true do - it "updates deploy job" do - click_on "Add a job" - - fill_in 'Script', with: 'pwd' - fill_in 'Name', with: 'New Job' - fill_in 'Tags', with: 'Tags' - fill_in 'Refs', with: 'master' - - click_button 'Save changes' - - page.should have_content 'successfully updated' - - find_field('Script').value.should eq 'pwd' - find_field('Name').value.should eq 'New Job' - find_field('Tags').value.should eq 'Tags' - find_field('Refs').value.should eq 'master' - end - end - end -end diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb index f01dc11..3f21af9 100644 --- a/spec/features/projects_spec.rb +++ b/spec/features/projects_spec.rb @@ -34,12 +34,12 @@ describe "Projects" do it { page.should have_content 'Build Schedule' } it "updates configuration" do - fill_in 'Skip refs', with: 'deploy' + fill_in 'Timeout', with: '70' click_button 'Save changes' page.should have_content 'was successfully updated' - find_field('Skip refs').value.should eq 'deploy' + find_field('Timeout').value.should eq '70' end end diff --git a/spec/lib/gitlab_ci_yaml_processor_spec.rb b/spec/lib/gitlab_ci_yaml_processor_spec.rb new file mode 100644 index 0000000..2ce140c --- /dev/null +++ b/spec/lib/gitlab_ci_yaml_processor_spec.rb @@ -0,0 +1,218 @@ +require 'spec_helper' + +describe GitlabCiYamlProcessor do + + describe "#builds" do + it "returns builds from string" do + config = YAML.dump({ + jobs: ["ls"] + }) + + config_processor = GitlabCiYamlProcessor.new(config) + + config_processor.builds.size.should == 1 + config_processor.builds.first.should == { + branches: true, + commands: "\nls", + name: "ls", + tag_list: "", + tags: nil + } + end + + it "returns builds from string including before_script" do + config = YAML.dump({ + before_script: ["pwd"], + jobs: ["ls"] + }) + + config_processor = GitlabCiYamlProcessor.new(config) + + config_processor.builds.first.should == { + branches: true, + commands: "pwd\nls", + name: "ls", + tag_list: "", + tags: nil + } + end + + it "returns builds from job hash" do + config = YAML.dump({ + before_script: ["pwd"], + jobs: [{script: "ls", name: "Rspec", runner: "mysql,ruby"}] + }) + + config_processor = GitlabCiYamlProcessor.new(config) + + config_processor.builds.first.should == { + branches: true, + commands: "pwd\nls", + name: "Rspec", + tag_list: "mysql,ruby", + tags: nil + } + end + end + + describe "#deploy_builds" do + it "returns deploy builds from string" do + config = YAML.dump({ + deploy_jobs: ["ls"] + }) + + config_processor = GitlabCiYamlProcessor.new(config) + + config_processor.deploy_builds.size.should == 1 + config_processor.deploy_builds.first.should == { + commands: "\nls", + name: "ls", + deploy: true, + refs: [], + tag_list: "" + } + end + + it "returns deploy builds from string including before_script" do + config = YAML.dump({ + before_script: ["pwd"], + deploy_jobs: ["ls"] + }) + + config_processor = GitlabCiYamlProcessor.new(config) + + config_processor.deploy_builds.first.should == { + commands: "pwd\nls", + name: "ls", + deploy: true, + refs: [], + tag_list: "" + } + end + + it "returns deploy builds from job hash" do + config = YAML.dump({ + before_script: ["pwd"], + deploy_jobs: [{script: "ls", name: "Rspec", refs: "master,deploy"}] + }) + + config_processor = GitlabCiYamlProcessor.new(config) + + config_processor.deploy_builds.first.should == { + commands: "pwd\nls", + name: "Rspec", + deploy: true, + refs: ["master", "deploy"], + tag_list: "" + } + end + end + + describe "create_commit_for_tag?" do + it "returns true because there is a job for tags" do + config = YAML.dump({ + before_script: ["pwd"], + jobs: [{script: "ls", name: "Rspec", runners: "mysql,ruby", tags: true}], + deploy_jobs: ["ls"] + }) + + config_processor = GitlabCiYamlProcessor.new(config) + + config_processor.create_commit_for_tag?("deploy").should be_true + end + + it "returns true because there is a deploy job for this tag" do + config = YAML.dump({ + before_script: ["pwd"], + jobs: [{script: "ls", name: "Rspec", runner: "mysql,ruby", tags: false}], + deploy_jobs: [{script: "ls", refs: "deploy"}] + }) + + config_processor = GitlabCiYamlProcessor.new(config) + + config_processor.create_commit_for_tag?("deploy").should be_true + end + + it "returns true because there is a deploy job without tag specified" do + config = YAML.dump({ + before_script: ["pwd"], + jobs: [{script: "ls", name: "Rspec", runner: "mysql,ruby", tags: false}], + deploy_jobs: ["ls"] + }) + + config_processor = GitlabCiYamlProcessor.new(config) + + config_processor.create_commit_for_tag?("deploy").should be_true + end + + it "returns false because there is no deploy job for this ref nor job for tags" do + config = YAML.dump({ + before_script: ["pwd"], + jobs: [{script: "ls", name: "Rspec", runner: "mysql,ruby", tags: false}], + deploy_jobs: [{script: "ls", refs: "master"}] + }) + + config_processor = GitlabCiYamlProcessor.new(config) + + config_processor.create_commit_for_tag?("deploy").should be_false + end + end + + describe "#deploy_builds_for_ref" do + it "returns deploy job for ref" do + config = YAML.dump({ + before_script: ["pwd"], + deploy_jobs: [{script: "ls", name: "Deploy!1", refs: "deploy"}, {script: "pwd", refs: "staging"}] + }) + + config_processor = GitlabCiYamlProcessor.new(config) + + config_processor.deploy_builds_for_ref("deploy").size.should == 1 + config_processor.deploy_builds_for_ref("deploy").first[:name].should == 'Deploy!1' + end + + it "returns deploy job for ref including job without ref specified" do + config = YAML.dump({ + before_script: ["pwd"], + deploy_jobs: [{script: "ls", name: "Deploy!1", refs: "deploy"}, "pwd"] + }) + + config_processor = GitlabCiYamlProcessor.new(config) + + config_processor.deploy_builds_for_ref("deploy").size.should == 2 + end + + it "returns empty array because there is no deploy job for this ref" do + config = YAML.dump({ + before_script: ["pwd"], + deploy_jobs: [{script: "ls", name: "Deploy!1", refs: "deploy"}] + }) + + config_processor = GitlabCiYamlProcessor.new(config) + + config_processor.deploy_builds_for_ref("master").size.should == 0 + end + end + + describe "skip_ref?" do + it "skips ref" do + config = YAML.dump({ + skip_refs: "master" + }) + + config_processor = GitlabCiYamlProcessor.new(config) + + config_processor.skip_ref?("master").should be_true + config_processor.skip_ref?("deploy").should be_false + end + + it "does not skip ref if no refs specified" do + config = YAML.dump({}) + + config_processor = GitlabCiYamlProcessor.new(config) + + config_processor.skip_ref?("master").should be_false + end + end + +end
\ No newline at end of file diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb index 073a215..d996689 100644 --- a/spec/models/build_spec.rb +++ b/spec/models/build_spec.rb @@ -14,7 +14,6 @@ # commit_id :integer # coverage :float # commands :text -# job_id :integer # require 'spec_helper' diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb index 75d5610..1658ae6 100644 --- a/spec/models/commit_spec.rb +++ b/spec/models/commit_spec.rb @@ -132,10 +132,10 @@ describe Commit do describe "create_deploy_builds" do it "creates deploy build" do - FactoryGirl.create :job, job_type: :deploy, project: project - project.reload + config_processor = GitlabCiYamlProcessor.new(gitlab_ci_yaml) + commit.stub(:config_processor).and_return(config_processor) - commit.create_deploy_builds(commit.ref) + commit.create_deploy_builds commit.builds.reload commit.builds.size.should == 1 diff --git a/spec/models/job_spec.rb b/spec/models/job_spec.rb deleted file mode 100644 index d993139..0000000 --- a/spec/models/job_spec.rb +++ /dev/null @@ -1,45 +0,0 @@ -# == Schema Information -# -# Table name: jobs -# -# id :integer not null, primary key -# project_id :integer not null -# commands :text -# active :boolean default(TRUE), not null -# created_at :datetime -# updated_at :datetime -# name :string(255) -# build_branches :boolean default(TRUE), not null -# build_tags :boolean default(FALSE), not null -# job_type :string(255) default("parallel") -# refs :string(255) -# deleted_at :datetime -# - -require 'spec_helper' - -describe Job do - let(:project) { FactoryGirl.create :project } - - it { should belong_to(:project) } - it { should have_many(:builds) } - - describe "run_for_ref?" do - it "allows run for any ref if refs params is empty" do - job = FactoryGirl.create :job, project: project - job.run_for_ref?("anything").should be_true - end - - it "allows run for any ref in refs params" do - job = FactoryGirl.create :job, project: project, refs: "master, staging, tags/testing*" - job.run_for_ref?("master").should be_true - job.run_for_ref?("staging").should be_true - job.run_for_ref?("testing").should be_false - job.run_for_ref?("tags/testing").should be_true - job.run_for_ref?("tags/testing-v0.1.0").should be_true - job.run_for_ref?("tags/unstable-v0.1.0").should be_false - job.run_for_ref?("feature/feature-one").should be_false - job.run_for_ref?("anything").should be_false - end - end -end diff --git a/spec/models/project_services/hip_chat_message_spec.rb b/spec/models/project_services/hip_chat_message_spec.rb index 6096b5c..d4bfb31 100644 --- a/spec/models/project_services/hip_chat_message_spec.rb +++ b/spec/models/project_services/hip_chat_message_spec.rb @@ -4,62 +4,76 @@ describe HipChatMessage do subject { HipChatMessage.new(build) } let(:project) { FactoryGirl.create(:project) } - let(:commit) { FactoryGirl.create(:commit, project: project) } - let(:job) { FactoryGirl.create(:job, project: project) } - let(:build) { FactoryGirl.create(:build, commit: commit, job: job, status: 'success') } - context 'when build succeeds' do - - before { build.save } - - it 'returns a successful message' do - expect( subject.status_color ).to eq 'green' - expect( subject.notify? ).to be_false - expect( subject.to_s ).to match(/Build '[^']+' #\d+/) - expect( subject.to_s ).to match(/Successful in \d+ second\(s\)\./) + context "One build" do + let(:commit) do + commit = FactoryGirl.create(:commit, project: project) + commit.push_data[:ci_yaml_file] = YAML.dump({jobs: ["ls"]}) + commit.save + commit end - end - context 'when build fails' do + let(:build) do + commit.create_builds + commit.builds.first + end - before do - build.status = 'failed' - build.save + context 'when build succeeds' do + it 'returns a successful message' do + build.update(status: "success") + + expect( subject.status_color ).to eq 'green' + expect( subject.notify? ).to be_false + expect( subject.to_s ).to match(/Build '[^']+' #\d+/) + expect( subject.to_s ).to match(/Successful in \d+ second\(s\)\./) + end end - it 'returns a failure message' do - expect( subject.status_color ).to eq 'red' - expect( subject.notify? ).to be_true - expect( subject.to_s ).to match(/Build '[^']+' #\d+/) - expect( subject.to_s ).to match(/Failed in \d+ second\(s\)\./) + context 'when build fails' do + it 'returns a failure message' do + build.update(status: "failed") + + expect( subject.status_color ).to eq 'red' + expect( subject.notify? ).to be_true + expect( subject.to_s ).to match(/Build '[^']+' #\d+/) + expect( subject.to_s ).to match(/Failed in \d+ second\(s\)\./) + end end end - context 'when all matrix builds succeed' do - let(:job2) { FactoryGirl.create(:job, project: project, name: 'Another Job') } - let(:build2) { FactoryGirl.create(:build, id: 10, commit: commit, job: job2, status: 'success') } - - before { build.save; build2.save } + context "Several builds" do + let(:commit) {commit = FactoryGirl.create(:commit, project: project)} - it 'returns a successful message' do - expect( subject.status_color ).to eq 'green' - expect( subject.notify? ).to be_false - expect( subject.to_s ).to match(/Commit #\d+/) - expect( subject.to_s ).to match(/Successful in \d+ second\(s\)\./) + let(:build) do + commit.builds.first end - end - context 'when at least one matrix build fails' do - let(:job2) { FactoryGirl.create(:job, project: project, name: 'Another Job') } - let(:build2) { FactoryGirl.create(:build, id: 10, commit: commit, job: job2, status: 'failed') } + context 'when all matrix builds succeed' do + it 'returns a successful message' do + commit.create_builds + commit.builds.update_all(status: "success") + commit.reload - before { build.save; build2.save } + expect( subject.status_color ).to eq 'green' + expect( subject.notify? ).to be_false + expect( subject.to_s ).to match(/Commit #\d+/) + expect( subject.to_s ).to match(/Successful in \d+ second\(s\)\./) + end + end - it 'returns a failure message' do - expect( subject.status_color ).to eq 'red' - expect( subject.notify? ).to be_true - expect( subject.to_s ).to match(/Commit #\d+/) - expect( subject.to_s ).to match(/Failed in \d+ second\(s\)\./) + context 'when at least one matrix build fails' do + it 'returns a failure message' do + commit.create_builds + first_build = commit.builds.first + second_build = commit.builds.last + first_build.update(status: "success") + second_build.update(status: "failed") + + expect( subject.status_color ).to eq 'red' + expect( subject.notify? ).to be_true + expect( subject.to_s ).to match(/Commit #\d+/) + expect( subject.to_s ).to match(/Failed in \d+ second\(s\)\./) + end end end end diff --git a/spec/models/project_services/slack_message_spec.rb b/spec/models/project_services/slack_message_spec.rb index 1fa2e31..03c0cd3 100644 --- a/spec/models/project_services/slack_message_spec.rb +++ b/spec/models/project_services/slack_message_spec.rb @@ -4,76 +4,90 @@ describe SlackMessage do subject { SlackMessage.new(commit) } let(:project) { FactoryGirl.create :project } - let(:commit) { FactoryGirl.create :commit, project: project } - let(:job) { FactoryGirl.create :job, project: project } - let(:build) { FactoryGirl.create :build, commit: commit, job: job, status: 'success' } - context 'when build succeeded' do - let(:color) { 'good' } - - before { build } + context "One build" do + let(:commit) do + commit = FactoryGirl.create(:commit, project: project) + commit.push_data[:ci_yaml_file] = YAML.dump({jobs: ["ls"]}) + commit.save + commit + end - it 'returns a message with succeeded build' do - subject.color.should == color - subject.fallback.should include('Build') - subject.fallback.should include("\##{build.id}") - subject.fallback.should include('succeeded') - subject.attachments.first[:fields].should be_empty + let(:build) do + commit.create_builds + commit.builds.first end - end - context 'when build failed' do - let(:color) { 'danger' } + context 'when build succeeded' do + let(:color) { 'good' } - before do - build.status = 'failed' - build.save - end + it 'returns a message with succeeded build' do + build.update(status: "success") - it 'returns a message with failed build' do - subject.color.should == color - subject.fallback.should include('Build') - subject.fallback.should include("\##{build.id}") - subject.fallback.should include('failed') - subject.attachments.first[:fields].should be_empty + subject.color.should == color + subject.fallback.should include('Build') + subject.fallback.should include("\##{build.id}") + subject.fallback.should include('succeeded') + subject.attachments.first[:fields].should be_empty + end end - end - context 'when all matrix builds succeeded' do - let(:job2) { FactoryGirl.create :job, project: project } - let(:build2) { FactoryGirl.create :build, commit: commit, job: job2, status: 'success' } - let(:color) { 'good' } + context 'when build failed' do + let(:color) { 'danger' } - before { build; build2 } + it 'returns a message with failed build' do + build.update(status: "failed") - it 'returns a message with success' do - subject.color.should == color - subject.fallback.should include('Commit') - subject.fallback.should include("\##{commit.id}") - subject.fallback.should include('succeeded') - subject.attachments.first[:fields].should be_empty + subject.color.should == color + subject.fallback.should include('Build') + subject.fallback.should include("\##{build.id}") + subject.fallback.should include('failed') + subject.attachments.first[:fields].should be_empty + end end end - context 'when one of matrix builds failed' do - let(:job2) { FactoryGirl.create :job, project: project, name: 'Test JOB' } - let(:build2) { FactoryGirl.create :build, id: 10, commit: commit, job: job2, status: 'success' } - let(:color) { 'danger' } + context "Several builds" do + let(:commit) {commit = FactoryGirl.create(:commit, project: project)} - before do - build - build2.status = 'failed' - build2.save + let(:build) do + commit.builds.first end - it 'returns a message with information about failed build' do - subject.color.should == color - subject.fallback.should include('Commit') - subject.fallback.should include("\##{commit.id}") - subject.fallback.should include('failed') - subject.attachments.first[:fields].size.should == 1 - subject.attachments.first[:fields].first[:title].should == build2.job_name - subject.attachments.first[:fields].first[:value].should include("\##{build2.id}") + context 'when all matrix builds succeeded' do + let(:color) { 'good' } + + it 'returns a message with success' do + commit.create_builds + commit.builds.update_all(status: "success") + commit.reload + + subject.color.should == color + subject.fallback.should include('Commit') + subject.fallback.should include("\##{commit.id}") + subject.fallback.should include('succeeded') + subject.attachments.first[:fields].should be_empty + end + end + + context 'when one of matrix builds failed' do + let(:color) { 'danger' } + + it 'returns a message with information about failed build' do + commit.create_builds + first_build = commit.builds.first + second_build = commit.builds.last + first_build.update(status: "success") + second_build.update(status: "failed") + + subject.color.should == color + subject.fallback.should include('Commit') + subject.fallback.should include("\##{commit.id}") + subject.fallback.should include('failed') + subject.attachments.first[:fields].size.should == 1 + subject.attachments.first[:fields].first[:title].should == second_build.name + subject.attachments.first[:fields].first[:value].should include("\##{second_build.id}") + end end end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 9358db5..c27f05a 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -123,11 +123,10 @@ describe Project do end describe 'Project.parse' do - let(:project_dump) { File.read(Rails.root.join('spec/support/gitlab_stubs/raw_project.yml')) } + let(:project_dump) { YAML.load File.read(Rails.root.join('spec/support/gitlab_stubs/raw_project.yml')) } let(:parsed_project) { Project.parse(project_dump) } - before { parsed_project.build_default_job } - + it { parsed_project.should be_valid } it { parsed_project.should be_kind_of(Project) } it { parsed_project.name.should eq("GitLab / api.gitlab.org") } @@ -135,8 +134,7 @@ describe Project do it { parsed_project.gitlab_url.should eq("http://demo.gitlab.com/gitlab/api-gitlab-org") } it "parses plain hash" do - data = YAML.load(project_dump) - Project.parse(data).name.should eq("GitLab / api.gitlab.org") + Project.parse(project_dump).name.should eq("GitLab / api.gitlab.org") end end @@ -152,22 +150,6 @@ describe Project do it { should include(project.gitlab_url[7..-1]) } end - describe "#skip_ref?" do - let(:project) { FactoryGirl.create(:project, skip_refs: "master, develop, feature/*") } - - it 'returns true when item is not in list' do - expect(project.skip_ref?('someotherstring')).to eq false - end - - it 'accepts string values' do - expect(project.skip_ref?('master')).to eq true - end - - it 'accepts glob pattern syntax' do - expect(project.skip_ref?('feature/some_feature')).to eq true - end - end - describe :search do let!(:project) { FactoryGirl.create(:project, name: "foo") } diff --git a/spec/requests/api/builds_spec.rb b/spec/requests/api/builds_spec.rb index 2960388..2dd6e01 100644 --- a/spec/requests/api/builds_spec.rb +++ b/spec/requests/api/builds_spec.rb @@ -17,8 +17,8 @@ describe API::API do describe "POST /builds/register" do it "should start a build" do commit = FactoryGirl.create(:commit, project: project) - job = FactoryGirl.create :job, project: project - build = commit.create_builds.first + commit.create_builds + build = commit.builds.first post api("/builds/register"), token: runner.token, info: {platform: :darwin} diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index 87072bb..190df70 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -43,7 +43,8 @@ describe API::API, 'Commits' do "email" => "jordi@softcatala.org", } } - ] + ], + ci_yaml_file: gitlab_ci_yaml } } diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index ffcf638..7cdd3f4 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -45,203 +45,7 @@ describe API::API do end end end - - describe "POST /projects/:project_id/jobs" do - let!(:project) { FactoryGirl.create(:project) } - - let(:job_info) { - { - name: "A Job Name", - commands: "ls -lad", - active: false, - build_branches: false, - build_tags: true, - tags: "release, deployment", - } - } - let(:invalid_job_info) { {} } - - context "Invalid Job Info" do - before do - options.merge!(invalid_job_info) - end - - it "should error with invalid data" do - post api("/projects/#{project.id}/jobs"), options - response.status.should == 400 - end - end - - context "Valid Job Info" do - before do - options.merge!(job_info) - end - - it "should create a job for specified project" do - post api("/projects/#{project.id}/jobs"), options - response.status.should == 201 - json_response["name"].should == job_info[:name] - json_response["commands"].should == job_info[:commands] - json_response["active"].should == job_info[:active] - json_response["build_branches"].should == job_info[:build_branches] - json_response["build_tags"].should == job_info[:build_tags] - json_response["tags"].should have(2).items - end - - it "fails to create job for non existsing project" do - post api("/projects/non-existant-id/jobs"), options - response.status.should == 404 - end - - it "non-manager is not authorized" do - User.any_instance.stub(:can_manage_project?).and_return(false) - post api("/projects/#{project.id}/jobs"), options - response.status.should == 401 - end - end - end - - describe "POST /projects/:project_id/deploy_jobs" do - let!(:project) { FactoryGirl.create(:project) } - - let(:job_info) { - { - name: "A Job Name", - commands: "ls -lad", - active: false, - refs: "master", - tags: "release, deployment", - } - } - let(:invalid_job_info) { {} } - - context "Invalid Job Info" do - before do - options.merge!(invalid_job_info) - end - - it "should error with invalid data" do - post api("/projects/#{project.id}/deploy_jobs"), options - response.status.should == 400 - end - end - - context "Valid Job Info" do - before do - options.merge!(job_info) - end - - it "should create a job for specified project" do - post api("/projects/#{project.id}/deploy_jobs"), options - response.status.should == 201 - json_response["name"].should == job_info[:name] - json_response["commands"].should == job_info[:commands] - json_response["active"].should == job_info[:active] - json_response["refs"].should == job_info[:refs] - json_response["tags"].should have(2).items - end - - it "fails to create job for non existsing project" do - post api("/projects/non-existant-id/deploy_jobs"), options - response.status.should == 404 - end - - it "non-manager is not authorized" do - User.any_instance.stub(:can_manage_project?).and_return(false) - post api("/projects/#{project.id}/deploy_jobs"), options - response.status.should == 401 - end - end - end - - - describe "GET /projects/:project_id/jobs" do - let!(:project) { FactoryGirl.create(:project) } - let(:job_info) { - { - name: "A Job Name", - commands: "ls -lad", - } - } - - before do - options.merge!(job_info) - end - - it "should list the project's jobs" do - get api("/projects/#{project.id}/jobs"), options - response.status.should == 200 - json_response.count.should == 1 - json_response.first["project_id"].should == project.id - end - - it "should delete default job, add & list the project's new jobs" do - # delete default job - get api("/projects/#{project.id}/jobs"), options - response.status.should == 200 - json_response.count.should == 1 - job_id = json_response.first["id"] - delete api("/projects/#{project.id}/jobs/#{job_id}"), options - # add a new job - post api("/projects/#{project.id}/jobs"), options - response.status.should == 201 - # get the new job - get api("/projects/#{project.id}/jobs"), options - # assert job - response.status.should == 200 - json_response.count.should == 1 - json_response.first["project_id"].should == project.id - json_response.first["name"].should == job_info[:name] - json_response.first["commands"].should == job_info[:commands] - end - - it "fails to list jobs for non existsing project" do - get api("/projects/non-existant-id/jobs"), options - response.status.should == 404 - end - end - - describe "DELETE /projects/:id/jobs/:job_id" do - let!(:project) { FactoryGirl.create(:project) } - - let(:job_info) { - { - commands: "ls -lad", - name: "A Job Name", - } - } - - before do - options.merge!(job_info) - end - - it "should delete a project job" do - job = FactoryGirl.create(:job, project: project) - - delete api("/projects/#{project.id}/jobs/#{job.id}"), options - response.status.should == 200 - end - - it "fails to delete a job for a non existsing project" do - delete api("/projects/non-existant-id/jobs/non-existant-job-id"), options - response.status.should == 404 - end - - it "fails to delete a job for a non existsing job id" do - delete api("/projects/#{project.id}/jobs/non-existant-job-id"), options - response.status.should == 404 - end - - it "non-manager is not authorized" do - User.any_instance.stub(:can_manage_project?).and_return(false) - job = FactoryGirl.create(:job, project: project) - - delete api("/projects/#{project.id}/jobs/#{job.id}"), options - - response.status.should == 401 - end - end - + describe "POST /projects/:project_id/webhooks" do let!(:project) { FactoryGirl.create(:project) } diff --git a/spec/services/create_commit_service_spec.rb b/spec/services/create_commit_service_spec.rb index 7a4ff7d..4b4f788 100644 --- a/spec/services/create_commit_service_spec.rb +++ b/spec/services/create_commit_service_spec.rb @@ -6,7 +6,14 @@ describe CreateCommitService do describe :execute do context 'valid params' do - let(:commit) { service.execute(project, ref: 'refs/heads/master', before: '00000000', after: '31das312') } + let(:commit) do + service.execute(project, + ref: 'refs/heads/master', + before: '00000000', + after: '31das312', + ci_yaml_file: gitlab_ci_yaml + ) + end it { commit.should be_kind_of(Commit) } it { commit.should be_valid } @@ -15,61 +22,50 @@ describe CreateCommitService do it { commit.builds.first.should be_kind_of(Build) } end - context 'without params' do - subject { service.execute(project, {}) } - - it { should be_false } - end - context "deploy builds" do it "calls create_deploy_builds if there are no builds" do - project.jobs.destroy_all + config = YAML.dump({jobs: [], build_jobs: ["ls"]}) Commit.any_instance.should_receive(:create_deploy_builds) - service.execute(project, ref: 'refs/heads/master', before: '00000000', after: '31das312') + service.execute(project, ref: 'refs/heads/master', before: '00000000', after: '31das312', ci_yaml_file: config) end it "does not call create_deploy_builds if there is build" do + config = YAML.dump({jobs: ["ls"], build_jobs: ["ls"]}) Commit.any_instance.should_not_receive(:create_deploy_builds) - service.execute(project, ref: 'refs/heads/master', before: '00000000', after: '31das312') + service.execute(project, ref: 'refs/heads/master', before: '00000000', after: '31das312', ci_yaml_file: config) end end context "skip tag if there is no build for it" do - it "does not create commit if there is no appropriate job" do - project.jobs - - result = service.execute(project, ref: 'refs/tags/0_1', before: '00000000', after: '31das312') - result.should be_false - end - it "creates commit if there is appropriate job" do - project.jobs.first.update(build_tags: true) - - result = service.execute(project, ref: 'refs/tags/0_1', before: '00000000', after: '31das312') + result = service.execute(project, + ref: 'refs/tags/0_1', + before: '00000000', + after: '31das312', + ci_yaml_file: gitlab_ci_yaml + ) result.should be_persisted end it "does not create commit if there is no appropriate job nor deploy job" do - project.jobs.first.update(build_tags: false) - FactoryGirl.create(:deploy_job, project: project, refs: "release") - - result = service.execute(project, ref: 'refs/tags/0_1', before: '00000000', after: '31das312') + result = service.execute(project, + ref: 'refs/tags/0_1', + before: '00000000', + after: '31das312', + ci_yaml_file: YAML.dump({}) + ) result.should be_false end it "creates commit if there is no appropriate job but deploy job has right ref setting" do - project.jobs.first.update(build_tags: false) - FactoryGirl.create(:deploy_job, project: project, refs: "0_1") - - result = service.execute(project, ref: 'refs/tags/0_1', before: '00000000', after: '31das312') - result.should be_persisted - end - - it "creates commit if there is no appropriate job and deploy job has no ref setting" do - project.jobs.first.update(build_tags: true) - FactoryGirl.create(:deploy_job, project: project) - - result = service.execute(project, ref: 'refs/tags/0_1', before: '00000000', after: '31das312') + config = YAML.dump({deploy_jobs: [{script: "ls", refs: "0_1"}]}) + + result = service.execute(project, + ref: 'refs/heads/0_1', + before: '00000000', + after: '31das312', + ci_yaml_file: config + ) result.should be_persisted end end @@ -77,16 +73,27 @@ describe CreateCommitService do describe :ci_skip? do it "skips commit creation if there is [ci skip] tag in commit message" do commits = [{message: "some message[ci skip]"}] - result = service.execute(project, ref: 'refs/tags/0_1', before: '00000000', after: '31das312', commits: commits) + result = service.execute(project, + ref: 'refs/tags/0_1', + before: '00000000', + after: '31das312', + commits: commits, + ci_yaml_file: gitlab_ci_yaml + ) result.should be_false end it "does not skips commit creation if there is no [ci skip] tag in commit message" do - project.jobs.first.update(build_tags: true) - commits = [{message: "some message"}] - result = service.execute(project, ref: 'refs/tags/0_1', before: '00000000', after: '31das312', commits: commits) + result = service.execute(project, + ref: 'refs/tags/0_1', + before: '00000000', + after: '31das312', + commits: commits, + ci_yaml_file: gitlab_ci_yaml + ) + result.should be_persisted end end diff --git a/spec/services/create_project_service_spec.rb b/spec/services/create_project_service_spec.rb index 6ecb93e..3161496 100644 --- a/spec/services/create_project_service_spec.rb +++ b/spec/services/create_project_service_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe CreateProjectService do let(:service) { CreateProjectService.new } let(:current_user) { double.as_null_object } - let(:project_dump) { File.read(Rails.root.join('spec/support/gitlab_stubs/raw_project.yml')) } + let(:project_dump) { YAML.load File.read(Rails.root.join('spec/support/gitlab_stubs/raw_project.yml')) } before { Network.any_instance.stub(enable_ci: true) } @@ -24,7 +24,6 @@ describe CreateProjectService do context "forking" do it "uses project as a template for settings and jobs" do origin_project = FactoryGirl.create(:project) - origin_project.jobs << Job.new(commands: "pwd") origin_project.shared_runners_enabled = true origin_project.public = true origin_project.allow_git_fetch = true @@ -35,7 +34,6 @@ describe CreateProjectService do project.shared_runners_enabled.should be_true project.public.should be_true project.allow_git_fetch.should be_true - project.jobs.last.commands.should == "pwd" end end end diff --git a/spec/services/register_build_service_spec.rb b/spec/services/register_build_service_spec.rb index 7925dca..1407346 100644 --- a/spec/services/register_build_service_spec.rb +++ b/spec/services/register_build_service_spec.rb @@ -3,9 +3,8 @@ require 'spec_helper' describe RegisterBuildService do let!(:service) { RegisterBuildService.new } let!(:project) { FactoryGirl.create :project } - let!(:job) { FactoryGirl.create :job, project: project } let!(:commit) { FactoryGirl.create :commit, project: project } - let!(:pending_build) { commit.create_build_from_job(job) } + let!(:pending_build) { FactoryGirl.create :build, project: project, commit: commit } let!(:shared_runner) { FactoryGirl.create(:runner, is_shared: true) } let!(:specific_runner) { FactoryGirl.create(:runner, is_shared: false) } diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 4e8ef9a..54d3068 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -29,6 +29,7 @@ RSpec.configure do |config| config.include LoginHelpers, type: :feature config.include StubGitlabCalls + config.include StubGitlabData # ## Mock Framework # diff --git a/spec/support/gitlab_stubs/gitlab_ci.yml b/spec/support/gitlab_stubs/gitlab_ci.yml new file mode 100644 index 0000000..4f76e36 --- /dev/null +++ b/spec/support/gitlab_stubs/gitlab_ci.yml @@ -0,0 +1,24 @@ +# Refs to skip +skip_refs: “deploy-*” + +# Run before each script +before_script: + - ls + +# Parallel jobs, each line is parallel build +jobs: + - script: "rake spec" + runner: "ruby,postgres" + name: "Rspec" + - script: "rake spinach" + runner: "ruby,mysql" + name: "Spinach" + tags: true + branches: true + +# Parallel deploy jobs +deploy_jobs: + - "cap deploy production" + - script: "cap deploy staging" + refs: staging + name: "Deploy to staging" diff --git a/spec/support/stub_gitlab_data.rb b/spec/support/stub_gitlab_data.rb new file mode 100644 index 0000000..fa402f3 --- /dev/null +++ b/spec/support/stub_gitlab_data.rb @@ -0,0 +1,5 @@ +module StubGitlabData + def gitlab_ci_yaml + File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')) + end +end |