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

chore: Initial release #15

Merged
merged 20 commits into from
Dec 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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