Commit 4cdade66 authored by Grzegorz Bizon's avatar Grzegorz Bizon

Merge branch 'ce-to-ee-2018-10-22' into 'master'

CE upstream - 2018-10-22 21:21 UTC

Closes #4302

See merge request gitlab-org/gitlab-ee!8038
parents 04d40deb 17c38329
......@@ -3,3 +3,7 @@
/public/
/vendor/
/tmp/
# ignore stylesheets for now as this clashes with our linter
*.css
*.scss
danger.import_plugin('danger/plugins/helper.rb')
danger.import_dangerfile(path: 'danger/metadata')
danger.import_dangerfile(path: 'danger/changes_size')
danger.import_dangerfile(path: 'danger/changelog')
......
......@@ -17,7 +17,7 @@ import flash from '~/flash';
export default function renderMermaid($els) {
if (!$els.length) return;
import(/* webpackChunkName: 'mermaid' */ 'blackst0ne-mermaid')
import(/* webpackChunkName: 'mermaid' */ 'mermaid')
.then(mermaid => {
mermaid.initialize({
// mermaid core options
......
<script>
import _ from 'underscore';
import CiIcon from '~/vue_shared/components/ci_icon.vue';
import Icon from '~/vue_shared/components/icon.vue';
import tooltip from '~/vue_shared/directives/tooltip';
export default {
components: {
CiIcon,
Icon,
},
directives: {
tooltip,
},
props: {
job: {
type: Object,
required: true,
},
isActive: {
type: Boolean,
required: true,
},
},
computed: {
tooltipText() {
return `${_.escape(this.job.name)} - ${this.job.status.tooltip}`;
},
},
};
</script>
<template>
<div
class="build-job"
:class="{ retried: job.retried, active: isActive }"
>
<a
v-tooltip
:href="job.status.details_path"
:title="tooltipText"
data-container="body"
data-boundary="viewport"
class="js-job-link"
>
<icon
v-if="isActive"
name="arrow-right"
class="js-arrow-right icon-arrow-right"
/>
<ci-icon :status="job.status" />
<span>{{ job.name ? job.name : job.id }}</span>
<icon
v-if="job.retried"
name="retry"
class="js-retry-icon"
/>
</a>
</div>
</template>
<script>
import _ from 'underscore';
import CiIcon from '~/vue_shared/components/ci_icon.vue';
import Icon from '~/vue_shared/components/icon.vue';
import tooltip from '~/vue_shared/directives/tooltip';
import JobContainerItem from './job_container_item.vue';
export default {
components: {
CiIcon,
Icon,
},
directives: {
tooltip,
JobContainerItem,
},
props: {
jobs: {
type: Array,
......@@ -26,49 +20,16 @@ export default {
isJobActive(currentJobId) {
return this.jobId === currentJobId;
},
tooltipText(job) {
return `${_.escape(job.name)} - ${job.status.tooltip}`;
},
},
};
</script>
<template>
<div class="js-jobs-container builds-container">
<div
<job-container-item
v-for="job in jobs"
:key="job.id"
class="build-job"
:class="{ retried: job.retried, active: isJobActive(job.id) }"
>
<a
v-tooltip
:href="job.status.details_path"
:title="tooltipText(job)"
data-container="body"
>
<icon
v-if="isJobActive(job.id)"
name="arrow-right"
class="js-arrow-right icon-arrow-right"
/>
<ci-icon :status="job.status" />
<span>
<template v-if="job.name">
{{ job.name }}
</template>
<template v-else>
{{ job.id }}
</template>
</span>
<icon
v-if="job.retried"
name="retry"
class="js-retry-icon"
:job="job"
:is-active="isJobActive(job.id)"
/>
</a>
</div>
</div>
</template>
......@@ -238,10 +238,6 @@ h3.popover-header {
}
.card {
.card-title {
margin-bottom: 0;
}
&.card-without-border {
@extend .border-0;
}
......@@ -255,13 +251,6 @@ h3.popover-header {
}
}
.card-header {
h3.card-title,
h4.card-title {
margin-top: 0;
}
}
.nav-tabs {
// Override bootstrap's default border
border-bottom: 0;
......
......@@ -52,8 +52,3 @@
margin-top: $gl-padding;
}
}
.card-title {
font-size: inherit;
line-height: inherit;
}
......@@ -19,10 +19,6 @@
justify-content: space-between;
line-height: $line-height-base;
.card-title {
display: flex;
align-items: center;
.logo-text {
width: 55px;
height: 24px;
......@@ -30,7 +26,6 @@
flex-direction: column;
justify-content: center;
}
}
.navbar-collapse {
padding-right: 0;
......
......@@ -63,7 +63,6 @@
.card
.card-header
%h3.card-title
= _('Projects')
%span.badge.badge-pill
#{@group.projects.count}
......
......@@ -16,7 +16,6 @@
.content{ id: "content-body" }
.card
.card-header
.card-title
= brand_header_logo
- logo_text = brand_header_logo_type
- if logo_text.present?
......
......@@ -9,7 +9,6 @@
.card.prepend-top-10
.card-header
%h4.card-title
= panel_title
%ul.content-list.all-branches
- branches.first(overview_max_branches).each do |branch|
......
.protected-branches-list.js-protected-branches-list.qa-protected-branches-list
- if @protected_branches.empty?
.card-header.bg-white
%h3.card-title.mb-0
Protected branch (#{@protected_branches_count})
%p.settings-message.text-center
There are currently no protected branches, protect a branch with the form above.
......
......@@ -2,7 +2,6 @@
%input{ type: 'hidden', name: 'update_section', value: 'js-protected-branches-settings' }
.card
.card-header
%h3.card-title
Protect a branch
.card-body
= form_errors(@protected_branch)
......
......@@ -2,7 +2,6 @@
%input{ type: 'hidden', name: 'update_section', value: 'js-protected-tags-settings' }
.card
.card-header
%h3.card-title
Protect a tag
.card-body
= form_errors(@protected_tag)
......
.protected-tags-list.js-protected-tags-list
- if @protected_tags.empty?
.card-header
%h3.card-title
Protected tag (#{@protected_tags_count})
%p.settings-message.text-center
There are currently no protected tags, protect a tag with the form above.
......
......@@ -18,7 +18,6 @@
.col-lg-12
.card
.card-header
%h4.card-title
= s_('ContainerRegistry|How to use the Container Registry')
.card-body
%p
......
......@@ -2,7 +2,6 @@
.card.js-panel-monitored-metrics{ data: { active_metrics: active_common_project_prometheus_metrics_path(project, :json), metrics_help_path: help_page_path('user/project/integrations/prometheus_library/metrics') } }
.card-header
%h3.card-title
= s_('PrometheusService|Common metrics')
%span.badge.badge-pill.js-monitored-count 0
.card-body
......@@ -17,7 +16,6 @@
.card.hidden.js-panel-missing-env-vars
.card-header
%h3.card-title
= icon('caret-right lg fw', class: 'panel-toggle js-panel-toggle', 'aria-label' => 'Toggle panel')
= s_('PrometheusService|Missing environment variable')
%span.badge.badge-pill.js-env-var-count 0
......
......@@ -20,8 +20,9 @@
.col-sm-10.create-from
.dropdown
= hidden_field_tag :ref, default_ref
= button_tag type: 'button', title: default_ref, class: 'dropdown-menu-toggle wide js-branch-select', required: true, data: { toggle: 'dropdown', selected: default_ref, field_name: 'ref' } do
= button_tag type: 'button', title: default_ref, class: 'dropdown-menu-toggle wide js-branch-select monospace', required: true, data: { toggle: 'dropdown', selected: default_ref, field_name: 'ref' } do
.text-left.dropdown-toggle-text= default_ref
= icon('chevron-down')
= render 'shared/ref_dropdown', dropdown_class: 'wide'
.form-text.text-muted
= s_('TagsPage|Existing branch name, tag, or commit SHA')
......
......@@ -3,7 +3,6 @@
= render "projects/triggers/content"
.card
.card-header
%h4.card-title
Manage your project's triggers
.card-body
= render "projects/triggers/form", btn_text: "Add trigger"
......
---
title: Remove .card-title from .card-header for BS4 migration
merge_request: 19335
author: Takuya Noguchi
type: other
---
title: Bump mermaid to 8.0.0-rc.8
merge_request: 22509
author: "@blackst0ne"
type: changed
---
title: Change branch font type in tag creation
merge_request: 22454
author: George Tsiolis
type: other
---
title: Improve validation errors for external CI/CD configuration
merge_request: 22394
author:
type: added
---
title: Remove empty spec describe blocks
merge_request: 22451
author: george Tsiolis
author: George Tsiolis
type: other
......@@ -39,8 +39,6 @@ def database_paths_requiring_review(files)
to_review
end
all_files = git.added_files + git.modified_files
non_geo_db_schema_updated = !git.modified_files.grep(%r{\Adb/schema\.rb}).empty?
geo_db_schema_updated = !git.modified_files.grep(%r{\Aee/db/geo/schema\.rb}).empty?
......@@ -55,7 +53,7 @@ if geo_migration_created && !geo_db_schema_updated
warn format(SCHEMA_NOT_UPDATED_MESSAGE, migrations: 'Geo migrations', schema: gitlab.html_link("ee/db/geo/schema.rb"))
end
db_paths_to_review = database_paths_requiring_review(all_files)
db_paths_to_review = database_paths_requiring_review(helper.all_changed_files)
unless db_paths_to_review.empty?
message 'This merge request adds or changes files that require a ' \
......
......@@ -11,9 +11,7 @@ def docs_paths_requiring_review(files)
end
end
all_files = git.added_files + git.modified_files
docs_paths_to_review = docs_paths_requiring_review(all_files)
docs_paths_to_review = docs_paths_requiring_review(helper.all_changed_files)
unless docs_paths_to_review.empty?
message 'This merge request adds or changes files that require a ' \
......
......@@ -7,7 +7,7 @@ def get_eslint_files(files)
end
end
eslint_candidates = get_eslint_files(git.added_files + git.modified_files)
eslint_candidates = get_eslint_files(helper.all_changed_files)
return if eslint_candidates.empty?
......
# frozen_string_literal: true
module Danger
# Common helper functions for our danger scripts
# If we find ourselves repeating code in our danger files, we might as well put them in here.
class Helper < Plugin
# Returns a list of all files that have been added, modified or renamed.
# `git.modified_files` might contain paths that already have been renamed,
# so we need to remove them from the list.
#
# Considering these changes:
#
# - A new_file.rb
# - D deleted_file.rb
# - M modified_file.rb
# - R renamed_file_before.rb -> renamed_file_after.rb
#
# it will return
# ```
# [ 'new_file.rb', 'modified_file.rb', 'renamed_file_after.rb' ]
# ```
#
# @return [Array<String>]
def all_changed_files
Set.new
.merge(git.added_files.to_a)
.merge(git.modified_files.to_a)
.merge(git.renamed_files.map { |x| x[:after] })
.subtract(git.renamed_files.map { |x| x[:before] })
.to_a
.sort
end
end
end
......@@ -6,7 +6,7 @@ def get_prettier_files(files)
end
end
prettier_candidates = get_prettier_files(git.added_files + git.modified_files)
prettier_candidates = get_prettier_files(helper.all_changed_files)
return if prettier_candidates.empty?
......
......@@ -8,7 +8,7 @@ We want to create a welcoming environment for everyone who is interested in cont
For a first-time step-by-step guide to the contribution process, please see
["Contributing to GitLab"](https://about.gitlab.com/contributing/).
Looking for something to work on? Look for issues in the [Backlog (Accepting merge requests) milestone](#i-want-to-contribute).
Looking for something to work on? Look for issues with the label [`Accepting merge requests`](#i-want-to-contribute).
GitLab comes in two flavors, GitLab Community Edition (CE) our free and open
source edition, and GitLab Enterprise Edition (EE) which is our commercial
......@@ -30,6 +30,11 @@ Please report suspected security vulnerabilities in private to
Please do **NOT** create publicly viewable issues for suspected security
vulnerabilities.
## Code of conduct
Our code of conduct can be found on the
["Contributing to GitLab"](https://about.gitlab.com/contributing/) page.
## Closing policy for issues and merge requests
GitLab is a popular open source project and the capacity to deal with issues
......@@ -61,10 +66,10 @@ the remaining issues on the GitHub issue tracker.
## I want to contribute!
If you want to contribute to GitLab, [issues in the `Backlog (Accepting merge requests)` milestone][accepting-mrs-weight]
are a great place to start. Issues with a lower weight (1 or 2) are deemed
suitable for beginners. These issues will be of reasonable size and challenge,
for anyone to start contributing to GitLab. If you have any questions or need help visit [Getting Help](https://about.gitlab.com/getting-help/#discussion) to
If you want to contribute to GitLab,
[issues with the `Accepting merge requests` label](issue_workflow.md#label-for-community-contributors)
are a great place to start.
If you have any questions or need help visit [Getting Help](https://about.gitlab.com/getting-help/#discussion) to
learn how to communicate with GitLab. If you're looking for a Gitter or Slack channel
please consider we favor
[asynchronous communication](https://about.gitlab.com/handbook/communication/#internal-communication) over real time communication. Thanks for your contribution!
......@@ -126,4 +131,3 @@ This [documentation](style_guides.md) outlines the current style guidelines.
[team]: https://about.gitlab.com/team/
[getting-help]: https://about.gitlab.com/getting-help/
[codetriage]: http://www.codetriage.com/gitlabhq/gitlabhq
[accepting-mrs-weight]: https://gitlab.com/gitlab-org/gitlab-ce/issues?scope=all&utf8=✓&state=opened&assignee_id=0&milestone_title=Backlog%20&#40;Accepting%20merge%20requests&#41;
......@@ -181,10 +181,10 @@ Severity levels can be applied further depending on the facet of the impact; e.g
Issues that are beneficial to our users, 'nice to haves', that we currently do
not have the capacity for or want to give the priority to, are labeled as
~"Accepting Merge Requests", so the community can make a contribution.
~"Accepting merge requests", so the community can make a contribution.
Community contributors can submit merge requests for any issue they want, but
the ~"Accepting Merge Requests" label has a special meaning. It points to
the ~"Accepting merge requests" label has a special meaning. It points to
changes that:
1. We already agreed on,
......@@ -192,26 +192,26 @@ changes that:
1. Are likely to get accepted by a maintainer.
We want to avoid a situation when a contributor picks an
~"Accepting Merge Requests" issue and then their merge request gets closed,
~"Accepting merge requests" issue and then their merge request gets closed,
because we realize that it does not fit our vision, or we want to solve it in a
different way.
We add the ~"Accepting Merge Requests" label to:
We add the ~"Accepting merge requests" label to:
- Low priority ~bug issues (i.e. we do not add it to the bugs that we want to
solve in the ~"Next Patch Release")
- Small ~"feature proposal"
- Small ~"technical debt" issues
After adding the ~"Accepting Merge Requests" label, we try to estimate the
After adding the ~"Accepting merge requests" label, we try to estimate the
[weight](#issue-weight) of the issue. We use issue weight to let contributors
know how difficult the issue is. Additionally:
- We advertise ["Accepting Merge Requests" issues with weight < 5][up-for-grabs]
- We advertise [`Accepting merge requests` issues with weight < 5][up-for-grabs]
as suitable for people that have never contributed to GitLab before on the
[Up For Grabs campaign](http://up-for-grabs.net)
- We encourage people that have never contributed to any open source project to
look for ["Accepting Merge Requests" issues with a weight of 1][firt-timers]
look for [`Accepting merge requests` issues with a weight of 1][firt-timers]
If you've decided that you would like to work on an issue, please @-mention
the [appropriate product manager](https://about.gitlab.com/handbook/product/#who-to-talk-to-for-what)
......@@ -220,12 +220,12 @@ members to further discuss scope, design, and technical considerations. This wil
ensure that your contribution is aligned with the GitLab product and minimize
any rework and delay in getting it merged into master.
GitLab team members who apply the ~"Accepting Merge Requests" label to an issue
GitLab team members who apply the ~"Accepting merge requests" label to an issue
should update the issue description with a responsible product manager, inviting
any potential community contributor to @-mention per above.
[up-for-grabs]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=Accepting+Merge+Requests&scope=all&sort=weight_asc&state=opened
[firt-timers]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name%5B%5D=Accepting+Merge+Requests&scope=all&sort=upvotes_desc&state=opened&weight=1
[up-for-grabs]: https://gitlab.com/groups/gitlab-org/-/issues?state=opened&label_name[]=Accepting+merge+requests&assignee_id=0&sort=weight
[firt-timers]: https://gitlab.com/groups/gitlab-org/-/issues?state=opened&label_name[]=Accepting+merge+requests&assignee_id=0&sort=weight&weight=1
## Issue triaging
......
......@@ -2,10 +2,9 @@
We welcome merge requests with fixes and improvements to GitLab code, tests,
and/or documentation. The issues that are specifically suitable for
community contributions are listed with the
[`Backlog (Accepting merge requests)` milestone in the CE issue tracker][accepting-mrs-ce]
and [EE issue tracker][accepting-mrs-ee], but you are free to contribute to any other issue
you want.
community contributions are listed with
[the `Accepting merge requests` label](issue_workflow.md#label-for-community-contributors),
but you are free to contribute to any other issue you want.
Please note that if an issue is marked for the current milestone either before
or while you are working on it, a team member may take over the merge request
......@@ -25,8 +24,6 @@ some potentially easy issues.
To start with GitLab development download the [GitLab Development Kit][gdk] and
see the [Development section](../../README.md) for some guidelines.
[accepting-mrs-ce]: https://gitlab.com/gitlab-org/gitlab-ce/issues?milestone_title=Backlog%20&#40;Accepting%20merge%20requests&#41;
[accepting-mrs-ee]: https://gitlab.com/gitlab-org/gitlab-ee/issues?milestone_title=Backlog%20&#40;Accepting%20merge%20requests&#41;
[gitlab-mr-tracker]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests
[gdk]: https://gitlab.com/gitlab-org/gitlab-development-kit
......
......@@ -74,6 +74,8 @@ are very appreciative of the work done by translators and proofreaders!
have previously translated.
1. Your request to become a proofreader will be considered on the merits of
your previous translations.
your previous translations by [GitLab team members](https://about.gitlab.com/team/)
or [Core team members](https://about.gitlab.com/core-team/) who are fluent in
the language or current proofreaders.
[proofreader-src]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/development/i18n/proofreader.md
......@@ -13,10 +13,10 @@ module Gitlab
@global = Entry::Global.new(@config)
@global.compose!
rescue Loader::FormatError, Extendable::ExtensionError => e
rescue Loader::FormatError,
Extendable::ExtensionError,
External::Processor::IncludeError => e
raise Config::ConfigError, e.message
rescue ::Gitlab::Ci::External::Processor::FileError => e
raise ::Gitlab::Ci::YamlProcessor::ValidationError, e.message
end
def valid?
......@@ -81,7 +81,7 @@ module Gitlab
def process_external_files(config, project, opts)
sha = opts.fetch(:sha) { project.repository.root_ref_sha }
::Gitlab::Ci::External::Processor.new(config, project, sha).perform
Config::External::Processor.new(config, project, sha).perform
end
end
end
......
# frozen_string_literal: true
module Gitlab
module Ci
class Config
module External
module File
class Base
include Gitlab::Utils::StrongMemoize
attr_reader :location, :opts, :errors
YAML_WHITELIST_EXTENSION = /.+\.(yml|yaml)$/i.freeze
def initialize(location, opts = {})
@location = location
@opts = opts
@errors = []
validate!
end
def invalid_extension?
!::File.basename(location).match(YAML_WHITELIST_EXTENSION)
end
def valid?
errors.none?
end
def error_message
errors.first
end
def content
raise NotImplementedError, 'subclass must implement fetching raw content'
end
def to_hash
@hash ||= Ci::Config::Loader.new(content).load!
rescue Ci::Config::Loader::FormatError
nil
end
protected
def validate!
validate_location!
validate_content! if errors.none?
validate_hash! if errors.none?
end
def validate_location!
if invalid_extension?
errors.push("Included file `#{location}` does not have YAML extension!")
end
end
def validate_content!
if content.blank?
errors.push("Included file `#{location}` is empty or does not exist!")
end
end
def validate_hash!
if to_hash.blank?
errors.push("Included file `#{location}` does not have valid YAML syntax!")
end
end
end
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Ci
class Config
module External
module File
class Local < Base
include Gitlab::Utils::StrongMemoize
attr_reader :project, :sha
def initialize(location, opts = {})
@project = opts.fetch(:project)
@sha = opts.fetch(:sha)
super
end
def content
strong_memoize(:content) { fetch_local_content }
end
private
def validate_content!
if content.nil?
errors.push("Local file `#{location}` does not exist!")
elsif content.blank?
errors.push("Local file `#{location}` is empty!")
end
end
def fetch_local_content
project.repository.blob_data_at(sha, location)
end
end
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Ci
class Config
module External
module File
class Remote < Base
include Gitlab::Utils::StrongMemoize
def content
strong_memoize(:content) { fetch_remote_content }
end
private
def validate_location!
super
unless ::Gitlab::UrlSanitizer.valid?(location)
errors.push("Remote file `#{location}` does not have a valid address!")
end
end
def fetch_remote_content
begin
response = Gitlab::HTTP.get(location)
rescue SocketError
errors.push("Remote file `#{location}` could not be fetched because of a socket error!")
rescue Timeout::Error
errors.push("Remote file `#{location}` could not be fetched because of a timeout error!")
rescue Gitlab::HTTP::Error
errors.push("Remote file `#{location}` could not be fetched because of HTTP error!")
rescue Gitlab::HTTP::BlockedUrlError => e
errors.push("Remote file could not be fetched because #{e}!")
end
if response&.code.to_i >= 400
errors.push("Remote file `#{location}` could not be fetched because of HTTP code `#{response.code}` error!")
end
response.to_s if errors.none?
end
end
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Ci
class Config
module External
class Mapper
def initialize(values, project, sha)
@locations = Array(values.fetch(:include, []))
@project = project
@sha = sha
end
def process
locations.map { |location| build_external_file(location) }
end
private
attr_reader :locations, :project, :sha
def build_external_file(location)
if ::Gitlab::UrlSanitizer.valid?(location)
External::File::Remote.new(location)
else
External::File::Local.new(location, project: project, sha: sha)
end
end
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Ci
class Config
module External
class Processor
IncludeError = Class.new(StandardError)
def initialize(values, project, sha)
@values = values
@external_files = External::Mapper.new(values, project, sha).process
@content = {}
end
def perform
return @values if @external_files.empty?
validate_external_files!
merge_external_files!
append_inline_content!
remove_include_keyword!
end
private
def validate_external_files!
@external_files.each do |file|
raise IncludeError, file.error_message unless file.valid?
end
end
def merge_external_files!
@external_files.each do |file|
@content.deep_merge!(file.to_hash)
end
end
def append_inline_content!
@content.deep_merge!(@values)
end
def remove_include_keyword!
@content.tap { @content.delete(:include) }
end
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Ci
module External
module File
class Base
YAML_WHITELIST_EXTENSION = /(yml|yaml)$/i.freeze
def initialize(location, opts = {})
@location = location
end
def valid?
location.match(YAML_WHITELIST_EXTENSION) && content
end
def content
raise NotImplementedError, 'content must be implemented and return a string or nil'
end
def error_message
raise NotImplementedError, 'error_message must be implemented and return a string'
end
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Ci
module External
module File
class Local < Base
attr_reader :location, :project, :sha
def initialize(location, opts = {})
super
@project = opts.fetch(:project)
@sha = opts.fetch(:sha)
end
def content
@content ||= fetch_local_content
end
def error_message
"Local file '#{location}' is not valid."
end
private
def fetch_local_content
project.repository.blob_data_at(sha, location)
end
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Ci
module External
module File
class Remote < Base
include Gitlab::Utils::StrongMemoize
attr_reader :location
def content
return @content if defined?(@content)
@content = strong_memoize(:content) do
begin
Gitlab::HTTP.get(location)
rescue Gitlab::HTTP::Error, Timeout::Error, SocketError, Gitlab::HTTP::BlockedUrlError
nil
end
end
end
def error_message
"Remote file '#{location}' is not valid."
end
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Ci
module External
class Mapper
def initialize(values, project, sha)
@locations = Array(values.fetch(:include, []))
@project = project
@sha = sha
end
def process
locations.map { |location| build_external_file(location) }
end
private
attr_reader :locations, :project, :sha
def build_external_file(location)
if ::Gitlab::UrlSanitizer.valid?(location)
Gitlab::Ci::External::File::Remote.new(location)
else
options = { project: project, sha: sha }
Gitlab::Ci::External::File::Local.new(location, options)
end
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Ci
module External
class Processor
FileError = Class.new(StandardError)
def initialize(values, project, sha)
@values = values
@external_files = Gitlab::Ci::External::Mapper.new(values, project, sha).process
@content = {}
end
def perform
return values if external_files.empty?
external_files.each do |external_file|
validate_external_file(external_file)
@content.deep_merge!(content_of(external_file))
end
append_inline_content
remove_include_keyword
end
private
attr_reader :values, :external_files, :content
def validate_external_file(external_file)
unless external_file.valid?
raise FileError, external_file.error_message
end
end
def content_of(external_file)
Gitlab::Ci::Config::Loader.new(external_file.content).load!
end
def append_inline_content
@content.deep_merge!(@values)
end
def remove_include_keyword
content.delete(:include)
content
end
end
end
end
end
......@@ -10,7 +10,7 @@
"karma-coverage": "BABEL_ENV=coverage karma start --single-run true config/karma.config.js",
"karma-start": "BABEL_ENV=karma karma start config/karma.config.js",
"postinstall": "node ./scripts/frontend/postinstall.js",
"prettier-staged": "node ./scripts/frontend/prettier.js",
"prettier-staged": "node ./scripts/frontend/prettier.js check",
"prettier-staged-save": "node ./scripts/frontend/prettier.js save",
"prettier-all": "node ./scripts/frontend/prettier.js check-all",
"prettier-all-save": "node ./scripts/frontend/prettier.js save-all",
......@@ -29,7 +29,6 @@
"autosize": "^4.0.0",
"axios": "^0.17.1",
"babel-loader": "^8.0.4",
"blackst0ne-mermaid": "^7.1.0-fixed",
"bootstrap": "4.1.1",
"brace-expansion": "^1.1.8",
"cache-loader": "^1.2.2",
......@@ -73,6 +72,7 @@
"jszip-utils": "^0.0.2",
"katex": "^0.9.0",
"marked": "^0.3.12",
"mermaid": "^8.0.0-rc.8",
"monaco-editor": "^0.14.3",
"monaco-editor-webpack-plugin": "^1.5.4",
"mousetrap": "^1.4.6",
......@@ -128,7 +128,6 @@
"eslint-plugin-jasmine": "^2.10.1",
"gettext-extractor": "^3.3.2",
"gettext-extractor-vue": "^4.0.1",
"ignore": "^3.3.7",
"istanbul": "^0.4.5",
"jasmine-core": "^2.9.0",
"jasmine-diff": "^0.1.3",
......
......@@ -61,6 +61,8 @@ module QA
def configure_identity(name, email)
run(%Q{git config user.name #{name}})
run(%Q{git config user.email #{email}})
add_credentials_to_netrc
end
def commit_file(name, contents, message)
......@@ -111,17 +113,21 @@ module QA
attr_reader :uri, :username, :password, :known_hosts_file, :private_key_file
def debug?
Runtime::Env.respond_to?(:verbose?) && Runtime::Env.verbose?
end
def ssh_key_set?
!private_key_file.nil?
end
def run(command_str)
command = [env_vars, command_str, '2>&1'].compact.join(' ')
warn "DEBUG: command=[#{command}]" if Runtime::Env.debug?
warn "DEBUG: command=[#{command}]" if debug?
output, _ = Open3.capture2(command)
output = output.chomp.gsub(/\s+$/, '')
warn "DEBUG: output=[#{output}]" if Runtime::Env.debug?
warn "DEBUG: output=[#{output}]" if debug?
output
end
......
......@@ -13,7 +13,8 @@ const execGitCmd = args =>
exec('git', args)
.trim()
.toString()
.split('\n');
.split('\n')
.filter(Boolean);
module.exports = {
getStagedFiles: fileExtensionFilter => {
......
const glob = require('glob');
const prettier = require('prettier');
const fs = require('fs');
const path = require('path');
const prettierIgnore = require('ignore')();
const { getStagedFiles } = require('./frontend_script_utils');
const getStagedFiles = require('./frontend_script_utils').getStagedFiles;
const matchExtensions = ['js', 'vue'];
// This will improve glob performance by excluding certain directories.
// The .prettierignore file will also be respected, but after the glob has executed.
const globIgnore = ['**/node_modules/**', 'vendor/**', 'public/**'];
const readFileAsync = (file, options) =>
new Promise((resolve, reject) => {
fs.readFile(file, options, function(err, data) {
if (err) reject(err);
else resolve(data);
});
});
const writeFileAsync = (file, data, options) =>
new Promise((resolve, reject) => {
fs.writeFile(file, data, options, function(err) {
if (err) reject(err);
else resolve();
});
});
const mode = process.argv[2] || 'check';
const shouldSave = mode === 'save' || mode === 'save-all';
const allFiles = mode === 'check-all' || mode === 'save-all';
let dirPath = process.argv[3] || '';
if (dirPath && dirPath.charAt(dirPath.length - 1) !== '/') dirPath += '/';
const config = {
patterns: ['**/*.js', '**/*.vue', '**/*.scss'],
/*
* The ignore patterns below are just to reduce search time with glob, as it includes the
* folders with the most ignored assets, the actual `.prettierignore` will be used later on
*/
ignore: ['**/node_modules/**', '**/vendor/**', '**/public/**'],
parsers: {
js: 'babylon',
vue: 'vue',
scss: 'css',
},
};
let globDir = process.argv[3] || '';
if (globDir && globDir.charAt(globDir.length - 1) !== '/') globDir += '/';
/*
* Unfortunately the prettier API does not expose support for `.prettierignore` files, they however
* use the ignore package, so we do the same. We simply cannot use the glob package, because
* gitignore style is not compatible with globs ignore style.
*/
prettierIgnore.add(
fs
.readFileSync(path.join(__dirname, '../../', '.prettierignore'))
.toString()
.trim()
.split(/\r?\n/)
console.log(
`Loading all ${allFiles ? '' : 'staged '}files ${globDir ? `within ${globDir} ` : ''}...`
);
const availableExtensions = Object.keys(config.parsers);
console.log(`Loading ${allFiles ? 'All' : 'Selected'} Files ...`);
const globPatterns = matchExtensions.map(ext => `${globDir}**/*.${ext}`);
const matchedFiles = allFiles
? glob.sync(`{${globPatterns.join(',')}}`, { ignore: globIgnore })
: getStagedFiles(globPatterns);
const matchedCount = matchedFiles.length;
const stagedFiles =
allFiles || dirPath ? null : getStagedFiles(availableExtensions.map(ext => `*.${ext}`));
if (stagedFiles) {
if (!stagedFiles.length || (stagedFiles.length === 1 && !stagedFiles[0])) {
console.log('No matching staged files.');
process.exit(1);
}
console.log(`Matching staged Files : ${stagedFiles.length}`);
if (!matchedCount) {
console.log('No files found to process with prettier');
process.exit(0);
}
let didWarn = false;
let didError = false;
let files;
if (allFiles) {
const ignore = config.ignore;
const patterns = config.patterns;
const globPattern = patterns.length > 1 ? `{${patterns.join(',')}}` : `${patterns.join(',')}`;
files = glob.sync(globPattern, { ignore }).filter(f => allFiles || stagedFiles.includes(f));
} else if (dirPath) {
const ignore = config.ignore;
const patterns = config.patterns.map(item => {
return dirPath + item;
});
const globPattern = patterns.length > 1 ? `{${patterns.join(',')}}` : `${patterns.join(',')}`;
files = glob.sync(globPattern, { ignore });
} else {
files = stagedFiles.filter(f => availableExtensions.includes(f.split('.').pop()));
}
files = prettierIgnore.filter(files);
if (!files.length) {
console.log('No Files found to process with Prettier');
process.exit(1);
}
console.log(`${shouldSave ? 'Updating' : 'Checking'} ${files.length} file(s)`);
files.forEach(file => {
try {
prettier
.resolveConfig(file)
.then(options => {
const fileExtension = file.split('.').pop();
Object.assign(options, {
parser: config.parsers[fileExtension],
});
const input = fs.readFileSync(file, 'utf8');
let passedCount = 0;
let failedCount = 0;
let ignoredCount = 0;
console.log(`${shouldSave ? 'Updating' : 'Checking'} ${matchedCount} file(s)`);
const fixCommand = `yarn prettier-${allFiles ? 'all' : 'staged'}-save`;
const warningMessage = `
===============================
GitLab uses Prettier to format all JavaScript code.
Please format each file listed below or run "${fixCommand}"
===============================
`;
const checkFileWithOptions = (filePath, options) =>
readFileAsync(filePath, 'utf8').then(input => {
if (shouldSave) {
const output = prettier.format(input, options);
if (output !== input) {
fs.writeFileSync(file, output, 'utf8');
console.log(`Prettified : ${file}`);
if (input === output) {
passedCount += 1;
} else {
return writeFileAsync(filePath, output, 'utf8').then(() => {
console.log(`Prettified : ${filePath}`);
failedCount += 1;
});
}
} else if (!prettier.check(input, options)) {
} else {
if (prettier.check(input, options)) {
passedCount += 1;
} else {
if (!didWarn) {
console.log(
'\n===============================\nGitLab uses Prettier to format all JavaScript code.\nPlease format each file listed below or run "yarn prettier-staged-save"\n===============================\n'
);
console.log(warningMessage);
didWarn = true;
}
console.log(`Prettify Manually : ${file}`);
console.log(`Prettify Manually : ${filePath}`);
failedCount += 1;
}
}
})
.catch(e => {
console.log(`Error on loading the Config File: ${e.message}`);
process.exit(1);
});
} catch (error) {
didError = true;
console.log(`\n\nError with ${file}: ${error.message}`);
const checkFileWithPrettierConfig = filePath =>
prettier
.getFileInfo(filePath, { ignorePath: '.prettierignore' })
.then(({ ignored, inferredParser }) => {
if (ignored || !inferredParser) {
ignoredCount += 1;
return;
}
});
return prettier.resolveConfig(filePath).then(fileOptions => {
const options = { ...fileOptions, parser: inferredParser };
return checkFileWithOptions(filePath, options);
});
});
Promise.all(matchedFiles.map(checkFileWithPrettierConfig))
.then(() => {
const failAction = shouldSave ? 'fixed' : 'failed';
console.log(
`\nSummary:\n ${matchedCount} files processed (${passedCount} passed, ${failedCount} ${failAction}, ${ignoredCount} ignored)\n`
);
if (didWarn || didError) {
if (didWarn) process.exit(1);
})
.catch(e => {
console.log(`\nAn error occured while processing files with prettier: ${e.message}\n`);
process.exit(1);
}
});
import Vue from 'vue';
import JobContainerItem from '~/jobs/components/job_container_item.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import job from '../mock_data';
describe('JobContainerItem', () => {
const Component = Vue.extend(JobContainerItem);
let vm;
afterEach(() => {
vm.$destroy();
});
const sharedTests = () => {
it('displays a status icon', () => {
expect(vm.$el).toHaveSpriteIcon(job.status.icon);
});
it('displays the job name', () => {
expect(vm.$el).toContainText(job.name);
});
it('displays a link to the job', () => {
const link = vm.$el.querySelector('.js-job-link');
expect(link.href).toBe(job.status.details_path);
});
};
describe('when a job is not active and not retied', () => {
beforeEach(() => {
vm = mountComponent(Component, {
job,
isActive: false,
});
});
sharedTests();
});
describe('when a job is active', () => {
beforeEach(() => {
vm = mountComponent(Component, {
job,
isActive: true,
});
});
sharedTests();
it('displays an arrow', () => {
expect(vm.$el).toHaveSpriteIcon('arrow-right');
});
});
describe('when a job is retried', () => {
beforeEach(() => {
vm = mountComponent(Component, {
job: {
...job,
retried: true,
},
isActive: false,
});
});
sharedTests();
it('displays an icon', () => {
expect(vm.$el).toHaveSpriteIcon('retry');
});
});
});
import { TEST_HOST } from 'spec/test_constants';
const threeWeeksAgo = new Date();
threeWeeksAgo.setDate(threeWeeksAgo.getDate() - 21);
......@@ -19,7 +21,7 @@ export default {
label: 'passed',
group: 'success',
has_details: true,
details_path: '/root/ci-mock/-/jobs/4757',
details_path: `${TEST_HOST}/root/ci-mock/-/jobs/4757`,
favicon:
'/assets/ci_favicons/favicon_status_success-308b4fc054cdd1b68d0865e6cfb7b02e92e3472f201507418f8eddb74ac11a59.png',
action: {
......
# frozen_string_literal: true
require 'fast_spec_helper'
describe Gitlab::Ci::Config::External::File::Base do
subject { described_class.new(location) }
before do
allow_any_instance_of(described_class)
.to receive(:content).and_return('key: value')
end
describe '#valid?' do
context 'when location is not a YAML file' do
let(:location) { 'some/file.txt' }
it { is_expected.not_to be_valid }
end
context 'when location has not a valid naming scheme' do
let(:location) { 'some/file/.yml' }
it { is_expected.not_to be_valid }
end
context 'when location is a valid .yml extension' do
let(:location) { 'some/file/config.yml' }
it { is_expected.to be_valid }
end
context 'when location is a valid .yaml extension' do
let(:location) { 'some/file/config.yaml' }
it { is_expected.to be_valid }
end
context 'when there are YAML syntax errors' do
let(:location) { 'some/file/config.yml' }
before do
allow_any_instance_of(described_class)
.to receive(:content).and_return('invalid_syntax')
end
it 'is not a valid file' do
expect(subject).not_to be_valid
expect(subject.error_message).to match /does not have valid YAML syntax/
end
end
end
end
......@@ -2,7 +2,7 @@
require 'spec_helper'
describe Gitlab::Ci::External::File::Local do
describe Gitlab::Ci::Config::External::File::Local do
let(:project) { create(:project, :repository) }
let(:local_file) { described_class.new(location, { project: project, sha: '12345' }) }
......@@ -72,7 +72,7 @@ describe Gitlab::Ci::External::File::Local do
let(:location) { '/lib/gitlab/ci/templates/non-existent-file.yml' }
it 'should return an error message' do
expect(local_file.error_message).to eq("Local file '#{location}' is not valid.")
expect(local_file.error_message).to eq("Local file `#{location}` does not exist!")
end
end
end
......@@ -2,7 +2,7 @@
require 'spec_helper'
describe Gitlab::Ci::External::File::Remote do
describe Gitlab::Ci::Config::External::File::Remote do
let(:remote_file) { described_class.new(location) }
let(:location) { 'https://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' }
let(:remote_file_content) do
......@@ -105,10 +105,53 @@ describe Gitlab::Ci::External::File::Remote do
end
describe "#error_message" do
subject { remote_file.error_message }
context 'when remote file location is not valid' do
let(:location) { 'not-valid://gitlab.com/gitlab-org/gitlab-ce/blob/1234/.gitlab-ci-1.yml' }
it 'should return an error message' do
expect(remote_file.error_message).to eq("Remote file '#{location}' is not valid.")
it 'returns an error message describing invalid address' do
expect(subject).to match /does not have a valid address!/
end
end
context 'when timeout error has been raised' do
before do
WebMock.stub_request(:get, location).to_timeout
end
it 'should returns error message about a timeout' do
expect(subject).to match /could not be fetched because of a timeout error!/
end
end
context 'when HTTP error has been raised' do
before do
WebMock.stub_request(:get, location).to_raise(Gitlab::HTTP::Error)
end
it 'should returns error message about a HTTP error' do
expect(subject).to match /could not be fetched because of HTTP error!/
end
end
context 'when response has 404 status' do
before do
WebMock.stub_request(:get, location).to_return(body: remote_file_content, status: 404)
end
it 'should returns error message about a timeout' do
expect(subject).to match /could not be fetched because of HTTP code `404` error!/
end
end
context 'when the URL is blocked' do
let(:location) { 'http://127.0.0.1/some/path/to/config.yaml' }
it 'should include details about blocked URL' do
expect(subject).to eq "Remote file could not be fetched because URL '#{location}' " \
'is blocked: Requests to localhost are not allowed!'
end
end
end
end
......@@ -2,7 +2,7 @@
require 'spec_helper'
describe Gitlab::Ci::External::Mapper do
describe Gitlab::Ci::Config::External::Mapper do
let(:project) { create(:project, :repository) }
let(:file_content) do
<<~HEREDOC
......@@ -27,7 +27,8 @@ describe Gitlab::Ci::External::Mapper do
end
it 'returns File instances' do
expect(subject.first).to be_an_instance_of(Gitlab::Ci::External::File::Local)
expect(subject.first)
.to be_an_instance_of(Gitlab::Ci::Config::External::File::Local)
end
end
......@@ -49,7 +50,8 @@ describe Gitlab::Ci::External::Mapper do
end
it 'returns File instances' do
expect(subject.first).to be_an_instance_of(Gitlab::Ci::External::File::Remote)
expect(subject.first)
.to be_an_instance_of(Gitlab::Ci::Config::External::File::Remote)
end
end
end
......
......@@ -2,7 +2,7 @@
require 'spec_helper'
describe Gitlab::Ci::External::Processor do
describe Gitlab::Ci::Config::External::Processor do
let(:project) { create(:project, :repository) }
let(:processor) { described_class.new(values, project, '12345') }
......@@ -20,8 +20,8 @@ describe Gitlab::Ci::External::Processor do
it 'should raise an error' do
expect { processor.perform }.to raise_error(
described_class::FileError,
"Local file '/lib/gitlab/ci/templates/non-existent-file.yml' is not valid."
described_class::IncludeError,
"Local file `/lib/gitlab/ci/templates/non-existent-file.yml` does not exist!"
)
end
end
......@@ -36,8 +36,8 @@ describe Gitlab::Ci::External::Processor do
it 'should raise an error' do
expect { processor.perform }.to raise_error(
described_class::FileError,
"Remote file '#{remote_file}' is not valid."
described_class::IncludeError,
"Remote file `#{remote_file}` could not be fetched because of a socket error!"
)
end
end
......@@ -92,7 +92,8 @@ describe Gitlab::Ci::External::Processor do
end
before do
allow_any_instance_of(Gitlab::Ci::External::File::Local).to receive(:fetch_local_content).and_return(local_file_content)
allow_any_instance_of(Gitlab::Ci::Config::External::File::Local)
.to receive(:fetch_local_content).and_return(local_file_content)
end
it 'should append the file to the values' do
......@@ -131,7 +132,10 @@ describe Gitlab::Ci::External::Processor do
before do
local_file_content = File.read(Rails.root.join('spec/fixtures/gitlab/ci/external_files/.gitlab-ci-template-1.yml'))
allow_any_instance_of(Gitlab::Ci::External::File::Local).to receive(:fetch_local_content).and_return(local_file_content)
allow_any_instance_of(Gitlab::Ci::Config::External::File::Local)
.to receive(:fetch_local_content).and_return(local_file_content)
WebMock.stub_request(:get, remote_file).to_return(body: remote_file_content)
end
......@@ -150,11 +154,15 @@ describe Gitlab::Ci::External::Processor do
let(:local_file_content) { 'invalid content file ////' }
before do
allow_any_instance_of(Gitlab::Ci::External::File::Local).to receive(:fetch_local_content).and_return(local_file_content)
allow_any_instance_of(Gitlab::Ci::Config::External::File::Local)
.to receive(:fetch_local_content).and_return(local_file_content)
end
it 'should raise an error' do
expect { processor.perform }.to raise_error(Gitlab::Ci::Config::Loader::FormatError)
expect { processor.perform }.to raise_error(
described_class::IncludeError,
"Included file `/lib/gitlab/ci/templates/template.yml` does not have valid YAML syntax!"
)
end
end
......
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment