Skip to content

Commit

Permalink
Merge pull request #2149 from DFE-Digital/sync-configuration
Browse files Browse the repository at this point in the history
Sync country, region and english language provider configuration
  • Loading branch information
thomasleese authored Apr 17, 2024
2 parents a1fca6e + f582128 commit bd1dc1c
Show file tree
Hide file tree
Showing 6 changed files with 405 additions and 0 deletions.
104 changes: 104 additions & 0 deletions .github/workflows/configuration-sync.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
name: Synchronise configuration

on: workflow_dispatch

jobs:
export:
name: Export from production
runs-on: ubuntu-latest

environment: production

outputs:
output1: ${{ steps.step1.outputs.test }}
output2: ${{ steps.step2.outputs.test }}

steps:
- uses: actions/checkout@v4

- uses: Azure/login@v2
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}

- uses: ./.github/actions/set-kubernetes-credentials
with:
environment: production
azure-credentials: ${{ secrets.AZURE_CREDENTIALS }}

- name: Get pod name
id: pod-name
run: |
echo "value=$(kubectl get pod \
-n tra-production \
-l app=apply-for-qts-production-web \
-o jsonpath="{.items[0].metadata.name}")" >> $GITHUB_OUTPUT
- name: Export configuration data
run: |
kubectl exec \
-n tra-production \
${{ steps.pod-name.outputs.value }} \
-- sh -c "cd /app && /usr/local/bin/bundle exec rails configuration_sync:export[data.json]"
- name: Copy configuration data
run: |
kubectl cp \
-n tra-production \
${{ steps.pod-name.outputs.value }}:data.json \
data.json
- name: Upload configuration data
uses: actions/upload-artifact@v4
with:
name: data
path: data.json
retention-days: 3

import:
name: Export from production
runs-on: ubuntu-latest

strategy:
matrix:
environment: [development, test]

environment: ${{ matrix.environment }}

steps:
- uses: actions/checkout@v4

- uses: Azure/login@v2
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}

- uses: ./.github/actions/set-kubernetes-credentials
with:
environment: ${{ matrix.environment }}
azure-credentials: ${{ secrets.AZURE_CREDENTIALS }}

- name: Download configuration data
uses: actions/download-artifact@v4
with:
name: data

- name: Get pod name
id: pod-name
run: |
echo "value=$(kubectl get pod \
-n tra-${{ matrix.environment }} \
-l app=apply-for-qts-${{ matrix.environment }}-web \
-o jsonpath="{.items[0].metadata.name}")" >> $GITHUB_OUTPUT
- name: Copy configuration data
run: |
kubectl cp \
-n tra-${{ matrix.environment }} \
data.json \
${{ steps.pod-name.outputs.value }}:data.json
- name: Import configuration data
run: |
kubectl exec \
-n tra-${{ matrix.environment }} \
${{ steps.pod-name.outputs.value }} \
-- sh -c "cd /app && /usr/local/bin/bundle exec rails configuration_sync:import[data.json]"
84 changes: 84 additions & 0 deletions app/lib/configuration_sync/exporter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# frozen_string_literal: true

class ConfigurationSync::Exporter
include ServicePattern

def initialize(file:)
@file = file
end

def call
file.write(serialise.to_json)
end

private

attr_reader :file

def serialise
{
countries: Country.all.map { |country| serialise_country(country) },
english_language_providers:
EnglishLanguageProvider.all.map do |english_language_provider|
serialise_english_language_provider(english_language_provider)
end,
regions:
Region.includes(:country).map { |region| serialise_region(region) },
}
end

def serialise_country(country)
{
code: country.code,
eligibility_enabled: country.eligibility_enabled,
eligibility_skip_questions: country.eligibility_skip_questions,
other_information: country.other_information,
qualifications_information: country.qualifications_information,
sanction_information: country.sanction_information,
status_information: country.status_information,
subject_limited: country.subject_limited,
}
end

def serialise_english_language_provider(english_language_provider)
{
accepted_tests: english_language_provider.accepted_tests,
b2_level_requirement: english_language_provider.b2_level_requirement,
b2_level_requirement_prefix:
english_language_provider.b2_level_requirement_prefix,
check_url: english_language_provider.check_url,
name: english_language_provider.name,
reference_hint: english_language_provider.reference_hint,
reference_name: english_language_provider.reference_name,
url: english_language_provider.url,
}
end

def serialise_region(region)
{
application_form_skip_work_history:
region.application_form_skip_work_history,
country_code: region.country.code,
name: region.name,
other_information: region.other_information,
qualifications_information: region.qualifications_information,
reduced_evidence_accepted: region.reduced_evidence_accepted,
sanction_check: region.sanction_check,
sanction_information: region.sanction_information,
status_check: region.status_check,
status_information: region.status_information,
teaching_authority_address: region.teaching_authority_address,
teaching_authority_certificate: region.teaching_authority_certificate,
teaching_authority_emails: region.teaching_authority_emails,
teaching_authority_name: region.teaching_authority_name,
teaching_authority_online_checker_url:
region.teaching_authority_online_checker_url,
teaching_authority_provides_written_statement:
region.teaching_authority_provides_written_statement,
teaching_authority_requires_submission_email:
region.teaching_authority_requires_submission_email,
teaching_authority_websites: region.teaching_authority_websites,
written_statement_optional: region.written_statement_optional,
}
end
end
42 changes: 42 additions & 0 deletions app/lib/configuration_sync/importer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# frozen_string_literal: true

class ConfigurationSync::Importer
include ServicePattern

def initialize(file:)
@data = JSON.parse(file.read).deep_symbolize_keys
end

def call
data[:countries].each { |record| deserialise_country(record) }

data[:english_language_providers].each do |record|
deserialise_english_language_provider(record)
end

data[:regions].each { |record| deserialise_region(record) }
end

private

attr_reader :data

def deserialise_country(record)
Country.find_or_initialize_by(code: record[:code]).update!(
record.except(:code),
)
end

def deserialise_english_language_provider(record)
EnglishLanguageProvider.find_or_initialize_by(name: record[:name]).update!(
record.except(:name),
)
end

def deserialise_region(record)
country = Country.find_by!(code: record[:country_code])
Region.find_or_initialize_by(country:, name: record[:name]).update!(
record.except(:country_code, :name),
)
end
end
21 changes: 21 additions & 0 deletions lib/tasks/configuration_sync.rake
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# frozen_string_literal: true

namespace :configuration_sync do
desc "Export countries, regions and english language providers."
task :export, %i[filename] => :environment do |_task, args|
File.open(args[:filename], "w") do |file|
ConfigurationSync::Exporter.call(file:)
end
end

desc "Import countries, regions and english language providers."
task :import, %i[filename] => :environment do |_task, args|
if HostingEnvironment.production?
raise "This task cannot be run in production."
end

File.open(args[:filename], "r") do |file|
ConfigurationSync::Importer.call(file:)
end
end
end
81 changes: 81 additions & 0 deletions spec/lib/configuration_sync/exporter_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# frozen_string_literal: true

require "rails_helper"

RSpec.describe ConfigurationSync::Exporter do
subject(:call) { described_class.call(file:) }

let(:file) { StringIO.new }

let(:parsed_file) do
file.rewind
JSON.parse(file.read)
end

before do
country = create(:country, code: "FR")
create(:region, country:, name: "Region")
create(:english_language_provider, name: "Provider")
end

before { expect { call }.to_not raise_error }

it "exports the countries" do
expect(parsed_file).to include("countries")
expect(parsed_file["countries"].first).to eq(
{
"code" => "FR",
"eligibility_enabled" => true,
"eligibility_skip_questions" => false,
"other_information" => "",
"qualifications_information" => "",
"sanction_information" => "",
"status_information" => "",
"subject_limited" => false,
},
)
end

it "exports the english language providers" do
expect(parsed_file).to include("english_language_providers")
expect(parsed_file["english_language_providers"].first).to match(
{
"accepted_tests" => a_kind_of(String),
"b2_level_requirement" => a_kind_of(String),
"b2_level_requirement_prefix" => a_kind_of(String),
"check_url" => a_kind_of(String),
"name" => "Provider",
"reference_hint" => a_kind_of(String),
"reference_name" => a_kind_of(String),
"url" => a_kind_of(String),
},
)
end

it "exports the regions" do
expect(parsed_file).to include("regions")
expect(parsed_file["regions"].first).to eq(
{
"application_form_skip_work_history" => false,
"country_code" => "FR",
"name" => "Region",
"other_information" => "",
"qualifications_information" => "",
"reduced_evidence_accepted" => false,
"sanction_check" => "none",
"sanction_information" => "",
"status_check" => "none",
"status_information" => "",
"teaching_authority_address" => "",
"teaching_authority_certificate" => "",
"teaching_authority_emails" => [],
"teaching_authority_name" => "",
"teaching_authority_online_checker_url" => "",
"teaching_authority_provides_written_statement" => false,
"teaching_authority_requires_submission_email" => false,
"teaching_authority_websites" => [],
"written_statement_optional" => false,
},
)
end
end
Loading

0 comments on commit bd1dc1c

Please sign in to comment.