Skip to content

Commit

Permalink
Create route functionality added (#4140)
Browse files Browse the repository at this point in the history
* create route functionality added

* added secure and un secured routes

* fix lint issues

* fixed lint issues

* fixed review comments

* fixed mentioned comments

* add separate validate name junction for json value
  • Loading branch information
msivasubramaniaan authored May 27, 2024
1 parent 315e8fb commit d7db511
Show file tree
Hide file tree
Showing 19 changed files with 861 additions and 10 deletions.
1 change: 1 addition & 0 deletions build/esbuild.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import * as fs from 'fs/promises';
const webviews = [
'cluster',
'create-service',
'create-route',
'create-component',
'devfile-registry',
'helm-chart',
Expand Down
10 changes: 10 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -807,6 +807,11 @@
"title": "Create Operator-Backed Service",
"category": "OpenShift"
},
{
"command": "openshift.route.create",
"title": "Create Route",
"category": "OpenShift"
},
{
"command": "openshift.deployment.create.fromImageUrl",
"title": "Create Deployment from Container Image URL",
Expand Down Expand Up @@ -1626,6 +1631,11 @@
"when": "view == openshiftProjectExplorer && isLoggedIn && viewItem =~ /openshift.project.*/i && showCreateService",
"group": "c2"
},
{
"command": "openshift.route.create",
"when": "view == openshiftProjectExplorer && isLoggedIn && viewItem =~ /openshift.project.*/i && isOpenshiftCluster && showCreateRoute",
"group": "c2"
},
{
"command": "openshift.deployment.create.fromImageUrl",
"when": "view == openshiftProjectExplorer && isLoggedIn && viewItem =~ /openshift.project.*/i",
Expand Down
22 changes: 19 additions & 3 deletions src/explorer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,15 @@ import * as Helm from './helm/helm';
import { HelmRepo } from './helm/helmChartType';
import { Oc } from './oc/ocWrapper';
import { Component } from './openshift/component';
import { getServiceKindStubs } from './openshift/serviceHelpers';
import { getServiceKindStubs, getServices } from './openshift/serviceHelpers';
import { PortForward } from './port-forward';
import { KubeConfigUtils, getKubeConfigFiles, getNamespaceKind } from './util/kubeUtils';
import { LoginUtil } from './util/loginUtil';
import { Platform } from './util/platform';
import { Progress } from './util/progress';
import { FileContentChangeNotifier, WatchUtil } from './util/watch';
import { vsCommand } from './vscommand';
import { CustomResourceDefinitionStub } from './webview/common/createServiceTypes';
import { CustomResourceDefinitionStub, K8sResourceKind } from './webview/common/createServiceTypes';
import { OpenShiftTerminalManager } from './webview/openshift-terminal/openShiftTerminal';
import { getOutputFormat, helmfsUri, kubefsUri } from './k8s/vfs/kuberesources.virtualfs';

Expand Down Expand Up @@ -355,6 +355,16 @@ export class OpenShiftExplorer implements TreeDataProvider<ExplorerItem>, Dispos
// operator framework is not installed on cluster; do nothing
}
void commands.executeCommand('setContext', 'showCreateService', serviceKinds.length > 0);

// The 'Create Route' menu visibility
let services: K8sResourceKind[] = [];
try {
services = await getServices();
}
catch (_) {
// operator framework is not installed on cluster; do nothing
}
void commands.executeCommand('setContext', 'showCreateRoute', services.length > 0);
} else if ('kind' in element && element.kind === 'helmContexts') {
const helmRepos = {
kind: 'helmRepos',
Expand Down Expand Up @@ -411,6 +421,12 @@ export class OpenShiftExplorer implements TreeDataProvider<ExplorerItem>, Dispos
name: 'pods'
},
} as OpenShiftObject;
const routes = {
kind: 'routes',
metadata: {
name: 'routes'
},
} as OpenShiftObject;
const statefulSets = {
kind: 'statefulsets',
metadata: {
Expand Down Expand Up @@ -456,7 +472,7 @@ export class OpenShiftExplorer implements TreeDataProvider<ExplorerItem>, Dispos
result.push(pods,
statefulSets, daemonSets, jobs, cronJobs);
if (isOpenshiftCluster) {
result.push(deploymentConfigs, imageStreams, buildConfigs);
result.push(deploymentConfigs, imageStreams, buildConfigs, routes);
}
} else if ('kind' in element) {
const collectableServices: CustomResourceDefinitionStub[] = await this.getServiceKinds();
Expand Down
1 change: 1 addition & 0 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ export async function activate(extensionContext: ExtensionContext): Promise<unkn
'./openshift/project',
'./openshift/cluster',
'./openshift/service',
'./openshift/route',
'./k8s/console',
'./yamlFileCommands',
'./registriesView',
Expand Down
5 changes: 5 additions & 0 deletions src/k8s/olm/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -352,4 +352,9 @@ export type CapabilityProps<C extends SpecCapability | StatusCapability> = {
value: any;
};

export type Service = {
apiVersion: string;
items: K8sResourceKind[];
}

export type Error = { message: string };
1 change: 0 additions & 1 deletion src/k8s/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
* Copyright (c) Red Hat, Inc. All rights reserved.
* Licensed under the MIT License. See LICENSE file in the project root for license information.
*-----------------------------------------------------------------------------------------------*/

import { KubeConfig } from '@kubernetes/client-node';
import { commands, Uri } from 'vscode';
import { Oc } from '../oc/ocWrapper';
Expand Down
33 changes: 33 additions & 0 deletions src/oc/ocWrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { KubeConfigUtils } from '../util/kubeUtils';
import { Platform } from '../util/platform';
import { Project } from './project';
import { ClusterType, KubernetesConsole } from './types';
import validator from 'validator';

/**
* A wrapper around the `oc` CLI tool.
Expand Down Expand Up @@ -506,6 +507,38 @@ export class Oc {
});
}

public async createRoute(routeName: string, serviceName: string, hostName: string, path: string, port: { number: string, name: string, protocol: string, targetPort: string },
isSecured: boolean): Promise<string> {
let cmdText: CommandText;
if (isSecured) {

cmdText = new CommandText('oc', `create route edge ${routeName}`, [
new CommandOption('--service', serviceName),
new CommandOption('--port', port.number),
]);

} else {
cmdText = new CommandText('oc', `expose service ${serviceName.trim()}`, [
new CommandOption('--name', routeName),
new CommandOption('--port', port.number),
new CommandOption('--target-port', port.targetPort),
new CommandOption('--protocol', port.protocol)
]);
}

if (!validator.isEmpty(hostName)) {
cmdText.addOption(new CommandOption('--hostname', hostName));
}

if (!validator.isEmpty(path)) {
cmdText.addOption(new CommandOption('--path', path));
}
return await CliChannel.getInstance().executeTool(
cmdText
)
.then((result) => result.stdout);
}

/**
* Changes which project is currently being used.
*
Expand Down
5 changes: 5 additions & 0 deletions src/openshift/nameValidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ export function validateMatches(message: string, value: string): string | null {
return validator.matches(value, '^[a-z]([-a-z0-9]*[a-z0-9])*$') ? null : message;
}

export function validatePath(message: string, value: string): string | null {
const pathRegx = value.match(/^(\/{1}(?!\/))[A-Za-z0-9/\-_]*(([a-zA-Z]+))?$/);
return pathRegx ? null : message;
}

export function validateFilePath(message: string, value: string): string | null {
const proposedPath = path.parse(value);
return /^devfile\.ya?ml$/i.test(proposedPath.base) ? null : message;
Expand Down
17 changes: 17 additions & 0 deletions src/openshift/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*-----------------------------------------------------------------------------------------------
* Copyright (c) Red Hat, Inc. All rights reserved.
* Licensed under the MIT License. See LICENSE file in the project root for license information.
*-----------------------------------------------------------------------------------------------*/

import { vsCommand } from '../vscommand';
import CreateRouteViewLoader from '../webview/create-route/createRouteViewLoader';

/**
* Wraps commands that are used for interacting with routes.
*/
export class Route {
@vsCommand('openshift.route.create')
static async createNewRoute() {
await CreateRouteViewLoader.loadView();
}
}
7 changes: 7 additions & 0 deletions src/openshift/serviceHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* Licensed under the MIT License. See LICENSE file in the project root for license information.
*-----------------------------------------------------------------------------------------------*/

import { K8sResourceKind } from '../k8s/olm/types';
import { Oc } from '../oc/ocWrapper';
import {
ClusterServiceVersion,
Expand All @@ -21,3 +22,9 @@ export async function getServiceKindStubs(): Promise<CustomResourceDefinitionStu
return serviceKinds;
});
}

export async function getServices(): Promise<K8sResourceKind[]> {
return (await Oc.Instance.getKubernetesObjects(
'service'
)) as unknown as K8sResourceKind[];
}
30 changes: 24 additions & 6 deletions src/webview/common-ext/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ function isGitURL(host: string): boolean {
].includes(host);
}

export function validateURL(event: Message): validateURLProps {
if (typeof event.data === 'string' && (event.data).trim().length === 0) {
export function validateURL(event: Message | { command: string; data: object }, isRequired = true): validateURLProps {
if (isRequired && typeof event.data === 'string' && (event.data).trim().length === 0) {
return {
url: event.data,
error: true,
Expand All @@ -78,7 +78,7 @@ export function validateURL(event: Message): validateURLProps {
return {
url: event.data,
error: false,
helpText: 'URL is valid'
helpText: !isRequired ? '' : 'URL is valid'
} as validateURLProps
}

Expand Down Expand Up @@ -119,13 +119,31 @@ export function validateGitURL(event: Message): validateURLProps {
}

export function validateName(value: string): string | null {
let validationMessage = NameValidator.emptyName('Required', value.trim());
let validationMessage = NameValidator.emptyName('Required', value);
if (!validationMessage) {
validationMessage = NameValidator.validateMatches(
'Only lower case alphabets and numeric characters or \'-\', start and ends with only alphabets',
value,
'Only lower case alphabets and numeric characters or \'-\', start and ends with only alphabets', value
);
}
if (!validationMessage) { validationMessage = NameValidator.lengthName('Should be between 2-63 characters', value, 0); }
return validationMessage;
}

export function validateJSONValue(value: string): string | null {
let validationMessage = NameValidator.emptyName('Required', JSON.parse(value) as unknown as string);
if (!validationMessage) {
validationMessage = NameValidator.validateMatches(
'Only lower case alphabets and numeric characters or \'-\', start and ends with only alphabets',
JSON.parse(value) as unknown as string
);
}
if (!validationMessage) { validationMessage = NameValidator.lengthName('Should be between 2-63 characters', JSON.parse(value) as unknown as string, 0); }
return validationMessage;
}

export function validatePath(value: string): string | null {
return NameValidator.validatePath(
'Given path is not valid',
JSON.parse(value) as unknown as string
);
}
69 changes: 69 additions & 0 deletions src/webview/common/createServiceTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,72 @@ export type SpecDescriptor = {
*/
path: string;
}

export type OwnerReference = {
name: string;
kind: string;
uid: string;
apiVersion: string;
controller?: boolean;
blockOwnerDeletion?: boolean;
};

export type ObjectMetadata = {
annotations?: { [key: string]: string };
clusterName?: string;
creationTimestamp?: string;
deletionGracePeriodSeconds?: number;
deletionTimestamp?: string;
finalizers?: string[];
generateName?: string;
generation?: number;
labels?: { [key: string]: string };
managedFields?: any[];
name?: string;
namespace?: string;
ownerReferences?: OwnerReference[];
resourceVersion?: string;
uid?: string;
};

// Properties common to (almost) all Kubernetes resources.
export type K8sResourceCommon = {
apiVersion?: string;
kind?: string;
metadata?: ObjectMetadata;
};

export type MatchExpression = {
key: string;
operator: 'Exists' | 'DoesNotExist' | 'In' | 'NotIn' | 'Equals' | 'NotEqual';
values?: string[];
value?: string;
};

export type MatchLabels = {
[key: string]: string;
};

export type Selector = {
matchLabels?: MatchLabels;
matchExpressions?: MatchExpression[];
};

export type Port = {
name: string;
port: string;
protocol: string;
targetPort: string;
};

// Generic, unknown kind. Avoid when possible since it allows any key in spec
// or status, weakening type checking.
export type K8sResourceKind = K8sResourceCommon & {
spec?: {
selector?: Selector | MatchLabels;
ports?: Port[];
[key: string]: any;
};
status?: { [key: string]: any };
data?: { [key: string]: any };
};
25 changes: 25 additions & 0 deletions src/webview/common/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*-----------------------------------------------------------------------------------------------
* Copyright (c) Red Hat, Inc. All rights reserved.
* Licensed under the MIT License. See LICENSE file in the project root for license information.
*-----------------------------------------------------------------------------------------------*/

export type RouteInputBoxText = {

name: string;
error: boolean;
helpText: string;
}

export type CreateRoute = {
routeName: string;
hostname: string;
path: string;
serviceName: string;
port: {
number: string;
name: string;
protocal: string;
targetPort: string;
};
isSecured: boolean;
}
Loading

0 comments on commit d7db511

Please sign in to comment.