From abd9d66cc25224a316b55ce8a7f53d177554e926 Mon Sep 17 00:00:00 2001 From: Robin Kristiansen <1232179+nibrobb@users.noreply.github.com> Date: Tue, 7 May 2024 00:02:26 +0200 Subject: [PATCH] Ready. Set. GO! --- .github/workflows/docker-image.yml | 15 ++++ .gitignore | 3 + Dockerfile | 6 ++ README.md | 48 +++++++++++ modules-lock.json | 22 +++++ modules.json | 21 +++++ resources/eurofxref.csv | 2 + src/microservice.cbl | 124 +++++++++++++++++++++++++++++ tests/microservice-test.cbl | 28 +++++++ 9 files changed, 269 insertions(+) create mode 100644 .github/workflows/docker-image.yml create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 modules-lock.json create mode 100644 modules.json create mode 100644 resources/eurofxref.csv create mode 100644 src/microservice.cbl create mode 100644 tests/microservice-test.cbl diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml new file mode 100644 index 0000000..188710f --- /dev/null +++ b/.github/workflows/docker-image.yml @@ -0,0 +1,15 @@ +name: Docker Image CLI + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Build the Docker image + run: docker build . --file Dockerfile --tag cobol-ci-cd:$(date +%s) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..eb38a01 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.vscode +microservice +modules/ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..a4668e7 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,6 @@ +FROM olegkunitsyn/gnucobol:2.2 +RUN mkdir -p /microservice +WORKDIR /microservice +COPY . . +EXPOSE 8000 +RUN cobc -x -debug modules/gcblunit/gcblunit.cbl tests/* --job='microservice-test' diff --git a/README.md b/README.md new file mode 100644 index 0000000..ea4a823 --- /dev/null +++ b/README.md @@ -0,0 +1,48 @@ +# COBOL + CI/CD = ❤️ + +## Quick start + +Get the latest exchange rates from the [EBC](https://www.ecb.europa.eu/stats/policy_and_exchange_rates/euro_reference_exchange_rates/html/index.en.html) and put them in resources + +```bash +mkdir -p resources/ && wget -O - https://www.ecb.europa.eu/stats/eurofxref/eurofxref.zip?... | unzip -d resources/ - +``` + + +## Install the neccecary modules +```bash +mkdir -p modules + +npm install -g cobolget + +cobolget init + +cobolget add core-network + +cobolget add core-string + +cobolget add --debug gcblunit + +cobolget update + +cobolget -t bca12d6c4efed0627c87f2e576b72bdb5ab88e34 install +``` + +## Docker - build the image +```bash +docker build --tag microservice . +``` + +## Run the container +```bash +docker run -d -i --name microservice -p 8000:8000 microservice +docker exec -i microservice cobc -j -x src/microservice.cbl +``` + +## Stop the container +```bash +docker rm --force microservice +``` + + +Source: https://medium.com/swlh/modern-cobol-microservice-tutorial-7d7d738f0b00 \ No newline at end of file diff --git a/modules-lock.json b/modules-lock.json new file mode 100644 index 0000000..fde0aa7 --- /dev/null +++ b/modules-lock.json @@ -0,0 +1,22 @@ +{ + "gcblunit": { + "name": "gcblunit", + "version": "1.22.6", + "debug": true + }, + "core-network": { + "name": "core-network", + "version": "3.4.5", + "debug": false + }, + "core-string": { + "name": "core-string", + "version": "3.11.6", + "debug": false + }, + "core-datetime": { + "name": "core-datetime", + "version": "3.0.6", + "debug": false + } +} \ No newline at end of file diff --git a/modules.json b/modules.json new file mode 100644 index 0000000..6d402eb --- /dev/null +++ b/modules.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://cobolget.com/schema.json", + "name": "package-name", + "description": "Microservice in COBOL", + "modules": [], + "dialect": "gnucobol", + "licenses": [ + "MIT" + ], + "authors": [ + "Robin Kristiansen" + ], + "dependencies": { + "core-network": "*", + "core-string": "*" + }, + "dependencies-debug": { + "gcblunit": "*" + }, + "scripts": {} +} \ No newline at end of file diff --git a/resources/eurofxref.csv b/resources/eurofxref.csv new file mode 100644 index 0000000..bd906c0 --- /dev/null +++ b/resources/eurofxref.csv @@ -0,0 +1,2 @@ +Date, USD, JPY, BGN, CZK, DKK, GBP, HUF, PLN, RON, SEK, CHF, ISK, NOK, TRY, AUD, BRL, CAD, CNY, HKD, IDR, ILS, INR, KRW, MXN, MYR, NZD, PHP, SGD, THB, ZAR, +06 May 2024, 1.0776, 165.70, 1.9558, 24.995, 7.4589, 0.85660, 389.38, 4.3185, 4.9688, 11.6250, 0.9754, 150.30, 11.6663, 34.7747, 1.6248, 5.4683, 1.4731, 7.7662, 8.4223, 17289.39, 4.0322, 89.9875, 1460.00, 18.2346, 5.1067, 1.7891, 61.660, 1.4548, 39.618, 19.8369, diff --git a/src/microservice.cbl b/src/microservice.cbl new file mode 100644 index 0000000..a4bff0e --- /dev/null +++ b/src/microservice.cbl @@ -0,0 +1,124 @@ + >>SOURCE FORMAT FREE +identification division. +program-id. microservice. +environment division. +configuration section. +repository. + function csv-ecb-rates + function all intrinsic. +input-output section. +file-control. + select file-csv assign to "resources/eurofxref.csv" + organization is sequential + file status is file-status. +data division. +file section. +fd file-csv. + 01 csv-content pic x(1024). +working-storage section. + 78 SYSLOG-FACILITY-USER value 8. + 78 SYSLOG-SEVERITY-ERRROR value 3. + 01 file-status pic x(2). + 88 file-exists value "00". + 01 dataset external. + 05 dataset-ptr usage pointer. +procedure division. + *> read CSV file into csv-content + open input file-csv. + if not file-exists + display "Error reading file" upon syserr + stop run + end-if. + perform until exit + read file-csv at end exit perform end-read + end-perform. + close file-csv. + + *> convert csv-content to the list of key-value pairs + move csv-ecb-rates(csv-content) to dataset. + + *> start HTTP server with http-handler callback + call "receive-tcp" using "localhost", 8000, 0, address of entry "http-handler". +end program microservice. + +identification division. +program-id. http-handler. +environment division. +configuration section. +repository. function all intrinsic. +data division. +working-storage section. + 78 CRLF value x"0D" & x"0A". + 78 HTTP-OK value "200 OK". + 78 HTTP-NOT-FOUND value "404 Not Found". + 01 dataset external. + 05 dataset-ptr usage pointer. + 01 exchange-rates based. + 05 filer occurs 64 times indexed by idx. + 10 rate-currency pic x(3). + 10 rate-value pic 9(7)V9(8). + 01 request-method pic x(3). + 88 http-get value "GET". + 01 request-path. + 05 filler pic x value "/". + 05 get-currency pic x(3). + 05 filler pic x value "/". + 05 get-amount pic x(32). + 01 response. + 05 response-header. + 10 filler pic x(9) value "HTTP/1.1" & SPACE. + 10 response-status pic x(13). + 10 filler pic x(2) value CRLF. + 10 filler pic x(32) value "Content-Type: application/json" & CRLF. + 10 filler pic x(16) value "Content-Length: ". + 10 response-content-length pic 9(2). + 10 filler pic x(2) value CRLF. + 10 filler pic x(2) value CRLF. + 05 response-content. + 10 filler pic x(11) value '{"amount": '. + 10 eur-amount pic z(14)9.9(16). + 10 filler pic x(1) value '}'. +linkage section. + 01 l-buffer pic x any length. + 01 l-length usage binary-int unsigned. +procedure division using l-buffer, l-length returning omitted. + *> initialize exchange rates + set address of exchange-rates to dataset-ptr. + + *> parse request as "GET //" + unstring l-buffer(1:l-length) delimited by all SPACES into + request-method, request-path. + if not http-get + perform response-NOK + end-if. + + *> find currency and calculate eur-amount + perform varying idx from 1 by 1 until idx > 64 + if rate-currency(idx) = get-currency + compute eur-amount = numval(get-amount) / rate-value(idx) + on size error perform response-NOK + end-compute + perform response-OK + end-if + end-perform. + + *> or nothing + perform response-NOK. + +response-OK section. + move HTTP-OK to response-status. + move byte-length(response-content) to response-content-length. + perform response-any. + +response-NOK section. + move HTTP-NOT-FOUND to response-status. + move 0 to response-content-length. + perform response-any. + +response-any section. + string response delimited by size into l-buffer. + compute l-length = byte-length(response-header) + response-content-length. + goback. +end program http-handler. + +copy "modules/modules.cpy". diff --git a/tests/microservice-test.cbl b/tests/microservice-test.cbl new file mode 100644 index 0000000..49b4414 --- /dev/null +++ b/tests/microservice-test.cbl @@ -0,0 +1,28 @@ + >>SOURCE FORMAT FREE +identification division. +program-id. microservice-test. +environment division. +configuration section. +repository. + function csv-ecb-rates + function substr-pos + function all intrinsic. +data division. +working-storage section. + 01 dataset external. + 05 dataset-ptr usage pointer. + 01 buffer pic x(1024) value "GET /USD/1 HTTP1.1". +procedure division. + move csv-ecb-rates(concatenate("Date, USD, " x"0a" "06 May 2024, 1.0776, ")) to dataset. + call "http-handler" using buffer, byte-length(buffer). + perform http-handler-test. + goback. + +http-handler-test section. + call "assert-notequals" using 0, substr-pos(buffer, "HTTP/1.1 200 OK"). + call "assert-notequals" using 0, substr-pos(buffer, "Content-Type: application/json"). + call "assert-notequals" using 0, substr-pos(buffer, "Content-Length: 44"). + call "assert-equals" using 104, substr-pos(buffer, "0.9279881217520415"). +end program microservice-test. + +copy "src/microservice.cbl".