blob: e1c3f3b22a5855597f0f718ba54d6b70d3c701bb [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as vscode from 'vscode';
import * as openwhisk from 'openwhisk';
import * as request from 'request-promise-native';
import { getLocalAuthInfo } from './authentication';
import { StorageManager, SavedNamespaceState } from './storage';
import {
WskEntity,
WskEndpoint,
WskNamespace,
WskPackage,
createWskPackage,
createWskTrigger,
createWskAction,
} from './wskEntity';
export class WskEntityProvider implements vscode.TreeDataProvider<WskEntity> {
private _onDidChangeTreeData: vscode.EventEmitter<
WskEntity | undefined
> = new vscode.EventEmitter<WskEntity | undefined>();
readonly onDidChangeTreeData: vscode.Event<WskEntity | undefined> = this._onDidChangeTreeData
.event;
private storageManager: StorageManager;
constructor(public context: vscode.ExtensionContext) {
this.storageManager = new StorageManager(context.globalState);
}
refresh(): void {
this._onDidChangeTreeData.fire(undefined);
}
async getTreeItem(element: WskEntity): Promise<vscode.TreeItem> {
return element;
}
async getChildren(element?: WskEntity): Promise<WskEntity[]> {
const listOptions = { limit: 0 };
const compareName = (a: openwhisk.Desc, b: openwhisk.Desc) =>
a.name?.localeCompare(b.name as string) as number;
if (element) {
if (element instanceof WskEndpoint) {
const namespaces = this.storageManager.getNamespaces(element.apihost);
if (namespaces) {
return Promise.resolve(
namespaces.map(
(ns) =>
new WskNamespace(
ns.name,
openwhisk({ apihost: element.apihost, api_key: ns.api_key }),
element
)
)
);
}
} else if (element instanceof WskNamespace) {
const packages = element.client.packages
.list(listOptions)
.then((ps) =>
ps.sort(compareName).map((p) => createWskPackage(p, element.client))
);
const triggers = element.client.triggers
.list(listOptions)
.then((ts) =>
ts.sort(compareName).map((t) => createWskTrigger(t, element.client))
);
const actions = element.client.actions.list(listOptions).then((as) =>
as
.filter((a) => !a.namespace?.includes('/'))
.sort(compareName)
.map((a) => createWskAction(a, element.client))
);
return Promise.all<WskEntity[], WskEntity[], WskEntity[]>([
packages,
actions,
triggers,
]).then((arrs) => Promise.resolve<WskEntity[]>(arrs[0].concat(...arrs.slice(1))));
} else if (element instanceof WskPackage) {
if (element.packageDesc.name) {
const actionsInPackage = (
await element.client.packages.get(element.packageDesc.name)
).actions;
if (actionsInPackage) {
return Promise.resolve(
actionsInPackage.map((a) =>
createWskAction(a, element.client, element.packageDesc)
)
);
}
}
}
} else {
try {
await updateStatesByLocalWskPropFile(this.storageManager);
} catch (e) {
vscode.window.showErrorMessage(`Failed to load .wskprops (${e.message})`);
}
const endpoints = this.storageManager.getEndpoints();
if (endpoints) {
return Promise.resolve(
Object.entries(endpoints).map(
([host, value]) => new WskEndpoint(value.alias, host)
)
);
}
}
return [];
}
}
export async function updateStatesByLocalWskPropFile(manager: StorageManager): Promise<void> {
const localAuthInfo = await getLocalAuthInfo();
if (!localAuthInfo) {
return Promise.resolve();
}
const [isValid, apihost] = await validateURL(localAuthInfo.apihost);
let isExisted: any = manager.getEndpoints();
if (isExisted !== undefined) {
isExisted = isExisted[apihost];
if (isExisted !== undefined) {
isExisted = isExisted['namespaces'].find(
(namespace: SavedNamespaceState) => namespace.api_key === localAuthInfo.api_key
);
}
}
if (isExisted === undefined) {
const namespaceNames = await openwhisk({
apihost: apihost,
api_key: localAuthInfo.api_key,
}).namespaces.list();
try {
if (isValid) {
return manager.updateEndpoints(apihost, {
alias: localAuthInfo.apihost,
apihost: apihost,
namespaces: [
{
api_key: localAuthInfo.api_key,
name: namespaceNames[0],
},
],
});
}
} catch (e) {
return Promise.reject(e);
}
}
return Promise.resolve();
}
export async function addEndpoint(context: vscode.ExtensionContext) {
const manager = new StorageManager(context.globalState);
const endpoints = manager.getEndpoints();
const baseOptions = { ignoreFocusOut: true };
const rawApihost = await vscode.window.showInputBox({
...baseOptions,
prompt: 'Openwhisk API Host',
placeHolder: 'Enter Openwhisk API Host',
});
if (rawApihost === undefined) {
throw Error('Empty apihost');
}
const [isValid, apihost] = await validateURL(rawApihost);
if (!isValid) {
vscode.window.showErrorMessage(
'Failed to add new API host, please check the API host is valid'
);
return;
}
if (endpoints[apihost]) {
vscode.window.showErrorMessage('This API host is already registered');
return;
}
let alias = await vscode.window.showInputBox({
...baseOptions,
prompt: 'Alias for the endpoint',
placeHolder: 'Enter alias for the endpoint',
});
if (alias === undefined) {
return;
} else if (alias === '') {
alias = apihost;
}
if (Object.values(endpoints).find((e) => e.alias === alias)) {
vscode.window.showErrorMessage('Already existed alias');
return;
}
return manager.updateEndpoints(apihost, {
apihost,
alias: alias as string,
namespaces: [],
});
}
export function removeEndpoint(endpoint: WskEndpoint, context: vscode.ExtensionContext) {
const manager = new StorageManager(context.globalState);
const endpoints = manager.getEndpoints();
delete endpoints[endpoint.apihost];
return context.globalState.update('endpoints', endpoints);
}
export async function addNamespace(
apihost: string,
context: vscode.ExtensionContext
): Promise<void> {
const inputOptions = {
prompt: `Authorization KEY for ${apihost}`,
placeHolder: 'Enter authorization KEY',
password: true,
ignoreFocusOut: true,
};
const manager = new StorageManager(context.globalState);
const api_key = await vscode.window.showInputBox(inputOptions);
if (api_key === undefined) {
// it's cancelled
return;
}
if (manager.getEndpoints()[apihost].namespaces.find((ns) => ns.api_key === api_key)) {
vscode.window.showErrorMessage('Already registered namespace');
return;
}
const ns = await openwhisk({ apihost, api_key }).namespaces.list();
return manager.addNamespace(apihost, { name: ns[0], api_key });
}
export async function renameEndpoint(apihost: string, context: vscode.ExtensionContext) {
const manager = new StorageManager(context.globalState);
const endpoints = manager.getEndpoints();
const newAlias = await vscode.window.showInputBox({
placeHolder: 'New Alias for the Endpoint',
});
if (newAlias === undefined) {
return;
}
if (Object.values(endpoints).find((e) => e.alias === newAlias)) {
vscode.window.showErrorMessage('Duplicated alias');
}
const endpoint = endpoints[apihost];
endpoint.alias = newAlias;
return manager.updateEndpoints(apihost, endpoint);
}
export async function removeNamespace(namespace: WskNamespace, context: vscode.ExtensionContext) {
const manager = new StorageManager(context.globalState);
// @ts-ignore
const client: { apiKey: string } = namespace.client.namespaces.client.options;
const apihost = namespace.wskEndpoint.apihost;
const api_key = client.apiKey;
const endpoints = manager.getEndpoints();
const endpoint = endpoints[apihost];
if (endpoint) {
endpoint.namespaces = endpoint.namespaces.filter((n) => n.api_key !== api_key);
await context.globalState.update('endpoints', endpoints);
}
}
async function validateURL(url: string): Promise<[boolean, string]> {
const baseOptions = {
json: true,
resolveWithFullResponse: true,
};
const falseReturn: [boolean, string] = [false, ''];
let isValid = falseReturn;
if (url.startsWith('https://') || url.startsWith('http://')) {
try {
isValid = await request.get({ ...baseOptions, url }).then((res) => {
if (res.statusCode >= 200 && res.statusCode < 300) {
return [true, url];
}
return falseReturn;
});
} catch (e) {
isValid = falseReturn;
}
} else {
try {
isValid = await request.get({ url: 'https://' + url, ...baseOptions }).then((res) => {
if (res.statusCode >= 200 && res.statusCode < 300) {
return [true, 'https://' + url];
}
return falseReturn;
});
} catch (e) {
try {
isValid = await request
.get({ url: 'http://' + url, ...baseOptions })
.then((res) => {
if (res.statusCode >= 200 && res.statusCode < 300) {
return [true, 'http://' + url];
}
return falseReturn;
});
} catch (e) {
isValid = falseReturn;
}
}
}
return isValid;
}