Commit b18bd203 authored by Sarah Groff Hennigh-Palermo's avatar Sarah Groff Hennigh-Palermo

Merge branch 'jivanvl-runner-guided-install-frontend' into 'master'

Add install GitLab runner popup - FE

See merge request gitlab-org/gitlab!42877
parents b5fc0c85 8c7b8139
import initFilteredSearch from '~/pages/search/init_filtered_search';
import AdminRunnersFilteredSearchTokenKeys from '~/filtered_search/admin_runners_filtered_search_token_keys';
import { FILTERED_SEARCH } from '~/pages/constants';
import { initInstallRunner } from '~/pages/shared/mount_runner_instructions';
document.addEventListener('DOMContentLoaded', () => {
initFilteredSearch({
page: FILTERED_SEARCH.ADMIN_RUNNERS,
filteredSearchTokenKeys: AdminRunnersFilteredSearchTokenKeys,
useDefaultState: true,
});
initFilteredSearch({
page: FILTERED_SEARCH.ADMIN_RUNNERS,
filteredSearchTokenKeys: AdminRunnersFilteredSearchTokenKeys,
useDefaultState: true,
});
initInstallRunner();
......@@ -4,18 +4,18 @@ import initFilteredSearch from '~/pages/search/init_filtered_search';
import GroupRunnersFilteredSearchTokenKeys from '~/filtered_search/group_runners_filtered_search_token_keys';
import { FILTERED_SEARCH } from '~/pages/constants';
import initSharedRunnersForm from '~/group_settings/mount_shared_runners';
import { initInstallRunner } from '~/pages/shared/mount_runner_instructions';
document.addEventListener('DOMContentLoaded', () => {
// Initialize expandable settings panels
initSettingsPanels();
// Initialize expandable settings panels
initSettingsPanels();
initFilteredSearch({
page: FILTERED_SEARCH.ADMIN_RUNNERS,
filteredSearchTokenKeys: GroupRunnersFilteredSearchTokenKeys,
anchor: FILTERED_SEARCH.GROUP_RUNNERS_ANCHOR,
useDefaultState: false,
});
initSharedRunnersForm();
initVariableList();
initFilteredSearch({
page: FILTERED_SEARCH.ADMIN_RUNNERS,
filteredSearchTokenKeys: GroupRunnersFilteredSearchTokenKeys,
anchor: FILTERED_SEARCH.GROUP_RUNNERS_ANCHOR,
useDefaultState: false,
});
initSharedRunnersForm();
initVariableList();
initInstallRunner();
......@@ -4,32 +4,32 @@ import registrySettingsApp from '~/registry/settings/registry_settings_bundle';
import initVariableList from '~/ci_variable_list';
import initDeployFreeze from '~/deploy_freeze';
import initSettingsPipelinesTriggers from '~/ci_settings_pipeline_triggers';
import { initInstallRunner } from '~/pages/shared/mount_runner_instructions';
document.addEventListener('DOMContentLoaded', () => {
// Initialize expandable settings panels
initSettingsPanels();
// Initialize expandable settings panels
initSettingsPanels();
const runnerToken = document.querySelector('.js-secret-runner-token');
if (runnerToken) {
const runnerTokenSecretValue = new SecretValues({
container: runnerToken,
});
runnerTokenSecretValue.init();
}
initVariableList();
// hide extra auto devops settings based checkbox state
const autoDevOpsExtraSettings = document.querySelector('.js-extra-settings');
const instanceDefaultBadge = document.querySelector('.js-instance-default-badge');
document.querySelector('.js-toggle-extra-settings').addEventListener('click', event => {
const { target } = event;
if (instanceDefaultBadge) instanceDefaultBadge.style.display = 'none';
autoDevOpsExtraSettings.classList.toggle('hidden', !target.checked);
const runnerToken = document.querySelector('.js-secret-runner-token');
if (runnerToken) {
const runnerTokenSecretValue = new SecretValues({
container: runnerToken,
});
runnerTokenSecretValue.init();
}
registrySettingsApp();
initDeployFreeze();
initVariableList();
initSettingsPipelinesTriggers();
// hide extra auto devops settings based checkbox state
const autoDevOpsExtraSettings = document.querySelector('.js-extra-settings');
const instanceDefaultBadge = document.querySelector('.js-instance-default-badge');
document.querySelector('.js-toggle-extra-settings').addEventListener('click', event => {
const { target } = event;
if (instanceDefaultBadge) instanceDefaultBadge.style.display = 'none';
autoDevOpsExtraSettings.classList.toggle('hidden', !target.checked);
});
registrySettingsApp();
initDeployFreeze();
initSettingsPipelinesTriggers();
initInstallRunner();
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import InstallRunnerInstructions from '~/vue_shared/components/runner_instructions/runner_instructions.vue';
Vue.use(VueApollo);
export function initInstallRunner(componentId = 'js-install-runner') {
const installRunnerEl = document.getElementById(componentId);
const { projectPath, groupPath } = installRunnerEl?.dataset;
if (installRunnerEl) {
const defaultClient = createDefaultClient();
const apolloProvider = new VueApollo({
defaultClient,
});
// eslint-disable-next-line no-new
new Vue({
el: installRunnerEl,
apolloProvider,
provide: {
projectPath,
groupPath,
},
render(createElement) {
return createElement(InstallRunnerInstructions);
},
});
}
}
query getRunnerPlatforms($projectPath: ID!, $groupPath: ID!) {
runnerPlatforms {
nodes {
name
humanReadableName
architectures {
nodes {
name
downloadLocation
}
}
}
}
project(fullPath: $projectPath) {
id
}
group(fullPath: $groupPath) {
id
}
}
query runnerSetupInstructions(
$platform: String!
$architecture: String!
$projectId: ID!
$groupId: ID!
) {
runnerSetup(
platform: $platform
architecture: $architecture
projectId: $projectId
groupId: $groupId
) {
installInstructions
registerInstructions
}
}
<script>
import {
GlAlert,
GlButton,
GlModal,
GlModalDirective,
GlButtonGroup,
GlDropdown,
GlDropdownItem,
GlIcon,
} from '@gitlab/ui';
import { __, s__ } from '~/locale';
import getRunnerPlatforms from './graphql/queries/get_runner_platforms.query.graphql';
import getRunnerSetupInstructions from './graphql/queries/get_runner_setup.query.graphql';
export default {
components: {
GlAlert,
GlButton,
GlButtonGroup,
GlDropdown,
GlDropdownItem,
GlModal,
GlIcon,
},
directives: {
GlModalDirective,
},
inject: {
projectPath: {
default: '',
},
groupPath: {
default: '',
},
},
apollo: {
runnerPlatforms: {
query: getRunnerPlatforms,
variables() {
return {
projectPath: this.projectPath,
groupPath: this.groupPath,
};
},
update(data) {
return data;
},
error() {
this.showAlert = true;
},
},
},
data() {
return {
showAlert: false,
selectedPlatformArchitectures: [],
selectedPlatform: {},
selectedArchitecture: {},
runnerPlatforms: {},
instructions: {},
};
},
computed: {
isPlatformSelected() {
return Object.keys(this.selectedPlatform).length > 0;
},
instructionsEmpty() {
return this.instructions && Object.keys(this.instructions).length === 0;
},
groupId() {
return this.runnerPlatforms?.group?.id ?? '';
},
projectId() {
return this.runnerPlatforms?.project?.id ?? '';
},
platforms() {
return this.runnerPlatforms.runnerPlatforms?.nodes;
},
},
methods: {
selectPlatform(name) {
this.selectedPlatform = this.platforms.find(platform => platform.name === name);
this.selectedPlatformArchitectures = this.selectedPlatform?.architectures?.nodes;
[this.selectedArchitecture] = this.selectedPlatformArchitectures;
this.selectArchitecture(this.selectedArchitecture);
},
selectArchitecture(architecture) {
this.selectedArchitecture = architecture;
this.$apollo.addSmartQuery('instructions', {
variables() {
return {
platform: this.selectedPlatform.name,
architecture: this.selectedArchitecture.name,
projectId: this.projectId,
groupId: this.groupId,
};
},
query: getRunnerSetupInstructions,
update(data) {
return data?.runnerSetup;
},
error() {
this.showAlert = true;
},
});
},
toggleAlert(state) {
this.showAlert = state;
},
},
modalId: 'installation-instructions-modal',
i18n: {
installARunner: __('Install a Runner'),
architecture: s__('Runners|Architecture'),
downloadInstallBinary: s__('Runners|Download and Install Binary'),
downloadLatestBinary: s__('Runners|Download Latest Binary'),
registerRunner: s__('Runners|Register Runner'),
method: __('Method'),
fetchError: s__('An error has occurred fetching instructions'),
instructions: __('Show Runner installation instructions'),
},
closeButton: {
text: __('Close'),
attributes: [{ variant: 'default' }],
},
};
</script>
<template>
<div>
<gl-button v-gl-modal-directive="$options.modalId" data-testid="show-modal-button">
{{ $options.i18n.instructions }}
</gl-button>
<gl-modal
:modal-id="$options.modalId"
:title="$options.i18n.installARunner"
:action-secondary="$options.closeButton"
>
<gl-alert v-if="showAlert" variant="danger" @dismiss="toggleAlert(false)">
{{ $options.i18n.fetchError }}
</gl-alert>
<h5>{{ __('Environment') }}</h5>
<gl-button-group class="gl-mb-5">
<gl-button
v-for="platform in platforms"
:key="platform.name"
data-testid="platform-button"
@click="selectPlatform(platform.name)"
>
{{ platform.humanReadableName }}
</gl-button>
</gl-button-group>
<template v-if="isPlatformSelected">
<h5>
{{ $options.i18n.architecture }}
</h5>
<gl-dropdown class="gl-mb-5" :text="selectedArchitecture.name">
<gl-dropdown-item
v-for="architecture in selectedPlatformArchitectures"
:key="architecture.name"
data-testid="architecture-dropdown-item"
@click="selectArchitecture(architecture)"
>
{{ architecture.name }}
</gl-dropdown-item>
</gl-dropdown>
<div class="gl-display-flex gl-align-items-center gl-mb-5">
<h5>{{ $options.i18n.downloadInstallBinary }}</h5>
<gl-button
class="gl-ml-auto"
:href="selectedArchitecture.downloadLocation"
download
data-testid="binary-download-button"
>
{{ $options.i18n.downloadLatestBinary }}
</gl-button>
</div>
</template>
<template v-if="!instructionsEmpty">
<div class="gl-display-flex">
<pre class="bg-light gl-flex-fill-1" data-testid="binary-instructions">
{{ instructions.installInstructions }}
</pre>
<gl-button
class="gl-align-self-start gl-ml-2 gl-mt-2"
category="tertiary"
variant="link"
:data-clipboard-text="instructions.installationInstructions"
>
<gl-icon name="copy-to-clipboard" />
</gl-button>
</div>
<hr />
<h5 class="gl-mb-5">{{ $options.i18n.registerRunner }}</h5>
<h5 class="gl-mb-5">{{ $options.i18n.method }}</h5>
<div class="gl-display-flex">
<pre class="bg-light gl-flex-fill-1" data-testid="runner-instructions">
{{ instructions.registerInstructions }}
</pre>
<gl-button
class="gl-align-self-start gl-ml-2 gl-mt-2"
category="tertiary"
variant="link"
:data-clipboard-text="instructions.registerInstructions"
>
<gl-icon name="copy-to-clipboard" />
</gl-button>
</div>
</template>
</gl-modal>
</div>
</template>
......@@ -39,7 +39,9 @@
= render partial: 'ci/runner/how_to_setup_runner',
locals: { registration_token: Gitlab::CurrentSettings.runners_registration_token,
type: 'shared',
reset_token_url: reset_registration_token_admin_application_settings_path }
reset_token_url: reset_registration_token_admin_application_settings_path,
project_path: '',
group_path: '' }
.row
.col-sm-9
......
......@@ -19,3 +19,5 @@
data: { confirm: _("Are you sure you want to reset registration token?") }
%li
= _("Start the Runner!")
#js-install-runner{ data: { project_path: project_path, group_path: group_path } }
......@@ -17,4 +17,6 @@
= render partial: 'ci/runner/how_to_setup_runner',
locals: { registration_token: @group.runners_token,
type: 'group',
reset_token_url: reset_registration_token_group_settings_ci_cd_path }
reset_token_url: reset_registration_token_group_settings_ci_cd_path,
project_path: '',
group_path: @group.path }
......@@ -9,7 +9,9 @@
= render partial: 'ci/runner/how_to_setup_runner',
locals: { registration_token: @project.runners_token,
type: 'specific',
reset_token_url: reset_registration_token_namespace_project_settings_ci_cd_path }
reset_token_url: reset_registration_token_namespace_project_settings_ci_cd_path,
project_path: @project.path_with_namespace,
group_path: '' }
- if @project_runners.any?
%h4.underlined-title= _('Runners activated for this project')
......
---
title: Add install GitLab runner popup
merge_request: 42877
author:
type: added
......@@ -2831,6 +2831,9 @@ msgstr ""
msgid "An error has occurred"
msgstr ""
msgid "An error has occurred fetching instructions"
msgstr ""
msgid "An error occured while making the changes: %{error}"
msgstr ""
......@@ -14221,6 +14224,9 @@ msgstr ""
msgid "Install Runner on Kubernetes"
msgstr ""
msgid "Install a Runner"
msgstr ""
msgid "Install a soft token authenticator like %{free_otp_link} or Google Authenticator from your application repository and use that app to scan this QR code. More information is available in the %{help_link_start}documentation%{help_link_end}."
msgstr ""
......@@ -22980,6 +22986,12 @@ msgstr ""
msgid "Runners|Description"
msgstr ""
msgid "Runners|Download Latest Binary"
msgstr ""
msgid "Runners|Download and Install Binary"
msgstr ""
msgid "Runners|Group"
msgstr ""
......@@ -23007,6 +23019,9 @@ msgstr ""
msgid "Runners|Protected"
msgstr ""
msgid "Runners|Register Runner"
msgstr ""
msgid "Runners|Revision"
msgstr ""
......@@ -24414,6 +24429,9 @@ msgstr ""
msgid "Should you ever lose your phone or access to your one time password secret, each of these recovery codes can be used one time each to regain access to your account. Please save them in a safe place, or you %{b_start}will%{b_end} lose access to your account."
msgstr ""
msgid "Show Runner installation instructions"
msgstr ""
msgid "Show all activity"
msgstr ""
......
export const mockGraphqlRunnerPlatforms = {
data: {
runnerPlatforms: {
nodes: [
{
name: 'linux',
humanReadableName: 'Linux',
architectures: {
nodes: [
{
name: 'amd64',
downloadLocation:
'https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-amd64',
__typename: 'RunnerArchitecture',
},
{
name: '386',
downloadLocation:
'https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-386',
__typename: 'RunnerArchitecture',
},
{
name: 'arm',
downloadLocation:
'https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-arm',
__typename: 'RunnerArchitecture',
},
{
name: 'arm64',
downloadLocation:
'https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-arm64',
__typename: 'RunnerArchitecture',
},
],
__typename: 'RunnerArchitectureConnection',
},
__typename: 'RunnerPlatform',
},
{
name: 'osx',
humanReadableName: 'macOS',
architectures: {
nodes: [
{
name: 'amd64',
downloadLocation:
'https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-darwin-amd64',
__typename: 'RunnerArchitecture',
},
],
__typename: 'RunnerArchitectureConnection',
},
__typename: 'RunnerPlatform',
},
{
name: 'windows',
humanReadableName: 'Windows',
architectures: {
nodes: [
{
name: 'amd64',
downloadLocation:
'https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-windows-amd64.exe',
__typename: 'RunnerArchitecture',
},
{
name: '386',
downloadLocation:
'https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-windows-386.exe',
__typename: 'RunnerArchitecture',
},
],
__typename: 'RunnerArchitectureConnection',
},
__typename: 'RunnerPlatform',
},
{
name: 'docker',
humanReadableName: 'Docker',
architectures: null,
__typename: 'RunnerPlatform',
},
{
name: 'kubernetes',
humanReadableName: 'Kubernetes',
architectures: null,
__typename: 'RunnerPlatform',
},
],
__typename: 'RunnerPlatformConnection',
},
project: { id: 'gid://gitlab/Project/1', __typename: 'Project' },
group: null,
},
};
export const mockGraphqlInstructions = {
data: {
runnerSetup: {
installInstructions:
"# Download the binary for your system\nsudo curl -L --output /usr/local/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-amd64\n\n# Give it permissions to execute\nsudo chmod +x /usr/local/bin/gitlab-runner\n\n# Create a GitLab CI user\nsudo useradd --comment 'GitLab Runner' --create-home gitlab-runner --shell /bin/bash\n\n# Install and run as service\nsudo gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner\nsudo gitlab-runner start\n",
registerInstructions:
'sudo gitlab-runner register --url http://192.168.1.81:3000/ --registration-token GE5gsjeep_HAtBf9s3Yz',
__typename: 'RunnerSetup',
},
},
};
import { shallowMount, createLocalVue } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import createMockApollo from 'jest/helpers/mock_apollo_helper';
import RunnerInstructions from '~/vue_shared/components/runner_instructions/runner_instructions.vue';
import getRunnerPlatforms from '~/vue_shared/components/runner_instructions/graphql/queries/get_runner_platforms.query.graphql';
import getRunnerSetupInstructions from '~/vue_shared/components/runner_instructions/graphql/queries/get_runner_setup.query.graphql';
import { mockGraphqlRunnerPlatforms, mockGraphqlInstructions } from './mock_data';
const projectPath = 'gitlab-org/gitlab';
const localVue = createLocalVue();
localVue.use(VueApollo);
describe('RunnerInstructions component', () => {
let wrapper;
let fakeApollo;
const findModalButton = () => wrapper.find('[data-testid="show-modal-button"]');
const findPlatformButtons = () => wrapper.findAll('[data-testid="platform-button"]');
const findArchitectureDropdownItems = () =>
wrapper.findAll('[data-testid="architecture-dropdown-item"]');
const findBinaryInstructionsSection = () => wrapper.find('[data-testid="binary-instructions"]');
const findRunnerInstructionsSection = () => wrapper.find('[data-testid="runner-instructions"]');
beforeEach(() => {
const requestHandlers = [
[getRunnerPlatforms, jest.fn().mockResolvedValue(mockGraphqlRunnerPlatforms)],
[getRunnerSetupInstructions, jest.fn().mockResolvedValue(mockGraphqlInstructions)],
];
fakeApollo = createMockApollo(requestHandlers);
wrapper = shallowMount(RunnerInstructions, {
provide: {
projectPath,
},
localVue,
apolloProvider: fakeApollo,
});
});
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
it('should show the "Show Runner installation instructions" button', () => {
const button = findModalButton();
expect(button.exists()).toBe(true);
expect(button.text()).toBe('Show Runner installation instructions');
});
it('should contain a number of platforms buttons', () => {
const buttons = findPlatformButtons();
expect(buttons).toHaveLength(mockGraphqlRunnerPlatforms.data.runnerPlatforms.nodes.length);
});
it('should contain a number of dropdown items for the architecture options', () => {
const platformButton = findPlatformButtons().at(0);
platformButton.vm.$emit('click');
return wrapper.vm.$nextTick(() => {
const dropdownItems = findArchitectureDropdownItems();
expect(dropdownItems).toHaveLength(
mockGraphqlRunnerPlatforms.data.runnerPlatforms.nodes[0].architectures.nodes.length,
);
});
});
it('should display the binary installation instructions for a selected architecture', async () => {
const platformButton = findPlatformButtons().at(0);
platformButton.vm.$emit('click');
await wrapper.vm.$nextTick();
const dropdownItem = findArchitectureDropdownItems().at(0);
dropdownItem.vm.$emit('click');
await wrapper.vm.$nextTick();
const runner = findBinaryInstructionsSection();
expect(runner.text()).toEqual(
expect.stringContaining('sudo chmod +x /usr/local/bin/gitlab-runner'),
);
expect(runner.text()).toEqual(
expect.stringContaining(
`sudo useradd --comment 'GitLab Runner' --create-home gitlab-runner --shell /bin/bash`,
),
);
expect(runner.text()).toEqual(
expect.stringContaining(
'sudo gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner',
),
);
expect(runner.text()).toEqual(expect.stringContaining('sudo gitlab-runner start'));
});
it('should display the runner register instructions for a selected architecture', async () => {
const platformButton = findPlatformButtons().at(0);
platformButton.vm.$emit('click');
await wrapper.vm.$nextTick();
const dropdownItem = findArchitectureDropdownItems().at(0);
dropdownItem.vm.$emit('click');
await wrapper.vm.$nextTick();
const runner = findRunnerInstructionsSection();
expect(runner.text()).toEqual(
expect.stringContaining(mockGraphqlInstructions.data.runnerSetup.registerInstructions),
);
});
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment