Commit 83b643a0 authored by Kamil Trzcinski's avatar Kamil Trzcinski

Merge remote-tracking branch 'origin/lfs-support-for-ssh' into per-build-token

# Conflicts:
#	app/controllers/projects/git_http_client_controller.rb
#	app/helpers/lfs_helper.rb
#	lib/gitlab/auth.rb
#	spec/requests/lfs_http_spec.rb
parents eed5c58d be09bcf0
......@@ -37,6 +37,7 @@ v 8.12.0 (unreleased)
- Request only the LDAP attributes we need !6187
- Center build stage columns in pipeline overview (ClemMakesApps)
- Rename behaviour to behavior in bug issue template for consistency (ClemMakesApps)
- Fix bug stopping issue description being scrollable after selecting issue template
- Remove suggested colors hover underline (ClemMakesApps)
- Shorten task status phrase (ClemMakesApps)
- Fix project visibility level fields on settings
......@@ -92,6 +93,7 @@ v 8.12.0 (unreleased)
- Remove green outline from `New branch unavailable` button on issue page !5858 (winniehell)
- Fix repo title alignment (ClemMakesApps)
- Change update interval of contacted_at
- Add LFS support to SSH !6043
- Fix branch title trailing space on hover (ClemMakesApps)
- Don't include 'Created By' tag line when importing from GitHub if there is a linked GitLab account (EspadaV8)
- Award emoji tooltips containing more than 10 usernames are now truncated !4780 (jlogandavison)
......
......@@ -13,6 +13,9 @@
this.buildDropdown();
this.bindEvents();
this.onFilenameUpdate();
this.autosizeUpdateEvent = document.createEvent('Event');
this.autosizeUpdateEvent.initEvent('autosize:update', true, false);
}
TemplateSelector.prototype.buildDropdown = function() {
......@@ -72,6 +75,10 @@
TemplateSelector.prototype.requestFileSuccess = function(file, skipFocus) {
this.editor.setValue(file.content, 1);
if (!skipFocus) this.editor.focus();
if (this.editor instanceof jQuery) {
this.editor.get(0).dispatchEvent(this.autosizeUpdateEvent);
}
};
TemplateSelector.prototype.startLoadingSpinner = function() {
......
......@@ -318,9 +318,17 @@
.build-content {
width: 130px;
.ci-status-text {
width: 110px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
vertical-align: middle;
display: inline-block;
position: relative;
top: -1px;
}
a {
color: $layout-link-gray;
......@@ -331,13 +339,74 @@
text-decoration: underline;
}
}
}
.dropdown-menu-toggle {
border: none;
width: auto;
padding: 0;
color: $layout-link-gray;
.ci-status-text {
width: 80px;
}
}
.grouped-pipeline-dropdown {
padding: 8px 0;
width: 200px;
left: auto;
right: -214px;
top: -9px;
a:hover {
.ci-status-text {
text-decoration: none;
}
}
.ci-status-text {
width: 145px;
}
.arrow {
&:before,
&:after {
content: '';
display: inline-block;
position: absolute;
width: 0;
height: 0;
border-color: transparent;
border-style: solid;
top: 18px;
}
&:before {
left: -5px;
margin-top: -6px;
border-width: 7px 5px 7px 0;
border-right-color: $border-color;
}
&:after {
left: -4px;
margin-top: -9px;
border-width: 10px 7px 10px 0;
border-right-color: $white-light;
}
}
}
.badge {
background-color: $gray-dark;
color: $layout-link-gray;
font-weight: normal;
}
}
svg {
position: relative;
top: 2px;
vertical-align: middle;
margin-right: 5px;
}
......@@ -442,7 +511,7 @@
width: 21px;
height: 25px;
position: absolute;
top: -28.5px;
top: -29px;
border-top: 2px solid $border-color;
}
......
......@@ -334,6 +334,10 @@ a.deploy-project-label {
a {
color: $gl-dark-link-color;
}
.dropdown-menu {
width: 240px;
}
}
.last-push-widget {
......
......@@ -4,7 +4,7 @@ class Projects::GitHttpClientController < Projects::ApplicationController
include ActionController::HttpAuthentication::Basic
include KerberosSpnegoHelper
attr_reader :user, :capabilities
attr_reader :actor, :capabilities
# Git clients will not know what authenticity token to send along
skip_before_action :verify_authenticity_token
......@@ -21,31 +21,14 @@ class Projects::GitHttpClientController < Projects::ApplicationController
if allow_basic_auth? && basic_auth_provided?
login, password = user_name_and_password(request)
auth_result = Gitlab::Auth.find_for_git_client(login, password, project: project, ip: request.ip)
if auth_result.type == :ci && !download_request?
# Not allowed
auth_result = Gitlab::Auth::Result.new
elsif auth_result.type == :oauth && !download_request?
# Not allowed
auth_result = Gitlab::Auth::Result.new
elsif auth_result.type == :missing_personal_token
render_missing_personal_token
return # Render above denied access, nothing left to do
else
@user = auth_result.user
end
@capabilities = auth_result.capabilities || []
@ci = auth_result.type == :ci
if auth_result.succeeded?
if handle_basic_authentication(login, password)
return # Allow access
end
elsif allow_kerberos_spnego_auth? && spnego_provided?
@user = find_kerberos_user
@actor = find_kerberos_user
if user
if actor
send_final_spnego_response
return # Allow access
end
......@@ -53,6 +36,8 @@ class Projects::GitHttpClientController < Projects::ApplicationController
send_challenges
render plain: "HTTP Basic: Access denied\n", status: 401
rescue Gitlab::Auth::MissingPersonalTokenError
render_missing_personal_token
end
def basic_auth_provided?
......@@ -120,7 +105,49 @@ class Projects::GitHttpClientController < Projects::ApplicationController
end
def ci?
@ci.present?
@ci
end
def user
@actor
end
def handle_basic_authentication(login, password)
auth_result = Gitlab::Auth.find_for_git_client(login, password, project: project, ip: request.ip)
case auth_result.type
when :ci
if download_request?
@ci = true
else
return false
end
when :oauth
if download_request?
@actor = auth_result.actor
@capabilities = auth_result.capabilities
else
return false
end
when :lfs_deploy_token
if download_request?
@lfs_deploy_key = true
@actor = auth_result.actor
@capabilities = auth_result.capabilities
end
when :lfs_token, :personal_token, :gitlab_or_ldap, :build
@actor = auth_result.actor
@capabilities = auth_result.capabilities
else
# Not allowed
return false
end
true
end
def lfs_deploy_key?
@lfs_deploy_key && actor && actor.projects.include?(project)
end
def has_capability?(capability)
......
......@@ -25,7 +25,7 @@ module LfsHelper
def lfs_download_access?
return false unless project.lfs_enabled?
project.public? || ci? || user_can_download_code? || build_can_download_code?
project.public? || ci? || lfs_deploy_key? || user_can_download_code? || build_can_download_code?
end
def user_can_download_code?
......
......@@ -69,17 +69,15 @@ class CommitStatus < ActiveRecord::Base
commit_status.update_attributes finished_at: Time.now
end
# We use around_transition to process pipeline on next stages as soon as possible, before the `after_*` is executed
around_transition any => [:success, :failed, :canceled] do |commit_status, block|
block.call
commit_status.pipeline.try(:process!)
end
after_transition do |commit_status, transition|
commit_status.pipeline.try(:build_updated) unless transition.loopback?
end
after_transition any => [:success, :failed, :canceled] do |commit_status|
commit_status.pipeline.try(:process!)
true
end
after_transition [:created, :pending, :running] => :success do |commit_status|
MergeRequests::MergeWhenBuildSucceedsService.new(commit_status.pipeline.project, nil).trigger(commit_status)
end
......@@ -95,6 +93,10 @@ class CommitStatus < ActiveRecord::Base
pipeline.before_sha || Gitlab::Git::BLANK_SHA
end
def group_name
name.gsub(/\d+[\s:\/\\]+\d+\s*/, '').strip
end
def self.stages
# We group by stage name, but order stages by theirs' index
unscoped.from(all, :sg).group('stage').order('max(stage_idx)', 'stage').pluck('sg.stage')
......@@ -113,6 +115,10 @@ class CommitStatus < ActiveRecord::Base
allow_failure? && (failed? || canceled?)
end
def playable?
false
end
def duration
calculate_duration
end
......
......@@ -8,8 +8,9 @@ module HasStatus
class_methods do
def status_sql
scope = all.relevant
scope = all
builds = scope.select('count(*)').to_sql
created = scope.created.select('count(*)').to_sql
success = scope.success.select('count(*)').to_sql
ignored = scope.ignored.select('count(*)').to_sql if scope.respond_to?(:ignored)
ignored ||= '0'
......@@ -19,12 +20,12 @@ module HasStatus
skipped = scope.skipped.select('count(*)').to_sql
deduce_status = "(CASE
WHEN (#{builds})=0 THEN NULL
WHEN (#{builds})=(#{created}) THEN NULL
WHEN (#{builds})=(#{skipped}) THEN 'skipped'
WHEN (#{builds})=(#{success})+(#{ignored})+(#{skipped}) THEN 'success'
WHEN (#{builds})=(#{pending})+(#{skipped}) THEN 'pending'
WHEN (#{builds})=(#{created})+(#{pending})+(#{skipped}) THEN 'pending'
WHEN (#{builds})=(#{canceled})+(#{success})+(#{ignored})+(#{skipped}) THEN 'canceled'
WHEN (#{running})+(#{pending})>0 THEN 'running'
WHEN (#{running})+(#{pending})+(#{created})>0 THEN 'running'
ELSE 'failed'
END)"
......
- is_playable = subject.playable? && can?(current_user, :update_build, @project)
%li.build{class: ("playable" if is_playable)}
.curve
.build-content
- if is_playable
- if is_playable
= link_to play_namespace_project_build_path(subject.project.namespace, subject.project, subject, return_to: request.original_url), method: :post, title: 'Play' do
= render_status_with_link('build', 'play')
%span.ci-status-text= subject.name
- elsif can?(current_user, :read_build, @project)
.ci-status-text= subject.name
- elsif can?(current_user, :read_build, @project)
= link_to namespace_project_build_path(subject.project.namespace, subject.project, subject) do
= render_status_with_link('build', subject.status)
%span.ci-status-text= subject.name
- else
.ci-status-text= subject.name
- else
= render_status_with_link('build', subject.status)
= ci_icon_for_status(subject.status)
......@@ -39,8 +39,7 @@
= stage.titleize
.builds-container
%ul
- statuses.each do |status|
= render "projects/#{status.to_partial_path}_pipeline", subject: status
= render "projects/commit/pipeline_stage", statuses: statuses
- if pipeline.yaml_errors.present?
......
- status_groups = statuses.group_by(&:group_name)
- status_groups.each do |group_name, grouped_statuses|
- if grouped_statuses.one?
- status = grouped_statuses.first
- is_playable = status.playable? && can?(current_user, :update_build, @project)
%li.build{ class: ("playable" if is_playable) }
.curve
.build-content
= render "projects/#{status.to_partial_path}_pipeline", subject: status
- else
%li.build
.curve
.build-content
= render "projects/commit/pipeline_status_group", name: group_name, subject: grouped_statuses
- group_status = CommitStatus.where(id: subject).status
= render_status_with_link('build', group_status)
.dropdown.inline
%button.dropdown-menu-toggle{ type: 'button', data: { toggle: 'dropdown' } }
%span.ci-status-text
= name
%span.badge= subject.size
%ul.dropdown-menu.grouped-pipeline-dropdown
.arrow
- subject.each do |status|
= render "projects/#{status.to_partial_path}_pipeline", subject: status
%li.build
.curve
.build-content
- if subject.target_url
- link_to subject.target_url do
- if subject.target_url
= link_to subject.target_url do
= render_status_with_link('commit status', subject.status)
%span.ci-status-text= subject.name
- else
- else
= render_status_with_link('commit status', subject.status)
%span.ci-status-text= subject.name
......@@ -3,9 +3,13 @@ class Gitlab::Seeder::Pipelines
BUILDS = [
{ name: 'build:linux', stage: 'build', status: :success },
{ name: 'build:osx', stage: 'build', status: :success },
{ name: 'rspec:linux', stage: 'test', status: :success },
{ name: 'rspec:windows', stage: 'test', status: :success },
{ name: 'rspec:windows', stage: 'test', status: :success },
{ name: 'rspec:linux 0 3', stage: 'test', status: :success },
{ name: 'rspec:linux 1 3', stage: 'test', status: :success },
{ name: 'rspec:linux 2 3', stage: 'test', status: :success },
{ name: 'rspec:windows 0 3', stage: 'test', status: :success },
{ name: 'rspec:windows 1 3', stage: 'test', status: :success },
{ name: 'rspec:windows 2 3', stage: 'test', status: :success },
{ name: 'rspec:windows 2 3', stage: 'test', status: :success },
{ name: 'rspec:osx', stage: 'test', status_event: :success },
{ name: 'spinach:linux', stage: 'test', status: :success },
{ name: 'spinach:osx', stage: 'test', status: :failed, allow_failure: true},
......
# rubocop:disable all
class MigrateRepoSize < ActiveRecord::Migration
DOWNTIME = false
def up
project_data = execute('SELECT projects.id, namespaces.path AS namespace_path, projects.path AS project_path FROM projects LEFT JOIN namespaces ON projects.namespace_id = namespaces.id')
project_data.each do |project|
id = project['id']
namespace_path = project['namespace_path'] || ''
path = File.join(Gitlab.config.gitlab_shell.repos_path, namespace_path, project['project_path'] + '.git')
repos_path = Gitlab.config.gitlab_shell['repos_path'] || Gitlab.config.repositories.storages.default
path = File.join(repos_path, namespace_path, project['project_path'] + '.git')
begin
repo = Gitlab::Git::Repository.new(path)
......
......@@ -105,7 +105,8 @@ What is important is that each job is run independently from each other.
If you want to check whether your `.gitlab-ci.yml` file is valid, there is a
Lint tool under the page `/ci/lint` of your GitLab instance. You can also find
the link under **Settings > CI settings** in your project.
a "CI Lint" button to go to this page under **Pipelines > Pipelines** and
**Pipelines > Builds** in your project.
For more information and a complete `.gitlab-ci.yml` syntax, please read
[the documentation on .gitlab-ci.yml](../yaml/README.md).
......
......@@ -63,7 +63,7 @@ The following table depicts the various user permission levels in a project.
| Force push to protected branches [^2] | | | | | |
| Remove protected branches [^2] | | | | | |
[^1]: If **Allow guest to access builds** is enabled in CI settings
[^1]: If **Public pipelines** is enabled in **Project Settings > CI/CD Pipelines**
[^2]: Not allowed for Guest, Reporter, Developer, Master, or Owner
## Group
......
......@@ -45,5 +45,5 @@ In `config/gitlab.yml`:
* Currently, storing GitLab Git LFS objects on a non-local storage (like S3 buckets)
is not supported
* Currently, removing LFS objects from GitLab Git LFS storage is not supported
* LFS authentications via SSH is not supported for the time being
* Only compatible with the GitLFS client versions 1.1.0 or 1.0.2.
* LFS authentications via SSH was added with GitLab 8.12
* Only compatible with the GitLFS client versions 1.1.0 and up, or 1.0.2.
......@@ -36,6 +36,10 @@ Documentation for GitLab instance administrators is under [LFS administration do
* Git LFS always assumes HTTPS so if you have GitLab server on HTTP you will have
to add the URL to Git config manually (see #troubleshooting)
>**Note**: With 8.12 GitLab added LFS support to SSH. The Git LFS communication
still goes over HTTP, but now the SSH client passes the correct credentials
to the Git LFS client, so no action is required by the user.
## Using Git LFS
Lets take a look at the workflow when you need to check large files into your Git
......@@ -132,6 +136,10 @@ git config --add lfs.url "http://gitlab.example.com/group/project.git/info/lfs"
### Credentials are always required when pushing an object
>**Note**: With 8.12 GitLab added LFS support to SSH. The Git LFS communication
still goes over HTTP, but now the SSH client passes the correct credentials
to the Git LFS client, so no action is required by the user.
Given that Git LFS uses HTTP Basic Authentication to authenticate the user pushing
the LFS object on every push for every object, user HTTPS credentials are required.
......
......@@ -82,6 +82,19 @@ module API
response
end
post "/lfs_authenticate" do
status 200
key = Key.find(params[:key_id])
token_handler = Gitlab::LfsToken.new(key)
{
username: token_handler.actor_name,
lfs_token: token_handler.generate,
repository_http_path: project.http_url_to_repo
}
end
get "/merge_request_urls" do
::MergeRequests::GetUrlsService.new(project).execute(params[:changes])
end
......
module Gitlab
module Auth
Result = Struct.new(:user, :project, :type, :capabilities) do
def succeeded?
user.present? || [:ci].include?(type)
Result = Struct.new(:actor, :project, :type, :capabilities) do
def success?
actor.present? || type == :ci
end
end
class MissingPersonalTokenError < StandardError; end
class << self
def find_for_git_client(login, password, project:, ip:)
raise "Must provide an IP for rate limiting" if ip.nil?
result = service_access_token_check(login, password, project) ||
result =
service_request_check(login, password, project) ||
build_access_token_check(login, password) ||
user_with_password_for_git(login, password) ||
oauth_access_token_check(login, password) ||
lfs_token_check(login, password) ||
personal_access_token_check(login, password) ||
Result.new
rate_limit!(ip, success: result.succeeded?, login: login)
rate_limit!(ip, success: result.success?, login: login)
result
end
......@@ -59,7 +64,7 @@ module Gitlab
private
def service_access_token_check(login, password, project)
def service_request_check(login, password, project)
matched_login = /(?<service>^[a-zA-Z]*-ci)-token$/.match(login)
return unless project && matched_login.present?
......@@ -81,14 +86,9 @@ module Gitlab
user = find_with_user_password(login, password)
return unless user
type =
if user.two_factor_enabled?
:missing_personal_token
else
:gitlab_or_ldap
end
raise Gitlab::Auth::MissingPersonalTokenError if user.two_factor_enabled?
Result.new(user, nil, type, full_capabilities)
Result.new(user, nil, :gitlab_or_ldap, full_capabilities)
end
def oauth_access_token_check(login, password)
......@@ -105,9 +105,24 @@ module Gitlab
if login && password
user = User.find_by_personal_access_token(password)
validation = User.by_login(login)
if user && user == validation
Result.new(user, nil, :personal_token, full_capabilities)
Result.new(user, nil, :personal_token, full_capabilities) if user.present? && user == validation
end
end
def lfs_token_check(login, password)
deploy_key_matches = login.match(/\Alfs\+deploy-key-(\d+)\z/)
actor =
if deploy_key_matches
DeployKey.find(deploy_key_matches[1])
else
User.by_login(login)
end
if actor
token_handler = Gitlab::LfsToken.new(actor)
Result.new(actor, nil, token_handler.type, read_capabilities) if Devise.secure_compare(token_handler.value, password)
end
end
......
......@@ -22,10 +22,6 @@ module Gitlab
private
def repos_path
Gitlab.config.gitlab_shell.repos_path
end
def path_to_repo
@project.repository.path_to_repo
end
......
module Gitlab
class LfsToken
attr_accessor :actor
TOKEN_LENGTH = 50
EXPIRY_TIME = 1800
def initialize(actor)
@actor =
case actor
when DeployKey, User
actor
when Key
actor.user
else
raise 'Bad Actor'
end
end
def generate
token = Devise.friendly_token(TOKEN_LENGTH)
Gitlab::Redis.with do |redis|
redis.set(redis_key, token, ex: EXPIRY_TIME)
end
token
end
def value
Gitlab::Redis.with do |redis|
redis.get(redis_key)
end
end
def type
actor.is_a?(User) ? :lfs_token : :lfs_deploy_token
end
def actor_name
actor.is_a?(User) ? actor.username : "lfs+deploy-key-#{actor.id}"
end
private
def redis_key
"gitlab:lfs_token:#{actor.class.name.underscore}_#{actor.id}" if actor
end
end
end
......@@ -13,10 +13,12 @@ feature 'issuable templates', feature: true, js: true do
context 'user creates an issue using templates' do
let(:template_content) { 'this is a test "bug" template' }
let(:longtemplate_content) { %Q(this\n\n\n\n\nis\n\n\n\n\na\n\n\n\n\nbug\n\n\n\n\ntemplate) }
let(:issue) { create(:issue, author: user, assignee: user, project: project) }
background do
project.repository.commit_file(user, '.gitlab/issue_templates/bug.md', template_content, 'added issue template', 'master', false)
project.repository.commit_file(user, '.gitlab/issue_templates/test.md', longtemplate_content, 'added issue template', 'master', false)
visit edit_namespace_project_issue_path project.namespace, project, issue
fill_in :'issue[title]', with: 'test issue title'
end
......@@ -27,6 +29,17 @@ feature 'issuable templates', feature: true, js: true do
preview_template
save_changes
end
it 'updates height of markdown textarea' do
start_height = page.evaluate_script('$(".markdown-area").outerHeight()')
select_template 'test'
wait_for_ajax
end_height = page.evaluate_script('$(".markdown-area").outerHeight()')
expect(end_height).not_to eq(start_height)
end
end
context 'user creates a merge request using templates' do
......
......@@ -29,8 +29,11 @@ describe 'Dashboard > User filters todos', feature: true, js: true do
fill_in 'Search projects', with: project_1.name_with_namespace
click_link project_1.name_with_namespace
end
wait_for_ajax
expect('.prepend-top-default').not_to have_content project_2.name_with_namespace
expect(page).to have_content project_1.name_with_namespace
expect(page).not_to have_content project_2.name_with_namespace
end
it 'filters by author' do
......@@ -39,8 +42,11 @@ describe 'Dashboard > User filters todos', feature: true, js: true do
fill_in 'Search authors', with: user_1.name
click_link user_1.name
end
wait_for_ajax
expect('.prepend-top-default').not_to have_content user_2.name
expect(find('.todos-list')).to have_content user_1.name
expect(find('.todos-list')).not_to have_content user_2.name
end
it 'filters by type' do
......@@ -48,8 +54,11 @@ describe 'Dashboard > User filters todos', feature: true, js: true do
within '.dropdown-menu-type' do
click_link 'Issue'
end
wait_for_ajax
expect('.prepend-top-default').not_to have_content ' merge request !'
expect(find('.todos-list')).to have_content issue.to_reference
expect(find('.todos-list')).not_to have_content merge_request.to_reference
end
it 'filters by action' do
......@@ -57,7 +66,10 @@ describe 'Dashboard > User filters todos', feature: true, js: true do
within '.dropdown-menu-action' do
click_link 'Assigned'
end
wait_for_ajax
expect('.prepend-top-default').not_to have_content ' mentioned '
expect(find('.todos-list')).to have_content ' assigned you '
expect(find('.todos-list')).not_to have_content ' mentioned '
end
end
......@@ -59,6 +59,24 @@ describe Gitlab::Auth, lib: true do
expect(gl_auth.find_for_git_client(user.username, 'password', project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_capabilities))
end
it 'recognizes user lfs tokens' do
user = create(:user)
ip = 'ip'
token = Gitlab::LfsToken.new(user).generate
expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: user.username)
expect(gl_auth.find_for_git_client(user.username, token, project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(user, :lfs_token))
end
it 'recognizes deploy key lfs tokens' do
key = create(:deploy_key)
ip = 'ip'
token = Gitlab::LfsToken.new(key).generate
expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: "lfs+deploy-key-#{key.id}")
expect(gl_auth.find_for_git_client("lfs+deploy-key-#{key.id}", token, project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(key, :lfs_deploy_token))
end
it 'recognizes OAuth tokens' do
user = create(:user)
application = Doorkeeper::Application.create!(name: "MyApp", redirect_uri: "https://app.com", owner: user)
......@@ -73,7 +91,7 @@ describe Gitlab::Auth, lib: true do
login = 'foo'
ip = 'ip'
expect(gl_auth).to receive(:rate_limit!).with(ip, success: false, login: login)
expect(gl_auth).to receive(:rate_limit!).with(ip, success: nil, login: login)
expect(gl_auth.find_for_git_client(login, 'bar', project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new)
end
end
......
require 'spec_helper'
describe Gitlab::LfsToken, lib: true do
describe '#generate and #value' do
shared_examples 'an LFS token generator' do
it 'returns a randomly generated token' do
token = handler.generate
expect(token).not_to be_nil
expect(token).to be_a String
expect(token.length).to eq 50
end
it 'returns the correct token based on the key' do
token = handler.generate
expect(handler.value).to eq(token)
end
end
context 'when the actor is a user' do
let(:actor) { create(:user) }
let(:handler) { described_class.new(actor) }
it_behaves_like 'an LFS token generator'
it 'returns the correct username' do
expect(handler.actor_name).to eq(actor.username)
end
it 'returns the correct token type' do
expect(handler.type).to eq(:lfs_token)
end
end
context 'when the actor is a deploy key' do
let(:actor) { create(:deploy_key) }
let(:handler) { described_class.new(actor) }
it_behaves_like 'an LFS token generator'
it 'returns the correct username' do
expect(handler.actor_name).to eq("lfs+deploy-key-#{actor.id}")
end
it 'returns the correct token type' do
expect(handler.type).to eq(:lfs_deploy_token)
end
end
end
end
......@@ -373,8 +373,8 @@ describe Ci::Pipeline, models: true do
end
describe '#execute_hooks' do
let!(:build_a) { create_build('a') }
let!(:build_b) { create_build('b') }
let!(:build_a) { create_build('a', 0) }
let!(:build_b) { create_build('b', 1) }
let!(:hook) do
create(:project_hook, project: project, pipeline_events: enabled)
......@@ -398,7 +398,7 @@ describe Ci::Pipeline, models: true do
build_b.enqueue
end
it 'receive a pending event once' do
it 'receives a pending event once' do
expect(WebMock).to have_requested_pipeline_hook('pending').once
end
end
......@@ -411,7 +411,7 @@ describe Ci::Pipeline, models: true do
build_b.run
end
it 'receive a running event once' do
it 'receives a running event once' do
expect(WebMock).to have_requested_pipeline_hook('running').once
end
end
......@@ -422,11 +422,21 @@ describe Ci::Pipeline, models: true do
build_b.success
end
it 'receive a success event once' do
it 'receives a success event once' do
expect(WebMock).to have_requested_pipeline_hook('success').once
end
end
context 'when stage one failed' do
before do
build_a.drop
end
it 'receives a failed event once' do
expect(WebMock).to have_requested_pipeline_hook('failed').once
end
end
def have_requested_pipeline_hook(status)
have_requested(:post, hook.url).with do |req|
json_body = JSON.parse(req.body)
......@@ -450,8 +460,12 @@ describe Ci::Pipeline, models: true do
end
end
def create_build(name)
create(:ci_build, :created, pipeline: pipeline, name: name)
def create_build(name, stage_idx)
create(:ci_build,
:created,
pipeline: pipeline,
name: name,
stage_idx: stage_idx)
end
end
end
......@@ -40,7 +40,7 @@ describe CommitStatus, models: true do
it { is_expected.to be_falsey }
end
%w(running success failed).each do |status|
%w[running success failed].each do |status|
context "if commit status is #{status}" do
before { commit_status.status = status }
......@@ -48,7 +48,7 @@ describe CommitStatus, models: true do
end
end
%w(pending canceled).each do |status|
%w[pending canceled].each do |status|
context "if commit status is #{status}" do
before { commit_status.status = status }
......@@ -60,7 +60,7 @@ describe CommitStatus, models: true do
describe '#active?' do
subject { commit_status.active? }
%w(pending running).each do |state|
%w[pending running].each do |state|
context "if commit_status.status is #{state}" do
before { commit_status.status = state }
......@@ -68,7 +68,7 @@ describe CommitStatus, models: true do
end
end
%w(success failed canceled).each do |state|
%w[success failed canceled].each do |state|
context "if commit_status.status is #{state}" do
before { commit_status.status = state }
......@@ -80,7 +80,7 @@ describe CommitStatus, models: true do
describe '#complete?' do
subject { commit_status.complete? }
%w(success failed canceled).each do |state|
%w[success failed canceled].each do |state|
context "if commit_status.status is #{state}" do
before { commit_status.status = state }
......@@ -88,7 +88,7 @@ describe CommitStatus, models: true do
end
end
%w(pending running).each do |state|
%w[pending running].each do |state|
context "if commit_status.status is #{state}" do
before { commit_status.status = state }
......@@ -187,7 +187,7 @@ describe CommitStatus, models: true do
subject { CommitStatus.where(pipeline: pipeline).stages }
it 'returns ordered list of stages' do
is_expected.to eq(%w(build test deploy))
is_expected.to eq(%w[build test deploy])
end
end
......@@ -223,4 +223,33 @@ describe CommitStatus, models: true do
expect(commit_status.commit).to eq project.commit
end
end
describe '#group_name' do
subject { commit_status.group_name }
tests = {
'rspec:windows' => 'rspec:windows',
'rspec:windows 0' => 'rspec:windows 0',
'rspec:windows 0 test' => 'rspec:windows 0 test',
'rspec:windows 0 1' => 'rspec:windows',
'rspec:windows 0 1 name' => 'rspec:windows name',
'rspec:windows 0/1' => 'rspec:windows',
'rspec:windows 0/1 name' => 'rspec:windows name',
'rspec:windows 0:1' => 'rspec:windows',
'rspec:windows 0:1 name' => 'rspec:windows name',
'rspec:windows 10000 20000' => 'rspec:windows',
'rspec:windows 0 : / 1' => 'rspec:windows',
'rspec:windows 0 : / 1 name' => 'rspec:windows name',
'0 1 name ruby' => 'name ruby',
'0 :/ 1 name ruby' => 'name ruby'
}
tests.each do |name, group_name|
it "'#{name}' puts in '#{group_name}'" do
commit_status.name = name
is_expected.to eq(group_name)
end
end
end
end
......@@ -100,6 +100,43 @@ describe API::API, api: true do
end
end
describe "POST /internal/lfs_authenticate" do
before do
project.team << [user, :developer]
end
context 'user key' do
it 'returns the correct information about the key' do
lfs_auth(key.id, project)
expect(response).to have_http_status(200)
expect(json_response['username']).to eq(user.username)
expect(json_response['lfs_token']).to eq(Gitlab::LfsToken.new(key).value)
expect(json_response['repository_http_path']).to eq(project.http_url_to_repo)
end
it 'returns a 404 when the wrong key is provided' do
lfs_auth(nil, project)
expect(response).to have_http_status(404)
end
end
context 'deploy key' do
let(:key) { create(:deploy_key) }
it 'returns the correct information about the key' do
lfs_auth(key.id, project)
expect(response).to have_http_status(200)
expect(json_response['username']).to eq("lfs+deploy-key-#{key.id}")
expect(json_response['lfs_token']).to eq(Gitlab::LfsToken.new(key).value)
expect(json_response['repository_http_path']).to eq(project.http_url_to_repo)
end
end
end
describe "GET /internal/discover" do
it do
get(api("/internal/discover"), key_id: key.id, secret_token: secret_token)
......@@ -389,4 +426,13 @@ describe API::API, api: true do
protocol: 'ssh'
)
end
def lfs_auth(key_id, project)
post(
api("/internal/lfs_authenticate"),
key_id: key_id,
secret_token: secret_token,
project: project.path_with_namespace
)
end
end
......@@ -246,6 +246,18 @@ describe 'Git LFS API and storage' do
end
end
context 'when deploy key is authorized' do
let(:key) { create(:deploy_key) }
let(:authorization) { authorize_deploy_key }
let(:update_permissions) do
project.deploy_keys << key
project.lfs_objects << lfs_object
end
it_behaves_like 'responds with a file'
end
context 'when build is authorized' do
let(:authorization) { authorize_ci_project }
......@@ -906,6 +918,10 @@ describe 'Git LFS API and storage' do
ActionController::HttpAuthentication::Basic.encode_credentials(user.username, user.password)
end
def authorize_deploy_key
ActionController::HttpAuthentication::Basic.encode_credentials("lfs+deploy-key-#{key.id}", Gitlab::LfsToken.new(key).generate)
end
def fork_project(project, user, object = nil)
allow(RepositoryForkWorker).to receive(:perform_async).and_return(true)
Projects::ForkService.new(project, user, {}).execute
......
require 'spec_helper'
describe 'projects/pipelines/show' do
include Devise::TestHelpers
let(:project) { create(:project) }
let(:pipeline) { create(:ci_empty_pipeline, project: project, sha: project.commit.id) }
before do
controller.prepend_view_path('app/views/projects')
create_build('build', 0, 'build')
create_build('test', 1, 'rspec 0:2')
create_build('test', 1, 'rspec 1:2')
create_build('test', 1, 'audit')
create_build('deploy', 2, 'production')
create(:generic_commit_status, pipeline: pipeline, stage: 'external', name: 'jenkins', stage_idx: 3)
assign(:project, project)
assign(:pipeline, pipeline)
allow(view).to receive(:can?).and_return(true)
end
it 'shows a graph with grouped stages' do
render
expect(rendered).to have_css('.pipeline-graph')
expect(rendered).to have_css('.grouped-pipeline-dropdown')
# stages
expect(rendered).to have_text('Build')
expect(rendered).to have_text('Test')
expect(rendered).to have_text('Deploy')
expect(rendered).to have_text('External')
# builds
expect(rendered).to have_text('rspec')
expect(rendered).to have_text('rspec 0:2')
expect(rendered).to have_text('production')
expect(rendered).to have_text('jenkins')
end
private
def create_build(stage, stage_idx, name)
create(:ci_build, pipeline: pipeline, stage: stage, stage_idx: stage_idx, name: name)
end
end
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