diff options
24 files changed, 387 insertions, 164 deletions
diff --git a/app/assets/javascripts/notes/components/noteable_discussion.vue b/app/assets/javascripts/notes/components/noteable_discussion.vue index be451401e97..7c3f5d00308 100644 --- a/app/assets/javascripts/notes/components/noteable_discussion.vue +++ b/app/assets/javascripts/notes/components/noteable_discussion.vue @@ -212,16 +212,6 @@ export default { return this.line; }, - commit() { - if (!this.discussion.for_commit) { - return null; - } - - return { - id: this.discussion.commit_id, - url: this.discussion.discussion_path, - }; - }, }, watch: { isReplying() { @@ -387,7 +377,6 @@ Please check your network connection and try again.`; :note="componentData(initialDiscussion)" :line="line" :help-page-path="helpPagePath" - :commit="commit" @handleDeleteNote="deleteNoteHandler" > <note-edited-text diff --git a/app/assets/javascripts/notes/components/noteable_note.vue b/app/assets/javascripts/notes/components/noteable_note.vue index 6edc65c856e..4c02588127e 100644 --- a/app/assets/javascripts/notes/components/noteable_note.vue +++ b/app/assets/javascripts/notes/components/noteable_note.vue @@ -2,8 +2,6 @@ import $ from 'jquery'; import { mapGetters, mapActions } from 'vuex'; import { escape } from 'underscore'; -import { truncateSha } from '~/lib/utils/text_utility'; -import { s__, sprintf } from '~/locale'; import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue'; import Flash from '../../flash'; import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue'; @@ -39,11 +37,6 @@ export default { required: false, default: '', }, - commit: { - type: Object, - required: false, - default: () => null, - }, }, data() { return { @@ -80,24 +73,6 @@ export default { isTarget() { return this.targetNoteHash === this.noteAnchorId; }, - actionText() { - if (this.commit) { - const { id, url } = this.commit; - const linkStart = `<a class="commit-sha monospace" href="${escape(url)}">`; - const linkEnd = '</a>'; - return sprintf( - s__('MergeRequests|commented on commit %{linkStart}%{commitId}%{linkEnd}'), - { - commitId: truncateSha(id), - linkStart, - linkEnd, - }, - false, - ); - } - - return '<span class="d-none d-sm-inline">·</span>'; - }, }, created() { @@ -225,9 +200,13 @@ export default { </div> <div class="timeline-content"> <div class="note-header"> - <note-header v-once :author="author" :created-at="note.created_at" :note-id="note.id"> - <span v-html="actionText"></span> - </note-header> + <note-header + v-once + :author="author" + :created-at="note.created_at" + :note-id="note.id" + action-text="commented" + /> <note-actions :author-id="author.id" :note-id="note.id" diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb index 293dd20ad49..033686823a2 100644 --- a/app/helpers/notes_helper.rb +++ b/app/helpers/notes_helper.rb @@ -85,7 +85,7 @@ module NotesHelper diffs_project_merge_request_path(discussion.project, discussion.noteable, path_params) elsif discussion.for_commit? - anchor = discussion.diff_discussion? ? discussion.line_code : "note_#{discussion.first_note.id}" + anchor = discussion.line_code if discussion.diff_discussion? project_commit_path(discussion.project, discussion.noteable, anchor: anchor) end diff --git a/app/models/namespace.rb b/app/models/namespace.rb index 36de1c41b67..a0bebc5e9a2 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -306,6 +306,7 @@ class Namespace < ActiveRecord::Base def write_projects_repository_config all_projects.find_each do |project| project.write_repository_config + project.track_project_repository end end end diff --git a/app/models/project.rb b/app/models/project.rb index 9ba478a535c..09e2a6114fe 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1244,10 +1244,8 @@ class Project < ActiveRecord::Base end def track_project_repository - return unless hashed_storage?(:repository) - - project_repo = project_repository || build_project_repository - project_repo.update!(shard_name: repository_storage, disk_path: disk_path) + repository = project_repository || build_project_repository + repository.update!(shard_name: repository_storage, disk_path: disk_path) end def create_repository(force: false) diff --git a/app/services/projects/after_rename_service.rb b/app/services/projects/after_rename_service.rb index 4131da44f5a..aa9b253eb20 100644 --- a/app/services/projects/after_rename_service.rb +++ b/app/services/projects/after_rename_service.rb @@ -81,6 +81,7 @@ module Projects def update_repository_configuration project.reload_repository! project.write_repository_config + project.track_project_repository end def rename_transferred_documents diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb index 9db3fd9cf17..5da1e39a1fb 100644 --- a/app/services/projects/transfer_service.rb +++ b/app/services/projects/transfer_service.rb @@ -81,7 +81,7 @@ module Projects project.old_path_with_namespace = @old_path - write_repository_config(@new_path) + update_repository_configuration(@new_path) execute_system_hooks end @@ -106,8 +106,9 @@ module Projects project.save! end - def write_repository_config(full_path) + def update_repository_configuration(full_path) project.write_repository_config(gl_full_path: full_path) + project.track_project_repository end def refresh_permissions @@ -123,7 +124,7 @@ module Projects rollback_folder_move project.reload update_namespace_and_visibility(@old_namespace) - write_repository_config(@old_path) + update_repository_configuration(@old_path) end def rollback_folder_move diff --git a/changelogs/unreleased/support-gitaly-tls.yml b/changelogs/unreleased/support-gitaly-tls.yml new file mode 100644 index 00000000000..2a15500d6da --- /dev/null +++ b/changelogs/unreleased/support-gitaly-tls.yml @@ -0,0 +1,5 @@ +--- +title: Support tls communication in gitaly +merge_request: 22602 +author: +type: added diff --git a/changelogs/unreleased/winh-discussion-header-commented.yml b/changelogs/unreleased/winh-discussion-header-commented.yml deleted file mode 100644 index 8d08409b504..00000000000 --- a/changelogs/unreleased/winh-discussion-header-commented.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Display "commented" only for commit discussions on merge requests -merge_request: 23622 -author: -type: fixed diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 1c16b999e55..7fe85f0e0d7 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -612,7 +612,7 @@ production: &base storages: # You must have at least a `default` storage path. default: path: /home/git/repositories/ - gitaly_address: unix:/home/git/gitlab/tmp/sockets/private/gitaly.socket # TCP connections are supported too (e.g. tcp://host:port) + gitaly_address: unix:/home/git/gitlab/tmp/sockets/private/gitaly.socket # TCP connections are supported too (e.g. tcp://host:port). TLS connections are also supported using the system certificate pool (eg: tls://host:port). # gitaly_token: 'special token' # Optional: override global gitaly.token for this storage. ## Backup settings diff --git a/doc/administration/gitaly/index.md b/doc/administration/gitaly/index.md index dc6a71e2ebd..cf37eaa0b61 100644 --- a/doc/administration/gitaly/index.md +++ b/doc/administration/gitaly/index.md @@ -1,6 +1,6 @@ # Gitaly -[Gitaly](https://gitlab.com/gitlab-org/gitaly) is the service that +[Gitaly](https://gitlab.com/gitlab-org/gitaly) is the service that provides high-level RPC access to Git repositories. Without it, no other components can read or write Git data. @@ -23,7 +23,7 @@ gitaly['prometheus_listen_addr'] = 'localhost:9236' ``` To change a Gitaly setting in installations from source you can edit -`/home/git/gitaly/config.toml`. Changes will be applied when you run +`/home/git/gitaly/config.toml`. Changes will be applied when you run `service gitlab restart`. ```toml @@ -91,13 +91,13 @@ documentation on configuring Gitaly authentication](https://gitlab.com/gitlab-org/gitaly/blob/master/doc/configuration/README.md#authentication) . -Gitaly must trigger some callbacks to GitLab via GitLab Shell. As a result, +Gitaly must trigger some callbacks to GitLab via GitLab Shell. As a result, the GitLab Shell secret must be the same between the other GitLab servers and the Gitaly server. The easiest way to accomplish this is to copy `/etc/gitlab/gitlab-secrets.json` from an existing GitLab server to the Gitaly server. Without this shared secret, -Git operations in GitLab will result in an API error. +Git operations in GitLab will result in an API error. -> **NOTE:** In most or all cases the storage paths below end in `/repositories` which is +> **NOTE:** In most or all cases the storage paths below end in `/repositories` which is different than `path` in `git_data_dirs` of Omnibus installations. Check the directory layout on your Gitaly server to be sure. @@ -133,6 +133,11 @@ gitaly['storage'] = [ { 'name' => 'default', 'path' => '/mnt/gitlab/default/repositories' }, { 'name' => 'storage1', 'path' => '/mnt/gitlab/storage1/repositories' }, ] + +# To use tls for gitaly you need to add +gitaly['tls_listen_addr'] = "0.0.0.0:9999" +gitaly['certificate_path'] = "path/to/cert.pem" +gitaly['key_path'] = "path/to/key.pem" ``` Source installations: @@ -140,6 +145,11 @@ Source installations: ```toml # /home/git/gitaly/config.toml listen_addr = '0.0.0.0:8075' +tls_listen_addr = '0.0.0.0:9999' + +[tls] +certificate_path = /path/to/cert.pem +key_path = /path/to/key.pem [auth] token = 'abc123secret' @@ -205,6 +215,70 @@ Gitaly logs on your Gitaly server (`sudo gitlab-ctl tail gitaly` or coming in. One sure way to trigger a Gitaly request is to clone a repository from your GitLab server over HTTP. +## TLS support + +Gitaly supports TLS credentials for GRPC authentication. To be able to communicate +with a gitaly instance that listens for secure connections you will need to use `tls://` url +scheme in the `gitaly_address` of the corresponding storage entry in the gitlab configuration. + +The admin needs to bring their own certificate as we do not provide that automatically. +The certificate to be used needs to be installed on all gitaly nodes and on all client nodes that communicate with it following procedures described in [GitLab custom certificate configuration](https://docs.gitlab.com/omnibus/settings/ssl.html#install-custom-public-certificates) + +### Example TLS configuration + +### Omnibus installations: + +#### On client nodes: + +```ruby +# /etc/gitlab/gitlab.rb +git_data_dirs({ + 'default' => { 'path' => '/mnt/gitlab/default', 'gitaly_address' => 'tls://gitaly.internal:9999' }, + 'storage1' => { 'path' => '/mnt/gitlab/storage1', 'gitaly_address' => 'tls://gitaly.internal:9999' }, +}) + +gitlab_rails['gitaly_token'] = 'abc123secret' +``` + +#### On gitaly server nodes: + +```ruby +gitaly['tls_listen_addr'] = "0.0.0.0:9999" +gitaly['certificate_path'] = "path/to/cert.pem" +gitaly['key_path'] = "path/to/key.pem" +``` + +### Source installations: + +#### On client nodes: + +```yaml +# /home/git/gitlab/config/gitlab.yml +gitlab: + repositories: + storages: + default: + path: /mnt/gitlab/default/repositories + gitaly_address: tls://gitaly.internal:9999 + storage1: + path: /mnt/gitlab/storage1/repositories + gitaly_address: tls://gitaly.internal:9999 + + gitaly: + token: 'abc123secret' +``` + +#### On gitaly server nodes: + +```toml +# /home/git/gitaly/config.toml +tls_listen_addr = '0.0.0.0:9999' + +[tls] +certificate_path = '/path/to/cert.pem' +key_path = '/path/to/key.pem' +``` + ## Disabling or enabling the Gitaly service in a cluster environment If you are running Gitaly [as a remote diff --git a/doc/user/group/clusters/index.md b/doc/user/group/clusters/index.md index 8e03d116b81..9f9b2da23e1 100644 --- a/doc/user/group/clusters/index.md +++ b/doc/user/group/clusters/index.md @@ -1,9 +1,7 @@ # Group-level Kubernetes clusters > [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/34758) in GitLab 11.6. - -CAUTION: **Warning:** -Group Cluster integration is currently in **Beta**. +> Group Cluster integration is currently in [Beta](https://about.gitlab.com/handbook/product/#alpha-beta-ga). ## Overview diff --git a/doc/user/project/clusters/serverless/img/function-execution.png b/doc/user/project/clusters/serverless/img/function-execution.png Binary files differnew file mode 100644 index 00000000000..93b0b6d802d --- /dev/null +++ b/doc/user/project/clusters/serverless/img/function-execution.png diff --git a/doc/user/project/clusters/serverless/img/serverless-page.png b/doc/user/project/clusters/serverless/img/serverless-page.png Binary files differindex 5d808fc2d4f..960d6e736d6 100644 --- a/doc/user/project/clusters/serverless/img/serverless-page.png +++ b/doc/user/project/clusters/serverless/img/serverless-page.png diff --git a/doc/user/project/clusters/serverless/index.md b/doc/user/project/clusters/serverless/index.md index a1e5735670a..da6f4df01df 100644 --- a/doc/user/project/clusters/serverless/index.md +++ b/doc/user/project/clusters/serverless/index.md @@ -1,9 +1,7 @@ # Serverless > Introduced in GitLab 11.5. - -CAUTION: **Caution:** -Serverless is currently in [alpha](https://about.gitlab.com/handbook/product/#alpha). +> Serverless is currently in [alpha](https://about.gitlab.com/handbook/product/#alpha). Run serverless workloads on Kubernetes using [Knative](https://cloud.google.com/knative/). @@ -34,10 +32,10 @@ To run Knative on Gitlab, you will need: 1. **`.gitlab-ci.yml`:** GitLab uses [Kaniko](https://github.com/GoogleContainerTools/kaniko) to build the application and the [TriggerMesh CLI](https://github.com/triggermesh/tm) to simplify the deployment of knative services and functions. -1. **`serverless.yml`** (for [functions only](#deploying-functions)): When using serverless to deploy functions, the `serverless.yml` file +1. **`serverless.yaml`** (for [functions only](#deploying-functions)): When using serverless to deploy functions, the `serverless.yaml` file will contain the information for all the functions being hosted in the repository as well as a reference to the runtime being used. -1. **`Dockerfile`:** Knative requires a `Dockerfile` in order to build your application. It should be included +1. **`Dockerfile`** (for [applications only](#deploying-serverless-applications): Knative requires a `Dockerfile` in order to build your application. It should be included at the root of your project's repo and expose port `8080`. ## Installing Knative via GitLab's Kubernetes integration @@ -75,11 +73,16 @@ The minimum recommended cluster size to run Knative is 3-nodes, 6 vCPUs, and 22. > Introduced in GitLab 11.6. -Using functions is useful for initiating, responding, or triggering independent +Using functions is useful for dealing with independent events without needing to maintain a complex unified infrastructure. This allows you to focus on a single task that can be executed/scaled automatically and independently. -In order to deploy functions to your Knative instance, the following templates must be present: +Currently the following [runtimes](https://gitlab.com/triggermesh/runtimes) are offered: + +- node.js +- kaniko + +In order to deploy functions to your Knative instance, the following files must be present: 1. `.gitlab-ci.yml`: This template allows to define the stage, environment, and image to be used for your functions. It must be included at the root of your repository: @@ -97,11 +100,16 @@ In order to deploy functions to your Knative instance, the following templates m - tm -n "$KUBE_NAMESPACE" --registry-host "$CI_REGISTRY_IMAGE" deploy --wait ``` -2. `serverless.yml`: This template contains the metadata for your functions, - such as name, runtime, and environment. It must be included at the root of your repository: + The `gitlab-ci.yml` template creates a `Deploy` stage with a `functions` job that invokes the `tm` CLI with the required parameters. + +2. `serverless.yaml`: This file contains the metadata for your functions, + such as name, runtime, and environment. It must be included at the root of your repository. The following is a sample `echo` function which shows the required structure for the file. + + NOTE: **Note:** + The file extension for the `serverless.yaml` file must be specified as `.yaml` in order to the file to be parsed properly. Specifying the extension as `.yml` will not work. ```yaml - service: knative-test + service: my-functions description: "Deploying functions from GitLab using Knative" provider: @@ -111,27 +119,51 @@ In order to deploy functions to your Knative instance, the following templates m FOO: BAR functions: - container: - handler: simple - description: "knative canonical sample" - runtime: https://gitlab.com/triggermesh/runtimes/raw/master/kaniko.yaml + echo: + handler: echo + runtime: https://gitlab.com/triggermesh/runtimes/raw/master/nodejs.yaml + description: "echo function using node.js runtime" buildargs: - - DIRECTORY=simple - environment: - SIMPLE_MSG: Hello - - nodejs: - handler: nodejs - runtime: https://gitlab.com/triggermesh/runtimes/raw/master/nodejs.yaml - description: "nodejs fragment" - buildargs: - - DIRECTORY=nodejs + - DIRECTORY=echo environment: - FUNCTION: nodejs + FUNCTION: echo ``` -After the templates have been created, each function must be defined as a single -file in your repository. Committing a function to your project will result in a + +The `serverless.yaml` file contains three sections with distinct parameters: + +### `service` + +| Parameter | Description | +|-----------|-------------| +| `service` | Name for the Knative service which will serve the function. | +| `description` | A short description of the `service`. | + + +### `provider` + +| Parameter | Description | +|-----------|-------------| +| `name` | Indicates which provider is used to execute the `serverless.yaml` file. In this case, the TriggerMesh `tm` CLI. | +| `registry-secret` | Indicates which registry will be used to store docker images. The sample function is using the GitLab Registry (`gitlab-registry`). A different registry host may be specified using `registry` key in the `provider` object. If changing the default, update the permission and the secret value on the `gitlab-ci.yml` file | +| `environment` | Includes the environment variables to be passed as part of function execution for **all** functions in the file, where `FOO` is the variable name and `BAR` are he variable contents. You may replace this with you own variables. | + +### `functions` + +In the `serverless.yaml` example above, the function name is `echo` and the subsequent lines contain the function attributes. + + +| Parameter | Description | +|-----------|-------------| +| `handler` | The function's file name. In the example above, both the function name and the handler are the same. | +| `runtime` | The runtime to be used to execute the function. | +| `description` | A short description of the function. | +| `buildargs` | Pointer to the function file in the repo. In the sample the function is located in the `echo` directory. | +| `environment` | Sets an environment variable for the specific function only. | + +After the `gitlab-ci.yml` template has been added and the `serverless.yaml` file has been +created, each function must be defined as a single file in your repository. Committing a +function to your project will result in a CI pipeline being executed which will deploy each function as a Knative service. Once the deploy stage has finished, additional details for the function will appear under **Operations > Serverless**. @@ -149,6 +181,10 @@ The function details can be retrieved directly from Knative on the cluster: kubectl -n "$KUBE_NAMESPACE" get services.serving.knative.dev ``` +The sample function can now be triggered from any HTTP client using a simple `POST` call. + +![function exection](img/function-execution.png) + Currently, the Serverless page presents all functions available in all clusters registered for the project with Knative installed. ## Deploying Serverless applications @@ -190,17 +226,12 @@ deploy: ## Deploy the application with Knative -With all the pieces in place, you can create a new CI pipeline to deploy the Knative application. Navigate to -**CI/CD > Pipelines** and click the **Run Pipeline** button at the upper-right part of the screen. Then, on the -Pipelines page, click **Create pipeline**. +With all the pieces in place, the next time a CI pipeline runs, the Knative application will be deployed. Navigate to +**CI/CD > Pipelines** and click the most recent pipeline. ## Obtain the URL for the Knative deployment -There are two ways to obtain the URL for the Knative deployment. - -### Using the CI/CD deployment job output - -Once all the stages of the pipeline finish, click the **deploy** stage. +Use the CI/CD deployment job output to obtain the deployment URL. Once all the stages of the pipeline finish, click the **deploy** stage. ![deploy stage](img/deploy-stage.png) diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb index 11021ee06b3..8bf8a3b53cd 100644 --- a/lib/gitlab/gitaly_client.rb +++ b/lib/gitlab/gitaly_client.rb @@ -26,6 +26,7 @@ module Gitlab end end + PEM_REGEX = /\-+BEGIN CERTIFICATE\-+.+?\-+END CERTIFICATE\-+/m SERVER_VERSION_FILE = 'GITALY_SERVER_VERSION' MAXIMUM_GITALY_CALLS = 35 CLIENT_NAME = (Sidekiq.server? ? 'gitlab-sidekiq' : 'gitlab-web').freeze @@ -50,11 +51,42 @@ module Gitlab @stubs[storage][name] ||= begin klass = stub_class(name) addr = stub_address(storage) - klass.new(addr, :this_channel_is_insecure) + creds = stub_creds(storage) + klass.new(addr, creds) end end end + def self.stub_cert_paths + cert_paths = Dir["#{OpenSSL::X509::DEFAULT_CERT_DIR}/*"] + cert_paths << OpenSSL::X509::DEFAULT_CERT_FILE if File.exist? OpenSSL::X509::DEFAULT_CERT_FILE + cert_paths + end + + def self.stub_certs + return @certs if @certs + + @certs = stub_cert_paths.flat_map do |cert_file| + File.read(cert_file).scan(PEM_REGEX).map do |cert| + begin + OpenSSL::X509::Certificate.new(cert).to_pem + rescue OpenSSL::OpenSSLError => e + Rails.logger.error "Could not load certificate #{cert_file} #{e}" + Gitlab::Sentry.track_exception(e, extra: { cert_file: cert_file }) + nil + end + end.compact + end.uniq.join("\n") + end + + def self.stub_creds(storage) + if URI(address(storage)).scheme == 'tls' + GRPC::Core::ChannelCredentials.new stub_certs + else + :this_channel_is_insecure + end + end + def self.stub_class(name) if name == :health_check Grpc::Health::V1::Health::Stub @@ -64,9 +96,7 @@ module Gitlab end def self.stub_address(storage) - addr = address(storage) - addr = addr.sub(%r{^tcp://}, '') if URI(addr).scheme == 'tcp' - addr + address(storage).sub(%r{^tcp://|^tls://}, '') end def self.clear_stubs! @@ -88,8 +118,8 @@ module Gitlab raise "storage #{storage.inspect} is missing a gitaly_address" end - unless URI(address).scheme.in?(%w(tcp unix)) - raise "Unsupported Gitaly address: #{address.inspect} does not use URL scheme 'tcp' or 'unix'" + unless URI(address).scheme.in?(%w(tcp unix tls)) + raise "Unsupported Gitaly address: #{address.inspect} does not use URL scheme 'tcp' or 'unix' or 'tls'" end address diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 59c377c9ea3..b27efe04966 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -4138,9 +4138,6 @@ msgstr "" msgid "MergeRequests|View replaced file @ %{commitId}" msgstr "" -msgid "MergeRequests|commented on commit %{linkStart}%{commitId}%{linkEnd}" -msgstr "" - msgid "MergeRequests|started a discussion" msgstr "" diff --git a/spec/features/merge_request/user_sees_discussions_spec.rb b/spec/features/merge_request/user_sees_discussions_spec.rb index d130ea05654..4ab9a87ad4b 100644 --- a/spec/features/merge_request/user_sees_discussions_spec.rb +++ b/spec/features/merge_request/user_sees_discussions_spec.rb @@ -88,13 +88,5 @@ describe 'Merge request > User sees discussions', :js do expect(page).to have_content "started a discussion on commit #{note.commit_id[0...7]}" end end - - context 'a commit non-diff discussion' do - let(:note) { create(:discussion_note_on_commit, project: project) } - - it 'displays correct header' do - expect(page).to have_content "commented on commit #{note.commit_id[0...7]}" - end - end end end diff --git a/spec/helpers/notes_helper_spec.rb b/spec/helpers/notes_helper_spec.rb index 0715f34dafe..21461e46cf4 100644 --- a/spec/helpers/notes_helper_spec.rb +++ b/spec/helpers/notes_helper_spec.rb @@ -185,8 +185,8 @@ describe NotesHelper do context 'for a non-diff discussion' do let(:discussion) { create(:discussion_note_on_commit, project: project).to_discussion } - it 'returns the commit path with the note anchor' do - expect(helper.discussion_path(discussion)).to eq(project_commit_path(project, commit, anchor: "note_#{discussion.first_note.id}")) + it 'returns the commit path' do + expect(helper.discussion_path(discussion)).to eq(project_commit_path(project, commit)) end end end diff --git a/spec/lib/gitlab/gitaly_client_spec.rb b/spec/lib/gitlab/gitaly_client_spec.rb index 5eda4d041a8..e41a75c37a7 100644 --- a/spec/lib/gitlab/gitaly_client_spec.rb +++ b/spec/lib/gitlab/gitaly_client_spec.rb @@ -3,6 +3,20 @@ require 'spec_helper' # We stub Gitaly in `spec/support/gitaly.rb` for other tests. We don't want # those stubs while testing the GitalyClient itself. describe Gitlab::GitalyClient do + let(:sample_cert) { Rails.root.join('spec/fixtures/clusters/sample_cert.pem').to_s } + + before do + allow(described_class) + .to receive(:stub_cert_paths) + .and_return([sample_cert]) + end + + def stub_repos_storages(address) + allow(Gitlab.config.repositories).to receive(:storages).and_return({ + 'default' => { 'gitaly_address' => address } + }) + end + describe '.stub_class' do it 'returns the gRPC health check stub' do expect(described_class.stub_class(:health_check)).to eq(::Grpc::Health::V1::Health::Stub) @@ -15,12 +29,8 @@ describe Gitlab::GitalyClient do describe '.stub_address' do it 'returns the same result after being called multiple times' do - address = 'localhost:9876' - prefixed_address = "tcp://#{address}" - - allow(Gitlab.config.repositories).to receive(:storages).and_return({ - 'default' => { 'gitaly_address' => prefixed_address } - }) + address = 'tcp://localhost:9876' + stub_repos_storages address 2.times do expect(described_class.stub_address('default')).to eq('localhost:9876') @@ -28,6 +38,45 @@ describe Gitlab::GitalyClient do end end + describe '.stub_certs' do + it 'skips certificates if OpenSSLError is raised and report it' do + expect(Rails.logger).to receive(:error).at_least(:once) + expect(Gitlab::Sentry) + .to receive(:track_exception) + .with( + a_kind_of(OpenSSL::X509::CertificateError), + extra: { cert_file: a_kind_of(String) }).at_least(:once) + + expect(OpenSSL::X509::Certificate) + .to receive(:new) + .and_raise(OpenSSL::X509::CertificateError).at_least(:once) + + expect(described_class.stub_certs).to be_a(String) + end + end + describe '.stub_creds' do + it 'returns :this_channel_is_insecure if unix' do + address = 'unix:/tmp/gitaly.sock' + stub_repos_storages address + + expect(described_class.stub_creds('default')).to eq(:this_channel_is_insecure) + end + + it 'returns :this_channel_is_insecure if tcp' do + address = 'tcp://localhost:9876' + stub_repos_storages address + + expect(described_class.stub_creds('default')).to eq(:this_channel_is_insecure) + end + + it 'returns Credentials object if tls' do + address = 'tls://localhost:9876' + stub_repos_storages address + + expect(described_class.stub_creds('default')).to be_a(GRPC::Core::ChannelCredentials) + end + end + describe '.stub' do # Notice that this is referring to gRPC "stubs", not rspec stubs before do @@ -37,9 +86,19 @@ describe Gitlab::GitalyClient do context 'when passed a UNIX socket address' do it 'passes the address as-is to GRPC' do address = 'unix:/tmp/gitaly.sock' - allow(Gitlab.config.repositories).to receive(:storages).and_return({ - 'default' => { 'gitaly_address' => address } - }) + stub_repos_storages address + + expect(Gitaly::CommitService::Stub).to receive(:new).with(address, any_args) + + described_class.stub(:commit_service, 'default') + end + end + + context 'when passed a TLS address' do + it 'strips tls:// prefix before passing it to GRPC::Core::Channel initializer' do + address = 'localhost:9876' + prefixed_address = "tls://#{address}" + stub_repos_storages prefixed_address expect(Gitaly::CommitService::Stub).to receive(:new).with(address, any_args) @@ -51,10 +110,7 @@ describe Gitlab::GitalyClient do it 'strips tcp:// prefix before passing it to GRPC::Core::Channel initializer' do address = 'localhost:9876' prefixed_address = "tcp://#{address}" - - allow(Gitlab.config.repositories).to receive(:storages).and_return({ - 'default' => { 'gitaly_address' => prefixed_address } - }) + stub_repos_storages prefixed_address expect(Gitaly::CommitService::Stub).to receive(:new).with(address, any_args) diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index 18b54cce834..475fbe56e4d 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -337,32 +337,40 @@ describe Namespace do end end - it 'updates project full path in .git/config for each project inside namespace' do - parent = create(:group, name: 'mygroup', path: 'mygroup') - subgroup = create(:group, name: 'mysubgroup', path: 'mysubgroup', parent: parent) - project_in_parent_group = create(:project, :legacy_storage, :repository, namespace: parent, name: 'foo1') - hashed_project_in_subgroup = create(:project, :repository, namespace: subgroup, name: 'foo2') - legacy_project_in_subgroup = create(:project, :legacy_storage, :repository, namespace: subgroup, name: 'foo3') - - parent.update(path: 'mygroup_new') - - # Routes are loaded when creating the projects, so we need to manually - # reload them for the below code to be aware of the above UPDATE. - [ - project_in_parent_group, - hashed_project_in_subgroup, - legacy_project_in_subgroup - ].each do |project| - project.route.reload + context 'for each project inside the namespace' do + let!(:parent) { create(:group, name: 'mygroup', path: 'mygroup') } + let!(:subgroup) { create(:group, name: 'mysubgroup', path: 'mysubgroup', parent: parent) } + let!(:project_in_parent_group) { create(:project, :legacy_storage, :repository, namespace: parent, name: 'foo1') } + let!(:hashed_project_in_subgroup) { create(:project, :repository, namespace: subgroup, name: 'foo2') } + let!(:legacy_project_in_subgroup) { create(:project, :legacy_storage, :repository, namespace: subgroup, name: 'foo3') } + + it 'updates project full path in .git/config' do + parent.update(path: 'mygroup_new') + + expect(project_rugged(project_in_parent_group).config['gitlab.fullpath']).to eq "mygroup_new/#{project_in_parent_group.path}" + expect(project_rugged(hashed_project_in_subgroup).config['gitlab.fullpath']).to eq "mygroup_new/mysubgroup/#{hashed_project_in_subgroup.path}" + expect(project_rugged(legacy_project_in_subgroup).config['gitlab.fullpath']).to eq "mygroup_new/mysubgroup/#{legacy_project_in_subgroup.path}" end - expect(project_rugged(project_in_parent_group).config['gitlab.fullpath']).to eq "mygroup_new/#{project_in_parent_group.path}" - expect(project_rugged(hashed_project_in_subgroup).config['gitlab.fullpath']).to eq "mygroup_new/mysubgroup/#{hashed_project_in_subgroup.path}" - expect(project_rugged(legacy_project_in_subgroup).config['gitlab.fullpath']).to eq "mygroup_new/mysubgroup/#{legacy_project_in_subgroup.path}" - end + it 'updates the project storage location' do + repository_project_in_parent_group = create(:project_repository, project: project_in_parent_group) + repository_hashed_project_in_subgroup = create(:project_repository, project: hashed_project_in_subgroup) + repository_legacy_project_in_subgroup = create(:project_repository, project: legacy_project_in_subgroup) + + parent.update(path: 'mygroup_moved') + + expect(repository_project_in_parent_group.reload.disk_path).to eq "mygroup_moved/#{project_in_parent_group.path}" + expect(repository_hashed_project_in_subgroup.reload.disk_path).to eq hashed_project_in_subgroup.disk_path + expect(repository_legacy_project_in_subgroup.reload.disk_path).to eq "mygroup_moved/mysubgroup/#{legacy_project_in_subgroup.path}" + end + + def project_rugged(project) + # Routes are loaded when creating the projects, so we need to manually + # reload them for the below code to be aware of the above UPDATE. + project.route.reload - def project_rugged(project) - rugged_repo(project.repository) + rugged_repo(project.repository) + end end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index b95095a24cd..a01f76a5bab 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -1651,26 +1651,54 @@ describe Project do end describe '#track_project_repository' do - let(:project) { create(:project, :repository) } + shared_examples 'tracks storage location' do + context 'when a project repository entry does not exist' do + it 'creates a new entry' do + expect { project.track_project_repository }.to change(project, :project_repository) + end + + it 'tracks the project storage location' do + project.track_project_repository - it 'creates a project_repository' do - project.track_project_repository + expect(project.project_repository).to have_attributes( + disk_path: project.disk_path, + shard_name: project.repository_storage + ) + end + end + + context 'when a tracking entry exists' do + let!(:project_repository) { create(:project_repository, project: project) } + let!(:shard) { create(:shard, name: 'foo') } + + it 'does not create a new entry in the database' do + expect { project.track_project_repository }.not_to change(project, :project_repository) + end - expect(project.reload.project_repository).to be_present - expect(project.project_repository.disk_path).to eq(project.disk_path) - expect(project.project_repository.shard_name).to eq(project.repository_storage) + it 'updates the project storage location' do + allow(project).to receive(:disk_path).and_return('fancy/new/path') + allow(project).to receive(:repository_storage).and_return('foo') + + project.track_project_repository + + expect(project.project_repository).to have_attributes( + disk_path: 'fancy/new/path', + shard_name: 'foo' + ) + end + end end - it 'updates the project_repository' do - project.track_project_repository + context 'with projects on legacy storage' do + let(:project) { create(:project, :repository, :legacy_storage) } - allow(project).to receive(:disk_path).and_return('@fancy/new/path') + it_behaves_like 'tracks storage location' + end - expect do - project.track_project_repository - end.not_to change(ProjectRepository, :count) + context 'with projects on hashed storage' do + let(:project) { create(:project, :repository) } - expect(project.reload.project_repository.disk_path).to eq(project.disk_path) + it_behaves_like 'tracks storage location' end end diff --git a/spec/services/projects/after_rename_service_spec.rb b/spec/services/projects/after_rename_service_spec.rb index b4718a07204..59c08b30f9f 100644 --- a/spec/services/projects/after_rename_service_spec.rb +++ b/spec/services/projects/after_rename_service_spec.rb @@ -99,6 +99,17 @@ describe Projects::AfterRenameService do expect(rugged_config['gitlab.fullpath']).to eq(project.full_path) end + + it 'updates storage location' do + allow(project_storage).to receive(:rename_repo).and_return(true) + + described_class.new(project).execute + + expect(project.project_repository).to have_attributes( + disk_path: project.disk_path, + shard_name: project.repository_storage + ) + end end context 'using hashed storage' do @@ -193,6 +204,15 @@ describe Projects::AfterRenameService do expect(rugged_config['gitlab.fullpath']).to eq(project.full_path) end + + it 'updates storage location' do + described_class.new(project).execute + + expect(project.project_repository).to have_attributes( + disk_path: project.disk_path, + shard_name: project.repository_storage + ) + end end end end diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb index 132ad9a2646..766276fdba3 100644 --- a/spec/services/projects/transfer_service_spec.rb +++ b/spec/services/projects/transfer_service_spec.rb @@ -63,6 +63,15 @@ describe Projects::TransferService do expect(rugged_config['gitlab.fullpath']).to eq "#{group.full_path}/#{project.path}" end + it 'updates storage location' do + transfer_project(project, user, group) + + expect(project.project_repository).to have_attributes( + disk_path: "#{group.full_path}/#{project.path}", + shard_name: project.repository_storage + ) + end + context 'new group has a kubernetes cluster' do let(:group_cluster) { create(:cluster, :group, :provided_by_gcp) } let(:group) { group_cluster.group } @@ -139,6 +148,17 @@ describe Projects::TransferService do expect(service).not_to receive(:execute_system_hooks) end end + + it 'does not update storage location' do + create(:project_repository, project: project) + + attempt_project_transfer + + expect(project.project_repository).to have_attributes( + disk_path: project.disk_path, + shard_name: project.repository_storage + ) + end end context 'namespace -> no namespace' do |