Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(provider/google): Added Cloud Run manifest functionality in Deck. #9971

Merged
merged 6 commits into from
May 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/cloudrun/src/cloudrun.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { CLOUDRUN_LOAD_BALANCER_DETAILS_CTRL } from './loadBalancer/details/deta
import { CLOUDRUN_LOAD_BALANCER_TRANSFORMER } from './loadBalancer/loadBalancerTransformer';
import logo from './logo/cloudrun.logo.png';
import { CLOUDRUN_PIPELINE_MODULE } from './pipeline/pipeline.module';
import './pipeline/stages/deployManifest/deployStage';
import { CLOUDRUN_SERVER_GROUP_COMMAND_BUILDER } from './serverGroup/configure/serverGroupCommandBuilder.service';
import { ServerGroupWizard } from './serverGroup/configure/wizard/serverGroupWizard';
import { CLOUDRUN_SERVER_GROUP_DETAILS_CTRL } from './serverGroup/details/details.controller';
Expand Down
32 changes: 32 additions & 0 deletions packages/cloudrun/src/help/cloudrun.help.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,38 @@ const helpContents = [
key: 'cloudrun.loadBalancer.allocations',
value: 'An allocation is the percent of traffic directed to a server group.',
},
{
key: 'cloudrun.manifest.source',
value: `
<p>Where the manifest file content is read from.</p>
<p>
<b>text:</b> The manifest is supplied statically to the pipeline from the below text-box.
</p>
<p>
<b>artifact:</b> The manifest is read from an artifact supplied/created upstream. The expected artifact must be referenced here, and will be bound at runtime.
</p>`,
},
{
key: 'cloudrun.manifest.expectedArtifact',
value:
'The artifact that is to be applied to the Cloud Run account for this stage. The artifact should represent a valid Cloud Run manifest.',
},
{
key: 'cloudrun.manifest.skipExpressionEvaluation',
value:
'<p>Skip SpEL expression evaluation of the manifest artifact in this stage. Can be paired with the "Evaluate SpEL expressions in overrides at bake time" option in the Bake Manifest stage when baking a third-party manifest artifact with expressions not meant for Spinnaker to evaluate as SpEL.</p>',
},
{
key: 'cloudrun.manifest.requiredArtifactsToBind',
value:
'These artifacts must be present in the context for this stage to successfully complete. Artifacts specified will be <a href="https://www.spinnaker.io/reference/artifacts/in-cloudrun-v2/#binding-artifacts-in-manifests" target="_blank">bound to the deployed manifest.</a>',
},
{
key: 'cloudrun.manifest.account',
value: `
<p>A Spinnaker account corresponds to a physical Cloud Run cluster. If you are unsure which account to use, talk to your Spinnaker admin.</p>
`,
},
];

helpContents.forEach((entry) => HelpContentsRegistry.register(entry.key, entry.value));
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
application="$ctrl.application"
region="{{ $ctrl.loadBalancer.region }}"
account="{{ $ctrl.loadBalancer.account || $ctrl.loadBalancer.credentials }}"
name="{{ $ctrl.loadBalancer.name }}"
server-group-options="$ctrl.serverGroupOptions"
on-allocation-change="$ctrl.updateServerGroupOptions()"
remove-allocation="$ctrl.removeAllocation($index)"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,21 @@
</div>
</div>
</div>
<div class="form-group">
<div class="well col-md-11" style="padding-top: 5px; padding-bottom: 10px">
<div class="row">
<div class="form-group">
<div>
<div class="col-md-3 sm-label-right">Cluster</div>
<div class="col-md-7" style="margin-bottom: 10px">
<input ng-model="$ctrl.allocationDescription.cluster" type="text" class="form-control input-sm" readonly />
</div>
<div class="col-md-3 sm-label-right">Target</div>
<div class="col-md-7">
<target-select model="$ctrl.allocationDescription" options="$ctrl.targets"></target-select>
</div>
</div>
</div>
</div>
</div>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,36 @@ import type { IComponentOptions, IController } from 'angular';
import { module } from 'angular';
import { uniq } from 'lodash';

import type { Application } from '@spinnaker/core';
import { AppListExtractor, StageConstants } from '@spinnaker/core';
import { StageConstants } from '@spinnaker/core';

import type { ICloudrunAllocationDescription } from '../../loadBalancerTransformer';

class CloudrunStageAllocationLabelCtrl implements IController {
public inputViewValue: string;
private allocationDescription: ICloudrunAllocationDescription;

private static mapTargetCoordinateToLabel(targetCoordinate: string): string {
const target = StageConstants.TARGET_LIST.find((t) => t.val === targetCoordinate);
if (target) {
return target.label;
} else {
return null;
}
}

public $doCheck(): void {
this.setInputViewValue();
}

private setInputViewValue(): void {
this.inputViewValue = this.allocationDescription.revisionName;
if (this.allocationDescription.cluster && this.allocationDescription.target) {
const targetLabel = CloudrunStageAllocationLabelCtrl.mapTargetCoordinateToLabel(
this.allocationDescription.target,
);
this.inputViewValue = `${targetLabel} (${this.allocationDescription.cluster})`;
} else {
this.inputViewValue = null;
}
}
}

Expand All @@ -32,13 +47,10 @@ class CloudrunStageAllocationConfigurationRowCtrl implements IController {
public targets = StageConstants.TARGET_LIST;
public clusterList: string[];
public onAllocationChange: Function;
private application: Application;
private region: string;
private account: string;
private name: string;

public $onInit() {
const clusterFilter = AppListExtractor.clusterFilterForCredentialsAndRegion(this.account, this.region);
this.clusterList = AppListExtractor.getClusters([this.application], clusterFilter);
this.allocationDescription.cluster = this.name;
}

public getServerGroupOptions(): string[] {
Expand All @@ -48,21 +60,14 @@ class CloudrunStageAllocationConfigurationRowCtrl implements IController {
return this.serverGroupOptions;
}
}

public onLocatorTypeChange(): void {
// Prevents pipeline expressions (or non-existent server groups) from entering the dropdown.
if (!this.serverGroupOptions.includes(this.allocationDescription.revisionName)) {
delete this.allocationDescription.revisionName;
}
this.onAllocationChange();
}
}

const cloudrunStageAllocationConfigurationRow: IComponentOptions = {
bindings: {
application: '<',
region: '@',
account: '@',
name: '@',
allocationDescription: '<',
removeAllocation: '&',
serverGroupOptions: '<',
Expand Down
3 changes: 3 additions & 0 deletions packages/cloudrun/src/loadBalancer/details/details.html
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ <h3 class="horizontal middle space-between flex-1" select-on-dbl-click>{{ctrl.lo
</dd>
</dl>
</collapsible-section>
<collapsible-section heading="Status" expanded="true">
<health-counts class="pull-left" container="ctrl.loadBalancer.instanceCounts"></health-counts>
</collapsible-section>
<collapsible-section heading="Traffic Split" expanded="true">
<dl class="dl-horizontal dl-narrow">
<ul>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export class CloudrunLoadBalancerUpsertDescription implements ILoadBalancerUpser
split.trafficTargets,
(acc: any, trafficTarget: any) => {
const { revisionName, percent } = trafficTarget;
return acc.concat({ percent, revisionName, locatorType: 'fromExisting' });
return acc.concat({ percent, revisionName });
},
[],
);
Expand Down
4 changes: 4 additions & 0 deletions packages/cloudrun/src/manifest/ManifestSource.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export enum ManifestSource {
TEXT = 'text',
ARTIFACT = 'artifact',
}
2 changes: 2 additions & 0 deletions packages/cloudrun/src/manifest/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './manifest.service';
export * from './manifestCommandBuilder.service';
89 changes: 89 additions & 0 deletions packages/cloudrun/src/manifest/manifest.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import type { Application, IManifest } from '@spinnaker/core';
import { ManifestReader } from '@spinnaker/core';

export interface IStageManifest {
kind: string;
apiVersion: string;
metadata: {
namespace: string;
name: string;
};
}

export interface IManifestParams {
account: string;
location: string;
name: string;
}

export type IManifestCallback = (manifest: IManifest) => void;

export class CloudrunManifestService {
public static subscribe(app: Application, params: IManifestParams, fn: IManifestCallback): () => void {
CloudrunManifestService.updateManifest(params, fn);
return app.onRefresh(null, () => CloudrunManifestService.updateManifest(params, fn));
}

private static updateManifest(params: IManifestParams, fn: IManifestCallback) {
ManifestReader.getManifest(params.account, params.location, params.name).then((manifest) => fn(manifest));
}

public static manifestIdentifier(manifest: IStageManifest) {
const kind = manifest.kind.toLowerCase();
// manifest.metadata.namespace doesn't exist if it's a namespace being deployed
const namespace = (manifest.metadata.namespace || '_').toLowerCase();
const name = manifest.metadata.name.toLowerCase();
const apiVersion = (manifest.apiVersion || '_').toLowerCase();
// assuming this identifier is opaque and not parsed anywhere. Including the
// apiVersion will prevent collisions with CRD kinds without having any visible
// effect elsewhere
return `${namespace} ${kind} ${apiVersion} ${name}`;
}

public static stageManifestToManifestParams(manifest: IStageManifest, account: string): IManifestParams {
return {
account,
name: CloudrunManifestService.scopedKind(manifest) + ' ' + manifest.metadata.name,
location: manifest.metadata.namespace == null ? '_' : manifest.metadata.namespace,
};
}

private static apiGroup(manifest: IStageManifest): string {
const parts = (manifest.apiVersion || '_').split('/');
if (parts.length < 2) {
return '';
}
return parts[0];
}

private static isCRDGroup(manifest: IStageManifest): boolean {
return !CloudrunManifestService.BUILT_IN_GROUPS.includes(CloudrunManifestService.apiGroup(manifest));
}

private static scopedKind(manifest: IStageManifest): string {
if (CloudrunManifestService.isCRDGroup(manifest)) {
return manifest.kind + '.' + CloudrunManifestService.apiGroup(manifest);
}

return manifest.kind;
}

private static readonly BUILT_IN_GROUPS = [
'',
'core',
'batch',
'apps',
'extensions',
'storage.k8s.io',
'apiextensions.k8s.io',
'apiregistration.k8s.io',
'policy',
'scheduling.k8s.io',
'settings.k8s.io',
'authorization.k8s.io',
'authentication.k8s.io',
'rbac.authorization.k8s.io',
'certifcates.k8s.io',
'networking.k8s.io',
];
}
116 changes: 116 additions & 0 deletions packages/cloudrun/src/manifest/manifestCommandBuilder.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { cloneDeep } from 'lodash';
import { $q } from 'ngimport';

import type { Application, IAccountDetails, IArtifactAccount, IMoniker } from '@spinnaker/core';
import { AccountService } from '@spinnaker/core';

import { ManifestSource } from './ManifestSource';

export interface ICloudrunManifestCommandData {
command: ICloudrunManifestCommand;
metadata: ICloudrunManifestCommandMetadata;
}

export interface ICloudrunManifestCommand {
account: string;
cloudProvider: string;
manifest: any;
manifests: any[];
relationships: ICloudrunManifestSpinnakerRelationships;
moniker: IMoniker;
manifestArtifactId?: string;
manifestArtifactAccount?: string;
source: ManifestSource;
versioned?: boolean;
}

export interface ICloudrunManifestCommandMetadata {
backingData: any;
}

export interface ICloudrunManifestSpinnakerRelationships {
loadBalancers?: string[];
securityGroups?: string[];
}

export class CloudrunManifestCommandBuilder {
public static manifestCommandIsValid(command: ICloudrunManifestCommand): boolean {
if (!command.moniker) {
return false;
}

if (!command.moniker.app) {
return false;
}

return true;
}

public static copyAndCleanCommand(input: ICloudrunManifestCommand): ICloudrunManifestCommand {
const command = cloneDeep(input);
return command;
}

public static buildNewManifestCommand(
app: Application,
sourceManifest?: any,
sourceMoniker?: IMoniker,
sourceAccount?: string,
): PromiseLike<ICloudrunManifestCommandData> {
const dataToFetch = {
accounts: AccountService.getAllAccountDetailsForProvider('cloudrun'),
artifactAccounts: AccountService.getArtifactAccounts(),
};

return $q
.all(dataToFetch)
.then((backingData: { accounts: IAccountDetails[]; artifactAccounts: IArtifactAccount[] }) => {
const { accounts, artifactAccounts } = backingData;

const account = accounts.some((a) => a.name === sourceAccount)
? accounts.find((a) => a.name === sourceAccount).name
: accounts.length
? accounts[0].name
: null;

let manifestArtifactAccount: string = null;
const [artifactAccountData] = artifactAccounts;
if (artifactAccountData) {
manifestArtifactAccount = artifactAccountData.name;
}

const cloudProvider = 'cloudrun';
const moniker = sourceMoniker || {
app: app.name,
};

const relationships = {
loadBalancers: [] as string[],
securityGroups: [] as string[],
};

const versioned: any = null;

return {
command: {
cloudProvider,
manifest: null,
manifests: Array.isArray(sourceManifest)
? sourceManifest
: sourceManifest != null
? [sourceManifest]
: null,
relationships,
moniker,
account,
versioned,
manifestArtifactAccount,
source: ManifestSource.TEXT,
},
metadata: {
backingData,
},
} as ICloudrunManifestCommandData;
});
}
}
Loading