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

Commit

Permalink
feat: update 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 6e52c51
Show file tree
Hide file tree
Showing 10 changed files with 313 additions and 135 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
70 changes: 1 addition & 69 deletions src/resources/index.ts → src/command/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,81 +5,13 @@ 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';

const errorCode = ['ServiceNotFound', 'FunctionNotFound', 'TriggerNotFound'];

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

static async remove(props: IProperties, { nonOptionsArg, name }) {
const { service, function: functionConfig, triggers } = props;
const serviceName = service.name;
const functionName = functionConfig?.name;
const fcClient = Client.fcClient();

const deleteService = nonOptionsArg === 'service';
const deleteFunction = nonOptionsArg === 'function' || deleteService;

if (triggers) {
let isContinue = false;
const onlyDeleteOneTrigger = name && nonOptionsArg === 'trigger';
for (const { name: triggerName } of triggers) {
if (onlyDeleteOneTrigger) {
if (triggerName !== name) {
continue;
}
isContinue = true;
}
const vm = spinner(`Delete trigger ${serviceName}/${functionName}/${triggerName}...`);
try {
await fcClient.deleteTrigger(serviceName, functionName, triggerName);
vm.succeed(`Delete trigger ${serviceName}/${functionName}/${triggerName} success.`);
} catch (ex) {
if (errorCode.includes(ex.code)) {
vm.warn(`[${ex.code}], ${ex.message}`);
continue;
}
vm.fail();
throw ex;
}
if (isContinue) {
return;
}
}
}

if (functionName && deleteFunction) {
const vm = spinner(`Delete function ${serviceName}/${functionName}...`);
try {
await fcClient.deleteFunction(serviceName, functionName);
vm.succeed(`Delete function ${serviceName}/${functionName} success.`);
} catch (ex) {
if (!errorCode.includes(ex.code)) {
vm.fail();
throw ex;
}
vm.warn(`[${ex.code}], ${ex.message}`);
}
}

if (deleteService) {
const vm = spinner(`Delete service ${serviceName}...`);
try {
await fcClient.deleteService(serviceName);
vm.succeed(`Delete service ${serviceName} success.`);
} catch (ex) {
if (!errorCode.includes(ex.code)) {
vm.fail();
throw ex;
}
vm.warn(`[${ex.code}], ${ex.message}`);
}
}
}

static async deploy(props: IProperties): Promise<any> {
const { region, service, function: functionConfig, triggers } = props;
const serviceName = service.name;
Expand Down
221 changes: 221 additions & 0 deletions src/command/remove.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
/* 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;
removeNameList: 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.`);

this.removeNameList.service = serviceName;

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.`);

this.removeNameList.functions || (this.removeNameList.functions = []);
this.removeNameList.functions.push({ serviceName, functionName });

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.`);

this.removeNameList.triggers || (this.removeNameList.triggers = []);
this.removeNameList.triggers.push({ serviceName, functionName, triggerName });

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 [];
}
}
}
Loading

0 comments on commit 6e52c51

Please sign in to comment.