Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
473694b
feat: add SNS/SQS messaging infrastructure for hyperfleet notifications
rrp-bot Jul 23, 2026
ec7c43d
fix: use kms:GenerateDataKey* wildcard in messaging KMS policies
rrp-bot Jul 27, 2026
b203d7d
fix: close missing ] in AllowSNSDelivery Action array in KMS key poli…
rrp-bot Jul 27, 2026
b7b4a3c
chore: update kube-applier-aws and hyperfleet-operator image tags
rrp-bot Jul 27, 2026
ceac24d
fix: remove invalid sns:Unsubscribe action from MC status topic policy
rrp-bot Jul 27, 2026
e62760f
feat: wire kube-applier SQS queue URL and SNS topic ARN through annot…
rrp-bot Jul 27, 2026
b2e970d
feat: symmetric SNS/SQS wiring via CLI subscriptions in register.sh
rrp-bot Jul 27, 2026
d7d61ab
feat: wire --sqs-queue-url-prefix to hyperfleet-operator via Applicat…
rrp-bot Jul 28, 2026
9b2f032
fix: use correct image repo quay.io/openshift-online/hyperfleet-operator
rrp-bot Jul 28, 2026
82bfa59
fix: use rrp-bot quay org for hyperfleet-operator image
rrp-bot Jul 28, 2026
629c9a2
fix: revert to quay.io/psav/hyperfleet-operator image repo
rrp-bot Jul 28, 2026
af5a39c
fix: add AllowRCAccountSubscribe to RC specs SNS topic policy
rrp-bot Jul 30, 2026
5406f00
chore: bump image tags to latest feature/sns-sqs tips
rrp-bot Jul 30, 2026
8f3419b
fix: allow RC account SNS service to use MC KMS key for cross-account…
rrp-bot Jul 30, 2026
ff2d46f
fix: subscribe specs SNS→SQS from MC account for auto-confirmation
rrp-bot Jul 30, 2026
f7d3832
chore: bump hyperfleet-operator image tag to c756faf
rrp-bot Jul 30, 2026
d1b2919
chore: bump kube-applier image tag to 1f8498a (add SNS publish logging)
rrp-bot Jul 31, 2026
7ea3944
fix(rc-messaging): allow MC account in RC KMS AllowSNSDelivery
rrp-bot Jul 31, 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
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ spec:
- --leader-election-id={{ .Values.kubeApplier.config.leaderElectionId }}
- --log-verbosity={{ .Values.kubeApplier.config.logVerbosity }}
- --exit-on-panic={{ .Values.kubeApplier.config.exitOnPanic }}
{{- if .Values.kubeApplier.config.sqsQueueUrl }}
- --sqs-queue-url={{ .Values.kubeApplier.config.sqsQueueUrl }}
{{- end }}
{{- if .Values.kubeApplier.config.snsStatusTopicArn }}
- --sns-status-topic-arn={{ .Values.kubeApplier.config.snsStatusTopicArn }}
{{- end }}
{{- if .Values.kubeApplier.config.awsEndpointUrl }}
- --aws-endpoint-url={{ .Values.kubeApplier.config.awsEndpointUrl }}
{{- end }}
Expand Down
7 changes: 6 additions & 1 deletion argocd/config/management-cluster/kube-applier/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ kubeApplier:
image:
registry: "quay.io"
repository: "psav/kube-applier-aws"
tag: "remove-delete-desire-18d87ca"
tag: "1f8498a"
pullPolicy: IfNotPresent

# Deployment configuration
Expand All @@ -32,6 +32,11 @@ kubeApplier:
leaderElectionId: "kube-applier"
logVerbosity: 4
exitOnPanic: true
# Injected at runtime from the local-cluster-identity secret annotations via the
# ArgoCD ApplicationSet valuesObject. These must not be set here — they are always
# provided by the annotation pattern (MC Terraform output → bootstrap → annotation → here).
sqsQueueUrl: ""
snsStatusTopicArn: ""

serviceAccount:
name: kube-applier
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ spec:
{{- end }}
awsRegion: {{ .Values.global.aws_region }}
baseDomain: {{ .Values.baseDomain }}
sqsQueueUrlPrefix: {{ index .Values "hyperfleet" "sqsQueueUrlPrefix" | default "" | quote }}
postgres:
secretName: {{ .Values.global.postgres_secret_name | default "hyperfleet-db-dsn" }}
secretKey: {{ .Values.global.postgres_secret_key | default "dsn" }}
Expand Down
12 changes: 7 additions & 5 deletions argocd/config/regional-cluster/hyperfleet/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ hyperfleet:

project: default

sqsQueueUrlPrefix: ""

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate hyperfleet manifests =="
fd -a 'values.yaml|application.yaml|applicationset.yaml' . | rg 'argocd/config/regional-cluster/hyperfleet|argocd/config/regional-cluster|argocd/config' | sed 's#^\./##' | sort

echo
echo "== changed files/stat =="
git diff --stat || true

echo
echo "== relevant hyperfleet files =="
for f in argocd/config/regional-cluster/hyperfleet/values.yaml argocd/config/regional-cluster/hyperfleet/templates/application.yaml; do
  if [ -f "$f" ]; then
    echo "--- $f ($(wc -l < "$f") lines) ---"
    cat -n "$f"
  fi
done

echo
echo "== search sqsQueueUrlPrefix in argocd config =="
rg -n "sqsQueueUrlPrefix|sqs|sns" argocd/config || true

echo
echo "== search sqsQueueUrlPrefix in repository =="
rg -n "sqsQueueUrlPrefix|sqsQueueUrl|queueUrlPrefix" . || true

Repository: openshift-online/rosa-hyperfleet

Length of output: 8968


Populate the regional SQS queue URL prefix.

argocd/config/regional-cluster/hyperfleet/templates/application.yaml passes this value directly to the Hyperfleet chart, and no overlay overrides it. Sync renders the regional Application with an empty sqsQueueUrlPrefix, so the Hyperfleet SQS messaging path does not receive the required queue URL prefix. Populate this regional cluster value from the Terraform/bootstrap SQS queue-name output or the same source used for the regional bootstrap ApplicationSet.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@argocd/config/regional-cluster/hyperfleet/values.yaml` at line 10, Update the
regional Hyperfleet values entry sqsQueueUrlPrefix from its empty default to the
Terraform/bootstrap SQS queue-name output or the existing source used by the
regional bootstrap ApplicationSet, so templates/application.yaml passes the
required regional queue URL prefix to the Hyperfleet chart.

Source: Path instructions


source:
repoURL: https://github.com/typeid/hyperfleet-operator.git
targetRevision: main
path: charts/hyperfleet-operator
repoURL: https://github.com/rrp-bot/rosa-hyperfleet-api.git
targetRevision: feature/sns-sqs
path: hyperfleet-operator/charts
Comment on lines 12 to +15

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== candidate file =="
if [ -f argocd/config/regional-cluster/hyperfleet/values.yaml ]; then
  nl -ba argocd/config/regional-cluster/hyperfleet/values.yaml | sed -n '1,40p'
else
  echo "missing argocd/config/regional-cluster/hyperfleet/values.yaml"
fi

echo
echo "== all rosa-hyperfleet-api refs in argocd =="
rg -n "rosa-hyperfleet-api|source:|repoURL:|targetRevision:" argocd/config/regional-cluster || true

echo
echo "== git status/stat =="
git status --short

Repository: openshift-online/rosa-hyperfleet

Length of output: 231


🌐 Web query:

GitHub rrp-bot rosa-hyperfleet-api fork openshift-online rosa-hyperfleet-api

💡 Result:

The term "rrp-bot" refers to a GitHub bot (frequently appearing as rrp-bot in contribution lists) associated with the ROSA Regional Platform (RRP) ecosystem managed under the openshift-online organization [1]. The repository openshift-online/rosa-hyperfleet-api (also referred to as rosa-regional-platform-api in some contexts) is a project focused on ROSA Hosted Control Plane (HCP) regional cluster management [2][3]. It provides a stateless gateway API, an operator for managing Kubernetes custom resources (such as Cluster, NodePool, and Placement), and a library for PostgreSQL-backed controller-runtime operations [2][3]. Regarding the "fork" mentioned in your query: - There is an organization named rosa-regional-platform-ci that contains repositories named rosa-hyperfleet, rosa-hyperfleet-api, and rosa-hyperfleet-cli [4]. These repositories appear to be forks or mirrors created for Continuous Integration (CI) purposes within the OpenShift ecosystem [5][4]. - The main authoritative source for these components is the openshift-online organization (e.g., github.com/openshift-online/rosa-hyperfleet-api and github.com/openshift-online/rosa-regional-platform-api) [2][3]. It is also important to distinguish this from openshift-hyperfleet/hyperfleet-api, which is a separate repository [6]. While sharing similar naming conventions, the openshift-hyperfleet project serves as a general-purpose, event-driven data layer for cluster lifecycle management, distinct from the specific ROSA-focused RRP project [6].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== argocd file excerpt =="
while IFS= read -r line; do
  printf '%s\n' "$line"
done < argocd/config/regional-cluster/hyperfleet/values.yaml | sed -n '1,60p'

echo
echo "== rosa-hyperfleet-api refs in argocd =="
rg -n "rosa-hyperfleet-api|repoURL:|targetRevision:" argocd/config/regional-cluster || true

echo
echo "== git status/stat =="
git status --short

Repository: openshift-online/rosa-hyperfleet

Length of output: 1492


Pin the Argo CD chart source to an approved immutable revision.

This config points to the mutable feature/sns-sqs branch on rrp-bot/rosa-hyperfleet-api, while openshift-online/rosa-hyperfleet-api is the canonical repository. Automated sync can later deploy branch changes without a change in this repository. Use the approved source repository and an immutable commit SHA for Argo CD chart source pinning.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@argocd/config/regional-cluster/hyperfleet/values.yaml` around lines 12 - 15,
Update the Argo CD source configuration in values.yaml to use the canonical
openshift-online/rosa-hyperfleet-api repository instead of rrp-bot, and replace
the mutable feature/sns-sqs targetRevision with the approved immutable commit
SHA. Keep the existing hyperfleet-operator/charts path unchanged.

Sources: Coding guidelines, Path instructions


syncPolicy:
automated:
Expand All @@ -19,8 +21,8 @@ hyperfleet:

helmValues:
image:
repository: quay.io/redhat-user-workloads/rosa-tenant/hyperfleet-operator
tag: "42e0e77996c25cd4daaf4655f7d882fff0724227"
repository: quay.io/psav/hyperfleet-operator
tag: "c756faf"
Comment on lines +24 to +25

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- values.yaml context ---\n'
if [ -f argocd/config/regional-cluster/hyperfleet/values.yaml ]; then
  nl -ba argocd/config/regional-cluster/hyperfleet/values.yaml | sed -n '1,80p'
else
  fd -a 'values.yaml' . | sed -n '1,50p'
fi

printf '\n--- image references nearby ---\n'
rg -n "repository:|tag:|pullPolicy:|quay.io/psav/hyperfleet-operator|c756faf" argocd/config/regional-cluster/hyperfleet || true

Repository: openshift-online/rosa-hyperfleet

Length of output: 261


🌐 Web query:

Quay.io repository immutable tags setting documentation

💡 Result:

As of August 2026, Red Hat Quay and Project Quay support image tag immutability, which prevents existing tags from being overwritten, modified, or deleted [1]. This feature can be managed via the Web UI or the API [1]. There are three primary ways to implement tag immutability: 1. Individual Tag Settings: Users with write access can manually toggle the immutable status of a specific tag using the Red Hat Quay UI or the API [1]. For the API, you can use the PUT /api/v1/repository/{repository}/tag/{tag} endpoint with the "immutable" boolean field [2][3]. Setting this to true marks the tag as immutable; unsetting it requires admin permissions [2][1][3]. 2. Immutability Policies: Administrators can define regex-based policies at the organization or repository level [1]. These policies automatically mark new tags that match the specified regex pattern as immutable [1]. This is configured via the Red Hat Quay v2 UI under the Immutability Policies tab or through the API endpoints under /api/v1/organization/{orgname}/immutabilitypolicy/ or /api/v1/repository/{repository}/immutabilitypolicy/ [4][1][5][6]. 3. Manifest Labels: During the build process, you can trigger immutability by including the quay.immutable=true label in your Containerfile or Dockerfile [1]. These features are enabled by the IMMUTABLE_TAGS feature flag within the Quay platform [5][6]. Note that older discussions suggesting Quay lacks this feature are outdated, as these capabilities were added in recent versions (e.g., version 3.17) [4][1][7][5].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- Quay metadata probe for tag and repo ---'
python3 - <<'PY'
import json
import urllib.request
import urllib.error

base = 'https://quay.io/api/v1/repository/psav/hyperfleet-operator/public'
urls = [
    base,
    base + '?includeTags=true&tag=100',
    base + '/tag/?specificTag=c756faf',
    'https://quay.io/api/v1/repository/psav/hyperfleet-operator/',
    'https://quay.io/api/v1/repository/psav/hyperfleet-operator/tag/?specificTag=c756faf&limit=10',
    'https://quay.io/api/v1/repository/psav/hyperfleet-operator/tag/?specificTag=c756faf',
]

for url in urls:
    print(f'\n### {url}')
    try:
        req = urllib.request.Request(url)
        req.add_header('Accept', 'application/json')
        with urllib.request.urlopen(req, timeout=20) as rsp:
            data = rsp.read()
        print(f'status={rsp.status}')
        text = data.decode('utf-8', 'replace')
        if text.startswith('{') or text.startswith('['):
            obj = json.loads(text)
            print('top_level_keys=', sorted(obj.keys()) if isinstance(obj, dict) else None)
            print(json.dumps(obj, indent=2, sort_keys=True)[:8000])
        else:
            print(text[:8000])
    except Exception as e:
        print(f'error={type(e).__name__}: {e}')
PY

Repository: openshift-online/rosa-hyperfleet

Length of output: 1636


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- values.yaml context ---'
if [ -f argocd/config/regional-cluster/hyperfleet/values.yaml ]; then
  nl -ba argocd/config/regional-cluster/hyperfleet/values.yaml | sed -n '1,80p'
else
  fd -a 'values.yaml' . | sed -n '1,50p'
fi || true

printf '%s\n' '--- image references nearby ---'
rg -n "repository:|tag:|pullPolicy:|quay.io/psav/hyperfleet-operator|c756faf" argocd/config/regional-cluster/hyperfleet || true

python3 - <<'PY'
import json
import ssl
import urllib.request
import urllib.error

ctx = ssl._create_unverified_context()

base = 'https://quay.io/api/v1/repository/psav/hyperfleet-operator/public'
urls = [
    base,
    base + '?includeTags=true&tag=100',
    base + '/tag/?specificTag=c756faf',
    'https://quay.io/api/v1/repository/psav/hyperfleet-operator/tag/?specificTag=c756faf&limit=10',
]

for url in urls:
    print(f'\n### {url}')
    try:
        req = urllib.request.Request(url)
        req.add_header('Accept', 'application/json')
        with urllib.request.urlopen(req, context=ctx, timeout=20) as rsp:
            data = rsp.read()
        print(f'status={rsp.status}')
        text = data.decode('utf-8', 'replace')
        if text.startswith('{') or text.startswith('['):
            obj = json.loads(text)
            print('top_level_keys=', sorted(obj.keys()) if isinstance(obj, dict) else None)
            print(json.dumps(obj, indent=2, sort_keys=True)[:10000])
        else:
            print(text[:10000])
    except Exception as e:
        print(f'error={type(e).__name__}: {e}')
PY

Repository: openshift-online/rosa-hyperfleet

Length of output: 1462


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- values.yaml excerpt ---'
awk 'NR>=1 && NR<=40 { printf "%6d  %s\n", NR, $0 }' argocd/config/regional-cluster/hyperfleet/values.yaml

python3 - <<'PY'
import json
import ssl
import urllib.request
import urllib.error

ctx = ssl._create_unverified_context()

urls = [
    'https://quay.io/api/v1/repository/psav/hyperfleet-operator/tag/?includeManifestSummary=true',
    'https://quay.io/api/v1/repository/psav/hyperfleet-operator/tag/?includeManifestSummary=true&limit=1',
]

for url in urls:
    print(f'\n### {url}')
    try:
        req = urllib.request.Request(url)
        req.add_header('Accept', 'application/json')
        with urllib.request.urlopen(req, context=ctx, timeout=20) as rsp:
            data = rsp.read()
        print(f'status={rsp.status}')
        obj = json.loads(data.decode('utf-8', 'replace'))
        tags = obj.get('tags', [])
        print('tag_count=', len(tags))
        for tag in tags[:5]:
            print('name=', tag.get('name'))
            ms = tag.get('manifest_summary') or {}
            print('  manifest_digest=', ms.get('manifest_digest'))
            for v in (tag.get('target') or {}).values() if isinstance(tag.get('target'), dict) else []:
                if isinstance(v, dict):
                    print('  image_digest=', v.get('manifest_digest'))
        if tags:
            print('manifest_deduplicated=', sorted(set(t.get('manifest_summary', {}).get('manifest_digest') for t in tags)))
    except Exception as e:
        print(f'error={type(e).__name__}: {e}')
PY

Repository: openshift-online/rosa-hyperfleet

Length of output: 1764


Pin the hyperfleet-operator image to an immutable digest.

c756faf is a tag, not a digest. With pullPolicy: Always, each pod start resolves the current content behind that tag unless immutability is separately enforced in Quay. Use quay.io/psav/hyperfleet-operator@sha256:<digest> for the pinned pull target.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@argocd/config/regional-cluster/hyperfleet/values.yaml` around lines 24 - 25,
Update the hyperfleet-operator image reference in the values configuration to
use an immutable quay.io digest with the sha256 format instead of the mutable
c756faf tag, preserving the existing repository and ensuring the deployment
pulls the pinned image content.

pullPolicy: Always

replicaCount: 2
Expand Down
12 changes: 12 additions & 0 deletions config/templates/argocd-bootstrap/applicationset.yaml.j2
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,18 @@ spec:
sre:
targetGroup:
arn: '{{ '{{ .metadata.annotations.sre_prometheus_target_group_arn }}' }}'
{%- if cluster_type == 'management-cluster' %}

kubeApplier:
config:
sqsQueueUrl: '{{ '{{ .metadata.annotations.kube_applier_specs_queue_url }}' }}'
snsStatusTopicArn: '{{ '{{ .metadata.annotations.kube_applier_status_topic_arn }}' }}'
{%- endif %}
{%- if cluster_type == 'regional-cluster' %}

hyperfleet:
sqsQueueUrlPrefix: 'https://sqs.{{ '{{ .metadata.labels.aws_region }}' }}.amazonaws.com/{{ '{{ .metadata.annotations.aws_account_id }}' }}/{{ '{{ .metadata.labels.cluster_name }}' }}-hyperfleet-operator-'
{%- endif %}
path: '{{ '{{ .path.path }}' }}'
repoURL: '{{ '{{ .metadata.annotations.git_repo }}' }}'
targetRevision: '{% if pinned %}{{ pinned }}{% else %}{{ "{{ .metadata.annotations.git_revision }}" }}{% endif %}'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,11 @@ spec:
sre:
targetGroup:
arn: '{{ .metadata.annotations.sre_prometheus_target_group_arn }}'

kubeApplier:
config:
sqsQueueUrl: '{{ .metadata.annotations.kube_applier_specs_queue_url }}'
snsStatusTopicArn: '{{ .metadata.annotations.kube_applier_status_topic_arn }}'
path: '{{ .path.path }}'
repoURL: '{{ .metadata.annotations.git_repo }}'
targetRevision: '{{ .metadata.annotations.git_revision }}'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,9 @@ spec:
sre:
targetGroup:
arn: '{{ .metadata.annotations.sre_prometheus_target_group_arn }}'

hyperfleet:
sqsQueueUrlPrefix: 'https://sqs.{{ .metadata.labels.aws_region }}.amazonaws.com/{{ .metadata.annotations.aws_account_id }}/{{ .metadata.labels.cluster_name }}-hyperfleet-operator-'
path: '{{ .path.path }}'
repoURL: '{{ .metadata.annotations.git_repo }}'
targetRevision: '{{ .metadata.annotations.git_revision }}'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,11 @@ spec:
sre:
targetGroup:
arn: '{{ .metadata.annotations.sre_prometheus_target_group_arn }}'

kubeApplier:
config:
sqsQueueUrl: '{{ .metadata.annotations.kube_applier_specs_queue_url }}'
snsStatusTopicArn: '{{ .metadata.annotations.kube_applier_status_topic_arn }}'
path: '{{ .path.path }}'
repoURL: '{{ .metadata.annotations.git_repo }}'
targetRevision: '{{ .metadata.annotations.git_revision }}'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,9 @@ spec:
sre:
targetGroup:
arn: '{{ .metadata.annotations.sre_prometheus_target_group_arn }}'

hyperfleet:
sqsQueueUrlPrefix: 'https://sqs.{{ .metadata.labels.aws_region }}.amazonaws.com/{{ .metadata.annotations.aws_account_id }}/{{ .metadata.labels.cluster_name }}-hyperfleet-operator-'
path: '{{ .path.path }}'
repoURL: '{{ .metadata.annotations.git_repo }}'
targetRevision: '{{ .metadata.annotations.git_revision }}'
Expand Down
13 changes: 12 additions & 1 deletion scripts/bootstrap-argocd.sh
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,15 @@ else
REDIS_ENDPOINT=""
fi

KUBE_APPLIER_SQS_QUEUE_URL="${KUBE_APPLIER_SQS_QUEUE_URL:-}"
KUBE_APPLIER_SNS_STATUS_TOPIC_ARN="${KUBE_APPLIER_SNS_STATUS_TOPIC_ARN:-}"

# For management clusters, read the messaging outputs from terraform state.
if [[ "$CLUSTER_TYPE" == "management-cluster" ]]; then
KUBE_APPLIER_SQS_QUEUE_URL=$(echo "$OUTPUTS" | jq -r '.kube_applier_specs_queue_url.value // ""')
KUBE_APPLIER_SNS_STATUS_TOPIC_ARN=$(echo "$OUTPUTS" | jq -r '.kube_applier_status_topic_arn.value // ""')
fi

RHOBS_API_URL="${RHOBS_API_URL:-}"
DNS_ZONE_OPERATOR_ROLE_ARN="${DNS_ZONE_OPERATOR_ROLE_ARN:-}"

Expand Down Expand Up @@ -149,7 +158,9 @@ RUN_TASK_OUTPUT=$(aws ecs run-task \
{\"name\": \"SRE_THANOS_TARGET_GROUP_ARN\", \"value\": \"$SRE_THANOS_TARGET_GROUP_ARN\"},
{\"name\": \"SRE_ALB_DNS_NAME\", \"value\": \"$SRE_ALB_DNS_NAME\"},
{\"name\": \"SRE_DOMAIN\", \"value\": \"$SRE_DOMAIN\"},
{\"name\": \"REDIS_ENDPOINT\", \"value\": \"$REDIS_ENDPOINT\"}
{\"name\": \"REDIS_ENDPOINT\", \"value\": \"$REDIS_ENDPOINT\"},
{\"name\": \"KUBE_APPLIER_SQS_QUEUE_URL\", \"value\": \"$KUBE_APPLIER_SQS_QUEUE_URL\"},
{\"name\": \"KUBE_APPLIER_SNS_STATUS_TOPIC_ARN\", \"value\": \"$KUBE_APPLIER_SNS_STATUS_TOPIC_ARN\"}
]
}]
}" 2>&1)
Expand Down
2 changes: 2 additions & 0 deletions scripts/buildspec/provision-kube-applier-dynamodb.sh
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ export TF_VAR_mc_aws_account_id="${TARGET_ACCOUNT_ID}"
export TF_VAR_rc_id="${_RC_REGIONAL_ID}"
TF_VAR_enable_pitr=$(parseBool '.kube_applier_dynamodb_enable_pitr' false "$DEPLOY_CONFIG_FILE")
export TF_VAR_enable_pitr
TF_VAR_operator_replica_count=$(jq -r '.operator_replica_count // 3' "$DEPLOY_CONFIG_FILE")
export TF_VAR_operator_replica_count
export TF_VAR_app_code="${APP_CODE}"
export TF_VAR_service_phase="${SERVICE_PHASE}"
export TF_VAR_cost_center="${COST_CENTER}"
Expand Down
56 changes: 56 additions & 0 deletions scripts/buildspec/register.sh
Original file line number Diff line number Diff line change
Expand Up @@ -155,3 +155,59 @@ if [ "$REG_OK" != "true" ]; then
cat /tmp/register-response.json >&2
exit 1
fi

# Wire SNS→SQS subscriptions.
#
# Both subscriptions are created here — after API registration succeeds — because
# this is the first point in the pipeline where all four resources are guaranteed
# to exist:
# Stage 1 (Deploy MC): MC SQS + MC SNS created
# Stage 2 (Provision-KubeApplier-DynamoDB): RC SNS + RC SQS created
# Stage 4 (Register, this script): safe to subscribe
#
# AWS only auto-confirms an SNS→SQS subscription when the caller is from the
# same account as the queue. The two subscriptions therefore need different
# caller identities:
#
# Specs (RC SNS → MC SQS): call subscribe from the MC account, which owns
# the specs SQS queue. The RC specs topic policy grants sns:Subscribe to
# the MC account root (AllowMCAccountSubscribe).
#
# Status (MC SNS → RC SQS): call subscribe from the RC account, which owns
# the status SQS queues. The MC status topic policy grants sns:Subscribe
# to the RC account root (AllowRCAccountSubscribe).
#
# AWS automatically removes subscriptions when their SNS topic is deleted, so
# no explicit teardown is needed — Terraform destroying a topic cleans up its
# subscriptions for free.

SPECS_TOPIC_ARN="arn:aws:sns:${TARGET_REGION}:${RESOLVED_REGIONAL_ACCOUNT_ID}:${CLUSTER_ID}-specs-notifications"
SPECS_QUEUE_ARN="arn:aws:sqs:${TARGET_REGION}:${TARGET_ACCOUNT_ID}:${CLUSTER_ID}-specs-notifications"
STATUS_TOPIC_ARN="arn:aws:sns:${TARGET_REGION}:${TARGET_ACCOUNT_ID}:${CLUSTER_ID}-status-notifications"
OPERATOR_REPLICA_COUNT=$(jq -r '.operator_replica_count // 3' "$DEPLOY_CONFIG_FILE")

# Specs subscription: must be called from the MC account (queue owner).
echo "Subscribing specs queue to specs topic (as MC account)"
use_mc_account
aws sns subscribe \
--topic-arn "$SPECS_TOPIC_ARN" \
--protocol sqs \
--notification-endpoint "$SPECS_QUEUE_ARN" \
--attributes '{"RawMessageDelivery":"true"}' \
--region "$TARGET_REGION"

# Status subscriptions: must be called from the RC account (queue owner).
echo "Subscribing ${OPERATOR_REPLICA_COUNT} operator replica queue(s) to status topic (as RC account)"
use_rc_account
for i in $(seq 0 $((OPERATOR_REPLICA_COUNT - 1))); do
STATUS_QUEUE_ARN="arn:aws:sqs:${TARGET_REGION}:${RESOLVED_REGIONAL_ACCOUNT_ID}:${RC_REGIONAL_ID}-hyperfleet-operator-${i}"
echo " Subscribing replica ${i}: ${STATUS_QUEUE_ARN}"
aws sns subscribe \
--topic-arn "$STATUS_TOPIC_ARN" \
--protocol sqs \
--notification-endpoint "$STATUS_QUEUE_ARN" \
--attributes '{"RawMessageDelivery":"true"}' \
--region "$TARGET_REGION"
done

echo "SNS→SQS subscriptions wired successfully"
26 changes: 26 additions & 0 deletions terraform/config/kube-applier-dynamodb-provisioning/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,29 @@ resource "aws_iam_role_policy" "hyperfleet_operator_dynamodb" {
]
})
}

# =============================================================================
# kube-applier RC-side Messaging (SNS/SQS cross-account notifications)
#
# Creates the specs SNS topic in the RC account (the operator publishes here
# after writing a desire document) and the per-replica status SQS queues
# (the operator polls its own queue for status notifications from kube-applier).
#
# Both modules use predictable ARNs to reference cross-account resources, so
# there is no dependency on MC Terraform outputs and no count gate. Both sides
# can be provisioned independently in a single pipeline run.
#
# Subscriptions (RC SNS → MC SQS and MC SNS → RC SQS × N) are created by
# the Register buildspec step, after both modules have run and all four
# resources are guaranteed to exist.
# =============================================================================

module "kube_applier_rc_messaging" {
source = "../../modules/kube-applier-rc-messaging"

mc_name = var.mc_name
mc_aws_account_id = var.mc_aws_account_id
rc_id = var.rc_id
aws_region = var.region
operator_replica_count = var.operator_replica_count
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
17 changes: 17 additions & 0 deletions terraform/config/kube-applier-dynamodb-provisioning/outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,20 @@ output "status_readdesires_stream_arn" {
description = "Stream ARN for the status-readdesires table"
value = module.kube_applier_dynamodb.status_readdesires_stream_arn
}

# =============================================================================
# Messaging Outputs
# Read by the MC management-cluster buildspec to pass rc_specs_sns_topic_arn
# into the MC terraform run.
# =============================================================================

output "specs_sns_topic_arn" {
description = "ARN of the RC-account specs SNS topic for this MC (operator publishes here after writing a desire document)."
value = module.kube_applier_rc_messaging.specs_topic_arn
}

output "status_sqs_queue_urls" {
description = "URLs of the RC-account operator status SQS queues (one per replica)."
value = module.kube_applier_rc_messaging.status_queue_urls
}

Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,9 @@ variable "environment" {
description = "Environment name (staging, production, etc.)"
type = string
}

variable "operator_replica_count" {
description = "Number of hyperfleet-operator replicas. One status SQS queue is created per replica in the RC account."
type = number
default = 3
}
26 changes: 26 additions & 0 deletions terraform/config/management-cluster/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ module "ecs_bootstrap" {

repository_url = var.repository_url
repository_branch = var.repository_branch

kube_applier_specs_queue_url = module.kube_applier_mc_messaging.specs_queue_url
kube_applier_status_topic_arn = module.kube_applier_mc_messaging.status_topic_arn
}

# =============================================================================
Expand Down Expand Up @@ -187,3 +190,26 @@ module "kube_applier" {
rc_aws_account_id = var.regional_aws_account_id
aws_region = var.region
}

# =============================================================================
# kube-applier MC-side Messaging (SNS/SQS cross-account notifications)
#
# Creates the specs SQS queue (receives notifications from the RC specs SNS
# topic when the operator writes a new desire document) and the status SNS
# topic (kube-applier publishes here after writing a status document so the
# RC-side operator queues are notified immediately).
#
# rc_specs_sns_topic_arn is read from the RC kube-applier-dynamodb terraform
# state by the buildspec script and passed in as TF_VAR_rc_specs_sns_topic_arn.
# When empty (e.g. during initial bootstrap before the RC run completes) the
# module is skipped and messaging falls back to 5-minute safety polling.
# =============================================================================

module "kube_applier_mc_messaging" {
source = "../../modules/kube-applier-mc-messaging"

mc_name = var.management_id
rc_aws_account_id = var.regional_aws_account_id
eks_cluster_name = module.management_cluster.cluster_name
aws_region = var.region
}
Comment on lines +208 to +215

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Gate messaging with an explicit feature flag.

The optional module is enabled solely when an upstream ARN is non-empty. Add an enable_kube_applier_messaging boolean and require both the flag and ARN, so deployment intent is explicit and independently controllable. As per coding guidelines, optional infrastructure must be gated by explicit enable_* flags.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@terraform/config/management-cluster/main.tf` around lines 205 - 214, Add the
enable_kube_applier_messaging boolean input and update the
kube_applier_mc_messaging count condition to require both this flag and a
non-empty var.rc_specs_sns_topic_arn. Preserve the module’s existing
configuration while ensuring deployment is explicitly controlled by the feature
flag.

Source: Coding guidelines

24 changes: 24 additions & 0 deletions terraform/config/management-cluster/outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -166,3 +166,27 @@ output "kube_applier_role_arn" {
description = "IAM role ARN for the kube-applier-aws controller"
value = module.kube_applier.kube_applier_role_arn
}

# =============================================================================
# kube-applier Messaging Outputs
# Read by bootstrap-argocd.sh to wire the queue URL and topic ARN into the
# ArgoCD cluster secret annotations, which the ApplicationSet then passes
# to the kube-applier Helm chart as --sqs-queue-url and --sns-status-topic-arn.
# Also read by the RC kube-applier-dynamodb buildspec to wire cross-account
# SNS subscriptions.
# =============================================================================

output "kube_applier_specs_queue_arn" {
description = "ARN of the MC-side specs SQS queue (receives RC specs SNS notifications)."
value = module.kube_applier_mc_messaging.specs_queue_arn
}

output "kube_applier_specs_queue_url" {
description = "URL of the MC-side specs SQS queue (polled by kube-applier for spec change notifications)."
value = module.kube_applier_mc_messaging.specs_queue_url
}

output "kube_applier_status_topic_arn" {
description = "ARN of the MC-side status SNS topic (kube-applier publishes here after writing status)."
value = module.kube_applier_mc_messaging.status_topic_arn
}
1 change: 0 additions & 1 deletion terraform/config/management-cluster/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -138,4 +138,3 @@ variable "oidc_cloudfront_domain" {
type = string
default = ""
}

10 changes: 10 additions & 0 deletions terraform/modules/ecs-bootstrap/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,8 @@ resource "aws_ecs_task_definition" "bootstrap" {
sre_alb_dns_name: "$SRE_ALB_DNS_NAME"
sre_domain: "$SRE_DOMAIN"
redis_endpoint: "$REDIS_ENDPOINT"
kube_applier_specs_queue_url: "$KUBE_APPLIER_SQS_QUEUE_URL"
kube_applier_status_topic_arn: "$KUBE_APPLIER_SNS_STATUS_TOPIC_ARN"
type: Opaque
stringData:
name: in-cluster
Expand Down Expand Up @@ -298,6 +300,14 @@ resource "aws_ecs_task_definition" "bootstrap" {
{
name = "REDIS_ENDPOINT"
value = var.redis_endpoint
},
{
name = "KUBE_APPLIER_SQS_QUEUE_URL"
value = var.kube_applier_specs_queue_url
},
{
name = "KUBE_APPLIER_SNS_STATUS_TOPIC_ARN"
value = var.kube_applier_status_topic_arn
}
]

Expand Down
12 changes: 12 additions & 0 deletions terraform/modules/ecs-bootstrap/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,15 @@ variable "redis_endpoint" {
default = ""
}

variable "kube_applier_specs_queue_url" {
description = "URL of the MC-side SQS queue that kube-applier polls for spec change notifications. Written as an annotation on the local-cluster-identity secret so the ApplicationSet can pass it to the kube-applier Helm chart."
type = string
default = ""
}

variable "kube_applier_status_topic_arn" {
description = "ARN of the MC-side SNS topic that kube-applier publishes status updates to. Written as an annotation on the local-cluster-identity secret so the ApplicationSet can pass it to the kube-applier Helm chart."
type = string
default = ""
}

Loading