Skip to content
This repository has been archived by the owner on Dec 22, 2021. It is now read-only.

Commit

Permalink
feat: remove
Browse files Browse the repository at this point in the history
  • Loading branch information
wss-git committed Jul 12, 2021
1 parent 30e2a05 commit 1f36481
Show file tree
Hide file tree
Showing 10 changed files with 302 additions and 66 deletions.
34 changes: 16 additions & 18 deletions examples/http-trigger/s.yaml
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
edition: 1.0.0 # 命令行YAML规范版本,遵循语义化版本(Semantic Versioning)规范
name: fcBaseApp # 项目名称
access: default # 秘钥别名
edition: 1.0.0
name: fcBaseApp
access: default

services:
fc-base-test: # 服务名称
# component: fc-base # 组件名称
component: /Users/wb447188/Desktop/sdk
props: # 组件的属性值
fc-base-test:
component: ${path(../..)}
props:
region: cn-shenzhen
service:
name: qianfeng-fc-base-service
name: fc-base-service
function:
name: test-function
service: qianfeng-fc-base-service
service: fc-base-service
filename: './code.zip'
handler: 'index.handler'
memorySize: 128
Expand All @@ -21,22 +20,21 @@ services:
triggers:
- name: httpTrigger
function: test-function
service: qianfeng-fc-base-service
service: fc-base-service
type: http
config:
authType: anonymous
methods:
- GET
fc-base-test-1: # 服务名称
# component: fc-base # 组件名称
component: /Users/zqf/Documents/git_proj/serverless-devs-component/fc-base-alibaba-component/
props: # 组件的属性值
region: cn-hangzhou
fc-base-test-1:
component: ${path(../..)}
props:
region: cn-shenzhen
service:
name: qianfeng-fc-base-service
name: fc-base-service
function:
name: test-function-1
service: qianfeng-fc-base-service
service: fc-base-service
filename: './code.zip'
handler: 'index.handler'
memorySize: 128
Expand All @@ -45,7 +43,7 @@ services:
triggers:
- name: httpTrigger
function: test-function-1
service: qianfeng-fc-base-service
service: fc-base-service
type: http
config:
authType: anonymous
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@
"@alicloud/fc2": "^2.2.2",
"@alicloud/pop-core": "^1.7.10",
"@serverless-devs/core": "^0.0.x",
"inquirer": "^8.1.1",
"lodash": "^4.17.20"
},
"devDependencies": {
"@types/eslint": "^7.2.6",
"@types/jest": "^26.0.10",
"@types/lodash": "^4.14.171",
"@types/node": "14",
"f2elint": "^0.4.4",
"jest": "^26.4.0",
Expand Down
2 changes: 1 addition & 1 deletion src/resources/index.ts → src/command/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import _ from 'lodash';
// import path from 'path';
import Client from '../utils/client';
import { transfromTriggerConfig } from '../utils/utils';
import { IProperties } from '../interface/inputs';
import { IProperties } from '../common/entity';
import { isCode, isCustomContainerConfig } from '../interface/function';
import { makeDestination } from './function-async-config';

Expand Down
212 changes: 212 additions & 0 deletions src/command/remove.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
/* eslint-disable no-await-in-loop */
/* eslint-disable require-atomic-updates */
import { ILogger, HLogger, spinner, getState, setState } from '@serverless-devs/core';
import _ from 'lodash';
import Client from '../utils/client';
import { IProperties } from '../common/entity';
import { promptForConfirmOrDetails } from '../utils/utils';

const errorCode = ['ServiceNotFound', 'FunctionNotFound', 'TriggerNotFound'];
interface RemoveInputsProps {
force?: boolean;
silent?: boolean;
triggerName?: string;
}

export default class Component {
@HLogger('FC-BASE-SDK') logger: ILogger;
fcClient: any;
region: any;

constructor(region) {
this.region = region;
this.fcClient = Client.fcClient();
}

async trigger(props: IProperties, { force, silent, triggerName }: RemoveInputsProps, command?: string) {
const { service, function: functionConfig, triggers = [] } = props;
const serviceName = service?.name || functionConfig?.service;
const functionName = functionConfig?.name;

if (_.isEmpty(serviceName)) {
throw new Error('Delete trigger, service name cannot be empty');
}
if (_.isEmpty(functionName)) {
throw new Error('Delete trigger, function name cannot be empty');
}

if (triggerName) {
return await this.deleteTrigger(serviceName, functionName, triggerName);
}

if (silent || command === 'trigger') {
for (const { name } of triggers) {
await this.deleteTrigger(serviceName, functionName, name);
}
return;
}

let deleteTriggerList: string[];
const yamlTriggerNames = triggers.map(({ name }) => name);
const listTrigger = await this.getListData(`/services/${serviceName}/functions/${functionName}/triggers`, 'triggers');
const listTriggerNames = listTrigger.map((item) => item.triggerName);

if (force) {
deleteTriggerList = Array.from(yamlTriggerNames.concat(listTriggerNames));
} else {
const prompt = `${serviceName}/${functionName} has triggers outside the configuration, delete all?`;
deleteTriggerList = await this.getDeleteList(yamlTriggerNames, listTriggerNames, prompt);
}

this.logger.debug(`delete trigger list: ${JSON.stringify(deleteTriggerList)}`);
for (const name of deleteTriggerList) {
await this.deleteTrigger(serviceName, functionName, name);
}
}

async function(props: IProperties, { force, silent }: RemoveInputsProps, command?: string) {
const serviceName = props.service?.name || props.function?.service;
const functionName = props.function?.name || '';

if (_.isEmpty(serviceName)) {
throw new Error('Delete function, service name cannot be empty');
}
if (silent || command === 'function') {
if (_.isEmpty(functionName)) {
throw new Error('Delete function, function name cannot be empty');
}
await this.trigger(props, { force, silent }, 'function');
return await this.deleteFunction(serviceName, functionName);
}

const listFunctions = await this.getListData(`/services/${serviceName}/functions`, 'functions');
const listFunctionNames = listFunctions.map((item) => item.functionName);

let deleteFunctionList: string[];
if (force) {
deleteFunctionList = listFunctionNames;
} else {
const prompt = `${serviceName} has function outside the configuration, delete all?`;
const yamlNames = _.isEmpty(functionName) ? [] : [functionName];
deleteFunctionList = await this.getDeleteList(yamlNames, listFunctionNames, prompt);
}

this.logger.debug(`delete function list: ${JSON.stringify(deleteFunctionList)}`);
for (const name of deleteFunctionList) {
const cloneProps = _.cloneDeep(props);
if (_.isEmpty(cloneProps.function)) {
cloneProps.function = {
name,
handler: '',
runtime: '',
};
} else {
cloneProps.function.name = name;
}

await this.trigger(cloneProps, { force, silent }, 'function');
await this.deleteFunction(serviceName, name);
}
}

async service(props: IProperties, { force, silent }: RemoveInputsProps) {
const serviceName = props.service?.name;
if (_.isEmpty(serviceName)) {
throw new Error('Delete service, service name cannot be empty');
}

await this.function(props, { force, silent }, 'service');
await this.deleteService(serviceName);
}

private async deleteService(serviceName) {
const vm = spinner(`Delete service ${serviceName}...`);
try {
await this.fcClient.deleteService(serviceName);
vm.succeed(`Delete service ${serviceName} success.`);

const stateId = `${this.fcClient.accountid}-${this.region}-${serviceName}`;
await this.unsetState(stateId);
} catch (ex) {
if (!errorCode.includes(ex.code)) {
vm.fail();
throw ex;
}
vm.warn(`[${ex.code}], ${ex.message}`);
}
}

private async deleteFunction(serviceName, functionName) {
const vm = spinner(`Delete function ${serviceName}/${functionName}...`);
try {
await this.fcClient.deleteFunction(serviceName, functionName);
vm.succeed(`Delete function ${serviceName}/${functionName} success.`);

const stateId = `${this.fcClient.accountid}-${this.region}-${serviceName}-${functionName}`;
await this.unsetState(stateId);
} catch (ex) {
if (!errorCode.includes(ex.code)) {
vm.fail();
throw ex;
}
vm.warn(`[${ex.code}], ${ex.message}`);
}
}

private async deleteTrigger(serviceName, functionName, triggerName) {
const vm = spinner(`Delete trigger ${serviceName}/${functionName}/${triggerName}...`);
try {
await this.fcClient.deleteTrigger(serviceName, functionName, triggerName);
vm.succeed(`Delete trigger ${serviceName}/${functionName}/${triggerName} success.`);

const stateId = `${this.fcClient.accountid}-${this.region}-${serviceName}-${functionName}-${triggerName}`;
await this.unsetState(stateId);
} catch (ex) {
if (!errorCode.includes(ex.code)) {
vm.fail();
throw ex;
}
vm.warn(`[${ex.code}], ${ex.message}`);
}
}

private async unsetState(stateId: string): Promise<void> {
const state: any = await getState(stateId);
if (!_.isEmpty(state)) {
await setState(stateId, {});
}
}

private async getDeleteList(yamlArr: string[], arr: string[], prompt: string) {
for (const name of arr) {
if (!yamlArr.includes(name)) {
if (await promptForConfirmOrDetails(prompt)) {
return Array.from(yamlArr.concat(arr));
} else {
return yamlArr;
}
}
}
return yamlArr;
}

private async getListData(path, dataKeyword, options: { [key: string]: any } = {}, headers?) {
try {
let data = [];
do {
const res = await this.fcClient.get(path, options, headers);
const keywordData = res.data?.[dataKeyword];
options.nextToken = res.data?.nextToken;

if (!_.isEmpty(keywordData)) {
data = data.concat(keywordData);
}
} while (options.nextToken);

return data;
} catch (ex) {
this.logger.warn(`get ${path} error: ${ex.code}\n${ex.message}`);
return [];
}
}
}
45 changes: 27 additions & 18 deletions src/interface/inputs.ts → src/common/entity.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,34 @@
import { IServiceConfig } from './service';
import { IFunctionConfig } from './function';
import { ITriggerConfig } from './trigger';
import { IServiceConfig } from '../interface/service';
import { IFunctionConfig } from '../interface/function';
import { ITriggerConfig } from '../interface/trigger';

export interface IInputProps {
export interface ICredentials {
AccountID?: string;
AccessKeyID?: string;
AccessKeySecret?: string;
SecurityToken?: string;
SecretID?: string;
SecretKey?: string;
SecretAccessKey?: string;
KeyVaultName?: string;
TenantID?: string;
ClientID?: string;
ClientSecret?: string;
PrivateKeyData?: string;
}

export interface InputProps {
props: any; // 用户自定义输入
credentials: ICredentials; // 用户秘钥
appName: string; //
appName: string;
project: {
component: string; // 组件名(支持本地绝对路径)
access: string; // 访问秘钥名
projectName: string; // 项目名
};
command: string; // 执行指令
args: string; // 命令行 扩展参数
argsObj: any;
path: {
configPath: string; // 配置路径
};
Expand All @@ -25,17 +41,10 @@ export interface IProperties {
triggers?: ITriggerConfig[];
}

export interface ICredentials {
AccountID?: string;
AccessKeyID?: string;
AccessKeySecret?: string;
SecretID?: string;
SecretKey?: string;
SecretAccessKey?: string;
KeyVaultName?: string;
TenantID?: string;
ClientID?: string;
ClientSecret?: string;
PrivateKeyData?: string;
SecurityToken?: string;
export interface ComponentInputs {
project?: Boolean;
credentials: any;
props?: any;
args?: string;
argsObj: any;
}
Loading

0 comments on commit 1f36481

Please sign in to comment.