Skip to content

Commit ba853cb

Browse files
committed
Merge branch 'develop'
2 parents 9660bc0 + 5f45884 commit ba853cb

File tree

11 files changed

+154
-24
lines changed

11 files changed

+154
-24
lines changed

.github/workflows/deploy.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: deploy
22

33
on:
44
push:
5-
branches: [main, develop, CU-868em67g4_Upgrade-to-Django-52]
5+
branches: [main, develop, 366-broken-resource-links]
66

77
jobs:
88
deploy:

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ RUN groupadd --gid $USER_GID $USERNAME \
129129
# openssh-client -- for git over SSH
130130
# sudo -- to run commands as superuser
131131
# vim -- enhanced vi editor for commits
132-
ENV KUBE_CLIENT_VERSION="v1.32.6"
132+
ENV KUBE_CLIENT_VERSION="v1.33.0"
133133
ENV HELM_VERSION="3.18.3"
134134
ENV POSTGRESQL_CLIENT_VERSION="16"
135135
RUN --mount=type=cache,target=/var/cache/apt --mount=type=cache,target=/var/lib/apt \

deploy/ansible.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ inventory = inventory
44
vault_password_file = ./echo-vault-pass.sh
55

66
any_errors_fatal = true
7-
error_on_undefined_vars = true
87

98
# Make errors more readable
109
# human-readable stdout/stderr results display
1110
result_format=yaml
11+
callback_result_format = yaml

deploy/group_vars/all.yml

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ cloudformation_stack_region: '{{ aws_region }}'
3737
cloudformation_stack_name: "{{ app_name }}-stack"
3838
cloudformation_stack_termination_protection: true
3939
cloudformation_stack_template_bucket: "aws-web-stacks-{{ app_name }}"
40-
cloudformation_stack_template_local_path: '{{ playbook_dir + "/stack/eks-no-nat.yml" }}'
40+
cloudformation_stack_template_local_path: '{{ playbook_dir + "/stack/eks-no-nat-2.4.0.yml" }}'
4141
cloudformation_stack_template_parameters:
4242
PrimaryAZ: "{{ aws_region }}a"
4343
SecondaryAZ: "{{ aws_region }}b"
@@ -61,6 +61,8 @@ cloudformation_stack_template_parameters:
6161
AssetsCloudFrontDomain: files.nccopwatch.org
6262
AssetsCloudFrontCertArn: arn:aws:acm:us-east-1:606178775542:certificate/379950bb-4b29-4308-8418-122674fe1076
6363
AssetsUseCloudFront: "true"
64+
CustomEKSAMI: "" # Optional when CustomAMIImageType is set. Preferrably do not set as AWS will select best optimized image if CustomAMIImageType is set.
65+
CustomAMIImageType: AL2023_x86_64_STANDARD
6466
cloudformation_stack_tags:
6567
Environment: "{{ app_name }}"
6668

@@ -84,9 +86,9 @@ k8s_iam_users: [copelco]
8486
# Pin ingress-nginx and cert-manager to current versions so future upgrades of this
8587
# role will not upgrade these charts without your intervention:
8688
# https://github.com/kubernetes/ingress-nginx/releases
87-
k8s_ingress_nginx_chart_version: "4.12.3"
89+
k8s_ingress_nginx_chart_version: "4.13.0"
8890
# https://github.com/jetstack/cert-manager/releases
89-
k8s_cert_manager_chart_version: "v1.17.2"
91+
k8s_cert_manager_chart_version: "v1.18"
9092
# AWS only:
9193
# Use the newer load balancer type (NLB). DO NOT edit k8s_aws_load_balancer_type after
9294
# creating your Service.
@@ -97,7 +99,7 @@ k8s_aws_load_balancer_type: nlb
9799
# ----------------------------------------------------------------------------
98100

99101
# New Relic Account: [email protected]
100-
k8s_newrelic_chart_version: "6.0.10"
102+
k8s_newrelic_chart_version: "6.0.11"
101103
k8s_newrelic_logging_enabled: true
102104
k8s_newrelic_license_key: !vault |
103105
$ANSIBLE_VAULT;1.1;AES256
@@ -113,7 +115,7 @@ k8s_install_descheduler: yes
113115
# You must set the k8s_descheduler_chart_version to match the Kubernetes
114116
# node version (0.23.x -> K8s 1.23.x); see:
115117
# https://github.com/kubernetes-sigs/descheduler#compatibility-matrix
116-
k8s_descheduler_chart_version: v0.32.2
118+
k8s_descheduler_chart_version: v0.33.0
117119
# See values.yaml for options:
118120
# https://github.com/kubernetes-sigs/descheduler/blob/master/charts/descheduler/values.yaml#L63
119121
k8s_descheduler_release_values:
Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,14 @@ Conditions:
5050
- !Equals
5151
- !Ref 'DatabaseReplication'
5252
- 'true'
53+
UseCustomAMI: !Not
54+
- !Equals
55+
- !Ref 'CustomEKSAMI'
56+
- ''
57+
UseCustomAMIType: !Not
58+
- !Equals
59+
- !Ref 'CustomAMIImageType'
60+
- ''
5361
EitherCacheCondition: !Or
5462
- !Condition 'UsingMemcached'
5563
- !Condition 'UsingRedis'
@@ -193,6 +201,15 @@ Metadata:
193201
- RedisNumCacheClusters
194202
- RedisSnapshotRetentionLimit
195203
- RedisAutomaticFailover
204+
- Label:
205+
default: Elastic Kubernetes Service (EKS)
206+
Parameters:
207+
- EnableEksEncryptionConfig
208+
- EksPublicAccessCidrs
209+
- EksClusterName
210+
- CustomEKSAMI
211+
- CustomAMIImageType
212+
- EksClusterVersion
196213
ParameterLabels:
197214
AdministratorIPAddress:
198215
default: Admin IP Address
@@ -210,6 +227,10 @@ Metadata:
210227
default: Instance Type
211228
ContainerVolumeSize:
212229
default: Root Volume Size
230+
CustomAMIImageType:
231+
default: Custom AMI Image Type
232+
CustomEKSAMI:
233+
default: Custom EKS AMI
213234
CustomerManagedCmkArn:
214235
default: Customer managed key ARN
215236
DatabaseAllocatedStorage:
@@ -609,6 +630,14 @@ Parameters:
609630
Default: '20'
610631
Description: Size of instance EBS root volume (in GB)
611632
Type: Number
633+
CustomAMIImageType:
634+
Default: ''
635+
Description: The image type to match the custom AMI. E.g., AL2023_x86_64_STANDARD, AL2_x86_64
636+
Type: String
637+
CustomEKSAMI:
638+
Default: ''
639+
Description: Custom AMI ID for EKS node group
640+
Type: String
612641
CustomerManagedCmkArn:
613642
Default: ''
614643
Description: KMS CMK ARN to encrypt stack resources (except for public buckets).
@@ -1587,10 +1616,14 @@ Resources:
15871616
DependsOn:
15881617
- EksCluster
15891618
Properties:
1619+
AmiType: !If
1620+
- UseCustomAMIType
1621+
- !Ref 'CustomAMIImageType'
1622+
- !Ref 'AWS::NoValue'
15901623
ClusterName: !Ref 'EksCluster'
1591-
DiskSize: !Ref 'ContainerVolumeSize'
1592-
InstanceTypes:
1593-
- !Ref 'ContainerInstanceType'
1624+
LaunchTemplate:
1625+
Id: !Ref 'NodegroupLaunchTemplate'
1626+
Version: !GetAtt 'NodegroupLaunchTemplate.LatestVersionNumber'
15941627
NodeRole: !GetAtt 'ContainerInstanceRole.Arn'
15951628
ScalingConfig:
15961629
DesiredSize: !Ref 'DesiredScale'
@@ -1602,6 +1635,29 @@ Resources:
16021635
Tags:
16031636
Key: Value
16041637
Type: AWS::EKS::Nodegroup
1638+
NodegroupLaunchTemplate:
1639+
Properties:
1640+
LaunchTemplateData:
1641+
BlockDeviceMappings:
1642+
- DeviceName: /dev/xvda
1643+
Ebs:
1644+
DeleteOnTermination: true
1645+
Encrypted: !Ref 'UseAES256Encryption'
1646+
KmsKeyId: !If
1647+
- CmkArnCondition
1648+
- !Ref 'CustomerManagedCmkArn'
1649+
- !Ref 'AWS::NoValue'
1650+
VolumeSize: !Ref 'ContainerVolumeSize'
1651+
VolumeType: gp3
1652+
ImageId: !If
1653+
- UseCustomAMI
1654+
- !Ref 'CustomEKSAMI'
1655+
- !Ref 'AWS::NoValue'
1656+
InstanceType: !Ref 'ContainerInstanceType'
1657+
MetadataOptions:
1658+
HttpPutResponseHopLimit: 3
1659+
HttpTokens: required
1660+
Type: AWS::EC2::LaunchTemplate
16051661
PrivateAssetsBucket:
16061662
DeletionPolicy: Retain
16071663
Properties:

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ dependencies = [
1313
"census>=0.8.24",
1414
"click>=8.2.0",
1515
"dj-database-url>=3.0.0",
16-
"django>=5.2.0",
16+
"django>=5.2.8",
1717
"django-ckeditor-5>=0.2.15",
1818
"django-click>=2.4.1",
1919
"django-crispy-forms>=2.4.0",
@@ -85,7 +85,7 @@ dev = [
8585
"ipython>=8.32.0",
8686
"isort>=6.0.0",
8787
"kubernetes==32.*",
88-
"kubernetes-validate~=1.32.0",
88+
"kubernetes-validate~=1.33.0",
8989
"openshift>=0.13.2",
9090
"pre-commit>=4.1.0",
9191
"pytest>=8.3.3",

pytest.ini

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
[pytest]
2-
testpaths = nc tsdata
2+
testpaths = nc tsdata traffic_stops
33
python_files = tests.py test_*.py *_tests.py
4-
addopts = --ds=traffic_stops.settings.test -p no:warnings --cov-config=.coveragerc --cov-fail-under=44 --cov=nc --cov=tsdata --cov-report=html --cov-report=term-missing:skip-covered -vvv
4+
addopts = --ds=traffic_stops.settings.test -p no:warnings --cov-config=.coveragerc --cov-fail-under=44 --cov=nc --cov=tsdata --cov=traffic_stops --cov-report=html --cov-report=term-missing:skip-covered -vvv

traffic_stops/settings/base.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,10 +112,20 @@ def __init__(self, tz_name=None):
112112
STATIC_URL = "/static/"
113113

114114
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
115-
MEDIA_URL = "/media/"
116115
MEDIA_STORAGE_BUCKET_NAME = os.getenv("MEDIA_STORAGE_BUCKET_NAME", "")
117116
MEDIA_LOCATION = os.getenv("MEDIA_LOCATION", "")
118117
MEDIA_S3_CUSTOM_DOMAIN = os.getenv("MEDIA_S3_CUSTOM_DOMAIN", "")
118+
119+
# Set MEDIA_URL based on whether we're using S3 storage
120+
if MEDIA_S3_CUSTOM_DOMAIN:
121+
# When using S3 with custom domain, construct the full URL
122+
MEDIA_URL = f"https://{MEDIA_S3_CUSTOM_DOMAIN}/"
123+
if MEDIA_LOCATION:
124+
MEDIA_URL += f"{MEDIA_LOCATION}/"
125+
else:
126+
# Fall back to local media serving
127+
MEDIA_URL = "/media/"
128+
119129
STORAGES = {
120130
"default": {
121131
"BACKEND": os.getenv("DEFAULT_FILE_STORAGE", "django.core.files.storage.FileSystemStorage")

traffic_stops/tests/__init__.py

Whitespace-only changes.
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
"""Tests for settings configuration."""
2+
3+
import os
4+
5+
from unittest import mock
6+
7+
import pytest
8+
9+
10+
@pytest.mark.parametrize(
11+
"custom_domain,media_location,expected_url",
12+
[
13+
# With custom domain and no location
14+
("files.nccopwatch.org", "", "https://files.nccopwatch.org/"),
15+
# With custom domain and location
16+
("files.nccopwatch.org", "media", "https://files.nccopwatch.org/media/"),
17+
# With custom domain and nested location
18+
("files.example.com", "uploads/files", "https://files.example.com/uploads/files/"),
19+
# Without custom domain (local development)
20+
("", "", "/media/"),
21+
(None, "", "/media/"),
22+
],
23+
)
24+
def test_media_url_configuration(custom_domain, media_location, expected_url):
25+
"""Test that MEDIA_URL is correctly set based on S3 configuration."""
26+
# Mock environment variables
27+
env_vars = {
28+
"MEDIA_S3_CUSTOM_DOMAIN": custom_domain or "",
29+
"MEDIA_LOCATION": media_location,
30+
"DEFAULT_FILE_STORAGE": "django.core.files.storage.FileSystemStorage",
31+
}
32+
33+
with mock.patch.dict(os.environ, env_vars, clear=False):
34+
# Import settings module to trigger configuration
35+
# We need to reload to pick up the mocked environment
36+
import importlib
37+
38+
from traffic_stops.settings import base
39+
40+
importlib.reload(base)
41+
42+
assert base.MEDIA_URL == expected_url
43+
44+
45+
def test_media_url_with_s3_storage():
46+
"""Test MEDIA_URL when using S3 storage backend."""
47+
env_vars = {
48+
"MEDIA_S3_CUSTOM_DOMAIN": "files.nccopwatch.org",
49+
"MEDIA_LOCATION": "",
50+
"DEFAULT_FILE_STORAGE": "traffic_stops.storages.MediaBoto3Storage",
51+
}
52+
53+
with mock.patch.dict(os.environ, env_vars, clear=False):
54+
import importlib
55+
56+
from traffic_stops.settings import base
57+
58+
importlib.reload(base)
59+
60+
# When S3 storage is configured with custom domain, MEDIA_URL should be absolute
61+
assert base.MEDIA_URL.startswith("https://")
62+
assert "files.nccopwatch.org" in base.MEDIA_URL

0 commit comments

Comments
 (0)