Commit 07539ab2 authored by Grzegorz Bizon's avatar Grzegorz Bizon

Merge branch 'master' into pipeline-blocking-actions

* master: (26 commits)
  Fix UserBasic
  Rename `/take` to `/take_ownership`, expose `owner` in `v3`.
  Update after review
  Fix values being called at Array instead of Hash
  Fix json response in branches controller
  Improve docs and specs related to pages artifacts
  Add MR fo changelog about removing pages artifacts
  Delete artifacts for pages unless expiry date is specified
  Lint doc
  Improved team selection
  Move foreign key to separate migration
  Fix import model attributes
  Update documentation and expose ID
  Introduce tests for pipeline triggers
  Fix trigger model
  Update db/schema
  Make triggers to be user aware
  Make Pipeline Triggers to be user aware
  Update triggers API
  Remove remnants of git annex
  ...

Conflicts:
	db/schema.rb
parents b7d74401 348dff0a
......@@ -198,7 +198,7 @@ require('./task_list');
this.refreshing = true;
return $.ajax({
url: this.notes_url,
data: "last_fetched_at=" + this.last_fetched_at,
headers: { "X-Last-Fetched-At": this.last_fetched_at },
dataType: "json",
success: (function(_this) {
return function(data) {
......
......@@ -8,6 +8,19 @@ body {
&.navless {
background-color: $white-light !important;
}
&.card-content {
background-color: $gray-darker;
.content-wrapper {
padding: 0;
.container-fluid,
.container-limited {
background-color: $gray-darker;
}
}
}
}
.container {
......
......@@ -10,11 +10,6 @@ class Profiles::KeysController < Profiles::ApplicationController
@key = current_user.keys.find(params[:id])
end
# Back-compat: We need to support this URL since git-annex webapp points to it
def new
redirect_to profile_keys_path
end
def create
@key = current_user.keys.new(key_params)
......
......@@ -47,11 +47,14 @@ class ProfilesController < Profiles::ApplicationController
end
def update_username
@user.update_attributes(username: user_params[:username])
respond_to do |format|
format.js
if @user.update_attributes(username: user_params[:username])
options = { notice: "Username successfully changed" }
else
message = @user.errors.full_messages.uniq.join('. ')
options = { alert: "Username change failed - #{message}" }
end
redirect_back_or_default(default: { action: 'show' }, options: options)
end
private
......
......@@ -20,7 +20,7 @@ class Projects::BranchesController < Projects::ApplicationController
respond_to do |format|
format.html
format.json do
render json: @repository.branch_names
render json: @branches.map(&:name)
end
end
end
......
......@@ -211,6 +211,11 @@ class Projects::NotesController < Projects::ApplicationController
end
def find_current_user_notes
@notes = NotesFinder.new(project, current_user, params).execute.inc_author
@notes = NotesFinder.new(project, current_user, params.merge(last_fetched_at: last_fetched_at))
.execute.inc_author
end
def last_fetched_at
request.headers['X-Last-Fetched-At']
end
end
module MattermostHelper
def mattermost_teams_options(teams)
teams_options = teams.map do |id, options|
[options['display_name'] || options['name'], id]
teams.map do |team|
[team['display_name'] || team['name'], team['id']]
end
teams_options.compact.unshift(['Select team...', '0'])
end
end
module TriggersHelper
def builds_trigger_url(project_id, ref: nil)
if ref.nil?
"#{Settings.gitlab.url}/api/v3/projects/#{project_id}/trigger/builds"
"#{Settings.gitlab.url}/api/v4/projects/#{project_id}/trigger/pipeline"
else
"#{Settings.gitlab.url}/api/v3/projects/#{project_id}/ref/#{ref}/trigger/builds"
"#{Settings.gitlab.url}/api/v4/projects/#{project_id}/ref/#{ref}/trigger/pipeline"
end
end
......
......@@ -5,10 +5,11 @@ module Ci
acts_as_paranoid
belongs_to :project, foreign_key: :gl_project_id
belongs_to :owner, class_name: "User"
has_many :trigger_requests, dependent: :destroy
validates :token, presence: true
validates :token, uniqueness: true
validates :token, presence: true, uniqueness: true
before_validation :set_default_values
......@@ -25,7 +26,11 @@ module Ci
end
def short_token
token[0...10]
token[0...4]
end
def can_show_token?(user)
owner.blank? || owner == user
end
end
end
......@@ -85,6 +85,7 @@ class Note < ActiveRecord::Base
before_validation :nullify_blank_type, :nullify_blank_line_code
before_validation :set_discussion_id
after_save :keep_around_commit, unless: :for_personal_snippet?
after_save :expire_etag_cache
class << self
def model_name
......@@ -272,4 +273,16 @@ class Note < ActiveRecord::Base
self.class.build_discussion_id(noteable_type, noteable_id || commit_id)
end
end
def expire_etag_cache
return unless for_issue?
key = Gitlab::Routing.url_helpers.namespace_project_noteable_notes_path(
noteable.project.namespace,
noteable.project,
target_type: noteable_type.underscore,
target_id: noteable.id
)
Gitlab::EtagCaching::Store.new.touch(key)
end
end
......@@ -95,6 +95,7 @@ class User < ActiveRecord::Base
has_many :todos, dependent: :destroy
has_many :notification_settings, dependent: :destroy
has_many :award_emoji, dependent: :destroy
has_many :triggers, dependent: :destroy, class_name: 'Ci::Trigger', foreign_key: :owner_id
has_many :assigned_issues, dependent: :nullify, foreign_key: :assignee_id, class_name: "Issue"
has_many :assigned_merge_requests, dependent: :nullify, foreign_key: :assignee_id, class_name: "MergeRequest"
......
......@@ -3,7 +3,7 @@ module Ci
def execute(project, trigger, ref, variables = nil)
trigger_request = trigger.trigger_requests.create(variables: variables)
pipeline = Ci::CreatePipelineService.new(project, nil, ref: ref).
pipeline = Ci::CreatePipelineService.new(project, trigger.owner, ref: ref).
execute(ignore_skip_ci: true, trigger_request: trigger_request)
if pipeline.persisted?
trigger_request
......
......@@ -34,6 +34,8 @@ module Projects
end
rescue => e
error(e.message)
ensure
build.erase_artifacts! unless build.has_expiring_artifacts?
end
private
......
!!! 5
%html{ lang: "en", class: "#{page_class}" }
= render "layouts/head"
%body{ data: { page: body_data_page, project: "#{@project.path if @project}", group: "#{@group.path if @group}" } }
%body{ class: @body_class, data: { page: body_data_page, project: "#{@project.path if @project}", group: "#{@group.path if @group}" } }
= Gon::Base.render_data
= render "layouts/header/default", title: header_title
......
......@@ -93,7 +93,7 @@
%p
Changing your username will change path to all personal projects!
.col-lg-9
= form_for @user, url: update_username_profile_path, method: :put, remote: true, html: {class: "update-username"} do |f|
= form_for @user, url: update_username_profile_path, method: :put, html: {class: "update-username"} do |f|
.form-group
= f.label :username, "Path", class: "label-light"
.input-group
......
- if @user.valid?
:plain
new Flash("Username successfully changed", "notice")
- else
- error = @user.errors.full_messages.first
:plain
new Flash("Username change failed - #{escape_javascript error.html_safe}", "alert")
......@@ -2,16 +2,15 @@
This service will be installed on the Mattermost instance at
%strong= link_to Gitlab.config.mattermost.host, Gitlab.config.mattermost.host
%hr
= form_for(:mattermost, method: :post, url: namespace_project_mattermost_path(@project.namespace, @project)) do |f|
= form_for(:mattermost, method: :post, url: namespace_project_mattermost_path(@project.namespace, @project), html: { class: 'js-requires-input'} ) do |f|
%h4 Team
%p
= @teams.one? ? 'The team' : 'Select the team'
where the slash commands will be used in
- selected_id = @teams.one? ? @teams.keys.first : 0
- options = mattermost_teams_options(@teams)
- options = options_for_select(options, selected_id)
= f.select(:team_id, options, {}, { class: 'form-control', disabled: @teams.one?, selected: selected_id })
= f.hidden_field(:team_id, value: selected_id) if @teams.one?
- selected_id = @teams.one? ? @teams.first['id'] : nil
- options = options_for_select(mattermost_teams_options(@teams), selected_id)
= f.select(:team_id, options, { include_blank: 'Select team...'}, { class: 'form-control', disabled: @teams.one?, selected: selected_id, required: true })
= f.hidden_field(:team_id, value: selected_id, required: true) if @teams.one?
.help-block
- if @teams.one?
This is the only available team.
......@@ -25,7 +24,7 @@
%hr
%h4 Command trigger word
%p Choose the word that will trigger commands
= f.text_field(:trigger, value: @project.path, class: 'form-control')
= f.text_field(:trigger, value: @project.path, class: 'form-control', required: true)
.help-block
%p
Trigger word must be unique, and can't begin with a slash or contain any spaces.
......
- @body_class = 'card-content'
.service-installation
.inline.pull-right
= custom_icon('mattermost_logo', size: 48)
......
......@@ -23,4 +23,4 @@
to post a comment
:javascript
var notes = new Notes("#{namespace_project_notes_path(namespace_id: @project.namespace, project_id: @project, target_id: @noteable.id, target_type: @noteable.class.name.underscore)}", #{@notes.map(&:id).to_json}, #{Time.now.to_i}, "#{diff_view}")
var notes = new Notes("#{namespace_project_noteable_notes_path(namespace_id: @project.namespace, project_id: @project, target_id: @noteable.id, target_type: @noteable.class.name.underscore)}", #{@notes.map(&:id).to_json}, #{Time.now.to_i}, "#{diff_view}")
---
title: Remove remnants of git annex support.
merge_request:
author:
---
title: Update account view to display new username
merge_request:
author:
---
title: Fix json response in branches controller
merge_request: 9710
author: George Andrinopoulos
---
title: Delete artifacts for pages unless expiry date is specified
merge_request: 9716
author:
---
title: Use ETag to improve performance of issue notes polling
merge_request: 9036
author:
---
title: Introduce Pipeline Triggers that are user-aware
merge_request:
author:
# This middleware has to come after Gitlab::Metrics::RackMiddleware
# in the middleware stack, because it tracks events with
# GitLab Performance Monitoring
Rails.application.config.middleware.use(Gitlab::EtagCaching::Middleware)
......@@ -21,7 +21,7 @@ resource :profile, only: [:show, :update] do
end
end
resource :preferences, only: [:show, :update]
resources :keys, only: [:index, :show, :new, :create, :destroy]
resources :keys, only: [:index, :show, :create, :destroy]
resources :emails, only: [:index, :create, :destroy]
resources :chat_names, only: [:index, :new, :create, :destroy] do
collection do
......
......@@ -267,7 +267,7 @@ constraints(ProjectUrlConstrainer.new) do
resources :group_links, only: [:index, :create, :update, :destroy], constraints: { id: /\d+/ }
resources :notes, only: [:index, :create, :destroy, :update], concerns: :awardable, constraints: { id: /\d+/ } do
resources :notes, only: [:create, :destroy, :update], concerns: :awardable, constraints: { id: /\d+/ } do
member do
delete :delete_attachment
post :resolve
......@@ -275,6 +275,8 @@ constraints(ProjectUrlConstrainer.new) do
end
end
get 'noteable/:target_type/:target_id/notes' => 'notes#index', as: 'noteable_notes'
resources :boards, only: [:index, :show] do
scope module: :boards do
resources :issues, only: [:index, :update]
......
class AddOwnerIdToTriggers < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def change
add_column :ci_triggers, :owner_id, :integer
end
end
class AddDescriptionToTriggers < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def change
add_column :ci_triggers, :description, :string
end
end
class AddOwnerIdForeignKey < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def change
add_concurrent_foreign_key :ci_triggers, :users, column: :owner_id, on_delete: :cascade
end
end
......@@ -111,7 +111,7 @@ ActiveRecord::Schema.define(version: 20170306090835) do
t.boolean "plantuml_enabled"
t.integer "max_pages_size", default: 100, null: false
t.integer "terminal_max_session_time", default: 0, null: false
t.string "default_artifacts_expire_in", default: '0', null: false
t.string "default_artifacts_expire_in", default: "0", null: false
end
create_table "audit_events", force: :cascade do |t|
......@@ -377,6 +377,8 @@ ActiveRecord::Schema.define(version: 20170306090835) do
t.datetime "created_at"
t.datetime "updated_at"
t.integer "gl_project_id"
t.integer "owner_id"
t.string "description"
end
add_index "ci_triggers", ["gl_project_id"], name: "index_ci_triggers_on_gl_project_id", using: :btree
......@@ -581,9 +583,9 @@ ActiveRecord::Schema.define(version: 20170306090835) do
end
add_index "labels", ["group_id", "project_id", "title"], name: "index_labels_on_group_id_and_project_id_and_title", unique: true, using: :btree
add_index "labels", ["type", "project_id"], name: "index_labels_on_type_and_project_id", using: :btree
add_index "labels", ["project_id"], name: "index_labels_on_project_id", using: :btree
add_index "labels", ["title"], name: "index_labels_on_title", using: :btree
add_index "labels", ["type", "project_id"], name: "index_labels_on_type_and_project_id", using: :btree
create_table "lfs_objects", force: :cascade do |t|
t.string "oid", null: false
......@@ -1333,6 +1335,7 @@ ActiveRecord::Schema.define(version: 20170306090835) do
add_index "web_hooks", ["project_id"], name: "index_web_hooks_on_project_id", using: :btree
add_foreign_key "boards", "projects"
add_foreign_key "ci_triggers", "users", column: "owner_id", name: "fk_e8e10d1964", on_delete: :cascade
add_foreign_key "issue_metrics", "issues", on_delete: :cascade
add_foreign_key "label_priorities", "labels", on_delete: :cascade
add_foreign_key "label_priorities", "projects", on_delete: :cascade
......
......@@ -12,7 +12,6 @@ following locations:
- [Branches](branches.md)
- [Broadcast Messages](broadcast_messages.md)
- [Builds](builds.md)
- [Build Triggers](build_triggers.md)
- [Build Variables](build_variables.md)
- [Commits](commits.md)
- [Deployments](deployments.md)
......@@ -33,6 +32,7 @@ following locations:
- [Notes](notes.md) (comments)
- [Notification settings](notification_settings.md)
- [Pipelines](pipelines.md)
- [Pipeline Triggers](pipeline_triggers.md)
- [Projects](projects.md) including setting Webhooks
- [Project Access Requests](access_requests.md)
- [Project Members](members.md)
......
# Build triggers
You can read more about [triggering builds through the API](../ci/triggers/README.md).
## List project triggers
Get a list of project's build triggers.
```
GET /projects/:id/triggers
```
| Attribute | Type | required | Description |
|-----------|---------|----------|---------------------|
| `id` | integer | yes | The ID of a project |
```
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/triggers"
```
```json
[
{
"created_at": "2015-12-23T16:24:34.716Z",
"deleted_at": null,
"last_used": "2016-01-04T15:41:21.986Z",
"token": "fbdb730c2fbdb095a0862dbd8ab88b",
"updated_at": "2015-12-23T16:24:34.716Z"
},
{
"created_at": "2015-12-23T16:25:56.760Z",
"deleted_at": null,
"last_used": null,
"token": "7b9148c158980bbd9bcea92c17522d",
"updated_at": "2015-12-23T16:25:56.760Z"
}
]
```
## Get trigger details
Get details of project's build trigger.
```
GET /projects/:id/triggers/:token
```
| Attribute | Type | required | Description |
|-----------|---------|----------|--------------------------|
| `id` | integer | yes | The ID of a project |
| `token` | string | yes | The `token` of a trigger |
```
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/triggers/7b9148c158980bbd9bcea92c17522d"
```
```json
{
"created_at": "2015-12-23T16:25:56.760Z",
"deleted_at": null,
"last_used": null,
"token": "7b9148c158980bbd9bcea92c17522d",
"updated_at": "2015-12-23T16:25:56.760Z"
}
```
## Create a project trigger
Create a build trigger for a project.
```
POST /projects/:id/triggers
```
| Attribute | Type | required | Description |
|-----------|---------|----------|--------------------------|
| `id` | integer | yes | The ID of a project |
```
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/triggers"
```
```json
{
"created_at": "2016-01-07T09:53:58.235Z",
"deleted_at": null,
"last_used": null,
"token": "6d056f63e50fe6f8c5f8f4aa10edb7",
"updated_at": "2016-01-07T09:53:58.235Z"
}
```
## Remove a project trigger
Remove a project's build trigger.
```
DELETE /projects/:id/triggers/:token
```
| Attribute | Type | required | Description |
|-----------|---------|----------|--------------------------|
| `id` | integer | yes | The ID of a project |
| `token` | string | yes | The `token` of a trigger |
```
curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/triggers/7b9148c158980bbd9bcea92c17522d"
```
This document was moved to [Pipeline Triggers](pipeline_triggers.md).
# Pipeline triggers
You can read more about [triggering pipelines through the API](../ci/triggers/README.md).
## List project triggers
Get a list of project's build triggers.
```
GET /projects/:id/triggers
```
| Attribute | Type | required | Description |
|-----------|---------|----------|---------------------|
| `id` | integer | yes | The ID of a project |
```
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/triggers"
```
```json
[
{
"id": 10,
"description": "my trigger",
"created_at": "2016-01-07T09:53:58.235Z",
"deleted_at": null,
"last_used": null,
"token": "6d056f63e50fe6f8c5f8f4aa10edb7",
"updated_at": "2016-01-07T09:53:58.235Z",
"owner": null
}
]
```
## Get trigger details
Get details of project's build trigger.
```
GET /projects/:id/triggers/:trigger_id
```
| Attribute | Type | required | Description |
|-----------|---------|----------|--------------------------|
| `id` | integer | yes | The ID of a project |
| `token` | string | yes | The `token` of a trigger |
```
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/triggers/5"
```
```json
{
"id": 10,
"description": "my trigger",
"created_at": "2016-01-07T09:53:58.235Z",
"deleted_at": null,
"last_used": null,
"token": "6d056f63e50fe6f8c5f8f4aa10edb7",
"updated_at": "2016-01-07T09:53:58.235Z",
"owner": null
}
```
## Create a project trigger
Create a trigger for a project.
```
POST /projects/:id/triggers
```
| Attribute | Type | required | Description |
|---------------|---------|----------|--------------------------|
| `id` | integer | yes | The ID of a project |
| `description` | string | yes | The trigger name |
```
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --form description="my description" "https://gitlab.example.com/api/v4/projects/1/triggers"
```
```json
{
"id": 10,
"description": "my trigger",
"created_at": "2016-01-07T09:53:58.235Z",
"deleted_at": null,
"last_used": null,
"token": "6d056f63e50fe6f8c5f8f4aa10edb7",
"updated_at": "2016-01-07T09:53:58.235Z",
"owner": null
}
```
## Update a project trigger
Update a trigger for a project.
```
PUT /projects/:id/triggers/:trigger_id
```
| Attribute | Type | required | Description |
|---------------|---------|----------|--------------------------|
| `trigger_id` | integer | yes | The trigger id |
| `description` | string | no | The trigger name |
```
curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --form description="my description" "https://gitlab.example.com/api/v4/projects/1/triggers/10"
```
```json
{
"id": 10,
"description": "my trigger",
"created_at": "2016-01-07T09:53:58.235Z",
"deleted_at": null,
"last_used": null,
"token": "6d056f63e50fe6f8c5f8f4aa10edb7",
"updated_at": "2016-01-07T09:53:58.235Z",
"owner": null
}
```
## Take ownership of a project trigger
Update an owner of a project trigger.
```
POST /projects/:id/triggers/:trigger_id/take_ownership
```
| Attribute | Type | required | Description |
|---------------|---------|----------|--------------------------|
| `trigger_id` | integer | yes | The trigger id |
```
curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/triggers/10/take_ownership"
```
```json
{
"id": 10,
"description": "my trigger",
"created_at": "2016-01-07T09:53:58.235Z",
"deleted_at": null,
"last_used": null,
"token": "6d056f63e50fe6f8c5f8f4aa10edb7",
"updated_at": "2016-01-07T09:53:58.235Z",
"owner": null
}
```
## Remove a project trigger
Remove a project's build trigger.
```
DELETE /projects/:id/triggers/:trigger_id
```
| Attribute | Type | required | Description |
|----------------|---------|----------|--------------------------|
| `id` | integer | yes | The ID of a project |
| `trigger_id` | integer | yes | The trigger id |
```
curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/triggers/5"
```
......@@ -59,3 +59,6 @@ changes are in V4:
- Return 202 with JSON body on async removals on V4 API (DELETE `/projects/:id/repository/merged_branches` and DELETE `/projects/:id`) [!9449](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9449)
- `projects/:id/milestones?iid[]=x&iid[]=y` array filter has been renamed to `iids` [!9096](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9096)
- Return basic info about pipeline in `GET /projects/:id/pipelines` [!8875](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8875)
- Rename Build Triggers to be Pipeline Triggers API [!9713](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9713)
- `POST /projects/:id/trigger/builds` to `POST /projects/:id/trigger/pipeline`
- Require description when creating a new trigger `POST /projects/:id/triggers`
......@@ -36,7 +36,7 @@ it will not trigger a job.
To trigger a job you need to send a `POST` request to GitLab's API endpoint:
```
POST /projects/:id/trigger/builds
POST /projects/:id/trigger/pipeline
```
The required parameters are the trigger's `token` and the Git `ref` on which
......@@ -71,7 +71,7 @@ To trigger a job from webhook of another project you need to add the following
webhook url for Push and Tag push events:
```
https://gitlab.example.com/api/v4/projects/:id/ref/:ref/trigger/builds?token=TOKEN
https://gitlab.example.com/api/v4/projects/:id/ref/:ref/trigger/pipeline?token=TOKEN
```
> **Note**:
......@@ -105,7 +105,7 @@ Using cURL you can trigger a rebuild with minimal effort, for example:
curl --request POST \
--form token=TOKEN \
--form ref=master \
https://gitlab.example.com/api/v4/projects/9/trigger/builds
https://gitlab.example.com/api/v4/projects/9/trigger/pipeline
```
In this case, the project with ID `9` will get rebuilt on `master` branch.
......@@ -114,7 +114,7 @@ Alternatively, you can pass the `token` and `ref` arguments in the query string:
```bash
curl --request POST \
"https://gitlab.example.com/api/v4/projects/9/trigger/builds?token=TOKEN&ref=master"
"https://gitlab.example.com/api/v4/projects/9/trigger/pipeline?token=TOKEN&ref=master"
```
### Triggering a job within `.gitlab-ci.yml`
......@@ -128,7 +128,7 @@ need to add in project's A `.gitlab-ci.yml`:
build_docs:
stage: deploy
script:
- "curl --request POST --form token=TOKEN --form ref=master https://gitlab.example.com/api/v4/projects/9/trigger/builds"
- "curl --request POST --form token=TOKEN --form ref=master https://gitlab.example.com/api/v4/projects/9/trigger/pipeline"
only:
- tags
```
......@@ -187,7 +187,7 @@ curl --request POST \
--form token=TOKEN \
--form ref=master \
--form "variables[UPLOAD_TO_S3]=true" \
https://gitlab.example.com/api/v4/projects/9/trigger/builds
https://gitlab.example.com/api/v4/projects/9/trigger/pipeline
```
### Using webhook to trigger job
......@@ -195,7 +195,7 @@ curl --request POST \
You can add the following webhook to another project in order to trigger a job:
```
https://gitlab.example.com/api/v4/projects/9/ref/master/trigger/builds?token=TOKEN&variables[UPLOAD_TO_S3]=true
https://gitlab.example.com/api/v4/projects/9/ref/master/trigger/pipeline?token=TOKEN&variables[UPLOAD_TO_S3]=true
```
### Using cron to trigger nightly jobs
......@@ -205,7 +205,7 @@ in conjunction with cron. The example below triggers a job on the `master`
branch of project with ID `9` every night at `00:30`:
```bash
30 0 * * * curl --request POST --form token=TOKEN --form ref=master https://gitlab.example.com/api/v4/projects/9/trigger/builds
30 0 * * * curl --request POST --form token=TOKEN --form ref=master https://gitlab.example.com/api/v4/projects/9/trigger/pipeline
```
[ci-229]: https://gitlab.com/gitlab-org/gitlab-ci/merge_requests/229
......@@ -308,8 +308,8 @@ Running on runner-8a2f473d-project-1796893-concurrent-0 via runner-8a2f473d-mach
++ CI_RUNNER_ID=1337
++ export CI_RUNNER_DESCRIPTION=shared-runners-manager-1.example.com
++ CI_RUNNER_DESCRIPTION=shared-runners-manager-1.example.com
++ export 'CI_RUNNER_TAGS=shared, docker, linux, ruby, mysql, postgres, mongo, git-annex'
++ CI_RUNNER_TAGS='shared, docker, linux, ruby, mysql, postgres, mongo, git-annex'
++ export 'CI_RUNNER_TAGS=shared, docker, linux, ruby, mysql, postgres, mongo'
++ CI_RUNNER_TAGS='shared, docker, linux, ruby, mysql, postgres, mongo'
++ export CI_REGISTRY=registry.example.com
++ CI_REGISTRY=registry.example.com
++ export CI_DEBUG_TRACE=true
......
......@@ -35,7 +35,7 @@ Using this method is in general preferred over directly calling the various
instrumentation methods.
Method instrumentation should be added in the initializer
`config/initializers/metrics.rb`.
`config/initializers/8_metrics.rb`.
### Examples
......
......@@ -15,13 +15,6 @@ Kerberos and Atlassian Crowd are only available on the Enterprise Edition, so
you should disable these mechanisms before downgrading and you should provide
alternative authentication methods to your users.
### Git Annex
Git Annex is also only available on the Enterprise Edition. This means that if
you have repositories that use Git Annex to store large files, these files will
no longer be easily available via Git. You should consider migrating these
repositories to use Git LFS before downgrading to the Community Edition.
### Remove Jenkins CI Service entries from the database
The `JenkinsService` class is only available on the Enterprise Edition codebase,
......
......@@ -165,7 +165,7 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project
#### 3.4. Large Files
1. [Big files in Git (Git LFS, Annex) - Video](https://www.youtube.com/watch?v=DawznUxYDe4)
1. [Big files in Git (Git LFS) - Video](https://www.youtube.com/watch?v=DawznUxYDe4)
#### 3.5. LDAP and Active Directory
......
......@@ -167,7 +167,6 @@ Some tickets need specific knowledge or a deep understanding of a particular com
Move on to understanding some of GitLab's more advanced features. You can make use of GitLab.com to understand the features from an end-user perspective and then use your own instance to understand setup and configuration of the feature from an Administrative perspective
- Set up and try [Git Annex](https://docs.gitlab.com/ee/workflow/git_annex.html)
- Set up and try [Git LFS](https://docs.gitlab.com/ee/workflow/lfs/manage_large_binaries_with_git_lfs.html)
- Get to know the [GitLab API](https://docs.gitlab.com/ee/api/README.html), its capabilities and shortcomings
- Learn how to [migrate from SVN to Git](https://docs.gitlab.com/ee/workflow/importing/migrating_from_svn.html)
......
......@@ -133,6 +133,9 @@ your Jekyll 3.4.0 site with GitLab Pages. This is the minimum
configuration for our example. On the steps below, we'll refine
the script by adding extra options to our GitLab CI.
Artifacts will be automatically deleted once GitLab Pages got deployed.
You can preserve artifacts for limited time by specifying the expiry time.
### Image
At this point, you probably ask yourself: "okay, but to install Jekyll
......
......@@ -4,13 +4,6 @@ Managing large files such as audio, video and graphics files has always been one
of the shortcomings of Git. The general recommendation is to not have Git repositories
larger than 1GB to preserve performance.
GitLab already supports [managing large files with git annex](http://docs.gitlab.com/ee/workflow/git_annex.html)
(EE only), however in certain environments it is not always convenient to use
different commands to differentiate between the large files and regular ones.
Git LFS makes this simpler for the end user by removing the requirement to
learn new commands.
## How it works
Git LFS client talks with the GitLab server over HTTPS. It uses HTTP Basic Authentication
......
......@@ -592,10 +592,6 @@ module API
end
end
class TriggerRequest < Grape::Entity
expose :id, :variables
end
class Runner < Grape::Entity
expose :id
expose :description
......@@ -643,7 +639,10 @@ module API
end
class Trigger < Grape::Entity
expose :token, :created_at, :updated_at, :deleted_at, :last_used
expose :id
expose :token, :description
expose :created_at, :updated_at, :deleted_at, :last_used
expose :owner, using: Entities::UserBasic
end
class Variable < Grape::Entity
......
......@@ -6,15 +6,15 @@ module API
requires :id, type: String, desc: 'The ID of a project'
end
resource :projects do
desc 'Trigger a GitLab project build' do
success Entities::TriggerRequest
desc 'Trigger a GitLab project pipeline' do
success Entities::Pipeline
end
params do
requires :ref, type: String, desc: 'The commit sha or name of a branch or tag'
requires :token, type: String, desc: 'The unique token of trigger'
optional :variables, type: Hash, desc: 'The list of variables to be injected into build'
end
post ":id/(ref/:ref/)trigger/builds" do
post ":id/(ref/:ref/)trigger/pipeline" do
project = find_project(params[:id])
trigger = Ci::Trigger.find_by_token(params[:token].to_s)
not_found! unless project && trigger
......@@ -29,9 +29,9 @@ module API
# create request and trigger builds
trigger_request = Ci::CreateTriggerRequestService.new.execute(project, trigger, params[:ref].to_s, variables)
if trigger_request
present trigger_request, with: Entities::TriggerRequest
present trigger_request.pipeline, with: Entities::Pipeline
else
errors = 'No builds created'
errors = 'No pipeline created'
render_api_error!(errors, 400)
end
end
......@@ -55,13 +55,13 @@ module API
success Entities::Trigger
end
params do
requires :token, type: String, desc: 'The unique token of trigger'
requires :trigger_id, type: Integer, desc: 'The trigger ID'
end
get ':id/triggers/:token' do
get ':id/triggers/:trigger_id' do
authenticate!
authorize! :admin_build, user_project
trigger = user_project.triggers.find_by(token: params[:token].to_s)
trigger = user_project.triggers.find(params.delete(:trigger_id))
return not_found!('Trigger') unless trigger
present trigger, with: Entities::Trigger
......@@ -70,26 +70,76 @@ module API
desc 'Create a trigger' do
success Entities::Trigger
end
params do
requires :description, type: String, desc: 'The trigger description'
end
post ':id/triggers' do
authenticate!
authorize! :admin_build, user_project
trigger = user_project.triggers.create
trigger = user_project.triggers.create(
declared_params(include_missing: false).merge(owner: current_user))
if trigger.valid?
present trigger, with: Entities::Trigger
else
render_validation_error!(trigger)
end
end
desc 'Update a trigger' do
success Entities::Trigger
end
params do
requires :trigger_id, type: Integer, desc: 'The trigger ID'
optional :description, type: String, desc: 'The trigger description'
end
put ':id/triggers/:trigger_id' do
authenticate!
authorize! :admin_build, user_project
trigger = user_project.triggers.find(params.delete(:trigger_id))
return not_found!('Trigger') unless trigger
if trigger.update(declared_params(include_missing: false))
present trigger, with: Entities::Trigger
else
render_validation_error!(trigger)
end
end
desc 'Take ownership of trigger' do
success Entities::Trigger
end
params do
requires :trigger_id, type: Integer, desc: 'The trigger ID'
end
post ':id/triggers/:trigger_id/take_ownership' do
authenticate!
authorize! :admin_build, user_project
trigger = user_project.triggers.find(params.delete(:trigger_id))
return not_found!('Trigger') unless trigger
if trigger.update(owner: current_user)
status :ok
present trigger, with: Entities::Trigger
else
render_validation_error!(trigger)
end
end
desc 'Delete a trigger' do
success Entities::Trigger
end
params do
requires :token, type: String, desc: 'The unique token of trigger'
requires :trigger_id, type: Integer, desc: 'The trigger ID'
end
delete ':id/triggers/:token' do
delete ':id/triggers/:trigger_id' do
authenticate!
authorize! :admin_build, user_project
trigger = user_project.triggers.find_by(token: params[:token].to_s)
trigger = user_project.triggers.find(params.delete(:trigger_id))
return not_found!('Trigger') unless trigger
trigger.destroy
......
......@@ -186,6 +186,15 @@ module API
class Environment < ::API::Entities::EnvironmentBasic
expose :project, using: Entities::Project
end
class Trigger < Grape::Entity
expose :token, :created_at, :updated_at, :deleted_at, :last_used
expose :owner, using: ::API::Entities::UserBasic
end
class TriggerRequest < Grape::Entity
expose :id, :variables
end
end
end
end
......@@ -7,8 +7,81 @@ module API
requires :id, type: String, desc: 'The ID of a project'
end
resource :projects do
desc 'Trigger a GitLab project build' do
success ::API::V3::Entities::TriggerRequest
end
params do
requires :ref, type: String, desc: 'The commit sha or name of a branch or tag'
requires :token, type: String, desc: 'The unique token of trigger'
optional :variables, type: Hash, desc: 'The list of variables to be injected into build'
end
post ":id/(ref/:ref/)trigger/builds" do
project = find_project(params[:id])
trigger = Ci::Trigger.find_by_token(params[:token].to_s)
not_found! unless project && trigger
unauthorized! unless trigger.project == project
# validate variables
variables = params[:variables].to_h
unless variables.all? { |key, value| key.is_a?(String) && value.is_a?(String) }
render_api_error!('variables needs to be a map of key-valued strings', 400)
end
# create request and trigger builds
trigger_request = Ci::CreateTriggerRequestService.new.execute(project, trigger, params[:ref].to_s, variables)
if trigger_request
present trigger_request, with: ::API::V3::Entities::TriggerRequest
else
errors = 'No builds created'
render_api_error!(errors, 400)
end
end
desc 'Get triggers list' do
success ::API::V3::Entities::Trigger
end
params do
use :pagination
end
get ':id/triggers' do
authenticate!
authorize! :admin_build, user_project
triggers = user_project.triggers.includes(:trigger_requests)
present paginate(triggers), with: ::API::V3::Entities::Trigger
end
desc 'Get specific trigger of a project' do
success ::API::V3::Entities::Trigger
end
params do
requires :token, type: String, desc: 'The unique token of trigger'
end
get ':id/triggers/:token' do
authenticate!
authorize! :admin_build, user_project
trigger = user_project.triggers.find_by(token: params[:token].to_s)
return not_found!('Trigger') unless trigger
present trigger, with: ::API::V3::Entities::Trigger
end
desc 'Create a trigger' do
success ::API::V3::Entities::Trigger
end
post ':id/triggers' do
authenticate!
authorize! :admin_build, user_project
trigger = user_project.triggers.create
present trigger, with: ::API::V3::Entities::Trigger
end
desc 'Delete a trigger' do
success ::API::Entities::Trigger
success ::API::V3::Entities::Trigger
end
params do
requires :token, type: String, desc: 'The unique token of trigger'
......@@ -22,7 +95,7 @@ module API
trigger.destroy
present trigger, with: ::API::Entities::Trigger
present trigger, with: ::API::V3::Entities::Trigger
end
end
end
......
......@@ -180,9 +180,8 @@ module Backup
return unless Dir.exist?(path)
dir_entries = Dir.entries(path)
%w[annex custom_hooks].each do |entry|
yield(entry) if dir_entries.include?(entry)
end
yield('custom_hooks') if dir_entries.include?('custom_hooks')
end
def prepare
......
module Gitlab
module EtagCaching
class Middleware
RESERVED_WORDS = ProjectPathValidator::RESERVED.map { |word| "/#{word}/" }.join('|')
ROUTE_REGEXP = Regexp.union(
%r(^(?!.*(#{RESERVED_WORDS})).*/noteable/issue/\d+/notes\z)
)
def initialize(app)
@app = app
end
def call(env)
return @app.call(env) unless enabled_for_current_route?(env)
Gitlab::Metrics.add_event(:etag_caching_middleware_used)
etag, cached_value_present = get_etag(env)
if_none_match = env['HTTP_IF_NONE_MATCH']
if if_none_match == etag
Gitlab::Metrics.add_event(:etag_caching_cache_hit)
[304, { 'ETag' => etag }, ['']]
else
track_cache_miss(if_none_match, cached_value_present)
status, headers, body = @app.call(env)
headers['ETag'] = etag
[status, headers, body]
end
end
private
def enabled_for_current_route?(env)
ROUTE_REGEXP.match(env['PATH_INFO'])
end
def get_etag(env)
cache_key = env['PATH_INFO']
store = Store.new
current_value = store.get(cache_key)
cached_value_present = current_value.present?
unless cached_value_present
current_value = store.touch(cache_key, only_if_missing: true)
end
[weak_etag_format(current_value), cached_value_present]
end
def weak_etag_format(value)
%Q{W/"#{value}"}
end
def track_cache_miss(if_none_match, cached_value_present)
if if_none_match.blank?
Gitlab::Metrics.add_event(:etag_caching_header_missing)
elsif !cached_value_present
Gitlab::Metrics.add_event(:etag_caching_key_not_found)
else
Gitlab::Metrics.add_event(:etag_caching_resource_changed)
end
end
end
end
end
module Gitlab
module EtagCaching
class Store
EXPIRY_TIME = 10.minutes
REDIS_NAMESPACE = 'etag:'.freeze
def get(key)
Gitlab::Redis.with { |redis| redis.get(redis_key(key)) }
end
def touch(key, only_if_missing: false)
etag = generate_etag
Gitlab::Redis.with do |redis|
redis.set(redis_key(key), etag, ex: EXPIRY_TIME, nx: only_if_missing)
end
etag
end
private
def generate_etag
SecureRandom.hex
end
def redis_key(key)
"#{REDIS_NAMESPACE}#{key}"
end
end
end
end
module Mattermost
class Team < Client
def all
session_get('/api/v3/teams/all')
session_get('/api/v3/teams/all').values
end
end
end
......@@ -3,16 +3,6 @@ require 'spec_helper'
describe Profiles::KeysController do
let(:user) { create(:user) }
describe '#new' do
before { sign_in(user) }
it 'redirects to #index' do
get :new
expect(response).to redirect_to(profile_keys_path)
end
end
describe "#get_keys" do
describe "non existant user" do
it "does not generally work" do
......
......@@ -244,4 +244,27 @@ describe Projects::BranchesController do
end
end
end
describe "GET index" do
render_views
before do
sign_in(user)
end
context 'when rendering a JSON format' do
it 'filters branches by name' do
get :index,
namespace_id: project.namespace,
project_id: project,
format: :json,
search: 'master'
parsed_response = JSON.parse(response.body)
expect(parsed_response.length).to eq 1
expect(parsed_response.first).to eq 'master'
end
end
end
end
......@@ -30,18 +30,18 @@ describe Projects::GraphsController do
double(languages: {
'Ruby' => 1000,
'CoffeeScript' => 350,
'PowerShell' => 15
'NSIS' => 15
})
end
let(:expected_values) do
ps_color = "##{Digest::SHA256.hexdigest('PowerShell')[0...6]}"
nsis_color = "##{Digest::SHA256.hexdigest('NSIS')[0...6]}"
[
# colors from Linguist:
{ label: "Ruby", color: "#701516", highlight: "#701516" },
{ label: "CoffeeScript", color: "#244776", highlight: "#244776" },
# colors from SHA256 fallback:
{ label: "PowerShell", color: ps_color, highlight: ps_color }
{ label: "NSIS", color: nsis_color, highlight: nsis_color }
]
end
......
......@@ -200,4 +200,31 @@ describe Projects::NotesController do
end
end
end
describe 'GET index' do
let(:last_fetched_at) { '1487756246' }
let(:request_params) do
{
namespace_id: project.namespace,
project_id: project,
target_type: 'issue',
target_id: issue.id
}
end
before do
sign_in(user)
project.team << [user, :developer]
end
it 'passes last_fetched_at from headers to NotesFinder' do
request.headers['X-Last-Fetched-At'] = last_fetched_at
expect(NotesFinder).to receive(:new)
.with(anything, anything, hash_including(last_fetched_at: last_fetched_at))
.and_call_original
get :index, request_params
end
end
end
......@@ -61,4 +61,18 @@ describe 'Profile account page', feature: true do
expect(find('#incoming-email-token').value).not_to eq(previous_token)
end
end
describe 'when I change my username' do
before do
visit profile_account_path
end
it 'changes my username' do
fill_in 'user_username', with: 'new-username'
click_button('Update username')
expect(page).to have_content('new-username')
end
end
end
require 'spec_helper'
feature 'Setup Mattermost slash commands', feature: true do
feature 'Setup Mattermost slash commands', :feature, :js do
let(:user) { create(:user) }
let(:project) { create(:empty_project) }
let(:service) { project.create_mattermost_slash_commands_service }
......@@ -62,11 +62,11 @@ feature 'Setup Mattermost slash commands', feature: true do
click_link 'Add to Mattermost'
team_name = teams.first[1]['display_name']
select_element = find('select#mattermost_team_id')
team_name = teams.first['display_name']
select_element = find('#mattermost_team_id')
selected_option = select_element.find('option[selected]')
expect(select_element['disabled']).to eq('disabled')
expect(select_element['disabled']).to be(true)
expect(selected_option).to have_content(team_name.to_s)
end
......@@ -75,7 +75,7 @@ feature 'Setup Mattermost slash commands', feature: true do
click_link 'Add to Mattermost'
expect(find('input#mattermost_team_id', visible: false).value).to eq(teams.first[0].to_s)
expect(find('input#mattermost_team_id', visible: false).value).to eq(teams.first['id'])
end
it 'shows an explanation user is a member of multiple teams' do
......@@ -92,12 +92,9 @@ feature 'Setup Mattermost slash commands', feature: true do
click_link 'Add to Mattermost'
select_element = find('select#mattermost_team_id')
selected_option = select_element.find('option[selected]')
select_element = find('#mattermost_team_id')
expect(select_element['disabled']).to be(nil)
expect(selected_option).to have_content('Select team...')
# The 'Select team...' placeholder is item `0`.
expect(select_element['disabled']).to be(false)
expect(select_element.all('option').count).to eq(3)
end
......@@ -110,20 +107,37 @@ feature 'Setup Mattermost slash commands', feature: true do
expect(page).to have_content('test mattermost error message')
end
it 'enables the submit button if the required fields are provided', :js do
stub_teams(count: 1)
click_link 'Add to Mattermost'
expect(find('input[type="submit"]')['disabled']).not_to be(true)
end
it 'disables the submit button if the required fields are not provided', :js do
stub_teams(count: 1)
click_link 'Add to Mattermost'
fill_in('mattermost_trigger', with: '')
expect(find('input[type="submit"]')['disabled']).to be(true)
end
def stub_teams(count: 0)
teams = create_teams(count)
allow_any_instance_of(MattermostSlashCommandsService).to receive(:list_teams) { teams }
allow_any_instance_of(MattermostSlashCommandsService).to receive(:list_teams) { [teams, nil] }
teams
end
def create_teams(count = 0)
teams = {}
teams = []
count.times do |i|
i += 1
teams[i] = { id: i, display_name: i }
teams.push({ "id" => "x#{i}", "display_name" => "x#{i}-name" })
end
teams
......
require 'spec_helper'
require_relative '../../config/initializers/metrics'
require_relative '../../config/initializers/8_metrics'
describe 'instrument_classes', lib: true do
let(:config) { double(:config) }
......
require 'spec_helper'
describe Gitlab::EtagCaching::Middleware do
let(:app) { double(:app) }
let(:middleware) { described_class.new(app) }
let(:app_status_code) { 200 }
let(:if_none_match) { nil }
let(:enabled_path) { '/gitlab-org/gitlab-ce/noteable/issue/1/notes' }
context 'when ETag caching is not enabled for current route' do
let(:path) { '/gitlab-org/gitlab-ce/tree/master/noteable/issue/1/notes' }
before do
mock_app_response
end
it 'does not add ETag header' do
_, headers, _ = middleware.call(build_env(path, if_none_match))
expect(headers['ETag']).to be_nil
end
it 'passes status code from app' do
status, _, _ = middleware.call(build_env(path, if_none_match))
expect(status).to eq app_status_code
end
end
context 'when there is no ETag in store for given resource' do
let(:path) { enabled_path }
before do
mock_app_response
mock_value_in_store(nil)
end
it 'generates ETag' do
expect_any_instance_of(Gitlab::EtagCaching::Store)
.to receive(:touch).and_return('123')
middleware.call(build_env(path, if_none_match))
end
context 'when If-None-Match header was specified' do
let(:if_none_match) { 'W/"abc"' }
it 'tracks "etag_caching_key_not_found" event' do
expect(Gitlab::Metrics).to receive(:add_event)
.with(:etag_caching_middleware_used)
expect(Gitlab::Metrics).to receive(:add_event)
.with(:etag_caching_key_not_found)
middleware.call(build_env(path, if_none_match))
end
end
end
context 'when there is ETag in store for given resource' do
let(:path) { enabled_path }
before do
mock_app_response
mock_value_in_store('123')
end
it 'returns this value as header' do
_, headers, _ = middleware.call(build_env(path, if_none_match))
expect(headers['ETag']).to eq 'W/"123"'
end
end
context 'when If-None-Match header matches ETag in store' do
let(:path) { enabled_path }
let(:if_none_match) { 'W/"123"' }
before do
mock_value_in_store('123')
end
it 'does not call app' do
expect(app).not_to receive(:call)
middleware.call(build_env(path, if_none_match))
end
it 'returns status code 304' do
status, _, _ = middleware.call(build_env(path, if_none_match))
expect(status).to eq 304
end
it 'tracks "etag_caching_cache_hit" event' do
expect(Gitlab::Metrics).to receive(:add_event)
.with(:etag_caching_middleware_used)
expect(Gitlab::Metrics).to receive(:add_event)
.with(:etag_caching_cache_hit)
middleware.call(build_env(path, if_none_match))
end
end
context 'when If-None-Match header does not match ETag in store' do
let(:path) { enabled_path }
let(:if_none_match) { 'W/"abc"' }
before do
mock_value_in_store('123')
end
it 'calls app' do
expect(app).to receive(:call).and_return([app_status_code, {}, ['body']])
middleware.call(build_env(path, if_none_match))
end
it 'tracks "etag_caching_resource_changed" event' do
mock_app_response
expect(Gitlab::Metrics).to receive(:add_event)
.with(:etag_caching_middleware_used)
expect(Gitlab::Metrics).to receive(:add_event)
.with(:etag_caching_resource_changed)
middleware.call(build_env(path, if_none_match))
end
end
context 'when If-None-Match header is not specified' do
let(:path) { enabled_path }
before do
mock_value_in_store('123')
mock_app_response
end
it 'tracks "etag_caching_header_missing" event' do
expect(Gitlab::Metrics).to receive(:add_event)
.with(:etag_caching_middleware_used)
expect(Gitlab::Metrics).to receive(:add_event)
.with(:etag_caching_header_missing)
middleware.call(build_env(path, if_none_match))
end
end
def mock_app_response
allow(app).to receive(:call).and_return([app_status_code, {}, ['body']])
end
def mock_value_in_store(value)
allow_any_instance_of(Gitlab::EtagCaching::Store)
.to receive(:get).and_return(value)
end
def build_env(path, if_none_match)
{
'PATH_INFO' => path,
'HTTP_IF_NONE_MATCH' => if_none_match
}
end
end
......@@ -97,6 +97,7 @@ variables:
triggers:
- project
- trigger_requests
- owner
deploy_keys:
- user
- deploy_keys_projects
......
......@@ -240,6 +240,8 @@ Ci::Trigger:
- created_at
- updated_at
- gl_project_id
- owner_id
- description
DeployKey:
- id
- user_id
......
......@@ -13,7 +13,7 @@ describe Mattermost::Team do
context 'for valid request' do
let(:response) do
[{
{ "xiyro8huptfhdndadpz8r3wnbo" => {
"id" => "xiyro8huptfhdndadpz8r3wnbo",
"create_at" => 1482174222155,
"update_at" => 1482174222155,
......@@ -26,7 +26,7 @@ describe Mattermost::Team do
"allowed_domains" => "",
"invite_id" => "o4utakb9jtb7imctdfzbf9r5ro",
"allow_open_invite" => false
}]
} }
end
before do
......@@ -39,7 +39,7 @@ describe Mattermost::Team do
end
it 'returns a token' do
is_expected.to eq(response)
is_expected.to eq(response.values)
end
end
......
require 'spec_helper'
describe Ci::Trigger, models: true do
let(:project) { FactoryGirl.create :empty_project }
let(:project) { create :empty_project }
describe 'associations' do
it { is_expected.to belong_to(:project) }
it { is_expected.to belong_to(:owner) }
it { is_expected.to have_many(:trigger_requests) }
end
describe 'before_validation' do
it 'sets an random token if none provided' do
trigger = FactoryGirl.create :ci_trigger_without_token, project: project
trigger = create(:ci_trigger_without_token, project: project)
expect(trigger.token).not_to be_nil
end
it 'does not set an random token if one provided' do
trigger = FactoryGirl.create :ci_trigger, project: project
trigger = create(:ci_trigger, project: project)
expect(trigger.token).to eq('token')
end
end
......
......@@ -387,4 +387,16 @@ describe Note, models: true do
end
end
end
describe 'expiring ETag cache' do
let(:note) { build(:note_on_issue) }
it "expires cache for note's issue when note is saved" do
expect_any_instance_of(Gitlab::EtagCaching::Store)
.to receive(:touch)
.with("/#{note.project.namespace.to_param}/#{note.project.to_param}/noteable/issue/#{note.noteable.id}/notes")
note.save!
end
end
end
......@@ -92,7 +92,7 @@ describe MattermostSlashCommandsService, :models do
to_return(
status: 200,
headers: { 'Content-Type' => 'application/json' },
body: ['list'].to_json
body: { 'list' => true }.to_json
)
end
......
......@@ -32,6 +32,7 @@ describe User, models: true do
it { is_expected.to have_many(:spam_logs).dependent(:destroy) }
it { is_expected.to have_many(:todos).dependent(:destroy) }
it { is_expected.to have_many(:award_emoji).dependent(:destroy) }
it { is_expected.to have_many(:triggers).dependent(:destroy) }
it { is_expected.to have_many(:builds).dependent(:nullify) }
it { is_expected.to have_many(:pipelines).dependent(:nullify) }
it { is_expected.to have_many(:chat_names).dependent(:destroy) }
......
......@@ -14,7 +14,7 @@ describe API::Triggers do
let!(:trigger2) { create(:ci_trigger, project: project, token: trigger_token_2) }
let!(:trigger_request) { create(:ci_trigger_request, trigger: trigger, created_at: '2015-01-01 12:13:14') }
describe 'POST /projects/:project_id/trigger' do
describe 'POST /projects/:project_id/trigger/pipeline' do
let!(:project2) { create(:project) }
let(:options) do
{
......@@ -28,17 +28,20 @@ describe API::Triggers do
context 'Handles errors' do
it 'returns bad request if token is missing' do
post api("/projects/#{project.id}/trigger/builds"), ref: 'master'
post api("/projects/#{project.id}/trigger/pipeline"), ref: 'master'
expect(response).to have_http_status(400)
end
it 'returns not found if project is not found' do
post api('/projects/0/trigger/builds'), options.merge(ref: 'master')
post api('/projects/0/trigger/pipeline'), options.merge(ref: 'master')
expect(response).to have_http_status(404)
end
it 'returns unauthorized if token is for different project' do
post api("/projects/#{project2.id}/trigger/builds"), options.merge(ref: 'master')
post api("/projects/#{project2.id}/trigger/pipeline"), options.merge(ref: 'master')
expect(response).to have_http_status(401)
end
end
......@@ -46,9 +49,11 @@ describe API::Triggers do
context 'Have a commit' do
let(:pipeline) { project.pipelines.last }
it 'creates builds' do
post api("/projects/#{project.id}/trigger/builds"), options.merge(ref: 'master')
it 'creates pipeline' do
post api("/projects/#{project.id}/trigger/pipeline"), options.merge(ref: 'master')
expect(response).to have_http_status(201)
expect(json_response).to include('id' => pipeline.id)
pipeline.builds.reload
expect(pipeline.builds.pending.size).to eq(2)
expect(pipeline.builds.size).to eq(5)
......@@ -56,15 +61,17 @@ describe API::Triggers do
it 'creates builds on webhook from other gitlab repository and branch' do
expect do
post api("/projects/#{project.id}/ref/master/trigger/builds?token=#{trigger_token}"), { ref: 'refs/heads/other-branch' }
post api("/projects/#{project.id}/ref/master/trigger/pipeline?token=#{trigger_token}"), { ref: 'refs/heads/other-branch' }
end.to change(project.builds, :count).by(5)
expect(response).to have_http_status(201)
end
it 'returns bad request with no builds created if there\'s no commit for that ref' do
post api("/projects/#{project.id}/trigger/builds"), options.merge(ref: 'other-branch')
it 'returns bad request with no pipeline created if there\'s no commit for that ref' do
post api("/projects/#{project.id}/trigger/pipeline"), options.merge(ref: 'other-branch')
expect(response).to have_http_status(400)
expect(json_response['message']).to eq('No builds created')
expect(json_response['message']).to eq('No pipeline created')
end
context 'Validates variables' do
......@@ -73,22 +80,24 @@ describe API::Triggers do
end
it 'validates variables to be a hash' do
post api("/projects/#{project.id}/trigger/builds"), options.merge(variables: 'value', ref: 'master')
post api("/projects/#{project.id}/trigger/pipeline"), options.merge(variables: 'value', ref: 'master')
expect(response).to have_http_status(400)
expect(json_response['error']).to eq('variables is invalid')
end
it 'validates variables needs to be a map of key-valued strings' do
post api("/projects/#{project.id}/trigger/builds"), options.merge(variables: { key: %w(1 2) }, ref: 'master')
post api("/projects/#{project.id}/trigger/pipeline"), options.merge(variables: { key: %w(1 2) }, ref: 'master')
expect(response).to have_http_status(400)
expect(json_response['message']).to eq('variables needs to be a map of key-valued strings')
end
it 'creates trigger request with variables' do
post api("/projects/#{project.id}/trigger/builds"), options.merge(variables: variables, ref: 'master')
post api("/projects/#{project.id}/trigger/pipeline"), options.merge(variables: variables, ref: 'master')
expect(response).to have_http_status(201)
pipeline.builds.reload
expect(pipeline.builds.first.trigger_request.variables).to eq(variables)
expect(pipeline.builds.reload.first.trigger_request.variables).to eq(variables)
end
end
end
......@@ -123,17 +132,17 @@ describe API::Triggers do
end
end
describe 'GET /projects/:id/triggers/:token' do
describe 'GET /projects/:id/triggers/:trigger_id' do
context 'authenticated user with valid permissions' do
it 'returns trigger details' do
get api("/projects/#{project.id}/triggers/#{trigger.token}", user)
get api("/projects/#{project.id}/triggers/#{trigger.id}", user)
expect(response).to have_http_status(200)
expect(json_response).to be_a(Hash)
end
it 'responds with 404 Not Found if requesting non-existing trigger' do
get api("/projects/#{project.id}/triggers/abcdef012345", user)
get api("/projects/#{project.id}/triggers/-5", user)
expect(response).to have_http_status(404)
end
......@@ -141,7 +150,7 @@ describe API::Triggers do
context 'authenticated user with invalid permissions' do
it 'does not return triggers list' do
get api("/projects/#{project.id}/triggers/#{trigger.token}", user2)
get api("/projects/#{project.id}/triggers/#{trigger.id}", user2)
expect(response).to have_http_status(403)
end
......@@ -149,7 +158,7 @@ describe API::Triggers do
context 'unauthenticated user' do
it 'does not return triggers list' do
get api("/projects/#{project.id}/triggers/#{trigger.token}")
get api("/projects/#{project.id}/triggers/#{trigger.id}")
expect(response).to have_http_status(401)
end
......@@ -158,19 +167,31 @@ describe API::Triggers do
describe 'POST /projects/:id/triggers' do
context 'authenticated user with valid permissions' do
context 'with required parameters' do
it 'creates trigger' do
expect do
post api("/projects/#{project.id}/triggers", user)
post api("/projects/#{project.id}/triggers", user),
description: 'trigger'
end.to change{project.triggers.count}.by(1)
expect(response).to have_http_status(201)
expect(json_response).to be_a(Hash)
expect(json_response).to include('description' => 'trigger')
end
end
context 'without required parameters' do
it 'does not create trigger' do
post api("/projects/#{project.id}/triggers", user)
expect(response).to have_http_status(:bad_request)
end
end
end
context 'authenticated user with invalid permissions' do
it 'does not create trigger' do
post api("/projects/#{project.id}/triggers", user2)
post api("/projects/#{project.id}/triggers", user2),
description: 'trigger'
expect(response).to have_http_status(403)
end
......@@ -178,25 +199,87 @@ describe API::Triggers do
context 'unauthenticated user' do
it 'does not create trigger' do
post api("/projects/#{project.id}/triggers")
post api("/projects/#{project.id}/triggers"),
description: 'trigger'
expect(response).to have_http_status(401)
end
end
end
describe 'PUT /projects/:id/triggers/:trigger_id' do
context 'authenticated user with valid permissions' do
let(:new_description) { 'new description' }
it 'updates description' do
put api("/projects/#{project.id}/triggers/#{trigger.id}", user),
description: new_description
expect(response).to have_http_status(200)
expect(json_response).to include('description' => new_description)
expect(trigger.reload.description).to eq(new_description)
end
end
context 'authenticated user with invalid permissions' do
it 'does not update trigger' do
put api("/projects/#{project.id}/triggers/#{trigger.id}", user2)
expect(response).to have_http_status(403)
end
end
context 'unauthenticated user' do
it 'does not update trigger' do
put api("/projects/#{project.id}/triggers/#{trigger.id}")
expect(response).to have_http_status(401)
end
end
end
describe 'POST /projects/:id/triggers/:trigger_id/take_ownership' do
context 'authenticated user with valid permissions' do
it 'updates owner' do
expect(trigger.owner).to be_nil
post api("/projects/#{project.id}/triggers/#{trigger.id}/take_ownership", user)
expect(response).to have_http_status(200)
expect(json_response).to include('owner')
expect(trigger.reload.owner).to eq(user)
end
end
context 'authenticated user with invalid permissions' do
it 'does not update owner' do
post api("/projects/#{project.id}/triggers/#{trigger.id}/take_ownership", user2)
expect(response).to have_http_status(403)
end
end
context 'unauthenticated user' do
it 'does not update owner' do
post api("/projects/#{project.id}/triggers/#{trigger.id}/take_ownership")
expect(response).to have_http_status(401)
end
end
end
describe 'DELETE /projects/:id/triggers/:token' do
describe 'DELETE /projects/:id/triggers/:trigger_id' do
context 'authenticated user with valid permissions' do
it 'deletes trigger' do
expect do
delete api("/projects/#{project.id}/triggers/#{trigger.token}", user)
delete api("/projects/#{project.id}/triggers/#{trigger.id}", user)
expect(response).to have_http_status(204)
end.to change{project.triggers.count}.by(-1)
end
it 'responds with 404 Not Found if requesting non-existing trigger' do
delete api("/projects/#{project.id}/triggers/abcdef012345", user)
delete api("/projects/#{project.id}/triggers/-5", user)
expect(response).to have_http_status(404)
end
......@@ -204,7 +287,7 @@ describe API::Triggers do
context 'authenticated user with invalid permissions' do
it 'does not delete trigger' do
delete api("/projects/#{project.id}/triggers/#{trigger.token}", user2)
delete api("/projects/#{project.id}/triggers/#{trigger.id}", user2)
expect(response).to have_http_status(403)
end
......@@ -212,7 +295,7 @@ describe API::Triggers do
context 'unauthenticated user' do
it 'does not delete trigger' do
delete api("/projects/#{project.id}/triggers/#{trigger.token}")
delete api("/projects/#{project.id}/triggers/#{trigger.id}")
expect(response).to have_http_status(401)
end
......
......@@ -11,6 +11,177 @@ describe API::V3::Triggers do
let!(:developer) { create(:project_member, :developer, user: user2, project: project) }
let!(:trigger) { create(:ci_trigger, project: project, token: trigger_token) }
describe 'POST /projects/:project_id/trigger' do
let!(:project2) { create(:project) }
let(:options) do
{
token: trigger_token
}
end
before do
stub_ci_pipeline_to_return_yaml_file
end
context 'Handles errors' do
it 'returns bad request if token is missing' do
post v3_api("/projects/#{project.id}/trigger/builds"), ref: 'master'
expect(response).to have_http_status(400)
end
it 'returns not found if project is not found' do
post v3_api('/projects/0/trigger/builds'), options.merge(ref: 'master')
expect(response).to have_http_status(404)
end
it 'returns unauthorized if token is for different project' do
post v3_api("/projects/#{project2.id}/trigger/builds"), options.merge(ref: 'master')
expect(response).to have_http_status(401)
end
end
context 'Have a commit' do
let(:pipeline) { project.pipelines.last }
it 'creates builds' do
post v3_api("/projects/#{project.id}/trigger/builds"), options.merge(ref: 'master')
expect(response).to have_http_status(201)
pipeline.builds.reload
expect(pipeline.builds.pending.size).to eq(2)
expect(pipeline.builds.size).to eq(5)
end
it 'creates builds on webhook from other gitlab repository and branch' do
expect do
post v3_api("/projects/#{project.id}/ref/master/trigger/builds?token=#{trigger_token}"), { ref: 'refs/heads/other-branch' }
end.to change(project.builds, :count).by(5)
expect(response).to have_http_status(201)
end
it 'returns bad request with no builds created if there\'s no commit for that ref' do
post v3_api("/projects/#{project.id}/trigger/builds"), options.merge(ref: 'other-branch')
expect(response).to have_http_status(400)
expect(json_response['message']).to eq('No builds created')
end
context 'Validates variables' do
let(:variables) do
{ 'TRIGGER_KEY' => 'TRIGGER_VALUE' }
end
it 'validates variables to be a hash' do
post v3_api("/projects/#{project.id}/trigger/builds"), options.merge(variables: 'value', ref: 'master')
expect(response).to have_http_status(400)
expect(json_response['error']).to eq('variables is invalid')
end
it 'validates variables needs to be a map of key-valued strings' do
post v3_api("/projects/#{project.id}/trigger/builds"), options.merge(variables: { key: %w(1 2) }, ref: 'master')
expect(response).to have_http_status(400)
expect(json_response['message']).to eq('variables needs to be a map of key-valued strings')
end
it 'creates trigger request with variables' do
post v3_api("/projects/#{project.id}/trigger/builds"), options.merge(variables: variables, ref: 'master')
expect(response).to have_http_status(201)
pipeline.builds.reload
expect(pipeline.builds.first.trigger_request.variables).to eq(variables)
end
end
end
end
describe 'GET /projects/:id/triggers' do
context 'authenticated user with valid permissions' do
it 'returns list of triggers' do
get v3_api("/projects/#{project.id}/triggers", user)
expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_a(Array)
expect(json_response[0]).to have_key('token')
end
end
context 'authenticated user with invalid permissions' do
it 'does not return triggers list' do
get v3_api("/projects/#{project.id}/triggers", user2)
expect(response).to have_http_status(403)
end
end
context 'unauthenticated user' do
it 'does not return triggers list' do
get v3_api("/projects/#{project.id}/triggers")
expect(response).to have_http_status(401)
end
end
end
describe 'GET /projects/:id/triggers/:token' do
context 'authenticated user with valid permissions' do
it 'returns trigger details' do
get v3_api("/projects/#{project.id}/triggers/#{trigger.token}", user)
expect(response).to have_http_status(200)
expect(json_response).to be_a(Hash)
end
it 'responds with 404 Not Found if requesting non-existing trigger' do
get v3_api("/projects/#{project.id}/triggers/abcdef012345", user)
expect(response).to have_http_status(404)
end
end
context 'authenticated user with invalid permissions' do
it 'does not return triggers list' do
get v3_api("/projects/#{project.id}/triggers/#{trigger.token}", user2)
expect(response).to have_http_status(403)
end
end
context 'unauthenticated user' do
it 'does not return triggers list' do
get v3_api("/projects/#{project.id}/triggers/#{trigger.token}")
expect(response).to have_http_status(401)
end
end
end
describe 'POST /projects/:id/triggers' do
context 'authenticated user with valid permissions' do
it 'creates trigger' do
expect do
post v3_api("/projects/#{project.id}/triggers", user)
end.to change{project.triggers.count}.by(1)
expect(response).to have_http_status(201)
expect(json_response).to be_a(Hash)
end
end
context 'authenticated user with invalid permissions' do
it 'does not create trigger' do
post v3_api("/projects/#{project.id}/triggers", user2)
expect(response).to have_http_status(403)
end
end
context 'unauthenticated user' do
it 'does not create trigger' do
post v3_api("/projects/#{project.id}/triggers")
expect(response).to have_http_status(401)
end
end
end
describe 'DELETE /projects/:id/triggers/:token' do
context 'authenticated user with valid permissions' do
it 'deletes trigger' do
......
......@@ -431,12 +431,22 @@ describe 'project routing' do
end
end
# project_notes GET /:project_id/notes(.:format) notes#index
# project_noteable_notes GET /:project_id/noteable/:target_type/:target_id/notes notes#index
# POST /:project_id/notes(.:format) notes#create
# project_note DELETE /:project_id/notes/:id(.:format) notes#destroy
describe Projects::NotesController, 'routing' do
it 'to #index' do
expect(get('/gitlab/gitlabhq/noteable/issue/1/notes')).to route_to(
'projects/notes#index',
namespace_id: 'gitlab',
project_id: 'gitlabhq',
target_type: 'issue',
target_id: '1'
)
end
it_behaves_like 'RESTful project resources' do
let(:actions) { [:index, :create, :destroy] }
let(:actions) { [:create, :destroy] }
let(:controller) { 'notes' }
end
end
......
......@@ -13,10 +13,24 @@ describe Ci::CreateTriggerRequestService, services: true do
context 'valid params' do
subject { service.execute(project, trigger, 'master') }
context 'without owner' do
it { expect(subject).to be_kind_of(Ci::TriggerRequest) }
it { expect(subject.pipeline).to be_kind_of(Ci::Pipeline) }
it { expect(subject.builds.first).to be_kind_of(Ci::Build) }
end
context 'with owner' do
let(:owner) { create(:user) }
let(:trigger) { create(:ci_trigger, project: project, owner: owner) }
it { expect(subject).to be_kind_of(Ci::TriggerRequest) }
it { expect(subject.pipeline).to be_kind_of(Ci::Pipeline) }
it { expect(subject.pipeline.user).to eq(owner) }
it { expect(subject.builds.first).to be_kind_of(Ci::Build) }
it { expect(subject.builds.first.user).to eq(owner) }
end
end
context 'no commit for ref' do
subject { service.execute(project, trigger, 'other-branch') }
......
......@@ -26,6 +26,28 @@ describe Projects::UpdatePagesService do
build.update_attributes(artifacts_metadata: metadata)
end
describe 'pages artifacts' do
context 'with expiry date' do
before do
build.artifacts_expire_in = "2 days"
end
it "doesn't delete artifacts" do
expect(execute).to eq(:success)
expect(build.reload.artifacts_file?).to eq(true)
end
end
context 'without expiry date' do
it "does delete artifacts" do
expect(execute).to eq(:success)
expect(build.reload.artifacts_file?).to eq(false)
end
end
end
it 'succeeds' do
expect(project.pages_deployed?).to be_falsey
expect(execute).to eq(:success)
......
......@@ -108,7 +108,7 @@ describe 'gitlab:app namespace rake task' do
$stdout = orig_stdout
end
describe 'backup creation and deletion using annex and custom_hooks' do
describe 'backup creation and deletion using custom_hooks' do
let(:project) { create(:project) }
let(:user_backup_path) { "repositories/#{project.path_with_namespace}" }
......@@ -132,25 +132,6 @@ describe 'gitlab:app namespace rake task' do
Dir.chdir(@origin_cd)
end
context 'project uses git-annex and successfully creates backup' do
let(:filename) { "annex" }
it 'creates annex.tar and project bundle' do
tar_contents, exit_status = Gitlab::Popen.popen(%W{tar -tvf #{@backup_tar}})
expect(exit_status).to eq(0)
expect(tar_contents).to match(user_backup_path)
expect(tar_contents).to match("#{user_backup_path}/annex.tar")
expect(tar_contents).to match("#{user_backup_path}.bundle")
end
it 'restores files correctly' do
restore_backup
expect(Dir.entries(File.join(project.repository.path, "annex"))).to include("dummy.txt")
end
end
context 'project uses custom_hooks and successfully creates backup' do
let(:filename) { "custom_hooks" }
......
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