Commit 931ab01a authored by Lin Jen-Shin's avatar Lin Jen-Shin

Merge remote-tracking branch 'upstream/master' into 33149-rename-more-builds

* upstream/master: (34 commits)
  Revert "Merge branch 'karma-headless-chrome' into 'master'"
  Make small pipeline schedules UI enhancements.
  Remove js classes from vue component that are not needed in vue component
  Update tests and application
  Adds "Pipeline" to job's sidebar
  Change border color of job's scroll controllers to $border-color
  Add database helpers 'add_timestamps_with_timezone' and 'timestamps_with_timezone'
  Added Tectonic to the page.
  Always check read_issue permissions when loading issue
  Handle legacy jobs without name
  Do not expose internal artifacts hash in build entity
  Use wait_for_requests instead of sleep 0.3
  Limit wiki container width
  Fix migrations testing support RSpec hooks order
  Rename BuildEntity to JobEntity
  Fix support for external_url for commit statuses
  Allow to access pipelines even if they are disabled, but only present jobs and commit statuses without giving ability to access them
  add CHANGELOG.md entry for !12036
  remove phantomjs-specific test hacks
  update karma job to use chrome build image created by gitlab-build-images!41
  ...
parents f99cee8e b29bf626
......@@ -20,12 +20,6 @@ export default {
default: 'top',
},
shortFormat: {
type: Boolean,
required: false,
default: false,
},
cssClass: {
type: String,
required: false,
......@@ -37,18 +31,12 @@ export default {
tooltipMixin,
timeagoMixin,
],
computed: {
timeagoCssClass() {
return this.shortFormat ? 'js-short-timeago' : 'js-timeago';
},
},
};
</script>
<template>
<time
:class="[timeagoCssClass, cssClass]"
class="js-timeago js-timeago-render"
:class="cssClass"
class="js-vue-timeago"
:title="tooltipTitle(time)"
:data-placement="tooltipPlacement"
data-container="body"
......
......@@ -148,7 +148,8 @@ label {
margin-top: 35px;
}
.form-group .control-label {
.form-group .control-label,
.form-group .control-label-full-width {
font-weight: normal;
}
......
......@@ -51,6 +51,10 @@ body {
&.limit-container-width {
max-width: $limited-layout-width;
}
&.limit-container-width-sm {
max-width: 790px;
}
}
.alert-wrapper {
......
......@@ -72,7 +72,7 @@
display: flex;
justify-content: flex-end;
background: $gray-light;
border: 1px solid $gray-normal;
border: 1px solid $border-color;
color: $gl-text-color;
.truncated-info {
......
......@@ -12,7 +12,7 @@
.interval-pattern-form-group {
label {
margin-right: 10px;
font-size: 12px;
font-weight: normal;
&[for='custom'] {
margin-right: 0;
......
......@@ -42,9 +42,7 @@
}
.git-access-header {
padding: 16px 40px 11px 0;
line-height: 28px;
font-size: 18px;
padding: $gl-padding 0 $gl-padding-top;
}
.git-clone-holder {
......@@ -66,6 +64,7 @@
.git-clone-holder {
width: 480px;
padding-bottom: $gl-padding;
}
.nav-controls {
......@@ -89,9 +88,9 @@
margin: $gl-padding 0;
h3 {
font-size: 22px;
font-size: 19px;
font-weight: normal;
margin-top: 1.4em;
margin: $gl-padding 0;
}
}
......
......@@ -80,10 +80,6 @@ class Projects::ApplicationController < ApplicationController
cookies.permanent[:diff_view] = params.delete(:view) if params[:view].present?
end
def builds_enabled
return render_404 unless @project.feature_available?(:builds, current_user)
end
def require_pages_enabled!
not_found unless Gitlab.config.pages.enabled
end
......
......@@ -5,7 +5,6 @@ class Projects::GraphsController < Projects::ApplicationController
before_action :require_non_empty_project
before_action :assign_ref_vars
before_action :authorize_download_code!
before_action :builds_enabled, only: :ci
def show
respond_to do |format|
......
......@@ -10,11 +10,7 @@ class Projects::IssuesController < Projects::ApplicationController
before_action :redirect_to_external_issue_tracker, only: [:index, :new]
before_action :module_enabled
before_action :issue, only: [:edit, :update, :show, :referenced_merge_requests,
:related_branches, :can_create_branch, :realtime_changes, :create_merge_request]
# Allow read any issue
before_action :authorize_read_issue!, only: [:show, :realtime_changes]
before_action :issue, except: [:index, :new, :create, :bulk_update]
# Allow write(create) issue
before_action :authorize_create_issue!, only: [:new, :create]
......@@ -229,18 +225,19 @@ class Projects::IssuesController < Projects::ApplicationController
protected
def issue
return @issue if defined?(@issue)
# The Sortable default scope causes performance issues when used with find_by
@noteable = @issue ||= @project.issues.where(iid: params[:id]).reorder(nil).take!
return render_404 unless can?(current_user, :read_issue, @issue)
@issue
end
alias_method :subscribable_resource, :issue
alias_method :issuable, :issue
alias_method :awardable, :issue
alias_method :spammable, :issue
def authorize_read_issue!
return render_404 unless can?(current_user, :read_issue, @issue)
end
def authorize_update_issue!
return render_404 unless can?(current_user, :update_issue, @issue)
end
......
......@@ -4,7 +4,6 @@ class Projects::PipelinesController < Projects::ApplicationController
before_action :authorize_read_pipeline!
before_action :authorize_create_pipeline!, only: [:new, :create]
before_action :authorize_update_pipeline!, only: [:retry, :cancel]
before_action :builds_enabled, only: :charts
wrap_parameters Ci::Pipeline
......
......@@ -218,6 +218,10 @@ module ProjectsHelper
nav_tabs << :container_registry
end
if project.builds_enabled? && can?(current_user, :read_pipeline, project)
nav_tabs << :pipelines
end
tab_ability_map.each do |tab, ability|
if can?(current_user, ability, project)
nav_tabs << tab
......@@ -231,7 +235,6 @@ module ProjectsHelper
{
environments: :read_environment,
milestones: :read_milestone,
pipelines: :read_pipeline,
snippets: :read_project_snippet,
settings: :admin_project,
builds: :read_build,
......
......@@ -112,7 +112,7 @@ class CommitStatus < ActiveRecord::Base
end
def group_name
name.gsub(/\d+[\s:\/\\]+\d+\s*/, '').strip
name.to_s.gsub(/\d+[\s:\/\\]+\d+\s*/, '').strip
end
def failed_but_allowed?
......@@ -156,7 +156,7 @@ class CommitStatus < ActiveRecord::Base
end
def sortable_name
name.split(/(\d+)/).map do |v|
name.to_s.split(/(\d+)/).map do |v|
v =~ /\d+/ ? v.to_i : v
end
end
......
......@@ -11,6 +11,7 @@ class GenericCommitStatus < CommitStatus
def set_default_values
self.context ||= 'default'
self.stage ||= 'external'
self.stage_idx ||= 1000000
end
def tags
......
......@@ -203,7 +203,7 @@ class ProjectPolicy < BasePolicy
unless project.feature_available?(:builds, user) && repository_enabled
cannot!(*named_abilities(:build))
cannot!(*named_abilities(:pipeline))
cannot!(*named_abilities(:pipeline) - [:read_pipeline])
cannot!(*named_abilities(:pipeline_schedule))
cannot!(*named_abilities(:environment))
cannot!(*named_abilities(:deployment))
......
class BuildDetailsEntity < BuildEntity
class BuildDetailsEntity < JobEntity
expose :coverage, :erased_at, :duration
expose :tag_list, as: :tags
expose :user, using: UserEntity
expose :runner, using: RunnerEntity
expose :pipeline, using: PipelineEntity
expose :erased_by, if: -> (*) { build.erased? }, using: UserEntity
expose :erase_path, if: -> (*) { build.erasable? && can?(current_user, :update_build, project) } do |build|
erase_namespace_project_job_path(project.namespace, project, build)
end
expose :artifacts, using: BuildArtifactEntity
expose :runner, using: RunnerEntity
expose :pipeline, using: PipelineEntity
expose :merge_request, if: -> (*) { can?(current_user, :read_merge_request, build.merge_request) } do
expose :iid do |build|
build.merge_request.iid
......
class BuildSerializer < BaseSerializer
entity BuildEntity
entity JobEntity
def represent_status(resource)
data = represent(resource, { only: [:status] })
......
......@@ -24,6 +24,6 @@ class DeploymentEntity < Grape::Entity
expose :user, using: UserEntity
expose :commit, using: CommitEntity
expose :deployable, using: BuildEntity
expose :manual_actions, using: BuildEntity
expose :deployable, using: JobEntity
expose :manual_actions, using: JobEntity
end
class BuildEntity < Grape::Entity
class JobEntity < Grape::Entity
include RequestAwareEntity
expose :id
expose :name
expose :build_path do |build|
path_to(:namespace_project_job, build)
build.target_url || path_to(:namespace_project_job, build)
end
expose :retry_path, if: -> (*) { retryable? } do |build|
......
......@@ -4,7 +4,7 @@ class JobGroupEntity < Grape::Entity
expose :name
expose :size
expose :detailed_status, as: :status, with: StatusEntity
expose :jobs, with: BuildEntity
expose :jobs, with: JobEntity
private
......
......@@ -101,12 +101,12 @@ class GitPushService < BaseService
UpdateMergeRequestsWorker
.perform_async(@project.id, current_user.id, params[:oldrev], params[:newrev], params[:ref])
SystemHookPushWorker.perform_async(build_push_data.dup, :push_hooks)
EventCreateService.new.push(@project, current_user, build_push_data)
Ci::CreatePipelineService.new(@project, current_user, build_push_data).execute(:push)
SystemHookPushWorker.perform_async(build_push_data.dup, :push_hooks)
@project.execute_hooks(build_push_data.dup, :push_hooks)
@project.execute_services(build_push_data.dup, :push_hooks)
Ci::CreatePipelineService.new(@project, current_user, build_push_data).execute(:push)
if push_remove_branch?
AfterBranchDeleteService
......
......@@ -8,10 +8,12 @@ class GitTagPushService < BaseService
@push_data = build_push_data
EventCreateService.new.push(project, current_user, @push_data)
Ci::CreatePipelineService.new(project, current_user, @push_data).execute(:push)
SystemHooksService.new.execute_hooks(build_system_push_data.dup, :tag_push_hooks)
project.execute_hooks(@push_data.dup, :tag_push_hooks)
project.execute_services(@push_data.dup, :tag_push_hooks)
Ci::CreatePipelineService.new(project, current_user, @push_data).execute(:push)
ProjectCacheWorker.perform_async(project.id, [], [:commit_count, :repository_size])
true
......
......@@ -72,6 +72,7 @@
.title
%span{ class: "ci-status-icon-#{@build.pipeline.status}" }
= ci_icon_for_status(@build.pipeline.status)
Pipeline
= link_to "##{@build.pipeline.id}", namespace_project_pipeline_path(@project.namespace, @project, @build.pipeline), class: 'link-commit'
from
= link_to "#{@build.pipeline.ref}", namespace_project_branch_path(@project.namespace, @project, @build.pipeline.ref), class: 'link-commit'
......
......@@ -20,7 +20,7 @@
.form-group
.col-md-9
= f.label :ref, _('Target Branch'), class: 'label-light'
= dropdown_tag(_("Select target branch"), options: { toggle_class: 'btn js-target-branch-dropdown git-revision-dropdown-toggle', dropdown_class: 'git-revision-dropdown', title: _("Select target branch"), filter: true, placeholder: _("Filter"), data: { data: @project.repository.branch_names, default_branch: @project.default_branch } } )
= dropdown_tag(_("Select target branch"), options: { toggle_class: 'btn js-target-branch-dropdown', dropdown_class: 'git-revision-dropdown', title: _("Select target branch"), filter: true, placeholder: _("Filter"), data: { data: @project.repository.branch_names, default_branch: @project.default_branch } } )
= f.text_field :ref, value: @schedule.ref, id: 'schedule_ref', class: 'hidden', name: 'schedule[ref]', required: true
.form-group
.col-md-9
......
......@@ -5,13 +5,13 @@
= f.hidden_field :title, value: @page.title
.form-group
= f.label :format, class: 'control-label'
.col-sm-10
.col-sm-12= f.label :format, class: 'control-label-full-width'
.col-sm-12
= f.select :format, options_for_select(ProjectWiki::MARKUPS, {selected: @page.format}), {}, class: "form-control"
.form-group
= f.label :content, class: 'control-label'
.col-sm-10
.col-sm-12= f.label :content, class: 'control-label-full-width'
.col-sm-12
= render layout: 'projects/md_preview', locals: { url: namespace_project_wiki_preview_markdown_path(@project.namespace, @project, @page.slug) } do
= render 'projects/zen', f: f, attr: :content, classes: 'note-textarea', placeholder: 'Write your content or drag files here...'
= render 'shared/notes/hints'
......@@ -29,8 +29,8 @@
= link_to 'documentation', help_page_path("user/markdown", anchor: "wiki-specific-markdown")
.form-group
= f.label :commit_message, class: 'control-label'
.col-sm-10= f.text_field :message, class: 'form-control', rows: 18, value: commit_message
.col-sm-12= f.label :commit_message, class: 'control-label-full-width'
.col-sm-12= f.text_field :message, class: 'form-control', rows: 18, value: commit_message
.form-actions
- if @page && @page.persisted?
......
- @no_container = true
%div{ class: container_class }
#modal-new-wiki.modal
#modal-new-wiki.modal
.modal-dialog
.modal-content
.modal-header
......
- @no_container = true
- @content_class = "limit-container-width limit-container-width-sm" unless fluid_layout
- page_title "Edit", @page.title.capitalize, "Wiki"
%div{ class: container_class }
.wiki-page-header.has-sidebar-toggle
.wiki-page-header.has-sidebar-toggle
%button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" }
= icon('angle-double-left')
......@@ -30,6 +29,6 @@
= link_to namespace_project_wiki_path(@project.namespace, @project, @page), data: { confirm: "Are you sure you want to delete this page?"}, method: :delete, class: "btn btn-danger" do
Delete
= render 'form'
= render 'form'
= render 'sidebar'
- @no_container = true
- @content_class = "limit-container-width limit-container-width-sm" unless fluid_layout
- page_title "Git Access", "Wiki"
%div{ class: container_class }
.wiki-page-header.has-sidebar-toggle
.wiki-page-header.has-sidebar-toggle
%button.btn.btn-default.visible-xs.visible-sm.pull-right.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" }
= icon('angle-double-left')
......@@ -12,7 +11,7 @@
= render "shared/clone_panel", project: @project_wiki
.wiki-git-access
.wiki-git-access
%h3 Install Gollum
%pre.dark
:preserve
......
- page_title "History", @page.title.capitalize, "Wiki"
%div{ class: container_class }
.wiki-page-header.has-sidebar-toggle
.wiki-page-header.has-sidebar-toggle
%button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" }
= icon('angle-double-left')
......@@ -12,7 +11,7 @@
&middot;
History
.table-holder
.table-holder
%table.table
%thead
%tr
......
- @no_container = true
- @content_class = "limit-container-width limit-container-width-sm" unless fluid_layout
- page_title @page.title.capitalize, "Wiki"
%div{ class: container_class }
.wiki-page-header.has-sidebar-toggle
.wiki-page-header.has-sidebar-toggle
%button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" }
= icon('angle-double-left')
......@@ -20,12 +19,12 @@
.nav-controls
= render 'main_links'
- if @page.historical?
- if @page.historical?
.warning_message
This is an old version of this page.
You can view the #{link_to "most recent version", namespace_project_wiki_path(@project.namespace, @project, @page)} or browse the #{link_to "history", namespace_project_wiki_history_path(@project.namespace, @project, @page)}.
.wiki-holder.prepend-top-default.append-bottom-default
.wiki-holder.prepend-top-default.append-bottom-default
.wiki
= render_wiki_content(@page)
......
......@@ -19,7 +19,7 @@
= text_field_tag :project_clone, default_url_to_repo(project), class: "js-select-on-focus form-control", readonly: true, aria: { label: 'Project clone URL' }
.input-group-btn
= clipboard_button(target: '#project_clone', title: _("Copy URL to clipboard"))
= clipboard_button(target: '#project_clone', title: _("Copy URL to clipboard"), class: "btn-default btn-clipboard")
:javascript
$('ul.clone-options-dropdown a').on('click',function(e){
......
---
title: Add database helpers 'add_timestamps_with_timezone' and 'timestamps_with_timezone'
merge_request: 11229
author: @blackst0ne
---
title: Add support for image and services configuration in .gitlab-ci.yml
merge_request: 8578
author:
---
title: Github - Fix token interpolation when cloning wiki repository
merge_request:
author:
---
title: Fix support for external CI services
merge_request: 11176
author:
---
title: Handle nameless legacy jobs
merge_request:
author:
# ActiveRecord custom data type for storing datetimes with timezone information.
# See https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11229
if Gitlab::Database.postgresql?
require 'active_record/connection_adapters/postgresql_adapter'
module ActiveRecord
module ConnectionAdapters
class PostgreSQLAdapter
NATIVE_DATABASE_TYPES.merge!(datetime_with_timezone: { name: 'timestamptz' })
end
end
end
elsif Gitlab::Database.mysql?
require 'active_record/connection_adapters/mysql2_adapter'
module ActiveRecord
module ConnectionAdapters
class AbstractMysqlAdapter
NATIVE_DATABASE_TYPES.merge!(datetime_with_timezone: { name: 'timestamp' })
end
end
end
end
# ActiveRecord custom method definitions with timezone information.
# See https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11229
require 'active_record/connection_adapters/abstract/schema_definitions'
# Appends columns `created_at` and `updated_at` to a table.
#
# It is used in table creation like:
# create_table 'users' do |t|
# t.timestamps_with_timezone
# end
module ActiveRecord
module ConnectionAdapters
class TableDefinition
def timestamps_with_timezone(**options)
options[:null] = false if options[:null].nil?
[:created_at, :updated_at].each do |column_name|
column(column_name, :datetime_with_timezone, options)
end
end
end
end
end
# rubocop:disable Migration/Datetime
class AddRequestedAtToMembers < ActiveRecord::Migration
def change
add_column :members, :requested_at, :datetime
......
# rubocop:disable Migration/Datetime
# rubocop:disable Migration/Timestamps
class CreatePersonalAccessTokens < ActiveRecord::Migration
def change
create_table :personal_access_tokens do |t|
......
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
# rubocop:disable Migration/Datetime
class AddDeployments < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
......
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
# rubocop:disable Migration/Datetime
class AddEnvironments < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
......
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
# rubocop:disable Migration/Timestamps
class AddProtectedBranchesPushAccess < ActiveRecord::Migration
DOWNTIME = false
......
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
# rubocop:disable Migration/Timestamps
class AddProtectedBranchesMergeAccess < ActiveRecord::Migration
DOWNTIME = false
......
# rubocop:disable Migration/Datetime
class AddResolvedToNotes < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
......
# rubocop:disable Migration/Timestamps
class CreateUserAgentDetails < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
......
# rubocop:disable Migration/Timestamps
class CreateBoards < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
......
# rubocop:disable Migration/Timestamps
class CreateLists < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
......
# rubocop:disable Migration/Datetime
# rubocop:disable RemoveIndex
class AddDeletedAtToNamespaces < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
......
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
# rubocop:disable Migration/Datetime
# rubocop:disable Migration/Timestamps
class AddTableIssueMetrics < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
......
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
# rubocop:disable Migration/Datetime
# rubocop:disable Migration/Timestamps
class AddTableMergeRequestMetrics < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
......
# rubocop:disable Migration/Timestamps
class CreateProjectFeatures < ActiveRecord::Migration
DOWNTIME = false
......
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
# rubocop:disable Migration/Timestamps
class CreateMergeRequestsClosingIssues < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
......
# rubocop:disable Migration/Timestamps
class CreateLabelPriorities < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
......
# rubocop:disable Migration/Datetime
# rubocop:disable Migration/Timestamps
class CreateUserChatNamesTable < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
......
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
# rubocop:disable Migration/Timestamps
class AddRoutesTable < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
......
# rubocop:disable Migration/Datetime
class AddLastUsedAtToKey < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
......
# rubocop:disable Migration/Timestamps
class CreateTimelogsCe < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
......
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
# rubocop:disable Migration/Datetime
class ChangeExpiresAtToDateInPersonalAccessTokens < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
......
# rubocop:disable Migration/Timestamps
class CreateChatTeams < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
......
# rubocop:disable Migration/Datetime
class CreateUploads < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
......
# rubocop:disable Migration/Datetime
class DropCiProjects < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
......
# rubocop:disable Migration/Datetime
class RemoveUnusedCiTablesAndColumns < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
......
# rubocop:disable Migration/Timestamps
class CreateProtectedTags < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
......
# rubocop:disable Migration/Timestamps
class CreateSystemNoteMetadata < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
......
# rubocop:disable Migration/Datetime
class AddClosedAtToIssues < ActiveRecord::Migration
DOWNTIME = false
......
# rubocop:disable Migration/Timestamps
class CreateContainerRepository < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
......
# rubocop:disable Migration/Datetime
class CreateCiTriggerSchedules < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
......
# rubocop:disable Migration/Datetime
# rubocop:disable Migration/Timestamps
class CreatePipelineSchedulesTable < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
......
# rubocop:disable Migration/Timestamps
class CreateRedirectRoutes < ActiveRecord::Migration
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
......
# rubocop:disable Migration/Datetime
class AddLastRepositoryUpdatedAtToProjects < ActiveRecord::Migration
DOWNTIME = false
......
# rubocop:disable Migration/Timestamps
class CreateConversationalDevelopmentIndexMetrics < ActiveRecord::Migration
DOWNTIME = false
......
# rubocop:disable Migration/Timestamps
class CreatePipelineStages < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
......
# rubocop:disable Migration/Datetime
class DropCiTriggerSchedulesTable < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
......
......@@ -122,7 +122,7 @@ limit can vary from installation to installation. As a result it's recommended
you do not use more than 32 threads in a single migration. Usually 4-8 threads
should be more than enough.
## Removing indices
## Removing indexes
When removing an index make sure to use the method `remove_concurrent_index` instead
of the regular `remove_index` method. The `remove_concurrent_index` method
......@@ -142,7 +142,7 @@ class MyMigration < ActiveRecord::Migration
end
```
## Adding indices
## Adding indexes
If you need to add a unique index please keep in mind there is the possibility
of existing duplicates being present in the database. This means that should
......@@ -222,6 +222,41 @@ add_column_with_default(:projects, :foo, :integer, default: 10, limit: 8)
add_column(:projects, :foo, :integer, default: 10, limit: 8)
```
## Timestamp column type
By default, Rails uses the `timestamp` data type that stores timestamp data without timezone information.
The `timestamp` data type is used by calling either the `add_timestamps` or the `timestamps` method.
Also Rails converts the `:datetime` data type to the `timestamp` one.
Example:
```ruby
# timestamps
create_table :users do |t|
t.timestamps
end
# add_timestamps
def up
add_timestamps :users
end
# :datetime
def up
add_column :users, :last_sign_in, :datetime
end
```
Instead of using these methods one should use the following methods to store timestamps with timezones:
* `add_timestamps_with_timezone`
* `timestamps_with_timezone`
This ensures all timestamps have a time zone specified. This in turn means existing timestamps won't
suddenly use a different timezone when the system's timezone changes. It also makes it very clear which
timezone was used in the first place.
## Testing
Make sure that your migration works with MySQL and PostgreSQL with data. An
......
# Installing GitLab on Kubernetes
> Officially supported cloud providers are Google Container Service and Azure Container Service.
> Officially supported schedulers are Kubernetes and Terraform.
> Officially supported schedulers are Kubernetes, Terraform and Tectonic.
The easiest method to deploy GitLab in [Kubernetes](https://kubernetes.io/) is
to take advantage of the official GitLab Helm charts. [Helm] is a package
......
......@@ -34,8 +34,8 @@ class Spinach::Features::AwardEmoji < Spinach::FeatureSteps
page.within '.awards' do
expect do
page.find('.js-emoji-btn.active').click
sleep 0.3
end.to change{ page.all(".award-control.js-emoji-btn").size }.from(3).to(2)
wait_for_requests
end.to change { page.all(".award-control.js-emoji-btn").size }.from(3).to(2)
end
end
......
......@@ -804,7 +804,11 @@ module API
end
class Image < Grape::Entity
expose :name
expose :name, :entrypoint
end
class Service < Image
expose :alias, :command
end
class Artifacts < Grape::Entity
......@@ -848,7 +852,7 @@ module API
expose :variables
expose :steps, using: Step
expose :image, using: Image
expose :services, using: Image
expose :services, using: Service
expose :artifacts, using: Artifacts
expose :cache, using: Cache
expose :credentials, using: Credentials
......
......@@ -45,7 +45,21 @@ module Ci
expose :artifacts_expire_at, if: ->(build, _) { build.artifacts? }
expose :options do |model|
model.options
# This part ensures that output of old API is still the same after adding support
# for extended docker configuration options, used by new API
#
# I'm leaving this here, not in the model, because it should be removed at the same time
# when old API will be removed (planned for August 2017).
model.options.dup.tap do |options|
options[:image] = options[:image][:name] if options[:image].is_a?(Hash)
options[:services].map! do |service|
if service.is_a?(Hash)
service[:name]
else
service
end
end
end
end
expose :timeout do |model|
......
......@@ -92,7 +92,7 @@ module Github
end
def fetch_wiki_repository
wiki_url = "https://{options.fetch(:token)}@github.com/#{repo}.wiki.git"
wiki_url = "https://#{options.fetch(:token)}@github.com/#{repo}.wiki.git"
wiki_path = "#{project.path_with_namespace}.wiki"
unless project.wiki.repository_exists?
......
......@@ -2,7 +2,7 @@ module Gitlab
module Ci
module Build
class Image
attr_reader :name
attr_reader :alias, :command, :entrypoint, :name
class << self
def from_image(job)
......@@ -21,7 +21,14 @@ module Gitlab
end
def initialize(image)
if image.is_a?(String)
@name = image
elsif image.is_a?(Hash)
@alias = image[:alias]
@command = image[:command]
@entrypoint = image[:entrypoint]
@name = image[:name]
end
end
def valid?
......
......@@ -8,8 +8,36 @@ module Gitlab
class Image < Node
include Validatable
ALLOWED_KEYS = %i[name entrypoint].freeze
validations do
validates :config, type: String
validates :config, hash_or_string: true
validates :config, allowed_keys: ALLOWED_KEYS
validates :name, type: String, presence: true
validates :entrypoint, type: String, allow_nil: true
end
def hash?
@config.is_a?(Hash)
end
def string?
@config.is_a?(String)
end
def name
value[:name]
end
def entrypoint
value[:entrypoint]
end
def value
return { name: @config } if string?
return @config if hash?
{}
end
end
end
......
module Gitlab
module Ci
class Config
module Entry
##
# Entry that represents a configuration of Docker service.
#
class Service < Image
include Validatable
ALLOWED_KEYS = %i[name entrypoint command alias].freeze
validations do
validates :config, hash_or_string: true
validates :config, allowed_keys: ALLOWED_KEYS
validates :name, type: String, presence: true
validates :entrypoint, type: String, allow_nil: true
validates :command, type: String, allow_nil: true
validates :alias, type: String, allow_nil: true
end
def alias
value[:alias]
end
def command
value[:command]
end
end
end
end
end
end
......@@ -9,7 +9,30 @@ module Gitlab
include Validatable
validations do
validates :config, array_of_strings: true
validates :config, type: Array
end
def compose!(deps = nil)
super do
@entries = []
@config.each do |config|
@entries << Entry::Factory.new(Entry::Service)
.value(config || {})
.create!
end
@entries.each do |entry|
entry.compose!(deps)
end
end
end
def value
@entries.map(&:value)
end
def descendants
@entries
end
end
end
......
......@@ -44,6 +44,14 @@ module Gitlab
end
end
class HashOrStringValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless value.is_a?(Hash) || value.is_a?(String)
record.errors.add(attribute, 'should be a hash or a string')
end
end
end
class KeyValidator < ActiveModel::EachValidator
include LegacyValidationHelpers
......
......@@ -3,6 +3,10 @@ module Gitlab
module Status
module External
module Common
def label
subject.description
end
def has_details?
subject.target_url.present? &&
can?(user, :read_commit_status, subject)
......
module Gitlab
module Database
module MigrationHelpers
# Adds `created_at` and `updated_at` columns with timezone information.
#
# This method is an improved version of Rails' built-in method `add_timestamps`.
#
# Available options are:
# default - The default value for the column.
# null - When set to `true` the column will allow NULL values.
# The default is to not allow NULL values.
def add_timestamps_with_timezone(table_name, options = {})
options[:null] = false if options[:null].nil?
[:created_at, :updated_at].each do |column_name|
if options[:default] && transaction_open?
raise '`add_timestamps_with_timezone` with default value cannot be run inside a transaction. ' \
'You can disable transactions by calling `disable_ddl_transaction!` ' \
'in the body of your migration class'
end
# If default value is presented, use `add_column_with_default` method instead.
if options[:default]
add_column_with_default(
table_name,
column_name,
:datetime_with_timezone,
default: options[:default],
allow_null: options[:null]
)
else
add_column(table_name, column_name, :datetime_with_timezone, options)
end
end
end
# Creates a new index, concurrently when supported
#
# On PostgreSQL this method creates an index concurrently, on MySQL this
......
require_relative '../../migration_helpers'
module RuboCop
module Cop
module Migration
# Cop that checks if 'add_timestamps' method is called with timezone information.
class AddTimestamps < RuboCop::Cop::Cop
include MigrationHelpers
MSG = 'Do not use `add_timestamps`, use `add_timestamps_with_timezone` instead'.freeze
# Check methods.
def on_send(node)
return unless in_migration?(node)
add_offense(node, :selector) if method_name(node) == :add_timestamps
end
def method_name(node)
node.children[1]
end
end
end
end
end
require_relative '../../migration_helpers'
module RuboCop
module Cop
module Migration
# Cop that checks if datetime data type is added with timezone information.
class Datetime < RuboCop::Cop::Cop
include MigrationHelpers
MSG = 'Do not use the `datetime` data type, use `datetime_with_timezone` instead'.freeze
# Check methods in table creation.
def on_def(node)
return unless in_migration?(node)
node.each_descendant(:send) do |send_node|
add_offense(send_node, :selector) if method_name(send_node) == :datetime
end
end
# Check methods.
def on_send(node)
return unless in_migration?(node)
node.each_descendant do |descendant|
add_offense(node, :expression) if descendant.type == :sym && descendant.children.last == :datetime
end
end
def method_name(node)
node.children[1]
end
end
end
end
end
require_relative '../../migration_helpers'
module RuboCop
module Cop
module Migration
# Cop that checks if 'timestamps' method is called with timezone information.
class Timestamps < RuboCop::Cop::Cop
include MigrationHelpers
MSG = 'Do not use `timestamps`, use `timestamps_with_timezone` instead'.freeze
# Check methods in table creation.
def on_def(node)
return unless in_migration?(node)
node.each_descendant(:send) do |send_node|
add_offense(send_node, :selector) if method_name(send_node) == :timestamps
end
end
def method_name(node)
node.children[1]
end
end
end
end
end
......@@ -8,7 +8,10 @@ require_relative 'cop/migration/add_column_with_default_to_large_table'
require_relative 'cop/migration/add_concurrent_foreign_key'
require_relative 'cop/migration/add_concurrent_index'
require_relative 'cop/migration/add_index'
require_relative 'cop/migration/add_timestamps'
require_relative 'cop/migration/datetime'
require_relative 'cop/migration/remove_concurrent_index'
require_relative 'cop/migration/remove_index'
require_relative 'cop/migration/reversible_add_column_with_default'
require_relative 'cop/migration/timestamps'
require_relative 'cop/migration/update_column_in_batches'
......@@ -5,9 +5,12 @@ describe Projects::PipelinesController do
let(:user) { create(:user) }
let(:project) { create(:empty_project, :public) }
let(:feature) { ProjectFeature::DISABLED }
before do
project.add_developer(user)
project.project_feature.update(
builds_access_level: feature)
sign_in(user)
end
......@@ -153,12 +156,22 @@ describe Projects::PipelinesController do
format: :json
end
context 'when builds are enabled' do
let(:feature) { ProjectFeature::ENABLED }
it 'retries a pipeline without returning any content' do
expect(response).to have_http_status(:no_content)
expect(build.reload).to be_retried
end
end
context 'when builds are disabled' do
it 'fails to retry pipeline' do
expect(response).to have_http_status(:not_found)
end
end
end
describe 'POST cancel.json' do
let!(:pipeline) { create(:ci_pipeline, project: project) }
let!(:build) { create(:ci_build, :running, pipeline: pipeline) }
......@@ -170,9 +183,19 @@ describe Projects::PipelinesController do
format: :json
end
context 'when builds are enabled' do
let(:feature) { ProjectFeature::ENABLED }
it 'cancels a pipeline without returning any content' do
expect(response).to have_http_status(:no_content)
expect(pipeline.reload).to be_canceled
end
end
context 'when builds are disabled' do
it 'fails to retry pipeline' do
expect(response).to have_http_status(:not_found)
end
end
end
end
......@@ -194,8 +194,8 @@ FactoryGirl.define do
trait :extended_options do
options do
{
image: 'ruby:2.1',
services: ['postgres'],
image: { name: 'ruby:2.1', entrypoint: '/bin/sh' },
services: ['postgres', { name: 'docker:dind', entrypoint: '/bin/sh', command: 'sleep 30', alias: 'docker' }],
after_script: %w(ls date),
artifacts: {
name: 'artifacts_file',
......
......@@ -68,9 +68,12 @@ describe 'Edit Project Settings', feature: true do
end
describe 'project features visibility pages' do
let(:pipeline) { create(:ci_empty_pipeline, project: project) }
let(:job) { create(:ci_build, pipeline: pipeline) }
let(:tools) do
{
builds: namespace_project_pipelines_path(project.namespace, project),
builds: namespace_project_job_path(project.namespace, project, job),
issues: namespace_project_issues_path(project.namespace, project),
wiki: namespace_project_wiki_path(project.namespace, project, :home),
snippets: namespace_project_snippets_path(project.namespace, project),
......
......@@ -127,7 +127,7 @@ feature 'Pipeline Schedules', :feature do
end
it 'shows the pipeline schedule with default ref' do
page.within('.git-revision-dropdown-toggle') do
page.within('.js-target-branch-dropdown') do
expect(first('.dropdown-toggle-text').text).to eq('master')
end
end
......
......@@ -300,4 +300,37 @@ describe ProjectsHelper do
expect(helper.send(:visibility_select_options, project, Gitlab::VisibilityLevel::PRIVATE)).to include('Private')
end
end
describe '#get_project_nav_tabs' do
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
before do
allow(helper).to receive(:can?) { true }
end
subject do
helper.send(:get_project_nav_tabs, project, user)
end
context 'when builds feature is enabled' do
before do
allow(project).to receive(:builds_enabled?).and_return(true)
end
it "does include pipelines tab" do
is_expected.to include(:pipelines)
end
end
context 'when builds feature is disabled' do
before do
allow(project).to receive(:builds_enabled?).and_return(false)
end
it "do not include pipelines tab" do
is_expected.not_to include(:pipelines)
end
end
end
end
......@@ -22,7 +22,7 @@ describe('Time ago with tooltip component', () => {
}).$mount();
expect(vm.$el.tagName).toEqual('TIME');
expect(vm.$el.classList.contains('js-timeago')).toEqual(true);
expect(vm.$el.classList.contains('js-vue-timeago')).toEqual(true);
expect(
vm.$el.getAttribute('data-original-title'),
).toEqual(gl.utils.formatDate('2017-05-08T14:57:39.781Z'));
......@@ -44,17 +44,6 @@ describe('Time ago with tooltip component', () => {
expect(vm.$el.getAttribute('data-placement')).toEqual('bottom');
});
it('should render short format class', () => {
vm = new TimeagoTooltip({
propsData: {
time: '2017-05-08T14:57:39.781Z',
shortFormat: true,
},
}).$mount();
expect(vm.$el.classList.contains('js-short-timeago')).toEqual(true);
});
it('should render provided html class', () => {
vm = new TimeagoTooltip({
propsData: {
......
......@@ -596,13 +596,69 @@ module Ci
end
describe "Image and service handling" do
context "when extended docker configuration is used" do
it "returns image and service when defined" do
config = YAML.dump({
image: "ruby:2.1",
config = YAML.dump({ image: { name: "ruby:2.1" },
services: ["mysql", { name: "docker:dind", alias: "docker" }],
before_script: ["pwd"],
rspec: { script: "rspec" } })
config_processor = GitlabCiYamlProcessor.new(config, path)
expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(1)
expect(config_processor.builds_for_stage_and_ref("test", "master").first).to eq({
stage: "test",
stage_idx: 1,
name: "rspec",
commands: "pwd\nrspec",
coverage_regex: nil,
tag_list: [],
options: {
image: { name: "ruby:2.1" },
services: [{ name: "mysql" }, { name: "docker:dind", alias: "docker" }]
},
allow_failure: false,
when: "on_success",
environment: nil,
yaml_variables: []
})
end
it "returns image and service when overridden for job" do
config = YAML.dump({ image: "ruby:2.1",
services: ["mysql"],
before_script: ["pwd"],
rspec: { script: "rspec" }
rspec: { image: { name: "ruby:2.5" },
services: [{ name: "postgresql", alias: "db-pg" }, "docker:dind"], script: "rspec" } })
config_processor = GitlabCiYamlProcessor.new(config, path)
expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(1)
expect(config_processor.builds_for_stage_and_ref("test", "master").first).to eq({
stage: "test",
stage_idx: 1,
name: "rspec",
commands: "pwd\nrspec",
coverage_regex: nil,
tag_list: [],
options: {
image: { name: "ruby:2.5" },
services: [{ name: "postgresql", alias: "db-pg" }, { name: "docker:dind" }]
},
allow_failure: false,
when: "on_success",
environment: nil,
yaml_variables: []
})
end
end
context "when etended docker configuration is not used" do
it "returns image and service when defined" do
config = YAML.dump({ image: "ruby:2.1",
services: ["mysql", "docker:dind"],
before_script: ["pwd"],
rspec: { script: "rspec" } })
config_processor = GitlabCiYamlProcessor.new(config, path)
......@@ -615,8 +671,8 @@ module Ci
coverage_regex: nil,
tag_list: [],
options: {
image: "ruby:2.1",
services: ["mysql"]
image: { name: "ruby:2.1" },
services: [{ name: "mysql" }, { name: "docker:dind" }]
},
allow_failure: false,
when: "on_success",
......@@ -626,12 +682,10 @@ module Ci
end
it "returns image and service when overridden for job" do
config = YAML.dump({
image: "ruby:2.1",
config = YAML.dump({ image: "ruby:2.1",
services: ["mysql"],
before_script: ["pwd"],
rspec: { image: "ruby:2.5", services: ["postgresql"], script: "rspec" }
})
rspec: { image: "ruby:2.5", services: ["postgresql", "docker:dind"], script: "rspec" } })
config_processor = GitlabCiYamlProcessor.new(config, path)
......@@ -644,8 +698,8 @@ module Ci
coverage_regex: nil,
tag_list: [],
options: {
image: "ruby:2.5",
services: ["postgresql"]
image: { name: "ruby:2.5" },
services: [{ name: "postgresql" }, { name: "docker:dind" }]
},
allow_failure: false,
when: "on_success",
......@@ -654,6 +708,7 @@ module Ci
})
end
end
end
describe 'Variables' do
let(:config_processor) { GitlabCiYamlProcessor.new(YAML.dump(config), path) }
......@@ -884,8 +939,8 @@ module Ci
coverage_regex: nil,
tag_list: [],
options: {
image: "ruby:2.1",
services: ["mysql"],
image: { name: "ruby:2.1" },
services: [{ name: "mysql" }],
artifacts: {
name: "custom_name",
paths: ["logs/", "binaries/"],
......@@ -1261,7 +1316,7 @@ EOT
config = YAML.dump({ image: ["test"], rspec: { script: "test" } })
expect do
GitlabCiYamlProcessor.new(config, path)
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "image config should be a string")
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "image config should be a hash or a string")
end
it "returns errors if job name is blank" do
......@@ -1282,35 +1337,35 @@ EOT
config = YAML.dump({ rspec: { script: "test", image: ["test"] } })
expect do
GitlabCiYamlProcessor.new(config, path)
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:image config should be a string")
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:image config should be a hash or a string")
end
it "returns errors if services parameter is not an array" do
config = YAML.dump({ services: "test", rspec: { script: "test" } })
expect do
GitlabCiYamlProcessor.new(config, path)
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "services config should be an array of strings")
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "services config should be a array")
end
it "returns errors if services parameter is not an array of strings" do
config = YAML.dump({ services: [10, "test"], rspec: { script: "test" } })
expect do
GitlabCiYamlProcessor.new(config, path)
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "services config should be an array of strings")
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "service config should be a hash or a string")
end
it "returns errors if job services parameter is not an array" do
config = YAML.dump({ rspec: { script: "test", services: "test" } })
expect do
GitlabCiYamlProcessor.new(config, path)
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:services config should be an array of strings")
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:services config should be a array")
end
it "returns errors if job services parameter is not an array of strings" do
config = YAML.dump({ rspec: { script: "test", services: [10, "test"] } })
expect do
GitlabCiYamlProcessor.new(config, path)
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:services config should be an array of strings")
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "service config should be a hash or a string")
end
it "returns error if job configuration is invalid" do
......@@ -1324,7 +1379,7 @@ EOT
config = YAML.dump({ extra: { script: 'rspec', services: "test" } })
expect do
GitlabCiYamlProcessor.new(config, path)
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:extra:services config should be an array of strings")
end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:extra:services config should be a array")
end
it "returns errors if there are no jobs defined" do
......
......@@ -10,6 +10,7 @@ describe Gitlab::Ci::Build::Image do
let(:image_name) { 'ruby:2.1' }
let(:job) { create(:ci_build, options: { image: image_name } ) }
context 'when image is defined as string' do
it 'fabricates an object of the proper class' do
is_expected.to be_kind_of(described_class)
end
......@@ -17,6 +18,21 @@ describe Gitlab::Ci::Build::Image do
it 'populates fabricated object with the proper name attribute' do
expect(subject.name).to eq(image_name)
end
end
context 'when image is defined as hash' do
let(:entrypoint) { '/bin/sh' }
let(:job) { create(:ci_build, options: { image: { name: image_name, entrypoint: entrypoint } } ) }
it 'fabricates an object of the proper class' do
is_expected.to be_kind_of(described_class)
end
it 'populates fabricated object with the proper attributes' do
expect(subject.name).to eq(image_name)
expect(subject.entrypoint).to eq(entrypoint)
end
end
context 'when image name is empty' do
let(:image_name) { '' }
......@@ -41,10 +57,39 @@ describe Gitlab::Ci::Build::Image do
let(:service_image_name) { 'postgres' }
let(:job) { create(:ci_build, options: { services: [service_image_name] }) }
context 'when service is defined as string' do
it 'fabricates an non-empty array of objects' do
is_expected.to be_kind_of(Array)
is_expected.not_to be_empty
end
it 'populates fabricated objects with the proper name attributes' do
expect(subject.first).to be_kind_of(described_class)
expect(subject.first.name).to eq(service_image_name)
end
end
context 'when service is defined as hash' do
let(:service_entrypoint) { '/bin/sh' }
let(:service_alias) { 'db' }
let(:service_command) { 'sleep 30' }
let(:job) do
create(:ci_build, options: { services: [{ name: service_image_name, entrypoint: service_entrypoint,
alias: service_alias, command: service_command }] })
end
it 'fabricates an non-empty array of objects' do
is_expected.to be_kind_of(Array)
is_expected.not_to be_empty
expect(subject.first).to be_kind_of(described_class)
end
it 'populates fabricated objects with the proper attributes' do
expect(subject.first.name).to eq(service_image_name)
expect(subject.first.entrypoint).to eq(service_entrypoint)
expect(subject.first.alias).to eq(service_alias)
expect(subject.first.command).to eq(service_command)
end
end
context 'when service image name is empty' do
......
......@@ -95,13 +95,13 @@ describe Gitlab::Ci::Config::Entry::Global do
describe '#image_value' do
it 'returns valid image' do
expect(global.image_value).to eq 'ruby:2.2'
expect(global.image_value).to eq(name: 'ruby:2.2')
end
end
describe '#services_value' do
it 'returns array of services' do
expect(global.services_value).to eq ['postgres:9.1', 'mysql:5.5']
expect(global.services_value).to eq [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }]
end
end
......@@ -150,8 +150,8 @@ describe Gitlab::Ci::Config::Entry::Global do
script: %w[rspec ls],
before_script: %w(ls pwd),
commands: "ls\npwd\nrspec\nls",
image: 'ruby:2.2',
services: ['postgres:9.1', 'mysql:5.5'],
image: { name: 'ruby:2.2' },
services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }],
stage: 'test',
cache: { key: 'k', untracked: true, paths: ['public/'] },
variables: { 'VAR' => 'value' },
......@@ -161,8 +161,8 @@ describe Gitlab::Ci::Config::Entry::Global do
before_script: [],
script: %w[spinach],
commands: 'spinach',
image: 'ruby:2.2',
services: ['postgres:9.1', 'mysql:5.5'],
image: { name: 'ruby:2.2' },
services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }],
stage: 'test',
cache: { key: 'k', untracked: true, paths: ['public/'] },
variables: {},
......
......@@ -3,13 +3,12 @@ require 'spec_helper'
describe Gitlab::Ci::Config::Entry::Image do
let(:entry) { described_class.new(config) }
describe 'validation' do
context 'when entry config value is correct' do
context 'when configuration is a string' do
let(:config) { 'ruby:2.2' }
describe '#value' do
it 'returns image string' do
expect(entry.value).to eq 'ruby:2.2'
it 'returns image hash' do
expect(entry.value).to eq({ name: 'ruby:2.2' })
end
end
......@@ -24,6 +23,52 @@ describe Gitlab::Ci::Config::Entry::Image do
expect(entry).to be_valid
end
end
describe '#image' do
it "returns image's name" do
expect(entry.name).to eq 'ruby:2.2'
end
end
describe '#entrypoint' do
it "returns image's entrypoint" do
expect(entry.entrypoint).to be_nil
end
end
end
context 'when configuration is a hash' do
let(:config) { { name: 'ruby:2.2', entrypoint: '/bin/sh' } }
describe '#value' do
it 'returns image hash' do
expect(entry.value).to eq(config)
end
end
describe '#errors' do
it 'does not append errors' do
expect(entry.errors).to be_empty
end
end
describe '#valid?' do
it 'is valid' do
expect(entry).to be_valid
end
end
describe '#image' do
it "returns image's name" do
expect(entry.name).to eq 'ruby:2.2'
end
end
describe '#entrypoint' do
it "returns image's entrypoint" do
expect(entry.entrypoint).to eq '/bin/sh'
end
end
end
context 'when entry value is not correct' do
......@@ -32,7 +77,7 @@ describe Gitlab::Ci::Config::Entry::Image do
describe '#errors' do
it 'saves errors' do
expect(entry.errors)
.to include 'image config should be a string'
.to include 'image config should be a hash or a string'
end
end
......@@ -42,5 +87,21 @@ describe Gitlab::Ci::Config::Entry::Image do
end
end
end
context 'when unexpected key is specified' do
let(:config) { { name: 'ruby:2.2', non_existing: 'test' } }
describe '#errors' do
it 'saves errors' do
expect(entry.errors)
.to include 'image config contains unknown keys: non_existing'
end
end
describe '#valid?' do
it 'is not valid' do
expect(entry).not_to be_valid
end
end
end
end
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment