Skip to content

Commit

Permalink
enforce unique ids across environments and feature flags
Browse files Browse the repository at this point in the history
  • Loading branch information
mk-armah committed Jun 14, 2024
1 parent 8eaaa0e commit e4f4057
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 45 deletions.
24 changes: 14 additions & 10 deletions integrations/launchdarkly/.port/resources/port-app-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ deleteDependentEntities: true
resources:
- kind: project
selector:
query: "true"
query: 'true'
port:
entity:
mappings:
Expand All @@ -14,11 +14,11 @@ resources:
tags: .tags
- kind: flag
selector:
query: "true"
query: 'true'
port:
entity:
mappings:
identifier: .key
identifier: .key + "-" + .__projectKey
title: .name
blueprint: '"launchDarklyFeatureFlag"'
properties:
Expand All @@ -37,11 +37,11 @@ resources:
project: .__projectKey
- kind: environment
selector:
query: "true"
query: 'true'
port:
entity:
mappings:
identifier: .key
identifier: .key + "-" + .__projectKey
title: .name
blueprint: '"launchDarklyEnvironment"'
properties:
Expand All @@ -56,15 +56,19 @@ resources:
project: .__projectKey
- kind: flag-status
selector:
query: "true"
query: 'true'
port:
entity:
mappings:
identifier: . as $root | ._links.self.href | split("/") | last as $last | "\($last)-\($root.__environmentKey)"
title: . as $root | ._links.self.href | split("/") | last as $last | "\($last)-\($root.__environmentKey)"
identifier: >-
. as $root | ._links.self.href | split("/") | last as $last |
"\($last)-\($root.__environmentKey)"
title: >-
. as $root | ._links.self.href | split("/") | last as $last |
"\($last)-\($root.__environmentKey)"
blueprint: '"launchDarklyFFInEnvironment"'
properties:
status: .name
relations:
environment: .__environmentKey
featureFlag: ._links.self.href | split("/") | last
environment: .__environmentKey + "-" + .__projectKey
featureFlag: . as $input | $input._links.self.href | split("/") | .[-1] + "-" + $input.__projectKey
6 changes: 6 additions & 0 deletions integrations/launchdarkly/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

<!-- towncrier release notes start -->

# Port_Ocean 0.1.24 (2024-06-14)

### Improvements

- Enforced unique entitiy identification across enviroonments and feature flags (0.1.24)


# Port_Ocean 0.1.23 (2024-06-13)

Expand Down
35 changes: 16 additions & 19 deletions integrations/launchdarkly/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,15 +115,13 @@ async def get_paginated_projects(
async def get_paginated_environments(
self,
) -> AsyncGenerator[list[dict[str, Any]], None]:
batch_project = [project async for project in self.get_paginated_projects()]
tasks = [
self.fetch_environments_for_project(project)
for projects in batch_project
for project in projects
]
environments = await asyncio.gather(*tasks)
for environment_batch in environments:
yield environment_batch
async for projects in self.get_paginated_projects():
tasks = [
self.fetch_environments_for_project(project) for project in projects
]
environments = await asyncio.gather(*tasks)
for environment_batch in environments:
yield environment_batch

async def fetch_environments_for_project(
self, project: dict[str, Any]
Expand Down Expand Up @@ -169,6 +167,7 @@ async def fetch_statuses_from_environment(
{
**status,
"__environmentKey": environment["key"],
"__projectKey": environment["__projectKey"],
}
for status in statuses
]
Expand All @@ -177,16 +176,14 @@ async def fetch_statuses_from_environment(
async def get_paginated_feature_flags(
self,
) -> AsyncGenerator[list[dict[str, Any]], None]:
batch_project = [project async for project in self.get_paginated_projects()]
tasks = [
self.fetch_feature_flags_for_project(project)
for projects in batch_project
for project in projects
]

feature_flags_batches = await asyncio.gather(*tasks)
for feature_flags in feature_flags_batches:
yield feature_flags
async for projects in self.get_paginated_projects():
tasks = [
self.fetch_feature_flags_for_project(project) for project in projects
]

feature_flags_batches = await asyncio.gather(*tasks)
for feature_flags in feature_flags_batches:
yield feature_flags

async def fetch_feature_flags_for_project(
self, project: dict[str, Any]
Expand Down
48 changes: 33 additions & 15 deletions integrations/launchdarkly/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,38 @@ def initialize_client() -> LaunchDarklyClient:
)


async def enrich_resource_with_project(endpoint: str, kind: str) -> dict[str, Any]:
launchdarkly_client = initialize_client()

response = await launchdarkly_client.send_api_request(endpoint)
project_key = endpoint.split(f"/api/v2/{kind}s/")[1].split("/")[0]
environment_keys = list(response["environments"].keys())
response.update({"__projectKey": project_key, "__environment": environment_keys})
async def enrich_resource_with_project(
endpoint: str, kind: str, client: LaunchDarklyClient
) -> dict[str, Any]:
response = await client.send_api_request(endpoint)
project_key = (
endpoint.split(f"/api/v2/{kind}s/")[1].split("/")[0]
if kind == ObjectKind.FEATURE_FLAG
else endpoint.split("/api/v2/projects/")[1].split("/")[0]
)
response.update({"__projectKey": project_key})
return response


async def list_feature_flag_statuses(
feature_flag: dict[str, Any], client: LaunchDarklyClient
) -> list[dict[str, Any]]:

project_key, feature_flag_key = feature_flag["__projectKey"], feature_flag["key"]
response = await client.get_feature_flag_status(project_key, feature_flag_key)
environments = response["environments"]
response["__projectKey"] = project_key

enriched_records = []
for env_key, env_data in environments.items():
record = response.copy()
record["__environmentKey"] = env_key
record.update(env_data)
enriched_records.append(record)

return enriched_records


@ocean.on_resync(kind=ObjectKind.AUDITLOG)
async def on_resync_auditlog(kind: str) -> ASYNC_GENERATOR_RESYNC_TYPE:
launchdarkly_client = initialize_client()
Expand Down Expand Up @@ -68,7 +90,6 @@ async def on_resync_feature_flag_statuses(kind: str) -> ASYNC_GENERATOR_RESYNC_T
@ocean.router.post("/webhook")
async def handle_launchdarkly_webhook_request(data: dict[str, Any]) -> dict[str, Any]:
launchdarkly_client = initialize_client()

kind = data["kind"]
endpoint = data["_links"]["canonical"]["href"]

Expand All @@ -78,18 +99,15 @@ async def handle_launchdarkly_webhook_request(data: dict[str, Any]) -> dict[str,
await ocean.register_raw(kind, [item])

elif kind in [ObjectKind.FEATURE_FLAG, ObjectKind.ENVIRONMENT]:
item = await enrich_resource_with_project(endpoint, kind)
item = await enrich_resource_with_project(endpoint, kind, launchdarkly_client)

await ocean.register_raw(kind=kind, change=[item])

if kind == ObjectKind.FEATURE_FLAG:

await ocean.register_raw(
ObjectKind.FEATURE_FLAG_STATUS,
[
await launchdarkly_client.get_feature_flag_status(
item["__projectKey"], item["key"]
)
],
await list_feature_flag_statuses(item, launchdarkly_client),
)
logger.info("Launchdarkly webhook event processed")
return {"ok": True}
Expand All @@ -103,7 +121,7 @@ async def on_start() -> None:

if not ocean.integration_config.get("app_host"):
logger.warning(
"No app host provided, skipping webhook creation. "
"No app host provided, skipping webhook creation."
"Without setting up the webhook, the integration will not export live changes from Launchdarkly"
)
return
Expand Down
2 changes: 1 addition & 1 deletion integrations/launchdarkly/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "launchdarkly"
version = "0.1.23"
version = "0.1.24"
description = "Launchdarkly integration for Port"
authors = ["Michael Armah <[email protected]>"]

Expand Down

0 comments on commit e4f4057

Please sign in to comment.