Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-05-04 21:09:41 +00:00
parent 9bbcab8301
commit b3cd77e904
45 changed files with 404 additions and 188 deletions

View File

@ -0,0 +1,3 @@
<template>
<router-view />
</template>

View File

@ -1,10 +1,11 @@
import Vue from 'vue'; import Vue from 'vue';
import { parseBoolean } from '~/lib/utils/common_utils'; import { parseBoolean } from '~/lib/utils/common_utils';
import StaticSiteEditor from './components/static_site_editor.vue'; import App from './components/app.vue';
import createStore from './store'; import createStore from './store';
import createRouter from './router';
const initStaticSiteEditor = el => { const initStaticSiteEditor = el => {
const { isSupportedContent, projectId, path: sourcePath, returnUrl } = el.dataset; const { isSupportedContent, projectId, path: sourcePath, returnUrl, baseUrl } = el.dataset;
const store = createStore({ const store = createStore({
initialState: { initialState: {
@ -15,15 +16,17 @@ const initStaticSiteEditor = el => {
username: window.gon.current_username, username: window.gon.current_username,
}, },
}); });
const router = createRouter(baseUrl);
return new Vue({ return new Vue({
el, el,
store, store,
router,
components: { components: {
StaticSiteEditor, App,
}, },
render(createElement) { render(createElement) {
return createElement('static-site-editor', StaticSiteEditor); return createElement('app');
}, },
}); });
}; };

View File

@ -2,12 +2,12 @@
import { mapState, mapGetters, mapActions } from 'vuex'; import { mapState, mapGetters, mapActions } from 'vuex';
import { GlSkeletonLoader } from '@gitlab/ui'; import { GlSkeletonLoader } from '@gitlab/ui';
import EditArea from './edit_area.vue'; import EditArea from '../components/edit_area.vue';
import EditHeader from './edit_header.vue'; import EditHeader from '../components/edit_header.vue';
import SavedChangesMessage from './saved_changes_message.vue'; import SavedChangesMessage from '../components/saved_changes_message.vue';
import PublishToolbar from './publish_toolbar.vue'; import PublishToolbar from '../components/publish_toolbar.vue';
import InvalidContentMessage from './invalid_content_message.vue'; import InvalidContentMessage from '../components/invalid_content_message.vue';
import SubmitChangesError from './submit_changes_error.vue'; import SubmitChangesError from '../components/submit_changes_error.vue';
export default { export default {
components: { components: {

View File

@ -0,0 +1,2 @@
// eslint-disable-next-line import/prefer-default-export
export const HOME_ROUTE_NAME = 'home';

View File

@ -0,0 +1,15 @@
import Vue from 'vue';
import VueRouter from 'vue-router';
import routes from './routes';
Vue.use(VueRouter);
export default function createRouter(base) {
const router = new VueRouter({
base,
mode: 'history',
routes,
});
return router;
}

View File

@ -0,0 +1,10 @@
import Home from '../pages/home.vue';
import { HOME_ROUTE_NAME } from './constants';
export default [
{
name: HOME_ROUTE_NAME,
path: '/',
component: Home,
},
];

View File

@ -4,7 +4,6 @@ import {
GlNewDropdownHeader, GlNewDropdownHeader,
GlFormInputGroup, GlFormInputGroup,
GlButton, GlButton,
GlIcon,
GlTooltipDirective, GlTooltipDirective,
} from '@gitlab/ui'; } from '@gitlab/ui';
import { __, sprintf } from '~/locale'; import { __, sprintf } from '~/locale';
@ -16,7 +15,6 @@ export default {
GlNewDropdownHeader, GlNewDropdownHeader,
GlFormInputGroup, GlFormInputGroup,
GlButton, GlButton,
GlIcon,
}, },
directives: { directives: {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
@ -59,9 +57,9 @@ export default {
v-gl-tooltip.hover v-gl-tooltip.hover
:title="$options.copyURLTooltip" :title="$options.copyURLTooltip"
:data-clipboard-text="sshLink" :data-clipboard-text="sshLink"
> icon="copy-to-clipboard"
<gl-icon name="copy-to-clipboard" :title="$options.copyURLTooltip" /> class="d-inline-flex"
</gl-button> />
</template> </template>
</gl-form-input-group> </gl-form-input-group>
</div> </div>
@ -77,9 +75,9 @@ export default {
v-gl-tooltip.hover v-gl-tooltip.hover
:title="$options.copyURLTooltip" :title="$options.copyURLTooltip"
:data-clipboard-text="httpLink" :data-clipboard-text="httpLink"
> icon="copy-to-clipboard"
<gl-icon name="copy-to-clipboard" :title="$options.copyURLTooltip" /> class="d-inline-flex"
</gl-button> />
</template> </template>
</gl-form-input-group> </gl-form-input-group>
</div> </div>

View File

@ -10,10 +10,11 @@ module HasUserType
visual_review_bot: 3, visual_review_bot: 3,
service_user: 4, service_user: 4,
ghost: 5, ghost: 5,
project_bot: 6 project_bot: 6,
migration_bot: 7
}.with_indifferent_access.freeze }.with_indifferent_access.freeze
BOT_USER_TYPES = %w[alert_bot project_bot support_bot visual_review_bot].freeze BOT_USER_TYPES = %w[alert_bot project_bot support_bot visual_review_bot migration_bot].freeze
NON_INTERNAL_USER_TYPES = %w[human project_bot service_user].freeze NON_INTERNAL_USER_TYPES = %w[human project_bot service_user].freeze
INTERNAL_USER_TYPES = (USER_TYPES.keys - NON_INTERNAL_USER_TYPES).freeze INTERNAL_USER_TYPES = (USER_TYPES.keys - NON_INTERNAL_USER_TYPES).freeze

View File

@ -636,6 +636,16 @@ class User < ApplicationRecord
end end
end end
def migration_bot
email_pattern = "noreply+gitlab-migration-bot%s@#{Settings.gitlab.host}"
unique_internal(where(user_type: :migration_bot), 'migration-bot', email_pattern) do |u|
u.bio = 'The GitLab migration bot'
u.name = 'GitLab Migration Bot'
u.confirmed_at = Time.zone.now
end
end
# Return true if there is only single non-internal user in the deployment, # Return true if there is only single non-internal user in the deployment,
# ghost user is ignored. # ghost user is ignored.
def single_user? def single_user?

View File

@ -18,6 +18,7 @@ class GlobalPolicy < BasePolicy
condition(:private_instance_statistics, score: 0) { Gitlab::CurrentSettings.instance_statistics_visibility_private? } condition(:private_instance_statistics, score: 0) { Gitlab::CurrentSettings.instance_statistics_visibility_private? }
condition(:project_bot, scope: :user) { @user&.project_bot? } condition(:project_bot, scope: :user) { @user&.project_bot? }
condition(:migration_bot, scope: :user) { @user&.migration_bot? }
rule { admin | (~private_instance_statistics & ~anonymous) } rule { admin | (~private_instance_statistics & ~anonymous) }
.enable :read_instance_statistics .enable :read_instance_statistics
@ -48,11 +49,14 @@ class GlobalPolicy < BasePolicy
rule { blocked | internal }.policy do rule { blocked | internal }.policy do
prevent :log_in prevent :log_in
prevent :access_api prevent :access_api
prevent :access_git
prevent :receive_notifications prevent :receive_notifications
prevent :use_slash_commands prevent :use_slash_commands
end end
rule { blocked | (internal & ~migration_bot) }.policy do
prevent :access_git
end
rule { project_bot }.policy do rule { project_bot }.policy do
prevent :log_in prevent :log_in
prevent :receive_notifications prevent :receive_notifications

View File

@ -14,7 +14,7 @@
.col.form-group .col.form-group
= f.label :expires_at, s_('Profiles|Expires at'), class: 'label-bold' = f.label :expires_at, s_('Profiles|Expires at'), class: 'label-bold'
= f.date_field :expires_at, class: "form-control input-lg qa-key-expiry-field", min: Date.tomorrow = f.date_field :expires_at, class: "form-control input-lg", min: Date.tomorrow, data: { qa_selector: 'key_expiry_date_field' }
.js-add-ssh-key-validation-warning.hide .js-add-ssh-key-validation-warning.hide
.bs-callout.bs-callout-warning{ role: 'alert', aria_live: 'assertive' } .bs-callout.bs-callout-warning{ role: 'alert', aria_live: 'assertive' }

View File

@ -1 +1,4 @@
#static-site-editor{ data: @config.payload } -# TODO: Remove after base URL is included in the model !30735
- data = @config.payload.merge({ base_url: namespace_project_show_sse_path })
#static-site-editor{ data: data }

View File

@ -0,0 +1,5 @@
---
title: Remove deprecated Release Evidence endpoints documentation
merge_request: 30978
author:
type: removed

View File

@ -0,0 +1,5 @@
---
title: Fixed alignment of Snippet Clone copy buttons
merge_request: 30897
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Add migration bot user
merge_request: 30738
author:
type: added

View File

@ -201,6 +201,7 @@ namespaced
namespaces namespaces
Nanoc Nanoc
NGINX NGINX
Nurtch
OAuth OAuth
Okta Okta
offboarded offboarded
@ -277,6 +278,7 @@ resync
reverified reverified
reverifies reverifies
reverify reverify
Rubix
runbook runbook
runbooks runbooks
runit runit

View File

@ -286,7 +286,6 @@ Example response:
], ],
"commit_path":"/root/awesome-app/commit/588440f66559714280628a4f9799f0c4eb880a4a", "commit_path":"/root/awesome-app/commit/588440f66559714280628a4f9799f0c4eb880a4a",
"tag_path":"/root/awesome-app/-/tags/v0.11.1", "tag_path":"/root/awesome-app/-/tags/v0.11.1",
"evidence_sha":"760d6cdfb0879c3ffedec13af470e0f71cf52c6cde4d",
"assets":{ "assets":{
"count":5, "count":5,
"sources":[ "sources":[
@ -314,9 +313,15 @@ Example response:
"url":"https://gitlab.example.com/root/awesome-app/-/tags/v0.11.1/binaries/linux-amd64", "url":"https://gitlab.example.com/root/awesome-app/-/tags/v0.11.1/binaries/linux-amd64",
"external":true "external":true
} }
], ]
"evidence_url":"https://gitlab.example.com/root/awesome-app/-/releases/v0.1/evidence.json"
}, },
"evidences":[
{
sha: "760d6cdfb0879c3ffedec13af470e0f71cf52c6cde4d",
filepath: "https://gitlab.example.com/root/awesome-app/-/releases/v0.1/evidence.json",
collected_at: "2019-07-16T14:00:12.256Z"
}
]
} }
``` ```

View File

@ -366,7 +366,7 @@ will also see ready-to-use DevOps Runbooks built with Nurtch's [Rubix library](h
More information on More information on
creating executable runbooks can be found in [our Runbooks creating executable runbooks can be found in [our Runbooks
documentation](../project/clusters/runbooks/index.md#executable-runbooks). Note that documentation](../project/clusters/runbooks/index.md#configure-an-executable-runbook-with-gitlab). Note that
Ingress must be installed and have an IP address assigned before Ingress must be installed and have an IP address assigned before
JupyterHub can be installed. JupyterHub can be installed.

View File

@ -4,11 +4,10 @@ Runbooks are a collection of documented procedures that explain how to
carry out a particular process, be it starting, stopping, debugging, carry out a particular process, be it starting, stopping, debugging,
or troubleshooting a particular system. or troubleshooting a particular system.
Using [Jupyter Notebooks](https://jupyter.org/) and the [Rubix library](https://github.com/Nurtch/rubix), Using [Jupyter Notebooks](https://jupyter.org/) and the
[Rubix library](https://github.com/Nurtch/rubix),
users can get started writing their own executable runbooks. users can get started writing their own executable runbooks.
## Overview
Historically, runbooks took the form of a decision tree or a detailed Historically, runbooks took the form of a decision tree or a detailed
step-by-step guide depending on the condition or system. step-by-step guide depending on the condition or system.
@ -22,121 +21,128 @@ pre-written code blocks or database queries against a given environment.
The JupyterHub app offered via GitLabs Kubernetes integration now ships The JupyterHub app offered via GitLabs Kubernetes integration now ships
with Nurtchs Rubix library, providing a simple way to create DevOps with Nurtchs Rubix library, providing a simple way to create DevOps
runbooks. A sample runbook is provided, showcasing common operations. While Rubix makes it runbooks. A sample runbook is provided, showcasing common operations. While
simple to create common Kubernetes and AWS workflows, you can also create them manually without Rubix makes it simple to create common Kubernetes and AWS workflows, you can
Rubix. also create them manually without Rubix.
**<i class="fa fa-youtube-play youtube" aria-hidden="true"></i> <i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
Watch this [video](https://www.youtube.com/watch?v=Q_OqHIIUPjE) Watch this [video](https://www.youtube.com/watch?v=Q_OqHIIUPjE)
for an overview of how this is accomplished in GitLab!** for an overview of how this is accomplished in GitLab!
## Requirements ## Requirements
To create an executable runbook, you will need: To create an executable runbook, you will need:
1. **Kubernetes** - A Kubernetes cluster is required to deploy the rest of the applications. - **Kubernetes** - A Kubernetes cluster is required to deploy the rest of the
The simplest way to get started is to add a cluster using one of [GitLab's integrations](../add_remove_clusters.md#create-new-cluster). applications. The simplest way to get started is to add a cluster using one
1. **Helm Tiller** - Helm is a package manager for Kubernetes and is required to install of [GitLab's integrations](../add_remove_clusters.md#create-new-cluster).
all the other applications. It is installed in its own pod inside the cluster which - **Helm Tiller** - Helm is a package manager for Kubernetes and is required to
can run the Helm CLI in a safe environment. install all the other applications. It's installed in its own pod inside the
1. **Ingress** - Ingress can provide load balancing, SSL termination, and name-based cluster which can run the Helm CLI in a safe environment.
virtual hosting. It acts as a web proxy for your applications. - **Ingress** - Ingress can provide load balancing, SSL termination, and name-based
1. **JupyterHub** - [JupyterHub](https://jupyterhub.readthedocs.io/) is a multi-user service for managing notebooks across virtual hosting. It acts as a web proxy for your applications.
a team. Jupyter Notebooks provide a web-based interactive programming environment - **JupyterHub** - [JupyterHub](https://jupyterhub.readthedocs.io/) is a multi-user
used for data analysis, visualization, and machine learning. service for managing notebooks across a team. Jupyter Notebooks provide a
web-based interactive programming environment used for data analysis,
visualization, and machine learning.
## Nurtch ## Nurtch
Nurtch is the company behind the [Rubix library](https://github.com/Nurtch/rubix). Rubix is Nurtch is the company behind the [Rubix library](https://github.com/Nurtch/rubix).
an open-source Python library that makes it easy to perform common DevOps tasks inside Jupyter Notebooks. Rubix is an open-source Python library that makes it easy to perform common
Tasks such as plotting Cloudwatch metrics and rolling your ECS/Kubernetes app are simplified DevOps tasks inside Jupyter Notebooks. Tasks such as plotting Cloudwatch metrics
down to a couple of lines of code. See the [Nurtch Documentation](http://docs.nurtch.com/en/latest/) and rolling your ECS/Kubernetes app are simplified down to a couple of lines of
for more information. code. See the [Nurtch Documentation](http://docs.nurtch.com/en/latest/) for more
information.
## Configure an executable runbook with GitLab ## Configure an executable runbook with GitLab
Follow this step-by-step guide to configure an executable runbook in GitLab using Follow this step-by-step guide to configure an executable runbook in GitLab using
the components outlined above and the preloaded demo runbook. the components outlined above and the pre-loaded demo runbook.
### 1. Add a Kubernetes cluster 1. Add a Kubernetes cluster to your project by following the steps outlined in
[Create new cluster](../add_remove_clusters.md#create-new-cluster).
1. After the cluster has been provisioned in GKE, click the **Install** button
next to the **Helm Tiller** application to install Helm Tiller.
Follow the steps outlined in [Create new cluster](../add_remove_clusters.md#create-new-cluster) ![install helm](img/helm-install.png)
to add a Kubernetes cluster to your project.
### 2. Install Helm Tiller, Ingress, and JupyterHub 1. After Helm Tiller has been installed successfully, click the **Install** button next
to the **Ingress** application.
Once the cluster has been provisioned in GKE, click the **Install** button next to the **Helm Tiller** app. ![install ingress](img/ingress-install.png)
![install helm](img/helm-install.png) 1. After Ingress has been installed successfully, click the **Install** button next
to the **JupyterHub** application. You will need the **Jupyter Hostname** provided
here in the next step.
Once Tiller has been installed successfully, click the **Install** button next to the **Ingress** app. ![install JupyterHub](img/jupyterhub-install.png)
![install ingress](img/ingress-install.png) 1. After JupyterHub has been installed successfully, open the **Jupyter Hostname**
in your browser. Click the **Sign in with GitLab** button to log in to
JupyterHub and start the server. Authentication is enabled for any user of the
GitLab instance with OAuth2. This button redirects you to a page at GitLab
requesting authorization for JupyterHub to use your GitLab account.
Once Ingress has been installed successfully, click the **Install** button next to the **JupyterHub** app. ![authorize Jupyter](img/authorize-jupyter.png)
![install jupyterhub](img/jupyterhub-install.png) 1. Click **Authorize**, and you will be redirected to the JupyterHub application.
1. Click **Start My Server**, and the server will start in a few seconds.
1. To configure the runbook's access to your GitLab project, you must enter your
[GitLab Access Token](../../../profile/personal_access_tokens.md)
and your Project ID in the **Setup** section of the demo runbook:
### 3. Login to JupyterHub and start the server 1. Double-click the **DevOps-Runbook-Demo** folder located on the left panel.
Once JupyterHub has been installed successfully, navigate to the displayed **Jupyter Hostname** URL and click ![demo runbook](img/demo-runbook.png)
**Sign in with GitLab**. Authentication is automatically enabled for any user of the GitLab instance via OAuth2. This
will redirect to GitLab in order to authorize JupyterHub to use your GitLab account. Click **Authorize**.
![authorize jupyter](img/authorize-jupyter.png) 1. Double-click the `Nurtch-DevOps-Demo.ipynb` runbook.
Once the application has been authorized you will taken back to the JupyterHub application. Click **Start My Server**. ![sample runbook](img/sample-runbook.png)
The server will take a couple of seconds to start.
### 4. Configure access Jupyter displays the runbook's contents in the right-hand side of the screen.
The **Setup** section displays your `PRIVATE_TOKEN` and your `PROJECT_ID`.
Enter these values, maintaining the single quotes as follows:
In order for the runbook to access your GitLab project, you will need to enter a ```sql
[GitLab Access Token](../../../profile/personal_access_tokens.md) PRIVATE_TOKEN = 'n671WNGecHugsdEDPsyo'
as well as your Project ID in the **Setup** section of the demo runbook. PROJECT_ID = '1234567'
```
Double-click the **DevOps-Runbook-Demo** folder located on the left panel. 1. Update the `VARIABLE_NAME` on the last line of this section to match the name of
the variable you're using for your access token. In this example, our variable
name is `PRIVATE_TOKEN`.
![demo runbook](img/demo-runbook.png) ```sql
VARIABLE_VALUE = project.variables.get('PRIVATE_TOKEN').value
```
Double-click the "Nurtch-DevOps-Demo.ipynb" runbook. 1. To configure the operation of a runbook, create and configure variables:
![sample runbook](img/sample-runbook.png) NOTE: **Note:**
For this example, we are using the **Run SQL queries in Notebook** section in the
sample runbook to query a PostgreSQL database. The first four lines of the following
code block define the variables that are required for this query to function:
The contents on the runbook will be displayed on the right side of the screen. Under the "Setup" section, you will find ```sql
entries for both your `PRIVATE_TOKEN` and your `PROJECT_ID`. Enter both these values, conserving the single quotes as follows: %env DB_USER={project.variables.get('DB_USER').value}
%env DB_PASSWORD={project.variables.get('DB_PASSWORD').value}
%env DB_ENDPOINT={project.variables.get('DB_ENDPOINT').value}
%env DB_NAME={project.variables.get('DB_NAME').value}
```
```sql 1. Navigate to **{settings}** **Settings >> CI/CD >> Variables** to create
PRIVATE_TOKEN = 'n671WNGecHugsdEDPsyo' the variables in your project.
PROJECT_ID = '1234567'
```
Update the `VARIABLE_NAME` on the last line of this section to match the name of the variable you are using for your ![GitLab variables](img/gitlab-variables.png)
access token. In this example our variable name is `PRIVATE_TOKEN`.
```sql 1. Click **Save variables**.
VARIABLE_VALUE = project.variables.get('PRIVATE_TOKEN').value
```
### 5. Configure an operation 1. In Jupyter, click the **Run SQL queries in Notebook** heading, and then click
**Run**. The results are displayed inline as follows:
For this example we'll use the "**Run SQL queries in Notebook**" section in the sample runbook to query ![PostgreSQL query](img/postgres-query.png)
a PostgreSQL database. The first 4 lines of the section define the variables that are required for this query to function.
```sql You can try other operations, such as running shell scripts or interacting with a
%env DB_USER={project.variables.get('DB_USER').value} Kubernetes cluster. Visit the
%env DB_PASSWORD={project.variables.get('DB_PASSWORD').value}
%env DB_ENDPOINT={project.variables.get('DB_ENDPOINT').value}
%env DB_NAME={project.variables.get('DB_NAME').value}
```
Create the matching variables in your project's **Settings >> CI/CD >> Variables**
![gitlab variables](img/gitlab-variables.png)
Back in Jupyter, click the "Run SQL queries in Notebook" heading and the click *Run*. The results will be
displayed in-line as follows:
![PostgreSQL query](img/postgres-query.png)
You can try other operations such as running shell scripts or interacting with a Kubernetes cluster. Visit the
[Nurtch Documentation](http://docs.nurtch.com/) for more information. [Nurtch Documentation](http://docs.nurtch.com/) for more information.

View File

@ -931,6 +931,14 @@ Prerequisites for embedding from a Grafana instance:
1. In GitLab, paste the URL into a Markdown field and save. The chart will take a few moments to render. 1. In GitLab, paste the URL into a Markdown field and save. The chart will take a few moments to render.
![GitLab Rendered Grafana Panel](img/rendered_grafana_embed_v12_5.png) ![GitLab Rendered Grafana Panel](img/rendered_grafana_embed_v12_5.png)
## Metrics dashboard visibility
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/201924) in GitLab 13.0.
You can set the visibility of the metrics dashboard to **Only Project Members**
or **Everyone With Access**. When set to **Everyone with Access**, the metrics
dashboard, and all of the custom dashboard, YAML files, are visible to authenticated and non-authenticated users.
## Troubleshooting ## Troubleshooting
When troubleshooting issues with a managed Prometheus app, it is often useful to When troubleshooting issues with a managed Prometheus app, it is often useful to

View File

@ -43,10 +43,10 @@ To add a new feature flag:
1. Click on the **New Feature Flag** button. 1. Click on the **New Feature Flag** button.
1. Give it a name. 1. Give it a name.
NOTE: **Note:** NOTE: **Note:**
A name can contain only lowercase letters, digits, underscores (`_`) A name can contain only lowercase letters, digits, underscores (`_`)
and dashes (`-`), must start with a letter, and cannot end with a dash (`-`) and dashes (`-`), must start with a letter, and cannot end with a dash (`-`)
or an underscore (`_`). or an underscore (`_`).
1. Give it a description (optional, 255 characters max). 1. Give it a description (optional, 255 characters max).
1. Define environment [specs](#define-environment-specs). If you want the flag on by default, enable the catch-all [wildcard spec (`*`)](#define-environment-specs) 1. Define environment [specs](#define-environment-specs). If you want the flag on by default, enable the catch-all [wildcard spec (`*`)](#define-environment-specs)
@ -91,6 +91,41 @@ NOTE: **NOTE**
We'd highly recommend you to use the [Environment](../../../ci/environments.md) We'd highly recommend you to use the [Environment](../../../ci/environments.md)
feature in order to quickly assess which flag is enabled per environment. feature in order to quickly assess which flag is enabled per environment.
## Feature flag behavior change in 13.0
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/35555) in GitLab 13.0.
Starting in GitLab 13.0, you can apply a feature flag strategy across multiple environment specs,
without defining the strategy multiple times.
This feature is under development and not ready for production use. It is
deployed behind a feature flag that is **disabled by default**.
[GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md)
can enable it for your instance.
To enable it:
```ruby
Feature.enable(:feature_flags_new_version)
```
To disable it:
```ruby
Feature.disable(:feature_flags_new_version)
```
### Applying a strategy to environments
After a strategy is defined, it applies to **All Environments** by default. To
make a strategy apply to a specific environment spec:
1. Click the **Add Environment** button.
1. Create a new
[spec](../../../ci/environments.md#scoping-environments-with-specs).
To apply the strategy to multiple environment specs, repeat these steps.
## Feature Flag strategies ## Feature Flag strategies
GitLab Feature Flag adopts [Unleash](https://unleash.github.io) GitLab Feature Flag adopts [Unleash](https://unleash.github.io)
@ -155,12 +190,12 @@ To get the access credentials that your application will need to talk to GitLab:
1. Navigate to your project's **Operations > Feature Flags**. 1. Navigate to your project's **Operations > Feature Flags**.
1. Click on the **Configure** button to see the values: 1. Click on the **Configure** button to see the values:
- **API URL**: URL where the client (application) connects to get a list of feature flags. - **API URL**: URL where the client (application) connects to get a list of feature flags.
- **Instance ID**: Unique token that authorizes the retrieval of the feature flags. - **Instance ID**: Unique token that authorizes the retrieval of the feature flags.
- **Application name**: The name of the running environment. For instance, - **Application name**: The name of the running environment. For instance,
if the application runs for production server, application name would be if the application runs for a production server, application name would be
`production` or similar. This value is used for `production` or similar. This value is used for
[the environment spec evaluation](#define-environment-specs). [the environment spec evaluation](#define-environment-specs).
NOTE: **Note:** NOTE: **Note:**
The meaning of these fields might change over time. For example, we are not sure The meaning of these fields might change over time. For example, we are not sure

View File

@ -7,7 +7,8 @@ gem 'capybara-screenshot', '~> 1.0.23'
gem 'rake', '~> 12.3.0' gem 'rake', '~> 12.3.0'
gem 'rspec', '~> 3.7' gem 'rspec', '~> 3.7'
gem 'selenium-webdriver', '~> 3.12' gem 'selenium-webdriver', '~> 3.12'
gem 'airborne', '~> 0.2.13' gem 'airborne', '~> 0.3.4'
gem 'rest-client', '~> 2.1.0'
gem 'nokogiri', '~> 1.10.9' gem 'nokogiri', '~> 1.10.9'
gem 'rspec-retry', '~> 0.6.1' gem 'rspec-retry', '~> 0.6.1'
gem 'rspec_junit_formatter', '~> 0.4.1' gem 'rspec_junit_formatter', '~> 0.4.1'

View File

@ -9,12 +9,12 @@ GEM
zeitwerk (~> 2.2) zeitwerk (~> 2.2)
addressable (2.7.0) addressable (2.7.0)
public_suffix (>= 2.0.2, < 5.0) public_suffix (>= 2.0.2, < 5.0)
airborne (0.2.13) airborne (0.3.4)
activesupport activesupport
rack rack
rack-test (~> 0.6, >= 0.6.2) rack-test (>= 1.1.0, < 2.0)
rest-client (>= 1.7.3, < 3.0) rest-client (>= 2.0.2, < 3.0)
rspec (~> 3.1) rspec (~> 3.8)
byebug (9.1.0) byebug (9.1.0)
capybara (3.29.0) capybara (3.29.0)
addressable addressable
@ -34,11 +34,12 @@ GEM
debase-ruby_core_source (>= 0.10.2) debase-ruby_core_source (>= 0.10.2)
debase-ruby_core_source (0.10.6) debase-ruby_core_source (0.10.6)
diff-lcs (1.3) diff-lcs (1.3)
domain_name (0.5.20170404) domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0) unf (>= 0.0.5, < 1.0.0)
faker (1.9.3) faker (1.9.3)
i18n (>= 0.7) i18n (>= 0.7)
gitlab-qa (4.0.0) gitlab-qa (4.0.0)
http-accept (1.7.0)
http-cookie (1.0.3) http-cookie (1.0.3)
domain_name (~> 0.5) domain_name (~> 0.5)
i18n (1.8.2) i18n (1.8.2)
@ -48,9 +49,9 @@ GEM
launchy (2.4.3) launchy (2.4.3)
addressable (~> 2.3) addressable (~> 2.3)
method_source (0.9.0) method_source (0.9.0)
mime-types (3.1) mime-types (3.3.1)
mime-types-data (~> 3.2015) mime-types-data (~> 3.2015)
mime-types-data (3.2016.0521) mime-types-data (3.2020.0425)
mini_mime (1.0.2) mini_mime (1.0.2)
mini_portile2 (2.4.0) mini_portile2 (2.4.0)
minitest (5.14.0) minitest (5.14.0)
@ -67,30 +68,31 @@ GEM
byebug (~> 9.1) byebug (~> 9.1)
pry (~> 0.10) pry (~> 0.10)
public_suffix (4.0.1) public_suffix (4.0.1)
rack (2.0.7) rack (2.2.2)
rack-test (0.8.3) rack-test (1.1.0)
rack (>= 1.0, < 3) rack (>= 1.0, < 3)
rake (12.3.0) rake (12.3.0)
regexp_parser (1.6.0) regexp_parser (1.6.0)
rest-client (2.0.2) rest-client (2.1.0)
http-accept (>= 1.7.0, < 2.0)
http-cookie (>= 1.0.2, < 2.0) http-cookie (>= 1.0.2, < 2.0)
mime-types (>= 1.16, < 4.0) mime-types (>= 1.16, < 4.0)
netrc (~> 0.8) netrc (~> 0.8)
rspec (3.7.0) rspec (3.9.0)
rspec-core (~> 3.7.0) rspec-core (~> 3.9.0)
rspec-expectations (~> 3.7.0) rspec-expectations (~> 3.9.0)
rspec-mocks (~> 3.7.0) rspec-mocks (~> 3.9.0)
rspec-core (3.7.1) rspec-core (3.9.2)
rspec-support (~> 3.7.0) rspec-support (~> 3.9.3)
rspec-expectations (3.7.0) rspec-expectations (3.9.1)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.7.0) rspec-support (~> 3.9.0)
rspec-mocks (3.7.0) rspec-mocks (3.9.1)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.7.0) rspec-support (~> 3.9.0)
rspec-retry (0.6.1) rspec-retry (0.6.1)
rspec-core (> 3.3) rspec-core (> 3.3)
rspec-support (3.7.0) rspec-support (3.9.3)
rspec_junit_formatter (0.4.1) rspec_junit_formatter (0.4.1)
rspec-core (>= 2, < 4, != 2.12.0) rspec-core (>= 2, < 4, != 2.12.0)
ruby-debug-ide (0.7.2) ruby-debug-ide (0.7.2)
@ -101,11 +103,11 @@ GEM
rubyzip (>= 1.2.2) rubyzip (>= 1.2.2)
thread_safe (0.3.6) thread_safe (0.3.6)
timecop (0.9.1) timecop (0.9.1)
tzinfo (1.2.6) tzinfo (1.2.7)
thread_safe (~> 0.1) thread_safe (~> 0.1)
unf (0.1.4) unf (0.1.4)
unf_ext unf_ext
unf_ext (0.0.7.4) unf_ext (0.0.7.7)
xpath (3.2.0) xpath (3.2.0)
nokogiri (~> 1.8) nokogiri (~> 1.8)
zeitwerk (2.3.0) zeitwerk (2.3.0)
@ -115,7 +117,7 @@ PLATFORMS
DEPENDENCIES DEPENDENCIES
activesupport (~> 6.0.2.2) activesupport (~> 6.0.2.2)
airborne (~> 0.2.13) airborne (~> 0.3.4)
capybara (~> 3.29.0) capybara (~> 3.29.0)
capybara-screenshot (~> 1.0.23) capybara-screenshot (~> 1.0.23)
debase (~> 0.2.4.1) debase (~> 0.2.4.1)
@ -126,6 +128,7 @@ DEPENDENCIES
parallel_tests (~> 2.29) parallel_tests (~> 2.29)
pry-byebug (~> 3.5.1) pry-byebug (~> 3.5.1)
rake (~> 12.3.0) rake (~> 12.3.0)
rest-client (~> 2.1.0)
rspec (~> 3.7) rspec (~> 3.7)
rspec-retry (~> 0.6.1) rspec-retry (~> 0.6.1)
rspec_junit_formatter (~> 0.4.1) rspec_junit_formatter (~> 0.4.1)

View File

@ -5,6 +5,7 @@ module QA
module Profile module Profile
class SSHKeys < Page::Base class SSHKeys < Page::Base
view 'app/views/profiles/keys/_form.html.haml' do view 'app/views/profiles/keys/_form.html.haml' do
element :key_expiry_date_field
element :key_title_field element :key_title_field
element :key_public_key_field element :key_public_key_field
element :add_key_button element :add_key_button
@ -19,17 +20,26 @@ module QA
end end
def add_key(public_key, title) def add_key(public_key, title)
fill_element :key_public_key_field, public_key fill_element(:key_public_key_field, public_key)
fill_element :key_title_field, title fill_element(:key_title_field, title)
# Expire in 2 days just in case the key is created just before midnight
fill_expiry_date(Date.today + 2)
click_element :add_key_button click_element(:add_key_button)
end
def fill_expiry_date(date)
date = date.strftime('%m/%d/%Y') if date.is_a?(Date)
Date.strptime(date, '%m/%d/%Y') rescue ArgumentError raise "Expiry date must be in mm/dd/yyyy format"
fill_element(:key_expiry_date_field, date)
end end
def remove_key(title) def remove_key(title)
click_link(title) click_link(title)
accept_alert do accept_alert do
click_element :delete_key_button click_element(:delete_key_button)
end end
end end

View File

@ -5,12 +5,16 @@ module QA
class SSHKey < Base class SSHKey < Base
extend Forwardable extend Forwardable
attr_accessor :title attr_reader :title
attribute :id attribute :id
def_delegators :key, :private_key, :public_key, :md5_fingerprint def_delegators :key, :private_key, :public_key, :md5_fingerprint
def initialize
self.title = Time.now.to_f
end
def key def key
@key ||= Runtime::Key::RSA.new @key ||= Runtime::Key::RSA.new
end end
@ -28,6 +32,10 @@ module QA
api_post api_post
end end
def title=(title)
@title = "E2E test key: #{title}"
end
def api_delete def api_delete
QA::Runtime::Logger.debug("Deleting SSH key with title '#{title}' and fingerprint '#{md5_fingerprint}'") QA::Runtime::Logger.debug("Deleting SSH key with title '#{title}' and fingerprint '#{md5_fingerprint}'")

View File

@ -1,7 +1,5 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'airborne'
module QA module QA
module Runtime module Runtime
module API module API

View File

@ -1,5 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'airborne'
module QA module QA
context 'Manage with IP rate limits', :requires_admin do context 'Manage with IP rate limits', :requires_admin do
describe 'Users API' do describe 'Users API' do

View File

@ -1,5 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'airborne'
module QA module QA
context 'Manage' do context 'Manage' do
describe 'Users API' do describe 'Users API' do

View File

@ -1,5 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'airborne'
module QA module QA
context 'Plan' do context 'Plan' do
include Support::Api include Support::Api

View File

@ -1,5 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'airborne'
require 'securerandom' require 'securerandom'
module QA module QA

View File

@ -1,5 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'airborne'
require 'securerandom' require 'securerandom'
require 'digest' require 'digest'

View File

@ -3,6 +3,8 @@
module QA module QA
context 'Plan', :orchestrated, :smtp do context 'Plan', :orchestrated, :smtp do
describe 'Email Notification' do describe 'Email Notification' do
include Support::Api
let(:user) do let(:user) do
Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_1, Runtime::Env.gitlab_qa_password_1) Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_1, Runtime::Env.gitlab_qa_password_1)
end end

View File

@ -12,7 +12,7 @@ module QA
resource.title = key_title resource.title = key_title
end end
expect(page).to have_content("Title: #{key_title}") expect(page).to have_content(key.title)
expect(page).to have_content(key.md5_fingerprint) expect(page).to have_content(key.md5_fingerprint)
Page::Main::Menu.perform(&:click_settings_link) Page::Main::Menu.perform(&:click_settings_link)

View File

@ -3,6 +3,8 @@
module QA module QA
context 'Create', :requires_admin do context 'Create', :requires_admin do
describe 'push after setting the file size limit via admin/application_settings' do describe 'push after setting the file size limit via admin/application_settings' do
include Support::Api
before(:context) do before(:context) do
@project = Resource::Project.fabricate_via_api! do |p| @project = Resource::Project.fabricate_via_api! do |p|
p.name = 'project-test-push-limit' p.name = 'project-test-push-limit'
@ -39,12 +41,10 @@ module QA
def set_file_size_limit(limit) def set_file_size_limit(limit)
request = Runtime::API::Request.new(@api_client, '/application/settings') request = Runtime::API::Request.new(@api_client, '/application/settings')
put request.url, receive_max_input_size: limit response = put request.url, receive_max_input_size: limit
expect_status(200) expect(response.code).to eq(200)
expect(json_body).to match( expect(parse_body(response)[:receive_max_input_size]).to eq(limit)
a_hash_including(receive_max_input_size: limit)
)
end end
def push_new_file(file_name, wait_for_push: true) def push_new_file(file_name, wait_for_push: true)

View File

@ -1,5 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'rest-client'
module QA module QA
module Support module Support
module Api module Api

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
describe QA::Resource::SSHKey do
describe '#key' do
it 'generates a default key' do
expect(subject.key).to be_a(QA::Runtime::Key::RSA)
end
end
describe '#title' do
it 'generates a default title' do
expect(subject.title).to match(/E2E test key: \d+/)
end
it 'is possible to set the title' do
subject.title = 'I am in a title'
expect(subject.title).to eq('E2E test key: I am in a title')
end
end
end

View File

@ -31,14 +31,6 @@ RSpec.configure do |c|
config.color_mode = :off config.color_mode = :off
# Load airborne again to avoid "undefined method `match_expected_default?'" errors
# that happen because a hook calls a method added via a custom RSpec setting
# that is removed when the RSpec configuration is sandboxed.
# If this needs to be changed (e.g., to load other libraries as well), see
# this discussion for alternative solutions:
# https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/25223#note_143392053
load 'airborne.rb'
ex.run ex.run
end end
end end

View File

@ -31,6 +31,10 @@ FactoryBot.define do
user_type { :project_bot } user_type { :project_bot }
end end
trait :migration_bot do
user_type { :migration_bot }
end
trait :external do trait :external do
external { true } external { true }
end end

View File

@ -1,6 +1,7 @@
import MockAdapter from 'axios-mock-adapter'; import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import PipelineMediator from '~/pipelines/pipeline_details_mediator'; import PipelineMediator from '~/pipelines/pipeline_details_mediator';
import waitForPromises from 'helpers/wait_for_promises';
describe('PipelineMdediator', () => { describe('PipelineMdediator', () => {
let mediator; let mediator;
@ -23,14 +24,13 @@ describe('PipelineMdediator', () => {
}); });
describe('request and store data', () => { describe('request and store data', () => {
it('should store received data', done => { it('should store received data', () => {
mock.onGet('foo.json').reply(200, { id: '121123' }); mock.onGet('foo.json').reply(200, { id: '121123' });
mediator.fetchPipeline(); mediator.fetchPipeline();
setTimeout(() => { return waitForPromises().then(() => {
expect(mediator.store.state.pipeline).toEqual({ id: '121123' }); expect(mediator.store.state.pipeline).toEqual({ id: '121123' });
done(); });
}, 0);
}); });
}); });
}); });

View File

@ -4,7 +4,7 @@ import { GlSkeletonLoader } from '@gitlab/ui';
import createState from '~/static_site_editor/store/state'; import createState from '~/static_site_editor/store/state';
import StaticSiteEditor from '~/static_site_editor/components/static_site_editor.vue'; import Home from '~/static_site_editor/pages/home.vue';
import EditArea from '~/static_site_editor/components/edit_area.vue'; import EditArea from '~/static_site_editor/components/edit_area.vue';
import EditHeader from '~/static_site_editor/components/edit_header.vue'; import EditHeader from '~/static_site_editor/components/edit_header.vue';
import InvalidContentMessage from '~/static_site_editor/components/invalid_content_message.vue'; import InvalidContentMessage from '~/static_site_editor/components/invalid_content_message.vue';
@ -24,7 +24,7 @@ const localVue = createLocalVue();
localVue.use(Vuex); localVue.use(Vuex);
describe('StaticSiteEditor', () => { describe('static_site_editor/pages/home', () => {
let wrapper; let wrapper;
let store; let store;
let loadContentActionMock; let loadContentActionMock;
@ -68,7 +68,7 @@ describe('StaticSiteEditor', () => {
}; };
const buildWrapper = () => { const buildWrapper = () => {
wrapper = shallowMount(StaticSiteEditor, { wrapper = shallowMount(Home, {
localVue, localVue,
store, store,
}); });

View File

@ -44,18 +44,13 @@ exports[`Clone Dropdown Button rendering matches the snapshot 1`] = `
> >
<gl-button-stub <gl-button-stub
category="tertiary" category="tertiary"
class="d-inline-flex"
data-clipboard-text="ssh://foo.bar" data-clipboard-text="ssh://foo.bar"
icon="" icon="copy-to-clipboard"
size="medium" size="medium"
title="Copy URL" title="Copy URL"
variant="default" variant="default"
> />
<gl-icon-stub
name="copy-to-clipboard"
size="16"
title="Copy URL"
/>
</gl-button-stub>
</b-input-group-append-stub> </b-input-group-append-stub>
</b-input-group-stub> </b-input-group-stub>
</div> </div>
@ -94,18 +89,13 @@ exports[`Clone Dropdown Button rendering matches the snapshot 1`] = `
> >
<gl-button-stub <gl-button-stub
category="tertiary" category="tertiary"
class="d-inline-flex"
data-clipboard-text="http://foo.bar" data-clipboard-text="http://foo.bar"
icon="" icon="copy-to-clipboard"
size="medium" size="medium"
title="Copy URL" title="Copy URL"
variant="default" variant="default"
> />
<gl-icon-stub
name="copy-to-clipboard"
size="16"
title="Copy URL"
/>
</gl-button-stub>
</b-input-group-append-stub> </b-input-group-append-stub>
</b-input-group-stub> </b-input-group-stub>
</div> </div>

View File

@ -4,8 +4,8 @@ require 'spec_helper'
describe User do describe User do
specify 'types consistency checks', :aggregate_failures do specify 'types consistency checks', :aggregate_failures do
expect(described_class::USER_TYPES) expect(described_class::USER_TYPES.keys)
.to include(*%i[human ghost alert_bot project_bot support_bot service_user visual_review_bot]) .to match_array(%w[human ghost alert_bot project_bot support_bot service_user visual_review_bot migration_bot])
expect(described_class::USER_TYPES).to include(*described_class::BOT_USER_TYPES) expect(described_class::USER_TYPES).to include(*described_class::BOT_USER_TYPES)
expect(described_class::USER_TYPES).to include(*described_class::NON_INTERNAL_USER_TYPES) expect(described_class::USER_TYPES).to include(*described_class::NON_INTERNAL_USER_TYPES)
expect(described_class::USER_TYPES).to include(*described_class::INTERNAL_USER_TYPES) expect(described_class::USER_TYPES).to include(*described_class::INTERNAL_USER_TYPES)

View File

@ -4584,4 +4584,20 @@ describe User, :do_not_mock_admin_mode do
it_behaves_like 'does not require password to be present' it_behaves_like 'does not require password to be present'
end end
end end
describe '#migration_bot' do
it 'creates the user if it does not exist' do
expect do
described_class.migration_bot
end.to change { User.where(user_type: :migration_bot).count }.by(1)
end
it 'does not create a new user if it already exists' do
described_class.migration_bot
expect do
described_class.migration_bot
end.not_to change { User.count }
end
end
end end

View File

@ -6,6 +6,7 @@ describe GlobalPolicy do
include TermsHelper include TermsHelper
let_it_be(:project_bot) { create(:user, :project_bot) } let_it_be(:project_bot) { create(:user, :project_bot) }
let_it_be(:migration_bot) { create(:user, :migration_bot) }
let(:current_user) { create(:user) } let(:current_user) { create(:user) }
let(:user) { create(:user) } let(:user) { create(:user) }
@ -155,6 +156,12 @@ describe GlobalPolicy do
it { is_expected.to be_allowed(:access_api) } it { is_expected.to be_allowed(:access_api) }
end end
context 'migration bot' do
let(:current_user) { migration_bot }
it { is_expected.not_to be_allowed(:access_api) }
end
context 'when terms are enforced' do context 'when terms are enforced' do
before do before do
enforce_terms enforce_terms
@ -244,6 +251,12 @@ describe GlobalPolicy do
it { is_expected.not_to be_allowed(:receive_notifications) } it { is_expected.not_to be_allowed(:receive_notifications) }
end end
context 'migration bot' do
let(:current_user) { migration_bot }
it { is_expected.not_to be_allowed(:receive_notifications) }
end
end end
describe 'git access' do describe 'git access' do
@ -263,6 +276,12 @@ describe GlobalPolicy do
it { is_expected.to be_allowed(:access_git) } it { is_expected.to be_allowed(:access_git) }
end end
context 'migration bot' do
let(:current_user) { migration_bot }
it { is_expected.to be_allowed(:access_git) }
end
describe 'deactivated user' do describe 'deactivated user' do
before do before do
current_user.deactivate current_user.deactivate
@ -414,6 +433,12 @@ describe GlobalPolicy do
it { is_expected.to be_allowed(:use_slash_commands) } it { is_expected.to be_allowed(:use_slash_commands) }
end end
context 'migration bot' do
let(:current_user) { migration_bot }
it { is_expected.not_to be_allowed(:use_slash_commands) }
end
end end
describe 'create_snippet' do describe 'create_snippet' do
@ -440,5 +465,11 @@ describe GlobalPolicy do
it { is_expected.not_to be_allowed(:log_in) } it { is_expected.not_to be_allowed(:log_in) }
end end
context 'migration bot' do
let(:current_user) { migration_bot }
it { is_expected.not_to be_allowed(:log_in) }
end
end end
end end

View File

@ -27,6 +27,16 @@ describe 'admin/users/_user.html.haml' do
expect(rendered).not_to have_selector('.table-action-buttons') expect(rendered).not_to have_selector('.table-action-buttons')
end end
end end
context 'when showing a `Migration User`' do
let(:user) { create(:user, user_type: :migration_bot) }
it 'does not render action buttons' do
render
expect(rendered).not_to have_selector('.table-action-buttons')
end
end
end end
context 'when showing an external user' do context 'when showing an external user' do