From cf4c4392776fe604e7036e618b933cf6767dde61 Mon Sep 17 00:00:00 2001 From: Gagan Trivedi Date: Sat, 15 Aug 2026 16:03:47 +0530 Subject: [PATCH 1/2] feat: wire up the edge_proxy private app Detects the edge_proxy app and mounts its two URL trees: proxy key management for organisation admins, and the environment inventory that Edge Proxy instances poll. The feature is for private cloud only. SaaS images ship the private wheel, so the mount is gated on is_saas() at runtime rather than on the app being absent; the app's own views carry a matching permission as defence in depth. --- api/app/settings/common.py | 4 ++++ api/app/urls.py | 11 +++++++++++ api/pyproject.toml | 4 ++++ 3 files changed, 19 insertions(+) diff --git a/api/app/settings/common.py b/api/app/settings/common.py index 219e6aba9437..ad7434644c62 100644 --- a/api/app/settings/common.py +++ b/api/app/settings/common.py @@ -1160,6 +1160,10 @@ "USER_FILTER_PARSER": "scim.filters.UserFilterQuery", } +EDGE_PROXY_INSTALLED = importlib.util.find_spec("edge_proxy") is not None +if EDGE_PROXY_INSTALLED: + INSTALLED_APPS.append("edge_proxy") + DEFAULT_AUTO_FIELD = "django.db.models.AutoField" # Used to keep edge identities in sync by forwarding the http requests diff --git a/api/app/urls.py b/api/app/urls.py index 1b45a36d8732..5be04f462455 100644 --- a/api/app/urls.py +++ b/api/app/urls.py @@ -1,6 +1,7 @@ import importlib from common.core.urls import urlpatterns as core_urlpatterns +from common.core.utils import is_saas from django.conf import settings from django.contrib import admin from django.urls import include, path, re_path @@ -124,6 +125,16 @@ ), ] +# SaaS images ship the private wheel, so is_saas() is the gate, not module presence. +if settings.EDGE_PROXY_INSTALLED and not is_saas(): # pragma: no cover + urlpatterns += [ + path( + "api/v1/organisations//edge-proxy/", + include("edge_proxy.management_urls"), + ), + path("api/v1/proxy/", include("edge_proxy.urls")), + ] + if settings.WORKFLOWS_LOGIC_INSTALLED: # pragma: no cover workflow_views = importlib.import_module("workflows_logic.views") urlpatterns += [ diff --git a/api/pyproject.toml b/api/pyproject.toml index aa4ebcd30818..b6530ccca85a 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -242,6 +242,10 @@ ignore_missing_imports = true module = ["rbac.*"] ignore_missing_imports = true +[[tool.mypy.overrides]] +module = ["edge_proxy.*"] +ignore_missing_imports = true + [[tool.mypy.overrides]] module = ["saml.*"] ignore_missing_imports = true From 4330617bd59ad7b423b90749e768ecb4260e79d1 Mon Sep 17 00:00:00 2001 From: "flagsmith-engineering[bot]" Date: Sat, 29 Aug 2026 05:23:53 +0000 Subject: [PATCH 2/2] chore: Update documentation artefacts --- openapi.yaml | 311 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 311 insertions(+) diff --git a/openapi.yaml b/openapi.yaml index 78578f6e6a84..1853cdc33f36 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -9819,6 +9819,179 @@ paths: - Master API Key: [] tags: - Audit + '/api/v1/organisations/{organisation_pk}/edge-proxy/keys/': + get: + operationId: api_v1_organisations_edge_proxy_keys_list + parameters: + - name: organisation_pk + in: path + required: true + schema: + type: integer + responses: + '200': + description: '' + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/ProxyKey' + security: + - tokenAuth: [] + - Master API Key: [] + tags: + - Organisations + post: + operationId: api_v1_organisations_edge_proxy_keys_create + parameters: + - name: organisation_pk + in: path + required: true + schema: + type: integer + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ProxyKey' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/ProxyKey' + multipart/form-data: + schema: + $ref: '#/components/schemas/ProxyKey' + responses: + '201': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/ProxyKey' + security: + - tokenAuth: [] + - Master API Key: [] + tags: + - Organisations + '/api/v1/organisations/{organisation_pk}/edge-proxy/keys/{prefix}/': + get: + operationId: api_v1_organisations_edge_proxy_keys_retrieve + parameters: + - name: organisation_pk + in: path + required: true + schema: + type: integer + - name: prefix + in: path + required: true + schema: + type: string + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/ProxyKey' + security: + - tokenAuth: [] + - Master API Key: [] + tags: + - Organisations + put: + operationId: api_v1_organisations_edge_proxy_keys_update + parameters: + - name: organisation_pk + in: path + required: true + schema: + type: integer + - name: prefix + in: path + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ProxyKey' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/ProxyKey' + multipart/form-data: + schema: + $ref: '#/components/schemas/ProxyKey' + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/ProxyKey' + security: + - tokenAuth: [] + - Master API Key: [] + tags: + - Organisations + patch: + operationId: api_v1_organisations_edge_proxy_keys_partial_update + parameters: + - name: organisation_pk + in: path + required: true + schema: + type: integer + - name: prefix + in: path + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/PatchedProxyKey' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchedProxyKey' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchedProxyKey' + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/ProxyKey' + security: + - tokenAuth: [] + - Master API Key: [] + tags: + - Organisations + delete: + operationId: api_v1_organisations_edge_proxy_keys_destroy + parameters: + - name: organisation_pk + in: path + required: true + schema: + type: integer + - name: prefix + in: path + required: true + schema: + type: string + responses: + '204': + description: No response body + security: + - tokenAuth: [] + - Master API Key: [] + tags: + - Organisations '/api/v1/organisations/{organisation_pk}/github/create-cleanup-issue/': post: operationId: api_v1_organisations_github_create_cleanup_issue_create @@ -18644,6 +18817,20 @@ paths: - Master API Key: [] tags: - Projects + /api/v1/proxy/config/: + get: + operationId: api_v1_proxy_config_list + responses: + '200': + description: '' + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/EdgeProxyEnvironment' + tags: + - Other '/api/v1/segments/get-by-uuid/{uuid}/': get: operationId: api_v1_segments_get_by_uuid_retrieve @@ -20542,6 +20729,48 @@ components: required: - multivariate_feature_option - percentage_allocation + EdgeProxyEnvironment: + type: object + properties: + id: + type: integer + readOnly: true + name: + type: string + maxLength: 2000 + client_side_key: + type: string + server_side_keys: + type: array + items: + $ref: '#/components/schemas/EdgeProxyServerSideKey' + updated_at: + description: 'Tracks changes to self and related entities, e.g. FeatureStates.' + type: string + format: date-time + project_id: + type: integer + readOnly: true + organisation_id: + type: integer + readOnly: true + required: + - client_side_key + - name + - server_side_keys + EdgeProxyServerSideKey: + type: object + properties: + key: + type: string + maxLength: 100 + active: + type: boolean + expires_at: + type: + - string + - 'null' + format: date-time EdgeV2MigrationStatusEnum: description: |- * `NOT_STARTED` - Not Started @@ -25549,6 +25778,47 @@ components: enforce_feature_owners: description: Require at least one user or group owner when creating a feature. type: boolean + PatchedProxyKey: + type: object + properties: + prefix: + type: string + readOnly: true + created: + type: string + format: date-time + readOnly: true + name: + description: A free-form name for the API key. Need not be unique. 50 characters max. + type: string + maxLength: 50 + revoked: + description: 'If the API key is revoked, clients cannot use it anymore. (This cannot be undone.)' + type: boolean + expiry_date: + description: 'Once API key expires, clients cannot use it anymore.' + type: + - string + - 'null' + format: date-time + title: Expires + key: + description: 'Only the key hash is stored, so the full key is returned on create and is unavailable from every other endpoint.' + type: string + readOnly: true + has_expired: + type: boolean + readOnly: true + created_by_user: + type: + - integer + - 'null' + readOnly: true + created_by_master_api_key: + type: + - string + - 'null' + readOnly: true PatchedReleasePipeline: type: object properties: @@ -27080,6 +27350,47 @@ components: type: boolean required: - name + ProxyKey: + type: object + properties: + prefix: + type: string + readOnly: true + created: + type: string + format: date-time + readOnly: true + name: + description: A free-form name for the API key. Need not be unique. 50 characters max. + type: string + maxLength: 50 + revoked: + description: 'If the API key is revoked, clients cannot use it anymore. (This cannot be undone.)' + type: boolean + expiry_date: + description: 'Once API key expires, clients cannot use it anymore.' + type: + - string + - 'null' + format: date-time + title: Expires + key: + description: 'Only the key hash is stored, so the full key is returned on create and is unavailable from every other endpoint.' + type: string + readOnly: true + has_expired: + type: boolean + readOnly: true + created_by_user: + type: + - integer + - 'null' + readOnly: true + created_by_master_api_key: + type: + - string + - 'null' + readOnly: true ReleasePipeline: type: object properties: