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

Perform release #29

Merged
merged 26 commits into from
Mar 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
4c1330d
fix: Azure throws an Exception when the index doesn't exist
killij Dec 8, 2023
5e24f4e
Add telemetry client
killij Dec 11, 2023
e2f2d64
feat: Add telemetry client (#17)
killij Dec 11, 2023
9c334f9
feat: Switch to using an Azure Function instead of a Container App Jo…
killij Dec 12, 2023
4e8ecce
Create function-app-deploy.yml
albal Jan 9, 2024
24a2dfa
Update function-app-deploy.yml
albal Jan 9, 2024
e160768
Switch to manual and add tag to deploy
albal Jan 9, 2024
146af22
Select environment to use
albal Jan 9, 2024
e89bba2
Use wget to fetch asset
albal Jan 9, 2024
a86fa2e
Use repository.name and set --src
albal Jan 9, 2024
5c41b90
Update to use github.repository for repo org/name
albal Jan 9, 2024
3a0ab6c
Merge pull request #19 from DFE-Digital/function-app-deploy-action
albal Jan 10, 2024
a7e00f4
feat: Add the estimated reading time to the index (#20)
killij Jan 11, 2024
1ed3336
ci: Upload the packaged release to the Github release
killij Jan 15, 2024
024e848
ci: Update function-app-deploy.yml to retrieve the build zip asset fr…
killij Jan 15, 2024
0f16ec9
ci: Fix deployment file name (#23)
killij Jan 15, 2024
aacafe9
Create build-docker-image.yml
killij Jan 16, 2024
7a01fb6
Convert the function back into a Container App Job
killij Jan 16, 2024
808e82e
Delete unrequired github actions
killij Jan 16, 2024
901520b
Make sure *everything* gets logged to ApplicationInsights
killij Jan 16, 2024
cc37785
feat: Convert to Azure Container App
killij Jan 17, 2024
a6c9639
feat: Add Azure Key Vault as a configuration provider and compact the…
killij Jan 19, 2024
0413a78
fix: Fix logging by flushing the app insights buffer (#26)
killij Jan 19, 2024
243922a
feat: Make the Title field searchable (#27)
killij Jan 23, 2024
9766e70
fix: Remove the environment aspect of the configuration (#28)
killij Feb 14, 2024
462eaa3
Merge commit '9766e70e637befbc25f43345aa1b0e0104bb1764' into perform-…
Mar 27, 2024
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
17 changes: 2 additions & 15 deletions .github/workflows/build-docker-image.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
name: Docker Publish

on:
release:
types: [published]

env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
RELEASE_TAG: ${{ github.event.release.tag_name }}

jobs:
build:
name: 'Docker Publish'
Expand All @@ -18,53 +15,44 @@ jobs:
packages: write
# This is used to complete the identity challenge with sigstore/fulcio when running outside of PRs.
id-token: write

steps:
# Checkout the release tag version
- name: Checkout repository ${{ env.RELEASE_TAG }}
uses: actions/checkout@v3
with:
ref: ${{ env.RELEASE_TAG }}

# Get git commit hash
- name: Get short hash
run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_ENV

# Need to lower case the image name for the docker tags when publishing
- name: Downcase IMAGE_NAME variable
run: echo "IMAGE_NAME_LOWER=${IMAGE_NAME,,}" >> $GITHUB_ENV

# Sort out the image tags
- name: Set initial tag
run: echo "IMAGE_TAGS=${{ env.REGISTRY }}/${{ env.IMAGE_NAME_LOWER }}:${{ env.RELEASE_TAG }}" >> $GITHUB_ENV

- name: Add latest tag if we're not production release
if: contains(env.RELEASE_TAG, 'next')
run: echo "IMAGE_TAGS=${{ env.IMAGE_TAGS }},${{ env.REGISTRY }}/${{ env.IMAGE_NAME_LOWER }}:latest" >> $GITHUB_ENV

#debug
- name: Log the tags
run: echo "Calculated tags value => ${{ env.IMAGE_TAGS }}"

# Setup docker build tool
- name: Setup Docker buildx
uses: docker/setup-buildx-action@v3

# Login against a Docker registry
- name: Log into registry ${{ env.REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

# Extract metadata (tags, labels) for Docker
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}

# Build and push Docker image with Buildx (don't push on PR)
- name: Build and push Docker image
id: build-and-push
Expand All @@ -79,8 +67,7 @@ jobs:
VCSREF=${{ env.sha_short }}
VCSTAG=${{ env.RELEASE_TAG }}
cache-from: type=gha
cache-to: type=gha,mode=max

cache-to: type=gha,mode=max
# Sign the resulting Docker image digest.
# This will only write to the public Rekor transparency log when the Docker
# repository is public to avoid leaking data. If you would like to publish
Expand All @@ -92,4 +79,4 @@ jobs:
run: cosign version
- name: Sign the published Docker image
# This step uses the identity token to provision an ephemeral certificate against the sigstore community Fulcio instance.
run: echo "${{ steps.meta.outputs.tags }}" | xargs -I {} cosign sign --yes {}@${{ steps.build-and-push.outputs.digest }}
run: echo "${{ steps.meta.outputs.tags }}" | xargs -I {} cosign sign --yes {}@${{ steps.build-and-push.outputs.digest }}
52 changes: 37 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,41 @@ dotnet build
dotnet test
```

## Environment variables
The following environment variables will need to be specified for the application to run:

| Variable name | Type/Value | Description |
| ------------- | ------------- | ------------- |
| CPD_SEARCH_API_KEY | string | The Azure AI Search API key |
| CPD_INSTRUMENTATION_CONNECTIONSTRING | string | The Azure ApplicationInsights connection string |
| VCS-TAG | string | The application version |
| CPD_SEARCH_BATCH_SIZE | integer (e.g. 10/20 etc) | The batch size for queries into Contentful |
| CPD_SEARCH_ENDPOINT | string | The Azure AI Search endpoint |
| CPD_SEARCH_INDEX_NAME | string | The Azure AI Search index name to access/create |
| CPD_DELIVERY_KEY | string | The Contentful delivery API key |
| CPD_CONTENTFUL_ENVIRONMENT | string | The Contentful enviroment id |
| CPD_SPACE_ID | string | The Contentful space id |
| CPD_SEARCH_RECREATE_INDEX_ON_REBUILD | boolean (true/false) | Whether to delete the index and recreate before populating |
## Configuration
Three configuration values are required to be set in the environment:
* ``CPD_KEY_VAULT_NAME`` - the name of the keyvault to retrieve configuration from in a deployed environment
* ``CPD_CONFIG_SECTION_NAME`` - this is the key name of the root of the configuration section that stores most of the application config
* ``<CPD_CONFIG_SECTION_NAME>__Application__Version`` - the application version, note the prefix should be set to the above value, for example if `CPD_CONFIG_SECTION_NAME` was set to `DEV` then it would be `DEV__Application__Version`

The remaining application configuration is stored within an IConfiguration section, named using (1) above. In a deployed environment this should be stored in Azure Key Vault.

The configuration under the root key takes the following hierarchical structure:
```
ApplicationInsights
ConnectionString
Contentful
DeliveryKey
Environment
SpaceId
SearchIndexing
ApiKey
BatchSize
Endpoint
IndexName
RecreateIndex
```


### Local developer configuration
For development, this structure can be configured in the `secrets.json` file for the project.
Also make sure you set `LOCAL_ENVIRONMENT` to true, otherwise the application will try to initialise key vault. `LOCAL_ENVIRONMENT` is not required for any other scenario.

### Deployed environments
Key names for secrets are built from the full path to the config key, separating each level with two dash characters (--). So if `CPD_CONFIG_SECTION_NAME` is set to `DEV` then it would be:
```
DEV--ApplicationInsights--ConnectionString
DEV--ApplicationVersion
DEV--Contentful--DeliveryKey
...
```
Note this format is for **Key Vault only**.
Original file line number Diff line number Diff line change
@@ -1,15 +1,44 @@
using Azure;
using Azure.Core;
using Azure.Search.Documents;
using Azure.Search.Documents.Indexes;
using Azure.Search.Documents.Indexes.Models;
using Childrens_Social_Care_CPD_Indexer.Core;
using Microsoft.Extensions.Logging;
using NSubstitute.ExceptionExtensions;
using System.Diagnostics.CodeAnalysis;

namespace Childrens_Social_Care_CPD_Indexer.Tests.Core;

public class ResourcesIndexerTest
internal sealed class MockResponse : Response
{
private ILogger _logger;
public override int Status => 404;
public override string ReasonPhrase => string.Empty;

public override Stream? ContentStream
{
get => new MemoryStream();
set => throw new NotImplementedException();
}
public override string ClientRequestId
{
get => throw new NotImplementedException();
set => throw new NotImplementedException();
}

public override void Dispose() => throw new NotImplementedException();
protected override bool ContainsHeader(string name) => false;
protected override IEnumerable<HttpHeader> EnumerateHeaders() => Array.Empty<HttpHeader>();
protected override bool TryGetHeader(string name, [NotNullWhen(true)] out string? value) {
value = null;
return false;
}
protected override bool TryGetHeaderValues(string name, [NotNullWhen(true)] out IEnumerable<string>? values) => throw new NotImplementedException();
}

public class ResourcesIndexerTests
{
private ILogger<ResourcesIndexer> _logger;
private SearchIndexClient _client;
private IDocumentFetcher _documentFetcher;
private ResourcesIndexer _sut;
Expand All @@ -21,16 +50,15 @@ public void Setup()
_client = Substitute.For<SearchIndexClient>();
_documentFetcher = Substitute.For<IDocumentFetcher>();

_sut = new ResourcesIndexer(_client, _documentFetcher, _logger);
_sut = new ResourcesIndexer(_client, _documentFetcher, _logger, MockTelemetryClient.Create());
}

[Test]
public async Task DeleteIndexAsync_Skips_Deletion_If_Index_Does_Not_Exist()
{
// arrange
var response = Substitute.For<Response<SearchIndex>>();
response.HasValue.Returns(false);
_client.GetIndexAsync(Arg.Any<string>(), Arg.Any<CancellationToken>()).Returns(Task.FromResult(response));
var exception = new RequestFailedException(new MockResponse());
_client.GetIndexAsync(Arg.Any<string>(), Arg.Any<CancellationToken>()).Throws(exception);

// act
await _sut.DeleteIndexAsync("foo");
Expand Down Expand Up @@ -77,9 +105,8 @@ public async Task DeleteIndexAsync_Logs_Failure_To_Delete_Index()
public async Task CreateIndexAsync_Creates_The_Index()
{
// arrange
var getIndexResult = Substitute.For<Response<SearchIndex>>();
getIndexResult.HasValue.Returns(false);
_client.GetIndexAsync(Arg.Any<string>(), Arg.Any<CancellationToken>()).Returns(Task.FromResult(getIndexResult));
var exception = new RequestFailedException(new MockResponse());
_client.GetIndexAsync(Arg.Any<string>(), Arg.Any<CancellationToken>()).Throws(exception);

SearchIndex? searchIndex = null;
await _client.CreateIndexAsync(Arg.Do<SearchIndex>(x => searchIndex = x), Arg.Any<CancellationToken>());
Expand Down Expand Up @@ -141,7 +168,6 @@ public async Task PopulateIndexAsync_Uploads_Documents_In_Multiple_Batches()

await _sut.PopulateIndexAsync("foo", 10);

await client.Received(2)
.UploadDocumentsAsync(documents, Arg.Any<IndexDocumentsOptions>(), Arg.Any<CancellationToken>());
await client.Received(2).UploadDocumentsAsync(documents, Arg.Any<IndexDocumentsOptions>(), Arg.Any<CancellationToken>());
}
}

This file was deleted.

32 changes: 32 additions & 0 deletions src/Childrens-Social-Care-CPD-Indexer.Tests/MockTelemetryClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using Microsoft.ApplicationInsights;
using Microsoft.ApplicationInsights.Channel;
using Microsoft.ApplicationInsights.Extensibility;
using System.Collections.Concurrent;

namespace Childrens_Social_Care_CPD_Indexer.Tests;

internal sealed class MockTelemetryChannel : ITelemetryChannel
{
public ConcurrentBag<ITelemetry> SentTelemtries = [];
public bool IsFlushed { get; private set; }
public bool? DeveloperMode { get; set; }
public string EndpointAddress { get; set; } = string.Empty;

public void Send(ITelemetry item) => SentTelemtries.Add(item);
public void Flush() => IsFlushed = true;
public void Dispose() {}
}

internal static class MockTelemetryClient
{
public static TelemetryClient Create()
{
var mockTelemetryConfig = new TelemetryConfiguration
{
TelemetryChannel = new MockTelemetryChannel(),
};

return new TelemetryClient(mockTelemetryConfig);
}

}
Loading
Loading