diff options
91 files changed, 1915 insertions, 262 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b76c8f00d77..7b42e661dff 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -247,7 +247,6 @@ setup-test-env: script: - node --version - yarn install --frozen-lockfile --cache-folder .yarn-cache - - bundle exec rake gettext:po_to_json - bundle exec rake gitlab:assets:compile - bundle exec ruby -Ispec -e 'require "spec_helper" ; TestEnv.init' - scripts/gitaly-test-build # Do not use 'bundle exec' here @@ -412,7 +411,6 @@ db:migrate:reset-mysql: .migration-paths: &migration-paths <<: *dedicated-runner - <<: *only-canonical-masters <<: *pull-cache stage: test variables: @@ -496,7 +494,6 @@ gitlab:assets:compile: NO_COMPRESSION: "true" script: - yarn install --frozen-lockfile --production --cache-folder .yarn-cache - - bundle exec rake gettext:po_to_json - bundle exec rake gitlab:assets:compile artifacts: name: webpack-report diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 0f1a7dfc7c4..ca75280b09b 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -0.37.0 +0.38.0 @@ -31,7 +31,7 @@ gem 'omniauth-cas3', '~> 1.1.4' gem 'omniauth-facebook', '~> 4.0.0' gem 'omniauth-github', '~> 1.1.1' 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-oauth2-generic', '~> 0.2.2' gem 'omniauth-saml', '~> 1.7.0' @@ -324,7 +324,7 @@ group :development, :test do # Generate Fake data gem 'ffaker', '~> 2.4' - gem 'capybara', '~> 2.6.2' + gem 'capybara', '~> 2.15.0' gem 'capybara-screenshot', '~> 1.0.0' gem 'poltergeist', '~> 1.9.0' @@ -397,7 +397,7 @@ group :ed25519 do end # 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 diff --git a/Gemfile.lock b/Gemfile.lock index 320d42b8974..9fbf08d80e2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -100,9 +100,9 @@ GEM bundler (~> 1.2) thor (~> 0.18) byebug (9.0.6) - capybara (2.6.2) + capybara (2.15.1) addressable - mime-types (>= 1.16) + mini_mime (>= 0.1.3) nokogiri (>= 1.3.3) rack (>= 1.0.0) rack-test (>= 0.5.4) @@ -275,7 +275,7 @@ GEM po_to_json (>= 1.0.0) rails (>= 3.2.0) gherkin-ruby (0.3.2) - gitaly-proto (0.32.0) + gitaly-proto (0.33.0) google-protobuf (~> 3.1) grpc (~> 1.0) github-linguist (4.7.6) @@ -479,6 +479,7 @@ GEM method_source (0.8.2) mime-types (2.99.3) mimemagic (0.3.0) + mini_mime (0.1.4) mini_portile2 (2.2.0) minitest (5.7.0) mmap2 (2.2.7) @@ -529,8 +530,8 @@ GEM omniauth-gitlab (1.0.2) omniauth (~> 1.0) omniauth-oauth2 (~> 1.0) - omniauth-google-oauth2 (0.4.1) - jwt (~> 1.5.2) + omniauth-google-oauth2 (0.5.2) + jwt (~> 1.5) multi_json (~> 1.3) omniauth (>= 1.1.1) omniauth-oauth2 (>= 1.3.1) @@ -948,7 +949,7 @@ GEM expression_parser rinku xml-simple (1.1.5) - xpath (2.0.0) + xpath (2.1.0) nokogiri (~> 1.3) PLATFORMS @@ -979,7 +980,7 @@ DEPENDENCIES browser (~> 2.2) bullet (~> 5.5.0) bundler-audit (~> 0.5.0) - capybara (~> 2.6.2) + capybara (~> 2.15.0) capybara-screenshot (~> 1.0.0) carrierwave (~> 1.1) charlock_holmes (~> 0.7.5) @@ -1021,7 +1022,7 @@ DEPENDENCIES gettext (~> 3.2.2) gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails_js (~> 1.2.0) - gitaly-proto (~> 0.32.0) + gitaly-proto (~> 0.33.0) github-linguist (~> 4.7.0) gitlab-flowdock-git-hook (~> 1.0.1) gitlab-markup (~> 1.5.1) @@ -1075,7 +1076,7 @@ DEPENDENCIES omniauth-facebook (~> 4.0.0) omniauth-github (~> 1.1.1) omniauth-gitlab (~> 1.0.2) - omniauth-google-oauth2 (~> 0.4.1) + omniauth-google-oauth2 (~> 0.5.2) omniauth-kerberos (~> 0.3.0) omniauth-oauth2-generic (~> 0.2.2) omniauth-saml (~> 1.7.0) diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index 6f7671aa6fe..50d822eba5a 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -175,7 +175,7 @@ GitLabDropdownFilter = (function() { 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() { 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)"; @@ -702,7 +702,7 @@ GitLabDropdown = (function() { GitLabDropdown.prototype.noResults = function() { 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) { diff --git a/app/assets/javascripts/search_autocomplete.js b/app/assets/javascripts/search_autocomplete.js index 6fd5345a0a6..003a15592f3 100644 --- a/app/assets/javascripts/search_autocomplete.js +++ b/app/assets/javascripts/search_autocomplete.js @@ -363,7 +363,7 @@ restoreMenu() { 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); } diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index 5f397f08936..d4a1bb8402c 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -163,12 +163,6 @@ } } - &.dropdown-menu-empty-link { - &.is-focused { - background-color: $dropdown-empty-row-bg; - } - } - &.dropdown-menu-user-link { line-height: 16px; } @@ -256,6 +250,13 @@ @include dropdown-link; } + .dropdown-menu-empty-item a { + &:hover, + &:focus { + background-color: transparent; + } + } + .dropdown-header { color: $gl-text-color-secondary; font-size: 13px; @@ -800,6 +801,13 @@ } } } + + &.dropdown-menu-empty-item a { + &:hover, + &:focus { + background-color: transparent; + } + } } &.dropdown-menu-selectable { diff --git a/app/assets/stylesheets/new_nav.scss b/app/assets/stylesheets/new_nav.scss index e4b52ab480d..2b6c0fc015c 100644 --- a/app/assets/stylesheets/new_nav.scss +++ b/app/assets/stylesheets/new_nav.scss @@ -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 { margin-top: 3px; background-color: $indigo-100; diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 8cbf0ec6180..a7acaf6c728 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -578,12 +578,12 @@ @media (min-width: $screen-sm-min) { position: -webkit-sticky; position: sticky; - top: 34px; + top: 24px; background-color: $white-light; z-index: 190; &.diff-files-changed-merge-request { - top: 84px; + top: 76px; } + .files, @@ -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 { width: 450px; z-index: 150; diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 9f2cb979518..d8a15faf7e9 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -220,7 +220,7 @@ .right-sidebar { position: absolute; - top: $header-height; + top: $new-navbar-height; bottom: 0; right: 0; transition: width .3s; @@ -230,7 +230,7 @@ .issuable-sidebar { width: calc(100% + 100px); - height: calc(100% - #{$header-height}); + height: calc(100% - #{$new-navbar-height}); overflow-y: scroll; overflow-x: hidden; -webkit-overflow-scrolling: touch; @@ -479,10 +479,10 @@ } .with-performance-bar .right-sidebar { - top: $header-height + $performance-bar-height; + top: $new-navbar-height + $performance-bar-height; .issuable-sidebar { - height: calc(100% - #{$header-height} - #{$performance-bar-height}); + height: calc(100% - #{$new-navbar-height} - #{$performance-bar-height}); } } diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 8609f72bdab..439636fe026 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -645,7 +645,7 @@ } .merge-request-tabs-holder { - top: $header-height; + top: $new-navbar-height; z-index: 200; background-color: $white-light; border-bottom: 1px solid $border-color; @@ -675,7 +675,7 @@ } .with-performance-bar .merge-request-tabs-holder { - top: $header-height + $performance-bar-height; + top: $new-navbar-height + $performance-bar-height; } .merge-request-tabs { diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index 8367c22d1ca..4dfb397e82c 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -20,8 +20,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController def usage_data respond_to do |format| format.html do - usage_data = Gitlab::UsageData.data - usage_data_json = params[:pretty] ? JSON.pretty_generate(usage_data) : usage_data.to_json + usage_data_json = JSON.pretty_generate(Gitlab::UsageData.data) render html: Gitlab::Highlight.highlight('payload.json', usage_data_json) end diff --git a/app/helpers/tree_helper.rb b/app/helpers/tree_helper.rb index e0d3e9b88f3..95dbdc8ec46 100644 --- a/app/helpers/tree_helper.rb +++ b/app/helpers/tree_helper.rb @@ -99,7 +99,9 @@ module TreeHelper end # 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) if subtree.count == 1 && subtree.first.dir? return tree_join(tree.name, flatten_tree(subtree.first)) diff --git a/app/views/admin/cohorts/_usage_ping.html.haml b/app/views/admin/cohorts/_usage_ping.html.haml index 73aa95d84f1..3dda386fcf7 100644 --- a/app/views/admin/cohorts/_usage_ping.html.haml +++ b/app/views/admin/cohorts/_usage_ping.html.haml @@ -7,4 +7,4 @@ = succeed '.' do = 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) } } diff --git a/app/views/dashboard/_groups_head.html.haml b/app/views/dashboard/_groups_head.html.haml index 11bf3f5d323..7981daa0705 100644 --- a/app/views/dashboard/_groups_head.html.haml +++ b/app/views/dashboard/_groups_head.html.haml @@ -1,7 +1,3 @@ -- if current_user.can_create_group? - - content_for :breadcrumbs_extra do - = link_to "New group", new_group_path, class: "btn btn-new" - .top-area %ul.nav-links = nav_link(page: dashboard_groups_path) do @@ -10,8 +6,8 @@ = nav_link(page: explore_groups_path) do = link_to explore_groups_path, title: 'Explore public groups' do Explore public groups - .nav-controls.nav-controls-new-nav + .nav-controls = render 'shared/groups/search_form' = render 'shared/groups/dropdown' - 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" diff --git a/app/views/dashboard/_projects_head.html.haml b/app/views/dashboard/_projects_head.html.haml index e2a1914ada2..fd2ba9ac1ca 100644 --- a/app/views/dashboard/_projects_head.html.haml +++ b/app/views/dashboard/_projects_head.html.haml @@ -1,10 +1,6 @@ = content_for :flash_message do = 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 .fade-left= icon('angle-left') .fade-right= icon('angle-right') @@ -19,8 +15,8 @@ = link_to explore_root_path, title: 'Explore', data: {placement: 'right'} do Explore projects - .nav-controls.nav-controls-new-nav + .nav-controls = render 'shared/projects/search_form' = render 'shared/projects/dropdown' - 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" diff --git a/app/views/dashboard/_snippets_head.html.haml b/app/views/dashboard/_snippets_head.html.haml index 14c18678ab1..7330f4cb523 100644 --- a/app/views/dashboard/_snippets_head.html.haml +++ b/app/views/dashboard/_snippets_head.html.haml @@ -1,7 +1,3 @@ -- if current_user - - content_for :breadcrumbs_extra do - = link_to "New snippet", new_snippet_path, class: "btn btn-new", title: "New snippet" - .top-area %ul.nav-links = nav_link(page: dashboard_snippets_path, html_options: {class: 'home'}) do @@ -10,3 +6,7 @@ = nav_link(page: explore_snippets_path) do = link_to explore_snippets_path, title: 'Explore snippets', data: {placement: 'right'} do Explore Snippets + + - if current_user + .nav-controls.hidden-xs + = link_to "New snippet", new_snippet_path, class: "btn btn-new", title: "New snippet" diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml index ad0e205a79f..42941acc508 100644 --- a/app/views/dashboard/issues.html.haml +++ b/app/views/dashboard/issues.html.haml @@ -4,14 +4,9 @@ = content_for :meta_tags do = 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 = 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 = icon('rss') = render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", with_feature_enabled: 'issues', type: :issues diff --git a/app/views/dashboard/merge_requests.html.haml b/app/views/dashboard/merge_requests.html.haml index ccc74f7cf3d..53cd1130299 100644 --- a/app/views/dashboard/merge_requests.html.haml +++ b/app/views/dashboard/merge_requests.html.haml @@ -2,12 +2,9 @@ - page_title "Merge Requests" - 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 = 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/issuable/filter', type: :merge_requests diff --git a/app/views/dashboard/milestones/index.html.haml b/app/views/dashboard/milestones/index.html.haml index 9fffdded1a0..f66e2b40d76 100644 --- a/app/views/dashboard/milestones/index.html.haml +++ b/app/views/dashboard/milestones/index.html.haml @@ -2,13 +2,10 @@ - page_title 'Milestones' - 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 = 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 .milestones diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml index 13a4b4c90c9..7f411927429 100644 --- a/app/views/groups/issues.html.haml +++ b/app/views/groups/issues.html.haml @@ -9,17 +9,9 @@ = 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 .top-area = render 'shared/issuable/nav', type: :issues - .nav-controls.visible-xs + .nav-controls = link_to params.merge(rss_url_options), class: 'btn' do = icon('rss') %span.icon-label diff --git a/app/views/groups/labels/index.html.haml b/app/views/groups/labels/index.html.haml index 9e59a09d459..89165096fe2 100644 --- a/app/views/groups/labels/index.html.haml +++ b/app/views/groups/labels/index.html.haml @@ -1,7 +1,4 @@ - 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" @@ -10,7 +7,7 @@ .nav-text 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) = link_to "New label", new_group_label_path(@group), class: "btn btn-new" diff --git a/app/views/groups/merge_requests.html.haml b/app/views/groups/merge_requests.html.haml index 0344770e0dd..e56dc1fb9c2 100644 --- a/app/views/groups/merge_requests.html.haml +++ b/app/views/groups/merge_requests.html.haml @@ -4,17 +4,13 @@ = webpack_bundle_tag 'common_vue' = 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? = render 'shared/empty_states/merge_requests', project_select_button: true - else .top-area = render 'shared/issuable/nav', type: :merge_requests - 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/issuable/search_bar', type: :merge_requests diff --git a/app/views/groups/milestones/index.html.haml b/app/views/groups/milestones/index.html.haml index 6e7a1af243d..ed582e521c4 100644 --- a/app/views/groups/milestones/index.html.haml +++ b/app/views/groups/milestones/index.html.haml @@ -1,14 +1,11 @@ - 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" .top-area = render 'shared/milestones_filter', counts: @milestone_states - .nav-controls.visible-xs + .nav-controls - if can?(current_user, :admin_milestones, @group) = link_to "New milestone", new_group_milestone_path(@group), class: "btn btn-new" diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml index 59f16b47bf7..cd7a47da4a1 100644 --- a/app/views/layouts/_search.html.haml +++ b/app/views/layouts/_search.html.haml @@ -17,8 +17,8 @@ .dropdown-menu.dropdown-select = dropdown_content do %ul - %li - %a.is-focused.dropdown-menu-empty-link + %li.dropdown-menu-empty-item + %a Loading... = dropdown_loading %i.search-icon diff --git a/app/views/layouts/nav/_breadcrumbs.html.haml b/app/views/layouts/nav/_breadcrumbs.html.haml index feffd7707dc..7bd3f5306a2 100644 --- a/app/views/layouts/nav/_breadcrumbs.html.haml +++ b/app/views/layouts/nav/_breadcrumbs.html.haml @@ -2,7 +2,7 @@ - hide_top_links = @hide_top_links || false %nav.breadcrumbs{ role: "navigation", class: [container, @content_class] } - .breadcrumbs-container{ class: [container, @content_class] } + .breadcrumbs-container - if defined?(@left_sidebar) = button_tag class: 'toggle-mobile-nav', type: 'button' do %span.sr-only Open sidebar @@ -17,6 +17,4 @@ = render "layouts/nav/breadcrumbs/collapsed_dropdown", location: :after %li %h2.breadcrumbs-sub-title= @breadcrumb_title - - if content_for?(:breadcrumbs_extra) - .breadcrumbs-extra.hidden-xs= yield :breadcrumbs_extra = yield :header_content diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml index 35ad280b037..79f334176a5 100644 --- a/app/views/profiles/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -2,7 +2,7 @@ - @content_class = "limit-container-width" unless fluid_layout = 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) .row diff --git a/app/views/projects/diffs/_stats.html.haml b/app/views/projects/diffs/_stats.html.haml index 02fd54c97fb..ad2d355ab4a 100644 --- a/app/views/projects/diffs/_stats.html.haml +++ b/app/views/projects/diffs/_stats.html.haml @@ -29,6 +29,6 @@ +#{diff_file.added_lines} %span.cred< \-#{diff_file.removed_lines} - %li.dropdown-menu-empty-link.hidden - %a{ href: "#" } + %li.dropdown-menu-empty-item.hidden + %a No files found. diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml index 6fcb5975707..e72c94695bc 100644 --- a/app/views/projects/issues/index.html.haml +++ b/app/views/projects/issues/index.html.haml @@ -13,14 +13,11 @@ = content_for :meta_tags do = 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? %div{ class: (container_class) } .top-area = render 'shared/issuable/nav', type: :issues - .nav-controls.visible-xs + .nav-controls = render "projects/issues/nav_btns" = render 'shared/issuable/search_bar', type: :issues diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml index ec9e8444ac5..10d07ce8e45 100644 --- a/app/views/projects/labels/index.html.haml +++ b/app/views/projects/labels/index.html.haml @@ -3,10 +3,6 @@ - hide_class = '' - 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" - if @labels.exists? || @prioritized_labels.exists? @@ -18,7 +14,7 @@ Star a label to make it a priority label. Order the prioritized labels to change their relative priority, by dragging. - if can_admin_label - .nav-controls.visible-xs + .nav-controls = link_to new_project_label_path(@project), class: "btn btn-new" do New label diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml index fae6f34b062..2c53891a92d 100644 --- a/app/views/projects/merge_requests/index.html.haml +++ b/app/views/projects/merge_requests/index.html.haml @@ -12,9 +12,6 @@ = webpack_bundle_tag 'common_vue' = 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' - if @project.merge_requests.exists? @@ -23,7 +20,7 @@ = render 'shared/auto_devops_callout' .top-area = 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 'shared/issuable/search_bar', type: :merge_requests diff --git a/app/views/projects/milestones/index.html.haml b/app/views/projects/milestones/index.html.haml index 71ec88ef1c1..f3abecdd302 100644 --- a/app/views/projects/milestones/index.html.haml +++ b/app/views/projects/milestones/index.html.haml @@ -1,20 +1,16 @@ - @no_container = true - 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" %div{ class: container_class } .top-area = render 'shared/milestones_filter', counts: milestone_counts(@project.milestones) - .nav-controls.nav-controls-new-nav + .nav-controls = render 'shared/milestones_sort_dropdown' - 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 .milestones diff --git a/app/views/projects/pipeline_schedules/index.html.haml b/app/views/projects/pipeline_schedules/index.html.haml index d9957b54a4d..2b081786b6a 100644 --- a/app/views/projects/pipeline_schedules/index.html.haml +++ b/app/views/projects/pipeline_schedules/index.html.haml @@ -7,10 +7,6 @@ - @no_container = true - 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" %div{ class: container_class } @@ -20,7 +16,7 @@ = render "tabs", schedule_path_proc: schedule_path_proc, all_schedules: @all_schedules, scope: @scope - 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 %span= _('New schedule') diff --git a/app/views/projects/snippets/index.html.haml b/app/views/projects/snippets/index.html.haml index 1803e7f7211..65efc083fdd 100644 --- a/app/views/projects/snippets/index.html.haml +++ b/app/views/projects/snippets/index.html.haml @@ -1,15 +1,11 @@ - 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 .top-area - include_private = @project.team.member?(current_user) || current_user.admin? = 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) = link_to "New snippet", new_project_snippet_path(@project), class: "btn btn-new", title: "New snippet" diff --git a/app/views/projects/tree/_tree_item.html.haml b/app/views/projects/tree/_tree_item.html.haml index 0c9c8750f2c..56197382a70 100644 --- a/app/views/projects/tree/_tree_item.html.haml +++ b/app/views/projects/tree/_tree_item.html.haml @@ -1,7 +1,7 @@ %tr{ class: "tree-item #{tree_hex_class(tree_item)}" } %td.tree-item-file-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 %span.str-truncated= path %td.hidden-xs.tree-commit diff --git a/changelogs/unreleased/37023-remove-focus-styles-from-dropdown-empty-link.yml b/changelogs/unreleased/37023-remove-focus-styles-from-dropdown-empty-link.yml new file mode 100644 index 00000000000..fcaa6ec13f8 --- /dev/null +++ b/changelogs/unreleased/37023-remove-focus-styles-from-dropdown-empty-link.yml @@ -0,0 +1,5 @@ +--- +title: Remove focus styles from dropdown empty links +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/37499-add-description-template-examples-to-the-docs.yml b/changelogs/unreleased/37499-add-description-template-examples-to-the-docs.yml new file mode 100644 index 00000000000..2a8e8a33225 --- /dev/null +++ b/changelogs/unreleased/37499-add-description-template-examples-to-the-docs.yml @@ -0,0 +1,5 @@ +--- +title: Add description template examples to documentation +merge_request: +author: +type: other diff --git a/changelogs/unreleased/add_quick_submission_on_user_settings_page.yml b/changelogs/unreleased/add_quick_submission_on_user_settings_page.yml new file mode 100644 index 00000000000..3e4105a4232 --- /dev/null +++ b/changelogs/unreleased/add_quick_submission_on_user_settings_page.yml @@ -0,0 +1,5 @@ +--- +title: Add quick submission on user settings page +merge_request: 14007 +author: Vitaliy @blackst0ne Klachkov +type: added diff --git a/changelogs/unreleased/wiki_api.yml b/changelogs/unreleased/wiki_api.yml new file mode 100644 index 00000000000..9d60356aedc --- /dev/null +++ b/changelogs/unreleased/wiki_api.yml @@ -0,0 +1,5 @@ +--- +title: Add API support for wiki pages +merge_request: 13372 +author: Vitaliy @blackst0ne Klachkov +type: added diff --git a/db/migrate/20170825104051_migrate_issues_to_ghost_user.rb b/db/migrate/20170825104051_migrate_issues_to_ghost_user.rb index 294141e4fdb..c5fb5762d61 100644 --- a/db/migrate/20170825104051_migrate_issues_to_ghost_user.rb +++ b/db/migrate/20170825104051_migrate_issues_to_ghost_user.rb @@ -18,6 +18,7 @@ class MigrateIssuesToGhostUser < ActiveRecord::Migration ActiveRecord::Base.clear_cache! ::User.reset_column_information + ::Namespace.reset_column_information end def up diff --git a/db/migrate/20170828093725_create_project_auto_dev_ops.rb b/db/migrate/20170828093725_create_project_auto_dev_ops.rb index 00bf29f874c..c1bb4f20c1d 100644 --- a/db/migrate/20170828093725_create_project_auto_dev_ops.rb +++ b/db/migrate/20170828093725_create_project_auto_dev_ops.rb @@ -3,19 +3,14 @@ class CreateProjectAutoDevOps < ActiveRecord::Migration DOWNTIME = false - disable_ddl_transaction! - def up create_table :project_auto_devops do |t| - t.belongs_to :project, null: false, index: { unique: true } + t.belongs_to :project, null: false, index: { unique: true }, foreign_key: { on_delete: :cascade } + t.datetime_with_timezone :created_at, null: false + t.datetime_with_timezone :updated_at, null: false t.boolean :enabled, default: nil, null: true t.string :domain end - - add_timestamps_with_timezone(:project_auto_devops, null: false) - - # No need to check for violations as its a new table - add_concurrent_foreign_key(:project_auto_devops, :projects, column: :project_id) end def down diff --git a/db/schema.rb b/db/schema.rb index 73e4993e422..71417f586aa 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -1130,10 +1130,10 @@ ActiveRecord::Schema.define(version: 20170905112933) do create_table "project_auto_devops", force: :cascade do |t| t.integer "project_id", null: false - t.boolean "enabled" - t.string "domain" t.datetime_with_timezone "created_at", null: false t.datetime_with_timezone "updated_at", null: false + t.boolean "enabled" + t.string "domain" end add_index "project_auto_devops", ["project_id"], name: "index_project_auto_devops_on_project_id", unique: true, using: :btree @@ -1755,7 +1755,7 @@ ActiveRecord::Schema.define(version: 20170905112933) do add_foreign_key "personal_access_tokens", "users" add_foreign_key "project_authorizations", "projects", on_delete: :cascade add_foreign_key "project_authorizations", "users", on_delete: :cascade - add_foreign_key "project_auto_devops", "projects", name: "fk_45436b12b2", on_delete: :cascade + add_foreign_key "project_auto_devops", "projects", on_delete: :cascade add_foreign_key "project_features", "projects", name: "fk_18513d9b92", on_delete: :cascade add_foreign_key "project_group_links", "projects", name: "fk_daa8cee94c", on_delete: :cascade add_foreign_key "project_import_data", "projects", name: "fk_ffb9ee3a10", on_delete: :cascade diff --git a/doc/api/README.md b/doc/api/README.md index db61497db53..6cbea29bda6 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -58,6 +58,7 @@ following locations: - [Validate CI configuration](lint.md) - [V3 to V4](v3_to_v4.md) - [Version](version.md) +- [Wikis](wikis.md) ## Road to GraphQL diff --git a/doc/api/wikis.md b/doc/api/wikis.md new file mode 100644 index 00000000000..10eebc4a3c1 --- /dev/null +++ b/doc/api/wikis.md @@ -0,0 +1,157 @@ +# 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. diff --git a/doc/ci/autodeploy/img/auto_deploy_btn.png b/doc/ci/autodeploy/img/auto_deploy_btn.png Binary files differnew file mode 100644 index 00000000000..25915ed1c9d --- /dev/null +++ b/doc/ci/autodeploy/img/auto_deploy_btn.png diff --git a/doc/ci/autodeploy/img/auto_deploy_dropdown.png b/doc/ci/autodeploy/img/auto_deploy_dropdown.png Binary files differindex b93b0a08fea..5815937a4af 100644 --- a/doc/ci/autodeploy/img/auto_deploy_dropdown.png +++ b/doc/ci/autodeploy/img/auto_deploy_dropdown.png diff --git a/doc/ci/autodeploy/img/guide_connect_cluster.png b/doc/ci/autodeploy/img/guide_connect_cluster.png Binary files differnew file mode 100644 index 00000000000..b856b81a1d0 --- /dev/null +++ b/doc/ci/autodeploy/img/guide_connect_cluster.png diff --git a/doc/ci/autodeploy/img/guide_integration.png b/doc/ci/autodeploy/img/guide_integration.png Binary files differnew file mode 100644 index 00000000000..723b2619ea2 --- /dev/null +++ b/doc/ci/autodeploy/img/guide_integration.png diff --git a/doc/ci/autodeploy/img/guide_secret.png b/doc/ci/autodeploy/img/guide_secret.png Binary files differnew file mode 100644 index 00000000000..01f5aa49908 --- /dev/null +++ b/doc/ci/autodeploy/img/guide_secret.png diff --git a/doc/ci/autodeploy/index.md b/doc/ci/autodeploy/index.md index a714689ebd5..a128cf69c20 100644 --- a/doc/ci/autodeploy/index.md +++ b/doc/ci/autodeploy/index.md @@ -1,8 +1,13 @@ -# Auto deploy +# Auto Deploy -> [Introduced][mr-8135] in GitLab 8.15. -> 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. +>**Notes:** +- [Introduced][mr-8135] in GitLab 8.15. +- 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 application. GitLab Community maintains a list of `.gitlab-ci.yml` @@ -11,9 +16,23 @@ powering them. These scripts are responsible for packaging your application, setting up the infrastructure and spinning up necessary services (for example a database). -You can use [project services][project-services] to store credentials to -your infrastructure provider and they will be available during the -deployment. +## How it works + +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 @@ -22,20 +41,27 @@ The list of supported auto deploy templates is available in the ## 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 -credentials. For example, if you want to deploy to OpenShift you have to -enable [Kubernetes service][kubernetes-service]. -1. Configure GitLab Runner to use Docker or Kubernetes executor with -[privileged mode enabled][docker-in-docker]. + credentials. For example, if you want to deploy to OpenShift you have to + enable [Kubernetes service][kubernetes-service]. +1. Configure GitLab Runner to use the + [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. ![Auto deploy button](img/auto_deploy_button.png) 1. Select a template. ![Dropdown with auto deploy templates](img/auto_deploy_dropdown.png) 1. Commit your changes and create a merge request. 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. @@ -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. -## PostgreSQL Database Support +## PostgreSQL database support > Experimental support [introduced][mr-8] in GitLab 9.1. @@ -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 Variables +The following PostgreSQL variables are supported: 1. `DISABLE_POSTGRES: "yes"`: disable automatic deployment of PostgreSQL 1. `POSTGRES_USER: "my-user"`: use custom username for PostgreSQL 1. `POSTGRES_PASSWORD: "password"`: use custom password 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 > 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: 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. 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/ + diff --git a/doc/ci/autodeploy/quick_start_guide.md b/doc/ci/autodeploy/quick_start_guide.md new file mode 100644 index 00000000000..f76c2a2cf31 --- /dev/null +++ b/doc/ci/autodeploy/quick_start_guide.md @@ -0,0 +1,95 @@ +# 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!” diff --git a/doc/ci/pipelines.md b/doc/ci/pipelines.md index 5a2b61fb0cb..ac4a9b0ed27 100644 --- a/doc/ci/pipelines.md +++ b/doc/ci/pipelines.md @@ -222,6 +222,30 @@ total running time should be: Pipeline status and test coverage report badges are available. You can find their 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-yaml]: yaml/README.md#jobs [manual]: yaml/README.md#manual diff --git a/doc/user/permissions.md b/doc/user/permissions.md index bd0a58c4cca..0c17905aa8c 100644 --- a/doc/user/permissions.md +++ b/doc/user/permissions.md @@ -230,6 +230,14 @@ users: 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). +## 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 Since GitLab 8.15, LDAP user permissions can now be manually overridden by an admin user. diff --git a/doc/user/project/description_templates.md b/doc/user/project/description_templates.md index ea7496af089..7c94f4ef4d8 100644 --- a/doc/user/project/description_templates.md +++ b/doc/user/project/description_templates.md @@ -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 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 +[gitlab-ce-templates]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/.gitlab + diff --git a/doc/user/project/integrations/prometheus_library/kubernetes.md b/doc/user/project/integrations/prometheus_library/kubernetes.md index eb8cd821ddc..9f0308d8111 100644 --- a/doc/user/project/integrations/prometheus_library/kubernetes.md +++ b/doc/user/project/integrations/prometheus_library/kubernetes.md @@ -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 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. diff --git a/doc/user/project/protected_branches.md b/doc/user/project/protected_branches.md index 0570d9f471f..0cbb0c878c2 100644 --- a/doc/user/project/protected_branches.md +++ b/doc/user/project/protected_branches.md @@ -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 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 **9.2** diff --git a/features/steps/group/milestones.rb b/features/steps/group/milestones.rb index 20edcf75ff1..818bbb50d0e 100644 --- a/features/steps/group/milestones.rb +++ b/features/steps/group/milestones.rb @@ -47,7 +47,7 @@ class Spinach::Features::GroupMilestones < Spinach::FeatureSteps end step 'I click new milestone button' do - page.within('.breadcrumbs') do + page.within('.nav-controls') do click_link "New milestone" end end diff --git a/features/steps/project/issues/issues.rb b/features/steps/project/issues/issues.rb index f7dd4fc21e9..b9460f5b534 100644 --- a/features/steps/project/issues/issues.rb +++ b/features/steps/project/issues/issues.rb @@ -62,7 +62,7 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps end 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') end end diff --git a/features/steps/project/issues/milestones.rb b/features/steps/project/issues/milestones.rb index 307902a887e..16a2d4a6f93 100644 --- a/features/steps/project/issues/milestones.rb +++ b/features/steps/project/issues/milestones.rb @@ -16,7 +16,7 @@ class Spinach::Features::ProjectIssuesMilestones < Spinach::FeatureSteps end step 'I click link "New Milestone"' do - page.within('.breadcrumbs') do + page.within('.nav-controls') do click_link "New milestone" end end diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb index 3c3bffd7223..0d49a4ab90d 100644 --- a/features/steps/project/merge_requests.rb +++ b/features/steps/project/merge_requests.rb @@ -14,7 +14,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps end 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') end end diff --git a/features/steps/project/snippets.rb b/features/steps/project/snippets.rb index 96b7ba7549f..2a1e6b2bce8 100644 --- a/features/steps/project/snippets.rb +++ b/features/steps/project/snippets.rb @@ -23,7 +23,7 @@ class Spinach::Features::ProjectSnippets < Spinach::FeatureSteps end step 'I click link "New snippet"' do - page.within '.breadcrumbs' do + page.within '.nav-controls' do first(:link, "New snippet").click end end diff --git a/lib/api/api.rb b/lib/api/api.rb index 1405a5d0f0e..d9a62bffb6d 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -144,6 +144,7 @@ module API mount ::API::Variables mount ::API::GroupVariables mount ::API::Version + mount ::API::Wikis route :any, '*path' do error!('404 Not Found', 404) diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 1d224d7bc21..81da2cf820b 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -1,5 +1,15 @@ module API module Entities + class WikiPageBasic < Grape::Entity + expose :format + expose :slug + expose :title + end + + class WikiPage < WikiPageBasic + expose :content + end + class UserSafe < Grape::Entity expose :id, :name, :username end diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index e646c63467a..8b03df65ae4 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -56,6 +56,12 @@ module API @project ||= find_project!(params[:id]) end + def wiki_page + page = user_project.wiki.find_page(params[:slug]) + + page || not_found!('Wiki Page') + end + def available_labels @available_labels ||= LabelsFinder.new(current_user, project_id: user_project.id).execute end diff --git a/lib/api/wikis.rb b/lib/api/wikis.rb new file mode 100644 index 00000000000..b3fc4e876ad --- /dev/null +++ b/lib/api/wikis.rb @@ -0,0 +1,89 @@ +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 diff --git a/lib/gitlab/git/tree.rb b/lib/gitlab/git/tree.rb index b54962a4456..5cf336af3c6 100644 --- a/lib/gitlab/git/tree.rb +++ b/lib/gitlab/git/tree.rb @@ -5,7 +5,7 @@ module Gitlab class Tree 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 class << self @@ -19,8 +19,7 @@ module Gitlab Gitlab::GitalyClient.migrate(:tree_entries) do |is_enabled| if is_enabled - client = Gitlab::GitalyClient::CommitService.new(repository) - client.tree_entries(repository, sha, path) + repository.gitaly_commit_client.tree_entries(repository, sha, path) else tree_entries_from_rugged(repository, sha, path) end @@ -88,7 +87,7 @@ module Gitlab end 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 end end @@ -101,6 +100,10 @@ module Gitlab encode! @path end + def flat_path + encode! @flat_path + end + def dir? type == :tree end diff --git a/lib/gitlab/gitaly_client/commit_service.rb b/lib/gitlab/gitaly_client/commit_service.rb index 21a32a7e0db..0825a3a7694 100644 --- a/lib/gitlab/gitaly_client/commit_service.rb +++ b/lib/gitlab/gitaly_client/commit_service.rb @@ -88,14 +88,14 @@ module Gitlab response.flat_map do |message| message.entries.map do |gitaly_tree_entry| - entry_path = gitaly_tree_entry.path.dup Gitlab::Git::Tree.new( id: gitaly_tree_entry.oid, root_id: gitaly_tree_entry.root_oid, type: gitaly_tree_entry.type.downcase, mode: gitaly_tree_entry.mode.to_s(8), - name: File.basename(entry_path), - path: entry_path, + name: File.basename(gitaly_tree_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 ) end diff --git a/spec/features/dashboard/issues_filter_spec.rb b/spec/features/dashboard/issues_filter_spec.rb index ebc3d196118..facb67ae787 100644 --- a/spec/features/dashboard/issues_filter_spec.rb +++ b/spec/features/dashboard/issues_filter_spec.rb @@ -50,7 +50,7 @@ feature 'Dashboard Issues filtering', :js do it 'updates atom feed link' do 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) auto_discovery_link = find('link[type="application/atom+xml"]', visible: false) auto_discovery_params = CGI.parse(URI.parse(auto_discovery_link[:href]).query) diff --git a/spec/features/dashboard/projects_spec.rb b/spec/features/dashboard/projects_spec.rb index 06a43909053..71de9f04653 100644 --- a/spec/features/dashboard/projects_spec.rb +++ b/spec/features/dashboard/projects_spec.rb @@ -115,9 +115,9 @@ feature 'Dashboard Projects' do expect(page).to have_selector('.merge-request-form') 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('input#merge_request_source_branch').value).to eq 'feature' - expect(find('input#merge_request_target_branch').value).to eq 'master' + expect(find('#merge_request_target_project_id', visible: false).value).to eq project.id.to_s + expect(find('input#merge_request_source_branch', visible: false).value).to eq 'feature' + expect(find('input#merge_request_target_branch', visible: false).value).to eq 'master' end end end diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb index 11db1105d91..5c284a1fe5f 100644 --- a/spec/features/issues_spec.rb +++ b/spec/features/issues_spec.rb @@ -577,7 +577,7 @@ describe 'Issues' do it 'redirects to signin then back to new issue after signin' do visit project_issues_path(project) - page.within '.breadcrumbs' do + page.within '.nav-controls' do click_link 'New issue' end diff --git a/spec/features/merge_requests/diff_notes_resolve_spec.rb b/spec/features/merge_requests/diff_notes_resolve_spec.rb index ac7f75bd308..fd110e68e84 100644 --- a/spec/features/merge_requests/diff_notes_resolve_spec.rb +++ b/spec/features/merge_requests/diff_notes_resolve_spec.rb @@ -196,10 +196,11 @@ feature 'Diff notes resolve', js: true do end 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 - 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}") end diff --git a/spec/features/projects/files/creating_a_file_spec.rb b/spec/features/projects/files/creating_a_file_spec.rb index e13bf4b6089..e1852a6e544 100644 --- a/spec/features/projects/files/creating_a_file_spec.rb +++ b/spec/features/projects/files/creating_a_file_spec.rb @@ -14,7 +14,7 @@ feature 'User wants to create a file' do file_name = find('#file_name') 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' click_button 'Commit changes' diff --git a/spec/features/search_spec.rb b/spec/features/search_spec.rb index 05a089641f1..8f6d0bb9d1b 100644 --- a/spec/features/search_spec.rb +++ b/spec/features/search_spec.rb @@ -295,7 +295,7 @@ describe "Search" do fill_in 'search', with: 'foo' 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 it 'preserves the project being searched in' do @@ -304,7 +304,7 @@ describe "Search" do fill_in 'search', with: 'foo' 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 diff --git a/spec/helpers/tree_helper_spec.rb b/spec/helpers/tree_helper_spec.rb index 9523d0f4aa6..d7b66e6f078 100644 --- a/spec/helpers/tree_helper_spec.rb +++ b/spec/helpers/tree_helper_spec.rb @@ -3,25 +3,35 @@ require 'spec_helper' describe TreeHelper do describe 'flatten_tree' do 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 - @repository = project.repository - @commit = project.commit("e56497bb") - end + subject { flatten_tree(root_path, tree_item) } 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 - expect(flatten_tree(tree_item)).to match('files') + expect(subject).to match('html') end end 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 - 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 diff --git a/spec/javascripts/gl_dropdown_spec.js b/spec/javascripts/gl_dropdown_spec.js index dcb8dbce178..ca048123bf7 100644 --- a/spec/javascripts/gl_dropdown_spec.js +++ b/spec/javascripts/gl_dropdown_spec.js @@ -8,7 +8,7 @@ describe('glDropdown', function describeDropdown() { preloadFixtures('static/gl_dropdown.html.raw'); 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 ITEM_SELECTOR = `.dropdown-content li:not(${NON_SELECTABLE_CLASSES})`; const FOCUSED_ITEM_SELECTOR = `${ITEM_SELECTOR} a.is-focused`; diff --git a/spec/lib/gitlab/git/tree_spec.rb b/spec/lib/gitlab/git/tree_spec.rb index c07a2d91768..86f7bcb8e38 100644 --- a/spec/lib/gitlab/git/tree_spec.rb +++ b/spec/lib/gitlab/git/tree_spec.rb @@ -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.name).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') } context :subdir 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.name).to eq('html') } it { expect(subdir.path).to eq('files/html') } + it { expect(subdir.flat_path).to eq('files/html') } end context :subdir_file 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.name).to eq('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 diff --git a/spec/requests/api/wikis_spec.rb b/spec/requests/api/wikis_spec.rb new file mode 100644 index 00000000000..9e889d1eecf --- /dev/null +++ b/spec/requests/api/wikis_spec.rb @@ -0,0 +1,679 @@ +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 diff --git a/vendor/Dockerfile/CONTRIBUTING.md b/vendor/Dockerfile/CONTRIBUTING.md index 91b92eafa1b..0878db6dd9e 100644 --- a/vendor/Dockerfile/CONTRIBUTING.md +++ b/vendor/Dockerfile/CONTRIBUTING.md @@ -3,3 +3,50 @@ https://gitlab.com/gitlab-org/Dockerfile. GitLab only mirrors the templates. Please submit your merge requests to 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 diff --git a/vendor/gitignore/Actionscript.gitignore b/vendor/gitignore/Actionscript.gitignore index 11e612e9853..5d947ca8879 100644 --- a/vendor/gitignore/Actionscript.gitignore +++ b/vendor/gitignore/Actionscript.gitignore @@ -1,9 +1,8 @@ # Build and Release Folders -bin/ bin-debug/ bin-release/ -[Oo]bj/ # FlashDevelop obj -[Bb]in/ # FlashDevelop bin +[Oo]bj/ +[Bb]in/ # Other files and folders .settings/ diff --git a/vendor/gitignore/Drupal.gitignore b/vendor/gitignore/Drupal.gitignore index 0d2fe537f46..072b683190f 100644 --- a/vendor/gitignore/Drupal.gitignore +++ b/vendor/gitignore/Drupal.gitignore @@ -1,10 +1,12 @@ # Ignore configuration files that may contain sensitive information. sites/*/*settings*.php +sites/example.sites.php # Ignore paths that contain generated content. files/ sites/*/files sites/*/private +sites/*/translations # Ignore default text files robots.txt @@ -16,6 +18,7 @@ robots.txt /UPGRADE.txt /README.txt sites/README.txt +sites/all/libraries/README.txt sites/all/modules/README.txt sites/all/themes/README.txt diff --git a/vendor/gitignore/Kotlin.gitignore b/vendor/gitignore/Kotlin.gitignore new file mode 120000 index 00000000000..c48376eebcf --- /dev/null +++ b/vendor/gitignore/Kotlin.gitignore @@ -0,0 +1 @@ +Java.gitignore
\ No newline at end of file diff --git a/vendor/gitignore/Nanoc.gitignore b/vendor/gitignore/Nanoc.gitignore index 3f36ea2a878..6f35daaf478 100644 --- a/vendor/gitignore/Nanoc.gitignore +++ b/vendor/gitignore/Nanoc.gitignore @@ -4,7 +4,7 @@ output/ # Temporary file directory -tmp/ +tmp/nanoc/ # Crash Log crash.log diff --git a/vendor/gitignore/Node.gitignore b/vendor/gitignore/Node.gitignore index 00cbbdf53f6..97e28736892 100644 --- a/vendor/gitignore/Node.gitignore +++ b/vendor/gitignore/Node.gitignore @@ -29,7 +29,7 @@ bower_components # node-waf configuration .lock-wscript -# Compiled binary addons (http://nodejs.org/api/addons.html) +# Compiled binary addons (https://nodejs.org/api/addons.html) build/Release # Dependency directories diff --git a/vendor/gitignore/Qt.gitignore b/vendor/gitignore/Qt.gitignore index 5fa47c5a1f2..fe67fdf1ee6 100644 --- a/vendor/gitignore/Qt.gitignore +++ b/vendor/gitignore/Qt.gitignore @@ -26,6 +26,8 @@ moc_*.cpp moc_*.h qrc_*.cpp ui_*.h +*.qmlc +*.jsc Makefile* *build-* diff --git a/vendor/gitignore/Swift.gitignore b/vendor/gitignore/Swift.gitignore index d5340449396..161179bff3e 100644 --- a/vendor/gitignore/Swift.gitignore +++ b/vendor/gitignore/Swift.gitignore @@ -37,6 +37,7 @@ playground.xcworkspace # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. # Packages/ # Package.pins +# Package.resolved .build/ # CocoaPods diff --git a/vendor/gitignore/Terraform.gitignore b/vendor/gitignore/Terraform.gitignore index 41859c81f1c..f20453be963 100644 --- a/vendor/gitignore/Terraform.gitignore +++ b/vendor/gitignore/Terraform.gitignore @@ -1,5 +1,6 @@ # Compiled files *.tfstate +*.tfstate.*.backup *.tfstate.backup # Module directory diff --git a/vendor/gitignore/VisualStudio.gitignore b/vendor/gitignore/VisualStudio.gitignore index 89c66054885..f652b45c2ee 100644 --- a/vendor/gitignore/VisualStudio.gitignore +++ b/vendor/gitignore/VisualStudio.gitignore @@ -116,6 +116,10 @@ _TeamCity* # DotCover is a Code Coverage Tool *.dotCover +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + # Visual Studio code coverage results *.coverage *.coveragexml diff --git a/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml b/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml index 27537689b80..a07edd264c3 100644 --- a/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml @@ -1,35 +1,411 @@ -# 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: busybox:latest +image: alpine:latest + +variables: + # AUTO_DEVOPS_DOMAIN is the application deployment domain and should be set as a variable at the group or project level. + # AUTO_DEVOPS_DOMAIN: domain.example.com + + POSTGRES_USER: user + POSTGRES_PASSWORD: testing-password + POSTGRES_ENABLED: "true" + POSTGRES_DB: $CI_ENVIRONMENT_SLUG + +stages: + - build + - test + - review + - staging + - canary + - production + - cleanup + +build: + stage: build + image: docker:git + services: + - docker:dind + variables: + DOCKER_DRIVER: overlay2 + script: + - setup_docker + - build + only: + - branches + +test: + services: + - postgres:latest + variables: + POSTGRES_DB: test + stage: test + image: gliderlabs/herokuish:latest + script: + - setup_test_db + - cp -R . /tmp/app + - /bin/herokuish buildpack test + only: + - branches + +codequality: + 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: + - check_kube_domain + - 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. + +production: + stage: production + script: + - check_kube_domain + - 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: - - echo "Before script section" - - echo "For example you might run an update here or install a build dependency" - - echo "Or perhaps you might print out some debugging details" - -after_script: - - echo "After script section" - - echo "For example you might do some cleanup here" - -build1: - stage: build - script: - - echo "Do your build here" - -test1: - stage: test - script: - - echo "Do a test here" - - echo "For example run a test suite" - -test2: - stage: test - script: - - echo "Do another parallel test here" - - echo "For example run a lint test" - -deploy1: - stage: deploy - script: - - echo "Do your deploy here"
\ No newline at end of file + - *auto_devops diff --git a/vendor/gitlab-ci-yml/Bash.gitlab-ci.yml b/vendor/gitlab-ci-yml/Bash.gitlab-ci.yml index 27537689b80..2d218b2e164 100644 --- a/vendor/gitlab-ci-yml/Bash.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Bash.gitlab-ci.yml @@ -4,32 +4,32 @@ image: busybox:latest before_script: - - echo "Before script section" - - echo "For example you might run an update here or install a build dependency" - - echo "Or perhaps you might print out some debugging details" + - echo "Before script section" + - echo "For example you might run an update here or install a build dependency" + - echo "Or perhaps you might print out some debugging details" after_script: - echo "After script section" - echo "For example you might do some cleanup here" build1: - stage: build - script: - - echo "Do your build here" + stage: build + script: + - echo "Do your build here" test1: - stage: test - script: - - echo "Do a test here" - - echo "For example run a test suite" + stage: test + script: + - echo "Do a test here" + - echo "For example run a test suite" test2: - stage: test - script: - - echo "Do another parallel test here" - - echo "For example run a lint test" + stage: test + script: + - echo "Do another parallel test here" + - echo "For example run a lint test" deploy1: - stage: deploy - script: - - echo "Do your deploy here"
\ No newline at end of file + stage: deploy + script: + - echo "Do your deploy here" diff --git a/vendor/gitlab-ci-yml/Packer.gitlab-ci.yml b/vendor/gitlab-ci-yml/Packer.gitlab-ci.yml new file mode 100644 index 00000000000..fa296057c72 --- /dev/null +++ b/vendor/gitlab-ci-yml/Packer.gitlab-ci.yml @@ -0,0 +1,26 @@ +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 diff --git a/vendor/gitlab-ci-yml/Terraform.gitlab-ci.yml b/vendor/gitlab-ci-yml/Terraform.gitlab-ci.yml new file mode 100644 index 00000000000..7160fce26a8 --- /dev/null +++ b/vendor/gitlab-ci-yml/Terraform.gitlab-ci.yml @@ -0,0 +1,55 @@ +# 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 |