Skip to content

Commit 61f1675

Browse files
dzsquaredyorek
andauthored
ref PR #38 (#64)
* proposed improvement #38 * Update main.ts fixed null/empty check * moved check for required server address * added more connection string tests * improved code readability * updated readme * updated action.yml * updated readme * re-added support to server-name to retain backward compatibility * removed unused code? * improved readme * corrected typos * use server-name if specified * added debug message and comment * resolving test failure for connectionString.server Co-authored-by: Davide Mauri <damauri@microsoft.com>
1 parent c7fe1bd commit 61f1675

File tree

7 files changed

+78
-47
lines changed

7 files changed

+78
-47
lines changed

README.md

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ The definition of this GitHub Action is in [action.yml](https://github.com/Azure
1919

2020
If you *can* use the option [Allow Azure Services and resources to access this server](https://docs.microsoft.com/en-us/azure/azure-sql/database/firewall-configure#connections-from-inside-azure), you are all set and you don't need to to anything else to allow GitHub Action to connect to your Azure SQL database.
2121

22-
If you *cannot* use the aformentioned option, additional steps are needed.
22+
If you *cannot* use the aforementioned option, additional steps are needed.
2323

2424
- Authenticate using [Azure Login](https://github.com/Azure/login)
2525

@@ -35,30 +35,30 @@ Alternatively, if enough permissions are not granted on the service principal or
3535

3636
### Create SQL database and deploy using GitHub Actions
3737

38-
1. Follow the tutorial [Azure SQL Quickstart](https://docs.microsoft.com/en-in/azure/sql-database/sql-database-single-database-get-started?tabs=azure-portal)
39-
2. Copy the [SQL-on-Azure.yml template](https://github.com/Azure/actions-workflow-samples) and paste the contents in `.github/workflows/` in your project repository as `workflow.yml`.
40-
3. Change `server-name` to your Azure SQL Server name.
41-
4. Commit and push your project to GitHub repository, you should see a new GitHub Action initiated in **Actions** tab.
38+
1. Follow the tutorial [Azure SQL Quickstart](https://docs.microsoft.com/azure/sql-database/sql-database-single-database-get-started?tabs=azure-portal)
39+
1. Copy the [SQL-on-Azure.yml template](https://github.com/Azure/actions-workflow-samples) and paste the contents in `.github/workflows/` in your project repository as `workflow.yml`.
40+
1. Update the connection string with your values. Connection string format is: `Server=<server.database.windows.net>;User ID=<user>;Password=<password>;Initial Catalog=<database>`
41+
1. Commit and push your project to GitHub repository, you should see a new GitHub Action initiated in **Actions** tab.
4242

43-
### Configure GitHub Secrets
43+
### Configure GitHub Secrets
4444

4545
For using any sensitive data/secrets like Azure Service Principal or SQL Connection strings within an Action, add them as [secrets](https://help.github.com/en/github/automating-your-workflow-with-github-actions/virtual-environments-for-github-actions#creating-and-using-secrets-encrypted-variables) in the GitHub repository and then use them in the workflow.
4646

4747
Follow the steps to configure the secret:
4848

49-
* Define a new secret under your repository **Settings** > **Secrets** > **Add a new secret** menu
50-
* Paste the contents of the Secret (Example: Connection String) as Value
49+
- Define a new secret under your repository **Settings** > **Secrets** > **Add a new secret** menu
50+
- Paste the contents of the Secret (Example: Connection String) as Value
5151

52-
If you need to configure Azure Credentials to automatically manage firewall rules, you need to create a Service Principal, and store the related credentials into a GitHub Secrect so that it can be used by the Azure Login actions to authenticate and authorize any subsequent request.
52+
If you need to configure Azure Credentials to automatically manage firewall rules, you need to create a Service Principal, and store the related credentials into a GitHub Secret so that it can be used by the Azure Login actions to authenticate and authorize any subsequent request.
5353

54-
Paste the output of the below [az cli](https://docs.microsoft.com/en-us/cli/azure/?view=azure-cli-latest) command as the value of secret variable, for example `AZURE_CREDENTIALS`.
54+
Paste the output of the below [az cli](https://docs.microsoft.com/cli/azure/?view=azure-cli-latest) command as the value of secret variable, for example `AZURE_CREDENTIALS`.
5555

5656
```bash
5757
az ad sp create-for-rbac --name "mySQLServer" --role contributor \
5858
--scopes /subscriptions/{subscription-id}/resourceGroups/{resource-group} \
5959
--sdk-auth
6060

61-
# Replace {subscription-id}, {resource-group} and {server-name} with the subscription, resource group and name of the Azure SQL server
61+
# Replace {subscription-id}, {resource-group} with the subscription, resource group and name of the Azure SQL server
6262

6363
# The command should output a JSON object similar to this:
6464

@@ -70,7 +70,7 @@ az ad sp create-for-rbac --name "mySQLServer" --role contributor \
7070
// ...
7171
}
7272
```
73-
73+
7474
### Sample workflow to deploy to an Azure SQL database
7575

7676
```yaml
@@ -86,26 +86,27 @@ jobs:
8686
with:
8787
creds: ${{ secrets.AZURE_CREDENTIALS }}
8888
- uses: azure/sql-action@v1
89-
with:
89+
with:
9090
server-name: REPLACE_THIS_WITH_YOUR_SQL_SERVER_NAME
9191
connection-string: ${{ secrets.AZURE_SQL_CONNECTION_STRING }}
9292
dacpac-package: './Database.dacpac'
9393
```
9494
95-
**Note:**
95+
**Note:**
96+
97+
The above means you have to create secrets in GitHub which can be found within your repository within **Settings** and then **Secrets** and also be careful to check the connection string which you copy from Azure SQL as the connection string has this **Password={your_password}** and you will need to supply the correct password for your connection string.
9698
97-
The above means you have to create secrets in GitHub which can be found within your repository within **Settings** and then **Secrets** and also
98-
be careful to check the connection string which you copy from Azure SQL as the connection string has this **Password={your_password}** and you will need to supply
99-
the correct password for your connection string.
99+
The `server-name` is optional and is there only to provide backward compatibility. It is strongly recommended to put the server name in the connection string. The connection string uses this template: `Server=<servername>; User ID=<user_id>; Password=<password>; Initial Catalog=<database>`. In case the server name is put both in the `server-name` and in the `connection-string`, the server name used will be the one specified in the `server-name` YAML key.
100+
101+
### How to create a .dacpac file from your existing SQL Server Database
100102

101-
### How to create a dacpac file from your existing SQL Server Database
102-
103103
For the above action to work, you will need to create a file called `Database.dacpac` and place it into the root of your
104104
GitHub repository. The following link will show you how to go about creating a dacpac file but make sure the file is called `Database.dacpac`.
105105

106-
[Export a Data-tier application](https://docs.microsoft.com/en-us/sql/relational-databases/data-tier-applications/export-a-data-tier-application?view=sql-server-ver15)
106+
[Export a Data-tier application](https://docs.microsoft.com/sql/relational-databases/data-tier-applications/export-a-data-tier-application?view=sql-server-ver15)
107107

108108
Azure SQL Action for GitHub is supported for the Azure public cloud as well as Azure government clouds ('AzureUSGovernment' or 'AzureChinaCloud'). Before running this action, login to the respective Azure Cloud using [Azure Login](https://github.com/Azure/login) by setting appropriate value for the `environment` parameter.
109+
109110
## Contributing
110111

111112
This project welcomes contributions and suggestions. Most contributions require you to agree to a

__tests__/AzureSqlAction.test.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,22 +72,25 @@ describe('AzureSqlAction tests', () => {
7272
});
7373

7474
function getInputs(actionType: ActionType) {
75+
7576
switch(actionType) {
7677
case ActionType.DacpacAction: {
77-
return{
78-
serverName: 'testServer.database.windows.net',
78+
const connectionString = new SqlConnectionStringBuilder('Server=testServer.database.windows.net;Initial Catalog=testDB;User Id=testUser;Password=testPassword');
79+
return {
80+
serverName: connectionString.server,
7981
actionType: ActionType.DacpacAction,
80-
connectionString: new SqlConnectionStringBuilder('Server=testServer.database.windows.net;Initial Catalog=testDB;User Id=testUser;Password=testPassword'),
82+
connectionString: connectionString,
8183
dacpacPackage: './TestPackage.dacpac',
8284
sqlpackageAction: SqlPackageAction.Publish,
8385
additionalArguments: '/TargetTimeout:20'
8486
} as IDacpacActionInputs;
8587
}
8688
case ActionType.SqlAction: {
89+
const connectionString = new SqlConnectionStringBuilder('Server=testServer.database.windows.net;Initial Catalog=testDB;User Id=testUser;Password=testPassword');
8790
return {
88-
serverName: 'testServer.database.windows.net',
91+
serverName: connectionString.server,
8992
actionType: ActionType.SqlAction,
90-
connectionString: new SqlConnectionStringBuilder('Server=testServer.database.windows.net;Initial Catalog=testDB;User Id=testUser;Password=testPassword'),
93+
connectionString: connectionString,
9194
sqlFile: './TestFile.sql',
9295
additionalArguments: '-t 20'
9396
} as ISqlActionInputs;

__tests__/SqlConnectionStringBuilder.test.ts

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@ describe('SqlConnectionStringBuilder tests', () => {
77

88
describe('validate correct connection strings', () => {
99
let validConnectionStrings = [
10-
[`User Id=user;Password="ab'=abcdf''c;123";Initial catalog=testdb`, 'validates values enclosed with double quotes ', `ab'=abcdf''c;123`],
11-
[`User Id=user;Password='abc;1""2"adf=33';Initial catalog=testdb`, 'validates values enclosed with single quotes ', `abc;1""2"adf=33`],
12-
[`User Id=user;Password="abc;1""2""adf(012j^72''asj;')'=33";Initial catalog=testdb`, 'validates values beginning with double quotes and also contains escaped double quotes', `abc;1"2"adf(012j^72''asj;')'=33`],
13-
[`User Id=user;Password='ab""c;1''2''"''adf("0""12j^72''asj;'')''=33';Initial catalog=testdb`, 'validates values beginning with single quotes and also contains escaped single quotes', `ab""c;1'2'"'adf("0""12j^72'asj;')'=33`],
14-
[`User Id=user;Password=JustANormal123@#$password;Initial catalog=testdb`, 'validates values not beginning quotes and not containing quotes or semi-colon', `JustANormal123@#$password`]
10+
[`Server=test1.database.windows.net;User Id=user;Password="ab'=abcdf''c;123";Initial catalog=testdb`, 'validates values enclosed with double quotes ', `ab'=abcdf''c;123`],
11+
[`Server=test1.database.windows.net;User Id=user;Password='abc;1""2"adf=33';Initial catalog=testdb`, 'validates values enclosed with single quotes ', `abc;1""2"adf=33`],
12+
[`Server=test1.database.windows.net;User Id=user;Password="abc;1""2""adf(012j^72''asj;')'=33";Initial catalog=testdb`, 'validates values beginning with double quotes and also contains escaped double quotes', `abc;1"2"adf(012j^72''asj;')'=33`],
13+
[`Server=test1.database.windows.net;User Id=user;Password='ab""c;1''2''"''adf("0""12j^72''asj;'')''=33';Initial catalog=testdb`, 'validates values beginning with single quotes and also contains escaped single quotes', `ab""c;1'2'"'adf("0""12j^72'asj;')'=33`],
14+
[`Server=test1.database.windows.net;User Id=user;Password=JustANormal123@#$password;Initial catalog=testdb`, 'validates values not beginning quotes and not containing quotes or semi-colon', `JustANormal123@#$password`],
15+
[`User Id=user;Password=JustANormal123@#$password;Server=test1.database.windows.net;Initial catalog=testdb`, 'validates connection string without server', `JustANormal123@#$password`]
1516
];
1617

1718
it.each(validConnectionStrings)('Input `%s` %s', (connectionStringInput, testDescription, passwordOutput) => {
@@ -21,16 +22,20 @@ describe('SqlConnectionStringBuilder tests', () => {
2122
expect(connectionString.password).toMatch(passwordOutput);
2223
expect(connectionString.userId).toMatch(`user`);
2324
expect(connectionString.database).toMatch('testdb');
25+
if(!!connectionString.server) expect(connectionString.server).toMatch('test1.database.windows.net');
2426
});
2527
})
2628

2729
describe('throw for invalid connection strings', () => {
2830
let invalidConnectionStrings = [
29-
[`User Id=user;Password="ab'=abcdf''c;123;Initial catalog=testdb`, 'validates values beginning with double quotes but not ending with double quotes'],
30-
[`User Id=user;Password='abc;1""2"adf=33;Initial catalog=testdb`, 'validates values beginning with single quote but not ending with single quote'],
31-
[`User Id=user;Password="abc;1""2"adf(012j^72''asj;')'=33";Initial catalog=testdb`, 'validates values enclosed in double quotes but does not escape double quotes in between'],
32-
[`User Id=user;Password='ab""c;1'2''"''adf("0""12j^72''asj;'')''=33';Initial catalog=testdb`, 'validates values enclosed in single quotes but does not escape single quotes in between'],
33-
[`User Id=user;Password=NotANormal123@;#$password;Initial catalog=testdb`, 'validates values not enclosed in quotes and containing semi-colon']
31+
[`Server=test1.database.windows.net;User Id=user;Password="ab'=abcdf''c;123;Initial catalog=testdb`, 'validates values beginning with double quotes but not ending with double quotes'],
32+
[`Server=test1.database.windows.net;User Id=user;Password='abc;1""2"adf=33;Initial catalog=testdb`, 'validates values beginning with single quote but not ending with single quote'],
33+
[`Server=test1.database.windows.net;User Id=user;Password="abc;1""2"adf(012j^72''asj;')'=33";Initial catalog=testdb`, 'validates values enclosed in double quotes but does not escape double quotes in between'],
34+
[`Server=test1.database.windows.net;User Id=user;Password='ab""c;1'2''"''adf("0""12j^72''asj;'')''=33';Initial catalog=testdb`, 'validates values enclosed in single quotes but does not escape single quotes in between'],
35+
[`Server=test1.database.windows.net;User Id=user;Password=NotANormal123@;#$password;Initial catalog=testdb`, 'validates values not enclosed in quotes and containing semi-colon'],
36+
[`Server=test1.database.windows.net;Password=password;Initial catalog=testdb`, 'missing user id'],
37+
[`Server=test1.database.windows.net;User Id=user;Initial catalog=testdb`, 'missing password'],
38+
[`Server=test1.database.windows.net;User Id=user;Password=password;`, 'missing initial catalog']
3439
];
3540

3641
it.each(invalidConnectionStrings)('Input `%s` %s', (connectionString) => {
@@ -40,8 +45,8 @@ describe('SqlConnectionStringBuilder tests', () => {
4045

4146
it('should mask connection string password', () => {
4247
let setSecretSpy = jest.spyOn(core, 'setSecret');
43-
new SqlConnectionStringBuilder('User Id=user;Password=1234;Initial Catalog=testDB');
48+
new SqlConnectionStringBuilder('User Id=user;Password=1234;Server=test1.database.windows.net;Initial Catalog=testDB');
4449

4550
expect(setSecretSpy).toHaveBeenCalled();
46-
});
51+
});
4752
})

__tests__/main.test.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ describe('main.ts tests', () => {
3636
let addFirewallRuleSpy = jest.spyOn(FirewallManager.prototype, 'addFirewallRule');
3737
let actionExecuteSpy = jest.spyOn(AzureSqlAction.prototype, 'execute');
3838
let removeFirewallRuleSpy = jest.spyOn(FirewallManager.prototype, 'removeFirewallRule');
39-
let setFaledSpy = jest.spyOn(core, 'setFailed');
39+
let setFailedSpy = jest.spyOn(core, 'setFailed');
4040
let detectIPAddressSpy = SqlUtils.detectIPAddress = jest.fn().mockImplementationOnce(() => {
4141
return "";
4242
});
@@ -52,9 +52,9 @@ describe('main.ts tests', () => {
5252
expect(addFirewallRuleSpy).not.toHaveBeenCalled();
5353
expect(actionExecuteSpy).toHaveBeenCalled();
5454
expect(removeFirewallRuleSpy).not.toHaveBeenCalled();
55-
expect(setFaledSpy).not.toHaveBeenCalled();
55+
expect(setFailedSpy).not.toHaveBeenCalled();
5656
})
57-
57+
5858
it('gets inputs and executes sql action', async () => {
5959
let resolveFilePathSpy = jest.spyOn(AzureSqlActionHelper, 'resolveFilePath').mockReturnValue('./TestSqlFile.sql');
6060
let getInputSpy = jest.spyOn(core, 'getInput').mockImplementation((name, options) => {
@@ -66,7 +66,7 @@ describe('main.ts tests', () => {
6666
}
6767
});
6868

69-
let setFaledSpy = jest.spyOn(core, 'setFailed');
69+
let setFailedSpy = jest.spyOn(core, 'setFailed');
7070
let getAuthorizerSpy = jest.spyOn(AuthorizerFactory, 'getAuthorizer');
7171
let addFirewallRuleSpy = jest.spyOn(FirewallManager.prototype, 'addFirewallRule');
7272
let actionExecuteSpy = jest.spyOn(AzureSqlAction.prototype, 'execute');
@@ -86,7 +86,7 @@ describe('main.ts tests', () => {
8686
expect(addFirewallRuleSpy).not.toHaveBeenCalled();
8787
expect(actionExecuteSpy).toHaveBeenCalled();
8888
expect(removeFirewallRuleSpy).not.toHaveBeenCalled();
89-
expect(setFaledSpy).not.toHaveBeenCalled();
89+
expect(setFailedSpy).not.toHaveBeenCalled();
9090
})
9191

9292
it('throws if input file is not found', async() => {
@@ -106,11 +106,11 @@ describe('main.ts tests', () => {
106106
return "";
107107
});
108108

109-
let setFaledSpy = jest.spyOn(core, 'setFailed');
109+
let setFailedSpy = jest.spyOn(core, 'setFailed');
110110
await run();
111111

112112
expect(AzureSqlAction).not.toHaveBeenCalled();
113113
expect(detectIPAddressSpy).not.toHaveBeenCalled();
114-
expect(setFaledSpy).toHaveBeenCalledWith('Unable to find file at location');
114+
expect(setFailedSpy).toHaveBeenCalledWith('Unable to find file at location');
115115
})
116116
})

action.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,19 @@ description: 'Deploy a DACPAC or a SQL script to Azure SQL d
33
inputs:
44
server-name:
55
description: 'Name of the Azure SQL Server name, like Fabrikam.database.windows.net.'
6-
required: true
6+
required: false
77
connection-string:
88
description: 'The connection string, including authentication information, for the Azure SQL Server database.'
99
required: true
1010
dacpac-package:
1111
description: 'Path to DACPAC file to deploy'
12+
required: false
1213
sql-file:
1314
description: 'Path to SQL script file to deploy'
15+
required: false
1416
arguments:
1517
description: 'In case DACPAC option is selected, additional SqlPackage arguments that will be applied. When SQL query option is selected, additional sqlcmd arguments will be applied.'
18+
required: false
1619
runs:
1720
using: 'node12'
1821
main: 'lib/main.js'

src/SqlConnectionStringBuilder.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ export default class SqlConnectionStringBuilder {
5757
return this._parsedConnectionString.database;
5858
}
5959

60+
public get server(): string {
61+
return this._parsedConnectionString.server;
62+
}
63+
6064
private _validateConnectionString() {
6165
if (!connectionStringTester.test(this._connectionString)) {
6266
throw new Error('Invalid connection string. A valid connection string is a series of keyword/value pairs separated by semi-colons. If there are any special characters like quotes, semi-colons in the keyword value, enclose the value within quotes. Refer this link for more info on conneciton string https://aka.ms/sqlconnectionstring');

0 commit comments

Comments
 (0)