8000 enhance deploy framework support by tanhe123 · Pull Request #841 · alibaba/funcraft · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

enhance deploy framework support #841

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

Merged
merged 8 commits into from
Apr 1, 2020
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
4 changes: 3 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,9 @@
"@typescript-eslint/camelcase": "off",
"@typescript-eslint/no-empty-function": "off",
"@typescript-eslint/no-use-before-define": "off",
"@typescript-eslint/no-this-alias": "off"
"@typescript-eslint/no-this-alias": "off",
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/no-unused-vars": "off"
},
"env": {
"es6": true,
Expand Down
6 changes: 5 additions & 1 deletion src/lib/definition.js
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,10 @@ function isNasAutoConfig(nasConfig) {
return false;
}

function isLogConfigAuto(logConfig) {
return logConfig === 'Auto';
}

function getUserIdAndGroupId(nasConfig) {
if (_.isEmpty(nasConfig)) { return {}; }

Expand Down Expand Up @@ -483,5 +487,5 @@ module.exports = {
findServiceByCertainServiceAndFunctionName, deleteUnmatchFunctionsUnderServiceRes,
isNasAutoConfig, isVpcAutoConfig, parseFunctionPath, iterateFunctions, parseDomainRoutePath,
onlyOneNASExists, findServiceByServiceName, findFunctionByServiceAndFunctionName, getUserIdAndGroupId,
SERVICE_RESOURCE, FUNCTION_RESOURCE, FLOW_RESOURCE, validateNasAndVpcConfig
SERVICE_RESOURCE, FUNCTION_RESOURCE, FLOW_RESOURCE, validateNasAndVpcConfig, isLogConfigAuto
};
19 changes: 8 additions & 11 deletions src/lib/deploy/deploy-by-tpl.js
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,12 @@ async function generateServiceRole({ serviceName, vpcConfig, nasConfig,
}
} else if (logConfig.LogStore || logConfig.Project) {
throw new Error('LogStore and Project must both exist');
} else if (definition.isLogConfigAuto(logConfig)) {
if (!roleArn) {
console.log('\tattaching police \'AliyunLogFullAccess\' to role: ' + roleName);
await attachPolicyToRole('AliyunLogFullAccess', roleName);
console.log(green('\tattached police \'AliyunLogFullAccess\' to role: ' + roleName));
}
}

return ((role || {}).Role || {}).Arn || roleArn || '';
Expand Down Expand Up @@ -367,21 +373,13 @@ async function processTemporaryDomain(resources, { serviceName, functionName })
const tokenRs = await sendHttpRequest('POST', TMP_DOMAIN_URL, { accountID: accountId, region });
const token = tokenRs.token;

const { serviceRes, functionRes } = definition.findFunctionByServiceAndFunctionName(resources, serviceName, functionName);
const { functionRes } = definition.findFunctionByServiceAndFunctionName(resources, serviceName, functionName);

if (_.isEmpty(functionRes)) {
throw new Error(`could not found service/function:${serviceName}/${functionName}`);
}

const properties = (serviceRes.Properties || {});

const role = await generateServiceRole({ serviceName, roleArn: properties.Role, policies: properties.Policies,
vpcConfig: properties.VpcConfig,
nasConfig: properties.NasConfig,
logConfig: properties.LogConfig || {}
});

const { tmpServiceName, tmpFunctionName, tmpTriggerName } = await makeFcUtilsFunctionTmpDomainToken(token, role);
const { tmpServiceName, tmpFunctionName, tmpTriggerName } = await makeFcUtilsFunctionTmpDomainToken(token);

const domainRs = await sendHttpRequest('POST', TMP_DOMAIN_URL, { accountID: accountId, region, token });
const domainName = domainRs.domain;
Expand All @@ -393,7 +391,6 @@ async function processTemporaryDomain(resources, { serviceName, functionName })
const currentTimestamp = Math.round(new Date().getTime() / 1000);

if (expiredTime > currentTimestamp) {

console.log(`The assigned temporary domain is ${yellow(domainName)},expired at ${yellow(date.format(expiredTimeObj, 'YYYY-MM-DD HH:mm:ss'))}, limited by ${yellow(timesLimit)} per day.`);
} else {
console.log(`The temporary domain ${yellow(domainName)} of previous depoyment is expried.`);
Expand Down
15 changes: 15 additions & 0 deletions src/lib/deploy/deploy-diffs.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,28 @@ const DEFAULT_VALUES = {
}
}

return false;
},
LogConfig: ({ action, newPropsValue, oldPropsValue }) => {
if (action === 'Modify' && newPropsValue === 'Auto') {
const project = oldPropsValue.Project;
const logstore = oldPropsValue.Logstore;

if (project && logstore) {
if (project.startsWith('aliyun-fc-') && logstore === 'function-log') {
return true;
}
}
}

return false;
}
},
'Aliyun::Serverless::Function': {
MemorySize: 128,
Timeout: 3,
InitializationTimeout: 3,
InstanceConcurrency: 1,
CodeUri: () => {
return false;
},
Expand Down
68 changes: 47 additions & 21 deletions src/lib/fc.js
Original file line number Diff line number Diff line change
Expand Up @@ -556,7 +556,7 @@ async function detectJarfilePathFromBootstrap(bootstrapContent) {
if (matchedJar) {
jarfilePath = matchedJar[0];
} else {
throw new Error('not supported your java project');
throw new Error('not supported your java project');
}
}

Expand Down Expand Up @@ -760,11 +760,11 @@ Fun has detected that there is a model folder. It is recommend to synchronize yo
You can add the following configuration to ` + yellow(`'nasMapping.${serviceName}'`) + ` in ` + yellow(`${nasYmlPath}
`)

+ yellow(`
+ yellow(`
- localNasDir: ${absModelPath}
remoteNasDir: ${remoteNasDir}
`)
+ `
+ `
After adding, fun is going to automatically synchronize the ` + yellow(`local`) + ` directory ${absModelPath} to ` + yellow(`remote`) + ` ${remoteNasDir}.
If these files ` + yellow('under') + ` model directory are used on your function code, you need to ${remoteNasDir} update these files path manully.
`);
Expand Down Expand Up @@ -886,7 +886,8 @@ async function nasAutoConfigurationIfNecessary({ stage, tplPath, runtime, codeUr
}
await backupTemplateFile(tplPath); // backup tpl

tplChanged = await processNasAutoConfiguration({ tpl, tplPath, runtime, codeUri, stage,
tplChanged = await processNasAutoConfiguration({
tpl, tplPath, runtime, codeUri, stage,
serviceName: nasServiceName,
functionName: nasFunctionName
});
Expand All @@ -908,7 +909,8 @@ async function nasAutoConfigurationIfNecessary({ stage, tplPath, runtime, codeUr
< AE8F /td> if (yes) {
await backupTemplateFile(tplPath);

tplChanged = await processNasAutoConfiguration({ tpl, tplPath, runtime, codeUri, stage,
tplChanged = await processNasAutoConfiguration({
tpl, tplPath, runtime, codeUri, stage,
serviceName: nasServiceName,
functionName: nasFunctionName
});
Expand All @@ -920,7 +922,8 @@ async function nasAutoConfigurationIfNecessary({ stage, tplPath, runtime, codeUr
const convertedNasConfig = replaceNasConfig(nasConfig, answer.mountDir);
await backupTemplateFile(tplPath);

tplChanged = await processNasAutoConfiguration({ tpl, tplPath, runtime, codeUri, stage,
tplChanged = await processNasAutoConfiguration({
tpl, tplPath, runtime, codeUri, stage,
convertedNasConfig,
serviceName: nasServiceName,
functionName: nasFunctionName
Expand All @@ -935,7 +938,8 @@ async function nasAutoConfigurationIfNecessary({ stage, tplPath, runtime, codeUr
// write back to yml
const updatedTpl = await updateNasAutoConfigure(tplPath, tpl, nasServiceName);

tplChanged = await processNasAutoConfiguration({ tpl: updatedTpl, tplPath, runtime, codeUri, stage,
tplChanged = await processNasAutoConfiguration({
tpl: updatedTpl, tplPath, runtime, codeUri, stage,
serviceName: nasServiceName,
functionName: nasFunctionName
});
Expand All @@ -948,7 +952,8 @@ async function nasAutoConfigurationIfNecessary({ stage, tplPath, runtime, codeUr
const nasAndVpcConfig = generateNasAndVpcConfig(mountTarget, securityGroupId, nasServiceName);
const updatedTpl = await updateNasAndVpc(tplPath, tpl, nasServiceName, nasAndVpcConfig);

tplChanged = await processNasAutoConfiguration({ tpl: updatedTpl, tplPath, runtime, codeUri, stage,
tplChanged = await processNasAutoConfiguration({
tpl: updatedTpl, tplPath, runtime, codeUri, stage,
serviceName: nasServiceName,
functionName: nasFunctionName
});
Expand Down Expand Up @@ -1066,7 +1071,8 @@ async function makeFunction(baseDir, {
const fontsConfEnv = await generateFontsConfAndEnv(baseDir, codeUri);
if (!_.isEmpty(fontsConfEnv)) {

updateEnvironmentsInTpl({ serviceName, functionName, tplPath,
updateEnvironmentsInTpl({
serviceName, functionName, tplPath,
displayLog: false,
tpl: await getTpl(tplPath),
envs: DEFAULT_FONTS_CONFIG_ENV
Expand Down Expand Up @@ -1159,13 +1165,13 @@ function generateSlsProjectName(accountId, region) {
async function generateDefaultLogConfig() {
const profile = await getProfile();
return {
Project: generateSlsProjectName(profile.accountId, profile.defaultRegion),
Logstore: `function-log`
project: generateSlsProjectName(profile.accountId, profile.defaultRegion),
logstore: `function-log`
};
}

async function transformLogConfig(logConfig) {
if (logConfig === 'Auto') {
if (definition.isLogConfigAuto(logConfig)) {
const defaultLogConfig = await generateDefaultLogConfig();

console.log(yellow(`\tusing 'LogConfig: Auto'. Fun will generate default sls project.`));
Expand All @@ -1183,6 +1189,32 @@ async function transformLogConfig(logConfig) {
};
}

// make sure sls project and logstore is created
async function retryUntilSlsCreated(serviceName, options, create, fcClient) {
let slsRetry = 0;
let service;
do {
try {
if (create) {
debug('create service %s, options is %j', serviceName, options);
service = await fcClient.createService(serviceName, options);
} else {
debug('update service %s, options is %j', serviceName, options);
service = await fcClient.updateService(serviceName, options);
}
return service;
} catch (e) {
if (e.code === 'InvalidArgument'
&& _.includes(e.message, 'not exist')
&& (_.includes(e.message, 'logstore') || _.includes(e.message, 'project'))) {
slsRetry++;
await sleep(3000);
} else { throw e; }
}

} while (slsRetry <= 12);
}

async function makeService({
serviceName,
role,
Expand Down Expand Up @@ -1262,14 +1294,9 @@ async function makeService({
});

await promiseRetry(async (retry, times) => {

try {
if (!service) {
debug('create service %s, options is %j', serviceName, options);
service = await fc.createService(serviceName, options);
} else {
debug('update service %s, options is %j', serviceName, options);
service = await fc.updateService(serviceName, options);
}
service = await retryUntilSlsCreated(serviceName, options, !service, fc);
} catch (ex) {
if (ex.code === 'AccessDenied') {
throw ex;
Expand Down Expand Up @@ -1472,13 +1499,12 @@ permission, more information please refer to https://github.com/alibaba/funcraft
}
}

async function makeFcUtilsFunctionTmpDomainToken(token, role) {
async function makeFcUtilsFunctionTmpDomainToken(token) {

const tmpServiceName = 'fc-domain-challenge';

await makeService({
serviceName: tmpServiceName,
role,
description: 'generated by Funcraft for authentication',
vpcConfig: {},
nasConfig: {}
Expand Down
93 changes: 93 additions & 0 deletions src/lib/frameworks/common/file.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
'use strcit';

const debug = require('debug')('fun:deploy');
const fs = require('fs-extra');
const _ = require('lodash');
const { yellow, red, green } = require('colors');
const { promptForConfirmContinue } = require('../../init/prompt');

async function findMainFile(codeDir, fileSuffix, mainRegex) {
const regex = new RegExp(mainRegex, 'm');

const files = await fs.readdir(codeDir);
for (const file of files) {
if (!_.endsWith(file, fileSuffix)) { continue; }
const contents = await fs.readFile(file, 'utf8');
if (regex.test(contents)) {
debug('mainFile is ', file);

return file;
}
}

return null;
}

async function detectAndReplaceMainFileAddr(mainFile, addrProcessores) {
const mainFileContents = await fs.readFile(mainFile, 'utf8');

for (const addrProcessor of addrProcessores) {
const addrRegex = addrProcessor.regex;
const replacer = addrProcessor.replacer;

if (addrRegex.test(mainFileContents)) {
console.log(`${yellow('Fun detected')} your application doesn't listen on '${yellow('0.0.0.0:9000')}' in ${yellow(mainFile)}`);
console.log(`Fun will replace your addr to '${yellow('0.0.0.0:9000')}', and also backup your origin file ${yellow(mainFile)} to ${yellow(mainFile + '.bak')}`);

if (!await promptForConfirmContinue(yellow(`Are your sure?`))) {
console.warn(red(`Fun will not modify your application listen addr, but if you want deploy to fc, you must listen on '0.0.0.0:9000'`));
return;
}

const replacedContent = mainFileContents.replace(addrRegex, (match, p1) => {
if (_.isFunction(replacer)) {
return replacer(match, p1);
}
return replacer;
});

await fs.copyFile(mainFile, mainFile + '.bak');
await fs.writeFile(mainFile, replacedContent);

return;
}
}
}

async function detectAndReplaceAddr({
codeDir,
mainFileSuffix,
mainFileRegex,
addrProcessores
}) {
const mainFile = await findMainFile(codeDir, mainFileSuffix, mainFileRegex);
if (!mainFile) {
return { mainFile: null };
}

await detectAndReplaceMainFileAddr(mainFile, addrProcessores);

return { mainFile };
}

async function generateFile(p, backup, mode, content) {
console.log(green('Generating ' + p + '...'));

if (await fs.pathExists(p)) {
if (_.isNil(backup) || backup) {
console.warn(red(`File ${p} already exists, Fun will rename to ${p}.bak`));

await fs.copyFile(p, `${p}.bak`, {
overwrite: true
});
}
}

await fs.writeFile(p, content, {
mode
});
}

module.exports = {
detectAndReplaceAddr, generateFile
};
Loading
0