Skip to content

Commit b0f4106

Browse files
Merge pull request #310 from Rakshitha-D/githubWorkflows
#SBCOSS-409: GitHub workflows for code quality check and image publishing
2 parents 378acef + b77beb5 commit b0f4106

26 files changed

+2165
-2066
lines changed

.github/ghcr-publish.yml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
name: Build and Publish Docker Image
2+
3+
on:
4+
push:
5+
tags:
6+
- '*'
7+
8+
jobs:
9+
build-and-push:
10+
runs-on: ubuntu-latest
11+
12+
permissions:
13+
contents: read
14+
packages: write
15+
16+
steps:
17+
- name: Checkout repository
18+
uses: actions/checkout@v3
19+
20+
- name: Log in to GitHub Container Registry
21+
uses: docker/login-action@v3
22+
with:
23+
registry: ghcr.io
24+
username: ${{ github.actor }}
25+
password: ${{ secrets.GITHUB_TOKEN }}
26+
27+
- name: Get short commit hash
28+
id: vars
29+
run: echo "commit_hash=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
30+
31+
- name: Prepare Docker image name and tag
32+
run: |
33+
REPO_NAME_LOWERCASE=$(echo "${GITHUB_REPOSITORY}" | tr '[:upper:]' '[:lower:]')
34+
TAG_NAME=$(echo "${GITHUB_REF_NAME}" | tr '[:upper:]' '[:lower:]')
35+
BUILD_TAG="${TAG_NAME}_${{ steps.vars.outputs.commit_hash }}_${GITHUB_RUN_NUMBER}"
36+
37+
echo "IMAGE_NAME=ghcr.io/${REPO_NAME_LOWERCASE}" >> $GITHUB_ENV
38+
echo "IMAGE_TAG=${BUILD_TAG}" >> $GITHUB_ENV
39+
40+
- name: Build Docker image
41+
run: docker build -t $IMAGE_NAME:$IMAGE_TAG .
42+
43+
- name: Push Docker image
44+
run: docker push $IMAGE_NAME:$IMAGE_TAG

.github/pull_request.yml

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
name: Code Quality Checks
2+
3+
on:
4+
pull_request:
5+
branches:
6+
- '**'
7+
8+
jobs:
9+
code-quality:
10+
name: Run Code Quality Checks
11+
runs-on: ubuntu-latest
12+
13+
steps:
14+
- name: Checkout code
15+
uses: actions/checkout@v3
16+
with:
17+
submodules: recursive
18+
19+
- name: Set up Node.js
20+
uses: actions/setup-node@v3
21+
with:
22+
node-version: '22.15'
23+
24+
- name: Restore node_modules cache
25+
id: cache
26+
uses: actions/cache@v3
27+
with:
28+
path: src/node_modules
29+
key: ${{ runner.os }}-node-modules-${{ hashFiles('**/package-lock.json') }}
30+
restore-keys: |
31+
${{ runner.os }}-node-modules-
32+
33+
- name: Install dependencies
34+
run: npm ci
35+
working-directory: src
36+
37+
- name: Save node_modules cache
38+
if: steps.cache.outputs.cache-hit != 'true'
39+
uses: actions/cache@v3
40+
with:
41+
path: src/node_modules
42+
key: ${{ runner.os }}-node-modules-${{ hashFiles('**/package-lock.json') }}
43+
44+
- name: Run lint
45+
run: npm run lint
46+
working-directory: src
47+
48+
- name: Run tests
49+
run: npm test
50+
working-directory: src
51+
52+
- name: Run coverage
53+
run: npm run coverage
54+
working-directory: src

Dockerfile

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
FROM node:22.15-slim
22
RUN apt-get update && apt-get install -y git
3-
COPY .git /opt/content/.git
4-
COPY src /opt/content/
3+
COPY . /opt/content/
54
WORKDIR /opt/content/
65
RUN git config --global --add safe.directory /opt/content
7-
RUN git submodule update --init --recursive
8-
RUN npm install --unsafe-perm --production
6+
RUN git submodule init && \
7+
git submodule update
8+
RUN cd src && npm install --unsafe-perm --production
99

1010
FROM node:22.15-slim
1111

1212
RUN useradd -m sunbird
1313
COPY --from=0 --chown=sunbird /opt/content /home/sunbird/mw/content
14-
WORKDIR /home/sunbird/mw/content/
15-
CMD ["node", "app.js", "&"]
14+
WORKDIR /home/sunbird/mw/content/src
15+
CMD ["node", "app.js", "&"]

README.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,50 @@ The code in this repository is licensed under MIT unless otherwise noted. Please
4747
* Run "npm run test" to run test cases
4848
* Run "npm run coverage" to run test cases with coverage report
4949

50+
## Code Quality
51+
52+
The project maintains code quality through automated checks that run on every pull request:
53+
54+
1. **Linting**
55+
- ESLint for code style and quality
56+
- Command: `npm run lint`
57+
58+
2. **Dependencies**
59+
- Uses `npm ci` for deterministic installations
60+
- GitHub Actions cache for faster builds
61+
62+
3. **Code Formatting**
63+
- Ensures consistent code formatting
64+
- Can be automatically fixed using `npm run lint:fix`
65+
66+
These checks ensure consistent code style and secure dependency management.
67+
68+
## Container Image Publishing
69+
70+
This repository uses GitHub Actions to automatically build and publish Docker container images to GitHub Container Registry (GHCR) whenever a new tag is pushed to the repository.
71+
72+
### Build and Publish Workflow
73+
74+
The workflow is triggered on:
75+
- creation of any tag
76+
77+
Key features of the workflow:
78+
1. Automatically builds Docker images
79+
2. Tags images with a combination of:
80+
- The tag name (lowercased)
81+
- Short commit hash
82+
- GitHub run number
83+
3. Publishes images to `ghcr.io` using the repository name
84+
4. Uses GitHub Actions for secure authentication to GHCR
85+
86+
### Image Naming Convention
87+
The Docker images follow this naming convention:
88+
- Repository: `ghcr.io/${OWNER_NAME}/${REPO_NAME_LOWERCASE}`
89+
- Tag: `${TAG_NAME}_${COMMIT_HASH}_${RUN_NUMBER}`
90+
91+
For example, if you push a tag `v1.0.0` on commit `abc123`, the resulting image would be:
92+
```
93+
ghcr.io/sunbird-knowlg/knowledge-mw-service:v1.0.0_abc123_1
94+
```
95+
5096

src/helpers/configHelper.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ var orgDataHelper = require('./orgHelper')
1111
* data asynchronously and return back a promise
1212
* @returns promise
1313
*/
14-
function getAllChannelsFromAPI() {
14+
function getAllChannelsFromAPI () {
1515
return new Promise(function (resolve, reject) {
1616
var limit = 200
1717
var offset = 0

src/helpers/licenseHelper.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ function includeLicenseDetails (req, res, cb) {
1919
let contents = inputContentIsArray ? res.result.content : [res.result.content]
2020
if (_.size(fieldsToPopulate) && _.size(contents)) {
2121
populateLicenseDetailsByName(contents, fieldsToPopulate, function
22-
(err, contentWithLicenseDetails) {
22+
(err, contentWithLicenseDetails) {
2323
if (!err) {
2424
res.result.content = inputContentIsArray ? contentWithLicenseDetails : contentWithLicenseDetails[0]
2525
}

src/helpers/orgHelper.js

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ var async = require('async')
1414
* @param requestObj js object which contains the search request with filters,offset,limit,query etc
1515
* @param cb callback after success or error
1616
*/
17-
function getRootOrgs(requestObj, cb, noExitOnError) {
17+
function getRootOrgs (requestObj, cb, noExitOnError) {
1818
logger.debug({ msg: 'getRootOrgs() called', additionalInfo: { requestObj } })
1919
contentProvider.getAllRootOrgs(requestObj, (err, res) => {
2020
if (!err) {
@@ -43,7 +43,7 @@ function getRootOrgs(requestObj, cb, noExitOnError) {
4343
inputdata is array of contents that needs org data
4444
* @param CBW callback after success or error
4545
*/
46-
function getRootOrgsFromCache(orgfetchquery, tryfromcache, inputdata, cb) {
46+
function getRootOrgsFromCache (orgfetchquery, tryfromcache, inputdata, cb) {
4747
async.waterfall([
4848
function (CBW) {
4949
if (tryfromcache) {
@@ -82,7 +82,7 @@ function getRootOrgsFromCache(orgfetchquery, tryfromcache, inputdata, cb) {
8282
])
8383
}
8484

85-
function insertDataToCache(cacheinputdata) {
85+
function insertDataToCache (cacheinputdata) {
8686
cacheManager.mset({ data: cacheinputdata, ttl: configData.orgCacheExpiryTime }, function (err, data) {
8787
if (err) {
8888
logger.error({ msg: 'Caching allRootOrgs data failed', err, additionalInfo: { data: cacheinputdata } })
@@ -97,7 +97,7 @@ function insertDataToCache(cacheinputdata) {
9797
* @param inputdata is array of objects, it might be content or course
9898
* @param cb callback after success or error
9999
*/
100-
function populateOrgDetailsByHasTag(contents, inputfields, cb) {
100+
function populateOrgDetailsByHasTag (contents, inputfields, cb) {
101101
var orgDetails = []
102102
var orgFetchQuery = {
103103
'request': {
@@ -168,7 +168,7 @@ function populateOrgDetailsByHasTag(contents, inputfields, cb) {
168168
* @param inputdata is req object and res object
169169
* @param cb there will be no error callback , always returns success
170170
*/
171-
function includeOrgDetails(req, res, cb) {
171+
function includeOrgDetails (req, res, cb) {
172172
if (_.get(req, 'query.orgdetails') && _.get(res, 'result.content')) {
173173
var inputfields = req.query.orgdetails.split(',')
174174
var fieldsToPopulate = configData.orgfieldsAllowedToSend.filter(eachfield => inputfields.includes(eachfield))
@@ -177,7 +177,7 @@ function includeOrgDetails(req, res, cb) {
177177
var contents = inputContentIsArray ? res.result.content : [res.result.content]
178178
if (_.size(fieldsToPopulate) && _.size(contents)) {
179179
populateOrgDetailsByHasTag(contents, fieldsToPopulate, function
180-
(err, contentwithorgdetails) {
180+
(err, contentwithorgdetails) {
181181
if (!err) {
182182
res.result.content = inputContentIsArray ? contentwithorgdetails : contentwithorgdetails[0]
183183
}
@@ -192,7 +192,7 @@ function includeOrgDetails(req, res, cb) {
192192
}
193193

194194
// prepares the set data for inserting in cache
195-
function prepareCacheDataToInsert(data) {
195+
function prepareCacheDataToInsert (data) {
196196
var cacheKeyValuePairs = []
197197
_.forEach(data, function (eachdata) {
198198
if (eachdata.hashTagId) {
@@ -205,7 +205,7 @@ function prepareCacheDataToInsert(data) {
205205
}
206206

207207
// prepares the get data for fetching from cache
208-
function getKeyNames(data) {
208+
function getKeyNames (data) {
209209
var keyNames = []
210210
_.forEach(data, function (eachdata) {
211211
if (eachdata.channel) {

src/service/conceptService.js

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ var responseCode = messageUtils.RESPONSE_CODE
2121
* @param {Object} req
2222
* @param {Object} response
2323
*/
24-
function getDomainsAPI(req, response) {
24+
function getDomainsAPI (req, response) {
2525
logger.debug({ msg: 'conceptService.getDomainAPI() called' }, req)
2626
var data = {}
2727
var rspObj = req.rspObj
@@ -67,7 +67,7 @@ function getDomainsAPI(req, response) {
6767
* @param {Object} req
6868
* @param {Object} response
6969
*/
70-
function getDomainByIDAPI(req, response) {
70+
function getDomainByIDAPI (req, response) {
7171
logger.debug({ msg: 'conceptService.getDomainByIDAPI() called' }, req)
7272
var data = {}
7373
var rspObj = req.rspObj
@@ -130,7 +130,7 @@ function getDomainByIDAPI(req, response) {
130130
* @param {Object} req
131131
* @param {Object} response
132132
*/
133-
function getObjectTypesAPI(req, response) {
133+
function getObjectTypesAPI (req, response) {
134134
logger.debug({ msg: 'conceptService.getObjectTypesAPI() called' }, req)
135135
var data = {}
136136
var rspObj = req.rspObj
@@ -192,7 +192,7 @@ function getObjectTypesAPI(req, response) {
192192
* @param {Object} req
193193
* @param {Object} response
194194
*/
195-
function getObjectTypeByIDAPI(req, response) {
195+
function getObjectTypeByIDAPI (req, response) {
196196
logger.debug({ msg: 'conceptService.getObjectTypeByIDAPI() called' }, req)
197197
var data = {}
198198
var rspObj = req.rspObj
@@ -257,7 +257,7 @@ function getObjectTypeByIDAPI(req, response) {
257257
* @param {Object} req
258258
* @param {Object} response
259259
*/
260-
function getConceptByIdAPI(req, response) {
260+
function getConceptByIdAPI (req, response) {
261261
logger.debug({ msg: 'conceptService.getConceptByIdAPI() called' }, req)
262262
var data = {}
263263
var rspObj = req.rspObj
@@ -319,7 +319,7 @@ function getConceptByIdAPI(req, response) {
319319
* @param {Object} req
320320
* @param {Object} response
321321
*/
322-
function searchObjectTypeAPI(req, response) {
322+
function searchObjectTypeAPI (req, response) {
323323
logger.debug({ msg: 'conceptService.searchObjectTypeAPI() called' }, req)
324324
var rspObj = req.rspObj
325325
var data = req.body
@@ -387,7 +387,7 @@ function searchObjectTypeAPI(req, response) {
387387
* @param {Object} req
388388
* @param {Object} response
389389
*/
390-
function createObjectTypeAPI(req, response) {
390+
function createObjectTypeAPI (req, response) {
391391
var rspObj = req.rspObj
392392
logger.debug({ msg: 'conceptService.createObjectTypeAPI() called' }, req)
393393
var data = req.body
@@ -454,7 +454,7 @@ function createObjectTypeAPI(req, response) {
454454
* @param {Object} req
455455
* @param {Object} response
456456
*/
457-
function updateObjectTypeAPI(req, response) {
457+
function updateObjectTypeAPI (req, response) {
458458
logger.debug({ msg: 'conceptService.updateObjectTypeAPI() called' }, req)
459459
var rspObj = req.rspObj
460460
var data = req.body
@@ -523,7 +523,7 @@ function updateObjectTypeAPI(req, response) {
523523
* @param {Object} req
524524
* @param {Object} response
525525
*/
526-
function retireObjectTypeAPI(req, response) {
526+
function retireObjectTypeAPI (req, response) {
527527
logger.debug({ msg: 'conceptService.retireObjectTypeAPI() called' }, req)
528528
var rspObj = req.rspObj
529529
var data = req.body
@@ -590,7 +590,7 @@ function retireObjectTypeAPI(req, response) {
590590
* @param {Object} req
591591
* @param {Object} response
592592
*/
593-
function listTermsAPI(req, response) {
593+
function listTermsAPI (req, response) {
594594
logger.debug({ msg: 'conceptService.listTermsAPI() called' }, req)
595595
var rspObj = req.rspObj
596596
async.waterfall([
@@ -616,7 +616,7 @@ function listTermsAPI(req, response) {
616616
* @param {Object} req
617617
* @param {Object} response
618618
*/
619-
function listResourceBundlesAPI(req, response) {
619+
function listResourceBundlesAPI (req, response) {
620620
logger.debug({ msg: 'conceptService.listResourceBundlesAPI() called' }, req)
621621
var rspObj = req.rspObj
622622
async.waterfall([
@@ -642,7 +642,7 @@ function listResourceBundlesAPI(req, response) {
642642
* @param {Object} req
643643
* @param {Object} response
644644
*/
645-
function listOrdinalsAPI(req, response) {
645+
function listOrdinalsAPI (req, response) {
646646
logger.debug({ msg: 'conceptService.listOrdinalsAPI() called' }, req)
647647
var rspObj = req.rspObj
648648
async.waterfall([

src/service/contentService.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1750,8 +1750,8 @@ function copyContentAPI (req, response) {
17501750
data.contentId = req.params.contentId
17511751
var rspObj = req.rspObj
17521752
var query = {}
1753-
if (req.query){
1754-
query = req.query;
1753+
if (req.query) {
1754+
query = req.query
17551755
}
17561756

17571757
logger.debug({
@@ -1908,7 +1908,7 @@ function searchPluginsAPI (req, response, objectType) {
19081908
function validateContentLock (req, response) {
19091909
var rspObj = req.rspObj
19101910
var userId = req.get('x-authenticated-userid')
1911-
var isRootOrgAdmin = lodash.has(req.body.request, "isRootOrgAdmin") ? req.body.request.isRootOrgAdmin : false
1911+
var isRootOrgAdmin = lodash.has(req.body.request, 'isRootOrgAdmin') ? req.body.request.isRootOrgAdmin : false
19121912
logger.debug({ msg: 'contentService.validateContentLock() called', additionalInfo: { rspObj } }, req)
19131913
var qs = {
19141914
mode: 'edit'

0 commit comments

Comments
 (0)