Skip to content
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

Add Ballerina language support #3584

Merged
merged 12 commits into from
Jul 14, 2023
25 changes: 24 additions & 1 deletion .azure-pipelines/common/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,30 @@ steps:
displayName: 'Start X Virtual Frame Buffer'
condition: eq(variables['Agent.OS'], 'Linux')

# Tempoary workaround for /~https://github.com/ballerina-platform/ballerina-distribution/issues/4537
- script: |
curl -o ballerina.deb 'https://dist.ballerina.io/downloads/2201.6.0/ballerina-2201.6.0-swan-lake-linux-x64.deb'
sudo dpkg -i ballerina.deb
rm -f ballerina.deb
displayName: Install Ballerina(Linux)
condition: eq(variables['Agent.OS'], 'Linux')

- script: |
curl -o ballerina.pkg 'https://dist.ballerina.io/downloads/2201.6.0/ballerina-2201.6.0-swan-lake-macos-x64.pkg'
sudo installer -pkg ballerina.pkg -target /
rm -f ballerina.pkg
echo '##vso[task.prependpath]/Library/Ballerina/bin'
displayName: Install Ballerina(MacOS)
condition: eq(variables['Agent.OS'], 'Darwin')

- script: |
curl -o ballerina.msi https://dist.ballerina.io/downloads/2201.6.0/ballerina-2201.6.0-swan-lake-windows-x64.msi
msiexec /i ballerina.msi /quiet /qr /L*V "C:\Temp\msilog.log"
del ballerina.msi
echo "##vso[task.setvariable variable=PATH]C:\Program Files\Ballerina\bin;$(PATH)"
displayName: Install Ballerina(Windows)
condition: eq(variables['Agent.OS'], 'Windows_NT')

- task: UsePythonVersion@0
displayName: 'Use Python 3.7.x'
inputs:
Expand Down Expand Up @@ -46,4 +70,3 @@ steps:
testResultsFiles: '*-results.xml'
testRunTitle: '$(Agent.OS)'
condition: succeededOrFailed()

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -859,6 +859,7 @@
"PowerShell",
"Python",
"TypeScript",
"Ballerina",
"Custom"
],
"description": "%azureFunctions.projectLanguage%",
Expand All @@ -872,6 +873,7 @@
"",
"",
"",
"",
""
]
},
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"en":{"temp_category_api":"API & Webhooks","temp_category_core":"Core","temp_category_dataProcessing":"Data Processing","HttpTrigger_description":"A function that will be run whenever it receives an HTTP request, responding based on data in the body or query string","BlobTrigger_description":"A function that will be run whenever a blob is added to a specified container","CosmosDBTrigger_description":"A function that will be run whenever documents change in a document collection","QueueTrigger_description":"A function that will be run whenever a message is added to a specified Azure Storage queue","TimerTrigger_description":"A function that will be run on a specified schedule","variables_storageConnStringLabel":"Storage account connection","variables_appSettingsHelp":"The name of the app setting containing your storage account connection string.","variables_selectConnection":"Click select to choose a connection","variables_parameterName":"The parameter name must be an alphanumeric string of any number of characters and cannot start with a number.","timerTrigger_displayName":"Timer","timerTrigger_name_help":"The name used to identify this trigger in your code","timerTrigger_name_label":"Timestamp parameter name","timerTrigger_schedule_help":"Enter a cron expression of the format '{second} {minute} {hour} {day} {month} {day of week}' to specify the schedule.","timerTrigger_schedule_label":"Schedule","queueTrigger_displayName":"Azure Queue Storage","queueTrigger_queueName_help":"Name of the queue from which the message will be read","queueTrigger_name_label":"Message parameter name","queueTrigger_queueName_errorText":"Queue name must start and end with a letter or number, and it can contain only lowercase letters, numbers, and the hyphen. The name must be 3 to 63 characters.","queueTrigger_name_help":"The name used to identify this trigger in your code","queueTrigger_queueName_label":"Queue name","blobTrigger_displayName":"Azure Blob Storage","blobTrigger_name_label":"Blob parameter name","blobTrigger_name_help":"The name used to identify this trigger in your code","blobTrigger_path_label":"Path","blobTrigger_path_help":"The path within your storage account that the trigger will monitor.","blobTrigger_path_errorText":"Your blob path must be a container name of 3 to 63 characters, followed by a blob name of 1 to 1,024 characters. The blob name can be any combination of characters and can contain a maximum of 254 path segments, which are separated by the forward slash (/).","httpTrigger_displayName":"HTTP","httpTrigger_name_label":"Request parameter name","httpTrigger_name_help":"The name used to identify this trigger in your code","httpTrigger_authLevel_label":"Authorization level","httpTrigger_authLevel_help":"Authorization level controls whether the function requires an API key and which key to use; Function uses a function key; Admin uses your master key. The function and master keys are found in the 'keys' management panel on the portal, when your function is selected. For user-based authentication, go to Function App Settings.","httpTrigger_methods_label":"Selected HTTP methods","httpTrigger_methods_help":"These are the only methods to which this function will respond.","httpTrigger_methodRule_label":"Allowed HTTP methods","httpTrigger_route_label":"Route template","httpTrigger_route_help":"The route template setting allows you to change the URI that triggers this function. The values should be a relative path. Path segments may be treated as parameters by surrounding them with curly braces. For example: customer/{customerId}","httpTrigger_methodRule_help":"HttpTrigger can respond to any HTTP method. If you wish to restrict support to specific methods, choose the 'Selected methods' option.","httpTrigger_mode_label":"Mode","httpTrigger_mode_help":"The mode of the trigger. \"Standard\" means that the request will be standard HTTP with no additional semantics. \"Webhook\" means that the request will be processed according to a specified webhook type.","cosmosDB_trigger_displayName":"Azure Cosmos DB","cosmosDBIn_collectionName_help":"Name of the collection to be monitored.","cosmosDBIn_collectionName_label":"Collection name","cosmosDBIn_connection_help":"The name of the App Setting containing the connection string to the service that contains the collection to be monitored.","cosmosDBIn_connection_label":"Cosmos DB account connection","cosmosDBIn_databaseName_help":"Name of the Cosmos DB database that includes the collection to be monitored.","cosmosDBIn_databaseName_label":"Database name","cosmosDBIn_leaseCollectionName_help":"Name of the collection to store the leases.","cosmosDBIn_leaseCollectionName_label":"Collection name for leases","cosmosDBIn_leaseDatabaseName_help":"Name of the database that includes the collection to store the leases.","cosmosDBIn_leaseDatabaseName_label":"Database name for leases","cosmosDBIn_name_help":"The name used to identify this binding in your code","cosmosDBIn_name_label":"Document collection parameter name","cosmosDBIn_createIfNotExists_help":"Checks for existence and automatically creates the leases collection.","cosmosDBIn_createIfNotExists_label":"Create lease collection if it does not exist"}}
121 changes: 121 additions & 0 deletions resources/backupTemplates/ballerina/templates/templates.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
[
{
"id": "HttpTrigger-Ballerina",
"runtime": "2",
"files": {
"%functionName%.bal": "import ballerinax/azure.functions;\n\n// This function gets triggered by an HTTP call with the name query parameter and returns a processed HTTP output to the caller.\n@functions:HttpTrigger{\n authLevel: \"%authLevel%\"\n}\nlistener functions:HttpListener httpListener = new functions:HttpListener();\nservice /%functionName% on httpListener {\n resource function get .(string name) returns string {\n return \"Hello, \" + name + \"!\";\n }\n}\n"
},
"metadata": {
"defaultFunctionName": "httpTrigger",
"description": "$HttpTrigger_description",
"name": "HTTP trigger",
"language": "Ballerina",
"triggerType": "httpTrigger",
"category": [
"$temp_category_core",
"$temp_category_api"
],
"categoryStyle": "http",
"enabledInTryMode": true,
"userPrompt": [
"authLevel"
]
}
},
{
"id": "BlobTrigger-Ballerina",
"runtime": "2",
"files": {
"%functionName%.bal": "import ballerinax/azure.functions;\nimport ballerina/log;\n\n// The following Function will be invoked when a new blob added to the specified blob storage.\n@functions:BlobTrigger {\n path: \"%path%\",\n connection: \"%connection%\"\n}\nlistener functions:BlobListener blobListener = new functions:BlobListener();\n\nservice \"%functionName%\" on blobListener {\n remote function onUpdate(byte[] blobIn) {\n log:printInfo(\"Blob Store updated with file of \" + blobIn.length().toString() + \" bytes\");\n }\n}\n"
},
"metadata": {
"defaultFunctionName": "blobTrigger",
"description": "$BlobTrigger_description",
"name": "Blob trigger",
"language": "Ballerina",
"triggerType": "blobTrigger",
"category": [
"$temp_category_core",
"$temp_category_dataProcessing"
],
"categoryStyle": "blob",
"enabledInTryMode": true,
"userPrompt": [
"connection",
"path"
]
}
},
{
"id": "CosmosDBTrigger-Ballerina",
"runtime": "2",
"files": {
"%functionName%.bal": "import ballerina/log;\nimport ballerinax/azure.functions;\n\n// The following Function will be invoked when an entry is added to CosmosDB collection.\n@functions:CosmosDBTrigger {connectionStringSetting: \"%connectionStringSetting%\", databaseName: \"%databaseName%\", collectionName: \"%collectionName%\"}\nlistener functions:CosmosDBListener cosmosEp = new ();\n\ntype Users record {\n string id;\n string name;\n};\n\nservice \"%functionName%\" on cosmosEp {\n remote function onUpdate(Users[] users) {\n log:printInfo(users.toJsonString());\n }\n}\n"
},
"metadata": {
"defaultFunctionName": "cosmosTrigger",
"description": "$CosmosDBTrigger_description",
"name": "CosmosDB trigger",
"language": "Ballerina",
"triggerType": "cosmosDBTrigger",
"category": [
"$temp_category_core",
"$temp_category_dataProcessing"
],
"categoryStyle": "cosmosDB",
"enabledInTryMode": true,
"userPrompt": [
"connectionStringSetting",
"databaseName",
"collectionName"
]
}
},
{
"id": "QueueTrigger-Ballerina",
"runtime": "2",
"files": {
"%functionName%.bal": "import ballerina/log;\nimport ballerinax/azure.functions;\n\n\n// The following Function will be executed when a message is added to the queue storage.\n@functions:QueueTrigger {\n queueName: \"%queueName%\",\n connection: \"%connection%\"\n}\nlistener functions:QueueListener queueListener = new functions:QueueListener();\n\nservice \"%functionName%\" on queueListener {\n remote function onMessage(string message) {\n log:printInfo(\"Queue message received: \" + message);\n }\n}\n"
},
"metadata": {
"defaultFunctionName": "queueTrigger",
"description": "$QueueTrigger_description",
"name": "Queue trigger",
"language": "Ballerina",
"triggerType": "queueTrigger",
"category": [
"$temp_category_core",
"$temp_category_dataProcessing"
],
"categoryStyle": "queue",
"enabledInTryMode": true,
"userPrompt": [
"connection",
"queueName"
]
}
},
{
"id": "TimerTrigger-Ballerina",
"runtime": "2",
"files": {
"%functionName%.bal": "import ballerina/time;\nimport ballerina/log;\nimport ballerinax/azure.functions;\n\n// The following function will be invoked periodically according to the schedule given.\n@functions:TimerTrigger {schedule: \"%schedule%\"}\nlistener functions:TimerListener timerEp = new ();\n\nservice \"%functionName%\" on timerEp {\n remote function onTrigger(functions:TimerMetadata metadata) {\n log:printInfo(\"Function Executed at \" + time:utcToString(time:utcNow()));\n }\n}\n"
},
"metadata": {
"defaultFunctionName": "timerTrigger",
"description": "$TimerTrigger_description",
"name": "Timer trigger",
"language": "Ballerina",
"triggerType": "timerTrigger",
"category": [
"$temp_category_core",
"$temp_category_dataProcessing"
],
"categoryStyle": "timer",
"enabledInTryMode": true,
"userPrompt": [
"schedule"
]
}
}
]
1 change: 1 addition & 0 deletions resources/backupTemplates/ballerina/version.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1.0.0
2 changes: 2 additions & 0 deletions src/LocalResourceProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ async function getCompiledProjectInfo(context: IActionContext, projectPath: stri
} else {
return { compiledProjectPath: path.join(projectPath, getJavaDebugSubpath(functionAppName, buildTool)), isIsolated: false };
}
} else if (projectLanguage === ProjectLanguage.Ballerina) {
return { compiledProjectPath: path.join(projectPath, "target", "azure_functions"), isIsolated: false };
nturinski marked this conversation as resolved.
Show resolved Hide resolved
} else {
return undefined;
}
Expand Down
8 changes: 8 additions & 0 deletions src/commands/createFunction/FunctionSubWizard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { IFunctionTemplate } from '../../templates/IFunctionTemplate';
import { isNodeV4Plus, isPythonV2Plus } from '../../utils/programmingModelUtils';
import { addBindingSettingSteps } from '../addBinding/settingSteps/addBindingSettingSteps';
import { JavaPackageNameStep } from '../createNewProject/javaSteps/JavaPackageNameStep';
import { BallerinaFunctionCreateStep } from './ballerinaSteps/BallerinaFunctionCreateStep';
import { BallerinaFunctionNameStep } from './ballerinaSteps/BallerinaFunctionNameStep';
import { DotnetFunctionCreateStep } from './dotnetSteps/DotnetFunctionCreateStep';
import { DotnetFunctionNameStep } from './dotnetSteps/DotnetFunctionNameStep';
import { DotnetNamespaceStep } from './dotnetSteps/DotnetNamespaceStep';
Expand Down Expand Up @@ -45,6 +47,9 @@ export class FunctionSubWizard {
case ProjectLanguage.Java:
promptSteps.push(new JavaPackageNameStep(), new JavaFunctionNameStep());
break;
case ProjectLanguage.Ballerina:
promptSteps.push(new BallerinaFunctionNameStep());
break;
case ProjectLanguage.CSharp:
case ProjectLanguage.FSharp:
promptSteps.push(new DotnetFunctionNameStep(), new DotnetNamespaceStep());
Expand Down Expand Up @@ -81,6 +86,9 @@ export class FunctionSubWizard {
case ProjectLanguage.TypeScript:
executeSteps.push(new TypeScriptFunctionCreateStep());
break;
case ProjectLanguage.Ballerina:
executeSteps.push(new BallerinaFunctionCreateStep());
break;
default:
if (isV2PythonModel) {
executeSteps.push(new PythonFunctionCreateStep());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { AzExtFsExtra, nonNullProp } from '@microsoft/vscode-azext-utils';
import * as path from 'path';
import { FunctionCreateStepBase } from '../FunctionCreateStepBase';
import { IBallerinaFunctionTemplate, IBallerinaFunctionWizardContext } from './IBallerinaFunctionWizardContext';

export class BallerinaFunctionCreateStep extends FunctionCreateStepBase<IBallerinaFunctionWizardContext> {
public async executeCore(context: IBallerinaFunctionWizardContext): Promise<string> {
const functionPath = context.projectPath;
await AzExtFsExtra.ensureDir(functionPath);

const functionName = nonNullProp(context, 'functionName');
const fileName = `${functionName}.bal`;

const template: IBallerinaFunctionTemplate = nonNullProp(context, 'functionTemplate');
await Promise.all(Object.keys(template.templateFiles).map(async f => {
let contents = template.templateFiles[f];
contents = contents.replace(/%functionName%/g, functionName);

for (const setting of template.userPromptedSettings) {
// the setting name keys are lowercased
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
contents = contents.replace(new RegExp(`%${setting.name}%`, 'g'), context[setting.name.toLowerCase()]);
}

await AzExtFsExtra.writeFile(path.join(functionPath, fileName), contents);
}));

return path.join(functionPath, fileName);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { AzExtFsExtra } from '@microsoft/vscode-azext-utils';
import { localize } from "../../../localize";
import { IFunctionTemplate } from '../../../templates/IFunctionTemplate';
import { nonNullProp } from '../../../utils/nonNull';
import { getBallerinaFunctionFilePath, getBallerinaPackagePath, IBallerinaProjectWizardContext } from '../../createNewProject/ballerinaSteps/IBallerinaProjectWizardContext';
import { FunctionNameStepBase } from '../FunctionNameStepBase';
import { IFunctionWizardContext } from '../IFunctionWizardContext';

export class BallerinaFunctionNameStep extends FunctionNameStepBase<IFunctionWizardContext & IBallerinaProjectWizardContext> {
protected async getUniqueFunctionName(context: IFunctionWizardContext & IBallerinaProjectWizardContext): Promise<string | undefined> {
const template: IFunctionTemplate = nonNullProp(context, 'functionTemplate');
return await this.getUniqueFsPath(getBallerinaPackagePath(context.projectPath), template.defaultFunctionName, '.bal');
}

protected async validateFunctionNameCore(context: IFunctionWizardContext & IBallerinaProjectWizardContext, name: string): Promise<string | undefined> {
if (await AzExtFsExtra.pathExists(getBallerinaFunctionFilePath(context.projectPath, name))) {
return localize('existingError', 'A function with name "{0}" already exists in package "{1}".', name);
} else {
return undefined;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { IFunctionTemplate } from '../../../templates/IFunctionTemplate';
import { IFunctionWizardContext } from '../IFunctionWizardContext';

export interface IBallerinaFunctionWizardContext extends IFunctionWizardContext {
functionTemplate?: IBallerinaFunctionTemplate;
}

export interface IBallerinaFunctionTemplate extends IFunctionTemplate {
templateFiles: { [filename: string]: string };
}
Loading