From f268a53059b9ceb46274a8d2cacb531a787f0bf2 Mon Sep 17 00:00:00 2001 From: Ralph Wort Date: Thu, 13 Jun 2024 10:17:31 +0100 Subject: [PATCH 1/3] Added /info page --- .circleci/config.yml | 2 ++ Dockerfile | 6 +++++ Gemfile.lock | 1 + app/controllers/info_controller.rb | 17 ++++++++++++++ config/routes.rb | 1 + deploy/development/deployment.yaml | 2 ++ deploy/production/deployment.yaml | 2 ++ deploy/staging/deployment.yaml | 2 ++ spec/controllers/info_controller_spec.rb | 30 ++++++++++++++++++++++++ 9 files changed, 63 insertions(+) create mode 100644 app/controllers/info_controller.rb create mode 100644 spec/controllers/info_controller_spec.rb diff --git a/.circleci/config.yml b/.circleci/config.yml index f092f0ea7..17015a739 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -72,6 +72,8 @@ references: --build-arg COMMIT_ID=${CIRCLE_SHA1} \ --build-arg BUILD_DATE=${BUILD_DATE} \ --build-arg BUILD_TAG=${CIRCLE_BRANCH} \ + --build-arg BUILD_NUMBER=${BUILD_DATE}.${CIRCLE_BUILD_NUM}.${CIRCLE_SHA1:0:6} \ + --build-arg GIT_BRANCH=${CIRCLE_BRANCH} \ -t app . # push_docker_image: &push_docker_image diff --git a/Dockerfile b/Dockerfile index 40527612b..56051f2ed 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,8 @@ FROM ruby:3.2.2-bullseye +ARG BUILD_NUMBER +ARG GIT_BRANCH + RUN \ set -ex \ && apt-get update \ @@ -62,6 +65,9 @@ RUN bundle update --bundler RUN bundle install --without development test --jobs 2 --retry 3 COPY . /app +ENV BUILD_NUMBER=${BUILD_NUMBER} +ENV GIT_BRANCH=${GIT_BRANCH} + RUN mkdir -p /home/appuser && \ useradd appuser -u 1001 --user-group --home /home/appuser && \ chown -R appuser:appuser /app && \ diff --git a/Gemfile.lock b/Gemfile.lock index b1a2393db..960d30efc 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -455,6 +455,7 @@ GEM PLATFORMS arm64-darwin-22 + arm64-darwin-23 x86_64-linux DEPENDENCIES diff --git a/app/controllers/info_controller.rb b/app/controllers/info_controller.rb new file mode 100644 index 000000000..12900c0b5 --- /dev/null +++ b/app/controllers/info_controller.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class InfoController < ApplicationController + def index + render json: { + git: { + branch: ENV['GIT_BRANCH'] + }, + build: { + artifact: 'prison-visits-public', + version: ENV['BUILD_NUMBER'], + name: 'prison-visits-public' + }, + productId: ENV['PRODUCT_ID'] + } + end +end diff --git a/config/routes.rb b/config/routes.rb index 9edf621af..14875c5f2 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -15,6 +15,7 @@ constraints format: 'json' do get 'ping', to: 'ping#index' get 'healthcheck', to: 'healthcheck#index' + get 'info', to: 'info#index' end constraints format: 'html' do diff --git a/deploy/development/deployment.yaml b/deploy/development/deployment.yaml index a39517c6d..2e58f33b6 100644 --- a/deploy/development/deployment.yaml +++ b/deploy/development/deployment.yaml @@ -50,6 +50,8 @@ spec: memory: "125Mi" cpu: "15m" env: + - name: PRODUCT_ID + value: "DPS031" - name: SECRET_KEY_BASE valueFrom: secretKeyRef: diff --git a/deploy/production/deployment.yaml b/deploy/production/deployment.yaml index 9a982bbef..0eaaa5569 100644 --- a/deploy/production/deployment.yaml +++ b/deploy/production/deployment.yaml @@ -50,6 +50,8 @@ spec: memory: "125Mi" cpu: "15m" env: + - name: PRODUCT_ID + value: "DPS031" - name: SECRET_KEY_BASE valueFrom: secretKeyRef: diff --git a/deploy/staging/deployment.yaml b/deploy/staging/deployment.yaml index 3597fcae9..057449305 100644 --- a/deploy/staging/deployment.yaml +++ b/deploy/staging/deployment.yaml @@ -50,6 +50,8 @@ spec: memory: "125Mi" cpu: "15m" env: + - name: PRODUCT_ID + value: "DPS031" - name: SECRET_KEY_BASE valueFrom: secretKeyRef: diff --git a/spec/controllers/info_controller_spec.rb b/spec/controllers/info_controller_spec.rb new file mode 100644 index 000000000..6090acb38 --- /dev/null +++ b/spec/controllers/info_controller_spec.rb @@ -0,0 +1,30 @@ +require 'rails_helper' + +RSpec.describe InfoController, type: :controller do + let(:parsed_body) { + JSON.parse(response.body) + } + + subject(:index_request) { get :index } + + context 'when everything is OK' do + before do + ENV['GIT_BRANCH'] = 'GIT_BRANCH' + ENV['BUILD_NUMBER'] = 'BUILD_NUMBER' + ENV['PRODUCT_ID'] = 'PRODUCT_ID' + end + + it { is_expected.to be_successful } + + it 'returns the healthcheck data as JSON' do + index_request + + expect(parsed_body).to eq( + 'build' => { "artifact" => "prison-visits-public", + "name" => "prison-visits-public", + "version" => "BUILD_NUMBER" }, "git" => { "branch" => "GIT_BRANCH" }, + "productId" => "PRODUCT_ID" + ) + end + end +end From 5d84beeb12d6e9e7000833f33232a6dd2e9f60aa Mon Sep 17 00:00:00 2001 From: Ralph Wort Date: Mon, 24 Jun 2024 13:44:40 +0100 Subject: [PATCH 2/3] add /health endpoint --- .circleci/config.yml | 1 + Dockerfile | 2 ++ app/controllers/health_controller.rb | 26 ++++++++++++++ app/services/healthcheck/nomis_api_check.rb | 21 +++++++++++ app/services/healthcheck/vsip_api_check.rb | 21 +++++++++++ app/services/nomis/client.rb | 4 +++ app/services/vsip/client.rb | 4 +++ config/application.rb | 2 ++ config/routes.rb | 1 + spec/controllers/health_controller_spec.rb | 40 +++++++++++++++++++++ spec/services/nomis/client_spec.rb | 6 ++++ spec/services/vsip/client_spec.rb | 6 ++++ 12 files changed, 134 insertions(+) create mode 100644 app/controllers/health_controller.rb create mode 100644 app/services/healthcheck/nomis_api_check.rb create mode 100644 app/services/healthcheck/vsip_api_check.rb create mode 100644 spec/controllers/health_controller_spec.rb diff --git a/.circleci/config.yml b/.circleci/config.yml index aa42794ad..a215ff11c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -77,6 +77,7 @@ references: --build-arg BUILD_TAG=${CIRCLE_BRANCH} \ --build-arg BUILD_NUMBER=${BUILD_DATE}.${CIRCLE_BUILD_NUM}.${CIRCLE_SHA1:0:6} \ --build-arg GIT_BRANCH=${CIRCLE_BRANCH} \ + --build-arg GIT_REF=${CIRCLE_SHA1} \ -t app . # push_docker_image: &push_docker_image diff --git a/Dockerfile b/Dockerfile index f7d059f56..187ff5d9b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,6 +2,7 @@ FROM ruby:3.2.2-bullseye ARG BUILD_NUMBER ARG GIT_BRANCH +ARG GIT_REF RUN \ set -ex \ @@ -68,6 +69,7 @@ COPY . /app ENV BUILD_NUMBER=${BUILD_NUMBER} ENV GIT_BRANCH=${GIT_BRANCH} +ENV GIT_REF=${GIT_REF} RUN mkdir -p /home/appuser && \ useradd appuser -u 1001 --user-group --home /home/appuser && \ diff --git a/app/controllers/health_controller.rb b/app/controllers/health_controller.rb new file mode 100644 index 000000000..d58048277 --- /dev/null +++ b/app/controllers/health_controller.rb @@ -0,0 +1,26 @@ +class HealthController < ApplicationController + def index + nomis_health = Healthcheck::NomisApiCheck.new('Nomis API healthcheck') + vsip_health = Healthcheck::VsipApiCheck.new('Vsip API healthcheck') + + render status:, json: { + status: nomis_health.ok? && vsip_health.ok? ? :UP : :DOWN, + components: { + nomis: { + status: nomis_health.ok? ? :UP : :DOWN, + detail: nomis_health.ok? ? :UP : nomis_health.report[:error] + }, + vsip: { + status: vsip_health.ok? ? :UP : :DOWN, + detail: vsip_health.ok? ? :UP : vsip_health.report[:error] + } + }, + uptime: Time.zone.now - Rails.configuration.booted_at, + build: { + buildNumber: ENV['BUILD_NUMBER'], + gitRef: ENV['GIT_REF'] + }, + version: ENV['BUILD_NUMBER'] + } + end +end diff --git a/app/services/healthcheck/nomis_api_check.rb b/app/services/healthcheck/nomis_api_check.rb new file mode 100644 index 000000000..b98cf85c7 --- /dev/null +++ b/app/services/healthcheck/nomis_api_check.rb @@ -0,0 +1,21 @@ +module Healthcheck + class NomisApiCheck + include CheckComponent + + def initialize(description) + build_report(description) do + { ok: healthy_pvb_connection } + end + end + + private + + def healthy_pvb_connection + client.healthcheck.status == 200 + end + + def client + Nomis::Client.new(Rails.configuration.prison_api_host) + end + end +end diff --git a/app/services/healthcheck/vsip_api_check.rb b/app/services/healthcheck/vsip_api_check.rb new file mode 100644 index 000000000..5dd9009ea --- /dev/null +++ b/app/services/healthcheck/vsip_api_check.rb @@ -0,0 +1,21 @@ +module Healthcheck + class VsipApiCheck + include CheckComponent + + def initialize(description) + build_report(description) do + { ok: healthy_pvb_connection } + end + end + + private + + def healthy_pvb_connection + client.healthcheck.status == 200 + end + + def client + Vsip::Client.new(Rails.configuration.vsip_host) + end + end +end diff --git a/app/services/nomis/client.rb b/app/services/nomis/client.rb index f343a31d2..cad68b5fe 100644 --- a/app/services/nomis/client.rb +++ b/app/services/nomis/client.rb @@ -44,6 +44,10 @@ def get(route, params = {}) request(:get, route, params, idempotent: true) end + def healthcheck + @connection.head(path: 'health') + end + private def request(method, route, params, idempotent:, options: {}) diff --git a/app/services/vsip/client.rb b/app/services/vsip/client.rb index 5b9c6eb92..65f9c9b5a 100644 --- a/app/services/vsip/client.rb +++ b/app/services/vsip/client.rb @@ -44,6 +44,10 @@ def get(route, params = {}) request(:get, route, params, idempotent: true) end + def healthcheck + @connection.head(path: 'health') + end + private def request(method, route, params, idempotent:, options: {}) diff --git a/config/application.rb b/config/application.rb index 1f51d2dda..f96ac4143 100644 --- a/config/application.rb +++ b/config/application.rb @@ -9,6 +9,7 @@ require_relative '../app/middleware/http_method_not_allowed' require_relative '../app/middleware/robots_tag' require 'ostruct' +require 'active_support' # require "action_mailer/railtie" # require "action_mailbox/engine" @@ -126,5 +127,6 @@ class Application < Rails::Application config.vsip_host = ENV['VSIP_HOST'] config.vsip_supported_prisons_retrieved = false config.use_vsip = ENV['USE_VSIP'] == 'true' + config.booted_at = Time.current end end diff --git a/config/routes.rb b/config/routes.rb index 0750a1867..6669351e0 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -16,6 +16,7 @@ get 'ping', to: 'ping#index' get 'healthcheck', to: 'healthcheck#index' get 'info', to: 'info#index' + get 'health', to: 'health#index' end constraints format: 'html' do diff --git a/spec/controllers/health_controller_spec.rb b/spec/controllers/health_controller_spec.rb new file mode 100644 index 000000000..a49b48605 --- /dev/null +++ b/spec/controllers/health_controller_spec.rb @@ -0,0 +1,40 @@ +require 'rails_helper' + +RSpec.describe HealthController, type: :controller do + let(:parsed_body) { + JSON.parse(response.body) + } + + subject(:index_request) { get :index } + + context 'when everything is OK' do + before do + allow_any_instance_of(Nomis::Client).to receive(:healthcheck).and_return(double(status: 200)) + allow_any_instance_of(Vsip::Client).to receive(:healthcheck).and_return(double(status: 200)) + end + + it { is_expected.to be_successful } + + it 'returns the healthcheck data as JSON' do + index_request + + expect(parsed_body['components']['nomis']['status']).to eq('UP') + expect(parsed_body['components']['vsip']['status']).to eq('UP') + expect(parsed_body['status']).to eq('UP') + end + end + + context 'when the healthcheck is not OK' do + before do + allow_any_instance_of(Nomis::Client).to receive(:healthcheck).and_return(double(status: 500)) + allow_any_instance_of(Vsip::Client).to receive(:healthcheck).and_return(double(status: 500)) + end + + it 'returns the healthcheck data as JSON' do + index_request + + expect(parsed_body["components"]).to eq({ "nomis" => { "detail" => nil, "status" => "DOWN" }, + "vsip" => { "detail" => nil, "status" => "DOWN" } }) + end + end +end diff --git a/spec/services/nomis/client_spec.rb b/spec/services/nomis/client_spec.rb index 7966fbf1c..4eea2f8b3 100644 --- a/spec/services/nomis/client_spec.rb +++ b/spec/services/nomis/client_spec.rb @@ -93,4 +93,10 @@ }.to raise_error(Nomis::APIError) end end + + describe 'healthcheck' do + it 'run healthcheck' do + expect(described_class.new(Rails.configuration.prison_api_host).healthcheck.body).to eq('') + end + end end diff --git a/spec/services/vsip/client_spec.rb b/spec/services/vsip/client_spec.rb index da73982c2..8c9a8a134 100644 --- a/spec/services/vsip/client_spec.rb +++ b/spec/services/vsip/client_spec.rb @@ -107,4 +107,10 @@ def body expect(described_class.new(Rails.configuration.vsip_host).send(:params_options, :method, :params)).to eq({ query: :params }) end end + + describe 'healthcheck' do + it 'run healthcheck' do + expect(described_class.new(Rails.configuration.vsip_host).healthcheck.body).to eq('') + end + end end From a3eecbac7058f09bdfee8d660b2c4e723a8a0045 Mon Sep 17 00:00:00 2001 From: Ralph Wort Date: Mon, 24 Jun 2024 16:03:44 +0100 Subject: [PATCH 3/3] fix intermittent test failures --- spec/controllers/health_controller_spec.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/controllers/health_controller_spec.rb b/spec/controllers/health_controller_spec.rb index a49b48605..7219c3206 100644 --- a/spec/controllers/health_controller_spec.rb +++ b/spec/controllers/health_controller_spec.rb @@ -9,8 +9,8 @@ context 'when everything is OK' do before do - allow_any_instance_of(Nomis::Client).to receive(:healthcheck).and_return(double(status: 200)) - allow_any_instance_of(Vsip::Client).to receive(:healthcheck).and_return(double(status: 200)) + allow_any_instance_of(Nomis::Client).to receive(:healthcheck).and_return(OpenStruct.new(status: 200)) + allow_any_instance_of(Vsip::Client).to receive(:healthcheck).and_return(OpenStruct.new(status: 200)) end it { is_expected.to be_successful } @@ -26,8 +26,8 @@ context 'when the healthcheck is not OK' do before do - allow_any_instance_of(Nomis::Client).to receive(:healthcheck).and_return(double(status: 500)) - allow_any_instance_of(Vsip::Client).to receive(:healthcheck).and_return(double(status: 500)) + allow_any_instance_of(Nomis::Client).to receive(:healthcheck).and_return(OpenStruct.new(status: 500)) + allow_any_instance_of(Vsip::Client).to receive(:healthcheck).and_return(OpenStruct.new(status: 500)) end it 'returns the healthcheck data as JSON' do