Skip to content

Commit

Permalink
Refactor CI pipelines out of Pipelines view into a seperate view; Fix…
Browse files Browse the repository at this point in the history
… duplicate declaration of Pipeline in Pipelines view
  • Loading branch information
nathandf committed Jan 11, 2024
1 parent e1a287a commit d6ed16a
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 6 deletions.
1 change: 1 addition & 0 deletions src/api/src/backend/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from backend.views.HealthCheck import HealthCheck
from backend.views.Identities import Identities
from backend.views.Pipelines import Pipelines
from backend.views.CIPipelines import CIPipelines
from backend.views.Users import Users
from backend.views.Nuke import Nuke
from backend.views.ChangePipelineOwner import ChangePipelineOwner
Expand Down
130 changes: 130 additions & 0 deletions src/api/src/backend/views/CIPipelines.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
from typing import List
from pydantic import ValidationError
from django.db import DatabaseError, IntegrityError, OperationalError
from django.forms import model_to_dict

from backend.views.RestrictedAPIView import RestrictedAPIView
from backend.views.http.responses.errors import (
Conflict,
BadRequest,
NotFound,
Forbidden,
ServerError as ServerErrorResp
)
from backend.views.http.responses import ResourceURLResponse
from backend.views.http.requests import CIPipeline, ImageBuildTask
from backend.models import (
Pipeline as PipelineModel,
Archive,
PipelineArchive,
TASK_TYPE_IMAGE_BUILD
)
from backend.services.TaskService import service as task_service
from backend.services.GroupService import service as group_service
from backend.errors.api import BadRequestError
from backend.helpers import resource_url_builder


class CIPipelines(RestrictedAPIView):
def post(self, request, group_id, *_, **__):
"""Pipeline requests with type 'ci' are supported in order to make the
process of setting up a ci/cd pipeline as simple as possible. Rather than
specifying tasks, dependencies, etc, we let user pass most the required
data in the top level of the pipeline request."""
# Get the group
group = group_service.get(group_id, request.tenant_id)
if group == None:
return NotFound(f"No group found with id '{group_id}'")

# Check that the user belongs to the group
if not group_service.user_in_group(request.username, group_id, request.tenant_id):
return Forbidden(message="You do not have access to this group")

# Validate the request body based on the type of pipeline specified
prepared_request = self.prepare(CIPipeline)

# Return the failure view instance if validation failed
if not prepared_request.is_valid:
return prepared_request.failure_view

# Get the JSON encoded body from the validation result
body = prepared_request.body

# Check that the id of the pipeline is unique
if PipelineModel.objects.filter(id=body.id, group=group).exists():
return Conflict(f"A Pipeline already exists with the id '{body.id}'")

# Create the pipeline
try:
pipeline = PipelineModel.objects.create(
id=body.id,
group=group,
owner=request.username,
max_exec_time=body.execution_profile.max_exec_time,
invocation_mode=body.execution_profile.invocation_mode,
max_retries=body.execution_profile.max_retries,
retry_policy=body.execution_profile.retry_policy,
duplicate_submission_policy=body.execution_profile.duplicate_submission_policy,
env=body.dict()["env"],
params=body.dict()["params"]
)
except (IntegrityError, OperationalError) as e:
return BadRequest(message=e.__cause__)
except Exception as e:
return ServerErrorResp(f"{e}")

# Fetch the archives specified in the request then create relations
# between them and the pipline
pipeline_archives = []
try:
# Prevent duplicate pipeline archives by casting id array to 'set'
for archive_id in set(body.archive_ids):
# Fetch the archive object
archive = Archive.objects.filter(group=group, id=archive_id).first()

# Return bad request if archive not found
if archive == None:
pipeline.delete()
return BadRequest(message=f"Archive not found with an id of '{archive_id}' and group_id '{group.id}'")

pipeline_archives.append(
PipelineArchive.objects.create(
pipeline=pipeline,
archive=archive
)
)
except (IntegrityError, OperationalError, DatabaseError) as e:
# Delete the pipeline
pipeline.delete()

# Delete the pipeline archive relationships that were just created
[pipeline_archive.delete() for pipeline_archive in pipeline_archives]
return BadRequest(message=e.__cause__)

try:
# Build an task_request from the pipeline request body
task_request = ImageBuildTask(
id="build",
builder=body.builder,
cache=body.cache,
description="Build an image from a repository and push it to an image registry",
destination=body.destination,
context=body.context,
pipeline_id=pipeline.id,
type=TASK_TYPE_IMAGE_BUILD
)

# Create 'build' task
task_service.create(pipeline, task_request)
except (ValidationError, BadRequestError) as e:
pipeline.delete()
return BadRequest(message=e)
except (IntegrityError, OperationalError, DatabaseError) as e:
pipeline.delete()
return BadRequest(message=e.__cause__)
except Exception as e:
pipeline.delete()
return ServerErrorResp(message=e)

return ResourceURLResponse(
url=resource_url_builder(request.url.replace("/ci", "/pipelines"), pipeline.id))
12 changes: 6 additions & 6 deletions src/api/src/backend/views/Pipelines.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from backend.views.http.responses import BaseResponse, ResourceURLResponse
from backend.views.http.requests import Pipeline, CIPipeline, ImageBuildTask
from backend.models import (
Pipeline,
Pipeline as PipelineModel,
Archive,
PipelineArchive,
TASK_TYPE_IMAGE_BUILD
Expand Down Expand Up @@ -52,7 +52,7 @@ def get(self, request, group_id, pipeline_id=None):
return self.list(group)

# Get the pipeline by the id provided in the path params
pipeline = Pipeline.objects.filter(
pipeline = PipelineModel.objects.filter(
id=pipeline_id,
group=group
).prefetch_related("tasks").first()
Expand All @@ -71,7 +71,7 @@ def get(self, request, group_id, pipeline_id=None):
return BaseResponse(result=result)

def list(self, group):
pipelines = Pipeline.objects.filter(group=group)
pipelines = PipelineModel.objects.filter(group=group)
return ModelListResponse(pipelines)

def post(self, request, group_id, *_, **__):
Expand Down Expand Up @@ -112,12 +112,12 @@ def post(self, request, group_id, *_, **__):
body = prepared_request.body

# Check that the id of the pipeline is unique
if Pipeline.objects.filter(id=body.id, group=group).exists():
if PipelineModel.objects.filter(id=body.id, group=group).exists():
return Conflict(f"A Pipeline already exists with the id '{body.id}'")

# Create the pipeline
try:
pipeline = Pipeline.objects.create(
pipeline = PipelineModel.objects.create(
id=body.id,
group=group,
owner=request.username,
Expand Down Expand Up @@ -178,7 +178,7 @@ def delete(self, request, group_id, pipeline_id, *_, **__):
return Forbidden(message="You do not have access to this group")

# Get the pipeline by the id provided in the path params
pipeline = Pipeline.objects.filter(
pipeline = PipelineModel.objects.filter(
id=pipeline_id,
group=group
).prefetch_related("tasks").first()
Expand Down

0 comments on commit d6ed16a

Please sign in to comment.