From 9a13c250af7b6f3f81167a8f313bbe6cd90502d2 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 5 Oct 2018 16:21:27 +0200 Subject: Add packages section to CE config file Signed-off-by: Dmitriy Zaporozhets --- config/gitlab.yml.example | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 67337f4b82f..5417f3d448b 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -207,6 +207,10 @@ production: &base # endpoint: 'http://127.0.0.1:9000' # default: nil # path_style: true # Use 'host/bucket_name/object' instead of 'bucket_name.host/object' + ## Packages (maven repository so far) + packages: + enabled: false + ## GitLab Pages pages: enabled: false -- cgit v1.2.1 From 098c722542cce93046bf6012f089dac48251d091 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Rodr=C3=ADguez?= Date: Tue, 2 Oct 2018 17:11:07 -0300 Subject: Add gitlab:gitaly:check task for Gitaly health check Also, since Gitaly now takes care of checking for storage paths existence/accessibility, we can remove those check from the gitlab:gitlab_shell_check task and advance further into 0 direct disk approach on gitlab-rails --- changelogs/unreleased/rake-gitaly-check.yml | 5 + doc/administration/raketasks/maintenance.md | 3 +- lib/tasks/gitlab/check.rake | 139 +++++----------------------- 3 files changed, 29 insertions(+), 118 deletions(-) create mode 100644 changelogs/unreleased/rake-gitaly-check.yml diff --git a/changelogs/unreleased/rake-gitaly-check.yml b/changelogs/unreleased/rake-gitaly-check.yml new file mode 100644 index 00000000000..90fbd62d203 --- /dev/null +++ b/changelogs/unreleased/rake-gitaly-check.yml @@ -0,0 +1,5 @@ +--- +title: Add gitlab:gitaly:check task for Gitaly health check +merge_request: 22063 +author: +type: other diff --git a/doc/administration/raketasks/maintenance.md b/doc/administration/raketasks/maintenance.md index 29af07d12dc..0d863594fc7 100644 --- a/doc/administration/raketasks/maintenance.md +++ b/doc/administration/raketasks/maintenance.md @@ -53,6 +53,7 @@ Git: /usr/bin/git Runs the following rake tasks: - `gitlab:gitlab_shell:check` +- `gitlab:gitaly:check` - `gitlab:sidekiq:check` - `gitlab:app:check` @@ -252,7 +253,7 @@ clear it. To clear all exclusive leases: -DANGER: **DANGER**: +DANGER: **DANGER**: Don't run it while GitLab or Sidekiq is running ```bash diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake index e5b5f3548e4..663bebfe71a 100644 --- a/lib/tasks/gitlab/check.rake +++ b/lib/tasks/gitlab/check.rake @@ -1,6 +1,7 @@ namespace :gitlab do desc 'GitLab | Check the configuration of GitLab and its environment' task check: %w{gitlab:gitlab_shell:check + gitlab:gitaly:check gitlab:sidekiq:check gitlab:incoming_email:check gitlab:ldap:check @@ -44,13 +45,7 @@ namespace :gitlab do start_checking "GitLab Shell" check_gitlab_shell - Gitlab::GitalyClient::StorageSettings.allow_disk_access do - check_repo_base_exists - check_repo_base_is_not_symlink - check_repo_base_user_and_group - check_repo_base_permissions - check_repos_hooks_directory_is_link - end + check_repos_hooks_directory_is_link check_gitlab_shell_self_test finished_checking "GitLab Shell" @@ -59,116 +54,6 @@ namespace :gitlab do # Checks ######################## - def check_repo_base_exists - puts "Repo base directory exists?" - - Gitlab.config.repositories.storages.each do |name, repository_storage| - repo_base_path = repository_storage.legacy_disk_path - print "#{name}... " - - if File.exist?(repo_base_path) - puts "yes".color(:green) - else - puts "no".color(:red) - puts "#{repo_base_path} is missing".color(:red) - try_fixing_it( - "This should have been created when setting up GitLab Shell.", - "Make sure it's set correctly in config/gitlab.yml", - "Make sure GitLab Shell is installed correctly." - ) - for_more_information( - see_installation_guide_section "GitLab Shell" - ) - fix_and_rerun - end - end - end - - def check_repo_base_is_not_symlink - puts "Repo storage directories are symlinks?" - - Gitlab.config.repositories.storages.each do |name, repository_storage| - repo_base_path = repository_storage.legacy_disk_path - print "#{name}... " - - unless File.exist?(repo_base_path) - puts "can't check because of previous errors".color(:magenta) - break - end - - unless File.symlink?(repo_base_path) - puts "no".color(:green) - else - puts "yes".color(:red) - try_fixing_it( - "Make sure it's set to the real directory in config/gitlab.yml" - ) - fix_and_rerun - end - end - end - - def check_repo_base_permissions - puts "Repo paths access is drwxrws---?" - - Gitlab.config.repositories.storages.each do |name, repository_storage| - repo_base_path = repository_storage.legacy_disk_path - print "#{name}... " - - unless File.exist?(repo_base_path) - puts "can't check because of previous errors".color(:magenta) - break - end - - if File.stat(repo_base_path).mode.to_s(8).ends_with?("2770") - puts "yes".color(:green) - else - puts "no".color(:red) - try_fixing_it( - "sudo chmod -R ug+rwX,o-rwx #{repo_base_path}", - "sudo chmod -R ug-s #{repo_base_path}", - "sudo find #{repo_base_path} -type d -print0 | sudo xargs -0 chmod g+s" - ) - for_more_information( - see_installation_guide_section "GitLab Shell" - ) - fix_and_rerun - end - end - end - - def check_repo_base_user_and_group - gitlab_shell_ssh_user = Gitlab.config.gitlab_shell.ssh_user - puts "Repo paths owned by #{gitlab_shell_ssh_user}:root, or #{gitlab_shell_ssh_user}:#{Gitlab.config.gitlab_shell.owner_group}?" - - Gitlab.config.repositories.storages.each do |name, repository_storage| - repo_base_path = repository_storage.legacy_disk_path - print "#{name}... " - - unless File.exist?(repo_base_path) - puts "can't check because of previous errors".color(:magenta) - break - end - - user_id = uid_for(gitlab_shell_ssh_user) - root_group_id = gid_for('root') - group_ids = [root_group_id, gid_for(Gitlab.config.gitlab_shell.owner_group)] - if File.stat(repo_base_path).uid == user_id && group_ids.include?(File.stat(repo_base_path).gid) - puts "yes".color(:green) - else - puts "no".color(:red) - puts " User id for #{gitlab_shell_ssh_user}: #{user_id}. Groupd id for root: #{root_group_id}".color(:blue) - try_fixing_it( - "sudo chown -R #{gitlab_shell_ssh_user}:root #{repo_base_path}" - ) - for_more_information( - see_installation_guide_section "GitLab Shell" - ) - fix_and_rerun - end - end - end - def check_repos_hooks_directory_is_link print "hooks directories in repos are links: ... " @@ -247,6 +132,26 @@ namespace :gitlab do end end + namespace :gitaly do + desc 'GitLab | Check the health of Gitaly' + task check: :gitlab_environment do + warn_user_is_not_gitlab + start_checking 'Gitaly' + + Gitlab::HealthChecks::GitalyCheck.readiness.each do |result| + print "#{result.labels[:shard]} ... " + + if result.success + puts 'OK'.color(:green) + else + puts "FAIL: #{result.message}".color(:red) + end + end + + finished_checking 'Gitaly' + end + end + namespace :sidekiq do desc "GitLab | Check the configuration of Sidekiq" task check: :gitlab_environment do -- cgit v1.2.1 From 9e48cbf89d86e85868eb9a6496adb53d593dcacb Mon Sep 17 00:00:00 2001 From: James Edwards-Jones Date: Wed, 24 Oct 2018 21:11:34 +0100 Subject: EE render_if_exists for SSO badge in _member view --- app/views/shared/members/_member.html.haml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml index 2682d92fc56..b4b3f4a6b7e 100644 --- a/app/views/shared/members/_member.html.haml +++ b/app/views/shared/members/_member.html.haml @@ -14,6 +14,8 @@ = user_status(user) %span.cgray= user.to_reference + = render_if_exists 'shared/members/ee/sso_badge', member: member + - if user == current_user %span.badge.badge-success.prepend-left-5 It's you -- cgit v1.2.1 From c24d74dba9a9de3de1a445cc58d8a565b7ef8d43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=ADdac=20Rodr=C3=ADguez=20Arbon=C3=A8s?= Date: Wed, 31 Oct 2018 16:04:34 +0100 Subject: Allow single letter external issue IDs --- app/models/project_services/issue_tracker_service.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/project_services/issue_tracker_service.rb b/app/models/project_services/issue_tracker_service.rb index e1d342be188..4f7e16a1f5b 100644 --- a/app/models/project_services/issue_tracker_service.rb +++ b/app/models/project_services/issue_tracker_service.rb @@ -12,9 +12,9 @@ class IssueTrackerService < Service # overriden patterns. See ReferenceRegexes::EXTERNAL_PATTERN def self.reference_pattern(only_long: false) if only_long - /(\b[A-Z][A-Z0-9_]+-)(?\d+)/ + /(\b[A-Z][A-Z0-9_]*-)(?\d+)/ else - /(\b[A-Z][A-Z0-9_]+-|#{Issue.reference_prefix})(?\d+)/ + /(\b[A-Z][A-Z0-9_]*-|#{Issue.reference_prefix})(?\d+)/ end end -- cgit v1.2.1 From e63f76c9f173af1eddfe9e07b1b6998e653162c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=ADdac=20Rodr=C3=ADguez=20Arbon=C3=A8s?= Date: Thu, 1 Nov 2018 13:41:01 +0100 Subject: Adding changelog entry --- .../22717-single-letter-identifier-external-issue-tracker.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changelogs/unreleased/22717-single-letter-identifier-external-issue-tracker.yml diff --git a/changelogs/unreleased/22717-single-letter-identifier-external-issue-tracker.yml b/changelogs/unreleased/22717-single-letter-identifier-external-issue-tracker.yml new file mode 100644 index 00000000000..3f7a0d9204e --- /dev/null +++ b/changelogs/unreleased/22717-single-letter-identifier-external-issue-tracker.yml @@ -0,0 +1,5 @@ +--- +title: "Allowing issues with single letter identifiers to be linked to external issue tracker (f.ex T-123)" +merge_request: 22717 +author: Dídac Rodríguez Arbonès +type: changed \ No newline at end of file -- cgit v1.2.1 From cb3f8510980bf02a1187c40d87df130e517f868d Mon Sep 17 00:00:00 2001 From: Jakub Jirutka Date: Thu, 18 Jan 2018 14:16:31 +0100 Subject: Render index.* like README.* when it's present in a directory Resolves #18933 --- changelogs/unreleased/18933-render-index-as-readme.yml | 5 +++++ lib/gitlab/file_detector.rb | 2 +- spec/lib/gitlab/file_detector_spec.rb | 6 +++++- 3 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 changelogs/unreleased/18933-render-index-as-readme.yml diff --git a/changelogs/unreleased/18933-render-index-as-readme.yml b/changelogs/unreleased/18933-render-index-as-readme.yml new file mode 100644 index 00000000000..49a6a7929b2 --- /dev/null +++ b/changelogs/unreleased/18933-render-index-as-readme.yml @@ -0,0 +1,5 @@ +--- +title: Make index.* render like README.* when it's present in a repository +merge_request: 16550 +author: Jakub Jirutka +type: added diff --git a/lib/gitlab/file_detector.rb b/lib/gitlab/file_detector.rb index 4d89ee5a669..898dc7b0e23 100644 --- a/lib/gitlab/file_detector.rb +++ b/lib/gitlab/file_detector.rb @@ -8,7 +8,7 @@ module Gitlab module FileDetector PATTERNS = { # Project files - readme: %r{\Areadme[^/]*\z}i, + readme: %r{\A(readme|index)[^\/]*\z}i, changelog: %r{\A(changelog|history|changes|news)[^/]*\z}i, license: %r{\A((un)?licen[sc]e|copying)(\.[^/]+)?\z}i, contributing: %r{\Acontributing[^/]*\z}i, diff --git a/spec/lib/gitlab/file_detector_spec.rb b/spec/lib/gitlab/file_detector_spec.rb index 294ec2c2fd6..f0e7bd28ec0 100644 --- a/spec/lib/gitlab/file_detector_spec.rb +++ b/spec/lib/gitlab/file_detector_spec.rb @@ -15,7 +15,11 @@ describe Gitlab::FileDetector do describe '.type_of' do it 'returns the type of a README file' do - expect(described_class.type_of('README.md')).to eq(:readme) + %w(README readme INDEX index).each do |filename| + %w(.md .adoc).each do |extname| + expect(described_class.type_of(filename + extname)).to eq(:readme) + end + end end it 'returns nil for a README file in a directory' do -- cgit v1.2.1 From 133c1a7cc3f1441bf18c886c30681953992b38c9 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sat, 27 Oct 2018 09:36:43 +0200 Subject: Correct MR number in changelog --- changelogs/unreleased/18933-render-index-as-readme.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelogs/unreleased/18933-render-index-as-readme.yml b/changelogs/unreleased/18933-render-index-as-readme.yml index 49a6a7929b2..44acc2c719a 100644 --- a/changelogs/unreleased/18933-render-index-as-readme.yml +++ b/changelogs/unreleased/18933-render-index-as-readme.yml @@ -1,5 +1,5 @@ --- title: Make index.* render like README.* when it's present in a repository -merge_request: 16550 +merge_request: 22639 author: Jakub Jirutka type: added -- cgit v1.2.1 From be0af5e69fc60bf629130290b49dee07510359a3 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 2 Nov 2018 11:15:04 +0100 Subject: Test type of README file without extension --- lib/gitlab/file_detector.rb | 2 +- spec/lib/gitlab/file_detector_spec.rb | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/gitlab/file_detector.rb b/lib/gitlab/file_detector.rb index 898dc7b0e23..d6338b09e3d 100644 --- a/lib/gitlab/file_detector.rb +++ b/lib/gitlab/file_detector.rb @@ -8,7 +8,7 @@ module Gitlab module FileDetector PATTERNS = { # Project files - readme: %r{\A(readme|index)[^\/]*\z}i, + readme: %r{\A(readme|index)[^/]*\z}i, changelog: %r{\A(changelog|history|changes|news)[^/]*\z}i, license: %r{\A((un)?licen[sc]e|copying)(\.[^/]+)?\z}i, contributing: %r{\Acontributing[^/]*\z}i, diff --git a/spec/lib/gitlab/file_detector_spec.rb b/spec/lib/gitlab/file_detector_spec.rb index f0e7bd28ec0..edab53247e9 100644 --- a/spec/lib/gitlab/file_detector_spec.rb +++ b/spec/lib/gitlab/file_detector_spec.rb @@ -15,8 +15,9 @@ describe Gitlab::FileDetector do describe '.type_of' do it 'returns the type of a README file' do - %w(README readme INDEX index).each do |filename| - %w(.md .adoc).each do |extname| + %w[README readme INDEX index].each do |filename| + expect(described_class.type_of(filename)).to eq(:readme) + %w[.md .adoc .rst].each do |extname| expect(described_class.type_of(filename + extname)).to eq(:readme) end end -- cgit v1.2.1 From 75917c51f96cd09bade639f85ef6acb708ab4ab2 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 2 Nov 2018 15:15:26 +0100 Subject: Add docs for README and index files --- doc/user/project/repository/index.md | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/doc/user/project/repository/index.md b/doc/user/project/repository/index.md index beff4b89424..6d822d3f7f2 100644 --- a/doc/user/project/repository/index.md +++ b/doc/user/project/repository/index.md @@ -53,6 +53,32 @@ To get started with the command line, please read through the Use GitLab's [file finder](../../../workflow/file_finder.md) to search for files in a repository. +### Repository README and index files + +When a `README` or `index` file is present in a repository, its contents will be +automatically pre-rendered by GitLab without opening it. + +They can either be plain text or have an extension of a supported markup language: + +- Asciidoc: `README.adoc` or `index.adoc` +- Markdown: `README.md` or `index.md` +- reStructuredText: `README.rst` or `index.rst` +- Text: `README.txt` or `index.txt` + +Some things to note about precedence: + +1. When both a `README` and an `index` file are present, the `README` will always + take precedence. +1. When more than one file is present with different extensions, they are + ordered alphabetically, with the exception of a file without an extension + which will always be last in precedence. For example, `README.adoc` will take + precedence over `README.md`, and `README.rst` will take precedence over + `README`. + +NOTE: **Note:** +`index` files without an extension will not automatically pre-render. You'll +have to explicitly open them to see their contents. + ### Jupyter Notebook files > [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/2508) in GitLab 9.1 @@ -165,7 +191,7 @@ minutes. ![Repository Languages bar](img/repository_languages.png) -Not all files are detected, among others; documentation, +Not all files are detected, among others; documentation, vendored code, and most markup languages are excluded. This behaviour can be adjusted by overriding the default. For example, to enable `.proto` files to be detected, add the following to `.gitattributes` in the root of your repository. -- cgit v1.2.1 From ccb706d3466e9643164a86dfd96f582b42926bc6 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Fri, 2 Nov 2018 14:32:05 +0000 Subject: Refactor MarkupHelper to add INDEX plain filename --- lib/gitlab/markup_helper.rb | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/gitlab/markup_helper.rb b/lib/gitlab/markup_helper.rb index 142b7d1a472..e4e90d1d448 100644 --- a/lib/gitlab/markup_helper.rb +++ b/lib/gitlab/markup_helper.rb @@ -4,10 +4,11 @@ module Gitlab module MarkupHelper extend self - MARKDOWN_EXTENSIONS = %w(mdown mkd mkdn md markdown).freeze - ASCIIDOC_EXTENSIONS = %w(adoc ad asciidoc).freeze - OTHER_EXTENSIONS = %w(textile rdoc org creole wiki mediawiki rst).freeze + MARKDOWN_EXTENSIONS = %w[mdown mkd mkdn md markdown].freeze + ASCIIDOC_EXTENSIONS = %w[adoc ad asciidoc].freeze + OTHER_EXTENSIONS = %w[textile rdoc org creole wiki mediawiki rst].freeze EXTENSIONS = MARKDOWN_EXTENSIONS + ASCIIDOC_EXTENSIONS + OTHER_EXTENSIONS + PLAIN_FILENAMES = %w[readme index].freeze # Public: Determines if a given filename is compatible with GitHub::Markup. # @@ -43,7 +44,7 @@ module Gitlab # # Returns boolean def plain?(filename) - extension(filename) == 'txt' || filename.casecmp('readme').zero? + extension(filename) == 'txt' || plain_filename?(filename) end def previewable?(filename) @@ -55,5 +56,9 @@ module Gitlab def extension(filename) File.extname(filename).downcase.delete('.') end + + def plain_filename?(filename) + PLAIN_FILENAMES.include?(filename.downcase) + end end end -- cgit v1.2.1 From 0846e8a6f59c6a3a04adb935eba70f0187c48c7e Mon Sep 17 00:00:00 2001 From: James Lopez Date: Fri, 2 Nov 2018 15:10:41 +0000 Subject: Remove extra whitespace on markup_helper.rb --- lib/gitlab/markup_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gitlab/markup_helper.rb b/lib/gitlab/markup_helper.rb index e4e90d1d448..d419fa66e57 100644 --- a/lib/gitlab/markup_helper.rb +++ b/lib/gitlab/markup_helper.rb @@ -59,6 +59,6 @@ module Gitlab def plain_filename?(filename) PLAIN_FILENAMES.include?(filename.downcase) - end + end end end -- cgit v1.2.1 From d76fdfcfdb1c815f687b01f2f7610f70bf3e378e Mon Sep 17 00:00:00 2001 From: James Edwards-Jones Date: Fri, 2 Nov 2018 19:43:07 +0000 Subject: Add MembersPreloader to fix ActiveRecord cop MembersPresentation was previously preloading records and we wanted to extend this in https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/5807 By moving this to MembersPreloader we avoid introducing a new cop exception that would otherwise have been needed in EE::MembersPresentation --- app/controllers/concerns/members_presentation.rb | 7 +------ app/models/members_preloader.rb | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 6 deletions(-) create mode 100644 app/models/members_preloader.rb diff --git a/app/controllers/concerns/members_presentation.rb b/app/controllers/concerns/members_presentation.rb index c6c3598a976..0a9d3d86245 100644 --- a/app/controllers/concerns/members_presentation.rb +++ b/app/controllers/concerns/members_presentation.rb @@ -12,12 +12,7 @@ module MembersPresentation ).fabricate! end - # rubocop: disable CodeReuse/ActiveRecord def preload_associations(members) - ActiveRecord::Associations::Preloader.new.preload(members, :user) - ActiveRecord::Associations::Preloader.new.preload(members, :source) - ActiveRecord::Associations::Preloader.new.preload(members.map(&:user), :status) - ActiveRecord::Associations::Preloader.new.preload(members.map(&:user), :u2f_registrations) + MembersPreloader.new(members).preload_all end - # rubocop: enable CodeReuse/ActiveRecord end diff --git a/app/models/members_preloader.rb b/app/models/members_preloader.rb new file mode 100644 index 00000000000..33855191ca8 --- /dev/null +++ b/app/models/members_preloader.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class MembersPreloader + attr_reader :members + + def initialize(members) + @members = members + end + + def preload_all + ActiveRecord::Associations::Preloader.new.preload(members, :user) + ActiveRecord::Associations::Preloader.new.preload(members, :source) + ActiveRecord::Associations::Preloader.new.preload(members.map(&:user), :status) + ActiveRecord::Associations::Preloader.new.preload(members.map(&:user), :u2f_registrations) + end +end -- cgit v1.2.1 From 1b47b2147f327537e98bcf42adbcff11180367fe Mon Sep 17 00:00:00 2001 From: Thong Kuah Date: Sat, 3 Nov 2018 23:13:05 +1300 Subject: Sync `groups` and `group` to fix validation Otherwise adding to `groups` will cause validation (in EE) based on `group` to fail. This is consistent with how Clusters::Cluster#first_project. The alternative is to switch validation to use `groups` as well. --- app/models/clusters/cluster.rb | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb index 2bd373e0950..e80d35d0f3c 100644 --- a/app/models/clusters/cluster.rb +++ b/app/models/clusters/cluster.rb @@ -3,6 +3,7 @@ module Clusters class Cluster < ActiveRecord::Base include Presentable + include Gitlab::Utils::StrongMemoize self.table_name = 'clusters' @@ -24,9 +25,6 @@ module Clusters has_many :cluster_groups, class_name: 'Clusters::Group' has_many :groups, through: :cluster_groups, class_name: '::Group' - has_one :cluster_group, -> { order(id: :desc) }, class_name: 'Clusters::Group' - has_one :group, through: :cluster_group, class_name: '::Group' - # we force autosave to happen when we save `Cluster` model has_one :provider_gcp, class_name: 'Clusters::Providers::Gcp', autosave: true @@ -119,12 +117,19 @@ module Clusters end def first_project - return @first_project if defined?(@first_project) - - @first_project = projects.first + strong_memoize(:first_project) do + projects.first + end end alias_method :project, :first_project + def first_group + strong_memoize(:first_group) do + groups.first + end + end + alias_method :group, :first_group + def kubeclient platform_kubernetes.kubeclient if kubernetes? end -- cgit v1.2.1 From fe083f505d907c1d1cef14c29170b31cf30192fe Mon Sep 17 00:00:00 2001 From: Mayra Cabrera Date: Tue, 23 Oct 2018 17:30:04 -0500 Subject: Removes experimental label from cluster views As part of https://gitlab.com/gitlab-org/gitlab-ce/issues/51716, we need to remove 'experimental' labels from cluster views (show and form), as well as the promp about cluster configuration, since it won't apply. These changes were originally introduced on https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22011/, but we're trying to reduce the size of that MR as it's to big to review with confidence. --- app/helpers/user_callouts_helper.rb | 5 ----- app/views/clusters/clusters/_banner.html.haml | 6 ------ app/views/clusters/clusters/gcp/_form.html.haml | 2 +- app/views/clusters/clusters/gcp/_show.html.haml | 2 +- app/views/clusters/clusters/user/_form.html.haml | 2 +- app/views/clusters/clusters/user/_show.html.haml | 2 +- .../unreleased/remove-experimental-label-from-cluster-views.yml | 5 +++++ locale/gitlab.pot | 5 +---- 8 files changed, 10 insertions(+), 19 deletions(-) create mode 100644 changelogs/unreleased/remove-experimental-label-from-cluster-views.yml diff --git a/app/helpers/user_callouts_helper.rb b/app/helpers/user_callouts_helper.rb index bae01d476df..4aba48061ba 100644 --- a/app/helpers/user_callouts_helper.rb +++ b/app/helpers/user_callouts_helper.rb @@ -3,7 +3,6 @@ module UserCalloutsHelper GKE_CLUSTER_INTEGRATION = 'gke_cluster_integration'.freeze GCP_SIGNUP_OFFER = 'gcp_signup_offer'.freeze - CLUSTER_SECURITY_WARNING = 'cluster_security_warning'.freeze def show_gke_cluster_integration_callout?(project) can?(current_user, :create_cluster, project) && @@ -14,10 +13,6 @@ module UserCalloutsHelper !user_dismissed?(GCP_SIGNUP_OFFER) end - def show_cluster_security_warning? - !user_dismissed?(CLUSTER_SECURITY_WARNING) - end - private def user_dismissed?(feature_name) diff --git a/app/views/clusters/clusters/_banner.html.haml b/app/views/clusters/clusters/_banner.html.haml index 73cfea0ef92..160c5f009a7 100644 --- a/app/views/clusters/clusters/_banner.html.haml +++ b/app/views/clusters/clusters/_banner.html.haml @@ -7,9 +7,3 @@ .hidden.js-cluster-success.bs-callout.bs-callout-success{ role: 'alert' } = s_("ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine. Refresh the page to see Kubernetes cluster's details") - -- if show_cluster_security_warning? - .js-cluster-security-warning.alert.alert-block.alert-dismissable.bs-callout.bs-callout-warning - %button.close{ type: "button", data: { feature_id: UserCalloutsHelper::CLUSTER_SECURITY_WARNING, dismiss_endpoint: user_callouts_path } } × - = s_("ClusterIntegration|The default cluster configuration grants access to many functionalities needed to successfully build and deploy a containerised application.") - = link_to s_("More information"), help_page_path('user/project/clusters/index.md', anchor: 'security-implications') diff --git a/app/views/clusters/clusters/gcp/_form.html.haml b/app/views/clusters/clusters/gcp/_form.html.haml index ad842036a62..8ed4666e79a 100644 --- a/app/views/clusters/clusters/gcp/_form.html.haml +++ b/app/views/clusters/clusters/gcp/_form.html.haml @@ -64,7 +64,7 @@ .form-group .form-check = provider_gcp_field.check_box :legacy_abac, { class: 'form-check-input' }, false, true - = provider_gcp_field.label :legacy_abac, s_('ClusterIntegration|RBAC-enabled cluster (experimental)'), class: 'form-check-label label-bold' + = provider_gcp_field.label :legacy_abac, s_('ClusterIntegration|RBAC-enabled cluster'), class: 'form-check-label label-bold' .form-text.text-muted = s_('ClusterIntegration|Enable this setting if using role-based access control (RBAC).') = s_('ClusterIntegration|This option will allow you to install applications on RBAC clusters.') diff --git a/app/views/clusters/clusters/gcp/_show.html.haml b/app/views/clusters/clusters/gcp/_show.html.haml index 6021b220285..ca55ccb8fdf 100644 --- a/app/views/clusters/clusters/gcp/_show.html.haml +++ b/app/views/clusters/clusters/gcp/_show.html.haml @@ -40,7 +40,7 @@ .form-group .form-check = platform_kubernetes_field.check_box :authorization_type, { class: 'form-check-input', disabled: true }, 'rbac', 'abac' - = platform_kubernetes_field.label :authorization_type, s_('ClusterIntegration|RBAC-enabled cluster (experimental)'), class: 'form-check-label label-bold' + = platform_kubernetes_field.label :authorization_type, s_('ClusterIntegration|RBAC-enabled cluster'), class: 'form-check-label label-bold' .form-text.text-muted = s_('ClusterIntegration|Enable this setting if using role-based access control (RBAC).') = s_('ClusterIntegration|This option will allow you to install applications on RBAC clusters.') diff --git a/app/views/clusters/clusters/user/_form.html.haml b/app/views/clusters/clusters/user/_form.html.haml index 4e6232b69de..e4758938059 100644 --- a/app/views/clusters/clusters/user/_form.html.haml +++ b/app/views/clusters/clusters/user/_form.html.haml @@ -28,7 +28,7 @@ .form-group .form-check = platform_kubernetes_field.check_box :authorization_type, { class: 'form-check-input qa-rbac-checkbox' }, 'rbac', 'abac' - = platform_kubernetes_field.label :authorization_type, s_('ClusterIntegration|RBAC-enabled cluster (experimental)'), class: 'form-check-label label-bold' + = platform_kubernetes_field.label :authorization_type, s_('ClusterIntegration|RBAC-enabled cluster'), class: 'form-check-label label-bold' .form-text.text-muted = s_('ClusterIntegration|Enable this setting if using role-based access control (RBAC).') = s_('ClusterIntegration|This option will allow you to install applications on RBAC clusters.') diff --git a/app/views/clusters/clusters/user/_show.html.haml b/app/views/clusters/clusters/user/_show.html.haml index a871fef0240..ad8c35e32e3 100644 --- a/app/views/clusters/clusters/user/_show.html.haml +++ b/app/views/clusters/clusters/user/_show.html.haml @@ -29,7 +29,7 @@ .form-group .form-check = platform_kubernetes_field.check_box :authorization_type, { class: 'form-check-input', disabled: true }, 'rbac', 'abac' - = platform_kubernetes_field.label :authorization_type, s_('ClusterIntegration|RBAC-enabled cluster (experimental)'), class: 'form-check-label label-bold' + = platform_kubernetes_field.label :authorization_type, s_('ClusterIntegration|RBAC-enabled cluster'), class: 'form-check-label label-bold' .form-text.text-muted = s_('ClusterIntegration|Enable this setting if using role-based access control (RBAC).') = s_('ClusterIntegration|This option will allow you to install applications on RBAC clusters.') diff --git a/changelogs/unreleased/remove-experimental-label-from-cluster-views.yml b/changelogs/unreleased/remove-experimental-label-from-cluster-views.yml new file mode 100644 index 00000000000..af9512b27e9 --- /dev/null +++ b/changelogs/unreleased/remove-experimental-label-from-cluster-views.yml @@ -0,0 +1,5 @@ +--- +title: Removes experimental labels from cluster views +merge_request: 22550 +author: +type: other diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 324e5315821..88f2c6fa010 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -1583,7 +1583,7 @@ msgstr "" msgid "ClusterIntegration|Prometheus is an open-source monitoring system with %{gitlabIntegrationLink} to monitor deployed applications." msgstr "" -msgid "ClusterIntegration|RBAC-enabled cluster (experimental)" +msgid "ClusterIntegration|RBAC-enabled cluster" msgstr "" msgid "ClusterIntegration|Read our %{link_to_help_page} on Kubernetes cluster integration." @@ -1655,9 +1655,6 @@ msgstr "" msgid "ClusterIntegration|The IP address is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time." msgstr "" -msgid "ClusterIntegration|The default cluster configuration grants access to many functionalities needed to successfully build and deploy a containerised application." -msgstr "" - msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below" msgstr "" -- cgit v1.2.1 From 961d43a97b9a971e69b94f0882602746c493e455 Mon Sep 17 00:00:00 2001 From: Mark Chao Date: Thu, 27 Sep 2018 15:22:24 +0800 Subject: Memorize project to avoid re-query [skip ci] --- app/models/deploy_token.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/models/deploy_token.rb b/app/models/deploy_token.rb index 0b2eedf3631..e3524305346 100644 --- a/app/models/deploy_token.rb +++ b/app/models/deploy_token.rb @@ -4,6 +4,7 @@ class DeployToken < ActiveRecord::Base include Expirable include TokenAuthenticatable include PolicyActor + include Gitlab::Utils::StrongMemoize add_authentication_token_field :token AVAILABLE_SCOPES = %i(read_repository read_registry).freeze @@ -49,7 +50,9 @@ class DeployToken < ActiveRecord::Base # to a single project, later we're going to extend # that to be for multiple projects and namespaces. def project - projects.first + strong_memoize(:project) do + projects.first + end end def expires_at -- cgit v1.2.1 From c4bcb3a92ad6adb1acc76f337660d755e9db5409 Mon Sep 17 00:00:00 2001 From: Mark Chao Date: Thu, 27 Sep 2018 15:54:01 +0800 Subject: Add index to speed up loading projects from DeployToken --- ...dd_index_to_project_deploy_tokens_deploy_token_id.rb | 17 +++++++++++++++++ db/schema.rb | 1 + 2 files changed, 18 insertions(+) create mode 100644 db/migrate/20180927073410_add_index_to_project_deploy_tokens_deploy_token_id.rb diff --git a/db/migrate/20180927073410_add_index_to_project_deploy_tokens_deploy_token_id.rb b/db/migrate/20180927073410_add_index_to_project_deploy_tokens_deploy_token_id.rb new file mode 100644 index 00000000000..cd78779068a --- /dev/null +++ b/db/migrate/20180927073410_add_index_to_project_deploy_tokens_deploy_token_id.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class AddIndexToProjectDeployTokensDeployTokenId < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + add_concurrent_index :project_deploy_tokens, :deploy_token_id + end + + def down + remove_concurrent_index(:project_deploy_tokens, :deploy_token_id) if Gitlab::Database.postgresql? + end +end diff --git a/db/schema.rb b/db/schema.rb index 1a8b556228d..133b46819d0 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -1588,6 +1588,7 @@ ActiveRecord::Schema.define(version: 20181101144347) do t.datetime_with_timezone "created_at", null: false end + add_index "project_deploy_tokens", ["deploy_token_id"], name: "index_project_deploy_tokens_on_deploy_token_id", using: :btree add_index "project_deploy_tokens", ["project_id", "deploy_token_id"], name: "index_project_deploy_tokens_on_project_id_and_deploy_token_id", unique: true, using: :btree create_table "project_features", force: :cascade do |t| -- cgit v1.2.1 From 666ddec9b209d795ee315d1ea896feb04c30e048 Mon Sep 17 00:00:00 2001 From: Mark Chao Date: Mon, 1 Oct 2018 11:02:38 +0800 Subject: Combine two updates in one query --- app/models/project.rb | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index fa995b5b061..08315606711 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -95,8 +95,7 @@ class Project < ActiveRecord::Base unless: :ci_cd_settings, if: proc { ProjectCiCdSetting.available? } - after_create :set_last_activity_at - after_create :set_last_repository_updated_at + after_create :set_timestamps_for_create after_update :update_forks_visibility_level before_destroy :remove_private_deploy_keys @@ -2102,13 +2101,8 @@ class Project < ActiveRecord::Base gitlab_shell.exists?(repository_storage, "#{disk_path}.git") end - # set last_activity_at to the same as created_at - def set_last_activity_at - update_column(:last_activity_at, self.created_at) - end - - def set_last_repository_updated_at - update_column(:last_repository_updated_at, self.created_at) + def set_timestamps_for_create + update_columns(last_activity_at: self.created_at, last_repository_updated_at: self.created_at) end def cross_namespace_reference?(from) -- cgit v1.2.1 From b47e666026c30dbfaa0012655bb01eb18d1c3a63 Mon Sep 17 00:00:00 2001 From: Mark Chao Date: Mon, 29 Oct 2018 18:36:32 +0800 Subject: Make migration symmetric --- ...0180927073410_add_index_to_project_deploy_tokens_deploy_token_id.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/db/migrate/20180927073410_add_index_to_project_deploy_tokens_deploy_token_id.rb b/db/migrate/20180927073410_add_index_to_project_deploy_tokens_deploy_token_id.rb index cd78779068a..61d32fe16eb 100644 --- a/db/migrate/20180927073410_add_index_to_project_deploy_tokens_deploy_token_id.rb +++ b/db/migrate/20180927073410_add_index_to_project_deploy_tokens_deploy_token_id.rb @@ -8,7 +8,8 @@ class AddIndexToProjectDeployTokensDeployTokenId < ActiveRecord::Migration disable_ddl_transaction! def up - add_concurrent_index :project_deploy_tokens, :deploy_token_id + # MySQL already has index inserted + add_concurrent_index :project_deploy_tokens, :deploy_token_id if Gitlab::Database.postgresql? end def down -- cgit v1.2.1 From 72566b1706eb4d6668bd06bff881c00a8e6ab247 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 23 Jul 2018 14:25:02 +0200 Subject: Add AWS installation docs --- doc/install/README.md | 6 +- doc/install/aws/img/add_storage.png | Bin 0 -> 27490 bytes doc/install/aws/img/add_tags.png | Bin 0 -> 17834 bytes doc/install/aws/img/associate_subnet_gateway.png | Bin 0 -> 16522 bytes doc/install/aws/img/associate_subnet_gateway_2.png | Bin 0 -> 8455 bytes doc/install/aws/img/aws_diagram.png | Bin 0 -> 31122 bytes doc/install/aws/img/choose_ami.png | Bin 0 -> 28588 bytes doc/install/aws/img/choose_instance_type.png | Bin 0 -> 52257 bytes doc/install/aws/img/configure_instance.png | Bin 0 -> 44250 bytes doc/install/aws/img/configure_security_group.png | Bin 0 -> 33101 bytes doc/install/aws/img/create_gateway.png | Bin 0 -> 13927 bytes doc/install/aws/img/create_iam_role.png | Bin 0 -> 33094 bytes doc/install/aws/img/create_iam_role_review.png | Bin 0 -> 17129 bytes doc/install/aws/img/create_route_table.png | Bin 0 -> 8293 bytes doc/install/aws/img/create_security_group.png | Bin 0 -> 23218 bytes doc/install/aws/img/create_subnet.png | Bin 0 -> 16349 bytes doc/install/aws/img/create_vpc.png | Bin 0 -> 15613 bytes doc/install/aws/img/select_ssh_key.png | Bin 0 -> 18336 bytes doc/install/aws/index.md | 353 +++++++++++++++++++++ 19 files changed, 356 insertions(+), 3 deletions(-) create mode 100644 doc/install/aws/img/add_storage.png create mode 100644 doc/install/aws/img/add_tags.png create mode 100644 doc/install/aws/img/associate_subnet_gateway.png create mode 100644 doc/install/aws/img/associate_subnet_gateway_2.png create mode 100644 doc/install/aws/img/aws_diagram.png create mode 100644 doc/install/aws/img/choose_ami.png create mode 100644 doc/install/aws/img/choose_instance_type.png create mode 100644 doc/install/aws/img/configure_instance.png create mode 100644 doc/install/aws/img/configure_security_group.png create mode 100644 doc/install/aws/img/create_gateway.png create mode 100644 doc/install/aws/img/create_iam_role.png create mode 100644 doc/install/aws/img/create_iam_role_review.png create mode 100644 doc/install/aws/img/create_route_table.png create mode 100644 doc/install/aws/img/create_security_group.png create mode 100644 doc/install/aws/img/create_subnet.png create mode 100644 doc/install/aws/img/create_vpc.png create mode 100644 doc/install/aws/img/select_ssh_key.png create mode 100644 doc/install/aws/index.md diff --git a/doc/install/README.md b/doc/install/README.md index 27df03c6ac6..92116305775 100644 --- a/doc/install/README.md +++ b/doc/install/README.md @@ -34,11 +34,11 @@ the hardware requirements. - [Install GitLab on Google Cloud Platform](google_cloud_platform/index.md) - [Install GitLab on Google Kubernetes Engine (GKE)](https://about.gitlab.com/2017/01/23/video-tutorial-idea-to-production-on-google-container-engine-gke/): video tutorial on the full process of installing GitLab on Google Kubernetes Engine (GKE), pushing an application to GitLab, building the app with GitLab CI/CD, and deploying to production. -- [Install on AWS](https://about.gitlab.com/aws/) -- _Testing only!_ [DigitalOcean and Docker Machine](digitaloceandocker.md) - - Quickly test any version of GitLab on DigitalOcean using Docker Machine. +- [Install on AWS](aws/index.md): Install GitLab on AWS using the community AMIs that GitLab provides. - [Getting started with GitLab and DigitalOcean](https://about.gitlab.com/2016/04/27/getting-started-with-gitlab-and-digitalocean/): requirements, installation process, updates. - [Demo: Cloud Native Development with GitLab](https://about.gitlab.com/2017/04/18/cloud-native-demo/): video demonstration on how to install GitLab on Kubernetes, build a project, create Review Apps, store Docker images in Container Registry, deploy to production on Kubernetes, and monitor with Prometheus. +- _Testing only!_ [DigitalOcean and Docker Machine](digitaloceandocker.md) - + Quickly test any version of GitLab on DigitalOcean using Docker Machine. ## Database diff --git a/doc/install/aws/img/add_storage.png b/doc/install/aws/img/add_storage.png new file mode 100644 index 00000000000..6fb399c3cc5 Binary files /dev/null and b/doc/install/aws/img/add_storage.png differ diff --git a/doc/install/aws/img/add_tags.png b/doc/install/aws/img/add_tags.png new file mode 100644 index 00000000000..3572cd5daa1 Binary files /dev/null and b/doc/install/aws/img/add_tags.png differ diff --git a/doc/install/aws/img/associate_subnet_gateway.png b/doc/install/aws/img/associate_subnet_gateway.png new file mode 100644 index 00000000000..1edca974fca Binary files /dev/null and b/doc/install/aws/img/associate_subnet_gateway.png differ diff --git a/doc/install/aws/img/associate_subnet_gateway_2.png b/doc/install/aws/img/associate_subnet_gateway_2.png new file mode 100644 index 00000000000..17a3a87ac75 Binary files /dev/null and b/doc/install/aws/img/associate_subnet_gateway_2.png differ diff --git a/doc/install/aws/img/aws_diagram.png b/doc/install/aws/img/aws_diagram.png new file mode 100644 index 00000000000..c1ed0f41370 Binary files /dev/null and b/doc/install/aws/img/aws_diagram.png differ diff --git a/doc/install/aws/img/choose_ami.png b/doc/install/aws/img/choose_ami.png new file mode 100644 index 00000000000..b6dfa49e4bf Binary files /dev/null and b/doc/install/aws/img/choose_ami.png differ diff --git a/doc/install/aws/img/choose_instance_type.png b/doc/install/aws/img/choose_instance_type.png new file mode 100644 index 00000000000..06c288f3f0c Binary files /dev/null and b/doc/install/aws/img/choose_instance_type.png differ diff --git a/doc/install/aws/img/configure_instance.png b/doc/install/aws/img/configure_instance.png new file mode 100644 index 00000000000..f7c5c1cc975 Binary files /dev/null and b/doc/install/aws/img/configure_instance.png differ diff --git a/doc/install/aws/img/configure_security_group.png b/doc/install/aws/img/configure_security_group.png new file mode 100644 index 00000000000..ea4b43b2c8f Binary files /dev/null and b/doc/install/aws/img/configure_security_group.png differ diff --git a/doc/install/aws/img/create_gateway.png b/doc/install/aws/img/create_gateway.png new file mode 100644 index 00000000000..9408520e050 Binary files /dev/null and b/doc/install/aws/img/create_gateway.png differ diff --git a/doc/install/aws/img/create_iam_role.png b/doc/install/aws/img/create_iam_role.png new file mode 100644 index 00000000000..e557945a0a5 Binary files /dev/null and b/doc/install/aws/img/create_iam_role.png differ diff --git a/doc/install/aws/img/create_iam_role_review.png b/doc/install/aws/img/create_iam_role_review.png new file mode 100644 index 00000000000..b056f7d66d6 Binary files /dev/null and b/doc/install/aws/img/create_iam_role_review.png differ diff --git a/doc/install/aws/img/create_route_table.png b/doc/install/aws/img/create_route_table.png new file mode 100644 index 00000000000..ea72c57257e Binary files /dev/null and b/doc/install/aws/img/create_route_table.png differ diff --git a/doc/install/aws/img/create_security_group.png b/doc/install/aws/img/create_security_group.png new file mode 100644 index 00000000000..2d8d74ac988 Binary files /dev/null and b/doc/install/aws/img/create_security_group.png differ diff --git a/doc/install/aws/img/create_subnet.png b/doc/install/aws/img/create_subnet.png new file mode 100644 index 00000000000..ee4893bf6b1 Binary files /dev/null and b/doc/install/aws/img/create_subnet.png differ diff --git a/doc/install/aws/img/create_vpc.png b/doc/install/aws/img/create_vpc.png new file mode 100644 index 00000000000..a678f7013fd Binary files /dev/null and b/doc/install/aws/img/create_vpc.png differ diff --git a/doc/install/aws/img/select_ssh_key.png b/doc/install/aws/img/select_ssh_key.png new file mode 100644 index 00000000000..b2adcd631bc Binary files /dev/null and b/doc/install/aws/img/select_ssh_key.png differ diff --git a/doc/install/aws/index.md b/doc/install/aws/index.md new file mode 100644 index 00000000000..a026eba4c64 --- /dev/null +++ b/doc/install/aws/index.md @@ -0,0 +1,353 @@ +# Installing GitLab on AWS + +GitLab can be installed on Amazon Web Services (AWS) by using the official +AMIs provided with each release. + +## Introduction + +In this guide, we will explore the simplest way to install GitLab on AWS. +That means that this will be a single EC2 node, and all GitLab's components, +including the database, will be hosted on the same instance. + +If you are interested for a highly available environment, check the +[high availability docs](../../administration/high_availability/README.md). + +## Architecture + +Below is the diagram of the architecture. + +![AWS architecture](img/aws_diagram.png) + +## Requirements + +A basic familiarity with AWS and EC2 is assumed. In particular, you will need: + +- [An AWS account](https://console.aws.amazon.com/console/home) +- [Create or upload](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-key-pairs.html) + an SSH key to connect to the instance via SSH +- A domain name under which GitLab will be reached + +## Costs + +Based on [GitLab's requirements](../requirements.md#hardware-requirements), the +instance type should be at least `c4.xlarge`. This is enough to accommodate 100 users. + +Here's a list of the services we will use and their costs: + +- **EC2** - GitLab will deployed on shared hardware which means + [on-demand pricing](https://aws.amazon.com/ec2/pricing/on-demand) + will apply. If you want to run it on a dedicated or reserved instance, + consult the [EC2 pricing page](https://aws.amazon.com/ec2/pricing/) for more + information on the cost. +- **EBS** - We will also use an EBS volume to store the Git data. See the + [Amazon EBS pricing](https://aws.amazon.com/ebs/pricing/). +- **S3** - We will use S3 to store backups. See the + [Amazon S3 pricing](https://aws.amazon.com/s3/pricing/). + +## Security + +We will create a new IAM role specifically for deploying GitLab, a new VPC, as +well as a security group with limited port access to the instance. + +### Creating an IAM EC2 instance role and profile + +To minimize the permissions of the user, we'll create a new IAM role with +limited access: + +1. Navigate to the IAM dashboard https://console.aws.amazon.com/iam/home and + click on **Create role**. +1. Create a new role by choosing to **AWS service > EC2**. Once done, click on + **Next: Permissions**. + + ![Create role](img/create_iam_role.png) + +1. Choose **AmazonEC2FullAccess** and **AmazonS3FullAccess** and click on **Next: Review**. +1. Give the role the name `GitLabAdmin` and click **Create role**. + + ![Create role](img/create_iam_role_review.png) + +### Configuring the network + +We'll start by creating a VPC for our GitLab cloud infrastructure, then we can +create subnets to have public and private instances. Public subnets will require +a Route Table and an associated Internet Gateway. + +Let's create a VPC: + +1. Navigate to https://console.aws.amazon.com/vpc/home +1. Select **Your VPCs** from the left menu and then click on **Create VPC**. + At the name tag enter `gitlab-vpc` and at the IPv4 CIDR block enter `10.0.0.0/16`. + Click **Yes, Create** when ready. + + ![Create VPC](img/create_vpc.png) + +Now, onto creating a subnet: + +1. Select **Subnets** from the left menu. +1. Click on **Create subnet**. Give it a descriptive name tag based on the IP, + for example `gitlab-subnet-10.0.0.0`, select the VPC we created previously, + and at the IPv4 CIDR block let's give it a 24 subnet `10.0.0.0/24`: + + ![Create subnet](img/create_subnet.png) + +Since the newly created subnet is private, we need to create a Route Table to +associate an Internet Gateway: + +1. Select **Route Tables** from the left menu. +1. Click **Create Route Table**. +1. At the "Name tag" enter `gitlab-public` and choose `gitlab-vpc` under "VPC". +1. Hit **Yes, Create**. + +Now, create the Internet gateway: + +1. Select **Internet Gateways** from the left menu. +1. Click on **Create internet gateway**, give it the name `gitlab-gateway` and + click **Create**. +1. Select it from the table, and then under the **Actions** dropdown choose + "Attach to VPC". + + ![Create gateway](img/create_gateway.png) + +1. Choose `gitlab-vpc` from the list and hit **Create**. + +Now it's time to add the route to the subnet: + +1. Select **Route Tables** from the left menu and click on the `gitlab-public` + route to show the options at the bottom. +1. Select the **Routes** tab, hit **Edit > Add another route** and set `0.0.0.0/0` + as destination. In the target, select the `gitlab-gateway` we created previously. + Hit **Save** once done. + + ![Associate subnet with gateway](img/associate_subnet_gateway.png) + +1. Select the **Subnet Associations** tab and hit **Edit**. +1. Check the subnet and hit **Save**. + + ![Associate subnet with gateway](img/associate_subnet_gateway_2.png) + +Now that we're done with the network, let's create a security group. + +### Creating a security group + +The security group is basically the firewall. + +1. Select **Security Groups** from the left menu. +1. Click on **Create Security Group** and fill in the details. Give it a name, + add a description, choose the VPC we created previously, and finally, add + the inbound rules. + You will need to open the SSH, HTTP, HTTPS ports. Leave the outbound traffic + as is. + + ![Create security group](img/create_security_group.png) + + TIP: **Tip:** + Depending on your setup, you might want to allow SSH traffic from only a known + host. In that case, change the SSH source to be custom and give it the IP + you want to SSH from. + +1. When done, click on **Create**. + +--- + +Now that we have set up security, let's deploy GitLab. + +## Deploying GitLab + +We'll use AWS's wizard to deploy GitLab and then SSH into the instance to +configure the domain name. + +### Choose the AMI + +1. On the EC2 dashboard click **Launch Instance**. +1. Choose the AMI by going to the Community AMIs and search for `GitLab EE ` + where `` the latest version as seen in the + [releases page](https://about.gitlab.com/releases/). + + ![Choose AMI](img/choose_ami.png) + +### Choose instance type + +1. Choose the `c4.xlarge` instance. + + ![Choose instance type](img/choose_instance_type.png) + +1. Click **Next: Configure Instance Details** + +### Configure instance + +1. Configure the instance. At "Network" choose `gitlab-vpc` and the subnet we + created for that VPC. Select "Enable" for the "Auto-assign Public IP" and + choose the `GitLabAdmin` IAM role. + + ![Configure instance](img/configure_instance.png) + +1. Click **Next: Add Storage**. + +### Add storage + +Edit the root volume to 20GB, and add a new EBS volume that will host the Git data. +Its size depends on your needs and you can always migrate to a bigger volume later. + +![Add storage](img/add_storage.png) + +### Add tags + +To help you manage your instances, you can optionally assign your own metadata +to each resource in the [form of tags](https://docs.aws.amazon.com/console/ec2/tags). + +Let's add one with its key set to `Name` and value to `GitLab`. + +![Add tags](img/add_tags.png) + +### Configure security group + +1. Select the existing security group we [have created](#creating-a-security-group). + + ![Add security group](img/configure_security_group.png) + +1. Select **Review and Launch**. + +### Review and launch + +Now is a good time to review all the previous settings. Click **Launch** and +select the SSH key pair you have created previously. + +![Select SSH key](img/select_ssh_key.png) + +Finally, click on **Launch instances**. + +## After deployment + +After a few minutes, the instance should be up and accessible via the internet. +Let's connect to it and configure some things before logging in. + +### Setting up the EBS volume + +The EBS volume will host the Git data. We need to first format the `/dev/xvdb` +volume and then mount it: + +1. First, create the directory that the volume will be mounted to: + + ```sh + sudo mkdir /gitlab-data + ``` + +1. Create a partition with a GUID Partition Table (GPT), mark it as + primary, choose the `ext4` file system, and use all its size: + + ```sh + sudo parted --script /dev/xvdb mklabel gpt mkpart primary ext4 0% 100% + ``` + +1. Format to `ext4`: + + ```sh + sudo mkfs.ext4 -L Data /dev/xvdb1 + ``` + +1. Find its PARTUUID: + + ```sh + blkid /dev/xvdb1 + ``` + + You need to copy the PARTUUID number (without the quotes) and use this to + mount the newly created partition. + +1. Open `/etc/fstab` with your editor, comment out the entry about `/dev/xvdb`, + and add the new partition: + + ``` + PARTUUID=d4129b25-a3c9-4d2c-a090-2c234fee4d46 /gitlab-data ext4 defaults,nofail,x-systemd.requires=cloud-init.service,comment=cloudconfig 0 2 + ``` + +1. Mount the partition: + + ```sh + sudo mount -a + ``` + +--- + +Now that the partition is created and mounted, it's time to tell GitLab to store +its data to the new `/gitlab-data` directory: + +1. Edit `/etc/gitlab/gitlab.rb` with your editor and add the following: + + ```ruby + git_data_dirs({ "default" => { "path" => "/gitlab-data" } }) + ``` + +1. Save the file and reconfigure GitLab: + + ```sh + sudo gitlab-ctl reconfigure + ``` + +Read more on [storing Git data in an alternative directory](https://docs.gitlab.com/omnibus/settings/configuration.html#storing-git-data-in-an-alternative-directory). + +### Setting up a domain name + +After you SSH into the instance, configure the domain name: + +1. Open `/etc/gitlab/gitlab.rb` with your favorite editor. +1. Edit the `external_url` value: + + ```ruby + external_url 'http://example.com' + ``` + +1. Reconfigure GitLab: + + ```sh + sudo gitlab-ctl reconfigure + ``` + +You should now be able to reach GitLab at the URL you defined. To use HTTPS +(recommended), see the [HTTPS documentation](https://docs.gitlab.com/omnibus/settings/nginx.html#enable-https). + +### Logging in for the first time + +If you followed the previous section, you should be now able to visit GitLab +in your browser. The very first time, you will be asked to set up a password +for the `root` user which has admin privileges on the GitLab instance. + +After you set it up, login with username `root` and the newly created password. + +## Backup and restore + +GitLab provides [a tool to backup](../../raketasks/backup_restore.md#creating-a-backup-of-the-gitlab-system) +and restore its Git data, database, and other files. You can also +[backup GitLab using S3](../../raketasks/backup_restore.md#using-amazon-s3). + +Bare in mind that the backup tool does not store +[the configuration files](../../raketasks/backup_restore.md#storing-configuration-files), +you'll need to do it yourself. + +## Updating GitLab + +GitLab releases a new version every month on the 22nd. Whenever a new version is +released, you can update your GitLab instance: + +1. SSH into your instance +1. Take a backup: + + ```sh + sudo gitlab-rake gitlab:backup:create + ``` + +1. Update the repositories and install GitLab: + + ```sh + sudo apt update + sudo apt install gitlab-ee + ``` + +After a few minutes, the new version should be up and running. + +## Resources + +- [Omnibus GitLab](https://docs.gitlab.com/omnibus/) - Everything you need to know + about administering your GitLab instance. +- [Upload a license](https://docs.gitlab.com/ee/user/admin_area/license.html) - Activate all GitLab + Enterprise Edition functionality with a license. -- cgit v1.2.1 From 65a1d93341ec5339dbe518cd5518fab67959ed8a Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Wed, 19 Sep 2018 11:45:18 +0200 Subject: Clearer info on backup/restore --- doc/install/aws/index.md | 47 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/doc/install/aws/index.md b/doc/install/aws/index.md index a026eba4c64..bfbea56cd69 100644 --- a/doc/install/aws/index.md +++ b/doc/install/aws/index.md @@ -5,7 +5,8 @@ AMIs provided with each release. ## Introduction -In this guide, we will explore the simplest way to install GitLab on AWS. +In this guide, we will explore the simplest way to install GitLab on AWS using +the [Omnibus GitLab package](https://docs.gitlab.com/omnibus). That means that this will be a single EC2 node, and all GitLab's components, including the database, will be hosted on the same instance. @@ -34,15 +35,17 @@ instance type should be at least `c4.xlarge`. This is enough to accommodate 100 Here's a list of the services we will use and their costs: -- **EC2** - GitLab will deployed on shared hardware which means +- **EC2**: GitLab will deployed on shared hardware which means [on-demand pricing](https://aws.amazon.com/ec2/pricing/on-demand) will apply. If you want to run it on a dedicated or reserved instance, consult the [EC2 pricing page](https://aws.amazon.com/ec2/pricing/) for more information on the cost. -- **EBS** - We will also use an EBS volume to store the Git data. See the +- **EBS**: We will also use an EBS volume to store the Git data. See the [Amazon EBS pricing](https://aws.amazon.com/ebs/pricing/). -- **S3** - We will use S3 to store backups. See the +- **S3**: We will use S3 to store backups. See the [Amazon S3 pricing](https://aws.amazon.com/s3/pricing/). +- **ALB**: An Application Load Balancer will be used to route requests to the + GitLab instance. See the [Amazon ELB pricing](https://aws.amazon.com/elasticloadbalancing/pricing/). ## Security @@ -141,9 +144,9 @@ The security group is basically the firewall. ![Create security group](img/create_security_group.png) TIP: **Tip:** - Depending on your setup, you might want to allow SSH traffic from only a known - host. In that case, change the SSH source to be custom and give it the IP - you want to SSH from. + Based on best practices, you should only allow SSH traffic from only a known + host or CIDR block. In that case, change the SSH source to be custom and give + it the IP you want to SSH from. 1. When done, click on **Create**. @@ -317,12 +320,32 @@ After you set it up, login with username `root` and the newly created password. ## Backup and restore GitLab provides [a tool to backup](../../raketasks/backup_restore.md#creating-a-backup-of-the-gitlab-system) -and restore its Git data, database, and other files. You can also -[backup GitLab using S3](../../raketasks/backup_restore.md#using-amazon-s3). +and restore its Git data, database, attachments, LFS objects, etc. -Bare in mind that the backup tool does not store -[the configuration files](../../raketasks/backup_restore.md#storing-configuration-files), -you'll need to do it yourself. +Some things to know: + +- By default, the backup files are stored locally, but you can + [backup GitLab using S3](../../raketasks/backup_restore.md#using-amazon-s3). +- You can exclude [specific directories form the backup](../../raketasks/backup_restore.md#excluding-specific-directories-from-the-backup). +- The backup/restore tool does not store some configuration files, like secrets, you'll + need to [do it yourself](../../raketasks/backup_restore.md#storing-configuration-files). + +### Backing up GitLab + +To backup GitLab: + +1. SSH into your instance. +1. Take a backup: + + ```sh + sudo gitlab-rake gitlab:backup:create + ``` + +### Restoring GitLab from a backup + +To restore GitLab, first check the [restore documentation](../../raketasks/backup_restore.md#restore) +and mainly the restore prerequisites. Then, follow the steps under the +[Omnibus installations section](../../raketasks/backup_restore.md#restore-for-omnibus-installations). ## Updating GitLab -- cgit v1.2.1 From 237a6c6278cbcec28d08c0db782647ed1b1e68c5 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 20 Sep 2018 11:10:27 +0200 Subject: Mention LFS S3 support --- doc/install/aws/index.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/doc/install/aws/index.md b/doc/install/aws/index.md index bfbea56cd69..b3ac8c57fa5 100644 --- a/doc/install/aws/index.md +++ b/doc/install/aws/index.md @@ -289,6 +289,11 @@ its data to the new `/gitlab-data` directory: Read more on [storing Git data in an alternative directory](https://docs.gitlab.com/omnibus/settings/configuration.html#storing-git-data-in-an-alternative-directory). +### LFS objects on S3 + +If you intend to use Git LFS, you can +[store the LFS objects in S3](../../workflow/lfs/lfs_administration.md#s3-for-omnibus-installations). + ### Setting up a domain name After you SSH into the instance, configure the domain name: -- cgit v1.2.1 From cf232bb6d6a763b90ba1520845b1fa0e4957560f Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 21 Sep 2018 15:10:25 +0200 Subject: Move architecture section to the top --- doc/install/aws/index.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/install/aws/index.md b/doc/install/aws/index.md index b3ac8c57fa5..2069c2bde03 100644 --- a/doc/install/aws/index.md +++ b/doc/install/aws/index.md @@ -13,12 +13,6 @@ including the database, will be hosted on the same instance. If you are interested for a highly available environment, check the [high availability docs](../../administration/high_availability/README.md). -## Architecture - -Below is the diagram of the architecture. - -![AWS architecture](img/aws_diagram.png) - ## Requirements A basic familiarity with AWS and EC2 is assumed. In particular, you will need: @@ -28,6 +22,12 @@ A basic familiarity with AWS and EC2 is assumed. In particular, you will need: an SSH key to connect to the instance via SSH - A domain name under which GitLab will be reached +## Architecture + +Below is the diagram of the architecture. + +![AWS architecture](img/aws_diagram.png) + ## Costs Based on [GitLab's requirements](../requirements.md#hardware-requirements), the -- cgit v1.2.1 From 7a40204e759ad6a4394a867a349e0e963d9b7e34 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 24 Sep 2018 21:44:08 +0200 Subject: Merge content from university AWS docs https://docs.gitlab.com/ee/university/high-availability/aws --- doc/install/aws/img/associate_subnet_gateway_2.png | Bin 8455 -> 10617 bytes doc/install/aws/img/create_security_group.png | Bin 23218 -> 12594 bytes doc/install/aws/img/create_subnet.png | Bin 16349 -> 16679 bytes doc/install/aws/index.md | 306 ++++++++++++++++++--- 4 files changed, 266 insertions(+), 40 deletions(-) diff --git a/doc/install/aws/img/associate_subnet_gateway_2.png b/doc/install/aws/img/associate_subnet_gateway_2.png index 17a3a87ac75..76e101d32a3 100644 Binary files a/doc/install/aws/img/associate_subnet_gateway_2.png and b/doc/install/aws/img/associate_subnet_gateway_2.png differ diff --git a/doc/install/aws/img/create_security_group.png b/doc/install/aws/img/create_security_group.png index 2d8d74ac988..9a0dfccfe37 100644 Binary files a/doc/install/aws/img/create_security_group.png and b/doc/install/aws/img/create_security_group.png differ diff --git a/doc/install/aws/img/create_subnet.png b/doc/install/aws/img/create_subnet.png index ee4893bf6b1..26f5ab1b625 100644 Binary files a/doc/install/aws/img/create_subnet.png and b/doc/install/aws/img/create_subnet.png differ diff --git a/doc/install/aws/index.md b/doc/install/aws/index.md index 2069c2bde03..4134e822579 100644 --- a/doc/install/aws/index.md +++ b/doc/install/aws/index.md @@ -5,13 +5,16 @@ AMIs provided with each release. ## Introduction -In this guide, we will explore the simplest way to install GitLab on AWS using -the [Omnibus GitLab package](https://docs.gitlab.com/omnibus). -That means that this will be a single EC2 node, and all GitLab's components, -including the database, will be hosted on the same instance. +GitLab on AWS can leverage many of the services that are already +configurable with High Availability (HA). These services have a lot of +flexibility and are able to adopt to most companies, best of all is the +ability to automate both vertical and horizontal scaling. -If you are interested for a highly available environment, check the -[high availability docs](../../administration/high_availability/README.md). +In this guide we'll go through a basic HA setup where we'll start by +configuring our Virtual Private Cloud and subnets to later integrate +services such as RDS for our database server and ElastiCache as a Redis +cluster to finally manage them within an auto scaling group with custom +scaling policies. ## Requirements @@ -30,9 +33,6 @@ Below is the diagram of the architecture. ## Costs -Based on [GitLab's requirements](../requirements.md#hardware-requirements), the -instance type should be at least `c4.xlarge`. This is enough to accommodate 100 users. - Here's a list of the services we will use and their costs: - **EC2**: GitLab will deployed on shared hardware which means @@ -42,17 +42,15 @@ Here's a list of the services we will use and their costs: information on the cost. - **EBS**: We will also use an EBS volume to store the Git data. See the [Amazon EBS pricing](https://aws.amazon.com/ebs/pricing/). -- **S3**: We will use S3 to store backups. See the +- **S3**: We will use S3 to store backups, artifacts, LFS objects, etc. See the [Amazon S3 pricing](https://aws.amazon.com/s3/pricing/). - **ALB**: An Application Load Balancer will be used to route requests to the GitLab instance. See the [Amazon ELB pricing](https://aws.amazon.com/elasticloadbalancing/pricing/). +- **RDS**: An Amazon Relational Database Service using PostgreSQL will be used + to provide database High Availability. See the + [Amazon RDS pricing](https://aws.amazon.com/rds/postgresql/pricing/). -## Security - -We will create a new IAM role specifically for deploying GitLab, a new VPC, as -well as a security group with limited port access to the instance. - -### Creating an IAM EC2 instance role and profile +## Creating an IAM EC2 instance role and profile To minimize the permissions of the user, we'll create a new IAM role with limited access: @@ -69,39 +67,65 @@ limited access: ![Create role](img/create_iam_role_review.png) -### Configuring the network +## Configuring the network + +We'll start by creating a VPC for our GitLab cloud infrastructure, then +we can create subnets to have public and private instances in at least +two AZs. Public subnets will require a Route Table keep and an associated +Internet Gateway. -We'll start by creating a VPC for our GitLab cloud infrastructure, then we can -create subnets to have public and private instances. Public subnets will require -a Route Table and an associated Internet Gateway. +### VPC Let's create a VPC: 1. Navigate to https://console.aws.amazon.com/vpc/home 1. Select **Your VPCs** from the left menu and then click on **Create VPC**. At the name tag enter `gitlab-vpc` and at the IPv4 CIDR block enter `10.0.0.0/16`. + If you don't require dedicated hardware, you can leave tenancy as default. Click **Yes, Create** when ready. ![Create VPC](img/create_vpc.png) -Now, onto creating a subnet: +### Subnet + +Now, let's create some subnets in different Availability Zones. Make sure +that each subnet is associated the the VPC we just created and +that CIDR blocks don't overlap. This will also +allow us to enable multi AZ for redundancy. + +We will create private and public subnets to match load balancers and +RDS instances as well: 1. Select **Subnets** from the left menu. 1. Click on **Create subnet**. Give it a descriptive name tag based on the IP, - for example `gitlab-subnet-10.0.0.0`, select the VPC we created previously, + for example `gitlab-public-10.0.0.0`, select the VPC we created previously, and at the IPv4 CIDR block let's give it a 24 subnet `10.0.0.0/24`: ![Create subnet](img/create_subnet.png) -Since the newly created subnet is private, we need to create a Route Table to -associate an Internet Gateway: +1. Follow the same steps to create all subnets: + + | Name tag | Availability Zone | CIDR block | + | -------- | ----------------- | ---------- | + | gitlab-public-10.0.0.0 | us-west-2a | 10.0.0.0 | + | gitlab-private-10.0.1.0 | us-west-2a | 10.0.1.0 | + | gitlab-public-10.0.2.0 | us-west-2b | 10.0.2.0 | + | gitlab-private-10.0.3.0 | us-west-2b | 10.0.3.0 | + +### Route Table + +Up to now all our subnets are private. We need to create a Route Table +to associate an Internet Gateway. On the same VPC dashboard: 1. Select **Route Tables** from the left menu. 1. Click **Create Route Table**. 1. At the "Name tag" enter `gitlab-public` and choose `gitlab-vpc` under "VPC". 1. Hit **Yes, Create**. -Now, create the Internet gateway: +### Internet Gateway + +Now, still on the same dashboard head over to Internet Gateways and +create a new one: 1. Select **Internet Gateways** from the left menu. 1. Click on **Create internet gateway**, give it the name `gitlab-gateway` and @@ -111,11 +135,14 @@ Now, create the Internet gateway: ![Create gateway](img/create_gateway.png) -1. Choose `gitlab-vpc` from the list and hit **Create**. +1. Choose `gitlab-vpc` from the list and hit **Attach**. + +### Configuring subnets -Now it's time to add the route to the subnet: +We now need to add a new target which will be our Internet Gateway and have +it receive traffic from any destination. -1. Select **Route Tables** from the left menu and click on the `gitlab-public` +1. Select **Route Tables** from the left menu and select the `gitlab-public` route to show the options at the bottom. 1. Select the **Routes** tab, hit **Edit > Add another route** and set `0.0.0.0/0` as destination. In the target, select the `gitlab-gateway` we created previously. @@ -123,23 +150,27 @@ Now it's time to add the route to the subnet: ![Associate subnet with gateway](img/associate_subnet_gateway.png) +Next, we must associate the **public** subnets to the route table: + 1. Select the **Subnet Associations** tab and hit **Edit**. -1. Check the subnet and hit **Save**. +1. Check only the public subnet and hit **Save**. ![Associate subnet with gateway](img/associate_subnet_gateway_2.png) +--- + Now that we're done with the network, let's create a security group. -### Creating a security group +## Creating a security group The security group is basically the firewall. 1. Select **Security Groups** from the left menu. 1. Click on **Create Security Group** and fill in the details. Give it a name, - add a description, choose the VPC we created previously, and finally, add - the inbound rules. - You will need to open the SSH, HTTP, HTTPS ports. Leave the outbound traffic - as is. + add a description, and choose the VPC we created previously +1. Select the security group from the list and at the the bottom select the + Inbound Rules tab. You will need to open the SSH, HTTP, and HTTPS ports. Set + the source to `0.0.0.0/0`. ![Create security group](img/create_security_group.png) @@ -148,11 +179,70 @@ The security group is basically the firewall. host or CIDR block. In that case, change the SSH source to be custom and give it the IP you want to SSH from. -1. When done, click on **Create**. +1. When done, click on **Save**. ---- +## PostgreSQL with RDS + +For our database server we will use Amazon RDS which offers Multi AZ +for redundancy. Lets start by creating a subnet group and then we'll +create the actual RDS instance. + +### RDS Subnet Group -Now that we have set up security, let's deploy GitLab. +From the RDS dashboard select Subnet Groups. Lets select our VPC from +the VPC ID dropdown and at the bottom we can add our private subnets. + +![Subnet Group](img/db-subnet-group.png) + +### Creating the database + +Select the RDS service from the Database section and create a new +PostgreSQL instance. After choosing between a Production or +Development instance we'll start with the actual configuration. On the +image bellow we have the settings for this article but note the +following two options which are of particular interest for HA: + +1. Multi-AZ-Deployment is recommended as redundancy. Read more at +[High Availability (Multi-AZ)](http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.MultiAZ.html) +1. While we chose a General Purpose (SSD) for this article a Provisioned +IOPS (SSD) is best suited for HA. Read more about it at +[Storage for Amazon RDS](http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_Storage.html) + +![RDS Instance Specs](img/instance_specs.png) + +The rest of the setting on this page request a DB identifier, username +and a master password. We've chosen to use `gitlab-ha`, `gitlab` and a +very secure password respectively. Keep these in hand for later. + +![Network and Security](img/rds-net-opt.png) + +Make sure to choose our gitlab VPC, our subnet group, not have it public, +and to leave it to create a new security group. The only additional +change which will be helpful is the database name for which we can use +`gitlabhq_production`. + +*** + +## Redis with ElastiCache + +EC is an in-memory hosted caching solution. Redis maintains its own +persistence and is used for certain types of application. + +Let's choose the ElastiCache service in the Database section from our +AWS console. Now lets create a cache subnet group which will be very +similar to the RDS subnet group. Make sure to select our VPC and its +private subnets. + +![ElastiCache](img/ec-subnet.png) + +Now press the Launch a Cache Cluster and choose Redis for our +DB engine. You'll be able to configure details such as replication, +Multi AZ and node types. The second section will allow us to choose our +subnet and security group and + +![Redis Cluster details](img/redis-cluster-det.png) + +![Redis Network](img/redis-net.png) ## Deploying GitLab @@ -170,6 +260,9 @@ configure the domain name. ### Choose instance type +Based on [GitLab's requirements](../requirements.md#hardware-requirements), the +instance type should be at least `c4.xlarge`. This is enough to accommodate 100 users: + 1. Choose the `c4.xlarge` instance. ![Choose instance type](img/choose_instance_type.png) @@ -219,11 +312,141 @@ select the SSH key pair you have created previously. Finally, click on **Launch instances**. +### RDS and Redis Security Group + +After the instance is being created we will navigate to our EC2 security +groups and add a small change for our EC2 instances to be able to +connect to RDS. First copy the security group name we just defined, +namely `gitlab-ec2-security-group`, and edit select the RDS security +group and edit the inbound rules. Choose the rule type to be PostgreSQL +and paste the name under source. + +![RDS security group](img/rds-sec-group.png) + +Similar to the above we'll jump to the `gitlab-ec2-security-group` group +and add a custom TCP rule for port 6379 accessible within itself. + +## Load Balancer + +On the same dashboard look for Load Balancer on the left column and press +the Create button. Choose a classic Load Balancer, our gitlab VPC, not +internal and make sure its listening for HTTP and HTTPS on port 80. + +Here is a tricky part though, when adding subnets we need to associate +public subnets instead of the private ones where our instances will +actually live. + +On the security group section let's create a new one named +`gitlab-loadbalancer-sec-group` and allow both HTTP ad HTTPS traffic +from anywhere. + +The Load Balancer Health will allow us to indicate where to ping and what +makes up a healthy or unhealthy instance. + +We won't add the instance on the next session because we'll destroy it +momentarily as we'll be using the image we where creating. We will keep +the Enable Cross-Zone and Enable Connection Draining active. + +After we finish creating the Load Balancer we can re visit our Security +Groups to improve access only through the ELB and any other requirement +you might have. + +## Auto Scaling Group + +Our AMI should be done by now so we can start working on our Auto +Scaling Group. + +This option is also available through the EC2 dashboard on the left +sidebar. Press on the create button. Select the new image on My AMIs and +give it a `t2.medium` size. To be able to use Elastic File System we need +to add a script to mount EFS automatically at launch. We'll do this at +the Advanced Details section where we have a [User Data](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/user-data.html) +text area that allows us to add a lot of custom configurations which +allows you to add a custom script for when launching an instance. Let's +add the following script to the User Data section: + + + #cloud-config + package_upgrade: true + packages: + - nfs-common + runcmd: + - mkdir -p /gitlab-data + - chown ec2-user:ec2-user /gitlab-data + - echo "$(curl --silent http://169.254.169.254/latest/meta-data/placement/availability-zone).file-system-id.aws-region.amazonaws.com:/ /gitlab-data nfs defaults,vers=4.1 0 0" >> /etc/fstab + - mount -a -t nfs + - sudo gitlab-ctl reconfigure + +On the security group section we can choose our existing +`gitlab-ec2-security-group` group which has already been tested. + +After this is launched we are able to start creating our Auto Scaling +Group. Start by giving it a name and assigning it our VPC and private +subnets. We also want to always start with two instances and if you +scroll down to Advanced Details we can choose to receive traffic from ELBs. +Lets enable that option and select our ELB. We also want to use the ELB's +health check. + +![Auto scaling](img/auto-scaling-det.png) + +### Policies + +This is the really great part of Auto Scaling, we get to choose when AWS +launches new instances and when it removes them. For this group we'll +scale between 2 and 4 instances where one instance will be added if CPU +utilization is greater than 60% and one instance is removed if it falls +to less than 45%. Here are the complete policies: + +![Policies](img/policies.png) + +You'll notice that after we save this AWS starts launching our two +instances in different AZs and without a public IP which is exactly what +we where aiming for. + ## After deployment After a few minutes, the instance should be up and accessible via the internet. Let's connect to it and configure some things before logging in. +### Configuring GitLab to connect with postgres and Redis + +While connected to your server edit the `gitlab.rb` file at `/etc/gitlab/gitlab.rb` +find the `external_url 'http://gitlab.example.com'` option and change it +to the domain you will be using or the public IP address of the current +instance to test the configuration. + +For a more detailed description about configuring GitLab read [Configuring GitLab for HA](http://docs.gitlab.com/ee/administration/high_availability/gitlab.html) + +Now look for the GitLab database settings and uncomment as necessary. In +our current case we'll specify the adapter, encoding, host, db name, +username, and password. + + gitlab_rails['db_adapter'] = "postgresql" + gitlab_rails['db_encoding'] = "unicode" + gitlab_rails['db_database'] = "gitlabhq_production" + gitlab_rails['db_username'] = "gitlab" + gitlab_rails['db_password'] = "mypassword" + gitlab_rails['db_host'] = "" + +Next we only need to configure the Redis section by adding the host and +uncommenting the port. + +The last configuration step is to [change the default file locations ](http://docs.gitlab.com/ee/administration/high_availability/nfs.html) +to make the EFS integration easier to manage. + + gitlab_rails['redis_host'] = "" + gitlab_rails['redis_port'] = 6379 + +Finally run reconfigure, you might find it useful to run a check and +a service status to make sure everything has been setup correctly. + + sudo gitlab-ctl reconfigure + sudo gitlab-rake gitlab:check + sudo gitlab-ctl status + +If everything looks good copy the Elastic IP over to your browser and +test the instance manually. + ### Setting up the EBS volume The EBS volume will host the Git data. We need to first format the `/dev/xvdb` @@ -289,10 +512,13 @@ its data to the new `/gitlab-data` directory: Read more on [storing Git data in an alternative directory](https://docs.gitlab.com/omnibus/settings/configuration.html#storing-git-data-in-an-alternative-directory). -### LFS objects on S3 +### Using S3 for the LFS objects, artifacts and Registry images + +The S3 object storage can be used for various GitLab objects: -If you intend to use Git LFS, you can -[store the LFS objects in S3](../../workflow/lfs/lfs_administration.md#s3-for-omnibus-installations). +- [How to store the LFS objects in S3](../../workflow/lfs/lfs_administration.md#s3-for-omnibus-installations) ((Omnibus GitLab installations)) +- [How to store Container Registry images to S3](../../administration/container_registry.md#container-registry-storage-driver) (Omnibus GitLab installations) +- [How to store GitLab CI job artifacts to S3](../../administration/job_artifacts.md#using-object-storage) (Omnibus GitLab installations) ### Setting up a domain name -- cgit v1.2.1 From eb5d9f919d484419ce497d1505aee9d900359df0 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 25 Sep 2018 10:14:56 +0200 Subject: Database --- doc/install/aws/img/rds_subnet_group.png | Bin 0 -> 30107 bytes doc/install/aws/index.md | 84 ++++++++++++++++++------------- 2 files changed, 48 insertions(+), 36 deletions(-) create mode 100644 doc/install/aws/img/rds_subnet_group.png diff --git a/doc/install/aws/img/rds_subnet_group.png b/doc/install/aws/img/rds_subnet_group.png new file mode 100644 index 00000000000..7c6157e38e0 Binary files /dev/null and b/doc/install/aws/img/rds_subnet_group.png differ diff --git a/doc/install/aws/index.md b/doc/install/aws/index.md index 4134e822579..4dd11c2f94b 100644 --- a/doc/install/aws/index.md +++ b/doc/install/aws/index.md @@ -86,7 +86,7 @@ Let's create a VPC: ![Create VPC](img/create_vpc.png) -### Subnet +### Subnets Now, let's create some subnets in different Availability Zones. Make sure that each subnet is associated the the VPC we just created and @@ -105,12 +105,12 @@ RDS instances as well: 1. Follow the same steps to create all subnets: - | Name tag | Availability Zone | CIDR block | - | -------- | ----------------- | ---------- | - | gitlab-public-10.0.0.0 | us-west-2a | 10.0.0.0 | - | gitlab-private-10.0.1.0 | us-west-2a | 10.0.1.0 | - | gitlab-public-10.0.2.0 | us-west-2b | 10.0.2.0 | - | gitlab-private-10.0.3.0 | us-west-2b | 10.0.3.0 | + | Name tag | Type |Availability Zone | CIDR block | + | -------- | ---- | ---------------- | ---------- | + | gitlab-public-10.0.0.0 | public | us-west-2a | 10.0.0.0 | + | gitlab-private-10.0.1.0 | private | us-west-2a | 10.0.1.0 | + | gitlab-public-10.0.2.0 | public | us-west-2b | 10.0.2.0 | + | gitlab-private-10.0.3.0 | private | us-west-2b | 10.0.3.0 | ### Route Table @@ -163,7 +163,7 @@ Now that we're done with the network, let's create a security group. ## Creating a security group -The security group is basically the firewall. +The security group is basically the firewall: 1. Select **Security Groups** from the left menu. 1. Click on **Create Security Group** and fill in the details. Give it a name, @@ -184,44 +184,56 @@ The security group is basically the firewall. ## PostgreSQL with RDS For our database server we will use Amazon RDS which offers Multi AZ -for redundancy. Lets start by creating a subnet group and then we'll +for redundancy. Let's start by creating a subnet group and then we'll create the actual RDS instance. ### RDS Subnet Group -From the RDS dashboard select Subnet Groups. Lets select our VPC from -the VPC ID dropdown and at the bottom we can add our private subnets. +1. Navigate to the RDS dashboard and select **Subnet Groups** from the left menu. +1. Give it a name (`gitlab-rds-group`), a description, and choose the VPC from + the VPC dropdown. +1. Click on "Add all the subnets related to this VPC" and + remove the public ones, we only want the **private subnets**. + In the end, you should see `10.0.1.0/24` and `10.0.3.0/24` (as + we defined them in the [subnets section](#subnets)). + Click **Create** when ready. -![Subnet Group](img/db-subnet-group.png) + ![RDS Subnet Group](img/rds_subnet_group.png) ### Creating the database -Select the RDS service from the Database section and create a new -PostgreSQL instance. After choosing between a Production or -Development instance we'll start with the actual configuration. On the -image bellow we have the settings for this article but note the -following two options which are of particular interest for HA: +Now, it's time to create the database: + +1. Select **Instances** from the left menu and click on **Create database**. +1. Select PostgreSQL and click **Next**. +1. Since this is a production server, let's choose "Production". Click **Next**. +1. Let's see the instance specifications: + 1. Leave the license model as is (`postgresql-license`). + 1. For the version, select the latest of the 9.6 series (check the + [database requirements](../../install/requirements.md#postgresql-requirements)) + if there are any updates on this). + 1. For the size, let's select a `t2.medium` instance. + 1. Multi-AZ-deployment is recommended as redundancy, so choose "Create + replica in different zone". Read more at + [High Availability (Multi-AZ)](http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.MultiAZ.html). + 1. A Provisioned IOPS (SSD) storage type is best suited for HA (though you can + choose a General Purpose (SSD) to reduce the costs). Read more about it at + [Storage for Amazon RDS](http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_Storage.html). + +1. The rest of the settings on this page request a DB isntance identifier, username + and a master password. We've chosen to use `gitlab-ha`, `gitlab` and a + very secure password respectively. Keep these in hand for later. +1. Click on **Next** to proceed to the advanced settings. +1. Make sure to choose our gitlab VPC, our subnet group, set public accessibility to + **No**, and to leave it to create a new security group. The only additional + change which will be helpful is the database name for which we can use + `gitlabhq_production`. At the very bottom, there's an option to enable + auto updates to minor versions. You may want to turn it off. +1. When done, click **Create database**. -1. Multi-AZ-Deployment is recommended as redundancy. Read more at -[High Availability (Multi-AZ)](http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.MultiAZ.html) -1. While we chose a General Purpose (SSD) for this article a Provisioned -IOPS (SSD) is best suited for HA. Read more about it at -[Storage for Amazon RDS](http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_Storage.html) - -![RDS Instance Specs](img/instance_specs.png) - -The rest of the setting on this page request a DB identifier, username -and a master password. We've chosen to use `gitlab-ha`, `gitlab` and a -very secure password respectively. Keep these in hand for later. - -![Network and Security](img/rds-net-opt.png) - -Make sure to choose our gitlab VPC, our subnet group, not have it public, -and to leave it to create a new security group. The only additional -change which will be helpful is the database name for which we can use -`gitlabhq_production`. +--- -*** +Now that the database is created, let's move on setting up Redis with ElasticCache. ## Redis with ElastiCache -- cgit v1.2.1 From 1a3993a048c8c693c301641c4f66b871f6bfdb6b Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 25 Sep 2018 19:58:26 +0200 Subject: Add notes on ElastiCache --- doc/install/aws/img/ec_az.png | Bin 0 -> 10476 bytes doc/install/aws/img/ec_subnet.png | Bin 0 -> 23517 bytes doc/install/aws/index.md | 111 +++++++++++++++++++++++++------------- 3 files changed, 74 insertions(+), 37 deletions(-) create mode 100644 doc/install/aws/img/ec_az.png create mode 100644 doc/install/aws/img/ec_subnet.png diff --git a/doc/install/aws/img/ec_az.png b/doc/install/aws/img/ec_az.png new file mode 100644 index 00000000000..22a8291c593 Binary files /dev/null and b/doc/install/aws/img/ec_az.png differ diff --git a/doc/install/aws/img/ec_subnet.png b/doc/install/aws/img/ec_subnet.png new file mode 100644 index 00000000000..c44fb4485e3 Binary files /dev/null and b/doc/install/aws/img/ec_subnet.png differ diff --git a/doc/install/aws/index.md b/doc/install/aws/index.md index 4dd11c2f94b..5e92a8e6da0 100644 --- a/doc/install/aws/index.md +++ b/doc/install/aws/index.md @@ -49,6 +49,8 @@ Here's a list of the services we will use and their costs: - **RDS**: An Amazon Relational Database Service using PostgreSQL will be used to provide database High Availability. See the [Amazon RDS pricing](https://aws.amazon.com/rds/postgresql/pricing/). +- **ElastiCache**: An in-memory cache environment will be used to provide Redis + High Availability. See the [Amazon ElastiCache pricing](https://aws.amazon.com/elasticache/pricing/). ## Creating an IAM EC2 instance role and profile @@ -221,7 +223,7 @@ Now, it's time to create the database: [Storage for Amazon RDS](http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_Storage.html). 1. The rest of the settings on this page request a DB isntance identifier, username - and a master password. We've chosen to use `gitlab-ha`, `gitlab` and a + and a master password. We've chosen to use `gitlab-db-ha`, `gitlab` and a very secure password respectively. Keep these in hand for later. 1. Click on **Next** to proceed to the advanced settings. 1. Make sure to choose our gitlab VPC, our subnet group, set public accessibility to @@ -233,33 +235,50 @@ Now, it's time to create the database: --- + + Now that the database is created, let's move on setting up Redis with ElasticCache. ## Redis with ElastiCache -EC is an in-memory hosted caching solution. Redis maintains its own -persistence and is used for certain types of application. +ElastiCache is an in-memory hosted caching solution. Redis maintains its own +persistence and is used for certain types of the GitLab application. + +To set up Redis: -Let's choose the ElastiCache service in the Database section from our -AWS console. Now lets create a cache subnet group which will be very -similar to the RDS subnet group. Make sure to select our VPC and its -private subnets. +1. Navigate to the ElastiCache dashboard from your AWS console. +1. Go to **Subnet Groups** in the left menu, and create a new subnet group. + Make sure to select our VPC and its [private subnets](#subnets). Click + **Create** when ready. -![ElastiCache](img/ec-subnet.png) + ![ElastiCache subnet](img/ec_subnet.png) -Now press the Launch a Cache Cluster and choose Redis for our -DB engine. You'll be able to configure details such as replication, -Multi AZ and node types. The second section will allow us to choose our -subnet and security group and +1. Select **Redis** on the left menu and click on **Create** to create a new + Redis cluster. Depending on your load, you can choose whether to enable + cluster mode or not. Even without cluster mode on, you still get the + chance to deploy Redis in multi availability zones. In this guide, we chose + not to enable it. +1. In the settings section: + 1. Give the cluster a name (`gitlab-redis`) and a description. + 1. For the version, select the latest of `3.2` series (e.g., `3.2.10`). + 1. Select the node type and the number of replicas. +1. In the advanced settings section: + 1. Select the multi-AZ auto-failover option. + 1. Select the subnet group we created previously. + 1. Manually select the preferred availability zones, and under "Replica 2" + choose a different zone than the other two. -![Redis Cluster details](img/redis-cluster-det.png) + ![Redis availability zones](img/ec_az.png) -![Redis Network](img/redis-net.png) +1. In the security settings, edit the security groups and choose the + `gitlab-security-group` we had previously created. +1. Leave the rest of the settings to their default values or edit to your liking. +1. When done, click **Create**. ## Deploying GitLab We'll use AWS's wizard to deploy GitLab and then SSH into the instance to -configure the domain name. +configure the PostgreSQL and Redis connections. ### Choose the AMI @@ -283,9 +302,9 @@ instance type should be at least `c4.xlarge`. This is enough to accommodate 100 ### Configure instance -1. Configure the instance. At "Network" choose `gitlab-vpc` and the subnet we - created for that VPC. Select "Enable" for the "Auto-assign Public IP" and - choose the `GitLabAdmin` IAM role. +1. Configure the instance. At "Network" choose `gitlab-vpc` and one of the public + [subnets](#subnets) we created for that VPC. Select "Enable" for the + "Auto-assign Public IP", and choose the `GitLabAdmin` IAM role. ![Configure instance](img/configure_instance.png) @@ -333,7 +352,6 @@ namely `gitlab-ec2-security-group`, and edit select the RDS security group and edit the inbound rules. Choose the rule type to be PostgreSQL and paste the name under source. -![RDS security group](img/rds-sec-group.png) Similar to the above we'll jump to the `gitlab-ec2-security-group` group and add a custom TCP rule for port 6379 accessible within itself. @@ -399,7 +417,6 @@ scroll down to Advanced Details we can choose to receive traffic from ELBs. Lets enable that option and select our ELB. We also want to use the ELB's health check. -![Auto scaling](img/auto-scaling-det.png) ### Policies @@ -409,7 +426,6 @@ scale between 2 and 4 instances where one instance will be added if CPU utilization is greater than 60% and one instance is removed if it falls to less than 45%. Here are the complete policies: -![Policies](img/policies.png) You'll notice that after we save this AWS starts launching our two instances in different AZs and without a public IP which is exactly what @@ -422,39 +438,60 @@ Let's connect to it and configure some things before logging in. ### Configuring GitLab to connect with postgres and Redis -While connected to your server edit the `gitlab.rb` file at `/etc/gitlab/gitlab.rb` +While connected to your server, let's connect to the RDS instance to verify +access and to install a required extension: + +```sh +sudo /opt/gitlab/embedded/bin/psql -U gitlab -h -d gitlabhq_production +``` + +Edit the `gitlab.rb` file at `/etc/gitlab/gitlab.rb` find the `external_url 'http://gitlab.example.com'` option and change it to the domain you will be using or the public IP address of the current instance to test the configuration. -For a more detailed description about configuring GitLab read [Configuring GitLab for HA](http://docs.gitlab.com/ee/administration/high_availability/gitlab.html) +For a more detailed description about configuring GitLab read [Configuring GitLab for HA](../../administration/high_availability/gitlab.md) Now look for the GitLab database settings and uncomment as necessary. In our current case we'll specify the adapter, encoding, host, db name, -username, and password. +username, and password: - gitlab_rails['db_adapter'] = "postgresql" - gitlab_rails['db_encoding'] = "unicode" - gitlab_rails['db_database'] = "gitlabhq_production" - gitlab_rails['db_username'] = "gitlab" - gitlab_rails['db_password'] = "mypassword" - gitlab_rails['db_host'] = "" +```ruby +# Disable the built-in Postgres +postgresql['enable'] = false + +# Fill in the connection details +gitlab_rails['db_adapter'] = "postgresql" +gitlab_rails['db_encoding'] = "unicode" +gitlab_rails['db_database'] = "gitlabhq_production" +gitlab_rails['db_username'] = "gitlab" +gitlab_rails['db_password'] = "mypassword" +gitlab_rails['db_host'] = "" +``` Next we only need to configure the Redis section by adding the host and -uncommenting the port. +uncommenting the port: + +```ruby +# Disable the built-in Redis +redis['enable'] = false + +# Fill in the connection details +gitlab_rails['redis_host'] = "" +gitlab_rails['redis_port'] = 6379 +``` The last configuration step is to [change the default file locations ](http://docs.gitlab.com/ee/administration/high_availability/nfs.html) to make the EFS integration easier to manage. - gitlab_rails['redis_host'] = "" - gitlab_rails['redis_port'] = 6379 - Finally run reconfigure, you might find it useful to run a check and a service status to make sure everything has been setup correctly. - sudo gitlab-ctl reconfigure - sudo gitlab-rake gitlab:check - sudo gitlab-ctl status +```sh +sudo gitlab-ctl reconfigure +sudo gitlab-rake gitlab:check +sudo gitlab-ctl status +``` If everything looks good copy the Elastic IP over to your browser and test the instance manually. -- cgit v1.2.1 From 224da9f3ca961826d6611c535bf16a9e06e42e0e Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 19 Oct 2018 20:05:59 +0200 Subject: Refactor guide and streamline steps --- doc/install/aws/img/add_storage.png | Bin 27490 -> 0 bytes doc/install/aws/img/aws_diagram.png | Bin 31122 -> 502497 bytes doc/install/aws/img/choose_ami.png | Bin 28588 -> 4892 bytes doc/install/aws/img/choose_instance_type.png | Bin 52257 -> 0 bytes doc/install/aws/img/configure_instance.png | Bin 44250 -> 0 bytes doc/install/aws/img/configure_security_group.png | Bin 33101 -> 0 bytes doc/install/aws/img/policies.png | Bin 0 -> 39723 bytes doc/install/aws/img/select_ssh_key.png | Bin 18336 -> 0 bytes doc/install/aws/index.md | 313 ++++++++++------------- 9 files changed, 140 insertions(+), 173 deletions(-) delete mode 100644 doc/install/aws/img/add_storage.png delete mode 100644 doc/install/aws/img/choose_instance_type.png delete mode 100644 doc/install/aws/img/configure_instance.png delete mode 100644 doc/install/aws/img/configure_security_group.png create mode 100644 doc/install/aws/img/policies.png delete mode 100644 doc/install/aws/img/select_ssh_key.png diff --git a/doc/install/aws/img/add_storage.png b/doc/install/aws/img/add_storage.png deleted file mode 100644 index 6fb399c3cc5..00000000000 Binary files a/doc/install/aws/img/add_storage.png and /dev/null differ diff --git a/doc/install/aws/img/aws_diagram.png b/doc/install/aws/img/aws_diagram.png index c1ed0f41370..bcd5c69bbeb 100644 Binary files a/doc/install/aws/img/aws_diagram.png and b/doc/install/aws/img/aws_diagram.png differ diff --git a/doc/install/aws/img/choose_ami.png b/doc/install/aws/img/choose_ami.png index b6dfa49e4bf..034ac92691d 100644 Binary files a/doc/install/aws/img/choose_ami.png and b/doc/install/aws/img/choose_ami.png differ diff --git a/doc/install/aws/img/choose_instance_type.png b/doc/install/aws/img/choose_instance_type.png deleted file mode 100644 index 06c288f3f0c..00000000000 Binary files a/doc/install/aws/img/choose_instance_type.png and /dev/null differ diff --git a/doc/install/aws/img/configure_instance.png b/doc/install/aws/img/configure_instance.png deleted file mode 100644 index f7c5c1cc975..00000000000 Binary files a/doc/install/aws/img/configure_instance.png and /dev/null differ diff --git a/doc/install/aws/img/configure_security_group.png b/doc/install/aws/img/configure_security_group.png deleted file mode 100644 index ea4b43b2c8f..00000000000 Binary files a/doc/install/aws/img/configure_security_group.png and /dev/null differ diff --git a/doc/install/aws/img/policies.png b/doc/install/aws/img/policies.png new file mode 100644 index 00000000000..e99497a52a2 Binary files /dev/null and b/doc/install/aws/img/policies.png differ diff --git a/doc/install/aws/img/select_ssh_key.png b/doc/install/aws/img/select_ssh_key.png deleted file mode 100644 index b2adcd631bc..00000000000 Binary files a/doc/install/aws/img/select_ssh_key.png and /dev/null differ diff --git a/doc/install/aws/index.md b/doc/install/aws/index.md index 5e92a8e6da0..af51a95b47f 100644 --- a/doc/install/aws/index.md +++ b/doc/install/aws/index.md @@ -29,7 +29,7 @@ A basic familiarity with AWS and EC2 is assumed. In particular, you will need: Below is the diagram of the architecture. -![AWS architecture](img/aws_diagram.png) +AWS architecture diagram ## Costs @@ -177,7 +177,7 @@ The security group is basically the firewall: ![Create security group](img/create_security_group.png) TIP: **Tip:** - Based on best practices, you should only allow SSH traffic from only a known + Based on best practices, you should allow SSH traffic from only a known host or CIDR block. In that case, change the SSH source to be custom and give it the IP you want to SSH from. @@ -233,9 +233,30 @@ Now, it's time to create the database: auto updates to minor versions. You may want to turn it off. 1. When done, click **Create database**. ---- +### Installing the `pg_trgm` extension for PostgreSQL + +Once the database is created, connect to your new RDS instance to verify access +and to install a required extension. + +You can find the host or endpoint by selecting the instance you just created and +after the details drop down you'll find it labeled as 'Endpoint'. Do not to +include the colon and port number: + +```sh +sudo /opt/gitlab/embedded/bin/psql -U gitlab -h -d gitlabhq_production +``` + +At the psql prompt create the extension and then quit the session: + +```sh +psql (9.4.7) +Type "help" for help. +gitlab=# CREATE EXTENSION pg_trgm; +gitlab=# \q +``` +--- Now that the database is created, let's move on setting up Redis with ElasticCache. @@ -275,15 +296,58 @@ To set up Redis: 1. Leave the rest of the settings to their default values or edit to your liking. 1. When done, click **Create**. -## Deploying GitLab +## RDS and Redis Security Group + +Let's navigate to our EC2 security groups and add a small change for our EC2 +instances to be able to connect to RDS. First, copy the security group name we +defined, namely `gitlab-security-group`, select the RDS security group and edit the +inbound rules. Choose the rule type to be PostgreSQL and paste the name under +source. + +Similar to the above, jump to the `gitlab-security-group` group +and add a custom TCP rule for port `6379` accessible within itself. + +## Load Balancer + +On the EC2 dashboard, look for Load Balancer on the left column: + +1. Click the **Create Load Balancer** button. + 1. Choose the Application Load Balancer. + 1. Give it a name (`gitlab-loadbalancer`) and set the scheme to "internet-facing". + 1. In the "Listeners" section, make sure it has HTTP and HTTPS. + 1. In the "Availability Zones" section, select the `gitlab-vpc` we have created + and associate the **public subnets**. +1. Click on the **Configure Security Settings** to go to the next section to + select the TLS certificate. When done, go to the next step. +1. In the "Security Groups" section, create a new one by giving it a name + (`gitlab-loadbalancer-sec-group`) and allow both HTTP ad HTTPS traffic + from anywhere (`0.0.0.0/0, ::/0`). +1. In the next step, configure the routing and select an existing target group + (`gitlab-public`). The Load Balancer Health will allow us to indicate where to + ping and what makes up a healthy or unhealthy instance. +1. Leave the "Register Targets" section as is, and finally review the settings + and create the ELB. + +After the Load Balancer is up and running, you can re-visit your Security +Groups to improve access only through the ELB and any other requirement +you might have. + +## Deploying GitLab inside an auto scaling group We'll use AWS's wizard to deploy GitLab and then SSH into the instance to configure the PostgreSQL and Redis connections. +The Auto Scaling Group option is available through the EC2 dashboard on the left +sidebar. + +1. Click on the **Create Auto Scaling group** button. +1. Create a new launch configuration. + ### Choose the AMI -1. On the EC2 dashboard click **Launch Instance**. -1. Choose the AMI by going to the Community AMIs and search for `GitLab EE ` +Choose the AMI: + +1. Go to the Community AMIs and search for `GitLab EE ` where `` the latest version as seen in the [releases page](https://about.gitlab.com/releases/). @@ -295,145 +359,68 @@ Based on [GitLab's requirements](../requirements.md#hardware-requirements), the instance type should be at least `c4.xlarge`. This is enough to accommodate 100 users: 1. Choose the `c4.xlarge` instance. - - ![Choose instance type](img/choose_instance_type.png) - 1. Click **Next: Configure Instance Details** -### Configure instance - -1. Configure the instance. At "Network" choose `gitlab-vpc` and one of the public - [subnets](#subnets) we created for that VPC. Select "Enable" for the - "Auto-assign Public IP", and choose the `GitLabAdmin` IAM role. +### Configure details - ![Configure instance](img/configure_instance.png) +In this step we'll configure some details: +1. Give it a name (`gitlab-autoscaling`). +1. Select the IAM role we created. +1. Optionally, enable CloudWatch and the EBS-optimized instance settings. +1. In the "Advanced Details" section, set the IP address type to + "Do not assign a public IP address to any instances." 1. Click **Next: Add Storage**. ### Add storage -Edit the root volume to 20GB, and add a new EBS volume that will host the Git data. -Its size depends on your needs and you can always migrate to a bigger volume later. - -![Add storage](img/add_storage.png) - -### Add tags - -To help you manage your instances, you can optionally assign your own metadata -to each resource in the [form of tags](https://docs.aws.amazon.com/console/ec2/tags). - -Let's add one with its key set to `Name` and value to `GitLab`. - -![Add tags](img/add_tags.png) +The root volume is 8GB by default and should be enough given that we won't store +any data there. Let's add a new EBS volume that will host the Git data. Its +size depends on your needs and you can always migrate to a bigger volume. +You will be able to [set up that volume later](#setting-up-the-ebs-volume). ### Configure security group -1. Select the existing security group we [have created](#creating-a-security-group). - - ![Add security group](img/configure_security_group.png) +As a last step, configure the security group: -1. Select **Review and Launch**. +1. Select the existing load balancer security group we [have created](#load-balancer). +1. Select **Review**. ### Review and launch -Now is a good time to review all the previous settings. Click **Launch** and -select the SSH key pair you have created previously. - -![Select SSH key](img/select_ssh_key.png) - -Finally, click on **Launch instances**. - -### RDS and Redis Security Group - -After the instance is being created we will navigate to our EC2 security -groups and add a small change for our EC2 instances to be able to -connect to RDS. First copy the security group name we just defined, -namely `gitlab-ec2-security-group`, and edit select the RDS security -group and edit the inbound rules. Choose the rule type to be PostgreSQL -and paste the name under source. - - -Similar to the above we'll jump to the `gitlab-ec2-security-group` group -and add a custom TCP rule for port 6379 accessible within itself. - -## Load Balancer - -On the same dashboard look for Load Balancer on the left column and press -the Create button. Choose a classic Load Balancer, our gitlab VPC, not -internal and make sure its listening for HTTP and HTTPS on port 80. - -Here is a tricky part though, when adding subnets we need to associate -public subnets instead of the private ones where our instances will -actually live. - -On the security group section let's create a new one named -`gitlab-loadbalancer-sec-group` and allow both HTTP ad HTTPS traffic -from anywhere. - -The Load Balancer Health will allow us to indicate where to ping and what -makes up a healthy or unhealthy instance. - -We won't add the instance on the next session because we'll destroy it -momentarily as we'll be using the image we where creating. We will keep -the Enable Cross-Zone and Enable Connection Draining active. - -After we finish creating the Load Balancer we can re visit our Security -Groups to improve access only through the ELB and any other requirement -you might have. - -## Auto Scaling Group - -Our AMI should be done by now so we can start working on our Auto -Scaling Group. - -This option is also available through the EC2 dashboard on the left -sidebar. Press on the create button. Select the new image on My AMIs and -give it a `t2.medium` size. To be able to use Elastic File System we need -to add a script to mount EFS automatically at launch. We'll do this at -the Advanced Details section where we have a [User Data](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/user-data.html) -text area that allows us to add a lot of custom configurations which -allows you to add a custom script for when launching an instance. Let's -add the following script to the User Data section: - +Now is a good time to review all the previous settings. When ready, click +**Create launch configuration** and select the SSH key pair you have created previously. - #cloud-config - package_upgrade: true - packages: - - nfs-common - runcmd: - - mkdir -p /gitlab-data - - chown ec2-user:ec2-user /gitlab-data - - echo "$(curl --silent http://169.254.169.254/latest/meta-data/placement/availability-zone).file-system-id.aws-region.amazonaws.com:/ /gitlab-data nfs defaults,vers=4.1 0 0" >> /etc/fstab - - mount -a -t nfs - - sudo gitlab-ctl reconfigure +### Create Auto Scaling Group -On the security group section we can choose our existing -`gitlab-ec2-security-group` group which has already been tested. +We are now able to start creating our Auto Scaling Group: -After this is launched we are able to start creating our Auto Scaling -Group. Start by giving it a name and assigning it our VPC and private -subnets. We also want to always start with two instances and if you -scroll down to Advanced Details we can choose to receive traffic from ELBs. -Lets enable that option and select our ELB. We also want to use the ELB's -health check. - - -### Policies +1. Give it a group name. +1. Set the group size to 2 as we want to always start with two instances. +1. Assign it our network VPC and add the **private subnets**. +1. In the "Advanced Details" section, choose to receive traffic from ELBs + and select our ELB. +1. Choose the ELB health check. +1. Click **Next: Configure scaling policies**. This is the really great part of Auto Scaling, we get to choose when AWS launches new instances and when it removes them. For this group we'll scale between 2 and 4 instances where one instance will be added if CPU utilization is greater than 60% and one instance is removed if it falls -to less than 45%. Here are the complete policies: +to less than 45%. + +![Auto scaling group policies](img/policies.png) +Finally, configure notifications and tags as you see fit, and create the +auto scaling group. -You'll notice that after we save this AWS starts launching our two +You'll notice that after we save the configuration, AWS starts launching our two instances in different AZs and without a public IP which is exactly what we where aiming for. ## After deployment -After a few minutes, the instance should be up and accessible via the internet. +After a few minutes, the instances should be up and accessible via the internet. Let's connect to it and configure some things before logging in. ### Configuring GitLab to connect with postgres and Redis @@ -498,59 +485,17 @@ test the instance manually. ### Setting up the EBS volume -The EBS volume will host the Git data. We need to first format the `/dev/xvdb` -volume and then mount it: - -1. First, create the directory that the volume will be mounted to: - - ```sh - sudo mkdir /gitlab-data - ``` - -1. Create a partition with a GUID Partition Table (GPT), mark it as - primary, choose the `ext4` file system, and use all its size: - - ```sh - sudo parted --script /dev/xvdb mklabel gpt mkpart primary ext4 0% 100% - ``` - -1. Format to `ext4`: - - ```sh - sudo mkfs.ext4 -L Data /dev/xvdb1 - ``` - -1. Find its PARTUUID: - - ```sh - blkid /dev/xvdb1 - ``` - - You need to copy the PARTUUID number (without the quotes) and use this to - mount the newly created partition. - -1. Open `/etc/fstab` with your editor, comment out the entry about `/dev/xvdb`, - and add the new partition: - - ``` - PARTUUID=d4129b25-a3c9-4d2c-a090-2c234fee4d46 /gitlab-data ext4 defaults,nofail,x-systemd.requires=cloud-init.service,comment=cloudconfig 0 2 - ``` - -1. Mount the partition: - - ```sh - sudo mount -a - ``` +The EBS volume will host the Git repositories data: ---- - -Now that the partition is created and mounted, it's time to tell GitLab to store -its data to the new `/gitlab-data` directory: - -1. Edit `/etc/gitlab/gitlab.rb` with your editor and add the following: +1. First, format the `/dev/xvdb` volume and then mount it under the directory + that the data will live, for example `/mnt/gitlab-data/`. +1. Tell GitLab to store its data to the new directory by editing + `/etc/gitlab/gitlab.rb` with your editor: ```ruby - git_data_dirs({ "default" => { "path" => "/gitlab-data" } }) + git_data_dirs({ + "default" => { "path" => "/mnt/gitlab-data" } + }) ``` 1. Save the file and reconfigure GitLab: @@ -559,9 +504,11 @@ its data to the new `/gitlab-data` directory: sudo gitlab-ctl reconfigure ``` -Read more on [storing Git data in an alternative directory](https://docs.gitlab.com/omnibus/settings/configuration.html#storing-git-data-in-an-alternative-directory). +To add more than one data volumes, follow the same steps. + +Read more on [storing Git data in an alternative directory](../../administration/repository_storage_paths.md). -### Using S3 for the LFS objects, artifacts and Registry images +### Using Amazon S3 object storage The S3 object storage can be used for various GitLab objects: @@ -597,18 +544,28 @@ for the `root` user which has admin privileges on the GitLab instance. After you set it up, login with username `root` and the newly created password. +## Health check and monitoring with Prometheus + +Apart from Amazon's Cloudwatch which you can enable on various services, +GitLab provides its own integrated monitoring solution based on Prometheus. +For more information on how to set it up, visit the +[GitLab Prometheus documentation](../../administration/monitoring/prometheus/index.md) + +GitLab also has various [health check endpoints](../..//user/admin_area/monitoring/health_check.md) +that you can ping and get reports. + ## Backup and restore GitLab provides [a tool to backup](../../raketasks/backup_restore.md#creating-a-backup-of-the-gitlab-system) and restore its Git data, database, attachments, LFS objects, etc. -Some things to know: +Some important things to know: +- The backup/restore tool does not store some configuration files, like secrets, you'll + need to [do it yourself](../../raketasks/backup_restore.md#storing-configuration-files). - By default, the backup files are stored locally, but you can [backup GitLab using S3](../../raketasks/backup_restore.md#using-amazon-s3). - You can exclude [specific directories form the backup](../../raketasks/backup_restore.md#excluding-specific-directories-from-the-backup). -- The backup/restore tool does not store some configuration files, like secrets, you'll - need to [do it yourself](../../raketasks/backup_restore.md#storing-configuration-files). ### Backing up GitLab @@ -648,9 +605,19 @@ released, you can update your GitLab instance: After a few minutes, the new version should be up and running. -## Resources +## Conclusion + +High Availability is a very big area, we went mostly through scaling and some +redundancy options but it might also imply Geographic replication. There is a +lot of ground yet to cover so have a read through these other resources and feel +free to open an issue to request additional material: +- [GitLab High Availability](https://docs.gitlab.com/ee/administration/high_availability/): + GitLab supports several different types of clustering and high-availability. +- [Geo replication](https://docs.gitlab.com/ee/administration/geo/replication/): + Geo is the solution for widely distributed development teams. - [Omnibus GitLab](https://docs.gitlab.com/omnibus/) - Everything you need to know about administering your GitLab instance. -- [Upload a license](https://docs.gitlab.com/ee/user/admin_area/license.html) - Activate all GitLab - Enterprise Edition functionality with a license. +- [Upload a license](https://docs.gitlab.com/ee/user/admin_area/license.html): + Activate all GitLab Enterprise Edition functionality with a license. +- [Pricing](https://about.gitlab.com/pricing): Pricing for the different tiers. -- cgit v1.2.1 From 754a71c73d59b77f3f559b0ee151453a8ee87e9c Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 19 Oct 2018 20:15:53 +0200 Subject: Add GitLab Runners section --- doc/install/aws/index.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/doc/install/aws/index.md b/doc/install/aws/index.md index af51a95b47f..303c2d95f2f 100644 --- a/doc/install/aws/index.md +++ b/doc/install/aws/index.md @@ -554,6 +554,14 @@ For more information on how to set it up, visit the GitLab also has various [health check endpoints](../..//user/admin_area/monitoring/health_check.md) that you can ping and get reports. +## GitLab Runners + +If you want to take advantage of GitLab CI/CD, you have to set up at least one +GitLab Runner. + +Read more on configuring an +[autoscaling GitLab Runner on AWS](https://docs.gitlab.com/runner/configuration/runner_autoscale_aws/). + ## Backup and restore GitLab provides [a tool to backup](../../raketasks/backup_restore.md#creating-a-backup-of-the-gitlab-system) -- cgit v1.2.1 From 4d0fd75cd5ceda72692a229d27ab6891fa8082e0 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 2 Nov 2018 17:32:28 +0800 Subject: Rename QA::Factory to QA::Resource * Factory::Base -> Resource::Base, and therefore: * Factory::Resource::Project -> Resource::Project --- qa/qa.rb | 60 ++- qa/qa/factory/README.md | 410 --------------------- qa/qa/factory/api_fabricator.rb | 101 ----- qa/qa/factory/base.rb | 154 -------- qa/qa/factory/repository/project_push.rb | 42 --- qa/qa/factory/repository/push.rb | 91 ----- qa/qa/factory/repository/wiki_push.rb | 34 -- qa/qa/factory/resource/branch.rb | 77 ---- qa/qa/factory/resource/ci_variable.rb | 30 -- qa/qa/factory/resource/deploy_key.rb | 43 --- qa/qa/factory/resource/deploy_token.rb | 50 --- qa/qa/factory/resource/file.rb | 38 -- qa/qa/factory/resource/fork.rb | 43 --- qa/qa/factory/resource/group.rb | 68 ---- qa/qa/factory/resource/issue.rb | 30 -- qa/qa/factory/resource/kubernetes_cluster.rb | 57 --- qa/qa/factory/resource/label.rb | 39 -- qa/qa/factory/resource/merge_request.rb | 71 ---- qa/qa/factory/resource/merge_request_from_fork.rb | 31 -- qa/qa/factory/resource/personal_access_token.rb | 27 -- qa/qa/factory/resource/project.rb | 78 ---- .../resource/project_imported_from_github.rb | 36 -- qa/qa/factory/resource/project_milestone.rb | 36 -- qa/qa/factory/resource/runner.rb | 49 --- qa/qa/factory/resource/sandbox.rb | 60 --- qa/qa/factory/resource/ssh_key.rb | 28 -- qa/qa/factory/resource/user.rb | 92 ----- qa/qa/factory/resource/wiki.rb | 30 -- qa/qa/factory/settings/hashed_storage.rb | 24 -- qa/qa/page/main/login.rb | 2 +- qa/qa/resource/README.md | 392 ++++++++++++++++++++ qa/qa/resource/api_fabricator.rb | 101 +++++ qa/qa/resource/base.rb | 154 ++++++++ qa/qa/resource/branch.rb | 77 ++++ qa/qa/resource/ci_variable.rb | 30 ++ qa/qa/resource/deploy_key.rb | 43 +++ qa/qa/resource/deploy_token.rb | 50 +++ qa/qa/resource/file.rb | 36 ++ qa/qa/resource/fork.rb | 43 +++ qa/qa/resource/group.rb | 68 ++++ qa/qa/resource/issue.rb | 30 ++ qa/qa/resource/kubernetes_cluster.rb | 57 +++ qa/qa/resource/label.rb | 39 ++ qa/qa/resource/merge_request.rb | 71 ++++ qa/qa/resource/merge_request_from_fork.rb | 31 ++ qa/qa/resource/personal_access_token.rb | 27 ++ qa/qa/resource/project.rb | 80 ++++ qa/qa/resource/project_imported_from_github.rb | 36 ++ qa/qa/resource/project_milestone.rb | 36 ++ qa/qa/resource/repository/project_push.rb | 44 +++ qa/qa/resource/repository/push.rb | 93 +++++ qa/qa/resource/repository/wiki_push.rb | 36 ++ qa/qa/resource/runner.rb | 49 +++ qa/qa/resource/sandbox.rb | 60 +++ qa/qa/resource/settings/hashed_storage.rb | 26 ++ qa/qa/resource/ssh_key.rb | 26 ++ qa/qa/resource/user.rb | 92 +++++ qa/qa/resource/wiki.rb | 30 ++ qa/qa/runtime/api/client.rb | 2 +- .../browser_ui/1_manage/login/register_spec.rb | 2 +- .../1_manage/project/add_project_member_spec.rb | 4 +- .../1_manage/project/create_project_spec.rb | 2 +- .../1_manage/project/import_github_repo_spec.rb | 2 +- .../1_manage/project/view_project_activity_spec.rb | 2 +- .../browser_ui/2_plan/issue/create_issue_spec.rb | 2 +- .../2_plan/issue/filter_issue_comments_spec.rb | 2 +- .../merge_request/create_merge_request_spec.rb | 12 +- .../merge_merge_request_from_fork_spec.rb | 2 +- .../merge_request/rebase_merge_request_spec.rb | 6 +- .../merge_request/squash_merge_request_spec.rb | 6 +- .../3_create/repository/add_file_template_spec.rb | 2 +- .../3_create/repository/add_ssh_key_spec.rb | 2 +- .../browser_ui/3_create/repository/clone_spec.rb | 2 +- .../create_edit_delete_file_via_web_spec.rb | 2 +- .../repository/push_http_private_token_spec.rb | 6 +- .../3_create/repository/push_over_http_spec.rb | 2 +- .../repository/push_protected_branch_spec.rb | 6 +- .../3_create/repository/use_ssh_key_spec.rb | 4 +- .../3_create/web_ide/add_file_template_spec.rb | 2 +- .../wiki/create_edit_clone_push_wiki_spec.rb | 4 +- .../4_verify/ci_variable/add_ci_variable_spec.rb | 2 +- .../pipeline/create_and_process_pipeline_spec.rb | 6 +- .../4_verify/runner/register_runner_spec.rb | 2 +- .../6_release/deploy_key/add_deploy_key_spec.rb | 2 +- .../deploy_key/clone_using_deploy_key_spec.rb | 10 +- .../deploy_token/add_deploy_token_spec.rb | 2 +- .../create_project_with_auto_devops_spec.rb | 8 +- qa/spec/factory/api_fabricator_spec.rb | 161 -------- qa/spec/factory/base_spec.rb | 246 ------------- qa/spec/factory/repository/push_spec.rb | 26 -- qa/spec/factory/resource/user_spec.rb | 2 +- qa/spec/resource/api_fabricator_spec.rb | 161 ++++++++ qa/spec/resource/base_spec.rb | 246 +++++++++++++ qa/spec/resource/repository/push_spec.rb | 26 ++ 94 files changed, 2375 insertions(+), 2389 deletions(-) delete mode 100644 qa/qa/factory/README.md delete mode 100644 qa/qa/factory/api_fabricator.rb delete mode 100644 qa/qa/factory/base.rb delete mode 100644 qa/qa/factory/repository/project_push.rb delete mode 100644 qa/qa/factory/repository/push.rb delete mode 100644 qa/qa/factory/repository/wiki_push.rb delete mode 100644 qa/qa/factory/resource/branch.rb delete mode 100644 qa/qa/factory/resource/ci_variable.rb delete mode 100644 qa/qa/factory/resource/deploy_key.rb delete mode 100644 qa/qa/factory/resource/deploy_token.rb delete mode 100644 qa/qa/factory/resource/file.rb delete mode 100644 qa/qa/factory/resource/fork.rb delete mode 100644 qa/qa/factory/resource/group.rb delete mode 100644 qa/qa/factory/resource/issue.rb delete mode 100644 qa/qa/factory/resource/kubernetes_cluster.rb delete mode 100644 qa/qa/factory/resource/label.rb delete mode 100644 qa/qa/factory/resource/merge_request.rb delete mode 100644 qa/qa/factory/resource/merge_request_from_fork.rb delete mode 100644 qa/qa/factory/resource/personal_access_token.rb delete mode 100644 qa/qa/factory/resource/project.rb delete mode 100644 qa/qa/factory/resource/project_imported_from_github.rb delete mode 100644 qa/qa/factory/resource/project_milestone.rb delete mode 100644 qa/qa/factory/resource/runner.rb delete mode 100644 qa/qa/factory/resource/sandbox.rb delete mode 100644 qa/qa/factory/resource/ssh_key.rb delete mode 100644 qa/qa/factory/resource/user.rb delete mode 100644 qa/qa/factory/resource/wiki.rb delete mode 100644 qa/qa/factory/settings/hashed_storage.rb create mode 100644 qa/qa/resource/README.md create mode 100644 qa/qa/resource/api_fabricator.rb create mode 100644 qa/qa/resource/base.rb create mode 100644 qa/qa/resource/branch.rb create mode 100644 qa/qa/resource/ci_variable.rb create mode 100644 qa/qa/resource/deploy_key.rb create mode 100644 qa/qa/resource/deploy_token.rb create mode 100644 qa/qa/resource/file.rb create mode 100644 qa/qa/resource/fork.rb create mode 100644 qa/qa/resource/group.rb create mode 100644 qa/qa/resource/issue.rb create mode 100644 qa/qa/resource/kubernetes_cluster.rb create mode 100644 qa/qa/resource/label.rb create mode 100644 qa/qa/resource/merge_request.rb create mode 100644 qa/qa/resource/merge_request_from_fork.rb create mode 100644 qa/qa/resource/personal_access_token.rb create mode 100644 qa/qa/resource/project.rb create mode 100644 qa/qa/resource/project_imported_from_github.rb create mode 100644 qa/qa/resource/project_milestone.rb create mode 100644 qa/qa/resource/repository/project_push.rb create mode 100644 qa/qa/resource/repository/push.rb create mode 100644 qa/qa/resource/repository/wiki_push.rb create mode 100644 qa/qa/resource/runner.rb create mode 100644 qa/qa/resource/sandbox.rb create mode 100644 qa/qa/resource/settings/hashed_storage.rb create mode 100644 qa/qa/resource/ssh_key.rb create mode 100644 qa/qa/resource/user.rb create mode 100644 qa/qa/resource/wiki.rb delete mode 100644 qa/spec/factory/api_fabricator_spec.rb delete mode 100644 qa/spec/factory/base_spec.rb delete mode 100644 qa/spec/factory/repository/push_spec.rb create mode 100644 qa/spec/resource/api_fabricator_spec.rb create mode 100644 qa/spec/resource/base_spec.rb create mode 100644 qa/spec/resource/repository/push_spec.rb diff --git a/qa/qa.rb b/qa/qa.rb index f00331dfe93..c0d5244dbfa 100644 --- a/qa/qa.rb +++ b/qa/qa.rb @@ -36,42 +36,40 @@ module QA ## # GitLab QA fabrication mechanisms # - module Factory - autoload :ApiFabricator, 'qa/factory/api_fabricator' - autoload :Base, 'qa/factory/base' - - module Resource - autoload :Sandbox, 'qa/factory/resource/sandbox' - autoload :Group, 'qa/factory/resource/group' - autoload :Issue, 'qa/factory/resource/issue' - autoload :Project, 'qa/factory/resource/project' - autoload :Label, 'qa/factory/resource/label' - autoload :MergeRequest, 'qa/factory/resource/merge_request' - autoload :ProjectImportedFromGithub, 'qa/factory/resource/project_imported_from_github' - autoload :MergeRequestFromFork, 'qa/factory/resource/merge_request_from_fork' - autoload :DeployKey, 'qa/factory/resource/deploy_key' - autoload :DeployToken, 'qa/factory/resource/deploy_token' - autoload :Branch, 'qa/factory/resource/branch' - autoload :CiVariable, 'qa/factory/resource/ci_variable' - autoload :Runner, 'qa/factory/resource/runner' - autoload :PersonalAccessToken, 'qa/factory/resource/personal_access_token' - autoload :KubernetesCluster, 'qa/factory/resource/kubernetes_cluster' - autoload :User, 'qa/factory/resource/user' - autoload :ProjectMilestone, 'qa/factory/resource/project_milestone' - autoload :Wiki, 'qa/factory/resource/wiki' - autoload :File, 'qa/factory/resource/file' - autoload :Fork, 'qa/factory/resource/fork' - autoload :SSHKey, 'qa/factory/resource/ssh_key' - end + module Resource + autoload :ApiFabricator, 'qa/resource/api_fabricator' + autoload :Base, 'qa/resource/base' + + autoload :Sandbox, 'qa/resource/sandbox' + autoload :Group, 'qa/resource/group' + autoload :Issue, 'qa/resource/issue' + autoload :Project, 'qa/resource/project' + autoload :Label, 'qa/resource/label' + autoload :MergeRequest, 'qa/resource/merge_request' + autoload :ProjectImportedFromGithub, 'qa/resource/project_imported_from_github' + autoload :MergeRequestFromFork, 'qa/resource/merge_request_from_fork' + autoload :DeployKey, 'qa/resource/deploy_key' + autoload :DeployToken, 'qa/resource/deploy_token' + autoload :Branch, 'qa/resource/branch' + autoload :CiVariable, 'qa/resource/ci_variable' + autoload :Runner, 'qa/resource/runner' + autoload :PersonalAccessToken, 'qa/resource/personal_access_token' + autoload :KubernetesCluster, 'qa/resource/kubernetes_cluster' + autoload :User, 'qa/resource/user' + autoload :ProjectMilestone, 'qa/resource/project_milestone' + autoload :Wiki, 'qa/resource/wiki' + autoload :File, 'qa/resource/file' + autoload :Fork, 'qa/resource/fork' + autoload :SSHKey, 'qa/resource/ssh_key' module Repository - autoload :Push, 'qa/factory/repository/push' - autoload :ProjectPush, 'qa/factory/repository/project_push' - autoload :WikiPush, 'qa/factory/repository/wiki_push' + autoload :Push, 'qa/resource/repository/push' + autoload :ProjectPush, 'qa/resource/repository/project_push' + autoload :WikiPush, 'qa/resource/repository/wiki_push' end module Settings - autoload :HashedStorage, 'qa/factory/settings/hashed_storage' + autoload :HashedStorage, 'qa/resource/settings/hashed_storage' end end diff --git a/qa/qa/factory/README.md b/qa/qa/factory/README.md deleted file mode 100644 index 42077f60611..00000000000 --- a/qa/qa/factory/README.md +++ /dev/null @@ -1,410 +0,0 @@ -# Factory objects in GitLab QA - -In GitLab QA we are using factories to create resources. - -Factories implementation are primarily done using Browser UI steps, but can also -be done via the API. - -## Why do we need that? - -We need factory objects because we need to reduce duplication when creating -resources for our QA tests. - -## How to properly implement a factory object? - -All factories should inherit from [`Factory::Base`](./base.rb). - -There is only one mandatory method to implement to define a factory. This is the -`#fabricate!` method, which is used to build a resource via the browser UI. -Note that you should only use [Page objects](../page/README.md) to interact with -a Web page in this method. - -Here is an imaginary example: - -```ruby -module QA - module Factory - module Resource - class Shirt < Factory::Base - attr_accessor :name - - def fabricate! - Page::Dashboard::Index.perform do |dashboard_index| - dashboard_index.go_to_new_shirt - end - - Page::Shirt::New.perform do |shirt_new| - shirt_new.set_name(name) - shirt_new.create_shirt! - end - end - end - end - end -end -``` - -### Define API implementation - -A factory may also implement the three following methods to be able to create a -resource via the public GitLab API: - -- `#api_get_path`: The `GET` path to fetch an existing resource. -- `#api_post_path`: The `POST` path to create a new resource. -- `#api_post_body`: The `POST` body (as a Ruby hash) to create a new resource. - -Let's take the `Shirt` factory example, and add these three API methods: - -```ruby -module QA - module Factory - module Resource - class Shirt < Factory::Base - attr_accessor :name - - def fabricate! - # ... same as before - end - - def api_get_path - "/shirt/#{name}" - end - - def api_post_path - "/shirts" - end - - def api_post_body - { - name: name - } - end - end - end - end -end -``` - -The [`Project` factory](./resource/project.rb) is a good real example of Browser -UI and API implementations. - -#### Resource attributes - -A resource may need another resource to exist first. For instance, a project -needs a group to be created in. - -To define a resource attribute, you can use the `attribute` method with a -block using the other factory to fabricate the resource. - -That will allow access to the other resource from your resource object's -methods. You would usually use it in `#fabricate!`, `#api_get_path`, -`#api_post_path`, `#api_post_body`. - -Let's take the `Shirt` factory, and add a `project` attribute to it: - -```ruby -module QA - module Factory - module Resource - class Shirt < Factory::Base - attr_accessor :name - - attribute :project do - Factory::Resource::Project.fabricate! do |resource| - resource.name = 'project-to-create-a-shirt' - end - end - - def fabricate! - project.visit! - - Page::Project::Show.perform do |project_show| - project_show.go_to_new_shirt - end - - Page::Shirt::New.perform do |shirt_new| - shirt_new.set_name(name) - shirt_new.create_shirt! - end - end - - def api_get_path - "/project/#{project.path}/shirt/#{name}" - end - - def api_post_path - "/project/#{project.path}/shirts" - end - - def api_post_body - { - name: name - } - end - end - end - end -end -``` - -**Note that all the attributes are lazily constructed. This means if you want -a specific attribute to be fabricated first, you'll need to call the -attribute method first even if you're not using it.** - -#### Product data attributes - -Once created, you may want to populate a resource with attributes that can be -found in the Web page, or in the API response. -For instance, once you create a project, you may want to store its repository -SSH URL as an attribute. - -Again we could use the `attribute` method with a block, using a page object -to retrieve the data on the page. - -Let's take the `Shirt` factory, and define a `:brand` attribute: - -```ruby -module QA - module Factory - module Resource - class Shirt < Factory::Base - attr_accessor :name - - attribute :project do - Factory::Resource::Project.fabricate! do |resource| - resource.name = 'project-to-create-a-shirt' - end - end - - # Attribute populated from the Browser UI (using the block) - attribute :brand do - Page::Shirt::Show.perform do |shirt_show| - shirt_show.fetch_brand_from_page - end - end - - # ... same as before - end - end - end -end -``` - -**Note again that all the attributes are lazily constructed. This means if -you call `shirt.brand` after moving to the other page, it'll not properly -retrieve the data because we're no longer on the expected page.** - -Consider this: - -```ruby -shirt = - QA::Factory::Resource::Shirt.fabricate! do |resource| - resource.name = "GitLab QA" - end - -shirt.project.visit! - -shirt.brand # => FAIL! -``` - -The above example will fail because now we're on the project page, trying to -construct the brand data from the shirt page, however we moved to the project -page already. There are two ways to solve this, one is that we could try to -retrieve the brand before visiting the project again: - -```ruby -shirt = - QA::Factory::Resource::Shirt.fabricate! do |resource| - resource.name = "GitLab QA" - end - -shirt.brand # => OK! - -shirt.project.visit! - -shirt.brand # => OK! -``` - -The attribute will be stored in the instance therefore all the following calls -will be fine, using the data previously constructed. If we think that this -might be too brittle, we could eagerly construct the data right before -ending fabrication: - -```ruby -module QA - module Factory - module Resource - class Shirt < Factory::Base - # ... same as before - - def fabricate! - project.visit! - - Page::Project::Show.perform do |project_show| - project_show.go_to_new_shirt - end - - Page::Shirt::New.perform do |shirt_new| - shirt_new.set_name(name) - shirt_new.create_shirt! - end - - populate(:brand) # Eagerly construct the data - end - end - end - end -end -``` - -The `populate` method will iterate through its arguments and call each -attribute respectively. Here `populate(:brand)` has the same effect as -just `brand`. Using the populate method makes the intention clearer. - -With this, it will make sure we construct the data right after we create the -shirt. The drawback is that this will always construct the data when the resource is fabricated even if we don't need to use the data. - -Alternatively, we could just make sure we're on the right page before -constructing the brand data: - -```ruby -module QA - module Factory - module Resource - class Shirt < Factory::Base - attr_accessor :name - - attribute :project do - Factory::Resource::Project.fabricate! do |resource| - resource.name = 'project-to-create-a-shirt' - end - end - - # Attribute populated from the Browser UI (using the block) - attribute :brand do - back_url = current_url - visit! - - Page::Shirt::Show.perform do |shirt_show| - shirt_show.fetch_brand_from_page - end - - visit(back_url) - end - - # ... same as before - end - end - end -end -``` - -This will make sure it's on the shirt page before constructing brand, and -move back to the previous page to avoid breaking the state. - -#### Define an attribute based on an API response - -Sometimes, you want to define a resource attribute based on the API response -from its `GET` or `POST` request. For instance, if the creation of a shirt via -the API returns - -```ruby -{ - brand: 'a-brand-new-brand', - style: 't-shirt', - materials: [[:cotton, 80], [:polyamide, 20]] -} -``` - -you may want to store `style` as-is in the resource, and fetch the first value -of the first `materials` item in a `main_fabric` attribute. - -Let's take the `Shirt` factory, and define a `:style` and a `:main_fabric` -attributes: - -```ruby -module QA - module Factory - module Resource - class Shirt < Factory::Base - # ... same as before - - # Attribute from the Shirt factory if present, - # or fetched from the API response if present, - # or a QA::Factory::Base::NoValueError is raised otherwise - attribute :style - - # If the attribute from the Shirt factory is not present, - # and if the API does not contain this field, this block will be - # used to construct the value based on the API response. - attribute :main_fabric do - api_response.&dig(:materials, 0, 0) - end - - # ... same as before - end - end - end -end -``` - -**Notes on attributes precedence:** - -- factory instance variables have the highest precedence -- attributes from the API response take precedence over attributes from the - block (usually from Browser UI) -- attributes without a value will raise a `QA::Factory::Base::NoValueError` error - -## Creating resources in your tests - -To create a resource in your tests, you can call the `.fabricate!` method on the -factory class. -Note that if the factory supports API fabrication, this will use this -fabrication by default. - -Here is an example that will use the API fabrication method under the hood since -it's supported by the `Shirt` factory: - -```ruby -my_shirt = Factory::Resource::Shirt.fabricate! do |shirt| - shirt.name = 'my-shirt' -end - -expect(page).to have_text(my_shirt.name) # => "my-shirt" from the factory's attribute -expect(page).to have_text(my_shirt.brand) # => "a-brand-new-brand" from the API response -expect(page).to have_text(my_shirt.style) # => "t-shirt" from the API response -expect(page).to have_text(my_shirt.main_fabric) # => "cotton" from the API response via the block -``` - -If you explicitly want to use the Browser UI fabrication method, you can call -the `.fabricate_via_browser_ui!` method instead: - -```ruby -my_shirt = Factory::Resource::Shirt.fabricate_via_browser_ui! do |shirt| - shirt.name = 'my-shirt' -end - -expect(page).to have_text(my_shirt.name) # => "my-shirt" from the factory's attribute -expect(page).to have_text(my_shirt.brand) # => the brand name fetched from the `Page::Shirt::Show` page via the block -expect(page).to have_text(my_shirt.style) # => QA::Factory::Base::NoValueError will be raised because no API response nor a block is provided -expect(page).to have_text(my_shirt.main_fabric) # => QA::Factory::Base::NoValueError will be raised because no API response and the block didn't provide a value (because it's also based on the API response) -``` - -You can also explicitly use the API fabrication method, by calling the -`.fabricate_via_api!` method: - -```ruby -my_shirt = Factory::Resource::Shirt.fabricate_via_api! do |shirt| - shirt.name = 'my-shirt' -end -``` - -In this case, the result will be similar to calling `Factory::Resource::Shirt.fabricate!`. - -## Where to ask for help? - -If you need more information, ask for help on `#quality` channel on Slack -(internal, GitLab Team only). - -If you are not a Team Member, and you still need help to contribute, please -open an issue in GitLab CE issue tracker with the `~QA` label. diff --git a/qa/qa/factory/api_fabricator.rb b/qa/qa/factory/api_fabricator.rb deleted file mode 100644 index 887150cadf1..00000000000 --- a/qa/qa/factory/api_fabricator.rb +++ /dev/null @@ -1,101 +0,0 @@ -# frozen_string_literal: true - -require 'airborne' -require 'active_support/core_ext/object/deep_dup' -require 'capybara/dsl' - -module QA - module Factory - module ApiFabricator - include Airborne - include Capybara::DSL - - HTTP_STATUS_OK = 200 - HTTP_STATUS_CREATED = 201 - - ResourceNotFoundError = Class.new(RuntimeError) - ResourceFabricationFailedError = Class.new(RuntimeError) - ResourceURLMissingError = Class.new(RuntimeError) - - attr_reader :api_resource, :api_response - - def api_support? - respond_to?(:api_get_path) && - respond_to?(:api_post_path) && - respond_to?(:api_post_body) - end - - def fabricate_via_api! - unless api_support? - raise NotImplementedError, "Factory #{self.class.name} does not support fabrication via the API!" - end - - resource_web_url(api_post) - end - - def eager_load_api_client! - api_client.tap do |client| - # Eager-load the API client so that the personal token creation isn't - # taken in account in the actual resource creation timing. - client.personal_access_token - end - end - - private - - attr_writer :api_resource, :api_response - - def resource_web_url(resource) - resource.fetch(:web_url) do - raise ResourceURLMissingError, "API resource for #{self.class.name} does not expose a `web_url` property: `#{resource}`." - end - end - - def api_get - process_api_response(parse_body(api_get_from(api_get_path))) - end - - def api_get_from(get_path) - url = Runtime::API::Request.new(api_client, get_path).url - response = get(url) - - unless response.code == HTTP_STATUS_OK - raise ResourceNotFoundError, "Resource at #{url} could not be found (#{response.code}): `#{response}`." - end - - response - end - - def api_post - response = post( - Runtime::API::Request.new(api_client, api_post_path).url, - api_post_body) - - unless response.code == HTTP_STATUS_CREATED - raise ResourceFabricationFailedError, "Fabrication of #{self.class.name} using the API failed (#{response.code}) with `#{response}`." - end - - process_api_response(parse_body(response)) - end - - def api_client - @api_client ||= begin - Runtime::API::Client.new(:gitlab, is_new_session: !current_url.start_with?('http')) - end - end - - def parse_body(response) - JSON.parse(response.body, symbolize_names: true) - end - - def process_api_response(parsed_response) - self.api_response = parsed_response - self.api_resource = transform_api_resource(parsed_response.deep_dup) - end - - def transform_api_resource(resource) - resource - end - end - end -end diff --git a/qa/qa/factory/base.rb b/qa/qa/factory/base.rb deleted file mode 100644 index 75438b77bf3..00000000000 --- a/qa/qa/factory/base.rb +++ /dev/null @@ -1,154 +0,0 @@ -# frozen_string_literal: true - -require 'forwardable' -require 'capybara/dsl' - -module QA - module Factory - class Base - extend SingleForwardable - include ApiFabricator - extend Capybara::DSL - - NoValueError = Class.new(RuntimeError) - - def_delegators :evaluator, :attribute - - def fabricate!(*_args) - raise NotImplementedError - end - - def visit! - visit(web_url) - end - - def populate(*attributes) - attributes.each(&method(:public_send)) - end - - private - - def populate_attribute(name, block) - value = attribute_value(name, block) - - raise NoValueError, "No value was computed for #{name} of #{self.class.name}." unless value - - value - end - - def attribute_value(name, block) - api_value = api_resource&.dig(name) - - if api_value && block - log_having_both_api_result_and_block(name, api_value) - end - - api_value || (block && instance_exec(&block)) - end - - def log_having_both_api_result_and_block(name, api_value) - QA::Runtime::Logger.info "<#{self.class}> Attribute #{name.inspect} has both API response `#{api_value}` and a block. API response will be picked. Block will be ignored." - end - - def self.fabricate!(*args, &prepare_block) - fabricate_via_api!(*args, &prepare_block) - rescue NotImplementedError - fabricate_via_browser_ui!(*args, &prepare_block) - end - - def self.fabricate_via_browser_ui!(*args, &prepare_block) - options = args.extract_options! - factory = options.fetch(:factory) { new } - parents = options.fetch(:parents) { [] } - - do_fabricate!(factory: factory, prepare_block: prepare_block, parents: parents) do - log_fabrication(:browser_ui, factory, parents, args) { factory.fabricate!(*args) } - - current_url - end - end - - def self.fabricate_via_api!(*args, &prepare_block) - options = args.extract_options! - factory = options.fetch(:factory) { new } - parents = options.fetch(:parents) { [] } - - raise NotImplementedError unless factory.api_support? - - factory.eager_load_api_client! - - do_fabricate!(factory: factory, prepare_block: prepare_block, parents: parents) do - log_fabrication(:api, factory, parents, args) { factory.fabricate_via_api! } - end - end - - def self.do_fabricate!(factory:, prepare_block:, parents: []) - prepare_block.call(factory) if prepare_block - - resource_web_url = yield - factory.web_url = resource_web_url - - factory - end - private_class_method :do_fabricate! - - def self.log_fabrication(method, factory, parents, args) - return yield unless Runtime::Env.debug? - - start = Time.now - prefix = "==#{'=' * parents.size}>" - msg = [prefix] - msg << "Built a #{name}" - msg << "as a dependency of #{parents.last}" if parents.any? - msg << "via #{method}" - - yield.tap do - msg << "in #{Time.now - start} seconds" - puts msg.join(' ') - puts if parents.empty? - end - end - private_class_method :log_fabrication - - def self.evaluator - @evaluator ||= Factory::Base::DSL.new(self) - end - private_class_method :evaluator - - def self.dynamic_attributes - const_get(:DynamicAttributes) - rescue NameError - mod = const_set(:DynamicAttributes, Module.new) - - include mod - - mod - end - - def self.attributes_names - dynamic_attributes.instance_methods(false).sort.grep_v(/=$/) - end - - class DSL - def initialize(base) - @base = base - end - - def attribute(name, &block) - @base.dynamic_attributes.module_eval do - attr_writer(name) - - define_method(name) do - instance_variable_get("@#{name}") || - instance_variable_set( - "@#{name}", - populate_attribute(name, block)) - end - end - end - end - - attribute :web_url - end - end -end diff --git a/qa/qa/factory/repository/project_push.rb b/qa/qa/factory/repository/project_push.rb deleted file mode 100644 index 272b7fc5818..00000000000 --- a/qa/qa/factory/repository/project_push.rb +++ /dev/null @@ -1,42 +0,0 @@ -module QA - module Factory - module Repository - class ProjectPush < Factory::Repository::Push - attribute :project do - Factory::Resource::Project.fabricate! do |resource| - resource.name = 'project-with-code' - resource.description = 'Project with repository' - end - end - - def initialize - @file_name = 'file.txt' - @file_content = '# This is test project' - @commit_message = "This is a test commit" - @branch_name = 'master' - @new_branch = true - end - - def repository_http_uri - @repository_http_uri ||= begin - project.visit! - Page::Project::Show.act do - choose_repository_clone_http - repository_location.uri - end - end - end - - def repository_ssh_uri - @repository_ssh_uri ||= begin - project.visit! - Page::Project::Show.act do - choose_repository_clone_ssh - repository_location.uri - end - end - end - end - end - end -end diff --git a/qa/qa/factory/repository/push.rb b/qa/qa/factory/repository/push.rb deleted file mode 100644 index ffa755b9e88..00000000000 --- a/qa/qa/factory/repository/push.rb +++ /dev/null @@ -1,91 +0,0 @@ -require 'pathname' - -module QA - module Factory - module Repository - class Push < Factory::Base - attr_accessor :file_name, :file_content, :commit_message, - :branch_name, :new_branch, :output, :repository_http_uri, - :repository_ssh_uri, :ssh_key, :user - - attr_writer :remote_branch - - def initialize - @file_name = 'file.txt' - @file_content = '# This is test file' - @commit_message = "This is a test commit" - @branch_name = 'master' - @new_branch = true - @repository_http_uri = "" - @ssh_key = nil - end - - def remote_branch - @remote_branch ||= branch_name - end - - def directory=(dir) - raise "Must set directory as a Pathname" unless dir.is_a?(Pathname) - - @directory = dir - end - - def files=(files) - if !files.is_a?(Array) || files.empty? - raise ArgumentError, "Please provide an array of hashes e.g.: [{name: 'file1', content: 'foo'}]" - end - - @files = files - end - - def fabricate! - Git::Repository.perform do |repository| - if ssh_key - repository.uri = repository_ssh_uri - repository.use_ssh_key(ssh_key) - else - repository.uri = repository_http_uri - repository.use_default_credentials unless user - end - - username = 'GitLab QA' - email = 'root@gitlab.com' - - if user - repository.username = user.username - repository.password = user.password - username = user.name - email = user.email - end - - repository.clone - repository.configure_identity(username, email) - - if new_branch - repository.checkout_new_branch(branch_name) - else - repository.checkout(branch_name) - end - - if @directory - @directory.each_child do |f| - repository.add_file(f.basename, f.read) if f.file? - end - elsif @files - @files.each do |f| - repository.add_file(f[:name], f[:content]) - end - else - repository.add_file(file_name, file_content) - end - - repository.commit(commit_message) - @output = repository.push_changes("#{branch_name}:#{remote_branch}") - - repository.delete_ssh_key - end - end - end - end - end -end diff --git a/qa/qa/factory/repository/wiki_push.rb b/qa/qa/factory/repository/wiki_push.rb deleted file mode 100644 index 25b6ffe8323..00000000000 --- a/qa/qa/factory/repository/wiki_push.rb +++ /dev/null @@ -1,34 +0,0 @@ -module QA - module Factory - module Repository - class WikiPush < Factory::Repository::Push - attribute :wiki do - Factory::Resource::Wiki.fabricate! do |resource| - resource.title = 'Home' - resource.content = '# My First Wiki Content' - resource.message = 'Update home' - end - end - - def initialize - @file_name = 'Home.md' - @file_content = '# Welcome to My Wiki' - @commit_message = 'Updating Home Page' - @branch_name = 'master' - @new_branch = false - end - - def repository_http_uri - @repository_http_uri ||= begin - wiki.visit! - Page::Project::Wiki::Show.act do - go_to_clone_repository - choose_repository_clone_http - repository_location.uri - end - end - end - end - end - end -end diff --git a/qa/qa/factory/resource/branch.rb b/qa/qa/factory/resource/branch.rb deleted file mode 100644 index b05d1e252ec..00000000000 --- a/qa/qa/factory/resource/branch.rb +++ /dev/null @@ -1,77 +0,0 @@ -module QA - module Factory - module Resource - class Branch < Factory::Base - attr_accessor :project, :branch_name, - :allow_to_push, :allow_to_merge, :protected - - attribute :project do - Factory::Resource::Project.fabricate! do |resource| - resource.name = 'protected-branch-project' - end - end - - def initialize - @branch_name = 'test/branch' - @allow_to_push = true - @allow_to_merge = true - @protected = false - end - - def fabricate! - project.visit! - - Factory::Repository::ProjectPush.fabricate! do |resource| - resource.project = project - resource.file_name = 'kick-off.txt' - resource.commit_message = 'First commit' - end - - branch = Factory::Repository::ProjectPush.fabricate! do |resource| - resource.project = project - resource.file_name = 'README.md' - resource.commit_message = 'Add readme' - resource.branch_name = 'master' - resource.new_branch = false - resource.remote_branch = @branch_name - end - - Page::Project::Show.perform do |page| - page.wait { page.has_content?(branch_name) } - end - - # The upcoming process will make it access the Protected Branches page, - # select the already created branch and protect it according - # to `allow_to_push` variable. - return branch unless @protected - - Page::Project::Menu.perform(&:click_repository_settings) - - Page::Project::Settings::Repository.perform do |setting| - setting.expand_protected_branches do |page| - page.select_branch(branch_name) - - if allow_to_push - page.allow_devs_and_maintainers_to_push - else - page.allow_no_one_to_push - end - - if allow_to_merge - page.allow_devs_and_maintainers_to_merge - else - page.allow_no_one_to_merge - end - - page.wait(reload: false) do - !page.first('.btn-success').disabled? - end - - page.protect_branch - end - end - end - end - end - end -end diff --git a/qa/qa/factory/resource/ci_variable.rb b/qa/qa/factory/resource/ci_variable.rb deleted file mode 100644 index a0aefc61f9f..00000000000 --- a/qa/qa/factory/resource/ci_variable.rb +++ /dev/null @@ -1,30 +0,0 @@ -module QA - module Factory - module Resource - class CiVariable < Factory::Base - attr_accessor :key, :value - - attribute :project do - Factory::Resource::Project.fabricate! do |resource| - resource.name = 'project-with-ci-variables' - resource.description = 'project for adding CI variable test' - end - end - - def fabricate! - project.visit! - - Page::Project::Menu.perform(&:click_ci_cd_settings) - - Page::Project::Settings::CICD.perform do |setting| - setting.expand_ci_variables do |page| - page.fill_variable(key, value) - - page.save_variables - end - end - end - end - end - end -end diff --git a/qa/qa/factory/resource/deploy_key.rb b/qa/qa/factory/resource/deploy_key.rb deleted file mode 100644 index aea99c9f80d..00000000000 --- a/qa/qa/factory/resource/deploy_key.rb +++ /dev/null @@ -1,43 +0,0 @@ -module QA - module Factory - module Resource - class DeployKey < Factory::Base - attr_accessor :title, :key - - attribute :fingerprint do - Page::Project::Settings::Repository.perform do |setting| - setting.expand_deploy_keys do |key| - key_offset = key.key_titles.index do |key_title| - key_title.text == title - end - - key.key_fingerprints[key_offset].text - end - end - end - - attribute :project do - Factory::Resource::Project.fabricate! do |resource| - resource.name = 'project-to-deploy' - resource.description = 'project for adding deploy key test' - end - end - - def fabricate! - project.visit! - - Page::Project::Menu.perform(&:click_repository_settings) - - Page::Project::Settings::Repository.perform do |setting| - setting.expand_deploy_keys do |page| - page.fill_key_title(title) - page.fill_key_value(key) - - page.add_key - end - end - end - end - end - end -end diff --git a/qa/qa/factory/resource/deploy_token.rb b/qa/qa/factory/resource/deploy_token.rb deleted file mode 100644 index 68e98f0aa01..00000000000 --- a/qa/qa/factory/resource/deploy_token.rb +++ /dev/null @@ -1,50 +0,0 @@ -module QA - module Factory - module Resource - class DeployToken < Factory::Base - attr_accessor :name, :expires_at - - attribute :username do - Page::Project::Settings::Repository.perform do |page| - page.expand_deploy_tokens do |token| - token.token_username - end - end - end - - attribute :password do - Page::Project::Settings::Repository.perform do |page| - page.expand_deploy_tokens do |token| - token.token_password - end - end - end - - attribute :project do - Factory::Resource::Project.fabricate! do |resource| - resource.name = 'project-to-deploy' - resource.description = 'project for adding deploy token test' - end - end - - def fabricate! - project.visit! - - Page::Project::Menu.act do - click_repository_settings - end - - Page::Project::Settings::Repository.perform do |setting| - setting.expand_deploy_tokens do |page| - page.fill_token_name(name) - page.fill_token_expires_at(expires_at) - page.fill_scopes(read_repository: true, read_registry: false) - - page.add_token - end - end - end - end - end - end -end diff --git a/qa/qa/factory/resource/file.rb b/qa/qa/factory/resource/file.rb deleted file mode 100644 index 1148876c2d3..00000000000 --- a/qa/qa/factory/resource/file.rb +++ /dev/null @@ -1,38 +0,0 @@ -# frozen_string_literal: true - -module QA - module Factory - module Resource - class File < Factory::Base - attr_accessor :name, - :content, - :commit_message - - attribute :project do - Factory::Resource::Project.fabricate! do |resource| - resource.name = 'project-with-new-file' - end - end - - def initialize - @name = 'QA Test - File name' - @content = 'QA Test - File content' - @commit_message = 'QA Test - Commit message' - end - - def fabricate! - project.visit! - - Page::Project::Show.perform(&:create_new_file!) - - Page::File::Form.perform do |page| - page.add_name(@name) - page.add_content(@content) - page.add_commit_message(@commit_message) - page.commit_changes - end - end - end - end - end -end diff --git a/qa/qa/factory/resource/fork.rb b/qa/qa/factory/resource/fork.rb deleted file mode 100644 index d9bc44c9eb6..00000000000 --- a/qa/qa/factory/resource/fork.rb +++ /dev/null @@ -1,43 +0,0 @@ -module QA - module Factory - module Resource - class Fork < Factory::Base - attribute :push do - Factory::Repository::ProjectPush.fabricate! - end - - attribute :user do - Factory::Resource::User.fabricate! do |resource| - if Runtime::Env.forker? - resource.username = Runtime::Env.forker_username - resource.password = Runtime::Env.forker_password - end - end - end - - def fabricate! - populate(:push, :user) - - # Sign out as admin and sign is as the fork user - Page::Main::Menu.perform(&:sign_out) - Runtime::Browser.visit(:gitlab, Page::Main::Login) - Page::Main::Login.perform do |login| - login.sign_in_using_credentials(user) - end - - push.project.visit! - - Page::Project::Show.perform(&:fork_project) - - Page::Project::Fork::New.perform do |fork_new| - fork_new.choose_namespace(user.name) - end - - Page::Layout::Banner.perform do |page| - page.has_notice?('The project was successfully forked.') - end - end - end - end - end -end diff --git a/qa/qa/factory/resource/group.rb b/qa/qa/factory/resource/group.rb deleted file mode 100644 index 45e49da86f9..00000000000 --- a/qa/qa/factory/resource/group.rb +++ /dev/null @@ -1,68 +0,0 @@ -module QA - module Factory - module Resource - class Group < Factory::Base - attr_accessor :path, :description - - attribute :sandbox do - Factory::Resource::Sandbox.fabricate! - end - - attribute :id - - def initialize - @path = Runtime::Namespace.name - @description = "QA test run at #{Runtime::Namespace.time}" - end - - def fabricate! - sandbox.visit! - - Page::Group::Show.perform do |group_show| - if group_show.has_subgroup?(path) - group_show.go_to_subgroup(path) - else - group_show.go_to_new_subgroup - - Page::Group::New.perform do |group_new| - group_new.set_path(path) - group_new.set_description(description) - group_new.set_visibility('Public') - group_new.create - end - - # Ensure that the group was actually created - group_show.wait(time: 1) do - group_show.has_text?(path) && - group_show.has_new_project_or_subgroup_dropdown? - end - end - end - end - - def fabricate_via_api! - resource_web_url(api_get) - rescue ResourceNotFoundError - super - end - - def api_get_path - "/groups/#{CGI.escape("#{sandbox.path}/#{path}")}" - end - - def api_post_path - '/groups' - end - - def api_post_body - { - parent_id: sandbox.id, - path: path, - name: path, - visibility: 'public' - } - end - end - end - end -end diff --git a/qa/qa/factory/resource/issue.rb b/qa/qa/factory/resource/issue.rb deleted file mode 100644 index 3a28e0d5aa6..00000000000 --- a/qa/qa/factory/resource/issue.rb +++ /dev/null @@ -1,30 +0,0 @@ -module QA - module Factory - module Resource - class Issue < Factory::Base - attr_writer :description - - attribute :project do - Factory::Resource::Project.fabricate! do |resource| - resource.name = 'project-for-issues' - resource.description = 'project for adding issues' - end - end - - attribute :title - - def fabricate! - project.visit! - - Page::Project::Show.perform(&:go_to_new_issue) - - Page::Project::Issue::New.perform do |page| - page.add_title(@title) - page.add_description(@description) - page.create_new_issue - end - end - end - end - end -end diff --git a/qa/qa/factory/resource/kubernetes_cluster.rb b/qa/qa/factory/resource/kubernetes_cluster.rb deleted file mode 100644 index aac6864f42f..00000000000 --- a/qa/qa/factory/resource/kubernetes_cluster.rb +++ /dev/null @@ -1,57 +0,0 @@ -require 'securerandom' - -module QA - module Factory - module Resource - class KubernetesCluster < Factory::Base - attr_writer :project, :cluster, - :install_helm_tiller, :install_ingress, :install_prometheus, :install_runner - - attribute :ingress_ip do - Page::Project::Operations::Kubernetes::Show.perform(&:ingress_ip) - end - - def fabricate! - @project.visit! - - Page::Project::Menu.perform( - &:click_operations_kubernetes) - - Page::Project::Operations::Kubernetes::Index.perform( - &:add_kubernetes_cluster) - - Page::Project::Operations::Kubernetes::Add.perform( - &:add_existing_cluster) - - Page::Project::Operations::Kubernetes::AddExisting.perform do |page| - page.set_cluster_name(@cluster.cluster_name) - page.set_api_url(@cluster.api_url) - page.set_ca_certificate(@cluster.ca_certificate) - page.set_token(@cluster.token) - page.check_rbac! if @cluster.rbac - page.add_cluster! - end - - if @install_helm_tiller - Page::Project::Operations::Kubernetes::Show.perform do |page| - # We must wait a few seconds for permissions to be set up correctly for new cluster - sleep 10 - - # Helm must be installed before everything else - page.install!(:helm) - page.await_installed(:helm) - - page.install!(:ingress) if @install_ingress - page.install!(:prometheus) if @install_prometheus - page.install!(:runner) if @install_runner - - page.await_installed(:ingress) if @install_ingress - page.await_installed(:prometheus) if @install_prometheus - page.await_installed(:runner) if @install_runner - end - end - end - end - end - end -end diff --git a/qa/qa/factory/resource/label.rb b/qa/qa/factory/resource/label.rb deleted file mode 100644 index 32bc519b48c..00000000000 --- a/qa/qa/factory/resource/label.rb +++ /dev/null @@ -1,39 +0,0 @@ -require 'securerandom' - -module QA - module Factory - module Resource - class Label < Factory::Base - attr_accessor :description, :color - - attribute :title - - attribute :project do - Factory::Resource::Project.fabricate! do |resource| - resource.name = 'project-with-label' - end - end - - def initialize - @title = "qa-test-#{SecureRandom.hex(8)}" - @description = 'This is a test label' - @color = '#0033CC' - end - - def fabricate! - project.visit! - - Page::Project::Menu.perform(&:go_to_labels) - Page::Label::Index.perform(&:go_to_new_label) - - Page::Label::New.perform do |page| - page.fill_title(@title) - page.fill_description(@description) - page.fill_color(@color) - page.create_label - end - end - end - end - end -end diff --git a/qa/qa/factory/resource/merge_request.rb b/qa/qa/factory/resource/merge_request.rb deleted file mode 100644 index 4b7d2287f98..00000000000 --- a/qa/qa/factory/resource/merge_request.rb +++ /dev/null @@ -1,71 +0,0 @@ -require 'securerandom' - -module QA - module Factory - module Resource - class MergeRequest < Factory::Base - attr_accessor :title, - :description, - :source_branch, - :target_branch, - :assignee, - :milestone, - :labels - - attribute :project do - Factory::Resource::Project.fabricate! do |resource| - resource.name = 'project-with-merge-request' - end - end - - attribute :target do - project.visit! - - Factory::Repository::ProjectPush.fabricate! do |resource| - resource.project = project - resource.branch_name = 'master' - resource.remote_branch = target_branch - end - end - - attribute :source do - Factory::Repository::ProjectPush.fabricate! do |resource| - resource.project = project - resource.branch_name = target_branch - resource.remote_branch = source_branch - resource.new_branch = false - resource.file_name = "added_file.txt" - resource.file_content = "File Added" - end - end - - def initialize - @title = 'QA test - merge request' - @description = 'This is a test merge request' - @source_branch = "qa-test-feature-#{SecureRandom.hex(8)}" - @target_branch = "master" - @assignee = nil - @milestone = nil - @labels = [] - end - - def fabricate! - populate(:target, :source) - - project.visit! - Page::Project::Show.perform(&:new_merge_request) - Page::MergeRequest::New.perform do |page| - page.fill_title(@title) - page.fill_description(@description) - page.choose_milestone(@milestone) if @milestone - labels.each do |label| - page.select_label(label) - end - - page.create_merge_request - end - end - end - end - end -end diff --git a/qa/qa/factory/resource/merge_request_from_fork.rb b/qa/qa/factory/resource/merge_request_from_fork.rb deleted file mode 100644 index 1311bf625a6..00000000000 --- a/qa/qa/factory/resource/merge_request_from_fork.rb +++ /dev/null @@ -1,31 +0,0 @@ -module QA - module Factory - module Resource - class MergeRequestFromFork < MergeRequest - attr_accessor :fork_branch - - attribute :fork do - Factory::Resource::Fork.fabricate! - end - - attribute :push do - Factory::Repository::ProjectPush.fabricate! do |resource| - resource.project = fork - resource.branch_name = fork_branch - resource.file_name = 'file2.txt' - resource.user = fork.user - end - end - - def fabricate! - populate(:push) - - fork.visit! - - Page::Project::Show.perform(&:new_merge_request) - Page::MergeRequest::New.perform(&:create_merge_request) - end - end - end - end -end diff --git a/qa/qa/factory/resource/personal_access_token.rb b/qa/qa/factory/resource/personal_access_token.rb deleted file mode 100644 index ceb0f1c3d75..00000000000 --- a/qa/qa/factory/resource/personal_access_token.rb +++ /dev/null @@ -1,27 +0,0 @@ -module QA - module Factory - module Resource - ## - # Create a personal access token that can be used by the api - # - class PersonalAccessToken < Factory::Base - attr_accessor :name - - attribute :access_token do - Page::Profile::PersonalAccessTokens.perform(&:created_access_token) - end - - def fabricate! - Page::Main::Menu.perform(&:go_to_profile_settings) - Page::Profile::Menu.perform(&:click_access_tokens) - - Page::Profile::PersonalAccessTokens.perform do |page| - page.fill_token_name(name || 'api-test-token') - page.check_api - page.create_token - end - end - end - end - end -end diff --git a/qa/qa/factory/resource/project.rb b/qa/qa/factory/resource/project.rb deleted file mode 100644 index f691ae5a342..00000000000 --- a/qa/qa/factory/resource/project.rb +++ /dev/null @@ -1,78 +0,0 @@ -require 'securerandom' - -module QA - module Factory - module Resource - class Project < Factory::Base - attribute :name - attribute :description - - attribute :group do - Factory::Resource::Group.fabricate! - end - - attribute :repository_ssh_location do - Page::Project::Show.perform do |page| - page.choose_repository_clone_ssh - page.repository_location - end - end - - attribute :repository_http_location do - Page::Project::Show.perform do |page| - page.choose_repository_clone_http - page.repository_location - end - end - - def initialize - @description = 'My awesome project' - end - - def name=(raw_name) - @name = "#{raw_name}-#{SecureRandom.hex(8)}" - end - - def fabricate! - group.visit! - - Page::Group::Show.perform(&:go_to_new_project) - - Page::Project::New.perform do |page| - page.choose_test_namespace - page.choose_name(@name) - page.add_description(@description) - page.set_visibility('Public') - page.create_new_project - end - end - - def api_get_path - "/projects/#{name}" - end - - def api_post_path - '/projects' - end - - def api_post_body - { - namespace_id: group.id, - path: name, - name: name, - description: description, - visibility: 'public' - } - end - - private - - def transform_api_resource(resource) - resource[:repository_ssh_location] = Git::Location.new(resource[:ssh_url_to_repo]) - resource[:repository_http_location] = Git::Location.new(resource[:http_url_to_repo]) - resource - end - end - end - end -end diff --git a/qa/qa/factory/resource/project_imported_from_github.rb b/qa/qa/factory/resource/project_imported_from_github.rb deleted file mode 100644 index ce20641e6cc..00000000000 --- a/qa/qa/factory/resource/project_imported_from_github.rb +++ /dev/null @@ -1,36 +0,0 @@ -require 'securerandom' - -module QA - module Factory - module Resource - class ProjectImportedFromGithub < Resource::Project - attr_accessor :name - attr_writer :personal_access_token, :github_repository_path - - attribute :group do - Factory::Resource::Group.fabricate! - end - - def fabricate! - group.visit! - - Page::Group::Show.perform(&:go_to_new_project) - - Page::Project::New.perform do |page| - page.go_to_import_project - end - - Page::Project::New.perform do |page| - page.go_to_github_import - end - - Page::Project::Import::Github.perform do |page| - page.add_personal_access_token(@personal_access_token) - page.list_repos - page.import!(@github_repository_path, @name) - end - end - end - end - end -end diff --git a/qa/qa/factory/resource/project_milestone.rb b/qa/qa/factory/resource/project_milestone.rb deleted file mode 100644 index 383f534c12c..00000000000 --- a/qa/qa/factory/resource/project_milestone.rb +++ /dev/null @@ -1,36 +0,0 @@ -module QA - module Factory - module Resource - class ProjectMilestone < Factory::Base - attr_reader :title - attr_accessor :description - - attribute :project do - Factory::Resource::Project.fabricate! - end - - def title=(title) - @title = "#{title}-#{SecureRandom.hex(4)}" - @description = 'A milestone' - end - - def fabricate! - project.visit! - - Page::Project::Menu.perform do |page| - page.click_issues - page.click_milestones - end - - Page::Project::Milestone::Index.perform(&:click_new_milestone) - - Page::Project::Milestone::New.perform do |milestone_new| - milestone_new.set_title(@title) - milestone_new.set_description(@description) - milestone_new.create_new_milestone - end - end - end - end - end -end diff --git a/qa/qa/factory/resource/runner.rb b/qa/qa/factory/resource/runner.rb deleted file mode 100644 index 7108db1e55a..00000000000 --- a/qa/qa/factory/resource/runner.rb +++ /dev/null @@ -1,49 +0,0 @@ -require 'securerandom' - -module QA - module Factory - module Resource - class Runner < Factory::Base - attr_writer :name, :tags, :image - - attribute :project do - Factory::Resource::Project.fabricate! do |resource| - resource.name = 'project-with-ci-cd' - resource.description = 'Project with CI/CD Pipelines' - end - end - - def name - @name || "qa-runner-#{SecureRandom.hex(4)}" - end - - def tags - @tags || %w[qa e2e] - end - - def image - @image || 'gitlab/gitlab-runner:alpine' - end - - def fabricate! - project.visit! - - Page::Project::Menu.perform(&:click_ci_cd_settings) - - Service::Runner.new(name).tap do |runner| - Page::Project::Settings::CICD.perform do |settings| - settings.expand_runners_settings do |runners| - runner.pull - runner.token = runners.registration_token - runner.address = runners.coordinator_address - runner.tags = tags - runner.image = image - runner.register! - end - end - end - end - end - end - end -end diff --git a/qa/qa/factory/resource/sandbox.rb b/qa/qa/factory/resource/sandbox.rb deleted file mode 100644 index a125bac65dd..00000000000 --- a/qa/qa/factory/resource/sandbox.rb +++ /dev/null @@ -1,60 +0,0 @@ -module QA - module Factory - module Resource - ## - # Ensure we're in our sandbox namespace, either by navigating to it or by - # creating it if it doesn't yet exist. - # - class Sandbox < Factory::Base - attr_reader :path - - attribute :id - - def initialize - @path = Runtime::Namespace.sandbox_name - end - - def fabricate! - Page::Main::Menu.perform(&:go_to_groups) - - Page::Dashboard::Groups.perform do |page| - if page.has_group?(path) - page.go_to_group(path) - else - page.go_to_new_group - - Page::Group::New.perform do |group| - group.set_path(path) - group.set_description('GitLab QA Sandbox Group') - group.set_visibility('Public') - group.create - end - end - end - end - - def fabricate_via_api! - resource_web_url(api_get) - rescue ResourceNotFoundError - super - end - - def api_get_path - "/groups/#{path}" - end - - def api_post_path - '/groups' - end - - def api_post_body - { - path: path, - name: path, - visibility: 'public' - } - end - end - end - end -end diff --git a/qa/qa/factory/resource/ssh_key.rb b/qa/qa/factory/resource/ssh_key.rb deleted file mode 100644 index 6f952eda36f..00000000000 --- a/qa/qa/factory/resource/ssh_key.rb +++ /dev/null @@ -1,28 +0,0 @@ -# frozen_string_literal: true - -module QA - module Factory - module Resource - class SSHKey < Factory::Base - extend Forwardable - - attr_accessor :title - - def_delegators :key, :private_key, :public_key, :fingerprint - - def key - @key ||= Runtime::Key::RSA.new - end - - def fabricate! - Page::Main::Menu.perform(&:go_to_profile_settings) - Page::Profile::Menu.perform(&:click_ssh_keys) - - Page::Profile::SSHKeys.perform do |page| - page.add_key(public_key, title) - end - end - end - end - end -end diff --git a/qa/qa/factory/resource/user.rb b/qa/qa/factory/resource/user.rb deleted file mode 100644 index 68faadddd1c..00000000000 --- a/qa/qa/factory/resource/user.rb +++ /dev/null @@ -1,92 +0,0 @@ -require 'securerandom' - -module QA - module Factory - module Resource - class User < Factory::Base - attr_reader :unique_id - attr_writer :username, :password - - def initialize - @unique_id = SecureRandom.hex(8) - end - - def username - @username ||= "qa-user-#{unique_id}" - end - - def password - @password ||= 'password' - end - - def name - @name ||= username - end - - def email - @email ||= "#{username}@example.com" - end - - def credentials_given? - defined?(@username) && defined?(@password) - end - - def fabricate! - # Don't try to log-out if we're not logged-in - if Page::Main::Menu.perform { |p| p.has_personal_area?(wait: 0) } - Page::Main::Menu.perform { |main| main.sign_out } - end - - if credentials_given? - Page::Main::Login.perform do |login| - login.sign_in_using_credentials(self) - end - else - Page::Main::Login.perform do |login| - login.switch_to_register_tab - end - Page::Main::SignUp.perform do |signup| - signup.sign_up!(self) - end - end - end - - def fabricate_via_api! - resource_web_url(api_get) - rescue ResourceNotFoundError - super - end - - def api_get_path - "/users/#{fetch_id(username)}" - end - - def api_post_path - '/users' - end - - def api_post_body - { - email: email, - password: password, - username: username, - name: name, - skip_confirmation: true - } - end - - private - - def fetch_id(username) - users = parse_body(api_get_from("/users?username=#{username}")) - - unless users.size == 1 && users.first[:username] == username - raise ResourceNotFoundError, "Expected one user with username #{username} but found: `#{users}`." - end - - users.first[:id] - end - end - end - end -end diff --git a/qa/qa/factory/resource/wiki.rb b/qa/qa/factory/resource/wiki.rb deleted file mode 100644 index 769f394e85c..00000000000 --- a/qa/qa/factory/resource/wiki.rb +++ /dev/null @@ -1,30 +0,0 @@ -module QA - module Factory - module Resource - class Wiki < Factory::Base - attr_accessor :title, :content, :message - - attribute :project do - Factory::Resource::Project.fabricate! do |resource| - resource.name = 'project-for-wikis' - resource.description = 'project for adding wikis' - end - end - - def fabricate! - project.visit! - - Page::Project::Menu.perform { |menu_side| menu_side.click_wiki } - - Page::Project::Wiki::New.perform do |wiki_new| - wiki_new.go_to_create_first_page - wiki_new.set_title(@title) - wiki_new.set_content(@content) - wiki_new.set_message(@message) - wiki_new.create_new_page - end - end - end - end - end -end diff --git a/qa/qa/factory/settings/hashed_storage.rb b/qa/qa/factory/settings/hashed_storage.rb deleted file mode 100644 index 4e32382f910..00000000000 --- a/qa/qa/factory/settings/hashed_storage.rb +++ /dev/null @@ -1,24 +0,0 @@ -module QA - module Factory - module Settings - class HashedStorage < Factory::Base - def fabricate!(*traits) - raise ArgumentError unless traits.include?(:enabled) - - Page::Main::Login.perform(&:sign_in_using_credentials) - Page::Main::Menu.perform(&:go_to_admin_area) - Page::Admin::Menu.perform(&:go_to_repository_settings) - - Page::Admin::Settings::Repository.perform do |setting| - setting.expand_repository_storage do |page| - page.enable_hashed_storage - page.save_settings - end - end - - QA::Page::Main::Menu.perform(&:sign_out) - end - end - end - end -end diff --git a/qa/qa/page/main/login.rb b/qa/qa/page/main/login.rb index 94b9486b0d5..97ffe0e5716 100644 --- a/qa/qa/page/main/login.rb +++ b/qa/qa/page/main/login.rb @@ -65,7 +65,7 @@ module QA end def sign_in_using_admin_credentials - admin = QA::Factory::Resource::User.new.tap do |user| + admin = QA::Resource::User.new.tap do |user| user.username = QA::Runtime::User.admin_username user.password = QA::Runtime::User.admin_password end diff --git a/qa/qa/resource/README.md b/qa/qa/resource/README.md new file mode 100644 index 00000000000..4cdeb3f42a2 --- /dev/null +++ b/qa/qa/resource/README.md @@ -0,0 +1,392 @@ +# Resource class in GitLab QA + +Resources are primarily created using Browser UI steps, but can also +be created via the API. + +## How to properly implement a resource class? + +All resource classes should inherit from [`Resource::Base`](./base.rb). + +There is only one mandatory method to implement to define a resource class. +This is the `#fabricate!` method, which is used to build the resource via the +browser UI. Note that you should only use [Page objects](../page/README.md) to +interact with a Web page in this method. + +Here is an imaginary example: + +```ruby +module QA + module Resource + class Shirt < Base + attr_accessor :name + + def fabricate! + Page::Dashboard::Index.perform do |dashboard_index| + dashboard_index.go_to_new_shirt + end + + Page::Shirt::New.perform do |shirt_new| + shirt_new.set_name(name) + shirt_new.create_shirt! + end + end + end + end +end +``` + +### Define API implementation + +A resource class may also implement the three following methods to be able to +create the resource via the public GitLab API: + +- `#api_get_path`: The `GET` path to fetch an existing resource. +- `#api_post_path`: The `POST` path to create a new resource. +- `#api_post_body`: The `POST` body (as a Ruby hash) to create a new resource. + +Let's take the `Shirt` resource class, and add these three API methods: + +```ruby +module QA + module Resource + class Shirt < Base + attr_accessor :name + + def fabricate! + # ... same as before + end + + def api_get_path + "/shirt/#{name}" + end + + def api_post_path + "/shirts" + end + + def api_post_body + { + name: name + } + end + end + end +end +``` + +The [`Project` resource](./project.rb) is a good real example of Browser +UI and API implementations. + +#### Resource attributes + +A resource may need another resource to exist first. For instance, a project +needs a group to be created in. + +To define a resource attribute, you can use the `attribute` method with a +block using the other resource class to fabricate the resource. + +That will allow access to the other resource from your resource object's +methods. You would usually use it in `#fabricate!`, `#api_get_path`, +`#api_post_path`, `#api_post_body`. + +Let's take the `Shirt` resource class, and add a `project` attribute to it: + +```ruby +module QA + module Resource + class Shirt < Base + attr_accessor :name + + attribute :project do + Project.fabricate! do |resource| + resource.name = 'project-to-create-a-shirt' + end + end + + def fabricate! + project.visit! + + Page::Project::Show.perform do |project_show| + project_show.go_to_new_shirt + end + + Page::Shirt::New.perform do |shirt_new| + shirt_new.set_name(name) + shirt_new.create_shirt! + end + end + + def api_get_path + "/project/#{project.path}/shirt/#{name}" + end + + def api_post_path + "/project/#{project.path}/shirts" + end + + def api_post_body + { + name: name + } + end + end + end +end +``` + +**Note that all the attributes are lazily constructed. This means if you want +a specific attribute to be fabricated first, you'll need to call the +attribute method first even if you're not using it.** + +#### Product data attributes + +Once created, you may want to populate a resource with attributes that can be +found in the Web page, or in the API response. +For instance, once you create a project, you may want to store its repository +SSH URL as an attribute. + +Again we could use the `attribute` method with a block, using a page object +to retrieve the data on the page. + +Let's take the `Shirt` resource class, and define a `:brand` attribute: + +```ruby +module QA + module Resource + class Shirt < Base + attr_accessor :name + + attribute :project do + Project.fabricate! do |resource| + resource.name = 'project-to-create-a-shirt' + end + end + + # Attribute populated from the Browser UI (using the block) + attribute :brand do + Page::Shirt::Show.perform do |shirt_show| + shirt_show.fetch_brand_from_page + end + end + + # ... same as before + end + end +end +``` + +**Note again that all the attributes are lazily constructed. This means if +you call `shirt.brand` after moving to the other page, it'll not properly +retrieve the data because we're no longer on the expected page.** + +Consider this: + +```ruby +shirt = + QA::Resource::Shirt.fabricate! do |resource| + resource.name = "GitLab QA" + end + +shirt.project.visit! + +shirt.brand # => FAIL! +``` + +The above example will fail because now we're on the project page, trying to +construct the brand data from the shirt page, however we moved to the project +page already. There are two ways to solve this, one is that we could try to +retrieve the brand before visiting the project again: + +```ruby +shirt = + QA::Resource::Shirt.fabricate! do |resource| + resource.name = "GitLab QA" + end + +shirt.brand # => OK! + +shirt.project.visit! + +shirt.brand # => OK! +``` + +The attribute will be stored in the instance therefore all the following calls +will be fine, using the data previously constructed. If we think that this +might be too brittle, we could eagerly construct the data right before +ending fabrication: + +```ruby +module QA + module Resource + class Shirt < Base + # ... same as before + + def fabricate! + project.visit! + + Page::Project::Show.perform do |project_show| + project_show.go_to_new_shirt + end + + Page::Shirt::New.perform do |shirt_new| + shirt_new.set_name(name) + shirt_new.create_shirt! + end + + populate(:brand) # Eagerly construct the data + end + end + end +end +``` + +The `populate` method will iterate through its arguments and call each +attribute respectively. Here `populate(:brand)` has the same effect as +just `brand`. Using the populate method makes the intention clearer. + +With this, it will make sure we construct the data right after we create the +shirt. The drawback is that this will always construct the data when the +resource is fabricated even if we don't need to use the data. + +Alternatively, we could just make sure we're on the right page before +constructing the brand data: + +```ruby +module QA + module Resource + class Shirt < Base + attr_accessor :name + + attribute :project do + Project.fabricate! do |resource| + resource.name = 'project-to-create-a-shirt' + end + end + + # Attribute populated from the Browser UI (using the block) + attribute :brand do + back_url = current_url + visit! + + Page::Shirt::Show.perform do |shirt_show| + shirt_show.fetch_brand_from_page + end + + visit(back_url) + end + + # ... same as before + end + end +end +``` + +This will make sure it's on the shirt page before constructing brand, and +move back to the previous page to avoid breaking the state. + +#### Define an attribute based on an API response + +Sometimes, you want to define a resource attribute based on the API response +from its `GET` or `POST` request. For instance, if the creation of a shirt via +the API returns + +```ruby +{ + brand: 'a-brand-new-brand', + style: 't-shirt', + materials: [[:cotton, 80], [:polyamide, 20]] +} +``` + +you may want to store `style` as-is in the resource, and fetch the first value +of the first `materials` item in a `main_fabric` attribute. + +Let's take the `Shirt` resource class, and define a `:style` and a +`:main_fabric` attributes: + +```ruby +module QA + module Resource + class Shirt < Base + # ... same as before + + # @style from the instance if present, + # or fetched from the API response if present, + # or a QA::Resource::Base::NoValueError is raised otherwise + attribute :style + + # If @main_fabric is not present, + # and if the API does not contain this field, this block will be + # used to construct the value based on the API response, and + # store the result in @main_fabric + attribute :main_fabric do + api_response.&dig(:materials, 0, 0) + end + + # ... same as before + end + end +end +``` + +**Notes on attributes precedence:** + +- resource instance variables have the highest precedence +- attributes from the API response take precedence over attributes from the + block (usually from Browser UI) +- attributes without a value will raise a `QA::Resource::Base::NoValueError` error + +## Creating resources in your tests + +To create a resource in your tests, you can call the `.fabricate!` method on +the resource class. +Note that if the resource class supports API fabrication, this will use this +fabrication by default. + +Here is an example that will use the API fabrication method under the hood +since it's supported by the `Shirt` resource class: + +```ruby +my_shirt = Resource::Shirt.fabricate! do |shirt| + shirt.name = 'my-shirt' +end + +expect(page).to have_text(my_shirt.name) # => "my-shirt" from the resource's instance variable +expect(page).to have_text(my_shirt.brand) # => "a-brand-new-brand" from the API response +expect(page).to have_text(my_shirt.style) # => "t-shirt" from the API response +expect(page).to have_text(my_shirt.main_fabric) # => "cotton" from the API response via the block +``` + +If you explicitly want to use the Browser UI fabrication method, you can call +the `.fabricate_via_browser_ui!` method instead: + +```ruby +my_shirt = Resource::Shirt.fabricate_via_browser_ui! do |shirt| + shirt.name = 'my-shirt' +end + +expect(page).to have_text(my_shirt.name) # => "my-shirt" from the resource's instance variable +expect(page).to have_text(my_shirt.brand) # => the brand name fetched from the `Page::Shirt::Show` page via the block +expect(page).to have_text(my_shirt.style) # => QA::Resource::Base::NoValueError will be raised because no API response nor a block is provided +expect(page).to have_text(my_shirt.main_fabric) # => QA::Resource::Base::NoValueError will be raised because no API response and the block didn't provide a value (because it's also based on the API response) +``` + +You can also explicitly use the API fabrication method, by calling the +`.fabricate_via_api!` method: + +```ruby +my_shirt = Resource::Shirt.fabricate_via_api! do |shirt| + shirt.name = 'my-shirt' +end +``` + +In this case, the result will be similar to calling +`Resource::Shirt.fabricate!`. + +## Where to ask for help? + +If you need more information, ask for help on `#quality` channel on Slack +(internal, GitLab Team only). + +If you are not a Team Member, and you still need help to contribute, please +open an issue in GitLab CE issue tracker with the `~QA` label. diff --git a/qa/qa/resource/api_fabricator.rb b/qa/qa/resource/api_fabricator.rb new file mode 100644 index 00000000000..3762a94f312 --- /dev/null +++ b/qa/qa/resource/api_fabricator.rb @@ -0,0 +1,101 @@ +# frozen_string_literal: true + +require 'airborne' +require 'active_support/core_ext/object/deep_dup' +require 'capybara/dsl' + +module QA + module Resource + module ApiFabricator + include Airborne + include Capybara::DSL + + HTTP_STATUS_OK = 200 + HTTP_STATUS_CREATED = 201 + + ResourceNotFoundError = Class.new(RuntimeError) + ResourceFabricationFailedError = Class.new(RuntimeError) + ResourceURLMissingError = Class.new(RuntimeError) + + attr_reader :api_resource, :api_response + + def api_support? + respond_to?(:api_get_path) && + respond_to?(:api_post_path) && + respond_to?(:api_post_body) + end + + def fabricate_via_api! + unless api_support? + raise NotImplementedError, "Resource #{self.class.name} does not support fabrication via the API!" + end + + resource_web_url(api_post) + end + + def eager_load_api_client! + api_client.tap do |client| + # Eager-load the API client so that the personal token creation isn't + # taken in account in the actual resource creation timing. + client.personal_access_token + end + end + + private + + attr_writer :api_resource, :api_response + + def resource_web_url(resource) + resource.fetch(:web_url) do + raise ResourceURLMissingError, "API resource for #{self.class.name} does not expose a `web_url` property: `#{resource}`." + end + end + + def api_get + process_api_response(parse_body(api_get_from(api_get_path))) + end + + def api_get_from(get_path) + url = Runtime::API::Request.new(api_client, get_path).url + response = get(url) + + unless response.code == HTTP_STATUS_OK + raise ResourceNotFoundError, "Resource at #{url} could not be found (#{response.code}): `#{response}`." + end + + response + end + + def api_post + response = post( + Runtime::API::Request.new(api_client, api_post_path).url, + api_post_body) + + unless response.code == HTTP_STATUS_CREATED + raise ResourceFabricationFailedError, "Fabrication of #{self.class.name} using the API failed (#{response.code}) with `#{response}`." + end + + process_api_response(parse_body(response)) + end + + def api_client + @api_client ||= begin + Runtime::API::Client.new(:gitlab, is_new_session: !current_url.start_with?('http')) + end + end + + def parse_body(response) + JSON.parse(response.body, symbolize_names: true) + end + + def process_api_response(parsed_response) + self.api_response = parsed_response + self.api_resource = transform_api_resource(parsed_response.deep_dup) + end + + def transform_api_resource(api_resource) + api_resource + end + end + end +end diff --git a/qa/qa/resource/base.rb b/qa/qa/resource/base.rb new file mode 100644 index 00000000000..f3eefb70520 --- /dev/null +++ b/qa/qa/resource/base.rb @@ -0,0 +1,154 @@ +# frozen_string_literal: true + +require 'forwardable' +require 'capybara/dsl' + +module QA + module Resource + class Base + extend SingleForwardable + include ApiFabricator + extend Capybara::DSL + + NoValueError = Class.new(RuntimeError) + + def_delegators :evaluator, :attribute + + def fabricate!(*_args) + raise NotImplementedError + end + + def visit! + visit(web_url) + end + + def populate(*attributes) + attributes.each(&method(:public_send)) + end + + private + + def populate_attribute(name, block) + value = attribute_value(name, block) + + raise NoValueError, "No value was computed for #{name} of #{self.class.name}." unless value + + value + end + + def attribute_value(name, block) + api_value = api_resource&.dig(name) + + if api_value && block + log_having_both_api_result_and_block(name, api_value) + end + + api_value || (block && instance_exec(&block)) + end + + def log_having_both_api_result_and_block(name, api_value) + QA::Runtime::Logger.info "<#{self.class}> Attribute #{name.inspect} has both API response `#{api_value}` and a block. API response will be picked. Block will be ignored." + end + + def self.fabricate!(*args, &prepare_block) + fabricate_via_api!(*args, &prepare_block) + rescue NotImplementedError + fabricate_via_browser_ui!(*args, &prepare_block) + end + + def self.fabricate_via_browser_ui!(*args, &prepare_block) + options = args.extract_options! + resource = options.fetch(:resource) { new } + parents = options.fetch(:parents) { [] } + + do_fabricate!(resource: resource, prepare_block: prepare_block, parents: parents) do + log_fabrication(:browser_ui, resource, parents, args) { resource.fabricate!(*args) } + + current_url + end + end + + def self.fabricate_via_api!(*args, &prepare_block) + options = args.extract_options! + resource = options.fetch(:resource) { new } + parents = options.fetch(:parents) { [] } + + raise NotImplementedError unless resource.api_support? + + resource.eager_load_api_client! + + do_fabricate!(resource: resource, prepare_block: prepare_block, parents: parents) do + log_fabrication(:api, resource, parents, args) { resource.fabricate_via_api! } + end + end + + def self.do_fabricate!(resource:, prepare_block:, parents: []) + prepare_block.call(resource) if prepare_block + + resource_web_url = yield + resource.web_url = resource_web_url + + resource + end + private_class_method :do_fabricate! + + def self.log_fabrication(method, resource, parents, args) + return yield unless Runtime::Env.debug? + + start = Time.now + prefix = "==#{'=' * parents.size}>" + msg = [prefix] + msg << "Built a #{name}" + msg << "as a dependency of #{parents.last}" if parents.any? + msg << "via #{method}" + + yield.tap do + msg << "in #{Time.now - start} seconds" + puts msg.join(' ') + puts if parents.empty? + end + end + private_class_method :log_fabrication + + def self.evaluator + @evaluator ||= Base::DSL.new(self) + end + private_class_method :evaluator + + def self.dynamic_attributes + const_get(:DynamicAttributes) + rescue NameError + mod = const_set(:DynamicAttributes, Module.new) + + include mod + + mod + end + + def self.attributes_names + dynamic_attributes.instance_methods(false).sort.grep_v(/=$/) + end + + class DSL + def initialize(base) + @base = base + end + + def attribute(name, &block) + @base.dynamic_attributes.module_eval do + attr_writer(name) + + define_method(name) do + instance_variable_get("@#{name}") || + instance_variable_set( + "@#{name}", + populate_attribute(name, block)) + end + end + end + end + + attribute :web_url + end + end +end diff --git a/qa/qa/resource/branch.rb b/qa/qa/resource/branch.rb new file mode 100644 index 00000000000..bd52c4abe02 --- /dev/null +++ b/qa/qa/resource/branch.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +module QA + module Resource + class Branch < Base + attr_accessor :project, :branch_name, + :allow_to_push, :allow_to_merge, :protected + + attribute :project do + Project.fabricate! do |resource| + resource.name = 'protected-branch-project' + end + end + + def initialize + @branch_name = 'test/branch' + @allow_to_push = true + @allow_to_merge = true + @protected = false + end + + def fabricate! + project.visit! + + Repository::ProjectPush.fabricate! do |resource| + resource.project = project + resource.file_name = 'kick-off.txt' + resource.commit_message = 'First commit' + end + + branch = Repository::ProjectPush.fabricate! do |resource| + resource.project = project + resource.file_name = 'README.md' + resource.commit_message = 'Add readme' + resource.branch_name = 'master' + resource.new_branch = false + resource.remote_branch = @branch_name + end + + Page::Project::Show.perform do |page| + page.wait { page.has_content?(branch_name) } + end + + # The upcoming process will make it access the Protected Branches page, + # select the already created branch and protect it according + # to `allow_to_push` variable. + return branch unless @protected + + Page::Project::Menu.perform(&:click_repository_settings) + + Page::Project::Settings::Repository.perform do |setting| + setting.expand_protected_branches do |page| + page.select_branch(branch_name) + + if allow_to_push + page.allow_devs_and_maintainers_to_push + else + page.allow_no_one_to_push + end + + if allow_to_merge + page.allow_devs_and_maintainers_to_merge + else + page.allow_no_one_to_merge + end + + page.wait(reload: false) do + !page.first('.btn-success').disabled? + end + + page.protect_branch + end + end + end + end + end +end diff --git a/qa/qa/resource/ci_variable.rb b/qa/qa/resource/ci_variable.rb new file mode 100644 index 00000000000..0570c47d41c --- /dev/null +++ b/qa/qa/resource/ci_variable.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module QA + module Resource + class CiVariable < Base + attr_accessor :key, :value + + attribute :project do + Project.fabricate! do |resource| + resource.name = 'project-with-ci-variables' + resource.description = 'project for adding CI variable test' + end + end + + def fabricate! + project.visit! + + Page::Project::Menu.perform(&:click_ci_cd_settings) + + Page::Project::Settings::CICD.perform do |setting| + setting.expand_ci_variables do |page| + page.fill_variable(key, value) + + page.save_variables + end + end + end + end + end +end diff --git a/qa/qa/resource/deploy_key.rb b/qa/qa/resource/deploy_key.rb new file mode 100644 index 00000000000..9ed8fb7726e --- /dev/null +++ b/qa/qa/resource/deploy_key.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +module QA + module Resource + class DeployKey < Base + attr_accessor :title, :key + + attribute :fingerprint do + Page::Project::Settings::Repository.perform do |setting| + setting.expand_deploy_keys do |key| + key_offset = key.key_titles.index do |key_title| + key_title.text == title + end + + key.key_fingerprints[key_offset].text + end + end + end + + attribute :project do + Project.fabricate! do |resource| + resource.name = 'project-to-deploy' + resource.description = 'project for adding deploy key test' + end + end + + def fabricate! + project.visit! + + Page::Project::Menu.perform(&:click_repository_settings) + + Page::Project::Settings::Repository.perform do |setting| + setting.expand_deploy_keys do |page| + page.fill_key_title(title) + page.fill_key_value(key) + + page.add_key + end + end + end + end + end +end diff --git a/qa/qa/resource/deploy_token.rb b/qa/qa/resource/deploy_token.rb new file mode 100644 index 00000000000..cee4422f6b4 --- /dev/null +++ b/qa/qa/resource/deploy_token.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +module QA + module Resource + class DeployToken < Base + attr_accessor :name, :expires_at + + attribute :username do + Page::Project::Settings::Repository.perform do |page| + page.expand_deploy_tokens do |token| + token.token_username + end + end + end + + attribute :password do + Page::Project::Settings::Repository.perform do |page| + page.expand_deploy_tokens do |token| + token.token_password + end + end + end + + attribute :project do + Project.fabricate! do |resource| + resource.name = 'project-to-deploy' + resource.description = 'project for adding deploy token test' + end + end + + def fabricate! + project.visit! + + Page::Project::Menu.act do + click_repository_settings + end + + Page::Project::Settings::Repository.perform do |setting| + setting.expand_deploy_tokens do |page| + page.fill_token_name(name) + page.fill_token_expires_at(expires_at) + page.fill_scopes(read_repository: true, read_registry: false) + + page.add_token + end + end + end + end + end +end diff --git a/qa/qa/resource/file.rb b/qa/qa/resource/file.rb new file mode 100644 index 00000000000..effc5a7940b --- /dev/null +++ b/qa/qa/resource/file.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +module QA + module Resource + class File < Base + attr_accessor :name, + :content, + :commit_message + + attribute :project do + Project.fabricate! do |resource| + resource.name = 'project-with-new-file' + end + end + + def initialize + @name = 'QA Test - File name' + @content = 'QA Test - File content' + @commit_message = 'QA Test - Commit message' + end + + def fabricate! + project.visit! + + Page::Project::Show.perform(&:create_new_file!) + + Page::File::Form.perform do |page| + page.add_name(@name) + page.add_content(@content) + page.add_commit_message(@commit_message) + page.commit_changes + end + end + end + end +end diff --git a/qa/qa/resource/fork.rb b/qa/qa/resource/fork.rb new file mode 100644 index 00000000000..9fd66f3a36a --- /dev/null +++ b/qa/qa/resource/fork.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +module QA + module Resource + class Fork < Base + attribute :push do + Repository::ProjectPush.fabricate! + end + + attribute :user do + User.fabricate! do |resource| + if Runtime::Env.forker? + resource.username = Runtime::Env.forker_username + resource.password = Runtime::Env.forker_password + end + end + end + + def fabricate! + populate(:push, :user) + + # Sign out as admin and sign is as the fork user + Page::Main::Menu.perform(&:sign_out) + Runtime::Browser.visit(:gitlab, Page::Main::Login) + Page::Main::Login.perform do |login| + login.sign_in_using_credentials(user) + end + + push.project.visit! + + Page::Project::Show.perform(&:fork_project) + + Page::Project::Fork::New.perform do |fork_new| + fork_new.choose_namespace(user.name) + end + + Page::Layout::Banner.perform do |page| + page.has_notice?('The project was successfully forked.') + end + end + end + end +end diff --git a/qa/qa/resource/group.rb b/qa/qa/resource/group.rb new file mode 100644 index 00000000000..dce15e4f10b --- /dev/null +++ b/qa/qa/resource/group.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +module QA + module Resource + class Group < Base + attr_accessor :path, :description + + attribute :sandbox do + Sandbox.fabricate! + end + + attribute :id + + def initialize + @path = Runtime::Namespace.name + @description = "QA test run at #{Runtime::Namespace.time}" + end + + def fabricate! + sandbox.visit! + + Page::Group::Show.perform do |group_show| + if group_show.has_subgroup?(path) + group_show.go_to_subgroup(path) + else + group_show.go_to_new_subgroup + + Page::Group::New.perform do |group_new| + group_new.set_path(path) + group_new.set_description(description) + group_new.set_visibility('Public') + group_new.create + end + + # Ensure that the group was actually created + group_show.wait(time: 1) do + group_show.has_text?(path) && + group_show.has_new_project_or_subgroup_dropdown? + end + end + end + end + + def fabricate_via_api! + resource_web_url(api_get) + rescue ResourceNotFoundError + super + end + + def api_get_path + "/groups/#{CGI.escape("#{sandbox.path}/#{path}")}" + end + + def api_post_path + '/groups' + end + + def api_post_body + { + parent_id: sandbox.id, + path: path, + name: path, + visibility: 'public' + } + end + end + end +end diff --git a/qa/qa/resource/issue.rb b/qa/qa/resource/issue.rb new file mode 100644 index 00000000000..2c2f27fe231 --- /dev/null +++ b/qa/qa/resource/issue.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module QA + module Resource + class Issue < Base + attr_writer :description + + attribute :project do + Project.fabricate! do |resource| + resource.name = 'project-for-issues' + resource.description = 'project for adding issues' + end + end + + attribute :title + + def fabricate! + project.visit! + + Page::Project::Show.perform(&:go_to_new_issue) + + Page::Project::Issue::New.perform do |page| + page.add_title(@title) + page.add_description(@description) + page.create_new_issue + end + end + end + end +end diff --git a/qa/qa/resource/kubernetes_cluster.rb b/qa/qa/resource/kubernetes_cluster.rb new file mode 100644 index 00000000000..96c8843fb99 --- /dev/null +++ b/qa/qa/resource/kubernetes_cluster.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +require 'securerandom' + +module QA + module Resource + class KubernetesCluster < Base + attr_writer :project, :cluster, + :install_helm_tiller, :install_ingress, :install_prometheus, :install_runner + + attribute :ingress_ip do + Page::Project::Operations::Kubernetes::Show.perform(&:ingress_ip) + end + + def fabricate! + @project.visit! + + Page::Project::Menu.perform( + &:click_operations_kubernetes) + + Page::Project::Operations::Kubernetes::Index.perform( + &:add_kubernetes_cluster) + + Page::Project::Operations::Kubernetes::Add.perform( + &:add_existing_cluster) + + Page::Project::Operations::Kubernetes::AddExisting.perform do |page| + page.set_cluster_name(@cluster.cluster_name) + page.set_api_url(@cluster.api_url) + page.set_ca_certificate(@cluster.ca_certificate) + page.set_token(@cluster.token) + page.check_rbac! if @cluster.rbac + page.add_cluster! + end + + if @install_helm_tiller + Page::Project::Operations::Kubernetes::Show.perform do |page| + # We must wait a few seconds for permissions to be set up correctly for new cluster + sleep 10 + + # Helm must be installed before everything else + page.install!(:helm) + page.await_installed(:helm) + + page.install!(:ingress) if @install_ingress + page.install!(:prometheus) if @install_prometheus + page.install!(:runner) if @install_runner + + page.await_installed(:ingress) if @install_ingress + page.await_installed(:prometheus) if @install_prometheus + page.await_installed(:runner) if @install_runner + end + end + end + end + end +end diff --git a/qa/qa/resource/label.rb b/qa/qa/resource/label.rb new file mode 100644 index 00000000000..c0869cb1f2a --- /dev/null +++ b/qa/qa/resource/label.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +require 'securerandom' + +module QA + module Resource + class Label < Base + attr_accessor :description, :color + + attribute :title + + attribute :project do + Project.fabricate! do |resource| + resource.name = 'project-with-label' + end + end + + def initialize + @title = "qa-test-#{SecureRandom.hex(8)}" + @description = 'This is a test label' + @color = '#0033CC' + end + + def fabricate! + project.visit! + + Page::Project::Menu.perform(&:go_to_labels) + Page::Label::Index.perform(&:go_to_new_label) + + Page::Label::New.perform do |page| + page.fill_title(@title) + page.fill_description(@description) + page.fill_color(@color) + page.create_label + end + end + end + end +end diff --git a/qa/qa/resource/merge_request.rb b/qa/qa/resource/merge_request.rb new file mode 100644 index 00000000000..466a7942dc6 --- /dev/null +++ b/qa/qa/resource/merge_request.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +require 'securerandom' + +module QA + module Resource + class MergeRequest < Base + attr_accessor :title, + :description, + :source_branch, + :target_branch, + :assignee, + :milestone, + :labels + + attribute :project do + Project.fabricate! do |resource| + resource.name = 'project-with-merge-request' + end + end + + attribute :target do + project.visit! + + Repository::ProjectPush.fabricate! do |resource| + resource.project = project + resource.branch_name = 'master' + resource.remote_branch = target_branch + end + end + + attribute :source do + Repository::ProjectPush.fabricate! do |resource| + resource.project = project + resource.branch_name = target_branch + resource.remote_branch = source_branch + resource.new_branch = false + resource.file_name = "added_file.txt" + resource.file_content = "File Added" + end + end + + def initialize + @title = 'QA test - merge request' + @description = 'This is a test merge request' + @source_branch = "qa-test-feature-#{SecureRandom.hex(8)}" + @target_branch = "master" + @assignee = nil + @milestone = nil + @labels = [] + end + + def fabricate! + populate(:target, :source) + + project.visit! + Page::Project::Show.perform(&:new_merge_request) + Page::MergeRequest::New.perform do |page| + page.fill_title(@title) + page.fill_description(@description) + page.choose_milestone(@milestone) if @milestone + labels.each do |label| + page.select_label(label) + end + + page.create_merge_request + end + end + end + end +end diff --git a/qa/qa/resource/merge_request_from_fork.rb b/qa/qa/resource/merge_request_from_fork.rb new file mode 100644 index 00000000000..f91ae299d76 --- /dev/null +++ b/qa/qa/resource/merge_request_from_fork.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module QA + module Resource + class MergeRequestFromFork < MergeRequest + attr_accessor :fork_branch + + attribute :fork do + Fork.fabricate! + end + + attribute :push do + Repository::ProjectPush.fabricate! do |resource| + resource.project = fork + resource.branch_name = fork_branch + resource.file_name = 'file2.txt' + resource.user = fork.user + end + end + + def fabricate! + populate(:push) + + fork.visit! + + Page::Project::Show.perform(&:new_merge_request) + Page::MergeRequest::New.perform(&:create_merge_request) + end + end + end +end diff --git a/qa/qa/resource/personal_access_token.rb b/qa/qa/resource/personal_access_token.rb new file mode 100644 index 00000000000..b8dd0a3562f --- /dev/null +++ b/qa/qa/resource/personal_access_token.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module QA + module Resource + ## + # Create a personal access token that can be used by the api + # + class PersonalAccessToken < Base + attr_accessor :name + + attribute :access_token do + Page::Profile::PersonalAccessTokens.perform(&:created_access_token) + end + + def fabricate! + Page::Main::Menu.perform(&:go_to_profile_settings) + Page::Profile::Menu.perform(&:click_access_tokens) + + Page::Profile::PersonalAccessTokens.perform do |page| + page.fill_token_name(name || 'api-test-token') + page.check_api + page.create_token + end + end + end + end +end diff --git a/qa/qa/resource/project.rb b/qa/qa/resource/project.rb new file mode 100644 index 00000000000..7fdf69278f9 --- /dev/null +++ b/qa/qa/resource/project.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true + +require 'securerandom' + +module QA + module Resource + class Project < Base + attribute :name + attribute :description + + attribute :group do + Group.fabricate! + end + + attribute :repository_ssh_location do + Page::Project::Show.perform do |page| + page.choose_repository_clone_ssh + page.repository_location + end + end + + attribute :repository_http_location do + Page::Project::Show.perform do |page| + page.choose_repository_clone_http + page.repository_location + end + end + + def initialize + @description = 'My awesome project' + end + + def name=(raw_name) + @name = "#{raw_name}-#{SecureRandom.hex(8)}" + end + + def fabricate! + group.visit! + + Page::Group::Show.perform(&:go_to_new_project) + + Page::Project::New.perform do |page| + page.choose_test_namespace + page.choose_name(@name) + page.add_description(@description) + page.set_visibility('Public') + page.create_new_project + end + end + + def api_get_path + "/projects/#{name}" + end + + def api_post_path + '/projects' + end + + def api_post_body + { + namespace_id: group.id, + path: name, + name: name, + description: description, + visibility: 'public' + } + end + + private + + def transform_api_resource(api_resource) + api_resource[:repository_ssh_location] = + Git::Location.new(api_resource[:ssh_url_to_repo]) + api_resource[:repository_http_location] = + Git::Location.new(api_resource[:http_url_to_repo]) + api_resource + end + end + end +end diff --git a/qa/qa/resource/project_imported_from_github.rb b/qa/qa/resource/project_imported_from_github.rb new file mode 100644 index 00000000000..3f02fe885a9 --- /dev/null +++ b/qa/qa/resource/project_imported_from_github.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require 'securerandom' + +module QA + module Resource + class ProjectImportedFromGithub < Project + attr_accessor :name + attr_writer :personal_access_token, :github_repository_path + + attribute :group do + Group.fabricate! + end + + def fabricate! + group.visit! + + Page::Group::Show.perform(&:go_to_new_project) + + Page::Project::New.perform do |page| + page.go_to_import_project + end + + Page::Project::New.perform do |page| + page.go_to_github_import + end + + Page::Project::Import::Github.perform do |page| + page.add_personal_access_token(@personal_access_token) + page.list_repos + page.import!(@github_repository_path, @name) + end + end + end + end +end diff --git a/qa/qa/resource/project_milestone.rb b/qa/qa/resource/project_milestone.rb new file mode 100644 index 00000000000..a4d6657caff --- /dev/null +++ b/qa/qa/resource/project_milestone.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +module QA + module Resource + class ProjectMilestone < Base + attr_reader :title + attr_accessor :description + + attribute :project do + Project.fabricate! + end + + def title=(title) + @title = "#{title}-#{SecureRandom.hex(4)}" + @description = 'A milestone' + end + + def fabricate! + project.visit! + + Page::Project::Menu.perform do |page| + page.click_issues + page.click_milestones + end + + Page::Project::Milestone::Index.perform(&:click_new_milestone) + + Page::Project::Milestone::New.perform do |milestone_new| + milestone_new.set_title(@title) + milestone_new.set_description(@description) + milestone_new.create_new_milestone + end + end + end + end +end diff --git a/qa/qa/resource/repository/project_push.rb b/qa/qa/resource/repository/project_push.rb new file mode 100644 index 00000000000..c9fafe3419f --- /dev/null +++ b/qa/qa/resource/repository/project_push.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +module QA + module Resource + module Repository + class ProjectPush < Repository::Push + attribute :project do + Project.fabricate! do |resource| + resource.name = 'project-with-code' + resource.description = 'Project with repository' + end + end + + def initialize + @file_name = 'file.txt' + @file_content = '# This is test project' + @commit_message = "This is a test commit" + @branch_name = 'master' + @new_branch = true + end + + def repository_http_uri + @repository_http_uri ||= begin + project.visit! + Page::Project::Show.act do + choose_repository_clone_http + repository_location.uri + end + end + end + + def repository_ssh_uri + @repository_ssh_uri ||= begin + project.visit! + Page::Project::Show.act do + choose_repository_clone_ssh + repository_location.uri + end + end + end + end + end + end +end diff --git a/qa/qa/resource/repository/push.rb b/qa/qa/resource/repository/push.rb new file mode 100644 index 00000000000..c14d97ff7fb --- /dev/null +++ b/qa/qa/resource/repository/push.rb @@ -0,0 +1,93 @@ +# frozen_string_literal: true + +require 'pathname' + +module QA + module Resource + module Repository + class Push < Base + attr_accessor :file_name, :file_content, :commit_message, + :branch_name, :new_branch, :output, :repository_http_uri, + :repository_ssh_uri, :ssh_key, :user + + attr_writer :remote_branch + + def initialize + @file_name = 'file.txt' + @file_content = '# This is test file' + @commit_message = "This is a test commit" + @branch_name = 'master' + @new_branch = true + @repository_http_uri = "" + @ssh_key = nil + end + + def remote_branch + @remote_branch ||= branch_name + end + + def directory=(dir) + raise "Must set directory as a Pathname" unless dir.is_a?(Pathname) + + @directory = dir + end + + def files=(files) + if !files.is_a?(Array) || files.empty? + raise ArgumentError, "Please provide an array of hashes e.g.: [{name: 'file1', content: 'foo'}]" + end + + @files = files + end + + def fabricate! + Git::Repository.perform do |repository| + if ssh_key + repository.uri = repository_ssh_uri + repository.use_ssh_key(ssh_key) + else + repository.uri = repository_http_uri + repository.use_default_credentials unless user + end + + username = 'GitLab QA' + email = 'root@gitlab.com' + + if user + repository.username = user.username + repository.password = user.password + username = user.name + email = user.email + end + + repository.clone + repository.configure_identity(username, email) + + if new_branch + repository.checkout_new_branch(branch_name) + else + repository.checkout(branch_name) + end + + if @directory + @directory.each_child do |f| + repository.add_file(f.basename, f.read) if f.file? + end + elsif @files + @files.each do |f| + repository.add_file(f[:name], f[:content]) + end + else + repository.add_file(file_name, file_content) + end + + repository.commit(commit_message) + @output = repository.push_changes("#{branch_name}:#{remote_branch}") + + repository.delete_ssh_key + end + end + end + end + end +end diff --git a/qa/qa/resource/repository/wiki_push.rb b/qa/qa/resource/repository/wiki_push.rb new file mode 100644 index 00000000000..f1c39d507fe --- /dev/null +++ b/qa/qa/resource/repository/wiki_push.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +module QA + module Resource + module Repository + class WikiPush < Repository::Push + attribute :wiki do + Wiki.fabricate! do |resource| + resource.title = 'Home' + resource.content = '# My First Wiki Content' + resource.message = 'Update home' + end + end + + def initialize + @file_name = 'Home.md' + @file_content = '# Welcome to My Wiki' + @commit_message = 'Updating Home Page' + @branch_name = 'master' + @new_branch = false + end + + def repository_http_uri + @repository_http_uri ||= begin + wiki.visit! + Page::Project::Wiki::Show.act do + go_to_clone_repository + choose_repository_clone_http + repository_location.uri + end + end + end + end + end + end +end diff --git a/qa/qa/resource/runner.rb b/qa/qa/resource/runner.rb new file mode 100644 index 00000000000..08ae3f22117 --- /dev/null +++ b/qa/qa/resource/runner.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require 'securerandom' + +module QA + module Resource + class Runner < Base + attr_writer :name, :tags, :image + + attribute :project do + Project.fabricate! do |resource| + resource.name = 'project-with-ci-cd' + resource.description = 'Project with CI/CD Pipelines' + end + end + + def name + @name || "qa-runner-#{SecureRandom.hex(4)}" + end + + def tags + @tags || %w[qa e2e] + end + + def image + @image || 'gitlab/gitlab-runner:alpine' + end + + def fabricate! + project.visit! + + Page::Project::Menu.perform(&:click_ci_cd_settings) + + Service::Runner.new(name).tap do |runner| + Page::Project::Settings::CICD.perform do |settings| + settings.expand_runners_settings do |runners| + runner.pull + runner.token = runners.registration_token + runner.address = runners.coordinator_address + runner.tags = tags + runner.image = image + runner.register! + end + end + end + end + end + end +end diff --git a/qa/qa/resource/sandbox.rb b/qa/qa/resource/sandbox.rb new file mode 100644 index 00000000000..41ce857a8b8 --- /dev/null +++ b/qa/qa/resource/sandbox.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +module QA + module Resource + ## + # Ensure we're in our sandbox namespace, either by navigating to it or by + # creating it if it doesn't yet exist. + # + class Sandbox < Base + attr_reader :path + + attribute :id + + def initialize + @path = Runtime::Namespace.sandbox_name + end + + def fabricate! + Page::Main::Menu.perform(&:go_to_groups) + + Page::Dashboard::Groups.perform do |page| + if page.has_group?(path) + page.go_to_group(path) + else + page.go_to_new_group + + Page::Group::New.perform do |group| + group.set_path(path) + group.set_description('GitLab QA Sandbox Group') + group.set_visibility('Public') + group.create + end + end + end + end + + def fabricate_via_api! + resource_web_url(api_get) + rescue ResourceNotFoundError + super + end + + def api_get_path + "/groups/#{path}" + end + + def api_post_path + '/groups' + end + + def api_post_body + { + path: path, + name: path, + visibility: 'public' + } + end + end + end +end diff --git a/qa/qa/resource/settings/hashed_storage.rb b/qa/qa/resource/settings/hashed_storage.rb new file mode 100644 index 00000000000..40c06768ffe --- /dev/null +++ b/qa/qa/resource/settings/hashed_storage.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module QA + module Resource + module Settings + class HashedStorage < Base + def fabricate!(*traits) + raise ArgumentError unless traits.include?(:enabled) + + Page::Main::Login.perform(&:sign_in_using_credentials) + Page::Main::Menu.perform(&:go_to_admin_area) + Page::Admin::Menu.perform(&:go_to_repository_settings) + + Page::Admin::Settings::Repository.perform do |setting| + setting.expand_repository_storage do |page| + page.enable_hashed_storage + page.save_settings + end + end + + QA::Page::Main::Menu.perform(&:sign_out) + end + end + end + end +end diff --git a/qa/qa/resource/ssh_key.rb b/qa/qa/resource/ssh_key.rb new file mode 100644 index 00000000000..c6c97c8532f --- /dev/null +++ b/qa/qa/resource/ssh_key.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module QA + module Resource + class SSHKey < Base + extend Forwardable + + attr_accessor :title + + def_delegators :key, :private_key, :public_key, :fingerprint + + def key + @key ||= Runtime::Key::RSA.new + end + + def fabricate! + Page::Main::Menu.perform(&:go_to_profile_settings) + Page::Profile::Menu.perform(&:click_ssh_keys) + + Page::Profile::SSHKeys.perform do |page| + page.add_key(public_key, title) + end + end + end + end +end diff --git a/qa/qa/resource/user.rb b/qa/qa/resource/user.rb new file mode 100644 index 00000000000..16f0b311fa9 --- /dev/null +++ b/qa/qa/resource/user.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true + +require 'securerandom' + +module QA + module Resource + class User < Base + attr_reader :unique_id + attr_writer :username, :password + + def initialize + @unique_id = SecureRandom.hex(8) + end + + def username + @username ||= "qa-user-#{unique_id}" + end + + def password + @password ||= 'password' + end + + def name + @name ||= username + end + + def email + @email ||= "#{username}@example.com" + end + + def credentials_given? + defined?(@username) && defined?(@password) + end + + def fabricate! + # Don't try to log-out if we're not logged-in + if Page::Main::Menu.perform { |p| p.has_personal_area?(wait: 0) } + Page::Main::Menu.perform { |main| main.sign_out } + end + + if credentials_given? + Page::Main::Login.perform do |login| + login.sign_in_using_credentials(self) + end + else + Page::Main::Login.perform do |login| + login.switch_to_register_tab + end + Page::Main::SignUp.perform do |signup| + signup.sign_up!(self) + end + end + end + + def fabricate_via_api! + resource_web_url(api_get) + rescue ResourceNotFoundError + super + end + + def api_get_path + "/users/#{fetch_id(username)}" + end + + def api_post_path + '/users' + end + + def api_post_body + { + email: email, + password: password, + username: username, + name: name, + skip_confirmation: true + } + end + + private + + def fetch_id(username) + users = parse_body(api_get_from("/users?username=#{username}")) + + unless users.size == 1 && users.first[:username] == username + raise ResourceNotFoundError, "Expected one user with username #{username} but found: `#{users}`." + end + + users.first[:id] + end + end + end +end diff --git a/qa/qa/resource/wiki.rb b/qa/qa/resource/wiki.rb new file mode 100644 index 00000000000..e942e9718a0 --- /dev/null +++ b/qa/qa/resource/wiki.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module QA + module Resource + class Wiki < Base + attr_accessor :title, :content, :message + + attribute :project do + Project.fabricate! do |resource| + resource.name = 'project-for-wikis' + resource.description = 'project for adding wikis' + end + end + + def fabricate! + project.visit! + + Page::Project::Menu.perform { |menu_side| menu_side.click_wiki } + + Page::Project::Wiki::New.perform do |wiki_new| + wiki_new.go_to_create_first_page + wiki_new.set_title(@title) + wiki_new.set_content(@content) + wiki_new.set_message(@message) + wiki_new.create_new_page + end + end + end + end +end diff --git a/qa/qa/runtime/api/client.rb b/qa/qa/runtime/api/client.rb index 0545b500e4c..aff84c89f0e 100644 --- a/qa/qa/runtime/api/client.rb +++ b/qa/qa/runtime/api/client.rb @@ -32,7 +32,7 @@ module QA def do_create_personal_access_token Page::Main::Login.act { sign_in_using_credentials } - Factory::Resource::PersonalAccessToken.fabricate!.access_token + Resource::PersonalAccessToken.fabricate!.access_token end end end diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb index 4f960ee26a9..7d5fc3c4b65 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb @@ -5,7 +5,7 @@ module QA it 'user registers and logs in' do Runtime::Browser.visit(:gitlab, Page::Main::Login) - Factory::Resource::User.fabricate_via_browser_ui! + Resource::User.fabricate! # TODO, since `Signed in successfully` message was removed # this is the only way to tell if user is signed in correctly. diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb index 2cd5bf01c1f..bef89d5be24 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb @@ -7,9 +7,9 @@ module QA Runtime::Browser.visit(:gitlab, Page::Main::Login) Page::Main::Login.perform(&:sign_in_using_credentials) - user = Factory::Resource::User.fabricate! + user = Resource::User.fabricate! - project = Factory::Resource::Project.fabricate! do |resource| + project = Resource::Project.fabricate! do |resource| resource.name = 'add-member-project' end project.visit! diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb index a242f2158da..6632c2977ef 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb @@ -7,7 +7,7 @@ module QA Runtime::Browser.visit(:gitlab, Page::Main::Login) Page::Main::Login.act { sign_in_using_credentials } - created_project = Factory::Resource::Project.fabricate_via_browser_ui! do |project| + created_project = Resource::Project.fabricate_via_browser_ui! do |project| project.name = 'awesome-project' project.description = 'create awesome project test' end diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb index a99b0522e73..3ce48de2c25 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb @@ -4,7 +4,7 @@ module QA context 'Manage', :orchestrated, :github do describe 'Project import from GitHub' do let(:imported_project) do - Factory::Resource::ProjectImportedFromGithub.fabricate! do |project| + Resource::ProjectImportedFromGithub.fabricate! do |project| project.name = 'imported-project' project.personal_access_token = Runtime::Env.github_access_token project.github_repository_path = 'gitlab-qa/test-project' diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb index 768d40f3acf..275de3d332c 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb @@ -7,7 +7,7 @@ module QA Runtime::Browser.visit(:gitlab, Page::Main::Login) Page::Main::Login.act { sign_in_using_credentials } - Factory::Repository::ProjectPush.fabricate! do |push| + Resource::Repository::ProjectPush.fabricate! do |push| push.file_name = 'README.md' push.file_content = '# This is a test project' push.commit_message = 'Add README.md' diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb index e67561b3a39..f5002c8032f 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb @@ -9,7 +9,7 @@ module QA Runtime::Browser.visit(:gitlab, Page::Main::Login) Page::Main::Login.act { sign_in_using_credentials } - Factory::Resource::Issue.fabricate! do |issue| + Resource::Issue.fabricate! do |issue| issue.title = issue_title end end diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb index 24877d937d2..83603f1cda7 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb @@ -9,7 +9,7 @@ module QA Runtime::Browser.visit(:gitlab, Page::Main::Login) Page::Main::Login.act { sign_in_using_credentials } - Factory::Resource::Issue.fabricate! do |issue| + Resource::Issue.fabricate! do |issue| issue.title = issue_title end diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb index 037ff5efbd4..d33947f41da 100644 --- a/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb @@ -7,22 +7,22 @@ module QA Runtime::Browser.visit(:gitlab, Page::Main::Login) Page::Main::Login.act { sign_in_using_credentials } - current_project = Factory::Resource::Project.fabricate! do |project| + current_project = Resource::Project.fabricate! do |project| project.name = 'project-with-merge-request-and-milestone' end - current_milestone = Factory::Resource::ProjectMilestone.fabricate! do |milestone| + current_milestone = Resource::ProjectMilestone.fabricate! do |milestone| milestone.title = 'unique-milestone' milestone.project = current_project end - new_label = Factory::Resource::Label.fabricate! do |label| + new_label = Resource::Label.fabricate! do |label| label.project = current_project label.title = 'qa-mr-test-label' label.description = 'Merge Request label' end - Factory::Resource::MergeRequest.fabricate! do |merge_request| + Resource::MergeRequest.fabricate! do |merge_request| merge_request.title = 'This is a merge request with a milestone' merge_request.description = 'Great feature with milestone' merge_request.project = current_project @@ -49,11 +49,11 @@ module QA Runtime::Browser.visit(:gitlab, Page::Main::Login) Page::Main::Login.act { sign_in_using_credentials } - current_project = Factory::Resource::Project.fabricate! do |project| + current_project = Resource::Project.fabricate! do |project| project.name = 'project-with-merge-request' end - Factory::Resource::MergeRequest.fabricate! do |merge_request| + Resource::MergeRequest.fabricate! do |merge_request| merge_request.title = 'This is a merge request' merge_request.description = 'Great feature' merge_request.project = current_project diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb index 058af8aebdd..6dcd74471fe 100644 --- a/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb @@ -7,7 +7,7 @@ module QA Runtime::Browser.visit(:gitlab, Page::Main::Login) Page::Main::Login.act { sign_in_using_credentials } - merge_request = Factory::Resource::MergeRequestFromFork.fabricate! do |merge_request| + merge_request = Resource::MergeRequestFromFork.fabricate! do |merge_request| merge_request.fork_branch = 'feature-branch' end diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb index 3bcf086d332..e2d639fd150 100644 --- a/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb @@ -7,7 +7,7 @@ module QA Runtime::Browser.visit(:gitlab, Page::Main::Login) Page::Main::Login.act { sign_in_using_credentials } - project = Factory::Resource::Project.fabricate! do |project| + project = Resource::Project.fabricate! do |project| project.name = "only-fast-forward" end project.visit! @@ -15,12 +15,12 @@ module QA Page::Project::Menu.act { go_to_settings } Page::Project::Settings::MergeRequest.act { enable_ff_only } - merge_request = Factory::Resource::MergeRequest.fabricate! do |merge_request| + merge_request = Resource::MergeRequest.fabricate! do |merge_request| merge_request.project = project merge_request.title = 'Needs rebasing' end - Factory::Repository::ProjectPush.fabricate! do |push| + Resource::Repository::ProjectPush.fabricate! do |push| push.project = project push.file_name = "other.txt" push.file_content = "New file added!" diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb index 724c48cd125..6ff7360c413 100644 --- a/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb @@ -7,16 +7,16 @@ module QA Runtime::Browser.visit(:gitlab, Page::Main::Login) Page::Main::Login.act { sign_in_using_credentials } - project = Factory::Resource::Project.fabricate! do |project| + project = Resource::Project.fabricate! do |project| project.name = "squash-before-merge" end - merge_request = Factory::Resource::MergeRequest.fabricate! do |merge_request| + merge_request = Resource::MergeRequest.fabricate! do |merge_request| merge_request.project = project merge_request.title = 'Squashing commits' end - Factory::Repository::ProjectPush.fabricate! do |push| + Resource::Repository::ProjectPush.fabricate! do |push| push.project = project push.commit_message = 'to be squashed' push.branch_name = merge_request.source_branch diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb index 7705e12b95e..297485dd81e 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb @@ -13,7 +13,7 @@ module QA before(:all) do login - @project = Factory::Resource::Project.fabricate! do |project| + @project = Resource::Project.fabricate! do |project| project.name = 'file-template-project' project.description = 'Add file templates via the Files view' end diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb index df70b9608d9..94be66782c6 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb @@ -9,7 +9,7 @@ module QA Runtime::Browser.visit(:gitlab, Page::Main::Login) Page::Main::Login.act { sign_in_using_credentials } - key = Factory::Resource::SSHKey.fabricate! do |resource| + key = Resource::SSHKey.fabricate! do |resource| resource.title = key_title end diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/clone_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/clone_spec.rb index b18dee53cbc..6a0add56fe0 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/clone_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/clone_spec.rb @@ -14,7 +14,7 @@ module QA Runtime::Browser.visit(:gitlab, Page::Main::Login) Page::Main::Login.act { sign_in_using_credentials } - project = Factory::Resource::Project.fabricate! do |scenario| + project = Resource::Project.fabricate! do |scenario| scenario.name = 'project-with-code' scenario.description = 'project for git clone tests' end diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb index f65a1569fb0..46346d1b984 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb @@ -12,7 +12,7 @@ module QA file_content = 'QA Test - File content' commit_message_for_create = 'QA Test - Create new file' - Factory::Resource::File.fabricate! do |file| + Resource::File.fabricate! do |file| file.name = file_name file.content = file_content file.commit_message = commit_message_for_create diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_http_private_token_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_http_private_token_spec.rb index 8e4210482a2..a63b7dce8d6 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/push_http_private_token_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_http_private_token_spec.rb @@ -7,14 +7,14 @@ module QA Runtime::Browser.visit(:gitlab, Page::Main::Login) Page::Main::Login.perform(&:sign_in_using_credentials) - access_token = Factory::Resource::PersonalAccessToken.fabricate!.access_token + access_token = Resource::PersonalAccessToken.fabricate!.access_token - user = Factory::Resource::User.new.tap do |user| + user = Resource::User.new.tap do |user| user.username = Runtime::User.username user.password = access_token end - push = Factory::Repository::ProjectPush.fabricate! do |push| + push = Resource::Repository::ProjectPush.fabricate! do |push| push.user = user push.file_name = 'README.md' push.file_content = '# This is a test project' diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb index 2f63a07e0c3..92f596a44d9 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb @@ -7,7 +7,7 @@ module QA Runtime::Browser.visit(:gitlab, Page::Main::Login) Page::Main::Login.act { sign_in_using_credentials } - Factory::Repository::ProjectPush.fabricate! do |push| + Resource::Repository::ProjectPush.fabricate! do |push| push.file_name = 'README.md' push.file_content = '# This is a test project' push.commit_message = 'Add README.md' diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb index ac71cf52b6f..73a3dc14a65 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb @@ -6,7 +6,7 @@ module QA let(:branch_name) { 'protected-branch' } let(:commit_message) { 'Protected push commit message' } let(:project) do - Factory::Resource::Project.fabricate! do |resource| + Resource::Project.fabricate! do |resource| resource.name = 'protected-branch-project' end end @@ -47,7 +47,7 @@ module QA end def create_protected_branch(allow_to_push:) - Factory::Resource::Branch.fabricate! do |resource| + Resource::Branch.fabricate! do |resource| resource.branch_name = branch_name resource.project = project resource.allow_to_push = allow_to_push @@ -56,7 +56,7 @@ module QA end def push_new_file(branch) - Factory::Repository::ProjectPush.fabricate! do |resource| + Resource::Repository::ProjectPush.fabricate! do |resource| resource.project = project resource.file_name = 'new_file.md' resource.file_content = '# This is a new file' diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/use_ssh_key_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/use_ssh_key_spec.rb index 36068ffba69..9c764424129 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/use_ssh_key_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/use_ssh_key_spec.rb @@ -12,11 +12,11 @@ module QA Runtime::Browser.visit(:gitlab, Page::Main::Login) Page::Main::Login.act { sign_in_using_credentials } - key = Factory::Resource::SSHKey.fabricate! do |resource| + key = Resource::SSHKey.fabricate! do |resource| resource.title = key_title end - Factory::Repository::ProjectPush.fabricate! do |push| + Resource::Repository::ProjectPush.fabricate! do |push| push.ssh_key = key push.file_name = 'README.md' push.file_content = '# Test Use SSH Key' diff --git a/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb b/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb index 07dbf39a8a3..e7374377104 100644 --- a/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb @@ -13,7 +13,7 @@ module QA before(:all) do login - @project = Factory::Resource::Project.fabricate! do |project| + @project = Resource::Project.fabricate! do |project| project.name = 'file-template-project' project.description = 'Add file templates via the Web IDE' end diff --git a/qa/qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb b/qa/qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb index 4126fd9fd3e..210271705d9 100644 --- a/qa/qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb @@ -18,7 +18,7 @@ module QA end it 'user creates, edits, clones, and pushes to the wiki' do - wiki = Factory::Resource::Wiki.fabricate! do |resource| + wiki = Resource::Wiki.fabricate! do |resource| resource.title = 'Home' resource.content = '# My First Wiki Content' resource.message = 'Update home' @@ -34,7 +34,7 @@ module QA validate_content('My Second Wiki Content') - Factory::Repository::WikiPush.fabricate! do |push| + Resource::Repository::WikiPush.fabricate! do |push| push.wiki = wiki push.file_name = 'Home.md' push.file_content = '# My Third Wiki Content' diff --git a/qa/qa/specs/features/browser_ui/4_verify/ci_variable/add_ci_variable_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/ci_variable/add_ci_variable_spec.rb index 58b272adcf1..0837b720df1 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/ci_variable/add_ci_variable_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/ci_variable/add_ci_variable_spec.rb @@ -7,7 +7,7 @@ module QA Runtime::Browser.visit(:gitlab, Page::Main::Login) Page::Main::Login.act { sign_in_using_credentials } - Factory::Resource::CiVariable.fabricate! do |resource| + Resource::CiVariable.fabricate! do |resource| resource.key = 'VARIABLE_KEY' resource.value = 'some CI variable' end diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb index d66bcce879b..25cbe41c684 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb @@ -13,18 +13,18 @@ module QA Runtime::Browser.visit(:gitlab, Page::Main::Login) Page::Main::Login.act { sign_in_using_credentials } - project = Factory::Resource::Project.fabricate! do |project| + project = Resource::Project.fabricate! do |project| project.name = 'project-with-pipelines' project.description = 'Project with CI/CD Pipelines.' end - Factory::Resource::Runner.fabricate! do |runner| + Resource::Runner.fabricate! do |runner| runner.project = project runner.name = executor runner.tags = %w[qa test] end - Factory::Repository::ProjectPush.fabricate! do |push| + Resource::Repository::ProjectPush.fabricate! do |push| push.project = project push.file_name = '.gitlab-ci.yml' push.commit_message = 'Add .gitlab-ci.yml' diff --git a/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb index 5d9aa00582f..3af7db751e7 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb @@ -13,7 +13,7 @@ module QA Runtime::Browser.visit(:gitlab, Page::Main::Login) Page::Main::Login.act { sign_in_using_credentials } - Factory::Resource::Runner.fabricate! do |runner| + Resource::Runner.fabricate! do |runner| runner.name = executor end diff --git a/qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb b/qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb index 64b98da8bf5..84757f25379 100644 --- a/qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb +++ b/qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb @@ -11,7 +11,7 @@ module QA deploy_key_title = 'deploy key title' deploy_key_value = key.public_key - deploy_key = Factory::Resource::DeployKey.fabricate! do |resource| + deploy_key = Resource::DeployKey.fabricate! do |resource| resource.title = deploy_key_title resource.key = deploy_key_value end diff --git a/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb b/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb index 604641e54b8..e2320c92343 100644 --- a/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb +++ b/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb @@ -15,13 +15,13 @@ module QA @runner_name = "qa-runner-#{Time.now.to_i}" - @project = Factory::Resource::Project.fabricate! do |resource| + @project = Resource::Project.fabricate! do |resource| resource.name = 'deploy-key-clone-project' end @repository_location = @project.repository_ssh_location - Factory::Resource::Runner.fabricate! do |resource| + Resource::Runner.fabricate! do |resource| resource.project = @project resource.name = @runner_name resource.tags = %w[qa docker] @@ -47,7 +47,7 @@ module QA login - Factory::Resource::DeployKey.fabricate! do |resource| + Resource::DeployKey.fabricate! do |resource| resource.project = @project resource.title = "deploy key #{key.name}(#{key.bits})" resource.key = key.public_key @@ -55,7 +55,7 @@ module QA deploy_key_name = "DEPLOY_KEY_#{key.name}_#{key.bits}" - Factory::Resource::CiVariable.fabricate! do |resource| + Resource::CiVariable.fabricate! do |resource| resource.project = @project resource.key = deploy_key_name resource.value = key.private_key @@ -78,7 +78,7 @@ module QA - docker YAML - Factory::Repository::ProjectPush.fabricate! do |resource| + Resource::Repository::ProjectPush.fabricate! do |resource| resource.project = @project resource.file_name = '.gitlab-ci.yml' resource.commit_message = 'Add .gitlab-ci.yml' diff --git a/qa/qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb b/qa/qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb index 263ba6a6800..9f34e4218c1 100644 --- a/qa/qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb +++ b/qa/qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb @@ -10,7 +10,7 @@ module QA deploy_token_name = 'deploy token name' deploy_token_expires_at = Date.today + 7 # 1 Week from now - deploy_token = Factory::Resource::DeployToken.fabricate! do |resource| + deploy_token = Resource::DeployToken.fabricate! do |resource| resource.name = deploy_token_name resource.expires_at = deploy_token_expires_at end diff --git a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb index c2fce1e7df1..30ec0665973 100644 --- a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb +++ b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb @@ -15,21 +15,21 @@ module QA Runtime::Browser.visit(:gitlab, Page::Main::Login) Page::Main::Login.act { sign_in_using_credentials } - project = Factory::Resource::Project.fabricate! do |p| + project = Resource::Project.fabricate! do |p| p.name = 'project-with-autodevops' p.description = 'Project with Auto Devops' end # Disable code_quality check in Auto DevOps pipeline as it takes # too long and times out the test - Factory::Resource::CiVariable.fabricate! do |resource| + Resource::CiVariable.fabricate! do |resource| resource.project = project resource.key = 'CODE_QUALITY_DISABLED' resource.value = '1' end # Create Auto Devops compatible repo - Factory::Repository::ProjectPush.fabricate! do |push| + Resource::Repository::ProjectPush.fabricate! do |push| push.project = project push.directory = Pathname .new(__dir__) @@ -41,7 +41,7 @@ module QA # Create and connect K8s cluster @cluster = Service::KubernetesCluster.new(rbac: rbac).create! - kubernetes_cluster = Factory::Resource::KubernetesCluster.fabricate! do |cluster| + kubernetes_cluster = Resource::KubernetesCluster.fabricate! do |cluster| cluster.project = project cluster.cluster = @cluster cluster.install_helm_tiller = true diff --git a/qa/spec/factory/api_fabricator_spec.rb b/qa/spec/factory/api_fabricator_spec.rb deleted file mode 100644 index e5fbc064911..00000000000 --- a/qa/spec/factory/api_fabricator_spec.rb +++ /dev/null @@ -1,161 +0,0 @@ -# frozen_string_literal: true - -describe QA::Factory::ApiFabricator do - let(:factory_without_api_support) do - Class.new do - def self.name - 'FooBarFactory' - end - end - end - - let(:factory_with_api_support) do - Class.new do - def self.name - 'FooBarFactory' - end - - def api_get_path - '/foo' - end - - def api_post_path - '/bar' - end - - def api_post_body - { name: 'John Doe' } - end - end - end - - before do - allow(subject).to receive(:current_url).and_return('') - end - - subject { factory.tap { |f| f.include(described_class) }.new } - - describe '#api_support?' do - let(:api_client) { spy('Runtime::API::Client') } - let(:api_client_instance) { double('API Client') } - - context 'when factory does not support fabrication via the API' do - let(:factory) { factory_without_api_support } - - it 'returns false' do - expect(subject).not_to be_api_support - end - end - - context 'when factory supports fabrication via the API' do - let(:factory) { factory_with_api_support } - - it 'returns false' do - expect(subject).to be_api_support - end - end - end - - describe '#fabricate_via_api!' do - let(:api_client) { spy('Runtime::API::Client') } - let(:api_client_instance) { double('API Client') } - - before do - stub_const('QA::Runtime::API::Client', api_client) - - allow(api_client).to receive(:new).and_return(api_client_instance) - allow(api_client_instance).to receive(:personal_access_token).and_return('foo') - end - - context 'when factory does not support fabrication via the API' do - let(:factory) { factory_without_api_support } - - it 'raises a NotImplementedError exception' do - expect { subject.fabricate_via_api! }.to raise_error(NotImplementedError, "Factory FooBarFactory does not support fabrication via the API!") - end - end - - context 'when factory supports fabrication via the API' do - let(:factory) { factory_with_api_support } - let(:api_request) { spy('Runtime::API::Request') } - let(:resource_web_url) { 'http://example.org/api/v4/foo' } - let(:resource) { { id: 1, name: 'John Doe', web_url: resource_web_url } } - let(:raw_post) { double('Raw POST response', code: 201, body: resource.to_json) } - - before do - stub_const('QA::Runtime::API::Request', api_request) - - allow(api_request).to receive(:new).and_return(double(url: resource_web_url)) - end - - context 'when creating a resource' do - before do - allow(subject).to receive(:post).with(resource_web_url, subject.api_post_body).and_return(raw_post) - end - - it 'returns the resource URL' do - expect(api_request).to receive(:new).with(api_client_instance, subject.api_post_path).and_return(double(url: resource_web_url)) - expect(subject).to receive(:post).with(resource_web_url, subject.api_post_body).and_return(raw_post) - - expect(subject.fabricate_via_api!).to eq(resource_web_url) - end - - it 'populates api_resource with the resource' do - subject.fabricate_via_api! - - expect(subject.api_resource).to eq(resource) - end - - context 'when the POST fails' do - let(:post_response) { { error: "Name already taken." } } - let(:raw_post) { double('Raw POST response', code: 400, body: post_response.to_json) } - - it 'raises a ResourceFabricationFailedError exception' do - expect(api_request).to receive(:new).with(api_client_instance, subject.api_post_path).and_return(double(url: resource_web_url)) - expect(subject).to receive(:post).with(resource_web_url, subject.api_post_body).and_return(raw_post) - - expect { subject.fabricate_via_api! }.to raise_error(described_class::ResourceFabricationFailedError, "Fabrication of FooBarFactory using the API failed (400) with `#{raw_post}`.") - expect(subject.api_resource).to be_nil - end - end - end - - context '#transform_api_resource' do - let(:factory) do - Class.new do - def self.name - 'FooBarFactory' - end - - def api_get_path - '/foo' - end - - def api_post_path - '/bar' - end - - def api_post_body - { name: 'John Doe' } - end - - def transform_api_resource(resource) - resource[:new] = 'foobar' - resource - end - end - end - - let(:resource) { { existing: 'foo', web_url: resource_web_url } } - let(:transformed_resource) { { existing: 'foo', new: 'foobar', web_url: resource_web_url } } - - it 'transforms the resource' do - expect(subject).to receive(:post).with(resource_web_url, subject.api_post_body).and_return(raw_post) - expect(subject).to receive(:transform_api_resource).with(resource).and_return(transformed_resource) - - subject.fabricate_via_api! - end - end - end - end -end diff --git a/qa/spec/factory/base_spec.rb b/qa/spec/factory/base_spec.rb deleted file mode 100644 index 990eba76460..00000000000 --- a/qa/spec/factory/base_spec.rb +++ /dev/null @@ -1,246 +0,0 @@ -# frozen_string_literal: true - -describe QA::Factory::Base do - include Support::StubENV - - let(:factory) { spy('factory') } - let(:location) { 'http://location' } - - shared_context 'fabrication context' do - subject do - Class.new(described_class) do - def self.name - 'MyFactory' - end - end - end - - before do - allow(subject).to receive(:current_url).and_return(location) - allow(subject).to receive(:new).and_return(factory) - end - end - - shared_examples 'fabrication method' do |fabrication_method_called, actual_fabrication_method = nil| - let(:fabrication_method_used) { actual_fabrication_method || fabrication_method_called } - - it 'yields factory before calling factory method' do - expect(factory).to receive(:something!).ordered - expect(factory).to receive(fabrication_method_used).ordered.and_return(location) - - subject.public_send(fabrication_method_called, factory: factory) do |factory| - factory.something! - end - end - - it 'does not log the factory and build method when QA_DEBUG=false' do - stub_env('QA_DEBUG', 'false') - expect(factory).to receive(fabrication_method_used).and_return(location) - - expect { subject.public_send(fabrication_method_called, 'something', factory: factory) } - .not_to output.to_stdout - end - end - - describe '.fabricate!' do - context 'when factory does not support fabrication via the API' do - before do - expect(described_class).to receive(:fabricate_via_api!).and_raise(NotImplementedError) - end - - it 'calls .fabricate_via_browser_ui!' do - expect(described_class).to receive(:fabricate_via_browser_ui!) - - described_class.fabricate! - end - end - - context 'when factory supports fabrication via the API' do - it 'calls .fabricate_via_browser_ui!' do - expect(described_class).to receive(:fabricate_via_api!) - - described_class.fabricate! - end - end - end - - describe '.fabricate_via_api!' do - include_context 'fabrication context' - - it_behaves_like 'fabrication method', :fabricate_via_api! - - it 'instantiates the factory, calls factory method returns the resource' do - expect(factory).to receive(:fabricate_via_api!).and_return(location) - - result = subject.fabricate_via_api!(factory: factory, parents: []) - - expect(result).to eq(factory) - end - - it 'logs the factory and build method when QA_DEBUG=true' do - stub_env('QA_DEBUG', 'true') - expect(factory).to receive(:fabricate_via_api!).and_return(location) - - expect { subject.fabricate_via_api!('something', factory: factory, parents: []) } - .to output(/==> Built a MyFactory via api in [\d\.\-e]+ seconds+/) - .to_stdout - end - end - - describe '.fabricate_via_browser_ui!' do - include_context 'fabrication context' - - it_behaves_like 'fabrication method', :fabricate_via_browser_ui!, :fabricate! - - it 'instantiates the factory and calls factory method' do - subject.fabricate_via_browser_ui!('something', factory: factory, parents: []) - - expect(factory).to have_received(:fabricate!).with('something') - end - - it 'returns fabrication resource' do - result = subject.fabricate_via_browser_ui!('something', factory: factory, parents: []) - - expect(result).to eq(factory) - end - - it 'logs the factory and build method when QA_DEBUG=true' do - stub_env('QA_DEBUG', 'true') - - expect { subject.fabricate_via_browser_ui!('something', factory: factory, parents: []) } - .to output(/==> Built a MyFactory via browser_ui in [\d\.\-e]+ seconds+/) - .to_stdout - end - end - - shared_context 'simple factory' do - subject do - Class.new(QA::Factory::Base) do - attribute :test do - 'block' - end - - attribute :no_block - - def fabricate! - 'any' - end - - def self.current_url - 'http://stub' - end - end - end - - let(:factory) { subject.new } - end - - describe '.attribute' do - include_context 'simple factory' - - it 'appends new attribute' do - expect(subject.attributes_names).to eq([:no_block, :test, :web_url]) - end - - context 'when the attribute is populated via a block' do - it 'returns value from the block' do - result = subject.fabricate!(factory: factory) - - expect(result).to be_a(described_class) - expect(result.test).to eq('block') - end - end - - context 'when the attribute is populated via the api' do - let(:api_resource) { { no_block: 'api' } } - - before do - expect(factory).to receive(:api_resource).and_return(api_resource) - end - - it 'returns value from api' do - result = subject.fabricate!(factory: factory) - - expect(result).to be_a(described_class) - expect(result.no_block).to eq('api') - end - - context 'when the attribute also has a block' do - let(:api_resource) { { test: 'api_with_block' } } - - before do - allow(QA::Runtime::Logger).to receive(:info) - end - - it 'returns value from api and emits an INFO log entry' do - result = subject.fabricate!(factory: factory) - - expect(result).to be_a(described_class) - expect(result.test).to eq('api_with_block') - expect(QA::Runtime::Logger) - .to have_received(:info).with(/api_with_block/) - end - end - end - - context 'when the attribute is populated via direct assignment' do - before do - factory.test = 'value' - end - - it 'returns value from the assignment' do - result = subject.fabricate!(factory: factory) - - expect(result).to be_a(described_class) - expect(result.test).to eq('value') - end - - context 'when the api also has such response' do - before do - allow(factory).to receive(:api_resource).and_return({ test: 'api' }) - end - - it 'returns value from the assignment' do - result = subject.fabricate!(factory: factory) - - expect(result).to be_a(described_class) - expect(result.test).to eq('value') - end - end - end - - context 'when the attribute has no value' do - it 'raises an error because no values could be found' do - result = subject.fabricate!(factory: factory) - - expect { result.no_block } - .to raise_error(described_class::NoValueError, "No value was computed for no_block of #{factory.class.name}.") - end - end - end - - describe '#web_url' do - include_context 'simple factory' - - it 'sets #web_url to #current_url after fabrication' do - subject.fabricate!(factory: factory) - - expect(factory.web_url).to eq(subject.current_url) - end - end - - describe '#visit!' do - include_context 'simple factory' - - before do - allow(factory).to receive(:visit) - end - - it 'calls #visit with the underlying #web_url' do - factory.web_url = subject.current_url - factory.visit! - - expect(factory).to have_received(:visit).with(subject.current_url) - end - end -end diff --git a/qa/spec/factory/repository/push_spec.rb b/qa/spec/factory/repository/push_spec.rb deleted file mode 100644 index 2eb6c008248..00000000000 --- a/qa/spec/factory/repository/push_spec.rb +++ /dev/null @@ -1,26 +0,0 @@ -# frozen_string_literal: true - -describe QA::Factory::Repository::Push do - describe '.files=' do - let(:files) do - [ - { - name: 'file.txt', - content: 'foo' - } - ] - end - - it 'raises an error if files is not an array' do - expect { subject.files = '' }.to raise_error(ArgumentError) - end - - it 'raises an error if files is an empty array' do - expect { subject.files = [] }.to raise_error(ArgumentError) - end - - it 'does not raise if files is an array' do - expect { subject.files = files }.not_to raise_error - end - end -end diff --git a/qa/spec/factory/resource/user_spec.rb b/qa/spec/factory/resource/user_spec.rb index 2f6c59b3e69..820c506b715 100644 --- a/qa/spec/factory/resource/user_spec.rb +++ b/qa/spec/factory/resource/user_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -describe QA::Factory::Resource::User do +describe QA::Resource::User do describe "#fabricate_via_api!" do Response = Struct.new(:code, :body) diff --git a/qa/spec/resource/api_fabricator_spec.rb b/qa/spec/resource/api_fabricator_spec.rb new file mode 100644 index 00000000000..a5ed4422f6e --- /dev/null +++ b/qa/spec/resource/api_fabricator_spec.rb @@ -0,0 +1,161 @@ +# frozen_string_literal: true + +describe QA::Resource::ApiFabricator do + let(:resource_without_api_support) do + Class.new do + def self.name + 'FooBarResource' + end + end + end + + let(:resource_with_api_support) do + Class.new do + def self.name + 'FooBarResource' + end + + def api_get_path + '/foo' + end + + def api_post_path + '/bar' + end + + def api_post_body + { name: 'John Doe' } + end + end + end + + before do + allow(subject).to receive(:current_url).and_return('') + end + + subject { resource.tap { |f| f.include(described_class) }.new } + + describe '#api_support?' do + let(:api_client) { spy('Runtime::API::Client') } + let(:api_client_instance) { double('API Client') } + + context 'when resource does not support fabrication via the API' do + let(:resource) { resource_without_api_support } + + it 'returns false' do + expect(subject).not_to be_api_support + end + end + + context 'when resource supports fabrication via the API' do + let(:resource) { resource_with_api_support } + + it 'returns false' do + expect(subject).to be_api_support + end + end + end + + describe '#fabricate_via_api!' do + let(:api_client) { spy('Runtime::API::Client') } + let(:api_client_instance) { double('API Client') } + + before do + stub_const('QA::Runtime::API::Client', api_client) + + allow(api_client).to receive(:new).and_return(api_client_instance) + allow(api_client_instance).to receive(:personal_access_token).and_return('foo') + end + + context 'when resource does not support fabrication via the API' do + let(:resource) { resource_without_api_support } + + it 'raises a NotImplementedError exception' do + expect { subject.fabricate_via_api! }.to raise_error(NotImplementedError, "Resource FooBarResource does not support fabrication via the API!") + end + end + + context 'when resource supports fabrication via the API' do + let(:resource) { resource_with_api_support } + let(:api_request) { spy('Runtime::API::Request') } + let(:resource_web_url) { 'http://example.org/api/v4/foo' } + let(:response) { { id: 1, name: 'John Doe', web_url: resource_web_url } } + let(:raw_post) { double('Raw POST response', code: 201, body: response.to_json) } + + before do + stub_const('QA::Runtime::API::Request', api_request) + + allow(api_request).to receive(:new).and_return(double(url: resource_web_url)) + end + + context 'when creating a resource' do + before do + allow(subject).to receive(:post).with(resource_web_url, subject.api_post_body).and_return(raw_post) + end + + it 'returns the resource URL' do + expect(api_request).to receive(:new).with(api_client_instance, subject.api_post_path).and_return(double(url: resource_web_url)) + expect(subject).to receive(:post).with(resource_web_url, subject.api_post_body).and_return(raw_post) + + expect(subject.fabricate_via_api!).to eq(resource_web_url) + end + + it 'populates api_resource with the resource' do + subject.fabricate_via_api! + + expect(subject.api_resource).to eq(response) + end + + context 'when the POST fails' do + let(:post_response) { { error: "Name already taken." } } + let(:raw_post) { double('Raw POST response', code: 400, body: post_response.to_json) } + + it 'raises a ResourceFabricationFailedError exception' do + expect(api_request).to receive(:new).with(api_client_instance, subject.api_post_path).and_return(double(url: resource_web_url)) + expect(subject).to receive(:post).with(resource_web_url, subject.api_post_body).and_return(raw_post) + + expect { subject.fabricate_via_api! }.to raise_error(described_class::ResourceFabricationFailedError, "Fabrication of FooBarResource using the API failed (400) with `#{raw_post}`.") + expect(subject.api_resource).to be_nil + end + end + end + + context '#transform_api_resource' do + let(:resource) do + Class.new do + def self.name + 'FooBarResource' + end + + def api_get_path + '/foo' + end + + def api_post_path + '/bar' + end + + def api_post_body + { name: 'John Doe' } + end + + def transform_api_resource(resource) + resource[:new] = 'foobar' + resource + end + end + end + + let(:response) { { existing: 'foo', web_url: resource_web_url } } + let(:transformed_resource) { { existing: 'foo', new: 'foobar', web_url: resource_web_url } } + + it 'transforms the resource' do + expect(subject).to receive(:post).with(resource_web_url, subject.api_post_body).and_return(raw_post) + expect(subject).to receive(:transform_api_resource).with(response).and_return(transformed_resource) + + subject.fabricate_via_api! + end + end + end + end +end diff --git a/qa/spec/resource/base_spec.rb b/qa/spec/resource/base_spec.rb new file mode 100644 index 00000000000..dc9e16792d3 --- /dev/null +++ b/qa/spec/resource/base_spec.rb @@ -0,0 +1,246 @@ +# frozen_string_literal: true + +describe QA::Resource::Base do + include Support::StubENV + + let(:resource) { spy('resource') } + let(:location) { 'http://location' } + + shared_context 'fabrication context' do + subject do + Class.new(described_class) do + def self.name + 'MyResource' + end + end + end + + before do + allow(subject).to receive(:current_url).and_return(location) + allow(subject).to receive(:new).and_return(resource) + end + end + + shared_examples 'fabrication method' do |fabrication_method_called, actual_fabrication_method = nil| + let(:fabrication_method_used) { actual_fabrication_method || fabrication_method_called } + + it 'yields resource before calling resource method' do + expect(resource).to receive(:something!).ordered + expect(resource).to receive(fabrication_method_used).ordered.and_return(location) + + subject.public_send(fabrication_method_called, resource: resource) do |resource| + resource.something! + end + end + + it 'does not log the resource and build method when QA_DEBUG=false' do + stub_env('QA_DEBUG', 'false') + expect(resource).to receive(fabrication_method_used).and_return(location) + + expect { subject.public_send(fabrication_method_called, 'something', resource: resource) } + .not_to output.to_stdout + end + end + + describe '.fabricate!' do + context 'when resource does not support fabrication via the API' do + before do + expect(described_class).to receive(:fabricate_via_api!).and_raise(NotImplementedError) + end + + it 'calls .fabricate_via_browser_ui!' do + expect(described_class).to receive(:fabricate_via_browser_ui!) + + described_class.fabricate! + end + end + + context 'when resource supports fabrication via the API' do + it 'calls .fabricate_via_browser_ui!' do + expect(described_class).to receive(:fabricate_via_api!) + + described_class.fabricate! + end + end + end + + describe '.fabricate_via_api!' do + include_context 'fabrication context' + + it_behaves_like 'fabrication method', :fabricate_via_api! + + it 'instantiates the resource, calls resource method returns the resource' do + expect(resource).to receive(:fabricate_via_api!).and_return(location) + + result = subject.fabricate_via_api!(resource: resource, parents: []) + + expect(result).to eq(resource) + end + + it 'logs the resource and build method when QA_DEBUG=true' do + stub_env('QA_DEBUG', 'true') + expect(resource).to receive(:fabricate_via_api!).and_return(location) + + expect { subject.fabricate_via_api!('something', resource: resource, parents: []) } + .to output(/==> Built a MyResource via api in [\d\.\-e]+ seconds+/) + .to_stdout + end + end + + describe '.fabricate_via_browser_ui!' do + include_context 'fabrication context' + + it_behaves_like 'fabrication method', :fabricate_via_browser_ui!, :fabricate! + + it 'instantiates the resource and calls resource method' do + subject.fabricate_via_browser_ui!('something', resource: resource, parents: []) + + expect(resource).to have_received(:fabricate!).with('something') + end + + it 'returns fabrication resource' do + result = subject.fabricate_via_browser_ui!('something', resource: resource, parents: []) + + expect(result).to eq(resource) + end + + it 'logs the resource and build method when QA_DEBUG=true' do + stub_env('QA_DEBUG', 'true') + + expect { subject.fabricate_via_browser_ui!('something', resource: resource, parents: []) } + .to output(/==> Built a MyResource via browser_ui in [\d\.\-e]+ seconds+/) + .to_stdout + end + end + + shared_context 'simple resource' do + subject do + Class.new(QA::Resource::Base) do + attribute :test do + 'block' + end + + attribute :no_block + + def fabricate! + 'any' + end + + def self.current_url + 'http://stub' + end + end + end + + let(:resource) { subject.new } + end + + describe '.attribute' do + include_context 'simple resource' + + it 'appends new attribute' do + expect(subject.attributes_names).to eq([:no_block, :test, :web_url]) + end + + context 'when the attribute is populated via a block' do + it 'returns value from the block' do + result = subject.fabricate!(resource: resource) + + expect(result).to be_a(described_class) + expect(result.test).to eq('block') + end + end + + context 'when the attribute is populated via the api' do + let(:api_resource) { { no_block: 'api' } } + + before do + expect(resource).to receive(:api_resource).and_return(api_resource) + end + + it 'returns value from api' do + result = subject.fabricate!(resource: resource) + + expect(result).to be_a(described_class) + expect(result.no_block).to eq('api') + end + + context 'when the attribute also has a block' do + let(:api_resource) { { test: 'api_with_block' } } + + before do + allow(QA::Runtime::Logger).to receive(:info) + end + + it 'returns value from api and emits an INFO log entry' do + result = subject.fabricate!(resource: resource) + + expect(result).to be_a(described_class) + expect(result.test).to eq('api_with_block') + expect(QA::Runtime::Logger) + .to have_received(:info).with(/api_with_block/) + end + end + end + + context 'when the attribute is populated via direct assignment' do + before do + resource.test = 'value' + end + + it 'returns value from the assignment' do + result = subject.fabricate!(resource: resource) + + expect(result).to be_a(described_class) + expect(result.test).to eq('value') + end + + context 'when the api also has such response' do + before do + allow(resource).to receive(:api_resource).and_return({ test: 'api' }) + end + + it 'returns value from the assignment' do + result = subject.fabricate!(resource: resource) + + expect(result).to be_a(described_class) + expect(result.test).to eq('value') + end + end + end + + context 'when the attribute has no value' do + it 'raises an error because no values could be found' do + result = subject.fabricate!(resource: resource) + + expect { result.no_block } + .to raise_error(described_class::NoValueError, "No value was computed for no_block of #{resource.class.name}.") + end + end + end + + describe '#web_url' do + include_context 'simple resource' + + it 'sets #web_url to #current_url after fabrication' do + subject.fabricate!(resource: resource) + + expect(resource.web_url).to eq(subject.current_url) + end + end + + describe '#visit!' do + include_context 'simple resource' + + before do + allow(resource).to receive(:visit) + end + + it 'calls #visit with the underlying #web_url' do + resource.web_url = subject.current_url + resource.visit! + + expect(resource).to have_received(:visit).with(subject.current_url) + end + end +end diff --git a/qa/spec/resource/repository/push_spec.rb b/qa/spec/resource/repository/push_spec.rb new file mode 100644 index 00000000000..bf3ebce0cfe --- /dev/null +++ b/qa/spec/resource/repository/push_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +describe QA::Resource::Repository::Push do + describe '.files=' do + let(:files) do + [ + { + name: 'file.txt', + content: 'foo' + } + ] + end + + it 'raises an error if files is not an array' do + expect { subject.files = '' }.to raise_error(ArgumentError) + end + + it 'raises an error if files is an empty array' do + expect { subject.files = [] }.to raise_error(ArgumentError) + end + + it 'does not raise if files is an array' do + expect { subject.files = files }.not_to raise_error + end + end +end -- cgit v1.2.1 From af87e40a7814e97a5bb63fc354e1a7b6194a3052 Mon Sep 17 00:00:00 2001 From: Chantal Rollison Date: Thu, 1 Nov 2018 17:10:53 -0700 Subject: Fixed label removal from issue --- app/models/label.rb | 2 +- app/services/boards/issues/move_service.rb | 4 +--- changelogs/unreleased/ccr-51052_keep_labels_on_issue.yml | 5 +++++ spec/support/shared_examples/services/boards/issues_move_service.rb | 2 +- 4 files changed, 8 insertions(+), 5 deletions(-) create mode 100644 changelogs/unreleased/ccr-51052_keep_labels_on_issue.yml diff --git a/app/models/label.rb b/app/models/label.rb index 43b49445765..165e4a8f3e5 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -41,8 +41,8 @@ class Label < ActiveRecord::Base scope :templates, -> { where(template: true) } scope :with_title, ->(title) { where(title: title) } scope :with_lists_and_board, -> { joins(lists: :board).merge(List.movable) } - scope :on_group_boards, ->(group_id) { with_lists_and_board.where(boards: { group_id: group_id }) } scope :on_project_boards, ->(project_id) { with_lists_and_board.where(boards: { project_id: project_id }) } + scope :on_board, ->(board_id) { with_lists_and_board.where(boards: { id: board_id }) } scope :order_name_asc, -> { reorder(title: :asc) } scope :order_name_desc, -> { reorder(title: :desc) } scope :subscribed_by, ->(user_id) { joins(:subscriptions).where(subscriptions: { user_id: user_id, subscribed: true }) } diff --git a/app/services/boards/issues/move_service.rb b/app/services/boards/issues/move_service.rb index 7dd87034410..43a26f4264e 100644 --- a/app/services/boards/issues/move_service.rb +++ b/app/services/boards/issues/move_service.rb @@ -70,10 +70,8 @@ module Boards label_ids = if moving_to_list.movable? moving_from_list.label_id - elsif board.group_board? - ::Label.on_group_boards(parent.id).pluck(:label_id) else - ::Label.on_project_boards(parent.id).pluck(:label_id) + ::Label.on_board(board.id).pluck(:label_id) end Array(label_ids).compact diff --git a/changelogs/unreleased/ccr-51052_keep_labels_on_issue.yml b/changelogs/unreleased/ccr-51052_keep_labels_on_issue.yml new file mode 100644 index 00000000000..7ef857d38ed --- /dev/null +++ b/changelogs/unreleased/ccr-51052_keep_labels_on_issue.yml @@ -0,0 +1,5 @@ +--- +title: Fixed label removal from issue +merge_request: 22762 +author: +type: fixed diff --git a/spec/support/shared_examples/services/boards/issues_move_service.rb b/spec/support/shared_examples/services/boards/issues_move_service.rb index 6d29a97c56d..ec44b99d10e 100644 --- a/spec/support/shared_examples/services/boards/issues_move_service.rb +++ b/spec/support/shared_examples/services/boards/issues_move_service.rb @@ -34,7 +34,7 @@ shared_examples 'issues move service' do |group| described_class.new(parent, user, params).execute(issue) issue.reload - expect(issue.labels).to contain_exactly(bug) + expect(issue.labels).to contain_exactly(bug, regression) expect(issue).to be_closed end end -- cgit v1.2.1 From e65162842c47f43df5831f0b5416045171ea62c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Trzci=C5=84ski?= Date: Mon, 5 Nov 2018 17:32:03 +0100 Subject: Fix invalid artifact path for codequality --- app/models/ci/job_artifact.rb | 2 +- spec/lib/gitlab/ci/config/entry/reports_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/ci/job_artifact.rb b/app/models/ci/job_artifact.rb index 34a889057ab..11c88200c37 100644 --- a/app/models/ci/job_artifact.rb +++ b/app/models/ci/job_artifact.rb @@ -15,7 +15,7 @@ module Ci metadata: nil, trace: nil, junit: 'junit.xml', - codequality: 'codequality.json', + codequality: 'gl-code-quality-report.json', sast: 'gl-sast-report.json', dependency_scanning: 'gl-dependency-scanning-report.json', container_scanning: 'gl-container-scanning-report.json', diff --git a/spec/lib/gitlab/ci/config/entry/reports_spec.rb b/spec/lib/gitlab/ci/config/entry/reports_spec.rb index 8095a231cf3..1140bfdf6c3 100644 --- a/spec/lib/gitlab/ci/config/entry/reports_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/reports_spec.rb @@ -33,7 +33,7 @@ describe Gitlab::Ci::Config::Entry::Reports do where(:keyword, :file) do :junit | 'junit.xml' - :codequality | 'codequality.json' + :codequality | 'gl-code-quality-report.json' :sast | 'gl-sast-report.json' :dependency_scanning | 'gl-dependency-scanning-report.json' :container_scanning | 'gl-container-scanning-report.json' -- cgit v1.2.1 From 4f3a9be1f6c51f057cfaa5b9b417344760ed0590 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 5 Nov 2018 18:02:24 +0100 Subject: Add section about Gitaly --- doc/install/aws/index.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/doc/install/aws/index.md b/doc/install/aws/index.md index 303c2d95f2f..b41ee07fd52 100644 --- a/doc/install/aws/index.md +++ b/doc/install/aws/index.md @@ -508,6 +508,14 @@ To add more than one data volumes, follow the same steps. Read more on [storing Git data in an alternative directory](../../administration/repository_storage_paths.md). +### Setting up Gitaly + +Gitaly is a service that provides high-level RPC access to Git repositories. +It should be enabled and configured in a separate EC2 instance on the +[private VPC](#subnets) we configured previously. + +Follow the [documentation to set it up](../../administration/gitaly/index.md). + ### Using Amazon S3 object storage The S3 object storage can be used for various GitLab objects: -- cgit v1.2.1 From 00c7d78d49ab94f963e1e6b0bb0428b395aa036a Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Mon, 5 Nov 2018 13:22:25 +0000 Subject: [ci skip] Renders a warning block for archieved job When the job is archieved we render a affixed warning on the top of the job log --- app/assets/javascripts/jobs/components/job_app.vue | 29 ++++++++++++++++-- .../jobs/components/job_log_controllers.vue | 2 +- app/assets/stylesheets/pages/builds.scss | 20 +++++++++++++ changelogs/unreleased/53535-sticky-archived.yml | 5 ++++ locale/gitlab.pot | 3 ++ spec/javascripts/jobs/components/job_app_spec.js | 34 ++++++++++++++++++++++ 6 files changed, 90 insertions(+), 3 deletions(-) create mode 100644 changelogs/unreleased/53535-sticky-archived.yml diff --git a/app/assets/javascripts/jobs/components/job_app.vue b/app/assets/javascripts/jobs/components/job_app.vue index 6e95e3d16f8..c3fb4c30b06 100644 --- a/app/assets/javascripts/jobs/components/job_app.vue +++ b/app/assets/javascripts/jobs/components/job_app.vue @@ -3,9 +3,11 @@ import _ from 'underscore'; import { mapGetters, mapState, mapActions } from 'vuex'; import { GlLoadingIcon } from '@gitlab-org/gitlab-ui'; import { isScrolledToBottom } from '~/lib/utils/scroll_utils'; +import { polyfillSticky } from '~/lib/utils/sticky'; import bp from '~/breakpoints'; import CiHeader from '~/vue_shared/components/header_ci_component.vue'; import Callout from '~/vue_shared/components/callout.vue'; +import Icon from '~/vue_shared/components/icon.vue'; import createStore from '../store'; import EmptyState from './empty_state.vue'; import EnvironmentsBlock from './environments_block.vue'; @@ -25,6 +27,7 @@ export default { EnvironmentsBlock, ErasedBlock, GlLoadingIcon, + Icon, Log, LogTopBar, StuckBlock, @@ -119,6 +122,14 @@ export default { this.updateSidebar(); }, + updated() { + this.$nextTick(() => { + if (this.$refs.sticky) { + polyfillSticky(this.$refs.sticky); + } + }); + }, + destroyed() { window.removeEventListener('resize', this.onResize); window.removeEventListener('scroll', this.updateScroll); @@ -218,14 +229,28 @@ export default { :erased-at="job.erased_at" /> +
+ + + {{ __('This job is archived. Only the complete pipeline can be retried.') }} +
+ class="build-trace-container" + >