Commit 62a5cc71 authored by Zeger-Jan van de Weg's avatar Zeger-Jan van de Weg

Merge branch 'master' into zj-auto-devops-table

parents 492eaece 0c2dc727
...@@ -247,7 +247,6 @@ setup-test-env: ...@@ -247,7 +247,6 @@ setup-test-env:
script: script:
- node --version - node --version
- yarn install --frozen-lockfile --cache-folder .yarn-cache - yarn install --frozen-lockfile --cache-folder .yarn-cache
- bundle exec rake gettext:po_to_json
- bundle exec rake gitlab:assets:compile - bundle exec rake gitlab:assets:compile
- bundle exec ruby -Ispec -e 'require "spec_helper" ; TestEnv.init' - bundle exec ruby -Ispec -e 'require "spec_helper" ; TestEnv.init'
- scripts/gitaly-test-build # Do not use 'bundle exec' here - scripts/gitaly-test-build # Do not use 'bundle exec' here
...@@ -412,7 +411,6 @@ db:migrate:reset-mysql: ...@@ -412,7 +411,6 @@ db:migrate:reset-mysql:
.migration-paths: &migration-paths .migration-paths: &migration-paths
<<: *dedicated-runner <<: *dedicated-runner
<<: *only-canonical-masters
<<: *pull-cache <<: *pull-cache
stage: test stage: test
variables: variables:
...@@ -496,7 +494,6 @@ gitlab:assets:compile: ...@@ -496,7 +494,6 @@ gitlab:assets:compile:
NO_COMPRESSION: "true" NO_COMPRESSION: "true"
script: script:
- yarn install --frozen-lockfile --production --cache-folder .yarn-cache - yarn install --frozen-lockfile --production --cache-folder .yarn-cache
- bundle exec rake gettext:po_to_json
- bundle exec rake gitlab:assets:compile - bundle exec rake gitlab:assets:compile
artifacts: artifacts:
name: webpack-report name: webpack-report
......
...@@ -31,7 +31,7 @@ gem 'omniauth-cas3', '~> 1.1.4' ...@@ -31,7 +31,7 @@ gem 'omniauth-cas3', '~> 1.1.4'
gem 'omniauth-facebook', '~> 4.0.0' gem 'omniauth-facebook', '~> 4.0.0'
gem 'omniauth-github', '~> 1.1.1' gem 'omniauth-github', '~> 1.1.1'
gem 'omniauth-gitlab', '~> 1.0.2' gem 'omniauth-gitlab', '~> 1.0.2'
gem 'omniauth-google-oauth2', '~> 0.4.1' gem 'omniauth-google-oauth2', '~> 0.5.2'
gem 'omniauth-kerberos', '~> 0.3.0', group: :kerberos gem 'omniauth-kerberos', '~> 0.3.0', group: :kerberos
gem 'omniauth-oauth2-generic', '~> 0.2.2' gem 'omniauth-oauth2-generic', '~> 0.2.2'
gem 'omniauth-saml', '~> 1.7.0' gem 'omniauth-saml', '~> 1.7.0'
...@@ -324,7 +324,7 @@ group :development, :test do ...@@ -324,7 +324,7 @@ group :development, :test do
# Generate Fake data # Generate Fake data
gem 'ffaker', '~> 2.4' gem 'ffaker', '~> 2.4'
gem 'capybara', '~> 2.6.2' gem 'capybara', '~> 2.15.0'
gem 'capybara-screenshot', '~> 1.0.0' gem 'capybara-screenshot', '~> 1.0.0'
gem 'poltergeist', '~> 1.9.0' gem 'poltergeist', '~> 1.9.0'
...@@ -397,7 +397,7 @@ group :ed25519 do ...@@ -397,7 +397,7 @@ group :ed25519 do
end end
# Gitaly GRPC client # Gitaly GRPC client
gem 'gitaly-proto', '~> 0.32.0', require: 'gitaly' gem 'gitaly-proto', '~> 0.33.0', require: 'gitaly'
gem 'toml-rb', '~> 0.3.15', require: false gem 'toml-rb', '~> 0.3.15', require: false
......
...@@ -100,9 +100,9 @@ GEM ...@@ -100,9 +100,9 @@ GEM
bundler (~> 1.2) bundler (~> 1.2)
thor (~> 0.18) thor (~> 0.18)
byebug (9.0.6) byebug (9.0.6)
capybara (2.6.2) capybara (2.15.1)
addressable addressable
mime-types (>= 1.16) mini_mime (>= 0.1.3)
nokogiri (>= 1.3.3) nokogiri (>= 1.3.3)
rack (>= 1.0.0) rack (>= 1.0.0)
rack-test (>= 0.5.4) rack-test (>= 0.5.4)
...@@ -275,7 +275,7 @@ GEM ...@@ -275,7 +275,7 @@ GEM
po_to_json (>= 1.0.0) po_to_json (>= 1.0.0)
rails (>= 3.2.0) rails (>= 3.2.0)
gherkin-ruby (0.3.2) gherkin-ruby (0.3.2)
gitaly-proto (0.32.0) gitaly-proto (0.33.0)
google-protobuf (~> 3.1) google-protobuf (~> 3.1)
grpc (~> 1.0) grpc (~> 1.0)
github-linguist (4.7.6) github-linguist (4.7.6)
...@@ -479,6 +479,7 @@ GEM ...@@ -479,6 +479,7 @@ GEM
method_source (0.8.2) method_source (0.8.2)
mime-types (2.99.3) mime-types (2.99.3)
mimemagic (0.3.0) mimemagic (0.3.0)
mini_mime (0.1.4)
mini_portile2 (2.2.0) mini_portile2 (2.2.0)
minitest (5.7.0) minitest (5.7.0)
mmap2 (2.2.7) mmap2 (2.2.7)
...@@ -529,8 +530,8 @@ GEM ...@@ -529,8 +530,8 @@ GEM
omniauth-gitlab (1.0.2) omniauth-gitlab (1.0.2)
omniauth (~> 1.0) omniauth (~> 1.0)
omniauth-oauth2 (~> 1.0) omniauth-oauth2 (~> 1.0)
omniauth-google-oauth2 (0.4.1) omniauth-google-oauth2 (0.5.2)
jwt (~> 1.5.2) jwt (~> 1.5)
multi_json (~> 1.3) multi_json (~> 1.3)
omniauth (>= 1.1.1) omniauth (>= 1.1.1)
omniauth-oauth2 (>= 1.3.1) omniauth-oauth2 (>= 1.3.1)
...@@ -948,7 +949,7 @@ GEM ...@@ -948,7 +949,7 @@ GEM
expression_parser expression_parser
rinku rinku
xml-simple (1.1.5) xml-simple (1.1.5)
xpath (2.0.0) xpath (2.1.0)
nokogiri (~> 1.3) nokogiri (~> 1.3)
PLATFORMS PLATFORMS
...@@ -979,7 +980,7 @@ DEPENDENCIES ...@@ -979,7 +980,7 @@ DEPENDENCIES
browser (~> 2.2) browser (~> 2.2)
bullet (~> 5.5.0) bullet (~> 5.5.0)
bundler-audit (~> 0.5.0) bundler-audit (~> 0.5.0)
capybara (~> 2.6.2) capybara (~> 2.15.0)
capybara-screenshot (~> 1.0.0) capybara-screenshot (~> 1.0.0)
carrierwave (~> 1.1) carrierwave (~> 1.1)
charlock_holmes (~> 0.7.5) charlock_holmes (~> 0.7.5)
...@@ -1021,7 +1022,7 @@ DEPENDENCIES ...@@ -1021,7 +1022,7 @@ DEPENDENCIES
gettext (~> 3.2.2) gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.2.0) gettext_i18n_rails_js (~> 1.2.0)
gitaly-proto (~> 0.32.0) gitaly-proto (~> 0.33.0)
github-linguist (~> 4.7.0) github-linguist (~> 4.7.0)
gitlab-flowdock-git-hook (~> 1.0.1) gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-markup (~> 1.5.1) gitlab-markup (~> 1.5.1)
...@@ -1075,7 +1076,7 @@ DEPENDENCIES ...@@ -1075,7 +1076,7 @@ DEPENDENCIES
omniauth-facebook (~> 4.0.0) omniauth-facebook (~> 4.0.0)
omniauth-github (~> 1.1.1) omniauth-github (~> 1.1.1)
omniauth-gitlab (~> 1.0.2) omniauth-gitlab (~> 1.0.2)
omniauth-google-oauth2 (~> 0.4.1) omniauth-google-oauth2 (~> 0.5.2)
omniauth-kerberos (~> 0.3.0) omniauth-kerberos (~> 0.3.0)
omniauth-oauth2-generic (~> 0.2.2) omniauth-oauth2-generic (~> 0.2.2)
omniauth-saml (~> 1.7.0) omniauth-saml (~> 1.7.0)
......
...@@ -175,7 +175,7 @@ GitLabDropdownFilter = (function() { ...@@ -175,7 +175,7 @@ GitLabDropdownFilter = (function() {
elements.show().removeClass('option-hidden'); elements.show().removeClass('option-hidden');
} }
elements.parent().find('.dropdown-menu-empty-link').toggleClass('hidden', elements.is(':visible')); elements.parent().find('.dropdown-menu-empty-item').toggleClass('hidden', elements.is(':visible'));
} }
}; };
...@@ -247,7 +247,7 @@ GitLabDropdown = (function() { ...@@ -247,7 +247,7 @@ GitLabDropdown = (function() {
currentIndex = -1; currentIndex = -1;
NON_SELECTABLE_CLASSES = '.divider, .separator, .dropdown-header, .dropdown-menu-empty-link'; NON_SELECTABLE_CLASSES = '.divider, .separator, .dropdown-header, .dropdown-menu-empty-item';
SELECTABLE_CLASSES = ".dropdown-content li:not(" + NON_SELECTABLE_CLASSES + ", .option-hidden)"; SELECTABLE_CLASSES = ".dropdown-content li:not(" + NON_SELECTABLE_CLASSES + ", .option-hidden)";
...@@ -702,7 +702,7 @@ GitLabDropdown = (function() { ...@@ -702,7 +702,7 @@ GitLabDropdown = (function() {
GitLabDropdown.prototype.noResults = function() { GitLabDropdown.prototype.noResults = function() {
var html; var html;
return html = '<li class="dropdown-menu-empty-link"><a href="#" class="is-focused">No matching results</a></li>'; return '<li class="dropdown-menu-empty-item"><a>No matching results</a></li>';
}; };
GitLabDropdown.prototype.rowClicked = function(el) { GitLabDropdown.prototype.rowClicked = function(el) {
......
...@@ -363,7 +363,7 @@ ...@@ -363,7 +363,7 @@
restoreMenu() { restoreMenu() {
var html; var html;
html = "<ul> <li><a class='dropdown-menu-empty-link is-focused'>Loading...</a></li> </ul>"; html = '<ul><li class="dropdown-menu-empty-item"><a>Loading...</a></li></ul>';
return this.dropdownContent.html(html); return this.dropdownContent.html(html);
} }
......
...@@ -163,12 +163,6 @@ ...@@ -163,12 +163,6 @@
} }
} }
&.dropdown-menu-empty-link {
&.is-focused {
background-color: $dropdown-empty-row-bg;
}
}
&.dropdown-menu-user-link { &.dropdown-menu-user-link {
line-height: 16px; line-height: 16px;
} }
...@@ -256,6 +250,13 @@ ...@@ -256,6 +250,13 @@
@include dropdown-link; @include dropdown-link;
} }
.dropdown-menu-empty-item a {
&:hover,
&:focus {
background-color: transparent;
}
}
.dropdown-header { .dropdown-header {
color: $gl-text-color-secondary; color: $gl-text-color-secondary;
font-size: 13px; font-size: 13px;
...@@ -800,6 +801,13 @@ ...@@ -800,6 +801,13 @@
} }
} }
} }
&.dropdown-menu-empty-item a {
&:hover,
&:focus {
background-color: transparent;
}
}
} }
&.dropdown-menu-selectable { &.dropdown-menu-selectable {
......
...@@ -515,16 +515,6 @@ header.navbar-gitlab-new { ...@@ -515,16 +515,6 @@ header.navbar-gitlab-new {
} }
} }
.top-area {
.nav-controls-new-nav {
.dropdown {
@media (min-width: $screen-sm-min) {
margin-right: 0;
}
}
}
}
.btn-sign-in { .btn-sign-in {
margin-top: 3px; margin-top: 3px;
background-color: $indigo-100; background-color: $indigo-100;
......
...@@ -578,12 +578,12 @@ ...@@ -578,12 +578,12 @@
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
position: -webkit-sticky; position: -webkit-sticky;
position: sticky; position: sticky;
top: 34px; top: 24px;
background-color: $white-light; background-color: $white-light;
z-index: 190; z-index: 190;
&.diff-files-changed-merge-request { &.diff-files-changed-merge-request {
top: 84px; top: 76px;
} }
+ .files, + .files,
...@@ -614,6 +614,14 @@ ...@@ -614,6 +614,14 @@
} }
} }
@media (min-width: $screen-sm-min) {
.with-performance-bar {
.diff-files-changed.diff-files-changed-merge-request {
top: 76px + $performance-bar-height;
}
}
}
.diff-file-changes { .diff-file-changes {
width: 450px; width: 450px;
z-index: 150; z-index: 150;
......
...@@ -220,7 +220,7 @@ ...@@ -220,7 +220,7 @@
.right-sidebar { .right-sidebar {
position: absolute; position: absolute;
top: $header-height; top: $new-navbar-height;
bottom: 0; bottom: 0;
right: 0; right: 0;
transition: width .3s; transition: width .3s;
...@@ -230,7 +230,7 @@ ...@@ -230,7 +230,7 @@
.issuable-sidebar { .issuable-sidebar {
width: calc(100% + 100px); width: calc(100% + 100px);
height: calc(100% - #{$header-height}); height: calc(100% - #{$new-navbar-height});
overflow-y: scroll; overflow-y: scroll;
overflow-x: hidden; overflow-x: hidden;
-webkit-overflow-scrolling: touch; -webkit-overflow-scrolling: touch;
...@@ -479,10 +479,10 @@ ...@@ -479,10 +479,10 @@
} }
.with-performance-bar .right-sidebar { .with-performance-bar .right-sidebar {
top: $header-height + $performance-bar-height; top: $new-navbar-height + $performance-bar-height;
.issuable-sidebar { .issuable-sidebar {
height: calc(100% - #{$header-height} - #{$performance-bar-height}); height: calc(100% - #{$new-navbar-height} - #{$performance-bar-height});
} }
} }
......
...@@ -645,7 +645,7 @@ ...@@ -645,7 +645,7 @@
} }
.merge-request-tabs-holder { .merge-request-tabs-holder {
top: $header-height; top: $new-navbar-height;
z-index: 200; z-index: 200;
background-color: $white-light; background-color: $white-light;
border-bottom: 1px solid $border-color; border-bottom: 1px solid $border-color;
...@@ -675,7 +675,7 @@ ...@@ -675,7 +675,7 @@
} }
.with-performance-bar .merge-request-tabs-holder { .with-performance-bar .merge-request-tabs-holder {
top: $header-height + $performance-bar-height; top: $new-navbar-height + $performance-bar-height;
} }
.merge-request-tabs { .merge-request-tabs {
......
...@@ -20,8 +20,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController ...@@ -20,8 +20,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
def usage_data def usage_data
respond_to do |format| respond_to do |format|
format.html do format.html do
usage_data = Gitlab::UsageData.data usage_data_json = JSON.pretty_generate(Gitlab::UsageData.data)
usage_data_json = params[:pretty] ? JSON.pretty_generate(usage_data) : usage_data.to_json
render html: Gitlab::Highlight.highlight('payload.json', usage_data_json) render html: Gitlab::Highlight.highlight('payload.json', usage_data_json)
end end
......
...@@ -99,7 +99,9 @@ module TreeHelper ...@@ -99,7 +99,9 @@ module TreeHelper
end end
# returns the relative path of the first subdir that doesn't have only one directory descendant # returns the relative path of the first subdir that doesn't have only one directory descendant
def flatten_tree(tree) def flatten_tree(root_path, tree)
return tree.flat_path.sub(/\A#{root_path}\//, '') if tree.flat_path.present?
subtree = Gitlab::Git::Tree.where(@repository, @commit.id, tree.path) subtree = Gitlab::Git::Tree.where(@repository, @commit.id, tree.path)
if subtree.count == 1 && subtree.first.dir? if subtree.count == 1 && subtree.first.dir?
return tree_join(tree.name, flatten_tree(subtree.first)) return tree_join(tree.name, flatten_tree(subtree.first))
......
...@@ -7,4 +7,4 @@ ...@@ -7,4 +7,4 @@
= succeed '.' do = succeed '.' do
= link_to 'application settings', admin_application_settings_path(anchor: 'usage-statistics') = link_to 'application settings', admin_application_settings_path(anchor: 'usage-statistics')
%pre.usage-data.js-syntax-highlight.code.highlight{ data: { endpoint: usage_data_admin_application_settings_path(format: :html, pretty: true) } } %pre.usage-data.js-syntax-highlight.code.highlight{ data: { endpoint: usage_data_admin_application_settings_path(format: :html) } }
- if current_user.can_create_group?
- content_for :breadcrumbs_extra do
= link_to "New group", new_group_path, class: "btn btn-new"
.top-area .top-area
%ul.nav-links %ul.nav-links
= nav_link(page: dashboard_groups_path) do = nav_link(page: dashboard_groups_path) do
...@@ -10,8 +6,8 @@ ...@@ -10,8 +6,8 @@
= nav_link(page: explore_groups_path) do = nav_link(page: explore_groups_path) do
= link_to explore_groups_path, title: 'Explore public groups' do = link_to explore_groups_path, title: 'Explore public groups' do
Explore public groups Explore public groups
.nav-controls.nav-controls-new-nav .nav-controls
= render 'shared/groups/search_form' = render 'shared/groups/search_form'
= render 'shared/groups/dropdown' = render 'shared/groups/dropdown'
- if current_user.can_create_group? - if current_user.can_create_group?
= link_to "New group", new_group_path, class: "btn btn-new visible-xs" = link_to "New group", new_group_path, class: "btn btn-new"
= content_for :flash_message do = content_for :flash_message do
= render 'shared/project_limit' = render 'shared/project_limit'
- if current_user.can_create_project?
- content_for :breadcrumbs_extra do
= link_to "New project", new_project_path, class: 'btn btn-new'
.top-area.scrolling-tabs-container.inner-page-scroll-tabs .top-area.scrolling-tabs-container.inner-page-scroll-tabs
.fade-left= icon('angle-left') .fade-left= icon('angle-left')
.fade-right= icon('angle-right') .fade-right= icon('angle-right')
...@@ -19,8 +15,8 @@ ...@@ -19,8 +15,8 @@
= link_to explore_root_path, title: 'Explore', data: {placement: 'right'} do = link_to explore_root_path, title: 'Explore', data: {placement: 'right'} do
Explore projects Explore projects
.nav-controls.nav-controls-new-nav .nav-controls
= render 'shared/projects/search_form' = render 'shared/projects/search_form'
= render 'shared/projects/dropdown' = render 'shared/projects/dropdown'
- if current_user.can_create_project? - if current_user.can_create_project?
= link_to "New project", new_project_path, class: "btn btn-new visible-xs" = link_to "New project", new_project_path, class: "btn btn-new"
- if current_user
- content_for :breadcrumbs_extra do
= link_to "New snippet", new_snippet_path, class: "btn btn-new", title: "New snippet"
.top-area .top-area
%ul.nav-links %ul.nav-links
= nav_link(page: dashboard_snippets_path, html_options: {class: 'home'}) do = nav_link(page: dashboard_snippets_path, html_options: {class: 'home'}) do
...@@ -10,3 +6,7 @@ ...@@ -10,3 +6,7 @@
= nav_link(page: explore_snippets_path) do = nav_link(page: explore_snippets_path) do
= link_to explore_snippets_path, title: 'Explore snippets', data: {placement: 'right'} do = link_to explore_snippets_path, title: 'Explore snippets', data: {placement: 'right'} do
Explore Snippets Explore Snippets
- if current_user
.nav-controls.hidden-xs
= link_to "New snippet", new_snippet_path, class: "btn btn-new", title: "New snippet"
...@@ -4,14 +4,9 @@ ...@@ -4,14 +4,9 @@
= content_for :meta_tags do = content_for :meta_tags do
= auto_discovery_link_tag(:atom, params.merge(rss_url_options), title: "#{current_user.name} issues") = auto_discovery_link_tag(:atom, params.merge(rss_url_options), title: "#{current_user.name} issues")
- content_for :breadcrumbs_extra do
= link_to params.merge(rss_url_options), class: 'btn has-tooltip append-right-10', title: 'Subscribe' do
= icon('rss')
= render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", with_feature_enabled: 'issues', type: :issues
.top-area .top-area
= render 'shared/issuable/nav', type: :issues = render 'shared/issuable/nav', type: :issues
.nav-controls.visible-xs .nav-controls
= link_to params.merge(rss_url_options), class: 'btn has-tooltip', title: 'Subscribe' do = link_to params.merge(rss_url_options), class: 'btn has-tooltip', title: 'Subscribe' do
= icon('rss') = icon('rss')
= render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", with_feature_enabled: 'issues', type: :issues = render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", with_feature_enabled: 'issues', type: :issues
......
...@@ -2,12 +2,9 @@ ...@@ -2,12 +2,9 @@
- page_title "Merge Requests" - page_title "Merge Requests"
- header_title "Merge Requests", merge_requests_dashboard_path(assignee_id: current_user.id) - header_title "Merge Requests", merge_requests_dashboard_path(assignee_id: current_user.id)
- content_for :breadcrumbs_extra do
= render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request", with_feature_enabled: 'merge_requests', type: :merge_requests
.top-area .top-area
= render 'shared/issuable/nav', type: :merge_requests = render 'shared/issuable/nav', type: :merge_requests
.nav-controls.visible-xs .nav-controls
= render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request", with_feature_enabled: 'merge_requests', type: :merge_requests = render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request", with_feature_enabled: 'merge_requests', type: :merge_requests
= render 'shared/issuable/filter', type: :merge_requests = render 'shared/issuable/filter', type: :merge_requests
......
...@@ -2,13 +2,10 @@ ...@@ -2,13 +2,10 @@
- page_title 'Milestones' - page_title 'Milestones'
- header_title 'Milestones', dashboard_milestones_path - header_title 'Milestones', dashboard_milestones_path
- content_for :breadcrumbs_extra do
= render 'shared/new_project_item_select', path: 'milestones/new', label: 'New milestone', include_groups: true, type: :milestones
.top-area .top-area
= render 'shared/milestones_filter', counts: @milestone_states = render 'shared/milestones_filter', counts: @milestone_states
.nav-controls.visible-xs .nav-controls
= render 'shared/new_project_item_select', path: 'milestones/new', label: 'New milestone', include_groups: true, type: :milestones = render 'shared/new_project_item_select', path: 'milestones/new', label: 'New milestone', include_groups: true, type: :milestones
.milestones .milestones
......
...@@ -8,18 +8,10 @@ ...@@ -8,18 +8,10 @@
= webpack_bundle_tag 'common_vue' = webpack_bundle_tag 'common_vue'
= webpack_bundle_tag 'filtered_search' = webpack_bundle_tag 'filtered_search'
- if group_issues_exists
- content_for :breadcrumbs_extra do
= link_to params.merge(rss_url_options), class: 'btn btn-default append-right-10' do
= icon('rss')
%span.icon-label
Subscribe
= render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", type: :issues
- if group_issues_exists - if group_issues_exists
.top-area .top-area
= render 'shared/issuable/nav', type: :issues = render 'shared/issuable/nav', type: :issues
.nav-controls.visible-xs .nav-controls
= link_to params.merge(rss_url_options), class: 'btn' do = link_to params.merge(rss_url_options), class: 'btn' do
= icon('rss') = icon('rss')
%span.icon-label %span.icon-label
......
- page_title 'Labels' - page_title 'Labels'
- if can?(current_user, :admin_label, @group)
- content_for :breadcrumbs_extra do
= link_to "New label", new_group_label_path(@group), class: "btn btn-new"
= render "groups/head_issues" = render "groups/head_issues"
...@@ -10,7 +7,7 @@ ...@@ -10,7 +7,7 @@
.nav-text .nav-text
Labels can be applied to issues and merge requests. Group labels are available for any project within the group. Labels can be applied to issues and merge requests. Group labels are available for any project within the group.
.nav-controls.visible-xs .nav-controls
- if can?(current_user, :admin_label, @group) - if can?(current_user, :admin_label, @group)
= link_to "New label", new_group_label_path(@group), class: "btn btn-new" = link_to "New label", new_group_label_path(@group), class: "btn btn-new"
......
...@@ -4,17 +4,13 @@ ...@@ -4,17 +4,13 @@
= webpack_bundle_tag 'common_vue' = webpack_bundle_tag 'common_vue'
= webpack_bundle_tag 'filtered_search' = webpack_bundle_tag 'filtered_search'
- if current_user
- content_for :breadcrumbs_extra do
= render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request", type: :merge_requests
- if @group_merge_requests.empty? - if @group_merge_requests.empty?
= render 'shared/empty_states/merge_requests', project_select_button: true = render 'shared/empty_states/merge_requests', project_select_button: true
- else - else
.top-area .top-area
= render 'shared/issuable/nav', type: :merge_requests = render 'shared/issuable/nav', type: :merge_requests
- if current_user - if current_user
.nav-controls.visible-xs .nav-controls
= render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request", type: :merge_requests = render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request", type: :merge_requests
= render 'shared/issuable/search_bar', type: :merge_requests = render 'shared/issuable/search_bar', type: :merge_requests
......
- page_title "Milestones" - page_title "Milestones"
- if can?(current_user, :admin_milestones, @group)
- content_for :breadcrumbs_extra do
= link_to "New milestone", new_group_milestone_path(@group), class: "btn btn-new"
= render "groups/head_issues" = render "groups/head_issues"
.top-area .top-area
= render 'shared/milestones_filter', counts: @milestone_states = render 'shared/milestones_filter', counts: @milestone_states
.nav-controls.visible-xs .nav-controls
- if can?(current_user, :admin_milestones, @group) - if can?(current_user, :admin_milestones, @group)
= link_to "New milestone", new_group_milestone_path(@group), class: "btn btn-new" = link_to "New milestone", new_group_milestone_path(@group), class: "btn btn-new"
......
...@@ -17,8 +17,8 @@ ...@@ -17,8 +17,8 @@
.dropdown-menu.dropdown-select .dropdown-menu.dropdown-select
= dropdown_content do = dropdown_content do
%ul %ul
%li %li.dropdown-menu-empty-item
%a.is-focused.dropdown-menu-empty-link %a
Loading... Loading...
= dropdown_loading = dropdown_loading
%i.search-icon %i.search-icon
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
- hide_top_links = @hide_top_links || false - hide_top_links = @hide_top_links || false
%nav.breadcrumbs{ role: "navigation", class: [container, @content_class] } %nav.breadcrumbs{ role: "navigation", class: [container, @content_class] }
.breadcrumbs-container{ class: [container, @content_class] } .breadcrumbs-container
- if defined?(@left_sidebar) - if defined?(@left_sidebar)
= button_tag class: 'toggle-mobile-nav', type: 'button' do = button_tag class: 'toggle-mobile-nav', type: 'button' do
%span.sr-only Open sidebar %span.sr-only Open sidebar
...@@ -17,6 +17,4 @@ ...@@ -17,6 +17,4 @@
= render "layouts/nav/breadcrumbs/collapsed_dropdown", location: :after = render "layouts/nav/breadcrumbs/collapsed_dropdown", location: :after
%li %li
%h2.breadcrumbs-sub-title= @breadcrumb_title %h2.breadcrumbs-sub-title= @breadcrumb_title
- if content_for?(:breadcrumbs_extra)
.breadcrumbs-extra.hidden-xs= yield :breadcrumbs_extra
= yield :header_content = yield :header_content
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
- @content_class = "limit-container-width" unless fluid_layout - @content_class = "limit-container-width" unless fluid_layout
= render 'profiles/head' = render 'profiles/head'
= bootstrap_form_for @user, url: profile_path, method: :put, html: { multipart: true, class: 'edit-user prepend-top-default' }, authenticity_token: true do |f| = bootstrap_form_for @user, url: profile_path, method: :put, html: { multipart: true, class: 'edit-user prepend-top-default js-quick-submit' }, authenticity_token: true do |f|
= form_errors(@user) = form_errors(@user)
.row .row
......
...@@ -29,6 +29,6 @@ ...@@ -29,6 +29,6 @@
+#{diff_file.added_lines} +#{diff_file.added_lines}
%span.cred< %span.cred<
\-#{diff_file.removed_lines} \-#{diff_file.removed_lines}
%li.dropdown-menu-empty-link.hidden %li.dropdown-menu-empty-item.hidden
%a{ href: "#" } %a
No files found. No files found.
...@@ -13,14 +13,11 @@ ...@@ -13,14 +13,11 @@
= content_for :meta_tags do = content_for :meta_tags do
= auto_discovery_link_tag(:atom, params.merge(rss_url_options), title: "#{@project.name} issues") = auto_discovery_link_tag(:atom, params.merge(rss_url_options), title: "#{@project.name} issues")
- content_for :breadcrumbs_extra do
= render "projects/issues/nav_btns"
- if project_issues(@project).exists? - if project_issues(@project).exists?
%div{ class: (container_class) } %div{ class: (container_class) }
.top-area .top-area
= render 'shared/issuable/nav', type: :issues = render 'shared/issuable/nav', type: :issues
.nav-controls.visible-xs .nav-controls
= render "projects/issues/nav_btns" = render "projects/issues/nav_btns"
= render 'shared/issuable/search_bar', type: :issues = render 'shared/issuable/search_bar', type: :issues
......
...@@ -3,10 +3,6 @@ ...@@ -3,10 +3,6 @@
- hide_class = '' - hide_class = ''
- can_admin_label = can?(current_user, :admin_label, @project) - can_admin_label = can?(current_user, :admin_label, @project)
- if can?(current_user, :admin_label, @project)
- content_for :breadcrumbs_extra do
= link_to "New label", new_namespace_project_label_path(@project.namespace, @project), class: "btn btn-new"
= render "shared/mr_head" = render "shared/mr_head"
- if @labels.exists? || @prioritized_labels.exists? - if @labels.exists? || @prioritized_labels.exists?
...@@ -18,7 +14,7 @@ ...@@ -18,7 +14,7 @@
Star a label to make it a priority label. Order the prioritized labels to change their relative priority, by dragging. Star a label to make it a priority label. Order the prioritized labels to change their relative priority, by dragging.
- if can_admin_label - if can_admin_label
.nav-controls.visible-xs .nav-controls
= link_to new_project_label_path(@project), class: "btn btn-new" do = link_to new_project_label_path(@project), class: "btn btn-new" do
New label New label
......
...@@ -12,16 +12,13 @@ ...@@ -12,16 +12,13 @@
= webpack_bundle_tag 'common_vue' = webpack_bundle_tag 'common_vue'
= webpack_bundle_tag 'filtered_search' = webpack_bundle_tag 'filtered_search'
- content_for :breadcrumbs_extra do
= render "projects/merge_requests/nav_btns", merge_project: merge_project, new_merge_request_path: new_merge_request_path
= render 'projects/last_push' = render 'projects/last_push'
- if @project.merge_requests.exists? - if @project.merge_requests.exists?
%div{ class: container_class } %div{ class: container_class }
.top-area .top-area
= render 'shared/issuable/nav', type: :merge_requests = render 'shared/issuable/nav', type: :merge_requests
.nav-controls.visible-xs .nav-controls
= render "projects/merge_requests/nav_btns", merge_project: merge_project, new_merge_request_path: new_merge_request_path = render "projects/merge_requests/nav_btns", merge_project: merge_project, new_merge_request_path: new_merge_request_path
= render 'shared/issuable/search_bar', type: :merge_requests = render 'shared/issuable/search_bar', type: :merge_requests
......
- @no_container = true - @no_container = true
- page_title 'Milestones' - page_title 'Milestones'
- if can?(current_user, :admin_milestone, @project)
- content_for :breadcrumbs_extra do
= link_to "New milestone", new_namespace_project_milestone_path(@project.namespace, @project), class: 'btn btn-new', title: 'New milestone'
= render "shared/mr_head" = render "shared/mr_head"
%div{ class: container_class } %div{ class: container_class }
.top-area .top-area
= render 'shared/milestones_filter', counts: milestone_counts(@project.milestones) = render 'shared/milestones_filter', counts: milestone_counts(@project.milestones)
.nav-controls.nav-controls-new-nav .nav-controls
= render 'shared/milestones_sort_dropdown' = render 'shared/milestones_sort_dropdown'
- if can?(current_user, :admin_milestone, @project) - if can?(current_user, :admin_milestone, @project)
= link_to new_project_milestone_path(@project), class: "btn btn-new visible-xs", title: 'New milestone' do = link_to new_project_milestone_path(@project), class: "btn btn-new", title: 'New milestone' do
New milestone New milestone
.milestones .milestones
......
...@@ -7,10 +7,6 @@ ...@@ -7,10 +7,6 @@
- @no_container = true - @no_container = true
- page_title _("Pipeline Schedules") - page_title _("Pipeline Schedules")
- if can?(current_user, :create_pipeline_schedule, @project)
- content_for :breadcrumbs_extra do
= link_to _('New schedule'), new_namespace_project_pipeline_schedule_path(@project.namespace, @project), class: 'btn btn-create'
= render "projects/pipelines/head" = render "projects/pipelines/head"
%div{ class: container_class } %div{ class: container_class }
...@@ -20,7 +16,7 @@ ...@@ -20,7 +16,7 @@
= render "tabs", schedule_path_proc: schedule_path_proc, all_schedules: @all_schedules, scope: @scope = render "tabs", schedule_path_proc: schedule_path_proc, all_schedules: @all_schedules, scope: @scope
- if can?(current_user, :create_pipeline_schedule, @project) - if can?(current_user, :create_pipeline_schedule, @project)
.nav-controls.visible-xs .nav-controls
= link_to new_project_pipeline_schedule_path(@project), class: 'btn btn-create' do = link_to new_project_pipeline_schedule_path(@project), class: 'btn btn-create' do
%span= _('New schedule') %span= _('New schedule')
......
- page_title "Snippets" - page_title "Snippets"
- if can?(current_user, :create_project_snippet, @project)
- content_for :breadcrumbs_extra do
= link_to "New snippet", new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new", title: "New snippet"
- if current_user - if current_user
.top-area .top-area
- include_private = @project.team.member?(current_user) || current_user.admin? - include_private = @project.team.member?(current_user) || current_user.admin?
= render partial: 'snippets/snippets_scope_menu', locals: { subject: @project, include_private: include_private } = render partial: 'snippets/snippets_scope_menu', locals: { subject: @project, include_private: include_private }
.nav-controls.visible-xs .nav-controls
- if can?(current_user, :create_project_snippet, @project) - if can?(current_user, :create_project_snippet, @project)
= link_to "New snippet", new_project_snippet_path(@project), class: "btn btn-new", title: "New snippet" = link_to "New snippet", new_project_snippet_path(@project), class: "btn btn-new", title: "New snippet"
......
%tr{ class: "tree-item #{tree_hex_class(tree_item)}" } %tr{ class: "tree-item #{tree_hex_class(tree_item)}" }
%td.tree-item-file-name %td.tree-item-file-name
= tree_icon(type, tree_item.mode, tree_item.name) = tree_icon(type, tree_item.mode, tree_item.name)
- path = flatten_tree(tree_item) - path = flatten_tree(@path, tree_item)
= link_to project_tree_path(@project, tree_join(@id || @commit.id, path)), title: path do = link_to project_tree_path(@project, tree_join(@id || @commit.id, path)), title: path do
%span.str-truncated= path %span.str-truncated= path
%td.hidden-xs.tree-commit %td.hidden-xs.tree-commit
......
---
title: Remove focus styles from dropdown empty links
merge_request:
author:
type: fixed
---
title: Add description template examples to documentation
merge_request:
author:
type: other
---
title: Add quick submission on user settings page
merge_request: 14007
author: Vitaliy @blackst0ne Klachkov
type: added
---
title: Add API support for wiki pages
merge_request: 13372
author: Vitaliy @blackst0ne Klachkov
type: added
...@@ -18,6 +18,7 @@ class MigrateIssuesToGhostUser < ActiveRecord::Migration ...@@ -18,6 +18,7 @@ class MigrateIssuesToGhostUser < ActiveRecord::Migration
ActiveRecord::Base.clear_cache! ActiveRecord::Base.clear_cache!
::User.reset_column_information ::User.reset_column_information
::Namespace.reset_column_information
end end
def up def up
......
...@@ -58,6 +58,7 @@ following locations: ...@@ -58,6 +58,7 @@ following locations:
- [Validate CI configuration](lint.md) - [Validate CI configuration](lint.md)
- [V3 to V4](v3_to_v4.md) - [V3 to V4](v3_to_v4.md)
- [Version](version.md) - [Version](version.md)
- [Wikis](wikis.md)
## Road to GraphQL ## Road to GraphQL
......
# Wikis API
> [Introduced][ce-13372] in GitLab 10.0.
Available only in APIv4.
## List wiki pages
Get all wiki pages for a given project.
```
GET /projects/:id/wikis
```
| Attribute | Type | Required | Description |
| --------- | ------- | -------- | --------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
| `with_content` | boolean | no | Include pages' content |
```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/wikis?with_content=1
```
Example response:
```json
[
{
"content" : "Here is an instruction how to deploy this project.",
"format" : "markdown",
"slug" : "deploy",
"title" : "deploy"
},
{
"content" : "Our development process is described here.",
"format" : "markdown",
"slug" : "development",
"title" : "development"
},{
"content" : "* [Deploy](deploy)\n* [Development](development)",
"format" : "markdown",
"slug" : "home",
"title" : "home"
}
]
```
## Get a wiki page
Get a wiki page for a given project.
```
GET /projects/:id/wikis/:slug
```
| Attribute | Type | Required | Description |
| --------- | ------- | -------- | --------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
| `slug` | string | yes | The slug (a unique string) of the wiki page |
```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/wikis/home
```
Example response:
```json
[
{
"content" : "home page",
"format" : "markdown",
"slug" : "home",
"title" : "home"
}
]
```
## Create a new wiki page
Creates a new wiki page for the given repository with the given title, slug, and content.
```
POST /projects/:id/wikis
```
| Attribute | Type | Required | Description |
| ------------- | ------- | -------- | ---------------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
| `content` | string | yes | The content of the wiki page |
| `title` | string | yes | The title of the wiki page |
| `format` | string | no | The format of the wiki page. Available formats are: `markdown` (default), `rdoc`, and `asciidoc` |
```bash
curl --data "format=rdoc&title=Hello&content=Hello world" --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/wikis"
```
Example response:
```json
{
"content" : "Hello world",
"format" : "markdown",
"slug" : "Hello",
"title" : "Hello"
}
```
## Edit an existing wiki page
Updates an existing wiki page. At least one parameter is required to update the wiki page.
```
PUT /projects/:id/wikis/:slug
```
| Attribute | Type | Required | Description |
| --------------- | ------- | --------------------------------- | ------------------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
| `content` | string | yes if `title` is not provided | The content of the wiki page |
| `title` | string | yes if `content` is not provided | The title of the wiki page |
| `format` | string | no | The format of the wiki page. Available formats are: `markdown` (default), `rdoc`, and `asciidoc` |
| `slug` | string | yes | The slug (a unique string) of the wiki page |
```bash
curl --request PUT --data "format=rdoc&content=documentation&title=Docs" --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/wikis/foo"
```
Example response:
```json
{
"content" : "documentation",
"format" : "markdown",
"slug" : "Docs",
"title" : "Docs"
}
```
## Delete a wiki page
Deletes a wiki page with a given slug.
```
DELETE /projects/:id/wikis/:slug
```
| Attribute | Type | Required | Description |
| --------- | ------- | -------- | --------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
| `slug` | string | yes | The slug (a unique string) of the wiki page |
```bash
curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/wikis/foo"
```
On success the HTTP status code is `204` and no JSON response is expected.
# Auto deploy # Auto Deploy
> [Introduced][mr-8135] in GitLab 8.15. >**Notes:**
> Auto deploy is an experimental feature and is not recommended for Production use at this time. - [Introduced][mr-8135] in GitLab 8.15.
> As of GitLab 9.1, access to the container registry is only available while the Pipeline is running. Restarting a pod, scaling a service, or other actions which require on-going access will fail. On-going secure access is planned for a subsequent release. - Auto deploy is an experimental feature and is not recommended for Production
use at this time.
- As of GitLab 9.1, access to the Container Registry is only available while
the Pipeline is running. Restarting a pod, scaling a service, or other actions
which require on-going access will fail. On-going secure access is planned for
a subsequent release.
Auto deploy is an easy way to configure GitLab CI for the deployment of your Auto deploy is an easy way to configure GitLab CI for the deployment of your
application. GitLab Community maintains a list of `.gitlab-ci.yml` application. GitLab Community maintains a list of `.gitlab-ci.yml`
...@@ -11,9 +16,23 @@ powering them. These scripts are responsible for packaging your application, ...@@ -11,9 +16,23 @@ powering them. These scripts are responsible for packaging your application,
setting up the infrastructure and spinning up necessary services (for setting up the infrastructure and spinning up necessary services (for
example a database). example a database).
You can use [project services][project-services] to store credentials to ## How it works
your infrastructure provider and they will be available during the
deployment. The Autodeploy templates are based on the [kubernetes-deploy][kube-deploy]
project which is used to simplify the deployment process to Kubernetes by
providing intelligent `build`, `deploy`, and `destroy` commands which you can
use in your `.gitlab-ci.yml` as is. It uses [Herokuish](https://github.com/gliderlabs/herokuish),
which uses [Heroku buildpacks](https://devcenter.heroku.com/articles/buildpacks)
to do some of the work, plus some of GitLab's own tools to package it all up. For
your convenience, a [Docker image][kube-image] is also provided.
You can use the [Kubernetes project service](../../user/project/integrations/kubernetes.md)
to store credentials to your infrastructure provider and they will be available
during the deployment.
## Quick start
We made a [simple guide](quick_start_guide.md) to using Auto Deploy with GitLab.com.
## Supported templates ## Supported templates
...@@ -22,20 +41,27 @@ The list of supported auto deploy templates is available in the ...@@ -22,20 +41,27 @@ The list of supported auto deploy templates is available in the
## Configuration ## Configuration
>**Note:**
In order to understand why the following steps are required, read the
[how it works](#how-it-works) section.
To configure Autodeploy, you will need to:
1. Enable a deployment [project service][project-services] to store your 1. Enable a deployment [project service][project-services] to store your
credentials. For example, if you want to deploy to OpenShift you have to credentials. For example, if you want to deploy to OpenShift you have to
enable [Kubernetes service][kubernetes-service]. enable [Kubernetes service][kubernetes-service].
1. Configure GitLab Runner to use Docker or Kubernetes executor with 1. Configure GitLab Runner to use the
[privileged mode enabled][docker-in-docker]. [Docker or Kubernetes executor](https://docs.gitlab.com/runner/executors/) with
[privileged mode enabled][docker-in-docker].
1. Navigate to the "Project" tab and click "Set up auto deploy" button. 1. Navigate to the "Project" tab and click "Set up auto deploy" button.
![Auto deploy button](img/auto_deploy_button.png) ![Auto deploy button](img/auto_deploy_button.png)
1. Select a template. 1. Select a template.
![Dropdown with auto deploy templates](img/auto_deploy_dropdown.png) ![Dropdown with auto deploy templates](img/auto_deploy_dropdown.png)
1. Commit your changes and create a merge request. 1. Commit your changes and create a merge request.
1. Test your deployment configuration using a [Review App][review-app] that was 1. Test your deployment configuration using a [Review App][review-app] that was
created automatically for you. created automatically for you.
## Private Project Support ## Private project support
> Experimental support [introduced][mr-2] in GitLab 9.1. > Experimental support [introduced][mr-2] in GitLab 9.1.
...@@ -43,7 +69,7 @@ When a project has been marked as private, GitLab's [Container Registry][contain ...@@ -43,7 +69,7 @@ When a project has been marked as private, GitLab's [Container Registry][contain
After the pipeline completes, Kubernetes will no longer be able to access the container registry. Restarting a pod, scaling a service, or other actions which require on-going access to the registry will fail. On-going secure access is planned for a subsequent release. After the pipeline completes, Kubernetes will no longer be able to access the container registry. Restarting a pod, scaling a service, or other actions which require on-going access to the registry will fail. On-going secure access is planned for a subsequent release.
## PostgreSQL Database Support ## PostgreSQL database support
> Experimental support [introduced][mr-8] in GitLab 9.1. > Experimental support [introduced][mr-8] in GitLab 9.1.
...@@ -51,25 +77,13 @@ In order to support applications that require a database, [PostgreSQL][postgresq ...@@ -51,25 +77,13 @@ In order to support applications that require a database, [PostgreSQL][postgresq
PostgreSQL provisioning can be disabled by setting the variable `DISABLE_POSTGRES` to `"yes"`. PostgreSQL provisioning can be disabled by setting the variable `DISABLE_POSTGRES` to `"yes"`.
### PostgreSQL Variables The following PostgreSQL variables are supported:
1. `DISABLE_POSTGRES: "yes"`: disable automatic deployment of PostgreSQL 1. `DISABLE_POSTGRES: "yes"`: disable automatic deployment of PostgreSQL
1. `POSTGRES_USER: "my-user"`: use custom username for PostgreSQL 1. `POSTGRES_USER: "my-user"`: use custom username for PostgreSQL
1. `POSTGRES_PASSWORD: "password"`: use custom password for PostgreSQL 1. `POSTGRES_PASSWORD: "password"`: use custom password for PostgreSQL
1. `POSTGRES_DB: "my database"`: use custom database name for PostgreSQL 1. `POSTGRES_DB: "my database"`: use custom database name for PostgreSQL
[mr-8135]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8135
[mr-2]: https://gitlab.com/gitlab-examples/kubernetes-deploy/merge_requests/2
[mr-8]: https://gitlab.com/gitlab-examples/kubernetes-deploy/merge_requests/8
[project-settings]: https://docs.gitlab.com/ce/public_access/public_access.html
[project-services]: ../../user/project/integrations/project_services.md
[auto-deploy-templates]: https://gitlab.com/gitlab-org/gitlab-ci-yml/tree/master/autodeploy
[kubernetes-service]: ../../user/project/integrations/kubernetes.md
[docker-in-docker]: ../docker/using_docker_build.md#use-docker-in-docker-executor
[review-app]: ../review_apps/index.md
[container-registry]: https://docs.gitlab.com/ce/user/project/container_registry.html
[postgresql]: https://www.postgresql.org/
## Auto Monitoring ## Auto Monitoring
> Introduced in [GitLab 9.5](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/13438). > Introduced in [GitLab 9.5](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/13438).
...@@ -94,3 +108,18 @@ If you have installed GitLab using a different method: ...@@ -94,3 +108,18 @@ If you have installed GitLab using a different method:
1. [Deploy Prometheus](../../user/project/integrations/prometheus.md#configuring-your-own-prometheus-server-within-kubernetes) into your Kubernetes cluster 1. [Deploy Prometheus](../../user/project/integrations/prometheus.md#configuring-your-own-prometheus-server-within-kubernetes) into your Kubernetes cluster
1. If you would like response metrics, ensure you are running at least version 0.9.0 of NGINX Ingress and [enable Prometheus metrics](https://github.com/kubernetes/ingress/blob/master/examples/customization/custom-vts-metrics/nginx/nginx-vts-metrics-conf.yaml). 1. If you would like response metrics, ensure you are running at least version 0.9.0 of NGINX Ingress and [enable Prometheus metrics](https://github.com/kubernetes/ingress/blob/master/examples/customization/custom-vts-metrics/nginx/nginx-vts-metrics-conf.yaml).
1. Finally, [annotate](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/) the NGINX Ingress deployment to be scraped by Prometheus using `prometheus.io/scrape: "true"` and `prometheus.io/port: "10254"`. 1. Finally, [annotate](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/) the NGINX Ingress deployment to be scraped by Prometheus using `prometheus.io/scrape: "true"` and `prometheus.io/port: "10254"`.
[mr-8135]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8135
[mr-2]: https://gitlab.com/gitlab-examples/kubernetes-deploy/merge_requests/2
[mr-8]: https://gitlab.com/gitlab-examples/kubernetes-deploy/merge_requests/8
[project-settings]: https://docs.gitlab.com/ce/public_access/public_access.html
[project-services]: ../../user/project/integrations/project_services.md
[auto-deploy-templates]: https://gitlab.com/gitlab-org/gitlab-ci-yml/tree/master/autodeploy
[kubernetes-service]: ../../user/project/integrations/kubernetes.md
[docker-in-docker]: ../docker/using_docker_build.md#use-docker-in-docker-executor
[review-app]: ../review_apps/index.md
[kube-image]: https://gitlab.com/gitlab-examples/kubernetes-deploy/container_registry "Kubernetes deploy Container Registry"
[kube-deploy]: https://gitlab.com/gitlab-examples/kubernetes-deploy "Kubernetes deploy example project"
[container-registry]: https://docs.gitlab.com/ce/user/project/container_registry.html
[postgresql]: https://www.postgresql.org/
# Auto Deploy: quick start guide
This is a step-by-step guide to deploying a project hosted on GitLab.com to Google Cloud, using Auto Deploy.
We made a minimal [Ruby application](https://gitlab.com/gitlab-examples/minimal-ruby-app) to use as an example for this guide. It contains two files:
* `server.rb` - our application. It will start an HTTP server on port 5000 and render “Hello, world!”
* `Dockerfile` - to build our app into a container image. It will use a ruby base image and run `server.rb`
## Fork sample project on GitLab.com
Let’s start by forking our sample application. Go to [the project page](https://gitlab.com/gitlab-examples/minimal-ruby-app) and press the `Fork` button. Soon you should have a project under your namespace with the necessary files.
## Setup your own cluster on Google Container Engine
If you do not already have a Google Cloud account, create one at https://console.cloud.google.com.
Visit the [`Container Engine`](https://console.cloud.google.com/kubernetes/list) tab and create a new cluster. You can change the name and leave the rest of the default settings. Once you have your cluster running, you need to connect to the cluster by following the Google interface.
## Connect to Kubernetes cluster
You need to have the Google Cloud SDK installed. e.g.
On OSX, install [homebrew](https://brew.sh):
1. Install Brew Caskroom: `brew install caskroom/cask/brew-cask`
2. Install Google Cloud SDK: `brew cask install google-cloud-sdk`
3. Add `kubectl`: `gcloud components install kubectl`
4. Log in: `gcloud auth login`
Now go back to the Google interface, find your cluster, and follow the instructions under `Connect to the cluster` and open the Kubernetes Dashboard. It will look something like `gcloud container clusters get-credentials ruby-autodeploy \ --zone europe-west2-c --project api-project-XXXXXXX` and then `kubectl proxy`.
![connect to cluster](img/guide_connect_cluster.png)
## Copy credentials to GitLab.com project
Once you have the Kubernetes Dashboard interface running, you should visit `Secrets` under the `Config` section. There you should find the settings we need for GitLab integration: ca.crt and token.
![connect to cluster](img/guide_secret.png)
You need to copy-paste the ca.crt and token into your project on GitLab.com in the Kubernetes integration page under project `Settings` > `Integrations` > `Project services` > `Kubernetes`. Don't actually copy the namespace though. Each project should have a unique namespace, and by leaving it blank, GitLab will create one for you.
![connect to cluster](img/guide_integration.png)
For API URL, you should use the `Endpoint` IP from your cluster page on Google Cloud Platform.
## Expose the application to the internet
In order to be able to visit your application, you need to install an NGINX ingress controller and point your domain name to its external IP address.
### Set up Ingress controller
You’ll need to make sure you have an ingress controller. If you don’t have one, do:
```sh
brew install kubernetes-helm
helm init
helm install --name ruby-app stable/nginx-ingress
```
This should create several services including `ruby-app-nginx-ingress-controller`. You can list your services by running `kubectl get svc` to confirm that.
### Point DNS at Cluster IP
Find out the external IP address of the `ruby-app-nginx-ingress-controller` by running:
```sh
kubectl get svc ruby-app-nginx-ingress-controller -o jsonpath='{.status.loadBalancer.ingress[0].ip}'
```
Use this IP address to configure your DNS. This part heavily depends on your preferences and domain provider. But in case you are not sure, just create an A record with a wildcard host like `*.<your-domain>` pointing to the external IP address you found above.
Use `nslookup minimal-ruby-app-staging.<yourdomain>` to confirm that domain is assigned to the cluster IP.
## Setup Auto Deploy
Visit the home page of your GitLab.com project and press "Set up Auto Deploy" button.
![auto deploy button](img/auto_deploy_btn.png)
You will be redirected to the "New file" page where you can apply one of the Auto Deploy templates. Select "Kubernetes" to apply the template, then in the file, replace `domain.example.com` with your domain name and make any other adjustments you need.
![auto deploy template](img/auto_deploy_dropdown.png)
Change the target branch to `master`, and submit your changes. This should create
a new pipeline with several jobs. If you made only the domain name change, the
pipeline will have three jobs: `build`, `staging`, and `production`.
The `build` job will create a Docker image with your new change and push it to
the GitLab Container Registry. The `staging` job will deploy this image on your
cluster. Once the deploy job succeeds you should be able to see your application by
visiting the Kubernetes dashboard. Select the namespace of your project, which
will look like `ruby-autodeploy-23`, but with a unique ID for your project, and
your app will be listed as "staging" under the "Deployment" tab.
Once its ready - just visit http://minimal-ruby-app-staging.yourdomain.com to see “Hello, world!”
...@@ -222,6 +222,30 @@ total running time should be: ...@@ -222,6 +222,30 @@ total running time should be:
Pipeline status and test coverage report badges are available. You can find their Pipeline status and test coverage report badges are available. You can find their
respective link in the [Pipelines settings] page. respective link in the [Pipelines settings] page.
## Security on protected branches
A strict security model is enforced when pipelines are executed on
[protected branches](../user/project/protected_branches.md).
The following actions are allowed on protected branches only if the user is
[allowed to merge or push](../user/project/protected_branches.md#using-the-allowed-to-merge-and-allowed-to-push-settings)
on that specific branch:
- run **manual pipelines** (using Web UI or Pipelines API)
- run **scheduled pipelines**
- run pipelines using **triggers**
- trigger **manual actions** on existing pipelines
- **retry/cancel** existing jobs (using Web UI or Pipelines API)
**Secret variables** marked as **protected** are accessible only to jobs that
run on protected branches, avoiding untrusted users to get unintended access to
sensitive information like deployment credentials and tokens.
**Runners** marked as **protected** can run jobs only on protected
branches, avoiding untrusted code to be executed on the protected runner and
preserving deployment keys and other credentials from being unintentionally
accessed. In order to ensure that jobs intended to be executed on protected
runners will not use regular runners, they must be tagged accordingly.
[jobs]: #jobs [jobs]: #jobs
[jobs-yaml]: yaml/README.md#jobs [jobs-yaml]: yaml/README.md#jobs
[manual]: yaml/README.md#manual [manual]: yaml/README.md#manual
......
...@@ -230,6 +230,14 @@ users: ...@@ -230,6 +230,14 @@ users:
GitLab 8.12 has a completely redesigned job permissions system. To learn more, GitLab 8.12 has a completely redesigned job permissions system. To learn more,
read through the documentation on the [new CI/CD permissions model](project/new_ci_build_permissions_model.md#new-ci-job-permissions-model). read through the documentation on the [new CI/CD permissions model](project/new_ci_build_permissions_model.md#new-ci-job-permissions-model).
## Running pipelines on protected branches
The permission to merge or push to protected branches is used to define if a user can
run CI/CD pipelines and execute actions on jobs that are related to those branches.
See [Security on protected branches](../ci/pipelines.md#security-on-protected-branches)
for details about the pipelines security model.
## LDAP users permissions ## LDAP users permissions
Since GitLab 8.15, LDAP user permissions can now be manually overridden by an admin user. Since GitLab 8.15, LDAP user permissions can now be manually overridden by an admin user.
......
...@@ -39,4 +39,58 @@ changes you made after picking the template and return it to its initial status. ...@@ -39,4 +39,58 @@ changes you made after picking the template and return it to its initial status.
![Description templates](img/description_templates.png) ![Description templates](img/description_templates.png)
## Description template example
We make use of Description Templates for Issues and Merge Requests within the GitLab Community Edition project. Please refer to the [`.gitlab` folder][gitlab-ce-templates] for some examples.
> **Tip:**
It is possible to use [quick actions](./quick_actions.md) within description templates to quickly add labels, assignees, and milestones. The quick actions will only be executed if the user submitting the Issue or Merge Request has the permissions perform the relevant actions.
Here is an example for a Bug report template:
```
Summary
(Summarize the bug encountered concisely)
Steps to reproduce
(How one can reproduce the issue - this is very important)
Example Project
(If possible, please create an example project here on GitLab.com that exhibits the problematic behaviour, and link to it here in the bug report)
(If you are using an older version of GitLab, this will also determine whether the bug has been fixed in a more recent version)
What is the current bug behavior?
(What actually happens)
What is the expected correct behavior?
(What you should see instead)
Relevant logs and/or screenshots
(Paste any relevant logs - please use code blocks (```) to format console output,
logs, and code as it's very hard to read otherwise.)
Possible fixes
(If you can, link to the line of code that might be responsible for the problem)
/label ~bug ~reproduced ~needs-investigation
/cc @project-manager
/assign @qa-tester
```
[ce-4981]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4981 [ce-4981]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4981
[gitlab-ce-templates]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/.gitlab
...@@ -23,4 +23,4 @@ Prometheus server up and running. You have two options here: ...@@ -23,4 +23,4 @@ Prometheus server up and running. You have two options here:
In order to isolate and only display relevant metrics for a given environment In order to isolate and only display relevant metrics for a given environment
however, GitLab needs a method to detect which labels are associated. To do this, GitLab will [look for an `environment` label](metrics.md#identifying-environments). however, GitLab needs a method to detect which labels are associated. To do this, GitLab will [look for an `environment` label](metrics.md#identifying-environments).
If you are using [GitLab Auto-Deploy][autodeploy] and one of the two [provided Kubernetes monitoring solutions](../prometheus.md#getting-started-with-prometheus-monitoring), the `environment` label will be automatically added. If you are using [GitLab Auto-Deploy][../../../ci/autodeploy/index.md] and one of the two [provided Kubernetes monitoring solutions](../prometheus.md#getting-started-with-prometheus-monitoring), the `environment` label will be automatically added.
...@@ -115,6 +115,14 @@ Deleting a protected branch is only allowed via the web interface, not via Git. ...@@ -115,6 +115,14 @@ Deleting a protected branch is only allowed via the web interface, not via Git.
This means that you can't accidentally delete a protected branch from your This means that you can't accidentally delete a protected branch from your
command line or a Git client application. command line or a Git client application.
## Running pipelines on protected branches
The permission to merge or push to protected branches is used to define if a user can
run CI/CD pipelines and execute actions on jobs that are related to those branches.
See [Security on protected branches](../../ci/pipelines.md#security-on-protected-branches)
for details about the pipelines security model.
## Changelog ## Changelog
**9.2** **9.2**
......
...@@ -47,7 +47,7 @@ class Spinach::Features::GroupMilestones < Spinach::FeatureSteps ...@@ -47,7 +47,7 @@ class Spinach::Features::GroupMilestones < Spinach::FeatureSteps
end end
step 'I click new milestone button' do step 'I click new milestone button' do
page.within('.breadcrumbs') do page.within('.nav-controls') do
click_link "New milestone" click_link "New milestone"
end end
end end
......
...@@ -62,7 +62,7 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps ...@@ -62,7 +62,7 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
end end
step 'I click link "New issue"' do step 'I click link "New issue"' do
page.within '.breadcrumbs' do page.within '#content-body' do
page.has_link?('New Issue') ? click_link('New Issue') : click_link('New issue') page.has_link?('New Issue') ? click_link('New Issue') : click_link('New issue')
end end
end end
......
...@@ -16,7 +16,7 @@ class Spinach::Features::ProjectIssuesMilestones < Spinach::FeatureSteps ...@@ -16,7 +16,7 @@ class Spinach::Features::ProjectIssuesMilestones < Spinach::FeatureSteps
end end
step 'I click link "New Milestone"' do step 'I click link "New Milestone"' do
page.within('.breadcrumbs') do page.within('.nav-controls') do
click_link "New milestone" click_link "New milestone"
end end
end end
......
...@@ -14,7 +14,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps ...@@ -14,7 +14,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
end end
step 'I click link "New Merge Request"' do step 'I click link "New Merge Request"' do
page.within '.breadcrumbs' do page.within '.nav-controls' do
page.has_link?('New Merge Request') ? click_link("New Merge Request") : click_link('New merge request') page.has_link?('New Merge Request') ? click_link("New Merge Request") : click_link('New merge request')
end end
end end
......
...@@ -23,7 +23,7 @@ class Spinach::Features::ProjectSnippets < Spinach::FeatureSteps ...@@ -23,7 +23,7 @@ class Spinach::Features::ProjectSnippets < Spinach::FeatureSteps
end end
step 'I click link "New snippet"' do step 'I click link "New snippet"' do
page.within '.breadcrumbs' do page.within '.nav-controls' do
first(:link, "New snippet").click first(:link, "New snippet").click
end end
end end
......
...@@ -144,6 +144,7 @@ module API ...@@ -144,6 +144,7 @@ module API
mount ::API::Variables mount ::API::Variables
mount ::API::GroupVariables mount ::API::GroupVariables
mount ::API::Version mount ::API::Version
mount ::API::Wikis
route :any, '*path' do route :any, '*path' do
error!('404 Not Found', 404) error!('404 Not Found', 404)
......
module API module API
module Entities module Entities
class WikiPageBasic < Grape::Entity
expose :format
expose :slug
expose :title
end
class WikiPage < WikiPageBasic
expose :content
end
class UserSafe < Grape::Entity class UserSafe < Grape::Entity
expose :id, :name, :username expose :id, :name, :username
end end
......
...@@ -56,6 +56,12 @@ module API ...@@ -56,6 +56,12 @@ module API
@project ||= find_project!(params[:id]) @project ||= find_project!(params[:id])
end end
def wiki_page
page = user_project.wiki.find_page(params[:slug])
page || not_found!('Wiki Page')
end
def available_labels def available_labels
@available_labels ||= LabelsFinder.new(current_user, project_id: user_project.id).execute @available_labels ||= LabelsFinder.new(current_user, project_id: user_project.id).execute
end end
......
module API
class Wikis < Grape::API
helpers do
params :wiki_page_params do
requires :content, type: String, desc: 'Content of a wiki page'
requires :title, type: String, desc: 'Title of a wiki page'
optional :format,
type: String,
values: ProjectWiki::MARKUPS.values.map(&:to_s),
default: 'markdown',
desc: 'Format of a wiki page. Available formats are markdown, rdoc, and asciidoc'
end
end
resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do
desc 'Get a list of wiki pages' do
success Entities::WikiPageBasic
end
params do
optional :with_content, type: Boolean, default: false, desc: "Include pages' content"
end
get ':id/wikis' do
authorize! :read_wiki, user_project
entity = params[:with_content] ? Entities::WikiPage : Entities::WikiPageBasic
present user_project.wiki.pages, with: entity
end
desc 'Get a wiki page' do
success Entities::WikiPage
end
params do
requires :slug, type: String, desc: 'The slug of a wiki page'
end
get ':id/wikis/:slug' do
authorize! :read_wiki, user_project
present wiki_page, with: Entities::WikiPage
end
desc 'Create a wiki page' do
success Entities::WikiPage
end
params do
use :wiki_page_params
end
post ':id/wikis' do
authorize! :create_wiki, user_project
page = WikiPages::CreateService.new(user_project, current_user, params).execute
if page.valid?
present page, with: Entities::WikiPage
else
render_validation_error!(page)
end
end
desc 'Update a wiki page' do
success Entities::WikiPage
end
params do
use :wiki_page_params
end
put ':id/wikis/:slug' do
authorize! :create_wiki, user_project
page = WikiPages::UpdateService.new(user_project, current_user, params).execute(wiki_page)
if page.valid?
present page, with: Entities::WikiPage
else
render_validation_error!(page)
end
end
desc 'Delete a wiki page'
params do
requires :slug, type: String, desc: 'The slug of a wiki page'
end
delete ':id/wikis/:slug' do
authorize! :admin_wiki, user_project
status 204
WikiPages::DestroyService.new(user_project, current_user).execute(wiki_page)
end
end
end
end
...@@ -5,7 +5,7 @@ module Gitlab ...@@ -5,7 +5,7 @@ module Gitlab
class Tree class Tree
include Gitlab::EncodingHelper include Gitlab::EncodingHelper
attr_accessor :id, :root_id, :name, :path, :type, attr_accessor :id, :root_id, :name, :path, :flat_path, :type,
:mode, :commit_id, :submodule_url :mode, :commit_id, :submodule_url
class << self class << self
...@@ -19,8 +19,7 @@ module Gitlab ...@@ -19,8 +19,7 @@ module Gitlab
Gitlab::GitalyClient.migrate(:tree_entries) do |is_enabled| Gitlab::GitalyClient.migrate(:tree_entries) do |is_enabled|
if is_enabled if is_enabled
client = Gitlab::GitalyClient::CommitService.new(repository) repository.gitaly_commit_client.tree_entries(repository, sha, path)
client.tree_entries(repository, sha, path)
else else
tree_entries_from_rugged(repository, sha, path) tree_entries_from_rugged(repository, sha, path)
end end
...@@ -88,7 +87,7 @@ module Gitlab ...@@ -88,7 +87,7 @@ module Gitlab
end end
def initialize(options) def initialize(options)
%w(id root_id name path type mode commit_id).each do |key| %w(id root_id name path flat_path type mode commit_id).each do |key|
self.send("#{key}=", options[key.to_sym]) # rubocop:disable GitlabSecurity/PublicSend self.send("#{key}=", options[key.to_sym]) # rubocop:disable GitlabSecurity/PublicSend
end end
end end
...@@ -101,6 +100,10 @@ module Gitlab ...@@ -101,6 +100,10 @@ module Gitlab
encode! @path encode! @path
end end
def flat_path
encode! @flat_path
end
def dir? def dir?
type == :tree type == :tree
end end
......
...@@ -88,14 +88,14 @@ module Gitlab ...@@ -88,14 +88,14 @@ module Gitlab
response.flat_map do |message| response.flat_map do |message|
message.entries.map do |gitaly_tree_entry| message.entries.map do |gitaly_tree_entry|
entry_path = gitaly_tree_entry.path.dup
Gitlab::Git::Tree.new( Gitlab::Git::Tree.new(
id: gitaly_tree_entry.oid, id: gitaly_tree_entry.oid,
root_id: gitaly_tree_entry.root_oid, root_id: gitaly_tree_entry.root_oid,
type: gitaly_tree_entry.type.downcase, type: gitaly_tree_entry.type.downcase,
mode: gitaly_tree_entry.mode.to_s(8), mode: gitaly_tree_entry.mode.to_s(8),
name: File.basename(entry_path), name: File.basename(gitaly_tree_entry.path),
path: entry_path, path: GitalyClient.encode(gitaly_tree_entry.path),
flat_path: GitalyClient.encode(gitaly_tree_entry.flat_path),
commit_id: gitaly_tree_entry.commit_oid commit_id: gitaly_tree_entry.commit_oid
) )
end end
......
...@@ -50,7 +50,7 @@ feature 'Dashboard Issues filtering', :js do ...@@ -50,7 +50,7 @@ feature 'Dashboard Issues filtering', :js do
it 'updates atom feed link' do it 'updates atom feed link' do
visit_issues(milestone_title: '', assignee_id: user.id) visit_issues(milestone_title: '', assignee_id: user.id)
link = find('.breadcrumbs a[title="Subscribe"]') link = find('.nav-controls a[title="Subscribe"]')
params = CGI.parse(URI.parse(link[:href]).query) params = CGI.parse(URI.parse(link[:href]).query)
auto_discovery_link = find('link[type="application/atom+xml"]', visible: false) auto_discovery_link = find('link[type="application/atom+xml"]', visible: false)
auto_discovery_params = CGI.parse(URI.parse(auto_discovery_link[:href]).query) auto_discovery_params = CGI.parse(URI.parse(auto_discovery_link[:href]).query)
......
...@@ -115,9 +115,9 @@ feature 'Dashboard Projects' do ...@@ -115,9 +115,9 @@ feature 'Dashboard Projects' do
expect(page).to have_selector('.merge-request-form') expect(page).to have_selector('.merge-request-form')
expect(current_path).to eq project_new_merge_request_path(project) expect(current_path).to eq project_new_merge_request_path(project)
expect(find('#merge_request_target_project_id').value).to eq project.id.to_s expect(find('#merge_request_target_project_id', visible: false).value).to eq project.id.to_s
expect(find('input#merge_request_source_branch').value).to eq 'feature' expect(find('input#merge_request_source_branch', visible: false).value).to eq 'feature'
expect(find('input#merge_request_target_branch').value).to eq 'master' expect(find('input#merge_request_target_branch', visible: false).value).to eq 'master'
end end
end end
end end
...@@ -577,7 +577,7 @@ describe 'Issues' do ...@@ -577,7 +577,7 @@ describe 'Issues' do
it 'redirects to signin then back to new issue after signin' do it 'redirects to signin then back to new issue after signin' do
visit project_issues_path(project) visit project_issues_path(project)
page.within '.breadcrumbs' do page.within '.nav-controls' do
click_link 'New issue' click_link 'New issue'
end end
......
...@@ -196,10 +196,11 @@ feature 'Diff notes resolve', js: true do ...@@ -196,10 +196,11 @@ feature 'Diff notes resolve', js: true do
end end
it 'does not mark discussion as resolved when resolving single note' do it 'does not mark discussion as resolved when resolving single note' do
page.first '.diff-content .note' do page.within("#note_#{note.id}") do
first('.line-resolve-btn').click first('.line-resolve-btn').click
expect(page).to have_selector('.note-action-button .loading') wait_for_requests
expect(first('.line-resolve-btn')['data-original-title']).to eq("Resolved by #{user.name}") expect(first('.line-resolve-btn')['data-original-title']).to eq("Resolved by #{user.name}")
end end
......
...@@ -14,7 +14,7 @@ feature 'User wants to create a file' do ...@@ -14,7 +14,7 @@ feature 'User wants to create a file' do
file_name = find('#file_name') file_name = find('#file_name')
file_name.set options[:file_name] || 'README.md' file_name.set options[:file_name] || 'README.md'
file_content = find('#file-content') file_content = find('#file-content', visible: false)
file_content.set options[:file_content] || 'Some content' file_content.set options[:file_content] || 'Some content'
click_button 'Commit changes' click_button 'Commit changes'
......
...@@ -295,7 +295,7 @@ describe "Search" do ...@@ -295,7 +295,7 @@ describe "Search" do
fill_in 'search', with: 'foo' fill_in 'search', with: 'foo'
click_button 'Search' click_button 'Search'
expect(find('#group_id').value).to eq(project.namespace.id.to_s) expect(find('#group_id', visible: false).value).to eq(project.namespace.id.to_s)
end end
it 'preserves the project being searched in' do it 'preserves the project being searched in' do
...@@ -304,7 +304,7 @@ describe "Search" do ...@@ -304,7 +304,7 @@ describe "Search" do
fill_in 'search', with: 'foo' fill_in 'search', with: 'foo'
click_button 'Search' click_button 'Search'
expect(find('#project_id').value).to eq(project.id.to_s) expect(find('#project_id', visible: false).value).to eq(project.id.to_s)
end end
end end
end end
...@@ -3,25 +3,35 @@ require 'spec_helper' ...@@ -3,25 +3,35 @@ require 'spec_helper'
describe TreeHelper do describe TreeHelper do
describe 'flatten_tree' do describe 'flatten_tree' do
let(:project) { create(:project, :repository) } let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
let(:sha) { 'ce369011c189f62c815f5971d096b26759bab0d1' }
let(:tree) { repository.tree(sha, 'files') }
let(:root_path) { 'files' }
let(:tree_item) { tree.entries.find { |entry| entry.path == path } }
before do subject { flatten_tree(root_path, tree_item) }
@repository = project.repository
@commit = project.commit("e56497bb")
end
context "on a directory containing more than one file/directory" do context "on a directory containing more than one file/directory" do
let(:tree_item) { double(name: "files", path: "files") } let(:path) { 'files/html' }
it "returns the directory name" do it "returns the directory name" do
expect(flatten_tree(tree_item)).to match('files') expect(subject).to match('html')
end end
end end
context "on a directory containing only one directory" do context "on a directory containing only one directory" do
let(:tree_item) { double(name: "foo", path: "foo") } let(:path) { 'files/flat' }
it "returns the flattened path" do it "returns the flattened path" do
expect(flatten_tree(tree_item)).to match('foo/bar') expect(subject).to match('flat/path/correct')
end
context "with a nested root path" do
let(:root_path) { 'files/flat' }
it "returns the flattened path with the root path suffix removed" do
expect(subject).to match('path/correct')
end
end end
end end
end end
......
...@@ -8,7 +8,7 @@ describe('glDropdown', function describeDropdown() { ...@@ -8,7 +8,7 @@ describe('glDropdown', function describeDropdown() {
preloadFixtures('static/gl_dropdown.html.raw'); preloadFixtures('static/gl_dropdown.html.raw');
loadJSONFixtures('projects.json'); loadJSONFixtures('projects.json');
const NON_SELECTABLE_CLASSES = '.divider, .separator, .dropdown-header, .dropdown-menu-empty-link'; const NON_SELECTABLE_CLASSES = '.divider, .separator, .dropdown-header, .dropdown-menu-empty-item';
const SEARCH_INPUT_SELECTOR = '.dropdown-input-field'; const SEARCH_INPUT_SELECTOR = '.dropdown-input-field';
const ITEM_SELECTOR = `.dropdown-content li:not(${NON_SELECTABLE_CLASSES})`; const ITEM_SELECTOR = `.dropdown-content li:not(${NON_SELECTABLE_CLASSES})`;
const FOCUSED_ITEM_SELECTOR = `${ITEM_SELECTOR} a.is-focused`; const FOCUSED_ITEM_SELECTOR = `${ITEM_SELECTOR} a.is-focused`;
......
...@@ -20,6 +20,7 @@ describe Gitlab::Git::Tree, seed_helper: true do ...@@ -20,6 +20,7 @@ describe Gitlab::Git::Tree, seed_helper: true do
it { expect(dir.commit_id).to eq(SeedRepo::Commit::ID) } it { expect(dir.commit_id).to eq(SeedRepo::Commit::ID) }
it { expect(dir.name).to eq('encoding') } it { expect(dir.name).to eq('encoding') }
it { expect(dir.path).to eq('encoding') } it { expect(dir.path).to eq('encoding') }
it { expect(dir.flat_path).to eq('encoding') }
it { expect(dir.mode).to eq('40000') } it { expect(dir.mode).to eq('40000') }
context :subdir do context :subdir do
...@@ -30,6 +31,7 @@ describe Gitlab::Git::Tree, seed_helper: true do ...@@ -30,6 +31,7 @@ describe Gitlab::Git::Tree, seed_helper: true do
it { expect(subdir.commit_id).to eq(SeedRepo::Commit::ID) } it { expect(subdir.commit_id).to eq(SeedRepo::Commit::ID) }
it { expect(subdir.name).to eq('html') } it { expect(subdir.name).to eq('html') }
it { expect(subdir.path).to eq('files/html') } it { expect(subdir.path).to eq('files/html') }
it { expect(subdir.flat_path).to eq('files/html') }
end end
context :subdir_file do context :subdir_file do
...@@ -40,6 +42,7 @@ describe Gitlab::Git::Tree, seed_helper: true do ...@@ -40,6 +42,7 @@ describe Gitlab::Git::Tree, seed_helper: true do
it { expect(subdir_file.commit_id).to eq(SeedRepo::Commit::ID) } it { expect(subdir_file.commit_id).to eq(SeedRepo::Commit::ID) }
it { expect(subdir_file.name).to eq('popen.rb') } it { expect(subdir_file.name).to eq('popen.rb') }
it { expect(subdir_file.path).to eq('files/ruby/popen.rb') } it { expect(subdir_file.path).to eq('files/ruby/popen.rb') }
it { expect(subdir_file.flat_path).to eq('files/ruby/popen.rb') }
end end
end end
......
require 'spec_helper'
# For every API endpoint we test 3 states of wikis:
# - disabled
# - enabled only for team members
# - enabled for everyone who has access
# Every state is tested for 3 user roles:
# - guest
# - developer
# - master
# because they are 3 edge cases of using wiki pages.
describe API::Wikis do
let(:user) { create(:user) }
let(:payload) { { content: 'content', format: 'rdoc', title: 'title' } }
let(:expected_keys_with_content) { %w(content format slug title) }
let(:expected_keys_without_content) { %w(format slug title) }
shared_examples_for 'returns list of wiki pages' do
context 'when wiki has pages' do
let!(:pages) do
[create(:wiki_page, wiki: project.wiki, attrs: { title: 'page1', content: 'content of page1' }),
create(:wiki_page, wiki: project.wiki, attrs: { title: 'page2', content: 'content of page2' })]
end
it 'returns the list of wiki pages without content' do
get api(url, user)
expect(response).to have_http_status(200)
expect(json_response.size).to eq(2)
json_response.each_with_index do |page, index|
expect(page.keys).to match_array(expected_keys_without_content)
expect(page['slug']).to eq(pages[index].slug)
expect(page['title']).to eq(pages[index].title)
end
end
it 'returns the list of wiki pages with content' do
get api(url, user), with_content: 1
expect(response).to have_http_status(200)
expect(json_response.size).to eq(2)
json_response.each_with_index do |page, index|
expect(page.keys).to match_array(expected_keys_with_content)
expect(page['content']).to eq(pages[index].content)
expect(page['slug']).to eq(pages[index].slug)
expect(page['title']).to eq(pages[index].title)
end
end
end
it 'return the empty list of wiki pages' do
get api(url, user)
expect(response).to have_http_status(200)
expect(json_response.size).to eq(0)
end
end
shared_examples_for 'returns wiki page' do
it 'returns the wiki page' do
expect(response).to have_http_status(200)
expect(json_response.size).to eq(4)
expect(json_response.keys).to match_array(expected_keys_with_content)
expect(json_response['content']).to eq(page.content)
expect(json_response['slug']).to eq(page.slug)
expect(json_response['title']).to eq(page.title)
end
end
shared_examples_for 'creates wiki page' do
it 'creates the wiki page' do
post(api(url, user), payload)
expect(response).to have_http_status(201)
expect(json_response.size).to eq(4)
expect(json_response.keys).to match_array(expected_keys_with_content)
expect(json_response['content']).to eq(payload[:content])
expect(json_response['slug']).to eq(payload[:title].tr(' ', '-'))
expect(json_response['title']).to eq(payload[:title])
expect(json_response['rdoc']).to eq(payload[:rdoc])
end
[:title, :content].each do |part|
it "responds with validation error on empty #{part}" do
payload.delete(part)
post(api(url, user), payload)
expect(response).to have_http_status(400)
expect(json_response.size).to eq(1)
expect(json_response['error']).to eq("#{part} is missing")
end
end
end
shared_examples_for 'updates wiki page' do
it 'updates the wiki page' do
expect(response).to have_http_status(200)
expect(json_response.size).to eq(4)
expect(json_response.keys).to match_array(expected_keys_with_content)
expect(json_response['content']).to eq(payload[:content])
expect(json_response['slug']).to eq(payload[:title].tr(' ', '-'))
expect(json_response['title']).to eq(payload[:title])
end
end
shared_examples_for '403 Forbidden' do
it 'returns 403 Forbidden' do
expect(response).to have_http_status(403)
expect(json_response.size).to eq(1)
expect(json_response['message']).to eq('403 Forbidden')
end
end
shared_examples_for '404 Wiki Page Not Found' do
it 'returns 404 Wiki Page Not Found' do
expect(response).to have_http_status(404)
expect(json_response.size).to eq(1)
expect(json_response['message']).to eq('404 Wiki Page Not Found')
end
end
shared_examples_for '404 Project Not Found' do
it 'returns 404 Project Not Found' do
expect(response).to have_http_status(404)
expect(json_response.size).to eq(1)
expect(json_response['message']).to eq('404 Project Not Found')
end
end
shared_examples_for '204 No Content' do
it 'returns 204 No Content' do
expect(response).to have_http_status(204)
end
end
describe 'GET /projects/:id/wikis' do
let(:url) { "/projects/#{project.id}/wikis" }
context 'when wiki is disabled' do
let(:project) { create(:project, :wiki_disabled) }
context 'when user is guest' do
before do
get api(url)
end
include_examples '404 Project Not Found'
end
context 'when user is developer' do
before do
project.add_developer(user)
get api(url, user)
end
include_examples '403 Forbidden'
end
context 'when user is master' do
before do
project.add_master(user)
get api(url, user)
end
include_examples '403 Forbidden'
end
end
context 'when wiki is available only for team members' do
let(:project) { create(:project, :wiki_private) }
context 'when user is guest' do
before do
get api(url)
end
include_examples '404 Project Not Found'
end
context 'when user is developer' do
before do
project.add_developer(user)
end
include_examples 'returns list of wiki pages'
end
context 'when user is master' do
before do
project.add_master(user)
end
include_examples 'returns list of wiki pages'
end
end
context 'when wiki is available for everyone with access' do
let(:project) { create(:project) }
context 'when user is guest' do
before do
get api(url)
end
include_examples '404 Project Not Found'
end
context 'when user is developer' do
before do
project.add_developer(user)
end
include_examples 'returns list of wiki pages'
end
context 'when user is master' do
before do
project.add_master(user)
end
include_examples 'returns list of wiki pages'
end
end
end
describe 'GET /projects/:id/wikis/:slug' do
let(:page) { create(:wiki_page, wiki: project.wiki) }
let(:url) { "/projects/#{project.id}/wikis/#{page.slug}" }
context 'when wiki is disabled' do
let(:project) { create(:project, :wiki_disabled) }
context 'when user is guest' do
before do
get api(url)
end
include_examples '404 Project Not Found'
end
context 'when user is developer' do
before do
project.add_developer(user)
get api(url, user)
end
include_examples '403 Forbidden'
end
context 'when user is master' do
before do
project.add_master(user)
get api(url, user)
end
include_examples '403 Forbidden'
end
end
context 'when wiki is available only for team members' do
let(:project) { create(:project, :wiki_private) }
context 'when user is guest' do
before do
get api(url)
end
include_examples '404 Project Not Found'
end
context 'when user is developer' do
before do
project.add_developer(user)
get api(url, user)
end
include_examples 'returns wiki page'
context 'when page is not existing' do
let(:url) { "/projects/#{project.id}/wikis/unknown" }
include_examples '404 Wiki Page Not Found'
end
end
context 'when user is master' do
before do
project.add_master(user)
get api(url, user)
end
include_examples 'returns wiki page'
context 'when page is not existing' do
let(:url) { "/projects/#{project.id}/wikis/unknown" }
include_examples '404 Wiki Page Not Found'
end
end
end
context 'when wiki is available for everyone with access' do
let(:project) { create(:project) }
context 'when user is guest' do
before do
get api(url)
end
include_examples '404 Project Not Found'
end
context 'when user is developer' do
before do
project.add_developer(user)
get api(url, user)
end
include_examples 'returns wiki page'
context 'when page is not existing' do
let(:url) { "/projects/#{project.id}/wikis/unknown" }
include_examples '404 Wiki Page Not Found'
end
end
context 'when user is master' do
before do
project.add_master(user)
get api(url, user)
end
include_examples 'returns wiki page'
context 'when page is not existing' do
let(:url) { "/projects/#{project.id}/wikis/unknown" }
include_examples '404 Wiki Page Not Found'
end
end
end
end
describe 'POST /projects/:id/wikis' do
let(:payload) { { title: 'title', content: 'content' } }
let(:url) { "/projects/#{project.id}/wikis" }
context 'when wiki is disabled' do
let(:project) { create(:project, :wiki_disabled) }
context 'when user is guest' do
before do
post(api(url), payload)
end
include_examples '404 Project Not Found'
end
context 'when user is developer' do
before do
project.add_developer(user)
post(api(url, user), payload)
end
include_examples '403 Forbidden'
end
context 'when user is master' do
before do
project.add_master(user)
post(api(url, user), payload)
end
include_examples '403 Forbidden'
end
end
context 'when wiki is available only for team members' do
let(:project) { create(:project, :wiki_private) }
context 'when user is guest' do
before do
post(api(url), payload)
end
include_examples '404 Project Not Found'
end
context 'when user is developer' do
before do
project.add_developer(user)
end
include_examples 'creates wiki page'
end
context 'when user is master' do
before do
project.add_master(user)
end
include_examples 'creates wiki page'
end
end
context 'when wiki is available for everyone with access' do
let(:project) { create(:project) }
context 'when user is guest' do
before do
post(api(url), payload)
end
include_examples '404 Project Not Found'
end
context 'when user is developer' do
before do
project.add_developer(user)
end
include_examples 'creates wiki page'
end
context 'when user is master' do
before do
project.add_master(user)
end
include_examples 'creates wiki page'
end
end
end
describe 'PUT /projects/:id/wikis/:slug' do
let(:page) { create(:wiki_page, wiki: project.wiki) }
let(:payload) { { title: 'new title', content: 'new content' } }
let(:url) { "/projects/#{project.id}/wikis/#{page.slug}" }
context 'when wiki is disabled' do
let(:project) { create(:project, :wiki_disabled) }
context 'when user is guest' do
before do
put(api(url), payload)
end
include_examples '404 Project Not Found'
end
context 'when user is developer' do
before do
project.add_developer(user)
put(api(url, user), payload)
end
include_examples '403 Forbidden'
end
context 'when user is master' do
before do
project.add_master(user)
put(api(url, user), payload)
end
include_examples '403 Forbidden'
end
end
context 'when wiki is available only for team members' do
let(:project) { create(:project, :wiki_private) }
context 'when user is guest' do
before do
put(api(url), payload)
end
include_examples '404 Project Not Found'
end
context 'when user is developer' do
before do
project.add_developer(user)
put(api(url, user), payload)
end
include_examples 'updates wiki page'
context 'when page is not existing' do
let(:url) { "/projects/#{project.id}/wikis/unknown" }
include_examples '404 Wiki Page Not Found'
end
end
context 'when user is master' do
before do
project.add_master(user)
put(api(url, user), payload)
end
include_examples 'updates wiki page'
context 'when page is not existing' do
let(:url) { "/projects/#{project.id}/wikis/unknown" }
include_examples '404 Wiki Page Not Found'
end
end
end
context 'when wiki is available for everyone with access' do
let(:project) { create(:project) }
context 'when user is guest' do
before do
put(api(url), payload)
end
include_examples '404 Project Not Found'
end
context 'when user is developer' do
before do
project.add_developer(user)
put(api(url, user), payload)
end
include_examples 'updates wiki page'
context 'when page is not existing' do
let(:url) { "/projects/#{project.id}/wikis/unknown" }
include_examples '404 Wiki Page Not Found'
end
end
context 'when user is master' do
before do
project.add_master(user)
put(api(url, user), payload)
end
include_examples 'updates wiki page'
context 'when page is not existing' do
let(:url) { "/projects/#{project.id}/wikis/unknown" }
include_examples '404 Wiki Page Not Found'
end
end
end
end
describe 'DELETE /projects/:id/wikis/:slug' do
let(:page) { create(:wiki_page, wiki: project.wiki) }
let(:url) { "/projects/#{project.id}/wikis/#{page.slug}" }
context 'when wiki is disabled' do
let(:project) { create(:project, :wiki_disabled) }
context 'when user is guest' do
before do
delete(api(url))
end
include_examples '404 Project Not Found'
end
context 'when user is developer' do
before do
project.add_developer(user)
delete(api(url, user))
end
include_examples '403 Forbidden'
end
context 'when user is master' do
before do
project.add_master(user)
delete(api(url, user))
end
include_examples '403 Forbidden'
end
end
context 'when wiki is available only for team members' do
let(:project) { create(:project, :wiki_private) }
context 'when user is guest' do
before do
delete(api(url))
end
include_examples '404 Project Not Found'
end
context 'when user is developer' do
before do
project.add_developer(user)
delete(api(url, user))
end
include_examples '403 Forbidden'
end
context 'when user is master' do
before do
project.add_master(user)
delete(api(url, user))
end
include_examples '204 No Content'
end
end
context 'when wiki is available for everyone with access' do
let(:project) { create(:project) }
context 'when user is guest' do
before do
delete(api(url))
end
include_examples '404 Project Not Found'
end
context 'when user is developer' do
before do
project.add_developer(user)
delete(api(url, user))
end
include_examples '403 Forbidden'
end
context 'when user is master' do
before do
project.add_master(user)
delete(api(url, user))
end
include_examples '204 No Content'
context 'when page is not existing' do
let(:url) { "/projects/#{project.id}/wikis/unknown" }
include_examples '404 Wiki Page Not Found'
end
end
end
end
end
...@@ -3,3 +3,50 @@ https://gitlab.com/gitlab-org/Dockerfile. ...@@ -3,3 +3,50 @@ https://gitlab.com/gitlab-org/Dockerfile.
GitLab only mirrors the templates. Please submit your merge requests to GitLab only mirrors the templates. Please submit your merge requests to
https://gitlab.com/gitlab-org/Dockerfile. https://gitlab.com/gitlab-org/Dockerfile.
## Contributing
Thank you for your interest in contributing to this GitLab project! We welcome
all contributions. By participating in this project, you agree to abide by the
[code of conduct](#code-of-conduct).
## Contributor license agreement
By submitting code as an individual you agree to the [individual contributor
license agreement][individual-agreement].
By submitting code as an entity you agree to the [corporate contributor license
agreement][corporate-agreement].
## Code of conduct
As contributors and maintainers of this project, we pledge to respect all people
who contribute through reporting issues, posting feature requests, updating
documentation, submitting pull requests or patches, and other activities.
We are committed to making participation in this project a harassment-free
experience for everyone, regardless of level of experience, gender, gender
identity and expression, sexual orientation, disability, personal appearance,
body size, race, ethnicity, age, or religion.
Examples of unacceptable behavior by participants include the use of sexual
language or imagery, derogatory comments or personal attacks, trolling, public
or private harassment, insults, or other unprofessional conduct.
Project maintainers have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct. Project maintainers who do not follow the
Code of Conduct may be removed from the project team.
This code of conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community.
Instances of abusive, harassing, or otherwise unacceptable behavior can be
reported by emailing contact@gitlab.com.
This Code of Conduct is adapted from the [Contributor Covenant][contributor-covenant], version 1.1.0,
available at [http://contributor-covenant.org/version/1/1/0/](http://contributor-covenant.org/version/1/1/0/).
[contributor-covenant]: http://contributor-covenant.org
[individual-agreement]: https://docs.gitlab.com/ee/legal/individual_contributor_license_agreement.html
[corporate-agreement]: https://docs.gitlab.com/ee/legal/corporate_contributor_license_agreement.html
# Build and Release Folders # Build and Release Folders
bin/
bin-debug/ bin-debug/
bin-release/ bin-release/
[Oo]bj/ # FlashDevelop obj [Oo]bj/
[Bb]in/ # FlashDevelop bin [Bb]in/
# Other files and folders # Other files and folders
.settings/ .settings/
......
# Ignore configuration files that may contain sensitive information. # Ignore configuration files that may contain sensitive information.
sites/*/*settings*.php sites/*/*settings*.php
sites/example.sites.php
# Ignore paths that contain generated content. # Ignore paths that contain generated content.
files/ files/
sites/*/files sites/*/files
sites/*/private sites/*/private
sites/*/translations
# Ignore default text files # Ignore default text files
robots.txt robots.txt
...@@ -16,6 +18,7 @@ robots.txt ...@@ -16,6 +18,7 @@ robots.txt
/UPGRADE.txt /UPGRADE.txt
/README.txt /README.txt
sites/README.txt sites/README.txt
sites/all/libraries/README.txt
sites/all/modules/README.txt sites/all/modules/README.txt
sites/all/themes/README.txt sites/all/themes/README.txt
......
Java.gitignore
\ No newline at end of file
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
output/ output/
# Temporary file directory # Temporary file directory
tmp/ tmp/nanoc/
# Crash Log # Crash Log
crash.log crash.log
...@@ -29,7 +29,7 @@ bower_components ...@@ -29,7 +29,7 @@ bower_components
# node-waf configuration # node-waf configuration
.lock-wscript .lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html) # Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release build/Release
# Dependency directories # Dependency directories
......
...@@ -26,6 +26,8 @@ moc_*.cpp ...@@ -26,6 +26,8 @@ moc_*.cpp
moc_*.h moc_*.h
qrc_*.cpp qrc_*.cpp
ui_*.h ui_*.h
*.qmlc
*.jsc
Makefile* Makefile*
*build-* *build-*
......
...@@ -37,6 +37,7 @@ playground.xcworkspace ...@@ -37,6 +37,7 @@ playground.xcworkspace
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
# Packages/ # Packages/
# Package.pins # Package.pins
# Package.resolved
.build/ .build/
# CocoaPods # CocoaPods
......
# Compiled files # Compiled files
*.tfstate *.tfstate
*.tfstate.*.backup
*.tfstate.backup *.tfstate.backup
# Module directory # Module directory
......
...@@ -116,6 +116,10 @@ _TeamCity* ...@@ -116,6 +116,10 @@ _TeamCity*
# DotCover is a Code Coverage Tool # DotCover is a Code Coverage Tool
*.dotCover *.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Visual Studio code coverage results # Visual Studio code coverage results
*.coverage *.coverage
*.coveragexml *.coveragexml
......
# see https://docs.gitlab.com/ce/ci/yaml/README.html for all available options # Auto DevOps
# This CI/CD configuration provides a standard pipeline for
# * building a Docker image (using a buildpack if necessary),
# * storing the image in the container registry,
# * running tests from a buildpack,
# * running code quality analysis,
# * creating a review app for each topic branch,
# * and continuous deployment to production
#
# In order to deploy, you must have a Kubernetes cluster configured either
# via a project integration, or via group/project variables.
# AUTO_DEVOPS_DOMAIN must also be set as a variable at the group or project
# level, or manually added below.
#
# If you want to deploy to staging first, or enable canary deploys,
# uncomment the relevant jobs in the pipeline below.
#
# If Auto DevOps fails to detect the proper buildpack, or if you want to
# specify a custom buildpack, set a project variable `BUILDPACK_URL` to the
# repository URL of the buildpack.
# e.g. BUILDPACK_URL=https://github.com/heroku/heroku-buildpack-ruby.git#v142
# If you need multiple buildpacks, add a file to your project called
# `.buildpacks` that contains the URLs, one on each line, in order.
# Note: Auto CI does not work with multiple buildpacks yet
# you can delete this line if you're not using Docker image: alpine:latest
image: busybox:latest
before_script: variables:
- echo "Before script section" # AUTO_DEVOPS_DOMAIN is the application deployment domain and should be set as a variable at the group or project level.
- echo "For example you might run an update here or install a build dependency" # AUTO_DEVOPS_DOMAIN: domain.example.com
- echo "Or perhaps you might print out some debugging details"
POSTGRES_USER: user
POSTGRES_PASSWORD: testing-password
POSTGRES_ENABLED: "true"
POSTGRES_DB: $CI_ENVIRONMENT_SLUG
after_script: stages:
- echo "After script section" - build
- echo "For example you might do some cleanup here" - test
- review
- staging
- canary
- production
- cleanup
build1: build:
stage: build stage: build
image: docker:git
services:
- docker:dind
variables:
DOCKER_DRIVER: overlay2
script: script:
- echo "Do your build here" - setup_docker
- build
only:
- branches
test1: test:
services:
- postgres:latest
variables:
POSTGRES_DB: test
stage: test stage: test
image: gliderlabs/herokuish:latest
script: script:
- echo "Do a test here" - setup_test_db
- echo "For example run a test suite" - cp -R . /tmp/app
- /bin/herokuish buildpack test
only:
- branches
test2: codequality:
stage: test image: docker:latest
variables:
DOCKER_DRIVER: overlay2
allow_failure: true
services:
- docker:dind
script:
- setup_docker
- codeclimate
artifacts:
paths: [codeclimate.json]
review:
stage: review
script:
- check_kube_domain
- install_dependencies
- download_chart
- ensure_namespace
- install_tiller
- create_secret
- deploy
environment:
name: review/$CI_COMMIT_REF_NAME
url: http://$CI_PROJECT_PATH_SLUG-$CI_ENVIRONMENT_SLUG.$AUTO_DEVOPS_DOMAIN
on_stop: stop_review
only:
refs:
- branches
kubernetes: active
except:
- master
stop_review:
stage: cleanup
variables:
GIT_STRATEGY: none
script:
- install_dependencies
- delete
environment:
name: review/$CI_COMMIT_REF_NAME
action: stop
when: manual
allow_failure: true
only:
refs:
- branches
kubernetes: active
except:
- master
# Keys that start with a dot (.) will not be processed by GitLab CI.
# Staging and canary jobs are disabled by default, to enable them
# remove the dot (.) before the job name.
# https://docs.gitlab.com/ee/ci/yaml/README.html#hidden-keys
# Staging deploys are disabled by default since
# continuous deployment to production is enabled by default
# If you prefer to automatically deploy to staging and
# only manually promote to production, enable this job by removing the dot (.),
# and uncomment the `when: manual` line in the `production` job.
.staging:
stage: staging
script:
- check_kube_domain
- install_dependencies
- download_chart
- ensure_namespace
- install_tiller
- create_secret
- deploy
environment:
name: staging
url: http://$CI_PROJECT_PATH_SLUG-staging.$AUTO_DEVOPS_DOMAIN
only:
refs:
- master
kubernetes: active
# Canaries are disabled by default, but if you want them,
# and know what the downsides are, enable this job by removing the dot (.),
# and uncomment the `when: manual` line in the `production` job.
.canary:
stage: canary
script: script:
- echo "Do another parallel test here" - check_kube_domain
- echo "For example run a lint test" - install_dependencies
- download_chart
- ensure_namespace
- install_tiller
- create_secret
- deploy canary
environment:
name: production
url: http://$CI_PROJECT_PATH_SLUG.$AUTO_DEVOPS_DOMAIN
when: manual
only:
refs:
- master
kubernetes: active
# This job continuously deploys to production on every push to `master`.
# To make this a manual process, either because you're enabling `staging`
# or `canary` deploys, or you simply want more control over when you deploy
# to production, uncomment the `when: manual` line in the `production` job.
deploy1: production:
stage: deploy stage: production
script: script:
- echo "Do your deploy here" - check_kube_domain
\ No newline at end of file - install_dependencies
- download_chart
- ensure_namespace
- install_tiller
- create_secret
- deploy
- delete canary
environment:
name: production
url: http://$CI_PROJECT_PATH_SLUG.$AUTO_DEVOPS_DOMAIN
# when: manual
only:
refs:
- master
kubernetes: active
# ---------------------------------------------------------------------------
.auto_devops: &auto_devops |
# Auto DevOps variables and functions
[[ "$TRACE" ]] && set -x
auto_database_url=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${CI_ENVIRONMENT_SLUG}-postgres:5432/${POSTGRES_DB}
export DATABASE_URL=${DATABASE_URL-$auto_database_url}
export CI_APPLICATION_REPOSITORY=$CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG
export CI_APPLICATION_TAG=$CI_COMMIT_SHA
export CI_CONTAINER_NAME=ci_job_build_${CI_JOB_ID}
export TILLER_NAMESPACE=$KUBE_NAMESPACE
function codeclimate() {
cc_opts="--env CODECLIMATE_CODE="$PWD" \
--volume "$PWD":/code \
--volume /var/run/docker.sock:/var/run/docker.sock \
--volume /tmp/cc:/tmp/cc"
docker run ${cc_opts} codeclimate/codeclimate init
docker run ${cc_opts} codeclimate/codeclimate analyze -f json > codeclimate.json
}
function deploy() {
track="${1-stable}"
name="$CI_ENVIRONMENT_SLUG"
if [[ "$track" != "stable" ]]; then
name="$name-$track"
fi
replicas="1"
service_enabled="false"
postgres_enabled="$POSTGRES_ENABLED"
# canary uses stable db
[[ "$track" == "canary" ]] && postgres_enabled="false"
env_track=$( echo $track | tr -s '[:lower:]' '[:upper:]' )
env_slug=$( echo ${CI_ENVIRONMENT_SLUG//-/_} | tr -s '[:lower:]' '[:upper:]' )
if [[ "$track" == "stable" ]]; then
# for stable track get number of replicas from `PRODUCTION_REPLICAS`
eval new_replicas=\$${env_slug}_REPLICAS
service_enabled="true"
else
# for all tracks get number of replicas from `CANARY_PRODUCTION_REPLICAS`
eval new_replicas=\$${env_track}_${env_slug}_REPLICAS
fi
if [[ -n "$new_replicas" ]]; then
replicas="$new_replicas"
fi
helm upgrade --install \
--wait \
--set service.enabled="$service_enabled" \
--set releaseOverride="$CI_ENVIRONMENT_SLUG" \
--set image.repository="$CI_APPLICATION_REPOSITORY" \
--set image.tag="$CI_APPLICATION_TAG" \
--set image.pullPolicy=IfNotPresent \
--set application.track="$track" \
--set application.database_url="$DATABASE_URL" \
--set service.url="$CI_ENVIRONMENT_URL" \
--set replicaCount="$replicas" \
--set postgresql.enabled="$postgres_enabled" \
--set postgresql.nameOverride="postgres" \
--set postgresql.postgresUser="$POSTGRES_USER" \
--set postgresql.postgresPassword="$POSTGRES_PASSWORD" \
--set postgresql.postgresDatabase="$POSTGRES_DB" \
--namespace="$KUBE_NAMESPACE" \
--version="$CI_PIPELINE_ID-$CI_JOB_ID" \
"$name" \
chart/
if [[ "$track" == "stable" ]]; then
kubectl rollout status -n "$KUBE_NAMESPACE" -w "deployment/${CI_ENVIRONMENT_SLUG}-auto-deploy"
fi
}
function install_dependencies() {
apk add -U openssl curl tar gzip bash ca-certificates git
wget -q -O /etc/apk/keys/sgerrand.rsa.pub https://raw.githubusercontent.com/sgerrand/alpine-pkg-glibc/master/sgerrand.rsa.pub
wget https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.23-r3/glibc-2.23-r3.apk
apk add glibc-2.23-r3.apk
rm glibc-2.23-r3.apk
curl https://kubernetes-helm.storage.googleapis.com/helm-v2.6.1-linux-amd64.tar.gz | tar zx
mv linux-amd64/helm /usr/bin/
helm version --client
curl -L -o /usr/bin/kubectl https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl
chmod +x /usr/bin/kubectl
kubectl version --client
}
function setup_docker() {
if ! docker info &>/dev/null; then
if [ -z "$DOCKER_HOST" -a "$KUBERNETES_PORT" ]; then
export DOCKER_HOST='tcp://localhost:2375'
fi
fi
}
function setup_test_db() {
if [ -z ${KUBERNETES_PORT+x} ]; then
DB_HOST=postgres
else
DB_HOST=localhost
fi
export DATABASE_URL="postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${DB_HOST}:5432/${POSTGRES_DB}"
}
function download_chart() {
if [[ ! -d chart ]]; then
auto_chart=${AUTO_DEVOPS_CHART:-gitlab/auto-deploy-app}
auto_chart_name=$(basename $auto_chart)
auto_chart_name=${auto_chart_name%.tgz}
else
auto_chart="chart"
auto_chart_name="chart"
fi
helm init --client-only
helm repo add gitlab https://charts.gitlab.io
if [[ ! -d "$auto_chart" ]]; then
helm fetch ${auto_chart} --untar
fi
if [ "$auto_chart_name" != "chart" ]; then
mv ${auto_chart_name} chart
fi
helm dependency update chart/
helm dependency build chart/
}
function ensure_namespace() {
kubectl describe namespace "$KUBE_NAMESPACE" || kubectl create namespace "$KUBE_NAMESPACE"
}
function check_kube_domain() {
if [ -z ${AUTO_DEVOPS_DOMAIN+x} ]; then
echo "In order to deploy, AUTO_DEVOPS_DOMAIN must be set as a variable at the group or project level, or manually added in .gitlab-cy.yml"
false
else
true
fi
}
function build() {
if [[ -f Dockerfile ]]; then
echo "Building Dockerfile-based application..."
docker build -t "$CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG" .
else
echo "Building Heroku-based application using gliderlabs/herokuish docker image..."
docker run -i --name="$CI_CONTAINER_NAME" -v "$(pwd):/tmp/app:ro" gliderlabs/herokuish /bin/herokuish buildpack build
docker commit "$CI_CONTAINER_NAME" "$CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG"
docker rm "$CI_CONTAINER_NAME" >/dev/null
echo ""
echo "Configuring $CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG docker image..."
docker create --expose 5000 --env PORT=5000 --name="$CI_CONTAINER_NAME" "$CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG" /bin/herokuish procfile start web
docker commit "$CI_CONTAINER_NAME" "$CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG"
docker rm "$CI_CONTAINER_NAME" >/dev/null
echo ""
fi
if [[ -n "$CI_REGISTRY_USER" ]]; then
echo "Logging to GitLab Container Registry with CI credentials..."
docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
echo ""
fi
echo "Pushing to GitLab Container Registry..."
docker push "$CI_APPLICATION_REPOSITORY:$CI_APPLICATION_TAG"
echo ""
}
function install_tiller() {
echo "Checking Tiller..."
helm init --upgrade
kubectl rollout status -n "$TILLER_NAMESPACE" -w "deployment/tiller-deploy"
if ! helm version --debug; then
echo "Failed to init Tiller."
return 1
fi
echo ""
}
function create_secret() {
kubectl create secret -n "$KUBE_NAMESPACE" \
docker-registry gitlab-registry \
--docker-server="$CI_REGISTRY" \
--docker-username="$CI_REGISTRY_USER" \
--docker-password="$CI_REGISTRY_PASSWORD" \
--docker-email="$GITLAB_USER_EMAIL" \
-o yaml --dry-run | kubectl replace -n "$KUBE_NAMESPACE" --force -f -
}
function delete() {
track="${1-stable}"
name="$CI_ENVIRONMENT_SLUG"
if [[ "$track" != "stable" ]]; then
name="$name-$track"
fi
helm delete "$name" || true
}
before_script:
- *auto_devops
image:
name: hashicorp/packer:1.0.4
entrypoint:
- '/usr/bin/env'
- 'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
before_script:
- packer --version
stages:
- validate
- deploy
validate:
stage: validate
script:
- find . -maxdepth 1 -name '*.json' -print0 | xargs -t0n1 packer validate
build:
stage: deploy
environment: production
script:
- find . -maxdepth 1 -name '*.json' -print0 | xargs -t0n1 packer build
when: manual
only:
- master
# Official image for Hashicorp's Terraform. It uses light image which is Alpine
# based as it is much lighter.
#
# Entrypoint is also needed as image by default set `terraform` binary as an
# entrypoint.
image:
name: hashicorp/terraform:light
entrypoint:
- '/usr/bin/env'
- 'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
# Default output file for Terraform plan
variables:
PLAN: plan.tfplan
cache:
paths:
- .terraform
before_script:
- terraform --version
- terraform init
stages:
- validate
- build
- deploy
validate:
stage: validate
script:
- terraform validate
plan:
stage: build
script:
- terraform plan -out=$PLAN
artifacts:
name: plan
paths:
- $PLAN
# Separate apply job for manual launching Terraform as it can be destructive
# action.
apply:
stage: deploy
environment:
name: production
script:
- terraform apply -input=false $PLAN
dependencies:
- plan
when: manual
only:
- master
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