Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
017841e3c0
commit
89cb3fa774
35 changed files with 715 additions and 29 deletions
|
@ -102,6 +102,7 @@ include:
|
|||
- local: .gitlab/ci/qa.gitlab-ci.yml
|
||||
- local: .gitlab/ci/reports.gitlab-ci.yml
|
||||
- local: .gitlab/ci/rails.gitlab-ci.yml
|
||||
- local: .gitlab/ci/vendored-gems.gitlab-ci.yml
|
||||
- local: .gitlab/ci/review.gitlab-ci.yml
|
||||
- local: .gitlab/ci/rules.gitlab-ci.yml
|
||||
- local: .gitlab/ci/setup.gitlab-ci.yml
|
||||
|
|
|
@ -920,6 +920,16 @@
|
|||
- <<: *if-merge-request
|
||||
changes: [".gitlab/ci/rails.gitlab-ci.yml"]
|
||||
|
||||
#######################
|
||||
# Vendored gems rules #
|
||||
#######################
|
||||
|
||||
.vendor:rules:mail-smtp_pool:
|
||||
rules:
|
||||
- <<: *if-merge-request
|
||||
changes: ["vendor/gems/mail-smtp_pool/**/*"]
|
||||
- <<: *if-merge-request-title-run-all-rspec
|
||||
|
||||
##################
|
||||
# Releases rules #
|
||||
##################
|
||||
|
|
7
.gitlab/ci/vendored-gems.gitlab-ci.yml
Normal file
7
.gitlab/ci/vendored-gems.gitlab-ci.yml
Normal file
|
@ -0,0 +1,7 @@
|
|||
vendor mail-smtp_pool:
|
||||
extends:
|
||||
- .vendor:rules:mail-smtp_pool
|
||||
needs: []
|
||||
trigger:
|
||||
include: vendor/gems/mail-smtp_pool/.gitlab-ci.yml
|
||||
strategy: depend
|
1
Gemfile
1
Gemfile
|
@ -511,6 +511,7 @@ gem 'erubi', '~> 1.9.0'
|
|||
# Monkey-patched in `config/initializers/mail_encoding_patch.rb`
|
||||
# See https://gitlab.com/gitlab-org/gitlab/issues/197386
|
||||
gem 'mail', '= 2.7.1'
|
||||
gem 'mail-smtp_pool', '~> 0.1.0', path: 'vendor/gems/mail-smtp_pool', require: false
|
||||
|
||||
# File encryption
|
||||
gem 'lockbox', '~> 0.6.2'
|
||||
|
|
|
@ -1,3 +1,10 @@
|
|||
PATH
|
||||
remote: vendor/gems/mail-smtp_pool
|
||||
specs:
|
||||
mail-smtp_pool (0.1.0)
|
||||
connection_pool (~> 2.0)
|
||||
mail (~> 2.7)
|
||||
|
||||
PATH
|
||||
remote: vendor/shims/mimemagic
|
||||
specs:
|
||||
|
@ -1483,6 +1490,7 @@ DEPENDENCIES
|
|||
loofah (~> 2.2)
|
||||
lru_redux
|
||||
mail (= 2.7.1)
|
||||
mail-smtp_pool (~> 0.1.0)!
|
||||
marginalia (~> 1.10.0)
|
||||
memory_profiler (~> 0.9)
|
||||
method_source (~> 1.0)
|
||||
|
|
|
@ -7,6 +7,7 @@ import {
|
|||
GlFormCombobox,
|
||||
GlFormGroup,
|
||||
GlFormSelect,
|
||||
GlFormInput,
|
||||
GlFormTextarea,
|
||||
GlIcon,
|
||||
GlLink,
|
||||
|
@ -41,6 +42,7 @@ export default {
|
|||
GlFormCombobox,
|
||||
GlFormGroup,
|
||||
GlFormSelect,
|
||||
GlFormInput,
|
||||
GlFormTextarea,
|
||||
GlIcon,
|
||||
GlLink,
|
||||
|
@ -128,6 +130,12 @@ export default {
|
|||
|
||||
return true;
|
||||
},
|
||||
scopedVariablesEnabled() {
|
||||
return !this.isGroup || this.glFeatures.scopedGroupVariables;
|
||||
},
|
||||
scopedVariablesAvailable() {
|
||||
return !this.isGroup || this.glFeatures.groupScopedCiVariables;
|
||||
},
|
||||
variableValidationFeedback() {
|
||||
return `${this.tokenValidationFeedback} ${this.maskedFeedback}`;
|
||||
},
|
||||
|
@ -226,24 +234,27 @@ export default {
|
|||
:label="__('Type')"
|
||||
label-for="ci-variable-type"
|
||||
class="w-50 gl-mr-5"
|
||||
:class="{ 'w-100': isGroup }"
|
||||
:class="{ 'w-100': !scopedVariablesEnabled }"
|
||||
>
|
||||
<gl-form-select id="ci-variable-type" v-model="variable_type" :options="typeOptions" />
|
||||
</gl-form-group>
|
||||
|
||||
<gl-form-group
|
||||
v-if="!isGroup"
|
||||
v-if="scopedVariablesEnabled"
|
||||
:label="__('Environment scope')"
|
||||
label-for="ci-variable-env"
|
||||
class="w-50"
|
||||
data-testid="environment-scope"
|
||||
>
|
||||
<ci-environments-dropdown
|
||||
v-if="scopedVariablesAvailable"
|
||||
class="w-100"
|
||||
:value="environment_scope"
|
||||
@selectEnvironment="setEnvironmentScope"
|
||||
@createClicked="addWildCardScope"
|
||||
/>
|
||||
|
||||
<gl-form-input v-else v-model="environment_scope" class="w-100" readonly />
|
||||
</gl-form-group>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
import { GlTable, GlButton, GlModalDirective, GlIcon } from '@gitlab/ui';
|
||||
import { mapState, mapActions } from 'vuex';
|
||||
import { s__, __ } from '~/locale';
|
||||
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
import { ADD_CI_VARIABLE_MODAL_ID } from '../constants';
|
||||
import CiVariablePopover from './ci_variable_popover.vue';
|
||||
|
||||
|
@ -59,6 +60,7 @@ export default {
|
|||
directives: {
|
||||
GlModalDirective,
|
||||
},
|
||||
mixins: [glFeatureFlagsMixin()],
|
||||
computed: {
|
||||
...mapState(['variables', 'valuesHidden', 'isGroup', 'isLoading', 'isDeleting']),
|
||||
valuesButtonText() {
|
||||
|
@ -68,7 +70,7 @@ export default {
|
|||
return this.variables && this.variables.length > 0;
|
||||
},
|
||||
fields() {
|
||||
if (this.isGroup) {
|
||||
if (this.isGroup && !this.glFeatures.scopedGroupVariables) {
|
||||
return this.$options.fields.filter((field) => field.key !== 'environment_scope');
|
||||
}
|
||||
return this.$options.fields;
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
import Vue from 'vue';
|
||||
import ContributorsGraphs from './components/contributors.vue';
|
||||
import store from './stores';
|
||||
import { createStore } from './stores';
|
||||
|
||||
export default () => {
|
||||
const el = document.querySelector('.js-contributors-graph');
|
||||
|
||||
if (!el) return null;
|
||||
|
||||
const { projectGraphPath, projectBranch, defaultBranch } = el.dataset;
|
||||
const store = createStore(defaultBranch);
|
||||
|
||||
return new Vue({
|
||||
el,
|
||||
store,
|
||||
|
@ -14,8 +17,8 @@ export default () => {
|
|||
render(createElement) {
|
||||
return createElement(ContributorsGraphs, {
|
||||
props: {
|
||||
endpoint: el.dataset.projectGraphPath,
|
||||
branch: el.dataset.projectBranch,
|
||||
endpoint: projectGraphPath,
|
||||
branch: projectBranch,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
|
|
@ -7,12 +7,12 @@ import state from './state';
|
|||
|
||||
Vue.use(Vuex);
|
||||
|
||||
export const createStore = () =>
|
||||
export const createStore = (defaultBranch) =>
|
||||
new Vuex.Store({
|
||||
actions,
|
||||
mutations,
|
||||
getters,
|
||||
state: state(),
|
||||
state: state(defaultBranch),
|
||||
});
|
||||
|
||||
export default createStore();
|
||||
export default createStore;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
export default () => ({
|
||||
export default (branch) => ({
|
||||
loading: false,
|
||||
chartData: null,
|
||||
branch: 'master',
|
||||
branch,
|
||||
});
|
||||
|
|
|
@ -58,9 +58,13 @@ export default {
|
|||
<span v-if="request.hasWarnings">(!)</span>
|
||||
</option>
|
||||
</select>
|
||||
<span v-if="requestsWithWarnings.length">
|
||||
<span v-if="requestsWithWarnings.length" class="gl-cursor-default">
|
||||
<span id="performance-bar-request-selector-warning" v-html="glEmojiTag('warning')"></span>
|
||||
<gl-popover target="performance-bar-request-selector-warning" :content="warningMessage" />
|
||||
<gl-popover
|
||||
placement="bottom"
|
||||
target="performance-bar-request-selector-warning"
|
||||
:content="warningMessage"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -35,8 +35,8 @@ export default {
|
|||
};
|
||||
</script>
|
||||
<template>
|
||||
<span v-if="hasWarnings">
|
||||
<span v-if="hasWarnings" class="gl-cursor-default">
|
||||
<span :id="htmlId" v-html="glEmojiTag('warning')"></span>
|
||||
<gl-popover :target="htmlId" :content="warningMessage" />
|
||||
<gl-popover placement="bottom" :target="htmlId" :content="warningMessage" />
|
||||
</span>
|
||||
</template>
|
||||
|
|
|
@ -10,6 +10,8 @@ module Groups
|
|||
before_action :authorize_admin_group!
|
||||
before_action :authorize_update_max_artifacts_size!, only: [:update]
|
||||
before_action :define_variables, only: [:show]
|
||||
before_action :push_feature_flags, only: [:show]
|
||||
before_action :push_licensed_features, only: [:show]
|
||||
|
||||
feature_category :continuous_integration
|
||||
|
||||
|
@ -91,6 +93,16 @@ module Groups
|
|||
def update_group_params
|
||||
params.require(:group).permit(:max_artifacts_size)
|
||||
end
|
||||
|
||||
def push_feature_flags
|
||||
push_frontend_feature_flag(:scoped_group_variables, group)
|
||||
end
|
||||
|
||||
# Overridden in EE
|
||||
def push_licensed_features
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Groups::Settings::CiCdController.prepend_if_ee('EE::Groups::Settings::CiCdController')
|
||||
|
|
|
@ -56,3 +56,5 @@ module Groups
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
Groups::VariablesController.prepend_if_ee('EE::Groups::VariablesController')
|
||||
|
|
|
@ -86,7 +86,7 @@ class Group < Namespace
|
|||
validate :visibility_level_allowed_by_sub_groups
|
||||
validate :visibility_level_allowed_by_parent
|
||||
validate :two_factor_authentication_allowed
|
||||
validates :variables, nested_attributes_duplicates: true
|
||||
validates :variables, nested_attributes_duplicates: { scope: :environment_scope }
|
||||
|
||||
validates :two_factor_grace_period, presence: true, numericality: { greater_than_or_equal_to: 0 }
|
||||
|
||||
|
|
|
@ -2,5 +2,6 @@
|
|||
|
||||
module Ci
|
||||
class GroupVariableEntity < Ci::BasicVariableEntity
|
||||
expose :environment_scope
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,4 +5,4 @@
|
|||
= render 'shared/ref_switcher', destination: 'graphs'
|
||||
= link_to s_('Commits|History'), project_commits_path(@project, current_ref), class: 'btn gl-button btn-default'
|
||||
|
||||
.js-contributors-graph{ class: container_class, 'data-project-graph-path': project_graph_path(@project, current_ref, format: :json), 'data-project-branch': current_ref }
|
||||
.js-contributors-graph{ class: container_class, data: { project_graph_path: project_graph_path(@project, current_ref, format: :json), project_branch: current_ref, default_branch: @project.default_branch } }
|
||||
|
|
5
changelogs/unreleased/230717-add-mail-smtp-pool-gem.yml
Normal file
5
changelogs/unreleased/230717-add-mail-smtp-pool-gem.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add support for SMTP connection pooling when sending emails
|
||||
merge_request: 57805
|
||||
author:
|
||||
type: added
|
5
changelogs/unreleased/pb-popover-improvement.yml
Normal file
5
changelogs/unreleased/pb-popover-improvement.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Update popover placement and cursor on warning icon in PB
|
||||
merge_request: 58552
|
||||
author: Yogi (@yo)
|
||||
type: changed
|
|
@ -22,3 +22,28 @@ if Rails.env.production?
|
|||
openssl_verify_mode: 'peer' # See ActionMailer documentation for other possible options
|
||||
}
|
||||
end
|
||||
|
||||
# To use an SMTP connection pool, uncomment the following section:
|
||||
#
|
||||
# require 'mail/smtp_pool'
|
||||
#
|
||||
# ActionMailer::Base.add_delivery_method :smtp_pool, Mail::SMTPPool
|
||||
#
|
||||
# if Rails.env.production?
|
||||
# Rails.application.config.action_mailer.delivery_method = :smtp_pool
|
||||
#
|
||||
# ActionMailer::Base.delivery_method = :smtp_pool
|
||||
# ActionMailer::Base.smtp_pool_settings = {
|
||||
# pool: Mail::SMTPPool.create_pool(
|
||||
# pool_size: Gitlab::Runtime.max_threads,
|
||||
# address: "email.server.com",
|
||||
# port: 465,
|
||||
# user_name: "smtp",
|
||||
# password: "123456",
|
||||
# domain: "gitlab.company.com",
|
||||
# authentication: :login,
|
||||
# enable_starttls_auto: true,
|
||||
# openssl_verify_mode: 'peer' # See ActionMailer documentation for other possible options
|
||||
# )
|
||||
# }
|
||||
# end
|
||||
|
|
|
@ -20,7 +20,7 @@ Return all of the raw SQL queries used to compute usage ping.
|
|||
Example request:
|
||||
|
||||
```shell
|
||||
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/usage_data/queries
|
||||
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/usage_data/queries"
|
||||
```
|
||||
|
||||
Sample response
|
||||
|
@ -66,3 +66,51 @@ Sample response
|
|||
"ci_runners": "SELECT COUNT(\"ci_runners\".\"id\") FROM \"ci_runners\"",
|
||||
...
|
||||
```
|
||||
|
||||
## UsageDataNonSqlMetrics API
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/57050) in GitLab 13.11.
|
||||
> - [Deployed behind a feature flag](../user/feature_flags.md), disabled by default.
|
||||
|
||||
Return all non-SQL metrics data used in the usage ping.
|
||||
|
||||
Example request:
|
||||
|
||||
```shell
|
||||
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/usage_data/non_sql_metrics"
|
||||
```
|
||||
|
||||
Sample response:
|
||||
|
||||
```json
|
||||
{
|
||||
"recorded_at": "2021-03-26T07:04:03.724Z",
|
||||
"uuid": null,
|
||||
"hostname": "localhost",
|
||||
"version": "13.11.0-pre",
|
||||
"installation_type": "gitlab-development-kit",
|
||||
"active_user_count": -3,
|
||||
"edition": "EE",
|
||||
"license_md5": "bb8cd0d8a6d9569ff3f70b8927a1f949",
|
||||
"license_id": null,
|
||||
"historical_max_users": 0,
|
||||
"licensee": {
|
||||
"Name": "John Doe1"
|
||||
},
|
||||
"license_user_count": null,
|
||||
"license_starts_at": "1970-01-01",
|
||||
"license_expires_at": "2022-02-26",
|
||||
"license_plan": "starter",
|
||||
"license_add_ons": {
|
||||
"GitLab_FileLocks": 1,
|
||||
"GitLab_Auditor_User": 1
|
||||
},
|
||||
"license_trial": null,
|
||||
"license_subscription_id": "0000",
|
||||
"license": {},
|
||||
"settings": {
|
||||
"ldap_encrypted_secrets_enabled": false,
|
||||
"operating_system": "mac_os_x-11.2.2"
|
||||
},
|
||||
...
|
||||
```
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { GlButton } from '@gitlab/ui';
|
||||
import { GlButton, GlFormInput } from '@gitlab/ui';
|
||||
import { createLocalVue, shallowMount, mount } from '@vue/test-utils';
|
||||
import Vuex from 'vuex';
|
||||
import CiEnvironmentsDropdown from '~/ci_variable_list/components/ci_environments_dropdown.vue';
|
||||
import CiVariableModal from '~/ci_variable_list/components/ci_variable_modal.vue';
|
||||
import { AWS_ACCESS_KEY_ID } from '~/ci_variable_list/constants';
|
||||
import createStore from '~/ci_variable_list/store';
|
||||
|
@ -15,7 +16,7 @@ describe('Ci variable modal', () => {
|
|||
let store;
|
||||
|
||||
const createComponent = (method, options = {}) => {
|
||||
store = createStore();
|
||||
store = createStore({ isGroup: options.isGroup });
|
||||
wrapper = method(CiVariableModal, {
|
||||
attachTo: document.body,
|
||||
stubs: {
|
||||
|
@ -27,6 +28,7 @@ describe('Ci variable modal', () => {
|
|||
});
|
||||
};
|
||||
|
||||
const findCiEnvironmentsDropdown = () => wrapper.find(CiEnvironmentsDropdown);
|
||||
const findModal = () => wrapper.find(ModalStub);
|
||||
const findAddorUpdateButton = () =>
|
||||
findModal()
|
||||
|
@ -149,6 +151,61 @@ describe('Ci variable modal', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('Environment scope', () => {
|
||||
describe('group level variables', () => {
|
||||
it('renders the environment dropdown', () => {
|
||||
createComponent(shallowMount, {
|
||||
isGroup: true,
|
||||
provide: {
|
||||
glFeatures: {
|
||||
scopedGroupVariables: true,
|
||||
groupScopedCiVariables: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(findCiEnvironmentsDropdown().exists()).toBe(true);
|
||||
expect(findCiEnvironmentsDropdown().isVisible()).toBe(true);
|
||||
});
|
||||
|
||||
describe('feature flag is disabled', () => {
|
||||
it('hides the dropdown', () => {
|
||||
createComponent(shallowMount, {
|
||||
isGroup: true,
|
||||
provide: {
|
||||
glFeatures: {
|
||||
scopedGroupVariables: false,
|
||||
groupScopedCiVariables: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(findCiEnvironmentsDropdown().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('licensed feature is not available', () => {
|
||||
it('disables the dropdown', () => {
|
||||
createComponent(mount, {
|
||||
isGroup: true,
|
||||
provide: {
|
||||
glFeatures: {
|
||||
scopedGroupVariables: true,
|
||||
groupScopedCiVariables: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const environmentScopeInput = wrapper
|
||||
.find('[data-testid="environment-scope"]')
|
||||
.find(GlFormInput);
|
||||
expect(findCiEnvironmentsDropdown().exists()).toBe(false);
|
||||
expect(environmentScopeInput.attributes('readonly')).toBe('readonly');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Validations', () => {
|
||||
const maskError = 'This variable can not be masked.';
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { GlTable } from '@gitlab/ui';
|
||||
import { createLocalVue, mount } from '@vue/test-utils';
|
||||
import Vuex from 'vuex';
|
||||
import CiVariableTable from '~/ci_variable_list/components/ci_variable_table.vue';
|
||||
|
@ -12,21 +11,24 @@ describe('Ci variable table', () => {
|
|||
let wrapper;
|
||||
let store;
|
||||
|
||||
const createComponent = () => {
|
||||
store = createStore();
|
||||
store.state.isGroup = true;
|
||||
const createComponent = (isGroup = false, scopedGroupVariables = false) => {
|
||||
store = createStore({ isGroup });
|
||||
jest.spyOn(store, 'dispatch').mockImplementation();
|
||||
wrapper = mount(CiVariableTable, {
|
||||
attachTo: document.body,
|
||||
localVue,
|
||||
store,
|
||||
provide: {
|
||||
glFeatures: {
|
||||
scopedGroupVariables,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const findRevealButton = () => wrapper.find({ ref: 'secret-value-reveal-button' });
|
||||
const findEditButton = () => wrapper.find({ ref: 'edit-ci-variable' });
|
||||
const findEmptyVariablesPlaceholder = () => wrapper.find({ ref: 'empty-variables' });
|
||||
const findTable = () => wrapper.find(GlTable);
|
||||
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
|
@ -40,12 +42,14 @@ describe('Ci variable table', () => {
|
|||
expect(store.dispatch).toHaveBeenCalledWith('fetchVariables');
|
||||
});
|
||||
|
||||
it('fields prop does not contain environment_scope if group', () => {
|
||||
expect(findTable().props('fields')).not.toEqual(
|
||||
it('fields do not contain environment_scope if group level and feature is disabled', () => {
|
||||
createComponent(true, false);
|
||||
|
||||
expect(wrapper.vm.fields).not.toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
key: 'environment_scope',
|
||||
label: 'Environment Scope',
|
||||
label: 'Environments',
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
|
|
@ -10,7 +10,7 @@ RSpec.describe Ci::GroupVariableEntity do
|
|||
subject { entity.as_json }
|
||||
|
||||
it 'contains required fields' do
|
||||
expect(subject).to include(:id, :key, :value, :protected, :variable_type)
|
||||
expect(subject).to include(:id, :key, :value, :protected, :variable_type, :environment_scope)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
3
vendor/gems/mail-smtp_pool/.gitignore
vendored
Normal file
3
vendor/gems/mail-smtp_pool/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
Gemfile.lock
|
||||
*.gem
|
||||
.bundle
|
29
vendor/gems/mail-smtp_pool/.gitlab-ci.yml
vendored
Normal file
29
vendor/gems/mail-smtp_pool/.gitlab-ci.yml
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
workflow:
|
||||
rules:
|
||||
- if: $CI_MERGE_REQUEST_ID
|
||||
|
||||
.rspec:
|
||||
cache:
|
||||
key: mail-smtp_pool-ruby
|
||||
paths:
|
||||
- vendor/gems/mail-smtp_pool/vendor/ruby
|
||||
before_script:
|
||||
- cd vendor/gems/mail-smtp_pool
|
||||
- ruby -v # Print out ruby version for debugging
|
||||
- gem install bundler --no-document # Bundler is not installed with the image
|
||||
- bundle config set --local path 'vendor' # Install dependencies into ./vendor/ruby
|
||||
- bundle install -j $(nproc)
|
||||
script:
|
||||
- bundle exec rspec
|
||||
|
||||
rspec-2.6:
|
||||
image: "ruby:2.6"
|
||||
extends: .rspec
|
||||
|
||||
rspec-2.7:
|
||||
image: "ruby:2.7"
|
||||
extends: .rspec
|
||||
|
||||
rspec-3.0:
|
||||
image: "ruby:3.0"
|
||||
extends: .rspec
|
5
vendor/gems/mail-smtp_pool/Gemfile
vendored
Normal file
5
vendor/gems/mail-smtp_pool/Gemfile
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
source 'https://rubygems.org'
|
||||
|
||||
gemspec
|
21
vendor/gems/mail-smtp_pool/LICENSE
vendored
Normal file
21
vendor/gems/mail-smtp_pool/LICENSE
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2021 GitLab B.V.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
57
vendor/gems/mail-smtp_pool/README.md
vendored
Normal file
57
vendor/gems/mail-smtp_pool/README.md
vendored
Normal file
|
@ -0,0 +1,57 @@
|
|||
# Mail::SMTPPool
|
||||
|
||||
This gem is an extension to `Mail` that allows delivery of emails using an SMTP connection pool
|
||||
|
||||
## Installation
|
||||
|
||||
Add this line to your application's Gemfile:
|
||||
|
||||
```ruby
|
||||
gem 'mail-smtp_pool'
|
||||
```
|
||||
|
||||
And then execute:
|
||||
|
||||
```shell
|
||||
bundle
|
||||
```
|
||||
|
||||
Or install it yourself as:
|
||||
|
||||
```shell
|
||||
gem install mail-smtp_pool
|
||||
```
|
||||
|
||||
## Usage with ActionMailer
|
||||
|
||||
```ruby
|
||||
# config/environments/development.rb
|
||||
|
||||
Rails.application.configure do
|
||||
...
|
||||
|
||||
ActionMailer::Base.add_delivery_method :smtp_pool, Mail::SMTPPool
|
||||
|
||||
config.action_mailer.perform_deliveries = true
|
||||
config.action_mailer.smtp_pool_settings = {
|
||||
pool: Mail::SMTPPool.create_pool(
|
||||
pool_size: 5,
|
||||
pool_timeout: 5,
|
||||
address: 'smtp.gmail.com',
|
||||
port: 587,
|
||||
domain: 'example.com',
|
||||
user_name: '<username>',
|
||||
password: '<password>',
|
||||
authentication: 'plain',
|
||||
enable_starttls_auto: true
|
||||
)
|
||||
}
|
||||
end
|
||||
```
|
||||
|
||||
Configuration options:
|
||||
|
||||
* `pool_size` - The maximum number of SMTP connections in the pool. Connections are created lazily as needed.
|
||||
* `pool_timeout` - The number of seconds to wait for a connection in the pool to be available. A `Timeout::Error` exception is raised when this is exceeded.
|
||||
|
||||
This also accepts all options supported by `Mail::SMTP`. See https://www.rubydoc.info/gems/mail/2.6.1/Mail/SMTP for more information.
|
34
vendor/gems/mail-smtp_pool/lib/mail/smtp_pool.rb
vendored
Normal file
34
vendor/gems/mail-smtp_pool/lib/mail/smtp_pool.rb
vendored
Normal file
|
@ -0,0 +1,34 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'connection_pool'
|
||||
require 'mail/smtp_pool/connection'
|
||||
|
||||
module Mail
|
||||
class SMTPPool
|
||||
POOL_DEFAULTS = {
|
||||
pool_size: 5,
|
||||
pool_timeout: 5
|
||||
}.freeze
|
||||
|
||||
class << self
|
||||
def create_pool(settings = {})
|
||||
pool_settings = POOL_DEFAULTS.merge(settings)
|
||||
smtp_settings = settings.reject { |k, v| POOL_DEFAULTS.keys.include?(k) }
|
||||
|
||||
ConnectionPool.new(size: pool_settings[:pool_size], timeout: pool_settings[:pool_timeout]) do
|
||||
Mail::SMTPPool::Connection.new(smtp_settings)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(settings)
|
||||
raise ArgumentError, 'pool is required. You can create one using Mail::SMTPPool.create_pool.' if settings[:pool].nil?
|
||||
|
||||
@pool = settings[:pool]
|
||||
end
|
||||
|
||||
def deliver!(mail)
|
||||
@pool.with { |conn| conn.deliver!(mail) }
|
||||
end
|
||||
end
|
||||
end
|
60
vendor/gems/mail-smtp_pool/lib/mail/smtp_pool/connection.rb
vendored
Normal file
60
vendor/gems/mail-smtp_pool/lib/mail/smtp_pool/connection.rb
vendored
Normal file
|
@ -0,0 +1,60 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# A connection object that can be used to deliver mail.
|
||||
#
|
||||
# This is meant to be used in a pool so the main difference between this
|
||||
# and Mail::SMTP is that this expects deliver! to be called multiple times.
|
||||
#
|
||||
# SMTP connection reset and error handling is handled by this class and
|
||||
# the SMTP connection is not closed after a delivery.
|
||||
|
||||
require 'mail'
|
||||
|
||||
module Mail
|
||||
class SMTPPool
|
||||
class Connection < Mail::SMTP
|
||||
def initialize(values)
|
||||
super
|
||||
|
||||
@smtp_session = nil
|
||||
end
|
||||
|
||||
def deliver!(mail)
|
||||
response = Mail::SMTPConnection.new(connection: smtp_session, return_response: true).deliver!(mail)
|
||||
|
||||
settings[:return_response] ? response : self
|
||||
end
|
||||
|
||||
def finish
|
||||
finish_smtp_session if @smtp_session && @smtp_session.started?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def smtp_session
|
||||
return start_smtp_session if @smtp_session.nil? || !@smtp_session.started?
|
||||
return @smtp_session if reset_smtp_session
|
||||
|
||||
finish_smtp_session
|
||||
start_smtp_session
|
||||
end
|
||||
|
||||
def start_smtp_session
|
||||
@smtp_session = build_smtp_session.start(settings[:domain], settings[:user_name], settings[:password], settings[:authentication])
|
||||
end
|
||||
|
||||
def reset_smtp_session
|
||||
!@smtp_session.instance_variable_get(:@error_occurred) && @smtp_session.rset.success?
|
||||
rescue Net::SMTPError, IOError
|
||||
false
|
||||
end
|
||||
|
||||
def finish_smtp_session
|
||||
@smtp_session.finish
|
||||
rescue Net::SMTPError, IOError
|
||||
ensure
|
||||
@smtp_session = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
26
vendor/gems/mail-smtp_pool/mail-smtp_pool.gemspec
vendored
Normal file
26
vendor/gems/mail-smtp_pool/mail-smtp_pool.gemspec
vendored
Normal file
|
@ -0,0 +1,26 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
lib = File.expand_path('lib', __dir__)
|
||||
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
||||
|
||||
Gem::Specification.new do |spec|
|
||||
spec.name = 'mail-smtp_pool'
|
||||
spec.version = '0.1.0'
|
||||
spec.authors = ['Heinrich Lee Yu']
|
||||
spec.email = ['heinrich@gitlab.com']
|
||||
|
||||
spec.summary = 'Mail extension for sending using an SMTP connection pool'
|
||||
spec.homepage = 'https://gitlab.com/gitlab-org/gitlab/-/tree/master/vendor/gems/mail-smtp_pool'
|
||||
spec.metadata = { 'source_code_uri' => 'https://gitlab.com/gitlab-org/gitlab/-/tree/master/vendor/gems/mail-smtp_pool' }
|
||||
spec.license = 'MIT'
|
||||
|
||||
spec.files = Dir['lib/**/*.rb']
|
||||
spec.require_paths = ['lib']
|
||||
|
||||
# Please maintain alphabetical order for dependencies
|
||||
spec.add_runtime_dependency 'connection_pool', '~> 2.0'
|
||||
spec.add_runtime_dependency 'mail', '~> 2.7'
|
||||
|
||||
# Please maintain alphabetical order for dev dependencies
|
||||
spec.add_development_dependency 'rspec', '~> 3.10.0'
|
||||
end
|
93
vendor/gems/mail-smtp_pool/spec/lib/mail/smtp_pool/connection_spec.rb
vendored
Normal file
93
vendor/gems/mail-smtp_pool/spec/lib/mail/smtp_pool/connection_spec.rb
vendored
Normal file
|
@ -0,0 +1,93 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Mail::SMTPPool::Connection do
|
||||
let(:connection) { described_class.new({}) }
|
||||
let(:mail) do
|
||||
Mail.new do
|
||||
from 'mikel@test.lindsaar.net'
|
||||
to 'you@test.lindsaar.net'
|
||||
subject 'This is a test email'
|
||||
body 'Test body'
|
||||
end
|
||||
end
|
||||
|
||||
after do
|
||||
MockSMTP.clear_deliveries
|
||||
end
|
||||
|
||||
describe '#deliver!' do
|
||||
it 'delivers mail using the same SMTP connection' do
|
||||
mock_smtp = MockSMTP.new
|
||||
|
||||
expect(Net::SMTP).to receive(:new).once.and_return(mock_smtp)
|
||||
expect(mock_smtp).to receive(:sendmail).twice.and_call_original
|
||||
expect(mock_smtp).to receive(:rset).once.and_call_original
|
||||
|
||||
connection.deliver!(mail)
|
||||
connection.deliver!(mail)
|
||||
|
||||
expect(MockSMTP.deliveries.size).to eq(2)
|
||||
end
|
||||
|
||||
context 'when RSET fails' do
|
||||
let(:mock_smtp) { MockSMTP.new }
|
||||
let(:mock_smtp_2) { MockSMTP.new }
|
||||
|
||||
before do
|
||||
expect(Net::SMTP).to receive(:new).twice.and_return(mock_smtp, mock_smtp_2)
|
||||
end
|
||||
|
||||
context 'with an IOError' do
|
||||
before do
|
||||
expect(mock_smtp).to receive(:rset).once.and_raise(IOError)
|
||||
end
|
||||
|
||||
it 'creates a new SMTP connection' do
|
||||
expect(mock_smtp).to receive(:sendmail).once.and_call_original
|
||||
expect(mock_smtp).to receive(:finish).once.and_call_original
|
||||
expect(mock_smtp_2).to receive(:sendmail).once.and_call_original
|
||||
|
||||
connection.deliver!(mail)
|
||||
connection.deliver!(mail)
|
||||
|
||||
expect(MockSMTP.deliveries.size).to eq(2)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with an SMTP error' do
|
||||
before do
|
||||
expect(mock_smtp).to receive(:rset).once.and_raise(Net::SMTPServerBusy)
|
||||
end
|
||||
|
||||
it 'creates a new SMTP connection' do
|
||||
expect(mock_smtp).to receive(:sendmail).once.and_call_original
|
||||
expect(mock_smtp).to receive(:finish).once.and_call_original
|
||||
expect(mock_smtp_2).to receive(:sendmail).once.and_call_original
|
||||
|
||||
connection.deliver!(mail)
|
||||
connection.deliver!(mail)
|
||||
|
||||
expect(MockSMTP.deliveries.size).to eq(2)
|
||||
end
|
||||
|
||||
context 'and closing the old connection fails' do
|
||||
before do
|
||||
expect(mock_smtp).to receive(:finish).once.and_raise(IOError)
|
||||
end
|
||||
|
||||
it 'creates a new SMTP connection' do
|
||||
expect(mock_smtp).to receive(:sendmail).once.and_call_original
|
||||
expect(mock_smtp_2).to receive(:sendmail).once.and_call_original
|
||||
|
||||
connection.deliver!(mail)
|
||||
connection.deliver!(mail)
|
||||
|
||||
expect(MockSMTP.deliveries.size).to eq(2)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
68
vendor/gems/mail-smtp_pool/spec/lib/mail/smtp_pool_spec.rb
vendored
Normal file
68
vendor/gems/mail-smtp_pool/spec/lib/mail/smtp_pool_spec.rb
vendored
Normal file
|
@ -0,0 +1,68 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Mail::SMTPPool do
|
||||
describe '.create_pool' do
|
||||
it 'sets the default pool settings' do
|
||||
expect(ConnectionPool).to receive(:new).with(size: 5, timeout: 5).once
|
||||
|
||||
described_class.create_pool
|
||||
end
|
||||
|
||||
it 'allows overriding pool size and timeout' do
|
||||
expect(ConnectionPool).to receive(:new).with(size: 3, timeout: 2).once
|
||||
|
||||
described_class.create_pool(pool_size: 3, pool_timeout: 2)
|
||||
end
|
||||
|
||||
it 'creates an SMTP connection with the correct settings' do
|
||||
settings = { address: 'smtp.example.com', port: '465' }
|
||||
|
||||
smtp_pool = described_class.create_pool(settings)
|
||||
|
||||
expect(Mail::SMTPPool::Connection).to receive(:new).with(settings).once.and_call_original
|
||||
|
||||
smtp_pool.checkout
|
||||
end
|
||||
end
|
||||
|
||||
describe '#initialize' do
|
||||
it 'raises an error if a pool is not specified' do
|
||||
expect { described_class.new({}) }.to raise_error(
|
||||
ArgumentError, 'pool is required. You can create one using Mail::SMTPPool.create_pool.'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#deliver!' do
|
||||
let(:mail) do
|
||||
Mail.new do
|
||||
from 'mikel@test.lindsaar.net'
|
||||
to 'you@test.lindsaar.net'
|
||||
subject 'This is a test email'
|
||||
body 'Test body'
|
||||
end
|
||||
end
|
||||
|
||||
after do
|
||||
MockSMTP.clear_deliveries
|
||||
end
|
||||
|
||||
it 'delivers mail using a connection from the pool' do
|
||||
connection_pool = double(ConnectionPool)
|
||||
connection = double(Mail::SMTPPool::Connection)
|
||||
|
||||
expect(connection_pool).to receive(:with).and_yield(connection)
|
||||
expect(connection).to receive(:deliver!).with(mail)
|
||||
|
||||
described_class.new(pool: connection_pool).deliver!(mail)
|
||||
end
|
||||
|
||||
it 'delivers mail' do
|
||||
described_class.new(pool: described_class.create_pool).deliver!(mail)
|
||||
|
||||
expect(MockSMTP.deliveries.size).to eq(1)
|
||||
end
|
||||
end
|
||||
end
|
84
vendor/gems/mail-smtp_pool/spec/spec_helper.rb
vendored
Normal file
84
vendor/gems/mail-smtp_pool/spec/spec_helper.rb
vendored
Normal file
|
@ -0,0 +1,84 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'mail/smtp_pool'
|
||||
|
||||
# Original mockup from ActionMailer
|
||||
# Based on https://github.com/mikel/mail/blob/22a7afc23f253319965bf9228a0a430eec94e06d/spec/spec_helper.rb#L74-L138
|
||||
class MockSMTP
|
||||
def self.deliveries
|
||||
@@deliveries
|
||||
end
|
||||
|
||||
def self.security
|
||||
@@security
|
||||
end
|
||||
|
||||
def initialize
|
||||
@@deliveries = []
|
||||
@@security = nil
|
||||
@started = false
|
||||
end
|
||||
|
||||
def sendmail(mail, from, to)
|
||||
@@deliveries << [mail, from, to]
|
||||
'OK'
|
||||
end
|
||||
|
||||
def rset
|
||||
Net::SMTP::Response.parse('250 OK')
|
||||
end
|
||||
|
||||
def start(*args)
|
||||
@started = true
|
||||
|
||||
if block_given?
|
||||
result = yield(self)
|
||||
@started = false
|
||||
|
||||
return result
|
||||
else
|
||||
return self
|
||||
end
|
||||
end
|
||||
|
||||
def started?
|
||||
@started
|
||||
end
|
||||
|
||||
def finish
|
||||
@started = false
|
||||
return true
|
||||
end
|
||||
|
||||
def self.clear_deliveries
|
||||
@@deliveries = []
|
||||
end
|
||||
|
||||
def self.clear_security
|
||||
@@security = nil
|
||||
end
|
||||
|
||||
def enable_tls(context)
|
||||
raise ArgumentError, "SMTPS and STARTTLS is exclusive" if @@security && @@security != :enable_tls
|
||||
@@security = :enable_tls
|
||||
context
|
||||
end
|
||||
|
||||
def enable_starttls(context = nil)
|
||||
raise ArgumentError, "SMTPS and STARTTLS is exclusive" if @@security == :enable_tls
|
||||
@@security = :enable_starttls
|
||||
context
|
||||
end
|
||||
|
||||
def enable_starttls_auto(context)
|
||||
raise ArgumentError, "SMTPS and STARTTLS is exclusive" if @@security == :enable_tls
|
||||
@@security = :enable_starttls_auto
|
||||
context
|
||||
end
|
||||
end
|
||||
|
||||
class Net::SMTP
|
||||
def self.new(*args)
|
||||
MockSMTP.new
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue