Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
fa3ce1b
wip cicd split
dogboat Apr 3, 2026
910d4b9
migration updates
dogboat Apr 8, 2026
696b114
old ui
dogboat Apr 9, 2026
e3c28a5
display under ci/cd eng details
dogboat Apr 9, 2026
3699305
cicdinfra type field is read only on form to prevent changes in usage…
dogboat Apr 9, 2026
f7cad8e
migration update after merge
dogboat May 20, 2026
4780c9c
add cicd link to navbar
dogboat May 20, 2026
44d4800
css for disabled controls (new old ui)
dogboat May 21, 2026
8df8d24
eng view updates
dogboat May 21, 2026
d79f975
classic copies of templates
dogboat May 21, 2026
b3bc58d
make (name, infra type) unique
dogboat May 21, 2026
0601e32
linter fix
dogboat May 29, 2026
feef44e
cicd tests
dogboat Jun 9, 2026
636eabb
linter fixes
dogboat Jun 10, 2026
d8cf230
test fixes
dogboat Jun 10, 2026
394c17c
refactor model into module in line with how it should be done
dogboat Jun 10, 2026
370fb86
refactor cicd api to correct layout
dogboat Jun 10, 2026
8c5b3e8
refactor (old) ui into proper package
dogboat Jun 11, 2026
f4940d8
add cicd_infrastructure to modules state table
dogboat Jun 11, 2026
b2fcf91
remove config authorized decorator usage
dogboat Jun 11, 2026
2a0d95f
rework api perms to allow read by authed user, edit by superuser
dogboat Jun 11, 2026
dacb8e6
rework edit-related view perms
dogboat Jun 11, 2026
46608e6
comment
dogboat Jun 11, 2026
f58e2f3
disallow editing of infra type on instances in api, check in model sa…
dogboat Jun 11, 2026
0b3ae7a
reorder listings to scm/build/orch for logical usage
dogboat Jun 11, 2026
36ebc04
fix migration
dogboat Jun 11, 2026
f8392d7
comments
dogboat Jun 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ Modules in various stages of reorganization:
|--------|-----------|-------------|-----|------|--------|
| **url** | In module | N/A | Done | Done | **Complete** |
| **location** | In module | N/A | N/A | Done | **Complete** |
| **cicd_infrastructure** | In module | N/A | Done | Done | **Complete** |
| **product_type** | In dojo/models.py | Missing | Partial (views at root) | In dojo/api_v2/ | Needs work |
| **test** | In dojo/models.py | Missing | Partial (views at root) | In dojo/api_v2/ | Needs work |
| **engagement** | In dojo/models.py | Partial (32 lines) | Partial (views at root) | In dojo/api_v2/ | Needs work |
Expand Down
13 changes: 13 additions & 0 deletions dojo/authorization/api_permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from dojo.importers.auto_create_context import AutoCreateContextManager
from dojo.location.models import Location
from dojo.models import (
CICDInfrastructure,
Development_Environment,
Endpoint,
Engagement,
Expand Down Expand Up @@ -919,6 +920,18 @@ class UserHasRegulationPermission(BaseDjangoModelPermission):
}


class UserHasCICDInfrastructurePermission(BaseDjangoModelPermission):
django_model = CICDInfrastructure
# Reads are open to any authenticated user (engagement views surface CICD
# references and need to render them). Writes require elevated privileges.
request_method_permission_map = {
"POST": "add",
"PUT": "change",
"PATCH": "change",
"DELETE": "delete",
}


def raise_no_auto_create_import_validation_error(
test_title,
scan_type,
Expand Down
1 change: 1 addition & 0 deletions dojo/cicd_infrastructure/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import dojo.cicd_infrastructure.admin # noqa: F401
9 changes: 9 additions & 0 deletions dojo/cicd_infrastructure/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from django.contrib import admin

from dojo.cicd_infrastructure.models import CICDInfrastructure


@admin.register(CICDInfrastructure)
class CICDInfrastructureAdmin(admin.ModelAdmin):

"""Admin support for the CICDInfrastructure model."""
Empty file.
16 changes: 16 additions & 0 deletions dojo/cicd_infrastructure/api/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from rest_framework import serializers

from dojo.models import CICDInfrastructure


class CICDInfrastructureSerializer(serializers.ModelSerializer):
class Meta:
model = CICDInfrastructure
fields = "__all__"

def get_fields(self):
fields = super().get_fields()
if self.instance is not None:
# Disallow editing of infra type on an instance; see the matching comment on CICDInfrastructure#save()
fields["infrastructure_type"].read_only = True
return fields
6 changes: 6 additions & 0 deletions dojo/cicd_infrastructure/api/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from dojo.cicd_infrastructure.api.views import CICDInfrastructureViewSet


def add_cicd_infrastructure_urls(router):
router.register(r"cicd_infrastructure", CICDInfrastructureViewSet, basename="cicd_infrastructure")
return router
21 changes: 21 additions & 0 deletions dojo/cicd_infrastructure/api/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.permissions import IsAuthenticated

from dojo.api_v2.views import DojoModelViewSet
from dojo.authorization import api_permissions as permissions
from dojo.cicd_infrastructure.api.serializers import CICDInfrastructureSerializer
from dojo.models import CICDInfrastructure


# Authorization: read open to authenticated users; write requires configuration permission.
class CICDInfrastructureViewSet(
DojoModelViewSet,
):
serializer_class = CICDInfrastructureSerializer
queryset = CICDInfrastructure.objects.none()
filter_backends = (DjangoFilterBackend,)
filterset_fields = ["id", "name", "infrastructure_type"]
permission_classes = (IsAuthenticated, permissions.UserHasCICDInfrastructurePermission)

def get_queryset(self):
return CICDInfrastructure.objects.all().order_by("id")
35 changes: 35 additions & 0 deletions dojo/cicd_infrastructure/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from django.core.exceptions import ValidationError
from django.db import models
from django.utils.translation import gettext as _


class CICDInfrastructure(models.Model):
INFRASTRUCTURE_TYPE_CHOICES = (
("scm_server", "SCM Server"),
("build_server", "Build Server"),
("orchestration", "Orchestration Engine"),
)

name = models.CharField(max_length=200)
description = models.CharField(max_length=2000, blank=True, default="")
url = models.URLField(max_length=2000, blank=True, default="", help_text=_("Public URL of the tool (e.g., https://jenkins.company.com)"))
infrastructure_type = models.CharField(max_length=30, choices=INFRASTRUCTURE_TYPE_CHOICES)

class Meta:
ordering = ["name"]
unique_together = [("name", "infrastructure_type")]

def __str__(self):
return self.name

def save(self, *args, **kwargs):
if self.pk:
# Disallow editing of the infra type on an instance; engagement CICD FKs are scoped by infrastructure_type
# via limit_choices_to (build_server/scm_server/orchestration), so changing the type would create a
# semantic conflict between an engagement and this object.
current_type = type(self).objects.filter(pk=self.pk).values_list("infrastructure_type", flat=True).first()
if current_type is not None and current_type != self.infrastructure_type:
raise ValidationError(
{"infrastructure_type": _("infrastructure_type cannot be changed once set.")},
)
super().save(*args, **kwargs)
Empty file.
15 changes: 15 additions & 0 deletions dojo/cicd_infrastructure/ui/forms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from django import forms

from dojo.models import CICDInfrastructure


class CICDInfrastructureForm(forms.ModelForm):
class Meta:
model = CICDInfrastructure
fields = ["name", "description", "url", "infrastructure_type"]

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.instance.pk:
# Disallow editing of infra type on an instance; see the matching comment on CICDInfrastructure#save()
self.fields["infrastructure_type"].disabled = True
10 changes: 10 additions & 0 deletions dojo/cicd_infrastructure/ui/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from django.urls import re_path

from dojo.cicd_infrastructure.ui import views

urlpatterns = [
re_path(r"^cicd_infrastructure/add$", views.new_cicd_infrastructure, name="add_cicd_infrastructure"),
re_path(r"^cicd_infrastructure/(?P<ciid>\d+)/edit$", views.edit_cicd_infrastructure, name="edit_cicd_infrastructure"),
re_path(r"^cicd_infrastructure/(?P<ciid>\d+)/delete$", views.delete_cicd_infrastructure, name="delete_cicd_infrastructure"),
re_path(r"^cicd_infrastructure$", views.cicd_infrastructure, name="cicd_infrastructure"),
]
66 changes: 66 additions & 0 deletions dojo/cicd_infrastructure/ui/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import logging

from django.contrib import messages
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from django.utils.translation import gettext as _

from dojo.authorization.authorization import user_has_configuration_permission_or_403
from dojo.cicd_infrastructure.ui.forms import CICDInfrastructureForm
from dojo.models import CICDInfrastructure
from dojo.utils import add_breadcrumb

logger = logging.getLogger(__name__)


def cicd_infrastructure(request):
confs = CICDInfrastructure.objects.all().order_by("name")
add_breadcrumb(title=_("CI/CD Infrastructure List"), top_level=not len(request.GET), request=request)
return render(request, "dojo/cicd_infrastructure.html", {"confs": confs})


def new_cicd_infrastructure(request):
user_has_configuration_permission_or_403(request.user, "dojo.add_cicdinfrastructure")
if request.method == "POST":
form = CICDInfrastructureForm(request.POST)
if form.is_valid():
form.save()
messages.add_message(request, messages.SUCCESS,
_("CI/CD Infrastructure successfully created."),
extra_tags="alert-success")
return HttpResponseRedirect(reverse("cicd_infrastructure"))
else:
form = CICDInfrastructureForm()
add_breadcrumb(title=_("New CI/CD Infrastructure"), top_level=False, request=request)
return render(request, "dojo/new_cicd_infrastructure.html", {"form": form})


def edit_cicd_infrastructure(request, ciid):
user_has_configuration_permission_or_403(request.user, "dojo.change_cicdinfrastructure")
conf = get_object_or_404(CICDInfrastructure, pk=ciid)
if request.method == "POST":
form = CICDInfrastructureForm(request.POST, instance=conf)
if form.is_valid():
form.save()
messages.add_message(request, messages.SUCCESS,
_("CI/CD Infrastructure successfully updated."),
extra_tags="alert-success")
return HttpResponseRedirect(reverse("cicd_infrastructure"))
else:
form = CICDInfrastructureForm(instance=conf)
add_breadcrumb(title=_("Edit CI/CD Infrastructure"), top_level=False, request=request)
return render(request, "dojo/edit_cicd_infrastructure.html", {"form": form, "conf": conf})


def delete_cicd_infrastructure(request, ciid):
user_has_configuration_permission_or_403(request.user, "dojo.delete_cicdinfrastructure")
conf = get_object_or_404(CICDInfrastructure, pk=ciid)
if request.method == "POST":
conf.delete()
messages.add_message(request, messages.SUCCESS,
_("CI/CD Infrastructure successfully deleted."),
extra_tags="alert-success")
return HttpResponseRedirect(reverse("cicd_infrastructure"))
add_breadcrumb(title=_("Delete CI/CD Infrastructure"), top_level=False, request=request)
return render(request, "dojo/delete_cicd_infrastructure.html", {"conf": conf})
Loading
Loading