summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG11
-rw-r--r--GITLAB_SHELL_VERSION2
-rw-r--r--app/assets/javascripts/gfm_auto_complete.js.es638
-rw-r--r--app/assets/stylesheets/pages/environments.scss14
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss9
-rw-r--r--app/assets/stylesheets/pages/pipelines.scss4
-rw-r--r--app/controllers/ci/application_controller.rb7
-rw-r--r--app/controllers/ci/lints_controller.rb2
-rw-r--r--app/controllers/ci/projects_controller.rb2
-rw-r--r--app/controllers/projects/environments_controller.rb23
-rw-r--r--app/controllers/slash_commands_controller.rb65
-rw-r--r--app/models/ci/build.rb20
-rw-r--r--app/models/ci/pipeline.rb2
-rw-r--r--app/models/commit_status.rb34
-rw-r--r--app/models/concerns/token_authenticatable.rb4
-rw-r--r--app/models/deployment.rb13
-rw-r--r--app/models/environment.rb18
-rw-r--r--app/models/namespace.rb14
-rw-r--r--app/models/project.rb8
-rw-r--r--app/models/project_feature.rb10
-rw-r--r--app/models/repository.rb6
-rw-r--r--app/services/ci/process_pipeline_service.rb2
-rw-r--r--app/services/create_deployment_service.rb12
-rw-r--r--app/services/mattermost/base_service.rb52
-rw-r--r--app/services/mattermost/deploy_service.rb48
-rw-r--r--app/services/mattermost/issue_service.rb84
-rw-r--r--app/services/mattermost/merge_request_service.rb15
-rw-r--r--app/views/projects/builds/_sidebar.html.haml2
-rw-r--r--app/views/projects/deployments/_actions.haml29
-rw-r--r--app/views/projects/deployments/_deployment.html.haml2
-rw-r--r--app/views/projects/environments/_environment.html.haml4
-rw-r--r--app/views/projects/environments/index.html.haml21
-rw-r--r--app/views/projects/environments/show.html.haml19
-rw-r--r--app/views/projects/merge_requests/widget/_heading.html.haml4
-rw-r--r--config/routes.rb4
-rw-r--r--config/routes/project.rb12
-rw-r--r--db/fixtures/development/14_pipelines.rb1
-rw-r--r--db/fixtures/production/010_settings.rb16
-rw-r--r--db/migrate/20161006104309_add_state_to_environment.rb15
-rw-r--r--db/schema.rb5
-rw-r--r--doc/administration/environment_variables.md23
-rw-r--r--doc/api/ci/runners.md6
-rw-r--r--doc/api/labels.md2
-rw-r--r--doc/gitlab-basics/start-using-git.md20
-rw-r--r--lib/expand_variables.rb2
-rw-r--r--lib/gitlab/ci/config/node/environment.rb8
-rw-r--r--lib/gitlab/regex.rb4
-rw-r--r--lib/gitlab/workhorse.rb2
-rw-r--r--spec/controllers/slash_commands_controller_spec.rb5
-rw-r--r--spec/db/production/settings.rb16
-rw-r--r--spec/features/environments_spec.rb68
-rw-r--r--spec/features/groups_spec.rb32
-rw-r--r--spec/features/merge_requests/merge_when_build_succeeds_spec.rb9
-rw-r--r--spec/lib/gitlab/ci/config/node/environment_spec.rb28
-rw-r--r--spec/models/concerns/token_authenticatable_spec.rb7
-rw-r--r--spec/models/namespace_spec.rb1
-rw-r--r--spec/models/repository_spec.rb18
-rw-r--r--spec/services/mattermost/deploy_service_spec.rb27
-rw-r--r--spec/services/mattermost/issue_service_spec.rb100
-rw-r--r--spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb24
-rw-r--r--vendor/gitlab-ci-yml/OpenShift.gitlab-ci.yml99
61 files changed, 1007 insertions, 147 deletions
diff --git a/CHANGELOG b/CHANGELOG
index e6cdc09b9c6..a784a690ebf 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -18,19 +18,24 @@ v 8.13.0 (unreleased)
- Keep refs for each deployment
- Log LDAP lookup errors and don't swallow unrelated exceptions. !6103 (Markus Koller)
- Add more tests for calendar contribution (ClemMakesApps)
+ - Update Gitlab Shell to fix some problems with moving projects between storages
- Cache rendered markdown in the database, rather than Redis
- Avoid database queries on Banzai::ReferenceParser::BaseParser for nodes without references
- Simplify Mentionable concern instance methods
- Fix permission for setting an issue's due date
- API: Multi-file commit !6096 (mahcsig)
+ - Add support for setting the GitLab Runners Registration Token during initial database seeding
- Revert "Label list shows all issues (opened or closed) with that label"
+ - Add support for setting the GitLab Runners Registration Token during initial database seeding
- Expose expires_at field when sharing project on API
- Fix VueJS template tags being rendered in code comments
- Fix issue with page scrolling to top when closing or pinning sidebar (lukehowell)
- Add Issue Board API support (andrebsguedes)
+ - Mattermost slash command support
- Allow the Koding integration to be configured through the API
- Add new issue button to each list on Issues Board
- Added soft wrap button to repository file/blob editor
+ - Update namespace validation to forbid reserved names (.git and .atom) (Will Starms)
- Add word-wrap to issue title on issue and milestone boards (ClemMakesApps)
- Fix todos page mobile viewport layout (ClemMakesApps)
- Fix inconsistent highlighting of already selected activity nav-links (ClemMakesApps)
@@ -65,6 +70,7 @@ v 8.13.0 (unreleased)
- Add organization field to user profile
- Fix enter key when navigating search site search dropdown. !6643 (Brennan Roberts)
- Fix deploy status responsiveness error !6633
+ - Make searching for commits case insensitive
- Fix resolved discussion display in side-by-side diff view !6575
- Optimize GitHub importing for speed and memory
- API: expose pipeline data in builds API (!6502, Guilherme Salazar)
@@ -79,6 +85,8 @@ v 8.13.0 (unreleased)
- Retouch environments list and deployments list
- Add Container Registry on/off status to Admin Area !6638 (the-undefined)
- Grouped pipeline dropdown is a scrollable container
+ - Cleanup Ci::ApplicationController. !6757 (Takuya Noguchi)
+ - Fix a typo in doc/api/labels.md
v 8.12.5 (unreleased)
@@ -115,6 +123,7 @@ v 8.12.2
- Fix bug where 'Search results' repeated many times when a search in the emoji search form is cleared (Xavier Bick) (@zeiv)
- Fix resolve discussion buttons endpoint path
- Refactor remnants of CoffeeScript destructured opts and super !6261
+ - Prevent running GfmAutocomplete setup for each diff note !6569
v 8.12.1
- Fix a memory leak in HTML::Pipeline::SanitizationFilter::WHITELIST
@@ -318,6 +327,7 @@ v 8.11.7
- Avoid conflict with admin labels when importing GitHub labels. !6158
- Restores `fieldName` to allow only string values in `gl_dropdown.js`. !6234
- Allow the Rails cookie to be used for API authentication.
+ - Updating verbiage on git basics to be more intuitive
v 8.11.6
- Fix unnecessary horizontal scroll area in pipeline visualizations. !6005
@@ -478,6 +488,7 @@ v 8.11.0
- Add pipeline events hook
- Bump gitlab_git to speedup DiffCollection iterations
- Rewrite description of a blocked user in admin settings. (Elias Werberich)
+ - Clarify documentation for Runners API (Gennady Trafimenkov)
- Make branches sortable without push permission !5462 (winniehell)
- Check for Ci::Build artifacts at database level on pipeline partial
- Convert image diff background image to CSS (ClemMakesApps)
diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION
index 4a788a01dad..0f44168a4d5 100644
--- a/GITLAB_SHELL_VERSION
+++ b/GITLAB_SHELL_VERSION
@@ -1 +1 @@
-3.6.3
+3.6.4
diff --git a/app/assets/javascripts/gfm_auto_complete.js.es6 b/app/assets/javascripts/gfm_auto_complete.js.es6
index d0786bf0053..845313b6b38 100644
--- a/app/assets/javascripts/gfm_auto_complete.js.es6
+++ b/app/assets/javascripts/gfm_auto_complete.js.es6
@@ -52,37 +52,27 @@
}
}
},
- setup: function(input) {
+ setup: _.debounce(function(input) {
// Add GFM auto-completion to all input fields, that accept GFM input.
this.input = input || $('.js-gfm-input');
// destroy previous instances
this.destroyAtWho();
// set up instances
this.setupAtWho();
- if (this.dataSource) {
- if (!this.dataLoading && !this.cachedData) {
- this.dataLoading = true;
- setTimeout((function(_this) {
- return function() {
- var fetch;
- fetch = _this.fetchData(_this.dataSource);
- return fetch.done(function(data) {
- _this.dataLoading = false;
- return _this.loadData(data);
- });
- };
- // We should wait until initializations are done
- // and only trigger the last .setup since
- // The previous .dataSource belongs to the previous issuable
- // and the last one will have the **proper** .dataSource property
- // TODO: Make this a singleton and turn off events when moving to another page
- })(this), 1000);
- }
- if (this.cachedData != null) {
- return this.loadData(this.cachedData);
- }
+
+ if (this.dataSource && !this.dataLoading && !this.cachedData) {
+ this.dataLoading = true;
+ return this.fetchData(this.dataSource)
+ .done((data) => {
+ this.dataLoading = false;
+ this.loadData(data);
+ });
+ };
+
+ if (this.cachedData != null) {
+ return this.loadData(this.cachedData);
}
- },
+ }, 1000),
setupAtWho: function() {
// Emoji
this.input.atwho({
diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss
index 3f19e920166..f17cf5566bd 100644
--- a/app/assets/stylesheets/pages/environments.scss
+++ b/app/assets/stylesheets/pages/environments.scss
@@ -1,7 +1,9 @@
.environments-container,
.deployments-container {
- width: 100%;
- overflow: auto;
+ @media (max-width: $screen-sm-max) {
+ width: 100%;
+ overflow: auto;
+ }
}
.environments {
@@ -38,6 +40,14 @@
color: $gl-dark-link-color;
}
+ .close-env-link {
+ color: $table-text-gray;
+
+ .close-env-icon {
+ font-size: 14px;
+ }
+ }
+
.deployment {
.build-column {
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index bc8693ae467..f2c2e06ad3e 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -177,6 +177,15 @@
.ci-coverage {
float: right;
}
+
+ .close-env-container {
+ color: $gl-text-color;
+ float: right;
+
+ a {
+ color: $gl-text-color;
+ }
+ }
}
.mr_source_commit,
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index 020e7365e38..3c86c15a240 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -590,6 +590,10 @@
}
}
+.terminal-icon {
+ margin-left: 3px;
+}
+
.terminal-container {
.content-block {
border-bottom: none;
diff --git a/app/controllers/ci/application_controller.rb b/app/controllers/ci/application_controller.rb
deleted file mode 100644
index 5bb7d499cdc..00000000000
--- a/app/controllers/ci/application_controller.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-module Ci
- class ApplicationController < ::ApplicationController
- def self.railtie_helpers_paths
- "app/helpers/ci"
- end
- end
-end
diff --git a/app/controllers/ci/lints_controller.rb b/app/controllers/ci/lints_controller.rb
index 78012960252..3eb485de9db 100644
--- a/app/controllers/ci/lints_controller.rb
+++ b/app/controllers/ci/lints_controller.rb
@@ -1,5 +1,5 @@
module Ci
- class LintsController < ApplicationController
+ class LintsController < ::ApplicationController
before_action :authenticate_user!
def show
diff --git a/app/controllers/ci/projects_controller.rb b/app/controllers/ci/projects_controller.rb
index aa894fde36b..ff297d6ff13 100644
--- a/app/controllers/ci/projects_controller.rb
+++ b/app/controllers/ci/projects_controller.rb
@@ -1,5 +1,5 @@
module Ci
- class ProjectsController < Ci::ApplicationController
+ class ProjectsController < ::ApplicationController
before_action :project
before_action :no_cache, only: [:badge]
before_action :authorize_read_project!, except: [:badge, :index]
diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb
index 58678f96879..1740057e0da 100644
--- a/app/controllers/projects/environments_controller.rb
+++ b/app/controllers/projects/environments_controller.rb
@@ -2,11 +2,17 @@ class Projects::EnvironmentsController < Projects::ApplicationController
layout 'project'
before_action :authorize_read_environment!
before_action :authorize_create_environment!, only: [:new, :create]
- before_action :authorize_update_environment!, only: [:edit, :update, :destroy]
- before_action :environment, only: [:show, :edit, :update, :destroy]
+ before_action :authorize_update_environment!, only: [:edit, :update, :close, :destroy]
+ before_action :environment, only: [:show, :edit, :update, :close, :destroy]
def index
- @environments = project.environments
+ @scope = params[:scope]
+ @all_environments = project.environments
+ @environments =
+ case @scope
+ when 'closed' then @all_environments.closed
+ else @all_environments.opened
+ end
end
def show
@@ -48,6 +54,17 @@ class Projects::EnvironmentsController < Projects::ApplicationController
redirect_to namespace_project_environments_path(project.namespace, project)
end
+ def close
+ close_action = @environment.close_action
+ if close_action
+ close_build = close_action.play(current_user)
+ redirect_to namespace_project_build_path(project.namespace, project, close_build)
+ else
+ @environment.close
+ redirect_to namespace_project_environment_path(project.namespace, project, @environment)
+ end
+ end
+
private
def environment_params
diff --git a/app/controllers/slash_commands_controller.rb b/app/controllers/slash_commands_controller.rb
new file mode 100644
index 00000000000..b100ff00c38
--- /dev/null
+++ b/app/controllers/slash_commands_controller.rb
@@ -0,0 +1,65 @@
+class SlashCommandsController < ApplicationController
+ respond_to :json
+
+ skip_before_action :verify_authenticity_token
+ skip_before_action :authenticate_user!
+ before_action :find_project
+
+ attr_reader :project
+
+ def trigger
+ if service
+ render json: service.new(@project, user, params).execute
+ else
+ render json: unavailable
+ end
+ end
+
+ private
+
+ def unavailable
+ {
+ response_type: :ephemeral,
+ text: 'This slash command has not been registered yet.',
+ }
+ end
+
+ def project_unavailable(path)
+ {
+ response_type: :ephemeral,
+ text: "The #{path} doesn't exist or you don't have access."
+ }
+ end
+
+ def project_not_found(path)
+ {
+ response_type: :ephemeral,
+ text: "We were unable to find a project for: #{path}"
+ }
+ end
+
+ def find_project
+ path = "#{params[:team_domain]}/#{params[:channel_name]}"
+ @project = Project.find_with_namespace(path)
+
+ render json: project_not_found(path) unless can?(user, :read_project, @project)
+ end
+
+ def user
+ @user ||= User.find_by(username: params[:user_name])
+ end
+
+ def service
+ command = params[:command]
+
+ if command == '/issue' && project.issues_enabled? && project.default_issues_tracker?
+ Mattermost::IssueService
+ elsif command == '/merge-request' && project.merge_requests_enabled?
+ Mattermost::MergeRequestService
+ elsif command == '/deploy'
+ Mattermost::DeployService
+ else
+ nil
+ end
+ end
+end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 5dbf66173de..4f8a505da0d 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -73,7 +73,7 @@ module Ci
end
end
- state_machine :status do
+ state_machine :status, use_transactions: false do
after_transition pending: :running do |build|
build.execute_hooks
end
@@ -110,6 +110,14 @@ module Ci
project.builds_enabled? && commands.present? && manual? && skipped?
end
+ def close_environment?
+ options.fetch(:environment, {}).fetch(:close, false)
+ end
+
+ def closes_environment?(name)
+ environment == name && close_environment?
+ end
+
def play(current_user = nil)
# Try to queue a current build
if self.enqueue
@@ -121,6 +129,16 @@ module Ci
end
end
+ def play_type
+ return nil unless playable?
+
+ if close_environment?
+ :close
+ else
+ :play
+ end
+ end
+
def retryable?
project.builds_enabled? && commands.present? && complete?
end
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 2cf9892edc5..291256548ac 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -249,6 +249,8 @@ module Ci
def process!
Ci::ProcessPipelineService.new(project, user).execute(self)
+
+ update_status
end
def update_status
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index 9fa8d17e74e..c541085b7bd 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -1,6 +1,7 @@
class CommitStatus < ActiveRecord::Base
include HasStatus
include Importable
+ include AfterCommitQueue
self.table_name = 'ci_builds'
@@ -43,7 +44,7 @@ class CommitStatus < ActiveRecord::Base
scope :latest_ci_stages, -> { latest.ordered.includes(project: :namespace) }
scope :retried_ci_stages, -> { retried.ordered.includes(project: :namespace) }
- state_machine :status do
+ state_machine :status, use_transactions: false do
event :enqueue do
transition [:created, :skipped] => :pending
end
@@ -85,25 +86,34 @@ class CommitStatus < ActiveRecord::Base
end
after_transition do |commit_status, transition|
- commit_status.pipeline.try do |pipeline|
- break if transition.loopback?
-
- if commit_status.complete?
- ProcessPipelineWorker.perform_async(pipeline.id)
+ return if transition.loopback?
+
+ commit_status.run_after_commit do
+ pipeline.try do |pipeline|
+ if complete?
+ ProcessPipelineWorker.perform_async(pipeline.id)
+ else
+ UpdatePipelineWorker.perform_async(pipeline.id)
+ end
end
-
- UpdatePipelineWorker.perform_async(pipeline.id)
end
-
- true
end
after_transition [:created, :pending, :running] => :success do |commit_status|
- MergeRequests::MergeWhenBuildSucceedsService.new(commit_status.pipeline.project, nil).trigger(commit_status)
+ commit_status.run_after_commit do
+ # TODO, temporary fix for race condition
+ UpdatePipelineWorker.new.perform(pipeline.id)
+
+ MergeRequests::MergeWhenBuildSucceedsService
+ .new(pipeline.project, nil).trigger(self)
+ end
end
after_transition any => :failed do |commit_status|
- MergeRequests::AddTodoWhenBuildFailsService.new(commit_status.pipeline.project, nil).execute(commit_status)
+ commit_status.run_after_commit do
+ MergeRequests::AddTodoWhenBuildFailsService
+ .new(pipeline.project, nil).execute(self)
+ end
end
end
diff --git a/app/models/concerns/token_authenticatable.rb b/app/models/concerns/token_authenticatable.rb
index 24c7b26d223..a4218a5d52a 100644
--- a/app/models/concerns/token_authenticatable.rb
+++ b/app/models/concerns/token_authenticatable.rb
@@ -35,6 +35,10 @@ module TokenAuthenticatable
current_token.blank? ? write_new_token(token_field) : current_token
end
+ define_method("set_#{token_field}") do |token|
+ write_attribute(token_field, token) if token
+ end
+
define_method("ensure_#{token_field}!") do
send("reset_#{token_field}!") if read_attribute(token_field).blank?
read_attribute(token_field)
diff --git a/app/models/deployment.rb b/app/models/deployment.rb
index 82b27b78229..070c76339b1 100644
--- a/app/models/deployment.rb
+++ b/app/models/deployment.rb
@@ -77,6 +77,19 @@ class Deployment < ActiveRecord::Base
take
end
+ def close_action
+ return nil unless manual_actions
+
+ @close_action ||=
+ manual_actions.find do |manual_action|
+ manual_action.try(:closes_environment?, deployable.environment)
+ end
+ end
+
+ def closeable?
+ close_action.present?
+ end
+
private
def ref_path
diff --git a/app/models/environment.rb b/app/models/environment.rb
index f0f3ee23223..6ec498ea2b7 100644
--- a/app/models/environment.rb
+++ b/app/models/environment.rb
@@ -19,6 +19,24 @@ class Environment < ActiveRecord::Base
allow_nil: true,
addressable_url: true
+ delegate :closeable?, :close_action, to: :last_deployment, allow_nil: true
+
+ scope :opened, -> { where(state: [:opened]) }
+ scope :closed, -> { where(state: [:closed]) }
+
+ state_machine :state, initial: :opened do
+ event :close do
+ transition opened: :closed
+ end
+
+ event :reopen do
+ transition closed: :opened
+ end
+
+ state :opened
+ state :closed
+ end
+
def last_deployment
deployments.last
end
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index b7f2b2bbe61..b67049f0f55 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -61,15 +61,13 @@ class Namespace < ActiveRecord::Base
def clean_path(path)
path = path.dup
# Get the email username by removing everything after an `@` sign.
- path.gsub!(/@.*\z/, "")
- # Usernames can't end in .git, so remove it.
- path.gsub!(/\.git\z/, "")
- # Remove dashes at the start of the username.
- path.gsub!(/\A-+/, "")
- # Remove periods at the end of the username.
- path.gsub!(/\.+\z/, "")
+ path.gsub!(/@.*\z/, "")
# Remove everything that's not in the list of allowed characters.
- path.gsub!(/[^a-zA-Z0-9_\-\.]/, "")
+ path.gsub!(/[^a-zA-Z0-9_\-\.]/, "")
+ # Remove trailing violations ('.atom', '.git', or '.')
+ path.gsub!(/(\.atom|\.git|\.)*\z/, "")
+ # Remove leading violations ('-')
+ path.gsub!(/\A\-+/, "")
# Users with the great usernames of "." or ".." would end up with a blank username.
# Work around that by setting their username to "blank", followed by a counter.
diff --git a/app/models/project.rb b/app/models/project.rb
index 88e4bd14860..43753bf5458 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -18,9 +18,11 @@ class Project < ActiveRecord::Base
UNKNOWN_IMPORT_URL = 'http://unknown.git'
- cache_markdown_field :description, pipeline: :description
+ delegate :feature_available?, :builds_enabled?, :wiki_enabled?,
+ :merge_requests_enabled?, :issues_enabled?, to: :project_feature,
+ allow_nil: true
- delegate :feature_available?, :builds_enabled?, :wiki_enabled?, :merge_requests_enabled?, to: :project_feature, allow_nil: true
+ cache_markdown_field :description, pipeline: :description
default_value_for :archived, false
default_value_for :visibility_level, gitlab_config_features.visibility_level
@@ -1305,7 +1307,7 @@ class Project < ActiveRecord::Base
environment_ids.where(ref: ref)
end
- environments.where(id: environment_ids).select do |environment|
+ environments.opened.where(id: environment_ids).select do |environment|
environment.includes_commit?(commit)
end
end
diff --git a/app/models/project_feature.rb b/app/models/project_feature.rb
index 530f7d5a30e..e70c0c3bbf9 100644
--- a/app/models/project_feature.rb
+++ b/app/models/project_feature.rb
@@ -38,23 +38,21 @@ class ProjectFeature < ActiveRecord::Base
end
def builds_enabled?
- return true unless builds_access_level
-
builds_access_level > DISABLED
end
def wiki_enabled?
- return true unless wiki_access_level
-
wiki_access_level > DISABLED
end
def merge_requests_enabled?
- return true unless merge_requests_access_level
-
merge_requests_access_level > DISABLED
end
+ def issues_enabled?
+ issues_access_level > DISABLED
+ end
+
private
def get_permission(user, level)
diff --git a/app/models/repository.rb b/app/models/repository.rb
index bf59b74495b..4da1933c189 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -111,8 +111,10 @@ class Repository
def find_commits_by_message(query, ref = nil, path = nil, limit = 1000, offset = 0)
ref ||= root_ref
- # Limited to 1000 commits for now, could be parameterized?
- args = %W(#{Gitlab.config.git.bin_path} log #{ref} --pretty=%H --skip #{offset} --max-count #{limit} --grep=#{query})
+ args = %W(
+ #{Gitlab.config.git.bin_path} log #{ref} --pretty=%H --skip #{offset}
+ --max-count #{limit} --grep=#{query} --regexp-ignore-case
+ )
args = args.concat(%W(-- #{path})) if path.present?
git_log_results = Gitlab::Popen.popen(args, path_to_repo).first.lines.map(&:chomp)
diff --git a/app/services/ci/process_pipeline_service.rb b/app/services/ci/process_pipeline_service.rb
index 36c93dddadb..d3dd30b2588 100644
--- a/app/services/ci/process_pipeline_service.rb
+++ b/app/services/ci/process_pipeline_service.rb
@@ -16,6 +16,8 @@ module Ci
process_stage(index)
end
+ @pipeline.update_status
+
# Return a flag if a when builds got enqueued
new_builds.flatten.any?
end
diff --git a/app/services/create_deployment_service.rb b/app/services/create_deployment_service.rb
index 799ad3e1bd0..c87542e57a2 100644
--- a/app/services/create_deployment_service.rb
+++ b/app/services/create_deployment_service.rb
@@ -4,6 +4,13 @@ class CreateDeploymentService < BaseService
def execute(deployable = nil)
environment = find_or_create_environment
+ if close?
+ environment.close
+ return
+ end
+
+ environment.reopen
+
deployment = project.deployments.create(
environment: environment,
ref: params[:ref],
@@ -14,7 +21,6 @@ class CreateDeploymentService < BaseService
)
deployment.update_merge_request_metrics!
-
deployment
end
@@ -44,6 +50,10 @@ class CreateDeploymentService < BaseService
options[:url]
end
+ def close?
+ options[:close]
+ end
+
def options
params[:options] || {}
end
diff --git a/app/services/mattermost/base_service.rb b/app/services/mattermost/base_service.rb
new file mode 100644
index 00000000000..d96d4eff13a
--- /dev/null
+++ b/app/services/mattermost/base_service.rb
@@ -0,0 +1,52 @@
+module Mattermost
+ class BaseService < ::BaseService
+ def execute
+ # Implement in child
+ raise NotImplementedError
+ end
+
+ private
+
+ def resource_id
+ match = params[:text].to_s.match(/\A(\$|#|!)?(?<id>\d+)\z/)
+
+ match ? match[:id] : nil
+ end
+
+
+ def generate_response(resource)
+ return respond_404 if resource.nil?
+ return single_resource(resource) unless resource.respond_to?(:count)
+ return no_search_results if resource.empty?
+
+ if resource.count == 1
+ single_resource(resource.first)
+ else
+ multiple_resources(resource)
+ end
+ end
+
+ def respond_404
+ {
+ response_type: :ephemeral,
+ text: "404 not found! GitLab couldn't find what your were looking for! :boom:",
+ }
+ end
+
+ def no_search_results
+ {
+ response_type: :ephemeral,
+ text: "### No search results for \"#{params[:text]}\". :disappointed:"
+ }
+ end
+
+ def multiple_resources(resources)
+ list = resources.map { |r| "#{r.to_reference} #{r.title}" }.join('\n\n')
+
+ {
+ response_type: :ephemeral,
+ text: "### Search results for \"#{params[:text]}\"\n\n#{list}"
+ }
+ end
+ end
+end
diff --git a/app/services/mattermost/deploy_service.rb b/app/services/mattermost/deploy_service.rb
new file mode 100644
index 00000000000..1ec2fb1c30b
--- /dev/null
+++ b/app/services/mattermost/deploy_service.rb
@@ -0,0 +1,48 @@
+module Mattermost
+ class DeployService < BaseService
+ def execute
+ environment_name, action_name = parse_command
+ return respond_404 unless environment_name
+
+ environment = project.environments.find_by(name: environment_name)
+
+ return respond_404 unless can?(current_user, :read_environment, environment)
+
+ deployment = environment.last_deployment
+ return respond_404 unless can?(current_user, :create_deployment, deployment) && deployment.deployable
+
+ manual_action = deployment.manual_actions.find { |action| action.name = action_name }
+
+ if manual_action
+ new_build = manual_action.play(current_user)
+ generate_response(new_build)
+ else
+ {
+ response_type: :ephemeral,
+ text: "No action '#{action_name}' defined for #{environment_name}."
+ }
+ end
+ end
+
+ private
+
+ def single_resource(build)
+ {
+ response_type: :in_channel,
+ text: "Action '#{action_name}' started on '#{environment_name}' you can [follow the progress](#{link(new_build)})."
+ }
+ end
+
+ def link(build)
+ Gitlab::Routing.
+ url_helpers.
+ namespace_project_build_url(project.namespace, project, build)
+ end
+
+ def parse_command
+ matches = params[:text].match(/\A(?<name>\w+) to (?<action>\w+)/)
+
+ matches ? [matches[:name], matches[:action]] : nil
+ end
+ end
+end
diff --git a/app/services/mattermost/issue_service.rb b/app/services/mattermost/issue_service.rb
new file mode 100644
index 00000000000..e1429c23438
--- /dev/null
+++ b/app/services/mattermost/issue_service.rb
@@ -0,0 +1,84 @@
+module Mattermost
+ class IssueService < BaseService
+ SUBCOMMANDS = ['create', 'search'].freeze
+
+ def execute
+ resource = if resource_id
+ find_resource
+ elsif subcommand
+ send(subcommand)
+ else
+ nil
+ end
+
+ generate_response(resource)
+ end
+
+ private
+
+ def find_resource
+ return nil unless can?(current_user, :read_issue, project)
+
+ collection.find_by(iid: resource_id)
+ end
+
+ def create
+ return nil unless can?(current_user, :create_issue, project)
+
+ Issues::CreateService.new(project, current_user, issue_params).execute
+ end
+
+ def search
+ return nil unless can?(current_user, :read_issue, project)
+
+ query = params[:text].gsub(/\Asearch /, '')
+ collection.search(query).limit(5)
+ end
+
+ def issue_create_error(errors)
+ {
+ response_type: :ephemeral,
+ text: "An error occured creating your issue: #{errors}" #TODO; this displays an array
+ }
+ end
+
+ def single_resource(issue)
+ return issue_create_error(issue) if issue.errors.any?
+
+ message = "### [#{issue.to_reference} #{issue.title}](#{link(issue)})"
+ message << "\n\n#{issue.description}" if issue.description
+
+ {
+ response_type: :in_channel,
+ text: message
+ }
+ end
+
+ def collection
+ project.issues
+ end
+
+ def link(issue)
+ Gitlab::Routing.
+ url_helpers.
+ namespace_project_issue_url(project.namespace, project, issue)
+ end
+
+ def issue_params
+ match = params[:text].match(/\Acreate (?<title>.+$)/)
+
+ return issue_create_error("No title given") unless match
+
+ {
+ title: match[:title],
+ description: params[:text].gsub(/\Acreate .+$\s*/, '')
+ }
+ end
+
+ def subcommand
+ match = params[:text].match(/\A(?<subcommand>(#{SUBCOMMANDS.join('|')}))/)
+
+ match ? match[:subcommand] : nil
+ end
+ end
+end
diff --git a/app/services/mattermost/merge_request_service.rb b/app/services/mattermost/merge_request_service.rb
new file mode 100644
index 00000000000..f2239fee4c2
--- /dev/null
+++ b/app/services/mattermost/merge_request_service.rb
@@ -0,0 +1,15 @@
+module Mattermost
+ class MergeRequestService < BaseService
+ private
+
+ def collection
+ project.merge_requests
+ end
+
+ def link(merge_request)
+ Gitlab::Routing.
+ url_helpers.
+ namespace_project_merge_request_url(project.namespace, project, merge_request)
+ end
+ end
+end
diff --git a/app/views/projects/builds/_sidebar.html.haml b/app/views/projects/builds/_sidebar.html.haml
index f5344091cae..966633f1f89 100644
--- a/app/views/projects/builds/_sidebar.html.haml
+++ b/app/views/projects/builds/_sidebar.html.haml
@@ -128,7 +128,7 @@
- builds.select{|build| build.status == build_status}.each do |build|
.build-job{class: ('active' if build == @build), data: {stage: build.stage}}
= link_to namespace_project_build_path(@project.namespace, @project, build) do
- = icon('right-arrow')
+ = icon('arrow-right')
= ci_icon_for_status(build.status)
%span
- if build.name
diff --git a/app/views/projects/deployments/_actions.haml b/app/views/projects/deployments/_actions.haml
index 9f249c43673..716eb8d2c12 100644
--- a/app/views/projects/deployments/_actions.haml
+++ b/app/views/projects/deployments/_actions.haml
@@ -1,15 +1,17 @@
-- if can?(current_user, :create_deployment, deployment) && deployment.deployable
- .pull-right
-
+.pull-right
+ - if local_assigns.fetch(:show_external_url, false)
- external_url = deployment.environment.external_url
- if external_url
= link_to external_url, target: '_blank', class: 'btn external-url' do
= icon('external-link')
+ - if can?(current_user, :update_deployment, deployment) && deployment.deployable
+ %a.btn.btn-default.btn-terminal{href: "deployments/#{deployment.id}/terminal"}
+ = custom_icon('icon_terminal')
+
+ - if can?(current_user, :create_deployment, deployment) && deployment.deployable && local_assigns.fetch(:show_actions, false)
- actions = deployment.manual_actions
- if actions.present?
- %a.btn.btn-default.btn-terminal{href: 'deployments/1/terminal'}
- = custom_icon('icon_terminal')
.inline
.dropdown
%a.dropdown-new.btn.btn-default{type: 'button', 'data-toggle' => 'dropdown'}
@@ -22,9 +24,14 @@
= custom_icon('icon_play')
%span= action.name.humanize
- - if local_assigns.fetch(:allow_rollback, false)
- = link_to [:retry, @project.namespace.becomes(Namespace), @project, deployment.deployable], method: :post, class: 'btn btn-build' do
- - if deployment.last?
- Re-deploy
- - else
- Rollback
+ - if can?(current_user, :update_deployment, deployment) && local_assigns.fetch(:allow_close, false) && deployment.closeable?
+ .inline
+ = link_to close_namespace_project_environment_path(@project.namespace.becomes(Namespace), @project, deployment.environment), method: :post, class: 'btn close-env-link', rel: 'nofollow', data: { confirm: 'Are you sure you want to close this environment?' } do
+ = icon('stop', class: 'close-env-icon')
+
+ - if can?(current_user, :create_deployment, deployment) && deployment.deployable && local_assigns.fetch(:allow_rollback, false)
+ = link_to [:retry, @project.namespace.becomes(Namespace), @project, deployment.deployable], method: :post, class: 'btn' do
+ - if deployment.last?
+ Re-deploy
+ - else
+ Rollback
diff --git a/app/views/projects/deployments/_deployment.html.haml b/app/views/projects/deployments/_deployment.html.haml
index ca0005abd0c..05dabe11871 100644
--- a/app/views/projects/deployments/_deployment.html.haml
+++ b/app/views/projects/deployments/_deployment.html.haml
@@ -17,4 +17,4 @@
#{time_ago_with_tooltip(deployment.created_at)}
%td.hidden-xs
- = render 'projects/deployments/actions', deployment: deployment, allow_rollback: true
+ = render 'projects/deployments/actions', deployment: deployment, allow_rollback: true, show_actions: true
diff --git a/app/views/projects/environments/_environment.html.haml b/app/views/projects/environments/_environment.html.haml
index 251694e897c..760bca572c9 100644
--- a/app/views/projects/environments/_environment.html.haml
+++ b/app/views/projects/environments/_environment.html.haml
@@ -28,4 +28,6 @@
#{time_ago_with_tooltip(last_deployment.created_at)}
%td.hidden-xs
- = render 'projects/deployments/actions', deployment: last_deployment
+ = render 'projects/deployments/actions', deployment: last_deployment,
+ allow_close: environment.opened?, allow_rollback: environment.closed?,
+ show_external_url: environment.opened?, show_actions: environment.opened?
diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml
index ab801409722..c83494cebd8 100644
--- a/app/views/projects/environments/index.html.haml
+++ b/app/views/projects/environments/index.html.haml
@@ -3,14 +3,27 @@
= render "projects/pipelines/head"
%div{ class: container_class }
- - if can?(current_user, :create_environment, @project) && !@environments.blank?
- .top-area
- .nav-controls
+ .top-area
+ %ul.nav-links
+ %li{class: ('active' if @scope.nil?)}
+ = link_to project_environments_path(@project) do
+ Available
+ %span.badge.js-avaibale-environments-count
+ = number_with_delimiter(@all_environments.opened.count)
+
+ %li{class: ('active' if @scope == 'closed')}
+ = link_to project_environments_path(@project, scope: :closed) do
+ Stopped
+ %span.badge.js-stopped-environments-count
+ = number_with_delimiter(@all_environments.closed.count)
+
+ .nav-controls
+ - if can?(current_user, :create_environment, @project) && !@all_environments.blank?
= link_to new_namespace_project_environment_path(@project.namespace, @project), class: 'btn btn-create' do
New environment
.environments-container
- - if @environments.blank?
+ - if @all_environments.blank?
.blank-state.blank-state-no-icon
%h2.blank-state-title
You don't have any environments right now.
diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml
index 7a8d196cf4e..01765f8008d 100644
--- a/app/views/projects/environments/show.html.haml
+++ b/app/views/projects/environments/show.html.haml
@@ -4,13 +4,18 @@
%div{ class: container_class }
.top-area
- .col-md-9
- %h3.page-title= @environment.name.capitalize
- .col-md-3
- .nav-controls
- - if can?(current_user, :update_environment, @environment)
- = link_to 'Edit', edit_namespace_project_environment_path(@project.namespace, @project, @environment), class: 'btn'
- = link_to 'Destroy', namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure you want to delete this environment?' }, class: 'btn btn-danger', method: :delete
+ .row
+ .col-sm-6
+ %h3.page-title= @environment.name.capitalize
+ .col-sm-6
+ .nav-controls
+ - if @environment.external_url
+ = link_to @environment.external_url, target: '_blank', class: 'btn external-url' do
+ = icon('external-link')
+ - if can?(current_user, :update_environment, @environment)
+ = link_to 'Edit', edit_namespace_project_environment_path(@project.namespace, @project, @environment), class: 'btn'
+ - if @environment.opened?
+ = link_to 'Close', close_namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure you want to close this environment?' }, class: 'btn btn-danger', method: :post
.deployments-container
- if @deployments.blank?
diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml
index 5b7f83c344f..bc68724ccf0 100644
--- a/app/views/projects/merge_requests/widget/_heading.html.haml
+++ b/app/views/projects/merge_requests/widget/_heading.html.haml
@@ -58,3 +58,7 @@
= link_to external_url, target: '_blank' do
%span.hidden-xs View on #{external_url.gsub(/\A.*?:\/\//, '')}
= icon('external-link', right: true)
+ %span.close-env-container
+ - if environment.closeable?
+ = link_to [:play, @project.namespace.becomes(Namespace), @project, environment.close_action], method: :post, class: 'close-evn-link', rel: 'nofollow', data: { confirm: 'Are you sure you want to close this environment?' } do
+ = icon('stop-circle-o', text: 'Stop environment')
diff --git a/config/routes.rb b/config/routes.rb
index bf7c5b76128..243fd57bf5b 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -74,6 +74,10 @@ Rails.application.routes.draw do
# Notification settings
resources :notification_settings, only: [:create, :update]
+ namespace :slash_commands do
+ post :trigger
+ end
+
draw :import
draw :uploads
draw :explore
diff --git a/config/routes/project.rb b/config/routes/project.rb
index bf991c97034..29aec7d260d 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -316,7 +316,17 @@ resources :namespaces, path: '/', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only:
end
end
- resources :environments
+ resources :environments do
+ member do
+ post :close
+ end
+ end
+
+ resources :deployments, only: [] do
+ member do
+ get :terminal
+ end
+ end
resources :deployments, only: [] do
member do
diff --git a/db/fixtures/development/14_pipelines.rb b/db/fixtures/development/14_pipelines.rb
index 803cbca584d..d3fabe111a1 100644
--- a/db/fixtures/development/14_pipelines.rb
+++ b/db/fixtures/development/14_pipelines.rb
@@ -17,6 +17,7 @@ class Gitlab::Seeder::Pipelines
{ name: 'env:beta', stage: 'deploy', environment: 'beta', status: :running },
{ name: 'env:gamma', stage: 'deploy', environment: 'gamma', status: :canceled },
{ name: 'staging', stage: 'deploy', environment: 'staging', status_event: :success },
+ { name: 'close staging', stage: 'deploy', environment: 'staging', when: 'manual', status: :skipped, options: { environment: { close: true } } },
{ name: 'production', stage: 'deploy', environment: 'production', when: 'manual', status: :skipped },
{ name: 'slack', stage: 'notify', when: 'manual', status: :created },
]
diff --git a/db/fixtures/production/010_settings.rb b/db/fixtures/production/010_settings.rb
new file mode 100644
index 00000000000..5522f31629a
--- /dev/null
+++ b/db/fixtures/production/010_settings.rb
@@ -0,0 +1,16 @@
+if ENV['GITLAB_SHARED_RUNNERS_REGISTRATION_TOKEN'].present?
+ settings = ApplicationSetting.current || ApplicationSetting.create_from_defaults
+ settings.set_runners_registration_token(ENV['GITLAB_SHARED_RUNNERS_REGISTRATION_TOKEN'])
+
+ if settings.save
+ puts "Saved Runner Registration Token".color(:green)
+ else
+ puts "Could not save Runner Registration Token".color(:red)
+ puts
+ settings.errors.full_messages.map do |message|
+ puts "--> #{message}".color(:red)
+ end
+ puts
+ exit 1
+ end
+end
diff --git a/db/migrate/20161006104309_add_state_to_environment.rb b/db/migrate/20161006104309_add_state_to_environment.rb
new file mode 100644
index 00000000000..83dff07069f
--- /dev/null
+++ b/db/migrate/20161006104309_add_state_to_environment.rb
@@ -0,0 +1,15 @@
+class AddStateToEnvironment < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ disable_ddl_transaction!
+
+ DOWNTIME = false
+
+ def up
+ add_column_with_default(:environments, :state, :string, default: :opened)
+ end
+
+ def down
+ remove_column(:environments, :state)
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 56da70b3c02..97ed7335c4b 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20160926145521) do
+ActiveRecord::Schema.define(version: 20161006104309) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -399,11 +399,12 @@ ActiveRecord::Schema.define(version: 20160926145521) do
create_table "environments", force: :cascade do |t|
t.integer "project_id"
- t.string "name", null: false
+ t.string "name", null: false
t.datetime "created_at"
t.datetime "updated_at"
t.string "external_url"
t.string "environment_type"
+ t.string "state", default: "opened", null: false
end
add_index "environments", ["project_id", "name"], name: "index_environments_on_project_id_and_name", using: :btree
diff --git a/doc/administration/environment_variables.md b/doc/administration/environment_variables.md
index b4a953d1ccc..76029b30dd8 100644
--- a/doc/administration/environment_variables.md
+++ b/doc/administration/environment_variables.md
@@ -13,17 +13,18 @@ override certain values.
Variable | Type | Description
-------- | ---- | -----------
-`GITLAB_ROOT_PASSWORD` | string | Sets the password for the `root` user on installation
-`GITLAB_HOST` | string | The full URL of the GitLab server (including `http://` or `https://`)
-`RAILS_ENV` | string | The Rails environment; can be one of `production`, `development`, `staging` or `test`
-`DATABASE_URL` | string | The database URL; is of the form: `postgresql://localhost/blog_development`
-`GITLAB_EMAIL_FROM` | string | The e-mail address used in the "From" field in e-mails sent by GitLab
-`GITLAB_EMAIL_DISPLAY_NAME` | string | The name used in the "From" field in e-mails sent by GitLab
-`GITLAB_EMAIL_REPLY_TO` | string | The e-mail address used in the "Reply-To" field in e-mails sent by GitLab
-`GITLAB_EMAIL_REPLY_TO` | string | The e-mail address used in the "Reply-To" field in e-mails sent by GitLab
-`GITLAB_EMAIL_SUBJECT_SUFFIX` | string | The e-mail subject suffix used in e-mails sent by GitLab
-`GITLAB_UNICORN_MEMORY_MIN` | integer | The minimum memory threshold (in bytes) for the Unicorn worker killer
-`GITLAB_UNICORN_MEMORY_MAX` | integer | The maximum memory threshold (in bytes) for the Unicorn worker killer
+`GITLAB_ROOT_PASSWORD` | string | Sets the password for the `root` user on installation
+`GITLAB_HOST` | string | The full URL of the GitLab server (including `http://` or `https://`)
+`RAILS_ENV` | string | The Rails environment; can be one of `production`, `development`, `staging` or `test`
+`DATABASE_URL` | string | The database URL; is of the form: `postgresql://localhost/blog_development`
+`GITLAB_EMAIL_FROM` | string | The e-mail address used in the "From" field in e-mails sent by GitLab
+`GITLAB_EMAIL_DISPLAY_NAME` | string | The name used in the "From" field in e-mails sent by GitLab
+`GITLAB_EMAIL_REPLY_TO` | string | The e-mail address used in the "Reply-To" field in e-mails sent by GitLab
+`GITLAB_EMAIL_REPLY_TO` | string | The e-mail address used in the "Reply-To" field in e-mails sent by GitLab
+`GITLAB_EMAIL_SUBJECT_SUFFIX` | string | The e-mail subject suffix used in e-mails sent by GitLab
+`GITLAB_UNICORN_MEMORY_MIN` | integer | The minimum memory threshold (in bytes) for the Unicorn worker killer
+`GITLAB_UNICORN_MEMORY_MAX` | integer | The maximum memory threshold (in bytes) for the Unicorn worker killer
+`GITLAB_SHARED_RUNNERS_REGISTRATION_TOKEN` | string | Sets the initial registration token used for GitLab Runners
## Complete database variables
diff --git a/doc/api/ci/runners.md b/doc/api/ci/runners.md
index ecec53fde03..16028d1f124 100644
--- a/doc/api/ci/runners.md
+++ b/doc/api/ci/runners.md
@@ -12,7 +12,9 @@ communication channel. For the consumer API see the
This API uses two types of authentication:
1. Unique Runner's token, which is the token assigned to the Runner after it
- has been registered.
+ has been registered. This token can be found on the Runner's edit page (go to
+ **Project > Runners**, select one of the Runners listed under **Runners activated for
+ this project**).
2. Using Runners' registration token.
This is a token that can be found in project's settings.
@@ -48,7 +50,7 @@ DELETE /ci/api/v1/runners/delete
| Attribute | Type | Required | Description |
| --------- | ------- | --------- | ----------- |
-| `token` | string | yes | Runner's registration token |
+| `token` | string | yes | Unique Runner's token |
Example request:
diff --git a/doc/api/labels.md b/doc/api/labels.md
index 3653ccf304a..656232cc940 100644
--- a/doc/api/labels.md
+++ b/doc/api/labels.md
@@ -148,7 +148,7 @@ PUT /projects/:id/labels
| --------------- | ------- | --------------------------------- | ------------------------------- |
| `id` | integer | yes | The ID of the project |
| `name` | string | yes | The name of the existing label |
-| `new_name` | string | yes if `color` if not provided | The new name of the label |
+| `new_name` | string | yes if `color` is not provided | The new name of the label |
| `color` | string | yes if `new_name` is not provided | The new color of the label in 6-digit hex notation with leading `#` sign |
| `description` | string | no | The new description of the label |
diff --git a/doc/gitlab-basics/start-using-git.md b/doc/gitlab-basics/start-using-git.md
index b61f436c1a4..42cd8bb3e48 100644
--- a/doc/gitlab-basics/start-using-git.md
+++ b/doc/gitlab-basics/start-using-git.md
@@ -1,11 +1,10 @@
# Start using Git on the command line
-If you want to start using a Git and GitLab, make sure that you have created an
-account on GitLab.
+If you want to start using Git and GitLab, make sure that you have created and/or signed into an account on GitLab.
## Open a shell
-Depending on your operating system, find the shell of your preference. Here are some suggestions.
+Depending on your operating system, you will need to use a shell of your preference. Here are some suggestions:
- [Terminal](http://blog.teamtreehouse.com/introduction-to-the-mac-os-x-command-line) on Mac OSX
@@ -22,19 +21,19 @@ Type the following command and then press enter:
git --version
```
-You should receive a message that will tell you which Git version you have in your computer. If you don’t receive a "Git version" message, it means that you need to [download Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git).
+You should receive a message that will tell you which Git version you have on your computer. If you don’t receive a "Git version" message, it means that you need to [download Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git).
If Git doesn't automatically download, there's an option on the website to [download manually](https://git-scm.com/downloads). Then follow the steps on the installation window.
-After you finished installing, open a new shell and type "git --version" again to verify that it was correctly installed.
+After you are finished installing, open a new shell and type "git --version" again to verify that it was correctly installed.
## Add your Git username and set your email
-It is important because every Git commit that you create will use this information.
+It is important to configure your Git username and email address as every Git commit will use this information to identify you as the author.
On your shell, type the following command to add your username:
```
-git config --global user.name ADD YOUR USERNAME
+git config --global user.name "YOUR_USERNAME"
```
Then verify that you have the correct username:
@@ -44,7 +43,7 @@ git config --global user.name
To set your email address, type the following command:
```
-git config --global user.email ADD YOUR EMAIL
+git config --global user.email "your_email_address@example.com"
```
To verify that you entered your email correctly, type:
@@ -52,7 +51,7 @@ To verify that you entered your email correctly, type:
git config --global user.email
```
-You'll need to do this only once because you are using the "--global" option. It tells Git to always use this information for anything you do on that system. If you want to override this with a different username or email address for specific projects, you can run the command without the "--global" option when you’re in that project.
+You'll need to do this only once as you are using the `--global` option. It tells Git to always use this information for anything you do on that system. If you want to override this with a different username or email address for specific projects, you can run the command without the `--global` option when you’re in that project.
## Check your information
@@ -76,7 +75,7 @@ git pull REMOTE NAME-OF-BRANCH -u
(REMOTE: origin) (NAME-OF-BRANCH: could be "master" or an existing branch)
### Create a branch
-Spaces won't be recognized, so you need to use a hyphen or underscore.
+Spaces won't be recognized, so you will need to use a hyphen or underscore.
```
git checkout -b NAME-OF-BRANCH
```
@@ -127,4 +126,3 @@ You need to be in the master branch.
git checkout master
git merge NAME-OF-BRANCH
```
-
diff --git a/lib/expand_variables.rb b/lib/expand_variables.rb
index 7b1533d0d32..9fac4831752 100644
--- a/lib/expand_variables.rb
+++ b/lib/expand_variables.rb
@@ -4,7 +4,7 @@ module ExpandVariables
# Convert hash array to variables
if variables.is_a?(Array)
variables = variables.reduce({}) do |hash, variable|
- hash[variable[:key]] = variable[:value]
+ hash[variable[:key].to_s] = variable[:value]
hash
end
end
diff --git a/lib/gitlab/ci/config/node/environment.rb b/lib/gitlab/ci/config/node/environment.rb
index d388ab6b879..da016becc96 100644
--- a/lib/gitlab/ci/config/node/environment.rb
+++ b/lib/gitlab/ci/config/node/environment.rb
@@ -8,7 +8,7 @@ module Gitlab
class Environment < Entry
include Validatable
- ALLOWED_KEYS = %i[name url]
+ ALLOWED_KEYS = %i[name url close]
validations do
validate do
@@ -35,6 +35,8 @@ module Gitlab
length: { maximum: 255 },
addressable_url: true,
allow_nil: true
+
+ validates :close, boolean: true, allow_nil: true
end
end
@@ -54,6 +56,10 @@ module Gitlab
value[:url]
end
+ def close
+ value[:close]
+ end
+
def value
case @config
when String then { name: @config }
diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb
index 776bbcbb5d0..0d30e1bb92e 100644
--- a/lib/gitlab/regex.rb
+++ b/lib/gitlab/regex.rb
@@ -2,7 +2,7 @@ module Gitlab
module Regex
extend self
- NAMESPACE_REGEX_STR = '(?:[a-zA-Z0-9_\.][a-zA-Z0-9_\-\.]*[a-zA-Z0-9_\-]|[a-zA-Z0-9_])'.freeze
+ NAMESPACE_REGEX_STR = '(?:[a-zA-Z0-9_\.][a-zA-Z0-9_\-\.]*[a-zA-Z0-9_\-]|[a-zA-Z0-9_])(?<!\.git|\.atom)'.freeze
def namespace_regex
@namespace_regex ||= /\A#{NAMESPACE_REGEX_STR}\z/.freeze
@@ -10,7 +10,7 @@ module Gitlab
def namespace_regex_message
"can contain only letters, digits, '_', '-' and '.'. " \
- "Cannot start with '-' or end in '.'." \
+ "Cannot start with '-' or end in '.', '.git' or '.atom'." \
end
def namespace_name_regex
diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb
index 5d33f98e89e..594439a5d4b 100644
--- a/lib/gitlab/workhorse.rb
+++ b/lib/gitlab/workhorse.rb
@@ -111,7 +111,7 @@ module Gitlab
def write_secret
bytes = SecureRandom.random_bytes(SECRET_LENGTH)
File.open(secret_path, 'w:BINARY', 0600) do |f|
- f.chmod(0600)
+ f.chmod(0600) # If the file already existed, the '0600' passed to 'open' above was a no-op.
f.write(Base64.strict_encode64(bytes))
end
end
diff --git a/spec/controllers/slash_commands_controller_spec.rb b/spec/controllers/slash_commands_controller_spec.rb
new file mode 100644
index 00000000000..6095130f679
--- /dev/null
+++ b/spec/controllers/slash_commands_controller_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe SlashCommandsController, type: :controller do
+
+end
diff --git a/spec/db/production/settings.rb b/spec/db/production/settings.rb
new file mode 100644
index 00000000000..a7c5283df94
--- /dev/null
+++ b/spec/db/production/settings.rb
@@ -0,0 +1,16 @@
+require 'spec_helper'
+require 'rainbow/ext/string'
+
+describe 'seed production settings', lib: true do
+ context 'GITLAB_SHARED_RUNNERS_REGISTRATION_TOKEN is set in the environment' do
+ before do
+ allow(ENV).to receive(:[]).and_call_original
+ allow(ENV).to receive(:[]).with('GITLAB_SHARED_RUNNERS_REGISTRATION_TOKEN').and_return('013456789')
+ end
+
+ it 'writes the token to the database' do
+ load(File.join(__dir__, '../../../db/fixtures/production/010_settings.rb'))
+ expect(ApplicationSetting.current.runners_registration_token).to eq('013456789')
+ end
+ end
+end
diff --git a/spec/features/environments_spec.rb b/spec/features/environments_spec.rb
index 68ea4eeae31..35f5706f920 100644
--- a/spec/features/environments_spec.rb
+++ b/spec/features/environments_spec.rb
@@ -18,11 +18,26 @@ feature 'Environments', feature: true do
before do
visit namespace_project_environments_path(project.namespace, project)
end
+
+ context 'shows two tabs' do
+ scenario 'does show Available tab with link' do
+ expect(page).to have_link('Available')
+ end
+
+ scenario 'does show Stopped tab with link' do
+ expect(page).to have_link('Stopped')
+ end
+ end
context 'without environments' do
scenario 'does show no environments' do
expect(page).to have_content('You don\'t have any environments right now.')
end
+
+ scenario 'does show 0 as counter for environments in both tabs' do
+ expect(page.find('.js-avaibale-environments-count').text).to eq('0')
+ expect(page.find('.js-stopped-environments-count').text).to eq('0')
+ end
end
context 'with environments' do
@@ -31,6 +46,14 @@ feature 'Environments', feature: true do
scenario 'does show environment name' do
expect(page).to have_link(environment.name)
end
+
+ scenario 'does show number of opened environments in Availabe tab' do
+ expect(page.find('.js-avaibale-environments-count').text).to eq('1')
+ end
+
+ scenario 'does show number of closed environments in Stopped tab' do
+ expect(page.find('.js-stopped-environments-count').text).to eq('0')
+ end
context 'without deployments' do
scenario 'does show no deployments' do
@@ -79,6 +102,16 @@ feature 'Environments', feature: true do
expect(page).to have_link(nil, href: environment.external_url)
end
end
+
+ scenario 'does show close button' do
+ # TODO: Add test to verify if close button is visible
+ # This needs to be true: if local_assigns.fetch(:allow_close, false) && deployment.closeable?
+ end
+
+ scenario 'does allow to close environment' do
+ # TODO: Add test to verify if close environment works
+ # This needs to be true: if local_assigns.fetch(:allow_close, false) && deployment.closeable?
+ end
end
end
end
@@ -150,6 +183,16 @@ feature 'Environments', feature: true do
expect(page).to have_link(nil, href: environment.external_url)
end
end
+
+ scenario 'does show close button' do
+ # TODO: Add test to verify if close button is visible
+ # This needs to be true: if local_assigns.fetch(:allow_close, false) && deployment.closeable?
+ end
+
+ scenario 'does allow to close environment' do
+ # TODO: Add test to verify if close environment works
+ # This needs to be true: if local_assigns.fetch(:allow_close, false) && deployment.closeable?
+ end
end
end
end
@@ -203,21 +246,34 @@ feature 'Environments', feature: true do
before do
visit namespace_project_environment_path(project.namespace, project, environment)
end
-
+
context 'when logged as master' do
given(:role) { :master }
- scenario 'does delete environment' do
- click_link 'Destroy'
- expect(page).not_to have_link(environment.name)
+ scenario 'does not have a Close link' do
+ expect(page).not_to have_link('Close')
+ end
+
+ context 'when environment is opened and can be closed' do
+ let(:project) { create(:project) }
+ let(:environment) { create(:environment, project: project) }
+
+ let!(:deployment) do
+ create(:deployment, environment: environment, sha: project.commit('master').id)
+ end
+
+ scenario 'does have a Close link' do
+ # TODO: Add missing validation. In order to have Close link
+ # this must be true: last_deployment.try(:close_action)
+ end
end
end
context 'when logged as developer' do
given(:role) { :developer }
- scenario 'does not have a Destroy link' do
- expect(page).not_to have_link('Destroy')
+ scenario 'does not have a Close link' do
+ expect(page).not_to have_link('Close')
end
end
end
diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb
index 2d8b59472e8..c54ec2563ad 100644
--- a/spec/features/groups_spec.rb
+++ b/spec/features/groups_spec.rb
@@ -5,6 +5,12 @@ feature 'Group', feature: true do
login_as(:admin)
end
+ matcher :have_namespace_error_message do
+ match do |page|
+ page.has_content?("Path can contain only letters, digits, '_', '-' and '.'. Cannot start with '-' or end in '.', '.git' or '.atom'.")
+ end
+ end
+
describe 'creating a group with space in group path' do
it 'renders new group form with validation errors' do
visit new_group_path
@@ -13,7 +19,31 @@ feature 'Group', feature: true do
click_button 'Create group'
expect(current_path).to eq(groups_path)
- expect(page).to have_content("Path can contain only letters, digits, '_', '-' and '.'. Cannot start with '-' or end in '.'.")
+ expect(page).to have_namespace_error_message
+ end
+ end
+
+ describe 'creating a group with .atom at end of group path' do
+ it 'renders new group form with validation errors' do
+ visit new_group_path
+ fill_in 'Group path', with: 'atom_group.atom'
+
+ click_button 'Create group'
+
+ expect(current_path).to eq(groups_path)
+ expect(page).to have_namespace_error_message
+ end
+ end
+
+ describe 'creating a group with .git at end of group path' do
+ it 'renders new group form with validation errors' do
+ visit new_group_path
+ fill_in 'Group path', with: 'git_group.git'
+
+ click_button 'Create group'
+
+ expect(current_path).to eq(groups_path)
+ expect(page).to have_namespace_error_message
end
end
diff --git a/spec/features/merge_requests/merge_when_build_succeeds_spec.rb b/spec/features/merge_requests/merge_when_build_succeeds_spec.rb
index 60bc07bd1a0..2c1a45af596 100644
--- a/spec/features/merge_requests/merge_when_build_succeeds_spec.rb
+++ b/spec/features/merge_requests/merge_when_build_succeeds_spec.rb
@@ -79,6 +79,15 @@ feature 'Merge When Build Succeeds', feature: true, js: true do
end
end
+ context 'Has Environment' do
+ let(:environment) { create(:environment, project: project) }
+
+ it 'does show link to close the environment' do
+ # TODO add test to verify if the button is visible when this condition
+ # is met: if environment.closeable?
+ end
+ end
+
def visit_merge_request(merge_request)
visit namespace_project_merge_request_path(merge_request.project.namespace, merge_request.project, merge_request)
end
diff --git a/spec/lib/gitlab/ci/config/node/environment_spec.rb b/spec/lib/gitlab/ci/config/node/environment_spec.rb
index df453223da7..d46cc4355ea 100644
--- a/spec/lib/gitlab/ci/config/node/environment_spec.rb
+++ b/spec/lib/gitlab/ci/config/node/environment_spec.rb
@@ -100,6 +100,34 @@ describe Gitlab::Ci::Config::Node::Environment do
end
end
+ context 'when close is used' do
+ context 'when close is a boolean' do
+ let(:config) do
+ { name: 'review/$CI_BUILD_REF_NAME',
+ close: true }
+ end
+
+ describe '#valid?' do
+ it 'is valid' do
+ expect(entry).to be_valid
+ end
+ end
+ end
+
+ context 'when close is a string' do
+ let(:config) do
+ { name: 'review/$CI_BUILD_REF_NAME',
+ close: 'string' }
+ end
+
+ describe '#valid?' do
+ it 'is invalid' do
+ expect(entry).not_to be_valid
+ end
+ end
+ end
+ end
+
context 'when configuration is invalid' do
context 'when configuration is an array' do
let(:config) { ['env'] }
diff --git a/spec/models/concerns/token_authenticatable_spec.rb b/spec/models/concerns/token_authenticatable_spec.rb
index eb64f3d0c83..4b0bfa43abf 100644
--- a/spec/models/concerns/token_authenticatable_spec.rb
+++ b/spec/models/concerns/token_authenticatable_spec.rb
@@ -6,6 +6,7 @@ shared_examples 'TokenAuthenticatable' do
it { expect(described_class).to be_private_method_defined(:write_new_token) }
it { expect(described_class).to respond_to("find_by_#{token_field}") }
it { is_expected.to respond_to("ensure_#{token_field}") }
+ it { is_expected.to respond_to("set_#{token_field}") }
it { is_expected.to respond_to("reset_#{token_field}!") }
end
end
@@ -55,6 +56,12 @@ describe ApplicationSetting, 'TokenAuthenticatable' do
end
end
+ describe 'setting new token' do
+ subject { described_class.new.send("set_#{token_field}", '0123456789') }
+
+ it { is_expected.to eq '0123456789' }
+ end
+
describe 'multiple token fields' do
before do
described_class.send(:add_authentication_token_field, :yet_another_token)
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index 544920d1824..431b3e4435f 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -114,6 +114,7 @@ describe Namespace, models: true do
it "cleans the path and makes sure it's available" do
expect(Namespace.clean_path("-john+gitlab-ETC%.git@gmail.com")).to eq("johngitlab-ETC2")
+ expect(Namespace.clean_path("--%+--valid_*&%name=.git.%.atom.atom.@email.com")).to eq("valid_name")
end
end
end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 98c64c079b9..4641f297465 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -97,12 +97,20 @@ describe Repository, models: true do
end
describe '#find_commits_by_message' do
- subject { repository.find_commits_by_message('submodule').map{ |k| k.id } }
+ it 'returns commits with messages containing a given string' do
+ commit_ids = repository.find_commits_by_message('submodule').map(&:id)
- it { is_expected.to include('5937ac0a7beb003549fc5fd26fc247adbce4a52e') }
- it { is_expected.to include('6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') }
- it { is_expected.to include('cfe32cf61b73a0d5e9f13e774abde7ff789b1660') }
- it { is_expected.not_to include('913c66a37b4a45b9769037c55c2d238bd0942d2e') }
+ expect(commit_ids).to include('5937ac0a7beb003549fc5fd26fc247adbce4a52e')
+ expect(commit_ids).to include('6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9')
+ expect(commit_ids).to include('cfe32cf61b73a0d5e9f13e774abde7ff789b1660')
+ expect(commit_ids).not_to include('913c66a37b4a45b9769037c55c2d238bd0942d2e')
+ end
+
+ it 'is case insensitive' do
+ commit_ids = repository.find_commits_by_message('SUBMODULE').map(&:id)
+
+ expect(commit_ids).to include('5937ac0a7beb003549fc5fd26fc247adbce4a52e')
+ end
end
describe '#blob_at' do
diff --git a/spec/services/mattermost/deploy_service_spec.rb b/spec/services/mattermost/deploy_service_spec.rb
new file mode 100644
index 00000000000..5e6f44518a8
--- /dev/null
+++ b/spec/services/mattermost/deploy_service_spec.rb
@@ -0,0 +1,27 @@
+require 'spec_helper'
+
+describe Mattermost::DeployService, services: true do
+ let(:project) { create(:empty_project) }
+ let(:user) { create(:user) }
+
+ let(:service) { described_class.new(project, user, params) }
+
+ shared_examples 'a 404 response' do
+ it 'responds with a 404 message' do
+ expect(subject[:response_type]).to be :ephemeral
+ expect(subject[:text]).to start_with '404 not found!'
+ end
+ end
+
+ describe '#execute' do
+ let(:params) { { text: 'envname to action' } }
+ subject { service.execute }
+
+ context 'when the environment can not be found' do
+ it_behaves_like 'a 404 response'
+ end
+
+ context 'the environment exists' do
+ end
+ end
+end
diff --git a/spec/services/mattermost/issue_service_spec.rb b/spec/services/mattermost/issue_service_spec.rb
new file mode 100644
index 00000000000..b45971bdf9b
--- /dev/null
+++ b/spec/services/mattermost/issue_service_spec.rb
@@ -0,0 +1,100 @@
+require 'spec_helper'
+
+describe Mattermost::IssueService, services: true do
+ let(:project) { create(:empty_project) }
+ let(:user) { create(:user) }
+
+ let(:service) { described_class.new(project, user, params) }
+
+ shared_examples 'a 404 response' do
+ it 'responds with a 404 message' do
+ expect(subject[:response_type]).to be :ephemeral
+ expect(subject[:text]).to start_with '404 not found!'
+ end
+ end
+
+ describe '#execute' do
+ subject { service.execute }
+
+ context 'Looking up on iid' do
+ let(:params) { { text: 1 } }
+
+ context 'when the user can not read issues' do
+ it_behaves_like 'a 404 response'
+ end
+
+ context 'when the user has access' do
+ context 'when the resource exists' do
+ let(:issue) { create(:issue, project: project) }
+ let(:params) { { text: issue.iid } }
+
+ before do
+ project.team << [user, :master]
+ end
+
+ it 'returns the resource' do
+ expect(subject[:response_type]).to be :in_channel
+ expect(subject[:text]).to start_with "### [#{issue.to_reference} "
+ end
+ end
+
+ context 'when the resource does not exists' do
+ it_behaves_like 'a 404 response'
+ end
+ end
+ end
+
+ context 'searching for issues' do
+ let!(:issue) { create(:issue, project: project, title: 'Bird is the word') }
+ let!(:issue1) { create(:issue, project: project, title: 'Everybody heard about the bird') }
+ let(:params) { { text: 'search bird' } }
+
+ context 'when the user has no access' do
+ it_behaves_like 'a 404 response'
+ end
+
+ context 'when the user has acces' do
+ before do
+ project.team << [user, :master]
+ end
+
+ context 'when there are results' do
+ it 'returns the resource' do
+ expect(subject[:response_type]).to be :ephemeral
+ expect(subject[:text]).to start_with "### Search results for"
+ end
+ end
+
+ context 'when there is only one result' do
+ let(:params) { { text: 'search about the bird' } }
+
+ it 'returns the resource' do
+ expect(subject[:response_type]).to be :in_channel
+ expect(subject[:text]).to start_with "### [#{issue1.to_reference} "
+ end
+ end
+ end
+ end
+
+ context 'creating an issue' do
+ let(:params) { { text: "create The Trashmen\nBird is the word" } }
+
+ context 'when the user has no access' do
+ it_behaves_like 'a 404 response'
+ end
+
+ context 'when the user has acces' do
+ before do
+ project.team << [user, :master]
+ end
+
+ context 'it creates an issue' do
+ it 'returns the resource' do
+ expect(subject[:response_type]).to be :in_channel
+ expect(subject[:text]).to match /The Trashmen/
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb b/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb
index 520e906b21f..9a29e400654 100644
--- a/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb
+++ b/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb
@@ -110,9 +110,21 @@ describe MergeRequests::MergeWhenBuildSucceedsService do
context 'properly handles multiple stages' do
let(:ref) { mr_merge_if_green_enabled.source_branch }
- let!(:build) { create(:ci_build, :created, pipeline: pipeline, ref: ref, name: 'build', stage: 'build') }
- let!(:test) { create(:ci_build, :created, pipeline: pipeline, ref: ref, name: 'test', stage: 'test') }
- let(:pipeline) { create(:ci_empty_pipeline, ref: mr_merge_if_green_enabled.source_branch, project: project) }
+ let(:sha) { project.commit(ref).id }
+
+ let(:pipeline) do
+ create(:ci_empty_pipeline, ref: ref, sha: sha, project: project)
+ end
+
+ let!(:build) do
+ create(:ci_build, :created, pipeline: pipeline, ref: ref,
+ name: 'build', stage: 'build')
+ end
+
+ let!(:test) do
+ create(:ci_build, :created, pipeline: pipeline, ref: ref,
+ name: 'test', stage: 'test')
+ end
before do
# This behavior of MergeRequest: we instantiate a new object
@@ -121,14 +133,16 @@ describe MergeRequests::MergeWhenBuildSucceedsService do
end
end
- it "doesn't merge if some stages failed" do
+ it "doesn't merge if any of stages failed" do
expect(MergeWorker).not_to receive(:perform_async)
+
build.success
test.drop
end
- it 'merge when all stages succeeded' do
+ it 'merges when all stages succeeded' do
expect(MergeWorker).to receive(:perform_async)
+
build.success
test.success
end
diff --git a/vendor/gitlab-ci-yml/OpenShift.gitlab-ci.yml b/vendor/gitlab-ci-yml/OpenShift.gitlab-ci.yml
new file mode 100644
index 00000000000..75924516fb2
--- /dev/null
+++ b/vendor/gitlab-ci-yml/OpenShift.gitlab-ci.yml
@@ -0,0 +1,99 @@
+image: ayufan/openshift-cli
+
+stages:
+ - build
+ - test
+ - staging
+ - production
+
+variables:
+ # Configure these variables in Secure Variables:
+ OPENSHIFT_SERVER: https://origin.gitlap.com:8443
+ OPENSHIFT_DOMAIN: apps.origin.gitlap.com
+ # OPENSHIFT_TOKEN: my.openshift.token
+
+before_script:
+ - oc login "$OPENSHIFT_SERVER" --token="$OPENSHIFT_TOKEN" --insecure-skip-tls-verify
+ - oc project "$CI_PROJECT_NAME"
+
+build:
+ stage: build
+ before_script: []
+ script:
+ - echo build
+
+test1:
+ stage: test
+ before_script: []
+ script:
+ - echo run tests
+
+test2:
+ stage: test
+ before_script: []
+ script:
+ - echo run tests
+
+.deploy: &deploy
+ image: ayufan/openshift-cli
+ script:
+ - oc get services $APP || oc new-app . --name=$APP --strategy=docker
+ - oc get routes $APP || oc expose service $APP --hostname=$APP_HOST
+ - oc start-build $APP --from-dir=. --wait
+
+production:
+ <<: *deploy
+ stage: production
+ variables:
+ APP: production
+ APP_HOST: production.$OPENSHIFT_DOMAIN
+ when: manual
+ environment:
+ name: production
+ url: http://production.$OPENSHIFT_DOMAIN
+ only:
+ - master
+
+staging:
+ <<: *deploy
+ stage: staging
+ variables:
+ APP: staging
+ APP_HOST: staging.$OPENSHIFT_DOMAIN
+ environment:
+ name: staging
+ url: http://staging.$OPENSHIFT_DOMAIN
+ only:
+ - master
+
+review:
+ <<: *deploy
+ stage: staging
+ variables:
+ APP: review-$CI_BUILD_REF_NAME
+ APP_HOST: $CI_BUILD_REF_NAME.review.$OPENSHIFT_DOMAIN
+ environment:
+ name: review/$CI_BUILD_REF_NAME
+ url: http://$CI_BUILD_REF_NAME.review.$OPENSHIFT_DOMAIN
+ only:
+ - branches
+ except:
+ - master
+
+close review:
+ <<: *deploy
+ stage: staging
+ script:
+ - oc delete all -l "app=$APP"
+ when: manual
+ variables:
+ APP: review-$CI_BUILD_REF_NAME
+ environment:
+ name: review/$CI_BUILD_REF_NAME
+ close: true
+ only:
+ - branches
+ except:
+ - master
+
+