Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
9bbcab8301
commit
b3cd77e904
|
@ -0,0 +1,3 @@
|
||||||
|
<template>
|
||||||
|
<router-view />
|
||||||
|
</template>
|
|
@ -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');
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -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: {
|
|
@ -0,0 +1,2 @@
|
||||||
|
// eslint-disable-next-line import/prefer-default-export
|
||||||
|
export const HOME_ROUTE_NAME = 'home';
|
|
@ -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;
|
||||||
|
}
|
|
@ -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,
|
||||||
|
},
|
||||||
|
];
|
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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?
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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' }
|
||||||
|
|
|
@ -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 }
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Remove deprecated Release Evidence endpoints documentation
|
||||||
|
merge_request: 30978
|
||||||
|
author:
|
||||||
|
type: removed
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Fixed alignment of Snippet Clone copy buttons
|
||||||
|
merge_request: 30897
|
||||||
|
author:
|
||||||
|
type: fixed
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Add migration bot user
|
||||||
|
merge_request: 30738
|
||||||
|
author:
|
||||||
|
type: added
|
|
@ -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
|
||||||
|
|
|
@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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 GitLab’s Kubernetes integration now ships
|
The JupyterHub app offered via GitLab’s Kubernetes integration now ships
|
||||||
with Nurtch’s Rubix library, providing a simple way to create DevOps
|
with Nurtch’s 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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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}'")
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'airborne'
|
||||||
require 'securerandom'
|
require 'securerandom'
|
||||||
|
|
||||||
module QA
|
module QA
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'airborne'
|
||||||
require 'securerandom'
|
require 'securerandom'
|
||||||
require 'digest'
|
require 'digest'
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
|
@ -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,
|
||||||
});
|
});
|
|
@ -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>
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue