Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-02-20 15:08:44 +00:00
parent 8c4198cbe6
commit b9bac6dbf7
46 changed files with 789 additions and 440 deletions

View file

@ -0,0 +1,23 @@
<!-- Instructions: Use this template for a proof of concept or when a deeper technical evaluation is required. Please weigh tech evaluation issues and follow the instructions below accordingly. -->
### Topic to Evaluate
<!-- Describe the related issue and challenge we need to establish a proof of concept for-->
* [Link to other Issue](link)
### Tasks to Evaluate
<!-- Outline the tasks with issues that you need evaluate as a part of the implementation issue -->
- [ ] Add task
- [ ] Add task
- [ ] Add task
### Risks and Implementation Considerations
<!-- Idenitfy any risks found in the research, whether this is performance, impacts to other functionality or other bugs -->
### Team
- [ ] Add ~"workflow::planning breakdown" ~feature and the corresponding `~devops::<stage>` and `~group::<group>` labels.
- [ ] Ping the PM and EM.

View file

@ -90,11 +90,7 @@ export default {
};
</script>
<template>
<div v-gl-resize-observer-directive="onResize" class="prometheus-graph">
<div class="prometheus-graph-header">
<h5 ref="graphTitle" class="prometheus-graph-title">{{ graphData.title }}</h5>
<div ref="graphWidgets" class="prometheus-graph-widgets"><slot></slot></div>
</div>
<div v-gl-resize-observer-directive="onResize">
<gl-column-chart
ref="columnChart"
v-bind="$attrs"

View file

@ -27,10 +27,7 @@ export default {
};
</script>
<template>
<div class="prometheus-graph d-flex flex-column justify-content-center">
<div class="prometheus-graph-header">
<h5 ref="graphTitle" class="prometheus-graph-title">{{ graphTitle }}</h5>
</div>
<div class="d-flex flex-column justify-content-center">
<div
class="prepend-top-8 svg-w-100 d-flex align-items-center"
:style="svgContainerStyle"

View file

@ -2,13 +2,11 @@
import { GlResizeObserverDirective } from '@gitlab/ui';
import { GlHeatmap } from '@gitlab/ui/dist/charts';
import dateformat from 'dateformat';
import PrometheusHeader from '../shared/prometheus_header.vue';
import { graphDataValidatorForValues } from '../../utils';
export default {
components: {
GlHeatmap,
PrometheusHeader,
},
directives: {
GlResizeObserverDirective,
@ -65,8 +63,7 @@ export default {
};
</script>
<template>
<div v-gl-resize-observer-directive="onResize" class="prometheus-graph col-12 col-lg-6">
<prometheus-header :graph-title="graphData.title" />
<div v-gl-resize-observer-directive="onResize" class="col-12 col-lg-6">
<gl-heatmap
ref="heatmapChart"
v-bind="$attrs"

View file

@ -42,10 +42,7 @@ export default {
};
</script>
<template>
<div class="prometheus-graph">
<div class="prometheus-graph-header">
<h5 ref="graphTitle" class="prometheus-graph-title">{{ graphTitle }}</h5>
</div>
<div>
<gl-single-stat :value="statValue" :title="graphTitle" variant="success" />
</div>
</template>

View file

@ -81,11 +81,7 @@ export default {
};
</script>
<template>
<div v-gl-resize-observer-directive="onResize" class="prometheus-graph">
<div class="prometheus-graph-header">
<h5 ref="graphTitle" class="prometheus-graph-title">{{ graphData.title }}</h5>
<div ref="graphWidgets" class="prometheus-graph-widgets"><slot></slot></div>
</div>
<div v-gl-resize-observer-directive="onResize">
<gl-stacked-column-chart
ref="chart"
v-bind="$attrs"

View file

@ -112,7 +112,6 @@ export default {
isDeployment: false,
sha: '',
},
showTitleTooltip: false,
width: 0,
height: chartHeight,
svgs: {},
@ -285,12 +284,6 @@ export default {
return `${this.graphData.y_label}`;
},
},
mounted() {
const graphTitleEl = this.$refs.graphTitle;
if (graphTitleEl && graphTitleEl.scrollWidth > graphTitleEl.offsetWidth) {
this.showTitleTooltip = true;
}
},
created() {
this.setSvg('rocket');
this.setSvg('scroll-handle');
@ -387,24 +380,7 @@ export default {
</script>
<template>
<div v-gl-resize-observer-directive="onResize" class="prometheus-graph">
<div class="prometheus-graph-header">
<h5
ref="graphTitle"
class="prometheus-graph-title js-graph-title text-truncate append-right-8"
>
{{ graphData.title }}
</h5>
<gl-tooltip :target="() => $refs.graphTitle" :disabled="!showTitleTooltip">
{{ graphData.title }}
</gl-tooltip>
<div
class="prometheus-graph-widgets js-graph-widgets flex-fill"
data-qa-selector="prometheus_graph_widgets"
>
<slot></slot>
</div>
</div>
<div v-gl-resize-observer-directive="onResize">
<component
:is="glChartComponent"
ref="chart"

View file

@ -3,10 +3,12 @@ import { mapState } from 'vuex';
import { pickBy } from 'lodash';
import invalidUrl from '~/lib/utils/invalid_url';
import {
GlResizeObserverDirective,
GlDropdown,
GlDropdownItem,
GlModal,
GlModalDirective,
GlTooltip,
GlTooltipDirective,
} from '@gitlab/ui';
import { __ } from '~/locale';
@ -29,11 +31,13 @@ export default {
MonitorStackedColumnChart,
MonitorEmptyChart,
Icon,
GlTooltip,
GlDropdown,
GlDropdownItem,
GlModal,
},
directives: {
GlResizeObserver: GlResizeObserverDirective,
GlModal: GlModalDirective,
GlTooltip: GlTooltipDirective,
TrackEvent: TrackEventDirective,
@ -61,11 +65,15 @@ export default {
},
data() {
return {
showTitleTooltip: false,
zoomedTimeRange: null,
};
},
computed: {
...mapState('monitoringDashboard', ['deploymentData', 'projectPath', 'logsPath', 'timeRange']),
title() {
return this.graphData.title || '';
},
alertWidgetAvailable() {
return IS_EE && this.prometheusAlertsAvailable && this.alertsEndpoint && this.graphData;
},
@ -97,12 +105,24 @@ export default {
const data = new Blob([this.csvText], { type: 'text/plain' });
return window.URL.createObjectURL(data);
},
monitorChartComponent() {
timeChartComponent() {
if (this.isPanelType('anomaly-chart')) {
return MonitorAnomalyChart;
}
return MonitorTimeSeriesChart;
},
isContextualMenuShown() {
return (
this.graphDataHasMetrics &&
!this.isPanelType('single-stat') &&
!this.isPanelType('heatmap') &&
!this.isPanelType('column') &&
!this.isPanelType('stacked-column')
);
},
},
mounted() {
this.refreshTitleTooltip();
},
methods: {
getGraphAlerts(queries) {
@ -119,9 +139,18 @@ export default {
showToast() {
this.$toast.show(__('Link copied'));
},
refreshTitleTooltip() {
const { graphTitle } = this.$refs;
this.showTitleTooltip =
Boolean(graphTitle) && graphTitle.scrollWidth > graphTitle.offsetWidth;
},
downloadCSVOptions,
generateLinkToChartOptions,
onResize() {
this.refreshTitleTooltip();
},
onDatazoom({ start, end }) {
this.zoomedTimeRange = { start, end };
},
@ -129,88 +158,109 @@ export default {
};
</script>
<template>
<monitor-single-stat-chart
v-if="isPanelType('single-stat') && graphDataHasMetrics"
:graph-data="graphData"
/>
<monitor-heatmap-chart
v-else-if="isPanelType('heatmap') && graphDataHasMetrics"
:graph-data="graphData"
/>
<monitor-column-chart
v-else-if="isPanelType('column') && graphDataHasMetrics"
:graph-data="graphData"
/>
<monitor-stacked-column-chart
v-else-if="isPanelType('stacked-column') && graphDataHasMetrics"
:graph-data="graphData"
/>
<component
:is="monitorChartComponent"
v-else-if="graphDataHasMetrics"
ref="timeChart"
:graph-data="graphData"
:deployment-data="deploymentData"
:project-path="projectPath"
:thresholds="getGraphAlertValues(graphData.metrics)"
:group-id="groupId"
@datazoom="onDatazoom"
>
<div class="d-flex align-items-center">
<alert-widget
v-if="alertWidgetAvailable && graphData"
:modal-id="`alert-modal-${index}`"
:alerts-endpoint="alertsEndpoint"
:relevant-queries="graphData.metrics"
:alerts-to-manage="getGraphAlerts(graphData.metrics)"
@setAlerts="setAlerts"
/>
<gl-dropdown
v-gl-tooltip
class="ml-auto mx-3"
toggle-class="btn btn-transparent border-0"
data-qa-selector="prometheus_widgets_dropdown"
:right="true"
:no-caret="true"
:title="__('More actions')"
<div v-gl-resize-observer="onResize" class="prometheus-graph">
<div class="prometheus-graph-header">
<h5
ref="graphTitle"
class="prometheus-graph-title gl-font-size-large font-weight-bold text-truncate append-right-8"
>
<template slot="button-content">
<icon name="ellipsis_v" class="text-secondary" />
</template>
{{ title }}
</h5>
<gl-tooltip :target="() => $refs.graphTitle" :disabled="!showTitleTooltip">
{{ title }}
</gl-tooltip>
<div
v-if="isContextualMenuShown"
class="prometheus-graph-widgets js-graph-widgets flex-fill"
data-qa-selector="prometheus_graph_widgets"
>
<div class="d-flex align-items-center">
<alert-widget
v-if="alertWidgetAvailable && graphData"
:modal-id="`alert-modal-${index}`"
:alerts-endpoint="alertsEndpoint"
:relevant-queries="graphData.metrics"
:alerts-to-manage="getGraphAlerts(graphData.metrics)"
@setAlerts="setAlerts"
/>
<gl-dropdown
v-gl-tooltip
class="ml-auto mx-3"
toggle-class="btn btn-transparent border-0"
data-qa-selector="prometheus_widgets_dropdown"
right
no-caret
:title="__('More actions')"
>
<template slot="button-content">
<icon name="ellipsis_v" class="text-secondary" />
</template>
<gl-dropdown-item
v-if="logsPathWithTimeRange"
ref="viewLogsLink"
:href="logsPathWithTimeRange"
>
{{ s__('Metrics|View logs') }}
</gl-dropdown-item>
<gl-dropdown-item
v-if="logsPathWithTimeRange"
ref="viewLogsLink"
:href="logsPathWithTimeRange"
>
{{ s__('Metrics|View logs') }}
</gl-dropdown-item>
<gl-dropdown-item
v-track-event="downloadCSVOptions(graphData.title)"
:href="downloadCsv"
download="chart_metrics.csv"
>
{{ __('Download CSV') }}
</gl-dropdown-item>
<gl-dropdown-item
v-if="clipboardText"
ref="copyChartLink"
v-track-event="generateLinkToChartOptions(clipboardText)"
:data-clipboard-text="clipboardText"
@click="showToast(clipboardText)"
>
{{ __('Generate link to chart') }}
</gl-dropdown-item>
<gl-dropdown-item
v-if="alertWidgetAvailable"
v-gl-modal="`alert-modal-${index}`"
data-qa-selector="alert_widget_menu_item"
>
{{ __('Alerts') }}
</gl-dropdown-item>
</gl-dropdown>
<gl-dropdown-item
v-if="csvText"
ref="downloadCsvLink"
v-track-event="downloadCSVOptions(title)"
:href="downloadCsv"
download="chart_metrics.csv"
>
{{ __('Download CSV') }}
</gl-dropdown-item>
<gl-dropdown-item
v-if="clipboardText"
ref="copyChartLink"
v-track-event="generateLinkToChartOptions(clipboardText)"
:data-clipboard-text="clipboardText"
@click="showToast(clipboardText)"
>
{{ __('Generate link to chart') }}
</gl-dropdown-item>
<gl-dropdown-item
v-if="alertWidgetAvailable"
v-gl-modal="`alert-modal-${index}`"
data-qa-selector="alert_widget_menu_item"
>
{{ __('Alerts') }}
</gl-dropdown-item>
</gl-dropdown>
</div>
</div>
</div>
</component>
<monitor-empty-chart v-else :graph-title="graphData.title" />
<monitor-single-stat-chart
v-if="isPanelType('single-stat') && graphDataHasMetrics"
:graph-data="graphData"
/>
<monitor-heatmap-chart
v-else-if="isPanelType('heatmap') && graphDataHasMetrics"
:graph-data="graphData"
/>
<monitor-column-chart
v-else-if="isPanelType('column') && graphDataHasMetrics"
:graph-data="graphData"
/>
<monitor-stacked-column-chart
v-else-if="isPanelType('stacked-column') && graphDataHasMetrics"
:graph-data="graphData"
/>
<component
:is="timeChartComponent"
v-else-if="graphDataHasMetrics"
ref="timeChart"
:graph-data="graphData"
:deployment-data="deploymentData"
:project-path="projectPath"
:thresholds="getGraphAlertValues(graphData.metrics)"
:group-id="groupId"
@datazoom="onDatazoom"
/>
<monitor-empty-chart v-else :graph-title="title" v-bind="$attrs" v-on="$listeners" />
</div>
</template>

View file

@ -1,15 +0,0 @@
<script>
export default {
props: {
graphTitle: {
type: String,
required: true,
},
},
};
</script>
<template>
<div class="prometheus-graph-header">
<h5 ref="title" class="prometheus-graph-title">{{ graphTitle }}</h5>
</div>
</template>

View file

@ -91,10 +91,6 @@
margin-bottom: $gl-padding-8;
}
.prometheus-graph-title {
font-size: $gl-font-size-large;
}
.alert-current-setting {
max-width: 240px;
}

View file

@ -0,0 +1,35 @@
# frozen_string_literal: true
class ServerlessDomainFinder
attr_reader :match, :serverless_domain_cluster, :environment
def initialize(uri)
@match = ::Serverless::Domain::REGEXP.match(uri)
end
def execute
return unless serverless?
@serverless_domain_cluster = ::Serverless::DomainCluster.for_uuid(serverless_domain_cluster_uuid)
return unless serverless_domain_cluster
@environment = ::Environment.for_id_and_slug(match[:environment_id].to_i(16), match[:environment_slug])
return unless environment
::Serverless::Domain.new(
function_name: match[:function_name],
serverless_domain_cluster: serverless_domain_cluster,
environment: environment
)
end
def serverless_domain_cluster_uuid
return unless serverless?
match[:cluster_left] + match[:cluster_middle] + match[:cluster_right]
end
def serverless?
!!match
end
end

View file

@ -15,7 +15,7 @@ module Clusters
def set_initial_status
return unless not_installable?
self.status = status_states[:installable] if cluster&.application_helm_available? || Feature.enabled?(:managed_apps_local_tiller)
self.status = status_states[:installable] if cluster&.application_helm_available? || ::Gitlab::Kubernetes::Helm.local_tiller_enabled?
end
def can_uninstall?

View file

@ -23,7 +23,7 @@ module Clusters
@files ||= begin
files = { 'values.yaml': values }
files.merge!(certificate_files) if cluster.application_helm.has_ssl?
files.merge!(certificate_files) if use_tiller_ssl?
files
end
@ -31,6 +31,12 @@ module Clusters
private
def use_tiller_ssl?
return false if ::Gitlab::Kubernetes::Helm.local_tiller_enabled?
cluster.application_helm.has_ssl?
end
def certificate_files
{
'ca.pem': ca_cert,

View file

@ -92,7 +92,10 @@ module Clusters
# When installing any application we are also performing an update
# of tiller (see Gitlab::Kubernetes::Helm::ClientCommand) so
# therefore we need to reflect that in the database.
application.cluster.application_helm.update!(version: Gitlab::Kubernetes::Helm::HELM_VERSION)
unless ::Gitlab::Kubernetes::Helm.local_tiller_enabled?
application.cluster.application_helm.update!(version: Gitlab::Kubernetes::Helm::HELM_VERSION)
end
end
after_transition any => [:uninstalling], :use_transactions => false do |application, _|

View file

@ -95,6 +95,10 @@ class Environment < ApplicationRecord
end
end
def self.for_id_and_slug(id, slug)
find_by(id: id, slug: slug)
end
def self.max_deployment_id_sql
Deployment.select(Deployment.arel_table[:id].maximum)
.where(Deployment.arel_table[:environment_id].eq(arel_table[:id]))

View file

@ -0,0 +1,44 @@
# frozen_string_literal: true
module Serverless
class Domain
include ActiveModel::Model
REGEXP = %r{^(?<scheme>https?://)?(?<function_name>[^.]+)-(?<cluster_left>\h{2})a1(?<cluster_middle>\h{10})f2(?<cluster_right>\h{2})(?<environment_id>\h+)-(?<environment_slug>[^.]+)\.(?<pages_domain_name>.+)}.freeze
UUID_LENGTH = 14
attr_accessor :function_name, :serverless_domain_cluster, :environment
validates :function_name, presence: true, allow_blank: false
validates :serverless_domain_cluster, presence: true
validates :environment, presence: true
def self.generate_uuid
SecureRandom.hex(UUID_LENGTH / 2)
end
def uri
URI("https://#{function_name}-#{serverless_domain_cluster_uuid}#{"%x" % environment.id}-#{environment.slug}.#{serverless_domain_cluster.domain}")
end
def knative_uri
URI("http://#{function_name}.#{namespace}.#{serverless_domain_cluster.knative.hostname}")
end
private
def namespace
serverless_domain_cluster.cluster.kubernetes_namespace_for(environment)
end
def serverless_domain_cluster_uuid
[
serverless_domain_cluster.uuid[0..1],
'a1',
serverless_domain_cluster.uuid[2..-3],
'f2',
serverless_domain_cluster.uuid[-2..-1]
].join
end
end
end

View file

@ -16,11 +16,18 @@ module Serverless
algorithm: 'aes-256-gcm'
validates :pages_domain, :knative, presence: true
validates :uuid, presence: true, uniqueness: true, length: { is: Gitlab::Serverless::Domain::UUID_LENGTH },
validates :uuid, presence: true, uniqueness: true, length: { is: ::Serverless::Domain::UUID_LENGTH },
format: { with: HEX_REGEXP, message: 'only allows hex characters' }
default_value_for(:uuid, allows_nil: false) { Gitlab::Serverless::Domain.generate_uuid }
default_value_for(:uuid, allows_nil: false) { ::Serverless::Domain.generate_uuid }
delegate :domain, to: :pages_domain
delegate :cluster, to: :knative
def self.for_uuid(uuid)
joins(:pages_domain, :knative)
.includes(:pages_domain, :knative)
.find_by(uuid: uuid)
end
end
end

View file

@ -1,4 +1,2 @@
#badge-settings{ data: { api_endpoint_url: @badge_api_endpoint,
docs_url: help_page_path('user/project/badges')} }
.text-center.prepend-top-default
= icon('spinner spin 2x')

View file

@ -0,0 +1,5 @@
---
title: Remove unused loading spinner from badge_settings partial
merge_request: 25044
author: nuwe1
type: other

View file

@ -13,7 +13,7 @@ The Packages feature allows GitLab to act as a repository for the following:
| [Conan Repository](conan_repository/index.md) **(PREMIUM)** | The GitLab Conan Repository enables every project in GitLab to have its own space to store [Conan](https://conan.io/) packages. | 12.6+ |
| [Maven Repository](maven_repository/index.md) **(PREMIUM)** | The GitLab Maven Repository enables every project in GitLab to have its own space to store [Maven](https://maven.apache.org/) packages. | 11.3+ |
| [NPM Registry](npm_registry/index.md) **(PREMIUM)** | The GitLab NPM Registry enables every project in GitLab to have its own space to store [NPM](https://www.npmjs.com/) packages. | 11.7+ |
| [NuGet Repository](nuget_repository/index.md) **(PREMIUM)** | *PLANNED* The GitLab NuGet Repository will enable every project in GitLab to have its own space to store [NuGet](https://www.nuget.org/) packages. | 12.8+ |
| [NuGet Repository](nuget_repository/index.md) **(PREMIUM)** | The GitLab NuGet Repository will enable every project in GitLab to have its own space to store [NuGet](https://www.nuget.org/) packages. | 12.8+ |
## Suggested contributions

View file

@ -280,7 +280,7 @@ page.
To work with NPM commands within [GitLab CI](./../../../ci/README.md), you can use
`CI_JOB_TOKEN` in place of the personal access token in your commands.
A simple example `gitlab-ci.yml` file for publishing NPM packages:
A simple example `.gitlab-ci.yml` file for publishing NPM packages:
```yml
image: node:latest

View file

@ -2,18 +2,21 @@
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/20050) in [GitLab Premium](https://about.gitlab.com/pricing/) 12.8.
CAUTION: **Work in progress**
This feature is in development, sections on uploading and installing packages will be coming soon, please follow along and help us make sure we're building the right solution for you in the [NuGet issue](https://gitlab.com/gitlab-org/gitlab/issues/20050).
With the GitLab NuGet Repository, every project can have its own space to store NuGet packages.
The GitLab NuGet Repository works with either [nuget CLI](https://www.nuget.org/) or [Visual Studio](https://visualstudio.microsoft.com/vs/).
The GitLab NuGet Repository works with:
- [NuGet CLI](https://docs.microsoft.com/en-us/nuget/reference/nuget-exe-cli-reference)
- [.NET Core CLI](https://docs.microsoft.com/en-us/dotnet/core/tools/)
- [Visual Studio](https://visualstudio.microsoft.com/vs/)
## Setting up your development environment
You will need [nuget CLI](https://www.nuget.org/) 5.2 or above. Previous versions have not been tested against the GitLab NuGet Repository and might not work. You can install it by visiting the [downloads page](https://www.nuget.org/downloads).
You will need [NuGet CLI 5.2 or later](https://www.nuget.org/downloads). Earlier versions have not been tested
against the GitLab NuGet Repository and might not work. If you have [Visual Studio](https://visualstudio.microsoft.com/vs/),
NuGet CLI is probably already installed.
If you have [Visual Studio](https://visualstudio.microsoft.com/vs/), [nuget CLI](https://www.nuget.org/) is probably already installed.
Alternatively, you can use [.NET SDK 3.0 or later](https://dotnet.microsoft.com/download/dotnet-core/3.0), which installs NuGet CLI.
You can confirm that [nuget CLI](https://www.nuget.org/) is properly installed with:
@ -37,7 +40,7 @@ Available commands:
NOTE: **Note:**
This option is available only if your GitLab administrator has
[enabled support for the NuGet Repository](../../../administration/packages/index.md).**(PREMIUM ONLY)**
[enabled support for the Package Registry](../../../administration/packages/index.md). **(PREMIUM ONLY)**
After the NuGet Repository is enabled, it will be available for all new projects
by default. To enable it for existing projects, or if you want to disable it:
@ -48,7 +51,7 @@ by default. To enable it for existing projects, or if you want to disable it:
You should then be able to see the **Packages** section on the left sidebar.
## Adding the GitLab NuGet Repository as a source to nuget
## Adding the GitLab NuGet Repository as a source to NuGet
You will need the following:
@ -57,23 +60,23 @@ You will need the following:
- A suitable name for your source.
- Your project ID which can be found on the home page of your project.
You can now add a new source to nuget either using [nuget CLI](https://www.nuget.org/) or [Visual Studio](https://visualstudio.microsoft.com/vs/).
You can now add a new source to NuGet with:
### Using nuget CLI
- [NuGet CLI](#add-nuget-repository-source-with-nuget-cli)
- [Visual Studio](#add-nuget-repository-source-with-visual-studio).
- [.NET CLI](#add-nuget-repository-source-with-net-cli)
### Add NuGet Repository source with NuGet CLI
To add the GitLab NuGet Repository as a source with `nuget`:
```shell
nuget source Add -Name <source_name> -Source "https://example.gitlab.com/api/v4/projects/<your_project_id>/packages/nuget/index.json" -UserName <gitlab_username> -Password <gitlab_token>
nuget source Add -Name <source_name> -Source "https://gitlab-instance.example.com/api/v4/projects/<your_project_id>/packages/nuget/index.json" -UserName <gitlab_username> -Password <gitlab_personal_access_token>
```
Replace:
Where:
- `<source_name>` with your desired source name.
- `<your_project_id>` with your project ID.
- `<gitlab-username>` with your GitLab username.
- `<gitlab-token>` with your personal access token.
- `example.gitlab.com` with the URL of the GitLab instance you're using.
- `<source_name>` is your desired source name.
For example:
@ -81,7 +84,7 @@ For example:
nuget source Add -Name "GitLab" -Source "https//gitlab.example/api/v4/projects/10/packages/nuget/index.json" -UserName carol -Password 12345678asdf
```
### Using Visual Studio
### Add NuGet Repository source with Visual Studio
1. Open [Visual Studio](https://visualstudio.microsoft.com/vs/).
1. Open the **FILE > OPTIONS** (Windows) or **Visual Studio > Preferences** (Mac OS).
@ -102,3 +105,109 @@ nuget source Add -Name "GitLab" -Source "https//gitlab.example/api/v4/projects/1
![Visual Studio NuGet source added](img/visual_studio_nuget_source_added.png)
In case of any warning, please make sure that the **Location**, **Username** and **Password** are correct.
### Add NuGet Repository source with .NET CLI
To add the GitLab NuGet Repository as a source for .NET, create a file named `nuget.config` in the root of your project with the following content:
```xml
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="gitlab" value="https://gitlab-instance.example.com/api/v4/projects/<your_project_id>/packages/nuget/index.json" />
</packageSources>
<packageSourceCredentials>
<gitlab>
<add key="Username" value="<gitlab_username>" />
<add key="ClearTextPassword" value="<gitlab_personal_access_token>" />
</gitlab>
</packageSourceCredentials>
</configuration>
```
## Uploading packages
When uploading packages, note that:
- The maximum allowed size is 50 Megabytes.
- If you upload the same package with the same version multiple times, each consecutive upload
is saved as a separate file. When installing a package, GitLab will serve the most recent file.
- When uploading packages to GitLab, they will not be displayed in the packages UI of your project
immediately. It can take up to 10 minutes to process a package.
### Upload packages with NuGet CLI
This section assumes that your project is properly built and you already [created a NuGet package with NuGet CLI](https://docs.microsoft.com/en-us/nuget/create-packages/creating-a-package).
Upload your package using the following command:
```shell
nuget push <package_file> -Source <source_name>
```
Where:
- `<package_file>` is your package filename, ending in `.nupkg`.
- `<source_name>` is the [source name used during setup](#adding-the-gitlab-nuget-repository-as-a-source-to-nuget).
### Upload packages with .NET CLI
This section assumes that your project is properly built and you already [created a NuGet package with .NET CLI](https://docs.microsoft.com/en-us/nuget/create-packages/creating-a-package-dotnet-cli.).
Upload your package using the following command:
```shell
dotnet nuget push <package_file> --source <source_name>
```
Where:
- `<package_file>` is your package filename, ending in `.nupkg`.
- `<source_name>` is the [source name used during setup](#adding-the-gitlab-nuget-repository-as-a-source-to-nuget).
For example:
```shell
dotnet nuget push MyPackage.1.0.0.nupkg --source gitlab
```
## Install packages
### Install a package with NuGet CLI
CAUTION: **Warning:**
By default, `nuget` checks the official source at `nuget.org` first. If you have a package in the
GitLab NuGet Repository with the same name as a package at `nuget.org`, you must specify the source
name or the wrong package will be installed.
Install the latest version of a package using the following command:
```shell
nuget install <package_id> -OutputDirectory <output_directory> \
-Version <package_version> \
-Source <source_name>
```
Where:
- `<package_id>` is the package id.
- `<output_directory>` is the output directory, where the package will be installed.
- `<package_version>` (Optional) is the package version.
- `<source_name>` (Optional) is the source name.
### Install a package with .NET CLI
CAUTION: **Warning:**
If you have a package in the GitLab NuGet Repository with the same name as a package at a different source,
you should verify the order in which `dotnet` checks sources during install. This is defined in the
`nuget.config` file.
Install the latest version of a package using the following command:
```shell
dotnet add package <package_id> \
-v <package_version>
```
Where:
- `<package_id>` is the package id.
- `<package_version>` (Optional) is the package version.

View file

@ -10,6 +10,12 @@ module Gitlab
SERVICE_ACCOUNT = 'tiller'
CLUSTER_ROLE_BINDING = 'tiller-admin'
CLUSTER_ROLE = 'cluster-admin'
MANAGED_APPS_LOCAL_TILLER_FEATURE_FLAG = :managed_apps_local_tiller
def self.local_tiller_enabled?
Feature.enabled?(MANAGED_APPS_LOCAL_TILLER_FEATURE_FLAG)
end
end
end
end

View file

@ -59,7 +59,7 @@ module Gitlab
end
def local_tiller_enabled?
Feature.enabled?(:managed_apps_local_tiller)
::Gitlab::Kubernetes::Helm.local_tiller_enabled?
end
end
end

View file

@ -1,13 +0,0 @@
# frozen_string_literal: true
module Gitlab
module Serverless
class Domain
UUID_LENGTH = 14
def self.generate_uuid
SecureRandom.hex(UUID_LENGTH / 2)
end
end
end
end

View file

@ -1,46 +0,0 @@
# frozen_string_literal: true
module Gitlab
module Serverless
class FunctionURI < URI::HTTPS
SERVERLESS_DOMAIN_REGEXP = %r{^(?<scheme>https?://)?(?<function>[^.]+)-(?<cluster_left>\h{2})a1(?<cluster_middle>\h{10})f2(?<cluster_right>\h{2})(?<environment_id>\h+)-(?<environment_slug>[^.]+)\.(?<domain>.+)}.freeze
attr_reader :function, :cluster, :environment
def initialize(function: nil, cluster: nil, environment: nil)
initialize_required_argument(:function, function)
initialize_required_argument(:cluster, cluster)
initialize_required_argument(:environment, environment)
@host = "#{function}-#{cluster.uuid[0..1]}a1#{cluster.uuid[2..-3]}f2#{cluster.uuid[-2..-1]}#{"%x" % environment.id}-#{environment.slug}.#{cluster.domain}"
super('https', nil, host, nil, nil, nil, nil, nil, nil)
end
def self.parse(uri)
match = SERVERLESS_DOMAIN_REGEXP.match(uri)
return unless match
cluster = ::Serverless::DomainCluster.find(match[:cluster_left] + match[:cluster_middle] + match[:cluster_right])
return unless cluster
environment = ::Environment.find(match[:environment_id].to_i(16))
return unless environment&.slug == match[:environment_slug]
new(
function: match[:function],
cluster: cluster,
environment: environment
)
end
private
def initialize_required_argument(name, value)
raise ArgumentError.new("missing argument: #{name}") unless value
instance_variable_set("@#{name}".to_sym, value)
end
end
end
end

View file

@ -60,7 +60,11 @@ class Gitlab::Serverless::Service
def proxy_url
if cluster&.serverless_domain
Gitlab::Serverless::FunctionURI.new(function: name, cluster: cluster.serverless_domain, environment: environment)
::Serverless::Domain.new(
function_name: name,
serverless_domain_cluster: cluster.serverless_domain,
environment: environment
).uri.to_s
end
end

View file

@ -12,11 +12,8 @@ module QA
element :prometheus_graphs
end
view 'app/assets/javascripts/monitoring/components/charts/time_series.vue' do
element :prometheus_graph_widgets
end
view 'app/assets/javascripts/monitoring/components/panel_type.vue' do
element :prometheus_graph_widgets
element :prometheus_widgets_dropdown
element :alert_widget_menu_item
end

View file

@ -135,7 +135,7 @@ describe Projects::Serverless::FunctionsController do
context 'when there is no serverless domain for a cluster' do
it 'keeps function URL as it was' do
expect(Gitlab::Serverless::Domain).not_to receive(:new)
expect(::Serverless::Domain).not_to receive(:new)
get :index, params: params({ format: :json })
expect(response).to have_gitlab_http_status(:ok)

View file

@ -73,39 +73,71 @@ FactoryBot.define do
factory :clusters_applications_ingress, class: 'Clusters::Applications::Ingress' do
modsecurity_enabled { false }
cluster factory: %i(cluster with_installed_helm provided_by_gcp)
trait :no_helm_installed do
cluster factory: %i(cluster provided_by_gcp)
end
end
factory :clusters_applications_cert_manager, class: 'Clusters::Applications::CertManager' do
email { 'admin@example.com' }
cluster factory: %i(cluster with_installed_helm provided_by_gcp)
trait :no_helm_installed do
cluster factory: %i(cluster provided_by_gcp)
end
end
factory :clusters_applications_elastic_stack, class: 'Clusters::Applications::ElasticStack' do
cluster factory: %i(cluster with_installed_helm provided_by_gcp)
trait :no_helm_installed do
cluster factory: %i(cluster provided_by_gcp)
end
end
factory :clusters_applications_crossplane, class: 'Clusters::Applications::Crossplane' do
stack { 'gcp' }
cluster factory: %i(cluster with_installed_helm provided_by_gcp)
trait :no_helm_installed do
cluster factory: %i(cluster provided_by_gcp)
end
end
factory :clusters_applications_prometheus, class: 'Clusters::Applications::Prometheus' do
cluster factory: %i(cluster with_installed_helm provided_by_gcp)
trait :no_helm_installed do
cluster factory: %i(cluster provided_by_gcp)
end
end
factory :clusters_applications_runner, class: 'Clusters::Applications::Runner' do
runner factory: %i(ci_runner)
cluster factory: %i(cluster with_installed_helm provided_by_gcp)
trait :no_helm_installed do
cluster factory: %i(cluster provided_by_gcp)
end
end
factory :clusters_applications_knative, class: 'Clusters::Applications::Knative' do
hostname { 'example.com' }
cluster factory: %i(cluster with_installed_helm provided_by_gcp)
trait :no_helm_installed do
cluster factory: %i(cluster provided_by_gcp)
end
end
factory :clusters_applications_jupyter, class: 'Clusters::Applications::Jupyter' do
oauth_application factory: :oauth_application
cluster factory: %i(cluster with_installed_helm provided_by_gcp project)
trait :no_helm_installed do
cluster factory: %i(cluster provided_by_gcp)
end
end
end
end

View file

@ -0,0 +1,11 @@
# frozen_string_literal: true
FactoryBot.define do
factory :serverless_domain, class: '::Serverless::Domain' do
function_name { 'test-function' }
serverless_domain_cluster { create(:serverless_domain_cluster) }
environment { create(:environment) }
skip_create
end
end

View file

@ -0,0 +1,81 @@
# frozen_string_literal: true
require 'spec_helper'
describe ServerlessDomainFinder do
let(:function_name) { 'test-function' }
let(:pages_domain_name) { 'serverless.gitlab.io' }
let(:pages_domain) { create(:pages_domain, :instance_serverless, domain: pages_domain_name) }
let!(:serverless_domain_cluster) { create(:serverless_domain_cluster, uuid: 'abcdef12345678', pages_domain: pages_domain) }
let(:valid_cluster_uuid) { 'aba1cdef123456f278' }
let(:invalid_cluster_uuid) { 'aba1cdef123456f178' }
let!(:environment) { create(:environment, name: 'test') }
let(:valid_uri) { "https://#{function_name}-#{valid_cluster_uuid}#{"%x" % environment.id}-#{environment.slug}.#{pages_domain_name}" }
let(:valid_fqdn) { "#{function_name}-#{valid_cluster_uuid}#{"%x" % environment.id}-#{environment.slug}.#{pages_domain_name}" }
let(:invalid_uri) { "https://#{function_name}-#{invalid_cluster_uuid}#{"%x" % environment.id}-#{environment.slug}.#{pages_domain_name}" }
let(:valid_finder) { described_class.new(valid_uri) }
let(:invalid_finder) { described_class.new(invalid_uri) }
describe '#serverless?' do
context 'with a valid URI' do
subject { valid_finder.serverless? }
it { is_expected.to be_truthy }
end
context 'with an invalid URI' do
subject { invalid_finder.serverless? }
it { is_expected.to be_falsy }
end
end
describe '#serverless_domain_cluster_uuid' do
context 'with a valid URI' do
subject { valid_finder.serverless_domain_cluster_uuid }
it { is_expected.to eq serverless_domain_cluster.uuid }
end
context 'with an invalid URI' do
subject { invalid_finder.serverless_domain_cluster_uuid }
it { is_expected.to be_nil }
end
end
describe '#execute' do
context 'with a valid URI' do
let(:serverless_domain) do
create(
:serverless_domain,
function_name: function_name,
serverless_domain_cluster: serverless_domain_cluster,
environment: environment
)
end
subject { valid_finder.execute }
it 'has the correct function_name' do
expect(subject.function_name).to eq function_name
end
it 'has the correct serverless_domain_cluster' do
expect(subject.serverless_domain_cluster).to eq serverless_domain_cluster
end
it 'has the correct environment' do
expect(subject.environment).to eq environment
end
end
context 'with an invalid URI' do
subject { invalid_finder.execute }
it { is_expected.to be_nil }
end
end
end

View file

@ -11,7 +11,6 @@ import {
} from '../../mock_data';
import MonitorTimeSeriesChart from '~/monitoring/components/charts/time_series.vue';
const mockWidgets = 'mockWidgets';
const mockProjectPath = `${TEST_HOST}${mockProjectDir}`;
jest.mock('~/lib/utils/icon_utils'); // mock getSvgIconPathContent
@ -35,9 +34,6 @@ describe('Anomaly chart component', () => {
const setupAnomalyChart = props => {
wrapper = shallowMount(Anomaly, {
propsData: { ...props },
slots: {
default: mockWidgets,
},
});
};
const findTimeSeries = () => wrapper.find(MonitorTimeSeriesChart);

View file

@ -13,14 +13,6 @@ describe('Empty Chart component', () => {
});
});
afterEach(() => {
emptyChart.destroy();
});
it('render the chart title', () => {
expect(emptyChart.find({ ref: 'graphTitle' }).text()).toBe(graphTitle);
});
describe('Computed props', () => {
it('sets the height for the svg container', () => {
expect(emptyChart.vm.svgContainerStyle.height).toBe('300px');

View file

@ -16,8 +16,6 @@ import {
} from '../../mock_data';
import * as iconUtils from '~/lib/utils/icon_utils';
const mockWidgets = 'mockWidgets';
const mockSvgPathContent = 'mockSvgPathContent';
jest.mock('lodash/throttle', () =>
@ -65,9 +63,6 @@ describe('Time series component', () => {
deploymentData: store.state.monitoringDashboard.deploymentData,
projectPath: `${mockHost}${mockProjectDir}`,
},
slots: {
default: mockWidgets,
},
store,
});
});
@ -82,14 +77,6 @@ describe('Time series component', () => {
timeSeriesChart.vm.$nextTick(done);
});
it('renders chart title', () => {
expect(timeSeriesChart.find('.js-graph-title').text()).toBe(mockGraphData.title);
});
it('contains graph widgets from slot', () => {
expect(timeSeriesChart.find('.js-graph-widgets').text()).toBe(mockWidgets);
});
it('allows user to override max value label text using prop', () => {
timeSeriesChart.setProps({ legendMaxText: 'legendMaxText' });

View file

@ -74,6 +74,18 @@ describe('Panel Type component', () => {
glEmptyChart = wrapper.find(EmptyChart);
});
it('renders the chart title', () => {
expect(wrapper.find({ ref: 'graphTitle' }).text()).toBe(graphDataNoResult.title);
});
it('renders the no download csv link', () => {
expect(wrapper.find({ ref: 'downloadCsvLink' }).exists()).toBe(false);
});
it('does not contain graph widgets', () => {
expect(wrapper.find('.js-graph-widgets').exists()).toBe(false);
});
it('is a Vue instance', () => {
expect(glEmptyChart.isVueInstance()).toBe(true);
});
@ -97,6 +109,15 @@ describe('Panel Type component', () => {
wrapper.destroy();
});
it('renders the chart title', () => {
expect(wrapper.find({ ref: 'graphTitle' }).text()).toBe(graphDataPrometheusQueryRange.title);
});
it('contains graph widgets', () => {
expect(wrapper.find('.js-graph-widgets').exists()).toBe(true);
expect(wrapper.find({ ref: 'downloadCsvLink' }).exists()).toBe(true);
});
it('sets no clipboard copy link on dropdown by default', () => {
expect(findCopyLink().exists()).toBe(false);
});

View file

@ -1,26 +0,0 @@
import { shallowMount } from '@vue/test-utils';
import PrometheusHeader from '~/monitoring/components/shared/prometheus_header.vue';
describe('Prometheus Header component', () => {
let prometheusHeader;
beforeEach(() => {
prometheusHeader = shallowMount(PrometheusHeader, {
propsData: {
graphTitle: 'graph header',
},
});
});
afterEach(() => {
prometheusHeader.destroy();
});
describe('Prometheus header component', () => {
it('should show a title', () => {
const title = prometheusHeader.find({ ref: 'title' }).text();
expect(title).toBe('graph header');
});
});
});

View file

@ -1,22 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::Serverless::Domain do
describe '.generate_uuid' do
it 'has 14 characters' do
expect(described_class.generate_uuid.length).to eq(described_class::UUID_LENGTH)
end
it 'consists of only hexadecimal characters' do
expect(described_class.generate_uuid).to match(/\A\h+\z/)
end
it 'uses random characters' do
uuid = 'abcd1234567890'
expect(SecureRandom).to receive(:hex).with(described_class::UUID_LENGTH / 2).and_return(uuid)
expect(described_class.generate_uuid).to eq(uuid)
end
end
end

View file

@ -1,81 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::Serverless::FunctionURI do
let(:function) { 'test-function' }
let(:domain) { 'serverless.gitlab.io' }
let(:pages_domain) { create(:pages_domain, :instance_serverless, domain: domain) }
let!(:cluster) { create(:serverless_domain_cluster, uuid: 'abcdef12345678', pages_domain: pages_domain) }
let(:valid_cluster) { 'aba1cdef123456f278' }
let(:invalid_cluster) { 'aba1cdef123456f178' }
let!(:environment) { create(:environment, name: 'test') }
let(:valid_uri) { "https://#{function}-#{valid_cluster}#{"%x" % environment.id}-#{environment.slug}.#{domain}" }
let(:valid_fqdn) { "#{function}-#{valid_cluster}#{"%x" % environment.id}-#{environment.slug}.#{domain}" }
let(:invalid_uri) { "https://#{function}-#{invalid_cluster}#{"%x" % environment.id}-#{environment.slug}.#{domain}" }
shared_examples 'a valid FunctionURI class' do
describe '#to_s' do
it 'matches valid URI' do
expect(subject.to_s).to eq valid_uri
end
end
describe '#function' do
it 'returns function' do
expect(subject.function).to eq function
end
end
describe '#cluster' do
it 'returns cluster' do
expect(subject.cluster).to eq cluster
end
end
describe '#environment' do
it 'returns environment' do
expect(subject.environment).to eq environment
end
end
end
describe '.new' do
context 'with valid arguments' do
subject { described_class.new(function: function, cluster: cluster, environment: environment) }
it_behaves_like 'a valid FunctionURI class'
end
context 'with invalid arguments' do
subject { described_class.new(function: function, environment: environment) }
it 'raises an exception' do
expect { subject }.to raise_error(ArgumentError)
end
end
end
describe '.parse' do
context 'with valid URI' do
subject { described_class.parse(valid_uri) }
it_behaves_like 'a valid FunctionURI class'
end
context 'with valid FQDN' do
subject { described_class.parse(valid_fqdn) }
it_behaves_like 'a valid FunctionURI class'
end
context 'with invalid URI' do
subject { described_class.parse(invalid_uri) }
it 'returns nil' do
expect(subject).to be_nil
end
end
end
end

View file

@ -94,17 +94,19 @@ describe Gitlab::Serverless::Service do
end
describe '#url' do
let(:serverless_domain) { instance_double(::Serverless::Domain, uri: URI('https://proxy.example.com')) }
it 'returns proxy URL if cluster has serverless domain' do
# cluster = create(:cluster)
knative = create(:clusters_applications_knative, :installed, cluster: cluster)
create(:serverless_domain_cluster, clusters_applications_knative_id: knative.id)
service = Gitlab::Serverless::Service.new(attributes.merge('cluster' => cluster))
expect(Gitlab::Serverless::FunctionURI).to receive(:new).with(
function: service.name,
cluster: service.cluster.serverless_domain,
expect(::Serverless::Domain).to receive(:new).with(
function_name: service.name,
serverless_domain_cluster: service.cluster.serverless_domain,
environment: service.environment
).and_return('https://proxy.example.com')
).and_return(serverless_domain)
expect(service.url).to eq('https://proxy.example.com')
end

View file

@ -274,7 +274,8 @@ describe Clusters::Applications::Prometheus do
subject { application.files_with_replaced_values({ hello: :world }) }
it 'does not modify #files' do
expect(subject[:'values.yaml']).not_to eq(files)
expect(subject[:'values.yaml']).not_to eq(files[:'values.yaml'])
expect(files[:'values.yaml']).to eq(application.values)
end
@ -282,27 +283,17 @@ describe Clusters::Applications::Prometheus do
expect(subject[:'values.yaml']).to eq({ hello: :world })
end
it 'includes cert files' do
expect(subject[:'ca.pem']).to be_present
expect(subject[:'ca.pem']).to eq(application.cluster.application_helm.ca_cert)
it 'uses values from #files, except for values.yaml' do
allow(application).to receive(:files).and_return({
'values.yaml': 'some value specific to files',
'file_a.txt': 'file_a',
'file_b.txt': 'file_b'
})
expect(subject[:'cert.pem']).to be_present
expect(subject[:'key.pem']).to be_present
cert = OpenSSL::X509::Certificate.new(subject[:'cert.pem'])
expect(cert.not_after).to be < 60.minutes.from_now
end
context 'when the helm application does not have a ca_cert' do
before do
application.cluster.application_helm.ca_cert = nil
end
it 'does not include cert files' do
expect(subject[:'ca.pem']).not_to be_present
expect(subject[:'cert.pem']).not_to be_present
expect(subject[:'key.pem']).not_to be_present
end
expect(subject.except(:'values.yaml')).to eq({
'file_a.txt': 'file_a',
'file_b.txt': 'file_b'
})
end
end

View file

@ -1264,6 +1264,14 @@ describe Environment, :use_clean_rails_memory_store_caching do
end
end
describe '.for_id_and_slug' do
subject { described_class.for_id_and_slug(environment.id, environment.slug) }
let(:environment) { create(:environment) }
it { is_expected.not_to be_nil }
end
describe '.find_or_create_by_name' do
it 'finds an existing environment if it exists' do
env = create(:environment)

View file

@ -10,7 +10,7 @@ describe ::Serverless::DomainCluster do
it { is_expected.to validate_presence_of(:knative) }
it { is_expected.to validate_presence_of(:uuid) }
it { is_expected.to validate_length_of(:uuid).is_equal_to(Gitlab::Serverless::Domain::UUID_LENGTH) }
it { is_expected.to validate_length_of(:uuid).is_equal_to(::Serverless::Domain::UUID_LENGTH) }
it { is_expected.to validate_uniqueness_of(:uuid) }
it 'validates that uuid has only hex characters' do
@ -31,7 +31,7 @@ describe ::Serverless::DomainCluster do
context 'when nil' do
it 'generates a value by default' do
attributes = build(:serverless_domain_cluster).attributes.merge(uuid: nil)
expect(Gitlab::Serverless::Domain).to receive(:generate_uuid).and_call_original
expect(::Serverless::Domain).to receive(:generate_uuid).and_call_original
subject = Serverless::DomainCluster.new(attributes)
@ -47,6 +47,10 @@ describe ::Serverless::DomainCluster do
end
end
describe 'cluster' do
it { is_expected.to respond_to(:cluster) }
end
describe 'domain' do
it { is_expected.to respond_to(:domain) }
end

View file

@ -0,0 +1,97 @@
# frozen_string_literal: true
require 'spec_helper'
describe ::Serverless::Domain do
let(:function_name) { 'test-function' }
let(:pages_domain_name) { 'serverless.gitlab.io' }
let(:pages_domain) { create(:pages_domain, :instance_serverless, domain: pages_domain_name) }
let!(:serverless_domain_cluster) { create(:serverless_domain_cluster, uuid: 'abcdef12345678', pages_domain: pages_domain) }
let(:valid_cluster_uuid) { 'aba1cdef123456f278' }
let(:invalid_cluster_uuid) { 'aba1cdef123456f178' }
let!(:environment) { create(:environment, name: 'test') }
let(:valid_uri) { "https://#{function_name}-#{valid_cluster_uuid}#{"%x" % environment.id}-#{environment.slug}.#{pages_domain_name}" }
let(:valid_fqdn) { "#{function_name}-#{valid_cluster_uuid}#{"%x" % environment.id}-#{environment.slug}.#{pages_domain_name}" }
let(:invalid_uri) { "https://#{function_name}-#{invalid_cluster_uuid}#{"%x" % environment.id}-#{environment.slug}.#{pages_domain_name}" }
shared_examples 'a valid Domain' do
describe '#uri' do
it 'matches valid URI' do
expect(subject.uri.to_s).to eq valid_uri
end
end
describe '#function_name' do
it 'returns function_name' do
expect(subject.function_name).to eq function_name
end
end
describe '#serverless_domain_cluster' do
it 'returns serverless_domain_cluster' do
expect(subject.serverless_domain_cluster).to eq serverless_domain_cluster
end
end
describe '#environment' do
it 'returns environment' do
expect(subject.environment).to eq environment
end
end
end
describe '.new' do
context 'with valid arguments' do
subject do
described_class.new(
function_name: function_name,
serverless_domain_cluster: serverless_domain_cluster,
environment: environment
)
end
it_behaves_like 'a valid Domain'
end
context 'with invalid arguments' do
subject do
described_class.new(
function_name: function_name,
environment: environment
)
end
it { is_expected.not_to be_valid }
end
context 'with nil cluster argument' do
subject do
described_class.new(
function_name: function_name,
serverless_domain_cluster: nil,
environment: environment
)
end
it { is_expected.not_to be_valid }
end
end
describe '.generate_uuid' do
it 'has 14 characters' do
expect(described_class.generate_uuid.length).to eq(described_class::UUID_LENGTH)
end
it 'consists of only hexadecimal characters' do
expect(described_class.generate_uuid).to match(/\A\h+\z/)
end
it 'uses random characters' do
uuid = 'abcd1234567890'
expect(SecureRandom).to receive(:hex).with(described_class::UUID_LENGTH / 2).and_return(uuid)
expect(described_class.generate_uuid).to eq(uuid)
end
end
end

View file

@ -28,22 +28,46 @@ RSpec.shared_examples 'cluster application helm specs' do |application_name|
describe '#files' do
subject { application.files }
context 'when the helm application does not have a ca_cert' do
context 'managed_apps_local_tiller feature flag is disabled' do
before do
application.cluster.application_helm.ca_cert = nil
stub_feature_flags(managed_apps_local_tiller: false)
end
it 'does not include cert files when there is no ca_cert entry' do
expect(subject).not_to include(:'ca.pem', :'cert.pem', :'key.pem')
context 'when the helm application does not have a ca_cert' do
before do
application.cluster.application_helm.ca_cert = nil
end
it 'does not include cert files when there is no ca_cert entry' do
expect(subject).not_to include(:'ca.pem', :'cert.pem', :'key.pem')
end
end
it 'includes cert files when there is a ca_cert entry' do
expect(subject).to include(:'ca.pem', :'cert.pem', :'key.pem')
expect(subject[:'ca.pem']).to eq(application.cluster.application_helm.ca_cert)
cert = OpenSSL::X509::Certificate.new(subject[:'cert.pem'])
expect(cert.not_after).to be < 60.minutes.from_now
end
end
it 'includes cert files when there is a ca_cert entry' do
expect(subject).to include(:'ca.pem', :'cert.pem', :'key.pem')
expect(subject[:'ca.pem']).to eq(application.cluster.application_helm.ca_cert)
context 'managed_apps_local_tiller feature flag is enabled' do
before do
stub_feature_flags(managed_apps_local_tiller: true)
end
cert = OpenSSL::X509::Certificate.new(subject[:'cert.pem'])
expect(cert.not_after).to be < 60.minutes.from_now
it 'does not include cert files' do
expect(subject).not_to include(:'ca.pem', :'cert.pem', :'key.pem')
end
context 'when cluster does not have helm installed' do
let(:application) { create(application_name, :no_helm_installed) }
it 'does not include cert files' do
expect(subject).not_to include(:'ca.pem', :'cert.pem', :'key.pem')
end
end
end
end
end

View file

@ -48,14 +48,44 @@ RSpec.shared_examples 'cluster application status specs' do |application_name|
expect(subject).to be_installed
end
it 'updates helm version' do
subject.cluster.application_helm.update!(version: '1.2.3')
context 'managed_apps_local_tiller feature flag disabled' do
before do
stub_feature_flags(managed_apps_local_tiller: false)
end
subject.make_installed!
it 'updates helm version' do
subject.cluster.application_helm.update!(version: '1.2.3')
subject.cluster.application_helm.reload
subject.make_installed!
expect(subject.cluster.application_helm.version).to eq(Gitlab::Kubernetes::Helm::HELM_VERSION)
subject.cluster.application_helm.reload
expect(subject.cluster.application_helm.version).to eq(Gitlab::Kubernetes::Helm::HELM_VERSION)
end
end
context 'managed_apps_local_tiller feature flag enabled' do
before do
stub_feature_flags(managed_apps_local_tiller: true)
end
it 'does not update the helm version' do
subject.cluster.application_helm.update!(version: '1.2.3')
expect do
subject.make_installed!
subject.cluster.application_helm.reload
end.not_to change { subject.cluster.application_helm.version }
end
context 'the cluster has no helm installed' do
subject { create(application_name, :installing, :no_helm_installed) }
it 'runs without errors' do
expect { subject.make_installed! }.not_to raise_error
end
end
end
it 'sets the correct version of the application' do
@ -77,14 +107,44 @@ RSpec.shared_examples 'cluster application status specs' do |application_name|
expect(subject).to be_updated
end
it 'updates helm version' do
subject.cluster.application_helm.update!(version: '1.2.3')
context 'managed_apps_local_tiller feature flag disabled' do
before do
stub_feature_flags(managed_apps_local_tiller: false)
end
subject.make_installed!
it 'updates helm version' do
subject.cluster.application_helm.update!(version: '1.2.3')
subject.cluster.application_helm.reload
subject.make_installed!
expect(subject.cluster.application_helm.version).to eq(Gitlab::Kubernetes::Helm::HELM_VERSION)
subject.cluster.application_helm.reload
expect(subject.cluster.application_helm.version).to eq(Gitlab::Kubernetes::Helm::HELM_VERSION)
end
end
context 'managed_apps_local_tiller feature flag enabled' do
before do
stub_feature_flags(managed_apps_local_tiller: true)
end
it 'does not update the helm version' do
subject.cluster.application_helm.update!(version: '1.2.3')
expect do
subject.make_installed!
subject.cluster.application_helm.reload
end.not_to change { subject.cluster.application_helm.version }
end
context 'the cluster has no helm installed' do
subject { create(application_name, :updating, :no_helm_installed) }
it 'runs without errors' do
expect { subject.make_installed! }.not_to raise_error
end
end
end
it 'updates the version of the application' do