Skip to content

Commit

Permalink
feat: Release (#87)
Browse files Browse the repository at this point in the history
* fix: update semantic release (#71)

* fix: update semantic release (#72)

* chore(release): 1.0.2-beta.1

## [1.0.2-beta.1](v1.0.1...v1.0.2-beta.1) (2024-04-04)

### Bug Fixes

* change version ([5f6e625](5f6e625))
* update actions in workflows ([#70](#70)) ([b5b9c0c](b5b9c0c))
* update dependencies and node ([#68](#68)) ([f35c614](f35c614))
* update node to 20.12.0 ([#69](#69)) ([d4b550d](d4b550d))
* update semantic release ([#71](#71)) ([38fcf6d](38fcf6d))
* update semantic release ([#72](#72)) ([316f060](316f060))

* chore(release): 1.0.2-beta.2

## [1.0.2-beta.2](v1.0.2-beta.1...v1.0.2-beta.2) (2024-04-04)

### Bug Fixes

* test beta release ([#73](#73)) ([b63fe66](b63fe66))

* fix: add error handling (#77)

* fix: add error handling

* chore: update node to 20.12

* chore(release): 1.0.2-beta.3

## [1.0.2-beta.3](v1.0.2-beta.2...v1.0.2-beta.3) (2024-04-10)

### Bug Fixes

* add error handling ([#77](#77)) ([fa096c7](fa096c7))

* chore: bump pymongo to v4.6.3 (#78)

* fix: docker release tag (#79)

* fix: docker release tag (#80)

* chore(release): 1.0.2-beta.4

## [1.0.2-beta.4](v1.0.2-beta.3...v1.0.2-beta.4) (2024-04-18)

### Bug Fixes

* docker release tag ([#79](#79)) ([3310fef](3310fef))
* docker release tag ([#80](#80)) ([bcf4ecf](bcf4ecf))

* chore(release): 1.0.3-beta.1

## [1.0.3-beta.1](v1.0.2...v1.0.3-beta.1) (2024-05-23)

### Bug Fixes

* add error handling ([#77](#77)) ([1f6f60b](1f6f60b))
* docker release tag ([#79](#79)) ([2d3611f](2d3611f))
* docker release tag ([#80](#80)) ([fce9a78](fce9a78))
* update semantic release ([#71](#71)) ([d0eefe3](d0eefe3))
* update semantic release ([#72](#72)) ([88aa126](88aa126))

* build(deps): bump flask-cors from 3.0.10 to 4.0.1 in /backend (#86)

Bumps [flask-cors](https://github.com/corydolphin/flask-cors) from 3.0.10 to 4.0.1.
- [Release notes](https://github.com/corydolphin/flask-cors/releases)
- [Changelog](https://github.com/corydolphin/flask-cors/blob/main/CHANGELOG.md)
- [Commits](corydolphin/flask-cors@3.0.10...4.0.1)

---
updated-dependencies:
- dependency-name: flask-cors
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* build(deps): bump ejs from 3.1.9 to 3.1.10 in /frontend (#85)

Bumps [ejs](https://github.com/mde/ejs) from 3.1.9 to 3.1.10.
- [Release notes](https://github.com/mde/ejs/releases)
- [Commits](mde/ejs@v3.1.9...v3.1.10)

---
updated-dependencies:
- dependency-name: ejs
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* build(deps): bump jinja2 from 3.1.3 to 3.1.4 in /backend (#84)

Bumps [jinja2](https://github.com/pallets/jinja) from 3.1.3 to 3.1.4.
- [Release notes](https://github.com/pallets/jinja/releases)
- [Changelog](https://github.com/pallets/jinja/blob/main/CHANGES.rst)
- [Commits](pallets/jinja@3.1.3...3.1.4)

---
updated-dependencies:
- dependency-name: jinja2
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* build(deps): bump werkzeug from 2.3.8 to 3.0.3 in /backend (#83)

Bumps [werkzeug](https://github.com/pallets/werkzeug) from 2.3.8 to 3.0.3.
- [Release notes](https://github.com/pallets/werkzeug/releases)
- [Changelog](https://github.com/pallets/werkzeug/blob/main/CHANGES.rst)
- [Commits](pallets/werkzeug@2.3.8...3.0.3)

---
updated-dependencies:
- dependency-name: werkzeug
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* build(deps): bump pymongo from 4.1.1 to 4.6.3 in /backend (#81)

Bumps [pymongo](https://github.com/mongodb/pymongo) from 4.1.1 to 4.6.3.
- [Commits](https://github.com/mongodb/pymongo/commits)

---
updated-dependencies:
- dependency-name: pymongo
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* feat: change workflow after clicking 'Apply changes' button (#82)

* chore(release): 1.1.0-beta.1

# [1.1.0-beta.1](v1.0.3-beta.1...v1.1.0-beta.1) (2024-05-23)

### Features

* change workflow after clicking 'Apply changes' button ([#82](#82)) ([99f85d0](99f85d0))

* chore: update CHANGELOG (#88)

---------

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: semantic-release-bot <[email protected]>
Co-authored-by: ajasnosz <[email protected]>
Co-authored-by: srv-rr-github-token <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
  • Loading branch information
5 people authored May 27, 2024
1 parent 448aff8 commit c1fcd29
Show file tree
Hide file tree
Showing 19 changed files with 242 additions and 105 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci-build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ jobs:
cache-to: type=inline
- uses: actions/[email protected]
with:
node-version: "20.12.0"
node-version: "20.12"

build-backend:
name: build-backend
Expand Down Expand Up @@ -128,4 +128,4 @@ jobs:
cache-to: type=inline
- uses: actions/[email protected]
with:
node-version: "20.12.0"
node-version: "20.12"
2 changes: 1 addition & 1 deletion .github/workflows/ci-main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ jobs:
strategy:
matrix:
node-version:
- 20.12.0
- 20.12
steps:
- uses: actions/checkout@v4
- name: Set Node.js ${{ matrix.node-version }}
Expand Down
19 changes: 11 additions & 8 deletions .github/workflows/ci-release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ on:
- "main"
- "develop"
- "next"
tags-ignore:
tags:
- "v*"

jobs:
Expand Down Expand Up @@ -61,6 +61,7 @@ jobs:
type=semver,pattern={{major}}
type=semver,pattern={{version}}
type=ref,event=branch
type=ref,event=pr
- name: Build and push action - frontend
id: docker_action_build_frontend
uses: docker/build-push-action@v5
Expand All @@ -73,7 +74,7 @@ jobs:
cache-to: type=inline
- uses: actions/[email protected]
with:
node-version: "20.12.0"
node-version: "20.12"

build-backend:
name: build-backend
Expand Down Expand Up @@ -111,6 +112,7 @@ jobs:
type=semver,pattern={{major}}
type=semver,pattern={{version}}
type=ref,event=branch
type=ref,event=pr
- name: Build and push action - backend
id: docker_action_build_backend
uses: docker/build-push-action@v5
Expand All @@ -123,7 +125,7 @@ jobs:
cache-to: type=inline
- uses: actions/[email protected]
with:
node-version: "20.12.0"
node-version: "20.12"
release:
name: Release
needs: [build-frontend, build-backend]
Expand All @@ -138,15 +140,16 @@ jobs:
persist-credentials: false
- uses: actions/[email protected]
with:
node-version: "20.12.0"
node-version: "20.12"
- name: Semantic Release
id: version
uses: cycjimmy/semantic-release-action@v4.1.0
uses: splunk/semantic-release-action@v1.3.4
with:
semantic_version: 21.1.1
git_committer_name: ${{ secrets.SA_GH_USER_NAME }}
git_committer_email: ${{ secrets.SA_GH_USER_EMAIL }}
gpg_private_key: ${{ secrets.SA_GPG_PRIVATE_KEY }}
passphrase: ${{ secrets.SA_GPG_PASSPHRASE }}
extra_plugins: |
@semantic-release/exec
@semantic-release/git
[email protected]
env:
GITHUB_TOKEN: ${{ secrets.GH_TOKEN_ADMIN }}
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## 1.1.0

### Changed
- add error handling for apply changes action
- after clicking 'Apply changes' workflow is initially attempting to create new job immediately, if it is impossible, schedule it for the future

## [1.0.2]

### Changed
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ source venv/bin/activate
Next step is to install required `python3` packages:

```shell
cd backend
pip3 install -r requirements.txt
```

Expand All @@ -80,7 +81,6 @@ docker run --rm -d -p 27017:27017 --name example-mongo mongo:4.4.6
To start backend service run:

```yaml
cd backend
flask run
```

Expand Down
2 changes: 1 addition & 1 deletion backend/SC4SNMP_UI_backend/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

load_dotenv()

__version__ = "1.0.2"
__version__ = "1.1.0-beta.1"

MONGO_URI = os.getenv("MONGO_URI")
mongo_client = MongoClient(MONGO_URI)
Expand Down
6 changes: 4 additions & 2 deletions backend/SC4SNMP_UI_backend/apply_changes/apply_changes.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,13 @@ def __init__(self) -> None:
mongo_config_collection.update_one(
{
"previous_job_start_time": {"$exists": True},
"currently_scheduled": {"$exists": True}}
"currently_scheduled": {"$exists": True},
"task_id": {"$exists": True}}
,{
"$set":{
"previous_job_start_time": None,
"currently_scheduled": False
"currently_scheduled": False,
"task_id": None
}
},
upsert=True
Expand Down
91 changes: 68 additions & 23 deletions backend/SC4SNMP_UI_backend/apply_changes/handling_chain.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
import ruamel.yaml
from flask import current_app
from SC4SNMP_UI_backend import mongo_client
from SC4SNMP_UI_backend.apply_changes.tasks import run_job
from SC4SNMP_UI_backend.apply_changes.tasks import run_job, get_job_config
from SC4SNMP_UI_backend.apply_changes.kubernetes_job import create_job
from kubernetes.client import ApiException
import datetime
import os

Expand All @@ -13,11 +15,23 @@
VALUES_DIRECTORY = os.getenv("VALUES_DIRECTORY", "")
VALUES_FILE = os.getenv("VALUES_FILE", "")
KEEP_TEMP_FILES = os.getenv("KEEP_TEMP_FILES", "false")
JOB_NAMESPACE = os.getenv("JOB_NAMESPACE", "sc4snmp")
mongo_config_collection = mongo_client.sc4snmp.config_collection
mongo_groups = mongo_client.sc4snmp.groups_ui
mongo_inventory = mongo_client.sc4snmp.inventory_ui
mongo_profiles = mongo_client.sc4snmp.profiles_ui


class EmptyValuesFileException(Exception):
def __init__(self, filename):
self.message = f"{filename} cannot be empty. Check sc4snmp documentation for template."
super().__init__(self.message)

class YamlParserException(Exception):
def __init__(self, filename):
self.message = f"Error occurred while reading {filename}. Check yaml syntax."
super().__init__(self.message)

class Handler(ABC):
@abstractmethod
def set_next(self, handler):
Expand Down Expand Up @@ -71,8 +85,15 @@ def handle(self, request: dict):
values_file_resolved = False
values = {}
if values_file_resolved:
with open(values_file_path, "r") as file:
values = yaml.load(file)
try:
with open(values_file_path, "r") as file:
values = yaml.load(file)
except ruamel.yaml.parser.ParserError as e:
current_app.logger.error(f"Error occurred while reading {VALUES_FILE}. Check yaml syntax.")
raise YamlParserException(VALUES_FILE)
if values is None:
current_app.logger.error(f"{VALUES_FILE} cannot be empty. Check sc4snmp documentation for template.")
raise EmptyValuesFileException(VALUES_FILE)

if not values_file_resolved or KEEP_TEMP_FILES.lower() in ["t", "true", "y", "yes", "1"]:
delete_temp_files = False
Expand Down Expand Up @@ -120,31 +141,55 @@ def handle(self, request: dict = None):
:return: pass dictionary with job_delay in seconds to the next handler
"""
record = list(mongo_config_collection.find())[0]
last_update = record["previous_job_start_time"]
if last_update is None:
# If it's the first time that the job is run (record in mongo_config_collection has been created
# in ApplyChanges class and last_update attribute is None) then job delay should be equal to
# CHANGES_INTERVAL_SECONDS. Update the mongo record with job state accordingly.
job_delay = CHANGES_INTERVAL_SECONDS
schedule_new_job = True
# get_job_config return job configuration in "job" variable and BatchV1Api from kubernetes client
job, batch_v1 = get_job_config()
if job is None or batch_v1 is None:
raise ValueError("CheckJobHandler: Job configuration is empty")
try:
# Try creating a new kubernetes job immediately. If the previous job is still present in the namespace,
# ApiException will be thrown.
create_job(batch_v1, job, JOB_NAMESPACE)
task_id = record["task_id"]
if task_id is not None:
# revoke existing Celery task with the previously scheduled job
current_app.extensions["celery"].control.revoke(task_id,
terminate=True, signal='SIGKILL')
mongo_config_collection.update_one({"_id": record["_id"]},
{"$set": {"previous_job_start_time": datetime.datetime.utcnow()}})
# time from the last update
{"$set": {"previous_job_start_time": datetime.datetime.utcnow(),
"currently_scheduled": False,
"task_id": None}})
job_delay = 1
time_difference = 0
else:
schedule_new_job = False
except ApiException:
# Check how many seconds have elapsed since the last time that the job was run. If the time difference
# is greater than CHANGES_INTERVAL_SECONDS then job can be run immediately. Otherwise, calculate how
# is greater than CHANGES_INTERVAL_SECONDS then job can be scheduled within 1 second. Otherwise, calculate how
# many seconds are left until minimum time difference between updates (CHANGES_INTERVAL_SECONDS).
current_time = datetime.datetime.utcnow()
delta = current_time - last_update
time_difference = delta.total_seconds()
if time_difference > CHANGES_INTERVAL_SECONDS:
job_delay = 1
last_update = record["previous_job_start_time"]
if last_update is None:
# If it's the first time that the job is run (record in mongo_config_collection has been created
# in ApplyChanges class and last_update attribute is None) but the previous job is still in the namespace
# then job delay should be equal to CHANGES_INTERVAL_SECONDS.
# Update the mongo record with job state accordingly.
job_delay = CHANGES_INTERVAL_SECONDS
mongo_config_collection.update_one({"_id": record["_id"]},
{"$set": {"previous_job_start_time": datetime.datetime.utcnow()}})
# time from the last update
time_difference = 0
else:
job_delay = int(CHANGES_INTERVAL_SECONDS - time_difference)
current_time = datetime.datetime.utcnow()
delta = current_time - last_update
time_difference = delta.total_seconds()
if time_difference > CHANGES_INTERVAL_SECONDS:
job_delay = 1
else:
job_delay = int(CHANGES_INTERVAL_SECONDS - time_difference)

result = {
"job_delay": job_delay,
"time_from_last_update": time_difference
"time_from_last_update": time_difference,
"schedule_new_job": schedule_new_job
}

current_app.logger.info(f"CheckJobHandler: {result}")
Expand All @@ -157,11 +202,11 @@ def handle(self, request: dict):
ScheduleHandler schedules the kubernetes job with updated sc4snmp configuration
"""
record = list(mongo_config_collection.find())[0]
if not record["currently_scheduled"]:
if not record["currently_scheduled"] and request["schedule_new_job"]:
# If the task isn't currently scheduled, schedule it and update its state in mongo.
async_result = run_job.apply_async(countdown=request["job_delay"], queue='apply_changes')
mongo_config_collection.update_one({"_id": record["_id"]},
{"$set": {"currently_scheduled": True}})
run_job.apply_async(countdown=request["job_delay"], queue='apply_changes')
{"$set": {"currently_scheduled": True, "task_id": async_result.id}})
current_app.logger.info(
f"ScheduleHandler: scheduling new task with the delay of {request['job_delay']} seconds.")
else:
Expand Down
17 changes: 15 additions & 2 deletions backend/SC4SNMP_UI_backend/apply_changes/routes.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from flask import Blueprint, jsonify
from flask import Blueprint, jsonify, current_app
from flask_cors import cross_origin
from SC4SNMP_UI_backend.apply_changes.apply_changes import ApplyChanges
from SC4SNMP_UI_backend.apply_changes.handling_chain import EmptyValuesFileException, YamlParserException
import os
import traceback

apply_changes_blueprint = Blueprint('common_blueprint', __name__)
JOB_CREATION_RETRIES = int(os.getenv("JOB_CREATION_RETRIES", 10))
Expand All @@ -19,4 +21,15 @@ def apply_changes():
else:
message = f"Configuration will be updated in approximately {job_delay} seconds."
result = jsonify({"message": message})
return result, 200
return result, 200

@apply_changes_blueprint.errorhandler(Exception)
@cross_origin()
def handle_exception(e):
current_app.logger.error(traceback.format_exc())
if isinstance(e, (EmptyValuesFileException, YamlParserException)):
result = jsonify({"message": e.message})
return result, 400

result = jsonify({"message": "Undentified error. Check logs."})
return result, 400
23 changes: 17 additions & 6 deletions backend/SC4SNMP_UI_backend/apply_changes/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@
JOB_CONFIG_PATH = os.getenv("JOB_CONFIG_PATH", "/config/job_config.yaml")
celery_logger = get_task_logger(__name__)

@shared_task()
def run_job():
def get_job_config():
"""
:return: job - configuration of the job
batch_v1 - BatchV1Api object from kubernetes client
"""
job = None
batch_v1 = None
with open(JOB_CONFIG_PATH, encoding="utf-8") as file:
Expand All @@ -26,6 +29,13 @@ def run_job():
config.load_incluster_config()
batch_v1 = client.BatchV1Api()
job = create_job_object(config_file)
return job, batch_v1

@shared_task()
def run_job():
job, batch_v1 = get_job_config()
if job is None or batch_v1 is None:
raise ValueError("Scheduled kubernetes job: Job configuration is empty")

with MongoClient(MONGO_URI) as connection:
try_creating = True
Expand All @@ -39,8 +49,9 @@ def run_job():
try:
record = list(connection.sc4snmp.config_collection.find())[0]
connection.sc4snmp.config_collection.update_one({"_id": record["_id"]},
{"$set": {"previous_job_start_time": datetime.datetime.utcnow(),
"currently_scheduled": False}})
{"$set": {"previous_job_start_time": datetime.datetime.utcnow(),
"currently_scheduled": False,
"task_id": None}})
except Exception as e:
celery_logger.info(f"Error occurred while updating job state after job creation: {str(e)}")
except ApiException:
Expand All @@ -50,6 +61,6 @@ def run_job():
celery_logger.info(f"Kubernetes job was not created. Max retries ({JOB_CREATION_RETRIES}) exceeded.")
record = list(connection.sc4snmp.config_collection.find())[0]
connection.sc4snmp.config_collection.update_one({"_id": record["_id"]},
{"$set": {"currently_scheduled": False}})
{"$set": {"currently_scheduled": False, "task_id": None}})
else:
time.sleep(10)
time.sleep(10)
8 changes: 4 additions & 4 deletions backend/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
click==8.1.3
Flask==2.2.5
Flask-Cors==3.0.10
Flask-Cors==4.0.1
itsdangerous==2.1.2
Jinja2==3.1.3
Jinja2==3.1.4
MarkupSafe==2.1.1
pymongo==4.1.1
pymongo==4.6.3
six==1.16.0
Werkzeug==2.3.8
Werkzeug==3.0.3
pytest~=7.2.0
gunicorn
kubernetes~=26.1.0
Expand Down
Loading

0 comments on commit c1fcd29

Please sign in to comment.