Skip to content

Commit 8abdf88

Browse files
committed
Merge branch 'develop' into staging
2 parents 90d5517 + 5b21a95 commit 8abdf88

File tree

386 files changed

+27389
-4827
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

386 files changed

+27389
-4827
lines changed

.github/workflows/ci.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ jobs:
4444
whoami
4545
4646
- name: Checkout code
47-
uses: actions/checkout@v3
47+
uses: actions/checkout@v4
4848

4949
- name: Prepare repository
5050
run: |

.github/workflows/e2e-tests.yaml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ jobs:
4242
whoami
4343
4444
- name: Checkout code
45-
uses: actions/checkout@v3
45+
uses: actions/checkout@v4
4646

4747
- name: Prepare repository
4848
run: |
@@ -58,7 +58,7 @@ jobs:
5858
echo -e "UID=1001\nGID=127" > .env
5959
6060
- name: A job to install MicroK8s
61-
uses: balchua/microk8s-actions@v0.3.2
61+
uses: balchua/microk8s-actions@v0.4.2
6262
with:
6363
channel: '1.28/stable'
6464
addons: '["dns", "rbac", "hostpath-storage", "ingress", "helm"]'
@@ -143,3 +143,7 @@ jobs:
143143
with:
144144
## If no one connects after 2 minutes, shut down server.
145145
wait-timeout-minutes: 2
146+
## limits ssh access and adds the ssh public key for the user which triggered the workflow
147+
limit-access-to-actor: true
148+
## limits ssh access and adds the ssh public keys of the listed GitHub users
149+
limit-access-to-users: sandstromviktor,churnikov,hamzaimran08,alfredeen,akochari

.github/workflows/pre-commit.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@ jobs:
1515
runs-on: ubuntu-20.04
1616
steps:
1717
- name: Checkout
18-
uses: actions/checkout@v3
18+
uses: actions/checkout@v4
1919

2020
- name: Set up Python
21-
uses: actions/setup-python@v3
21+
uses: actions/setup-python@v5
2222
with:
2323
python-version: 3.8
2424

.github/workflows/publish.yaml

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,21 @@ on:
77
push:
88
tags:
99
- 'v*'
10+
workflow_dispatch:
11+
inputs:
12+
logLevel:
13+
description: 'Log level'
14+
required: true
15+
default: 'warning'
16+
type: choice
17+
options:
18+
- info
19+
- warning
20+
- debug
21+
tags:
22+
description: 'Manual run'
23+
required: false
24+
type: boolean
1025

1126
jobs:
1227
publish-on-success:
@@ -16,6 +31,8 @@ jobs:
1631
(github.event_name == 'push' && contains(github.ref, 'refs/tags/'))
1732
||
1833
(github.event.workflow_run.conclusion == 'success')
34+
||
35+
(github.event_name == 'workflow_dispatch')
1936
)
2037
runs-on: ubuntu-20.04
2138
strategy:
@@ -31,7 +48,7 @@ jobs:
3148
contents: read
3249
steps:
3350
- name: Checkout
34-
uses: actions/checkout@v2
51+
uses: actions/checkout@v4
3552

3653
- name: Set current date as env variable
3754
run: echo "BUILD_DATE=$(date +'%Y-%m-%d')" >> $GITHUB_ENV
@@ -45,35 +62,35 @@ jobs:
4562
type=raw,value={{date 'YYYYMMDD'}},prefix=${{ github.ref_name }}-
4663
4764
- name: Set the build date project toml file
48-
uses: ciiiii/toml-editor@1.0.0
65+
uses: sandstromviktor/toml-editor@2.0.0
4966
with:
5067
file: "pyproject.toml"
5168
key: "tool.serve.build-date"
5269
value: ${{ env.BUILD_DATE }}
5370

5471
- name: Set the git tag or branch name in the project toml file
55-
uses: ciiiii/toml-editor@1.0.0
72+
uses: sandstromviktor/toml-editor@2.0.0
5673
with:
5774
file: "pyproject.toml"
5875
key: "tool.serve.gitref"
5976
value: ${{ github.ref_name }}
6077

6178
- name: Set the image tag in the project toml file
62-
uses: ciiiii/toml-editor@1.0.0
79+
uses: sandstromviktor/toml-editor@2.0.0
6380
with:
6481
file: "pyproject.toml"
6582
key: "tool.serve.imagetag"
6683
value: ${{ steps.meta.outputs.version }}
6784

6885
- name: Log in to ghcr.io
69-
uses: docker/login-action@v1
86+
uses: docker/login-action@v3
7087
with:
7188
registry: ghcr.io
7289
username: ${{ github.actor }}
7390
password: ${{ secrets.GITHUB_TOKEN }}
7491

7592
- name: docker build serve
76-
uses: docker/build-push-action@v2
93+
uses: docker/build-push-action@v5
7794
with:
7895
context: .
7996
push: "${{ github.event_name != 'pull_request' }}"

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ repos:
1616
- id: detect-private-key
1717
- id: mixed-line-ending
1818
- id: end-of-file-fixer
19-
exclude: static/vendor/
19+
exclude: static/vendor/|static/wiki/
2020
- id: pretty-format-json
2121
exclude: ^fixtures/
2222
args: [--autofix]

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ RUN apk add --update --no-cache \
2727
# Installing Pillow separate from the packages in requirements
2828
# greatly speeds up the docker build.
2929
RUN python3 -m pip install --upgrade pip \
30-
&& python3 -m pip install Pillow==10.1.0 --global-option="build_ext" --global-option="--disable-tiff" --global-option="--disable-freetype" --global-option="--disable-lcms" --global-option="--disable-webp" --global-option="--disable-webpmux" --global-option="--disable-imagequant" --global-option="--disable-xcb"
30+
&& python3 -m pip install Pillow==10.2.0 --global-option="build_ext" --global-option="--disable-tiff" --global-option="--disable-freetype" --global-option="--disable-lcms" --global-option="--disable-webp" --global-option="--disable-webpmux" --global-option="--disable-imagequant" --global-option="--disable-xcb"
3131

3232
FROM bitnami/kubectl:1.28.2 as kubectl
3333
FROM alpine/helm:3.12.3 as helm

api/APIpermissions.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ def has_permission(self, request, view):
2525
"""
2626
Should simply return, or raise a 403 response.
2727
"""
28+
# To implement expiring tokens, can access the token here: request.auth
2829
is_authorized = False
2930

3031
if request.user.is_superuser:

api/public_views.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,12 @@ def get_studio_settings(request):
1313
studio_settings.append(studio_url)
1414

1515
return JsonResponse({"data": studio_settings})
16+
17+
18+
def are_you_there(request):
19+
"""
20+
Most simple API endpoint useful for testing
21+
and verifications.
22+
:returns bool: true
23+
"""
24+
return JsonResponse({"status": True})

api/urls.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from django.urls import path
44
from rest_framework_nested import routers
55

6-
from .public_views import get_studio_settings
6+
from .public_views import are_you_there, get_studio_settings
77
from .views import (
88
AppInstanceList,
99
AppList,
@@ -21,6 +21,7 @@
2121
ReleaseNameList,
2222
ResourceList,
2323
S3List,
24+
update_app_status,
2425
)
2526

2627
app_name = "api"
@@ -50,6 +51,10 @@
5051
path("", include(router_drf.urls)),
5152
path("", include(router.urls)),
5253
path("", include(models_router.urls)),
54+
# Generic API endpoints
55+
path("are-you-there/", are_you_there),
56+
# Internal API endpoints
5357
path("token-auth/", CustomAuthToken.as_view(), name="api_token_auth"),
5458
path("settings/", get_studio_settings),
59+
path("app-status/", update_app_status),
5560
]

api/views.py

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
import json
22
import time
3+
from datetime import datetime
34

5+
import pytz
46
from django.conf import settings
57
from django.contrib.auth.models import User
8+
from django.core.exceptions import ObjectDoesNotExist
69
from django.db.models import Q
710
from django.http import HttpResponse
811
from django.utils.text import slugify
912
from django_filters.rest_framework import DjangoFilterBackend
1013
from rest_framework import generics
1114
from rest_framework.authtoken.models import Token
1215
from rest_framework.authtoken.views import ObtainAuthToken
16+
from rest_framework.decorators import api_view, permission_classes
1317
from rest_framework.mixins import (
1418
CreateModelMixin,
1519
ListModelMixin,
@@ -20,7 +24,8 @@
2024
from rest_framework.response import Response
2125
from rest_framework.viewsets import GenericViewSet
2226

23-
from apps.models import AppCategories, AppInstance, Apps
27+
from apps.helpers import HandleUpdateStatusResponseCode, handle_update_status_request
28+
from apps.models import AppCategories, AppInstance, Apps, AppStatus
2429
from apps.tasks import delete_resource
2530
from models.models import ObjectType
2631
from portal.models import PublishedModel
@@ -815,3 +820,97 @@ def create(self, request, *args, **kwargs):
815820
except Exception as err:
816821
print(err)
817822
return HttpResponse("Created new template: {}.".format(name), status=200)
823+
824+
825+
@api_view(["GET", "POST"])
826+
@permission_classes(
827+
(
828+
IsAuthenticated,
829+
AdminPermission,
830+
)
831+
)
832+
def update_app_status(request):
833+
"""
834+
Manages the app instance status.
835+
Implemented as a DRF function based view.
836+
Supports GET and POST verbs.
837+
838+
The service contract for the POST actions is as follows:
839+
:param release str: The release id of the app instance, stored in the AppInstance.parameters dict.
840+
:param new-status str: The new status code.
841+
:param event-ts timestamp: A JSON-formatted timestamp, e.g. 2024-01-25T16:02:50.00Z.
842+
:param event-msg json dict: An optional json dict containing pod-msg and/or container-msg.
843+
:returns: An http status code and status text.
844+
"""
845+
846+
# POST verb
847+
if request.method == "POST":
848+
print("INFO: API method update_app_status called with POST verb.")
849+
850+
utc = pytz.UTC
851+
852+
try:
853+
# Parse and validate the input
854+
855+
# Required input
856+
release = request.data["release"]
857+
new_status = request.data["new-status"]
858+
859+
event_ts = datetime.strptime(request.data["event-ts"], "%Y-%m-%dT%H:%M:%S.%fZ")
860+
event_ts = utc.localize(event_ts)
861+
862+
# Optional
863+
event_msg = request.data.get("event-msg", None)
864+
865+
except KeyError as err:
866+
print(f"API method called with invalid input. Missing required input parameter: {err}")
867+
return Response(f"Invalid input. Missing required input parameter: {err}", 400)
868+
869+
except Exception as err:
870+
print(f"API method called with invalid input: {err}, {type(err)}")
871+
return Response(f"Invalid input. {err}", 400)
872+
873+
print(f"DEBUG: API method update_app_status input: {release=}, {new_status=}, {event_ts=}, {event_msg=}")
874+
875+
try:
876+
result = handle_update_status_request(release, new_status, event_ts, event_msg)
877+
878+
if result == HandleUpdateStatusResponseCode.NO_ACTION:
879+
return Response(
880+
"OK. NO_ACTION. No action performed. Possibly the event time is older \
881+
than the currently stored time.",
882+
200,
883+
)
884+
885+
elif result == HandleUpdateStatusResponseCode.CREATED_FIRST_STATUS:
886+
return Response("OK. CREATED_FIRST_STATUS. Created a missing AppStatus.", 200)
887+
888+
elif result == HandleUpdateStatusResponseCode.UPDATED_STATUS:
889+
return Response(
890+
"OK. UPDATED_STATUS. Updated the app status. \
891+
Determined that the submitted event was newer and different status.",
892+
200,
893+
)
894+
895+
elif result == HandleUpdateStatusResponseCode.UPDATED_TIME_OF_STATUS:
896+
return Response(
897+
"OK. UPDATED_TIME_OF_STATUS. Updated only the event time of the status. \
898+
Determined that the new and old status codes are the same.",
899+
200,
900+
)
901+
902+
else:
903+
print(f"Unknown return code from handle_update_status_request() = {result}")
904+
return Response(f"Unknown return code from handle_update_status_request() = {result}", 500)
905+
906+
except ObjectDoesNotExist:
907+
print(f"The specified app instance was not found {release=}.")
908+
return Response(f"The specified app instance was not found {release=}.", 404)
909+
910+
except Exception as err:
911+
print(f"Unable to update the status of the specified app instance {release=}. {err}, {type(err)}")
912+
return Response(f"Unable to update the status of the specified app instance {release=}.", 500)
913+
914+
# GET verb
915+
print("API method update_app_status called with GET verb.")
916+
return Response({"message": "DEBUG: GET"})

apps/admin.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,17 @@ def display_project(self, obj):
3535
return obj.project.name
3636

3737

38+
class AppStatusAdmin(admin.ModelAdmin):
39+
list_display = (
40+
"appinstance",
41+
"status_type",
42+
"time",
43+
)
44+
45+
list_filter = ["appinstance", "status_type", "time"]
46+
47+
3848
admin.site.register(AppInstance, AppInstanceAdmin)
3949
admin.site.register(AppCategories)
4050
admin.site.register(ResourceData)
41-
admin.site.register(AppStatus)
51+
admin.site.register(AppStatus, AppStatusAdmin)

apps/controller.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ def delete(options):
2222

2323
def deploy(options):
2424
print("STARTING DEPLOY FROM CONTROLLER")
25-
_ = os.environ["BASE_PATH"]
25+
2626
app = Apps.objects.get(slug=options["app_slug"], revision=options["app_revision"])
2727
if app.chart_archive and app.chart_archive != "":
2828
try:
@@ -41,6 +41,37 @@ def deploy(options):
4141
if "release" not in options:
4242
print("Release option not specified.")
4343
return json.dumps({"status": "failed", "reason": "Option release not set."})
44+
if "appconfig" in options:
45+
# check if path is root path
46+
if "path" in options["appconfig"]:
47+
if "/" == options["appconfig"]["path"]:
48+
print("Root path cannot be copied.")
49+
return json.dumps({"status": "failed", "reason": "Cannot copy / root path."})
50+
# check if valid userid
51+
if "userid" in options["appconfig"]:
52+
try:
53+
userid = int(options["appconfig"]["userid"])
54+
except Exception as ex:
55+
print("Userid not a number.")
56+
print(ex)
57+
return json.dumps({"status": "failed", "reason": "Userid not an integer."})
58+
if userid > 1010 or userid < 999:
59+
print("Userid outside of allowed range.")
60+
return json.dumps({"status": "failed", "reason": "Userid outside of allowed range."})
61+
else:
62+
# if no userid, then add default id of 1000
63+
options["appconfig"]["userid"] = "1000"
64+
# check if valid port
65+
if "port" in options["appconfig"]:
66+
try:
67+
port = int(options["appconfig"]["port"])
68+
except Exception as ex:
69+
print("Userid not a number.")
70+
print(ex)
71+
return json.dumps({"status": "failed", "reason": "Port not an integer."})
72+
if port > 9999 or port < 3000:
73+
print("Port outside of allowed range.")
74+
return json.dumps({"status": "failed", "reason": "Port outside of allowed range."})
4475

4576
# Save helm values file for internal reference
4677
unique_filename = "charts/values/{}-{}.yaml".format(str(uuid.uuid4()), str(options["app_name"]))

0 commit comments

Comments
 (0)