Skip to content

Commit

Permalink
Completed verification step again secrets/variables defined in the Gi…
Browse files Browse the repository at this point in the history
…tHub project. #67
  • Loading branch information
jezzsantos committed Jan 3, 2025
1 parent dcb4a28 commit 3d7928f
Show file tree
Hide file tree
Showing 12 changed files with 384 additions and 98 deletions.
4 changes: 2 additions & 2 deletions src/AWSLambdas.Api.WorkerHost/appsettings.Deploy.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
"Notes": "Lists the required configuration keys that must be overwritten (by GitHub action) when we deploy this host",
"Required": [
{
"description": "AWS specific settings from appsettings.json",
"keys": [
"Description": "AWS specific settings from appsettings.json",
"Keys": [
"Hosts:ApiHost1:BaseUrl",
"Hosts:ApiHost1:HMACAuthNSecret",
"Hosts:AncillaryApi:BaseUrl",
Expand Down
48 changes: 30 additions & 18 deletions src/ApiHost1/appsettings.Deploy.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,24 @@
"Notes": "Lists the required configuration keys that must be overwritten (by the GitHub configuration action) when we deploy this host",
"Required": [
{
"description": "General settings from appsettings.json",
"keys": [
"ApplicationServices:Persistence:Kurrent:ConnectionString",
"Description": "General settings from appsettings.json",
"Keys": [
"ApplicationServices:SSOProvidersService:SSOUserTokens:AesSecret",
"ApplicationServices:Gravatar:BaseUrl",
"Hosts:AncillaryApi:BaseUrl",
"Hosts:AncillaryApi:HMACAuthNSecret",
"Hosts:IdentityApi:BaseUrl",
"Hosts:IdentityApi:JWT:SigningSecret",
"Hosts:ImagesApi:BaseUrl",
"Hosts:EndUsersApi:Authorization:OperatorWhitelist",
"Hosts:WebsiteHost:BaseUrl"
]
},
{
"Description": "General settings from appsettings.json for specific optional technology adapters",
"Disabled": true,
"Keys": [
"ApplicationServices:Persistence:Kurrent:ConnectionString",
"ApplicationServices:Chargebee:BaseUrl",
"ApplicationServices:Chargebee:ApiKey",
"ApplicationServices:Chargebee:SiteName",
Expand All @@ -19,7 +33,6 @@
"ApplicationServices:Chargebee:Webhook:Password",
"ApplicationServices:Flagsmith:BaseUrl",
"ApplicationServices:Flagsmith:EnvironmentKey",
"ApplicationServices:Gravatar:BaseUrl",
"ApplicationServices:Mailgun:BaseUrl",
"ApplicationServices:Mailgun:DomainName",
"ApplicationServices:Mailgun:ApiKey",
Expand All @@ -30,36 +43,35 @@
"ApplicationServices:Twilio:SenderPhoneNumber",
"ApplicationServices:Twilio:WebhookCallbackUrl",
"ApplicationServices:UserPilot:BaseUrl",
"ApplicationServices:UserPilot:ApiKey",
"Hosts:AncillaryApi:BaseUrl",
"Hosts:AncillaryApi:HMACAuthNSecret",
"Hosts:IdentityApi:BaseUrl",
"Hosts:IdentityApi:JWT:SigningSecret",
"Hosts:ImagesApi:BaseUrl",
"Hosts:EndUsersApi:Authorization:OperatorWhitelist",
"Hosts:WebsiteHost:BaseUrl"
"ApplicationServices:UserPilot:ApiKey"
]
},
{
"description": "Azure specific settings from appsettings.Azure.json",
"keys": [
"Description": "Azure specific settings from appsettings.Azure.json",
"Keys": [
"ApplicationInsights:ConnectionString",
"ApplicationServices:Persistence:AzureStorageAccount:AccountName",
"ApplicationServices:Persistence:AzureStorageAccount:AccountKey",
"ApplicationServices:Persistence:AzureServiceBus:ConnectionString",
"ApplicationServices:Persistence:SqlServer:DbServerName",
"ApplicationServices:Persistence:SqlServer:DbCredentials",
"ApplicationServices:Persistence:SqlServer:DbName",
"ApplicationServices:Persistence:SqlServer:DbName"
]
},
{
"Description": "Azure specific settings from appsettings.Azure.json for specific optional technology adapters",
"Disabled": true,
"Keys": [
"ApplicationServices:MicrosoftIdentity:BaseUrl",
"ApplicationServices:MicrosoftIdentity:ClientId",
"ApplicationServices:MicrosoftIdentity:ClientSecret",
"ApplicationServices:MicrosoftIdentity:RedirectUri"
]
},
{
"description": "AWS specific settings from appsettings.AWS.json",
"disabled": true,
"keys": [
"Description": "AWS specific settings from appsettings.AWS.json",
"Disabled": true,
"Keys": [
"ApplicationServices:Persistence:AWS:AccessKey",
"ApplicationServices:Persistence:AWS:SecretKey",
"ApplicationServices:Persistence:AWS:Region",
Expand Down
4 changes: 2 additions & 2 deletions src/AzureFunctions.Api.WorkerHost/appsettings.Deploy.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
"Notes": "Lists the required configuration keys that must be overwritten (by GitHub action) when we deploy this host",
"Required": [
{
"description": "Azure specific settings from appsettings.json",
"keys": [
"Description": "Azure specific settings from appsettings.json",
"Keys": [
"Hosts:ApiHost1:BaseUrl",
"Hosts:ApiHost1:HMACAuthNSecret",
"Hosts:AncillaryApi:BaseUrl",
Expand Down
1 change: 1 addition & 0 deletions src/SaaStack.sln.DotSettings
Original file line number Diff line number Diff line change
Expand Up @@ -1523,6 +1523,7 @@ public void When$condition$_Then$outcome$()
<s:Boolean x:Key="/Default/UserDictionary/Words/=adefaultorganizationid/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=adefaultvalue/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=adeleterid/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=adeploy/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=adescription/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=adetail/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=adetails/@EntryIndexedValue">True</s:Boolean>
Expand Down
21 changes: 11 additions & 10 deletions src/Tools.GitHubActions/VariableSubstitution/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ This action also relies on the variables/secrets defined in the GitHub project,

### Source Code

This action depends on the definition of a set of "required" settings defined in one of your `appsettings.json` files.
This action depends on the definition of a set of "Required" settings defined in one of your `appsettings.json` files.

For example,

Expand All @@ -29,25 +29,25 @@ For example,
"Notes": "Lists the required configuration keys that must be overwritten (by the GitHub configuration action) when we deploy this host",
"Required": [
{
"description": "General settings from appsettings.json",
"keys": [
"Description": "General settings from appsettings.json",
"Keys": [
"ApplicationServices:Persistence:Kurrent:ConnectionString",
"Hosts:WebsiteHost:BaseUrl"
]
},
{
"description": "Azure specific settings from appsettings.Azure.json",
"keys": [
"Description": "Azure specific settings from appsettings.Azure.json",
"Keys": [
"ApplicationInsights:ConnectionString",
"ApplicationServices:Persistence:SqlServer:DbServerName",
"ApplicationServices:Persistence:SqlServer:DbCredentials",
"ApplicationServices:Persistence:SqlServer:DbName"
]
},
{
"description": "AWS specific settings from appsettings.AWS.json",
"disabled": true,
"keys": [
"Description": "AWS specific settings from appsettings.AWS.json",
"Disabled": true,
"Keys": [
"ApplicationServices:Persistence:AWS:AccessKey",
"ApplicationServices:Persistence:AWS:SecretKey",
"ApplicationServices:Persistence:AWS:Region",
Expand All @@ -58,9 +58,10 @@ For example,
}
}
```
> Note: These definitions are assumed exist in your `appsettings.json` files. However, they can also exist in separate files, just used for deployment use. e.g., `appsettings.Deploy.json`.

> Note: without these definitions, this action just performs variable substitution, without any verification.
> Note: These definitions are assumed exist within your main `appsettings.json` file. Or they can also exist in separate files, just used for deployment use. e.g., `appsettings.Deploy.json`.
> Note: Keys where `Disabled` is set to `true` will be ignored.
> Note: without any of these "Keys" definitions, this action will just perform variable substitution, without any verification.
### GitHub Project

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ describe('AppSettingsJsonFileReader', () => {
try {
await reader.readAppSettingsFile(path);
} catch (error) {
expect(error.message).toMatch(`File '${path}' does not contain valid JSON: SyntaxError: Unexpected token 'i', \"invalid\" is not valid JSON`);
expect(error.message).toContain(`File '${path}' does not contain valid JSON: SyntaxError: Unexpected token`);
}
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ describe('ConfigurationSets', () => {
expect(sets.hasNone).toBe(true);
expect(globParser.parseFiles).toHaveBeenCalledWith([]);
expect(jsonFileReader.readAppSettingsFile).not.toHaveBeenCalled();
expect(logger.warning).toHaveBeenCalledWith('No settings files found in this repository, using the glob patterns: ');
expect(logger.warning).toHaveBeenCalledWith('No settings files found in this repository, applying the glob patterns: ');
});

it('should create a single set, when has one file at the root', async () => {
Expand Down Expand Up @@ -123,11 +123,23 @@ describe('ConfigurationSets', () => {
jsonFileReader.readAppSettingsFile
.mockResolvedValueOnce({
"aname1": "avalue",
"Required": ["arequired1", "arequired2"]
"Deploy": {
"Required": [
{
"Keys": ["arequired1", "arequired2"]
}
]
}
})
.mockResolvedValueOnce({
"aname2": "avalue",
"Required": ["arequired2", "arequired3"]
"Deploy": {
"Required": [
{
"Keys": ["arequired2", "arequired3"]
}
]
}
});

const sets = await ConfigurationSets.create(logger, globParser, jsonFileReader, '');
Expand Down Expand Up @@ -159,7 +171,7 @@ describe('ConfigurationSets', () => {

const sets = await ConfigurationSets.create(logger, globParser, jsonFileReader, '');

const result = sets.verifyConfiguration();
const result = sets.verifyConfiguration({}, {});

expect(result).toBe(true)
});
Expand All @@ -179,13 +191,13 @@ describe('ConfigurationSets', () => {

const sets = await ConfigurationSets.create(logger, globParser, jsonFileReader, '');

const result = sets.verifyConfiguration();
const result = sets.verifyConfiguration({}, {});

expect(result).toBe(true);
expect(logger.info).toHaveBeenCalledWith(`Verification of host 'apath' completed successfully`);
expect(logger.info).toHaveBeenCalledWith(`Verifying settings files in host: 'apath' -> Successful!`);
});

it('should return false, when the set contains required variable, and not exists', async () => {
it('should return true, but warn, when the set defines required variable, but no variable exists to substitute', async () => {

const globParser: jest.Mocked<IGlobPatternParser> = {
parseFiles: jest.fn(_matches => Promise.resolve(["apath/afile1.json"])),
Expand All @@ -196,20 +208,57 @@ describe('ConfigurationSets', () => {
jsonFileReader.readAppSettingsFile
.mockResolvedValueOnce({
"aname": "avalue",
"Required": ["arequired"]
"Deploy": {
"Required": [
{
"Keys": ["arequired"]
}
]
}
});

const sets = await ConfigurationSets.create(logger, globParser, jsonFileReader, '');

const result = sets.verifyConfiguration();
const result = sets.verifyConfiguration({}, {});

expect(result).toBe(true);
expect(logger.warning).toHaveBeenCalledWith(`Required variable 'arequired' is not yet defined in any of the settings files of this host! Consider either defining it in one of the settings files of this host, OR remove it from the 'Required' section of the settings files in this host`);
expect(logger.info).toHaveBeenCalledWith(`Verifying settings files in host: 'apath' -> Successful!`);
});

it('should return false, when the set defines required variable, but no variable/secret exists in GitHub', async () => {

const globParser: jest.Mocked<IGlobPatternParser> = {
parseFiles: jest.fn(_matches => Promise.resolve(["apath/afile1.json"])),
};
const jsonFileReader: jest.Mocked<IAppSettingsJsonFileReader> = {
readAppSettingsFile: jest.fn()
};
jsonFileReader.readAppSettingsFile
.mockResolvedValueOnce({
"arequired-arequired": {
"aname": "avalue"
},
"Deploy": {
"Required": [
{
"Keys": ["arequired-arequired:aname"]
}
]
}
});

const sets = await ConfigurationSets.create(logger, globParser, jsonFileReader, '');

const result = sets.verifyConfiguration({}, {});

expect(result).toBe(false);
expect(logger.info).not.toHaveBeenCalledWith(`Verification of host 'apath' completed successfully`);
expect(logger.error).toHaveBeenCalledWith(`Required variable 'arequired' is not defined in any of the settings files of this host!`);
expect(logger.error).toHaveBeenCalledWith(`Verification of host 'apath' failed, there is at least one missing required variable!`);
expect(logger.error).toHaveBeenCalledWith(`Required GitHub environment variable (or secret) 'AREQUIRED_AREQUIRED_ANAME' (alias: 'arequired-arequired:aname') has not been defined in the environment variables (or secrets) of this GitHub project!`);
expect(logger.error).toHaveBeenCalledWith(`Verification settings files in host: 'apath' -> Failed! there is at least one missing required environment variable or secret in this GitHub project`);
});

it('should return true, when the set contains required variable, and exists', async () => {
it('should return true, when the set defines required variable, and variable exists in GitHub', async () => {

const globParser: jest.Mocked<IGlobPatternParser> = {
parseFiles: jest.fn(_matches => Promise.resolve(["apath/afile1.json"])),
Expand All @@ -219,17 +268,54 @@ describe('ConfigurationSets', () => {
};
jsonFileReader.readAppSettingsFile
.mockResolvedValueOnce({
"aname": "avalue",
"Required": ["aname"]
"arequired-arequired": {
"aname": "avalue"
},
"Deploy": {
"Required": [
{
"Keys": ["arequired-arequired:aname"]
}
]
}
});

const sets = await ConfigurationSets.create(logger, globParser, jsonFileReader, '');

const result = sets.verifyConfiguration();
const result = sets.verifyConfiguration({"AREQUIRED_AREQUIRED_ANAME": "avalue"}, {});

expect(result).toBe(true);
expect(logger.info).toHaveBeenCalledWith(`Verification of host 'apath' completed successfully`);
expect(logger.info).toHaveBeenCalledWith(`Verifying settings files in host: 'apath' -> Successful!`);
});

it('should return true, when the set defines required variable, and secret exists in GitHub', async () => {

const globParser: jest.Mocked<IGlobPatternParser> = {
parseFiles: jest.fn(_matches => Promise.resolve(["apath/afile1.json"])),
};
const jsonFileReader: jest.Mocked<IAppSettingsJsonFileReader> = {
readAppSettingsFile: jest.fn()
};
jsonFileReader.readAppSettingsFile
.mockResolvedValueOnce({
"arequired-arequired": {
"aname": "avalue"
},
"Deploy": {
"Required": [
{
"Keys": ["arequired-arequired:aname"]
}
]
}
});

const sets = await ConfigurationSets.create(logger, globParser, jsonFileReader, '');

const result = sets.verifyConfiguration({}, {"AREQUIRED_AREQUIRED_ANAME": "avalue"});

expect(result).toBe(true);
expect(logger.info).toHaveBeenCalledWith(`Verifying settings files in host: 'apath' -> Successful!`);
});
});
});
Loading

0 comments on commit 3d7928f

Please sign in to comment.