Skip to content

Commit

Permalink
chore: Initial release
Browse files Browse the repository at this point in the history
Merge pull request #15 from DFE-Digital/next
  • Loading branch information
killij authored Dec 8, 2023
2 parents b0f5eff + a2e40ef commit 90219f3
Show file tree
Hide file tree
Showing 7 changed files with 192 additions and 15 deletions.
95 changes: 95 additions & 0 deletions .github/workflows/build-docker-image.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
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'
runs-on: ubuntu-latest
permissions:
contents: read
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
uses: docker/build-push-action@v5
with:
context: ./src
file: ./src/Childrens-Social-Care-CPD-Indexer/Dockerfile
push: true
tags: ${{ env.IMAGE_TAGS }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
VCSREF=${{ env.sha_short }}
VCSTAG=${{ env.RELEASE_TAG }}
cache-from: type=gha
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
# transparency data even for private images, pass --force to cosign below.
# https://github.com/sigstore/cosign
- name: Install Cosign
uses: sigstore/[email protected]
- name: Check install!
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 }}
31 changes: 31 additions & 0 deletions .github/workflows/semantic-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: Semantic release
on:
push:
branches:
- main
- next

permissions:
contents: read # for checkout

jobs:
release:
name: Run semantic-release
runs-on: ubuntu-latest
permissions:
contents: write # to be able to publish a GitHub release
issues: write # to be able to comment on released issues
pull-requests: write # to be able to comment on released pull requests
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "lts/*"
- name: Check for release
env:
GITHUB_TOKEN: ${{ secrets.PAT }}
run: npx semantic-release@22
File renamed without changes.
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Childrens Social Care CPD Search Indexer
This repository contains the search indexing application used within the [Childrens-Social-Care-CPD](https://github.com/DFE-Digital/childrens-social-care-cpd) website.

## Getting started
```
git clone https://github.com/DFE-Digital/childrens-social-care-cpd-indexer.git
cd src
dotnet build
```

## Running the tests
```
cd src
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 |

Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,7 @@ 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));
_client.GetIndexAsync(Arg.Any<string>(), Arg.Any<CancellationToken>()).Returns(Task.FromResult(response));

// act
await _sut.DeleteIndexAsync("foo");
Expand All @@ -46,8 +45,7 @@ public async Task DeleteIndexAsync_Deletes_The_Index()
// arrange
var response = Substitute.For<Response<SearchIndex>>();
response.HasValue.Returns(true);
_client.GetIndexAsync(Arg.Any<string>(), Arg.Any<CancellationToken>())
.Returns(Task.FromResult(response));
_client.GetIndexAsync(Arg.Any<string>(), Arg.Any<CancellationToken>()).Returns(Task.FromResult(response));

// act
await _sut.DeleteIndexAsync("foo");
Expand All @@ -65,10 +63,8 @@ public async Task DeleteIndexAsync_Logs_Failure_To_Delete_Index()

var deleteIndexResult = Substitute.For<Response>();
deleteIndexResult.IsError.Returns(true);
_client.GetIndexAsync(Arg.Any<string>(), Arg.Any<CancellationToken>())
.Returns(Task.FromResult(getIndexResult));
_client.DeleteIndexAsync(Arg.Any<string>(), Arg.Any<CancellationToken>())
.Returns(Task.FromResult(deleteIndexResult));
_client.GetIndexAsync(Arg.Any<string>(), Arg.Any<CancellationToken>()).Returns(Task.FromResult(getIndexResult));
_client.DeleteIndexAsync(Arg.Any<string>(), Arg.Any<CancellationToken>()).Returns(Task.FromResult(deleteIndexResult));

// act
await _sut.DeleteIndexAsync("foo");
Expand All @@ -81,6 +77,10 @@ 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));

SearchIndex? searchIndex = null;
await _client.CreateIndexAsync(Arg.Do<SearchIndex>(x => searchIndex = x), Arg.Any<CancellationToken>());

Expand All @@ -92,7 +92,23 @@ public async Task CreateIndexAsync_Creates_The_Index()
searchIndex.Should().NotBeNull();
searchIndex!.Name.Should().Be("foo");
}


[Test]
public async Task CreateIndexAsync_Skips_Index_Creation()
{
// arrange
var getIndexResult = Substitute.For<Response<SearchIndex>>();
getIndexResult.HasValue.Returns(true);
_client.GetIndexAsync(Arg.Any<string>(), Arg.Any<CancellationToken>()).Returns(Task.FromResult(getIndexResult));
await _client.CreateIndexAsync(Arg.Any<SearchIndex>(), Arg.Any<CancellationToken>());

// act
await _sut.CreateIndexAsync("foo");

// assert
await _client.Received(0).CreateIndexAsync(Arg.Any<SearchIndex>(), Arg.Any<CancellationToken>());
}

[Test]
public async Task PopulateIndexAsync_Uploads_Documents()
{
Expand Down
12 changes: 7 additions & 5 deletions src/Childrens-Social-Care-CPD-Indexer/Core/ResourcesIndexer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ internal class ResourcesIndexer(SearchIndexClient searchIndexClient, IDocumentFe
{
public async Task CreateIndexAsync(string indexName, CancellationToken cancellationToken = default)
{
var index = await searchIndexClient.GetIndexAsync(indexName, cancellationToken);
if (index.HasValue)
{
logger.LogInformation("Index already exists, skipping creation.");
return;
}

logger.LogInformation("Creating index...");
var fieldBuilder = new FieldBuilder();
var searchFields = fieldBuilder.Build(typeof(CpdDocument));
Expand All @@ -18,15 +25,10 @@ public async Task CreateIndexAsync(string indexName, CancellationToken cancellat
public async Task DeleteIndexAsync(string indexName, CancellationToken cancellationToken = default)
{
logger.LogInformation("Deleting index...");

var index = await searchIndexClient.GetIndexAsync(indexName, cancellationToken);
cancellationToken.ThrowIfCancellationRequested();

if (index.HasValue)
{
var deleteResponse = await searchIndexClient.DeleteIndexAsync(indexName, cancellationToken);
cancellationToken.ThrowIfCancellationRequested();

if (deleteResponse.IsError)
{
logger.LogError("Failed to delete the index");
Expand Down
2 changes: 1 addition & 1 deletion src/Childrens-Social-Care-CPD-Indexer/IndexingService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ public async Task StartAsync(CancellationToken cancellationToken)
if (config.RecreateIndex)
{
await resourcesIndexer.DeleteIndexAsync(config.IndexName, cancellationToken);
await resourcesIndexer.CreateIndexAsync(config.IndexName, cancellationToken);
}

await resourcesIndexer.CreateIndexAsync(config.IndexName, cancellationToken);
await resourcesIndexer.PopulateIndexAsync(config.IndexName, config.BatchSize, cancellationToken);

}
Expand Down

0 comments on commit 90219f3

Please sign in to comment.