Skip to content

Commit a83d42f

Browse files
fix(docker-to-sealos): omit pull secrets for public images (#34)
Allow public-image workloads to omit imagePullSecrets while still rejecting custom pull secret references when imagePullSecrets are declared. Update the converter, consistency rule, documentation, and tests to match the Sealos template behavior.
1 parent 0e966d8 commit a83d42f

10 files changed

Lines changed: 54 additions & 38 deletions

skills/docker-to-sealos/SKILL.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ If validation fails, fix template/rules/examples first.
140140
- Avoid floating tags (for example `:v2`, `:2.1`, `:stable`); use an explicit version tag or digest.
141141
- Managed workload image references must be concrete and must not contain Compose-style variable expressions (for example `${VAR}`, `${VAR:-default}`); resolve to explicit tag or digest before emitting template artifacts.
142142
- Application `originImageName` must match container image.
143-
- Managed app workloads must reference the app-scoped image pull Secret `${{ defaults.app_name }}` via `template.spec.imagePullSecrets`.
143+
- Public-image managed app workloads must omit `template.spec.imagePullSecrets`; private-registry workloads may reference only the app-scoped pull Secret `${{ defaults.app_name }}`.
144144
- The registry pull Secret is runtime-managed by `sealos-deploy` using local `gh` CLI credentials for private GHCR images; do not expose raw registry credential inputs in generated templates.
145145
- All containers must explicitly set `imagePullPolicy: IfNotPresent`.
146146

@@ -156,7 +156,7 @@ If validation fails, fix template/rules/examples first.
156156
- Non-database sensitive values/inputs use direct `env[].value`.
157157
- Business containers must source database connection fields (`endpoint`, `host`, `port`, `username`, `password`) from approved Kubeblocks database secrets via `env[].valueFrom.secretKeyRef`; exception: Redis `host`/`port` may use Sealos Redis Service FQDN and `6379` when the Redis secret only exposes credentials, and MongoDB connection URLs may use the Sealos MongoDB Service FQDN plus `27017` when the MongoDB secret exposes credentials only.
158158
- Business containers must not use custom env/volume `Secret` references except approved Kubeblocks database secrets and object storage secrets.
159-
- A dedicated app-scoped registry pull Secret is allowed and should be referenced only through `template.spec.imagePullSecrets`.
159+
- A dedicated app-scoped registry pull Secret is allowed only for private-registry images and must be referenced only through `template.spec.imagePullSecrets`; public images must not add pull secrets.
160160
- Database connection/bootstrap may use Kubeblocks-provided secrets, and reserved Kubeblocks database secret names must not be redefined by custom `Secret` resources.
161161
- Env vars must be declared before referenced (for example password before URL composition).
162162
- Follow official app env var naming; do not invent prefixes.

skills/docker-to-sealos/references/conversion-mappings.md

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -103,16 +103,14 @@ spec:
103103
template:
104104
spec:
105105
automountServiceAccountToken: false
106-
imagePullSecrets:
107-
- name: ${{ defaults.app_name }}
108106
containers:
109107
- name: ${{ defaults.app_name }}
110108
image: nginx:1.27.2
111109
imagePullPolicy: IfNotPresent # Must be set
112110
```
113111

114112
Notes:
115-
- Always reference the app-scoped image pull Secret `${{ defaults.app_name }}`.
113+
- Omit `imagePullSecrets` for public images. For private-registry images, reference only the app-scoped image pull Secret `${{ defaults.app_name }}`.
116114
- `sealos-deploy` should create or refresh that Secret automatically from local `gh` CLI credentials when deploying private GHCR images.
117115
- Reusable templates should not expose raw registry credential inputs as user-facing form fields.
118116

@@ -138,8 +136,6 @@ spec:
138136
template:
139137
spec:
140138
automountServiceAccountToken: false
141-
imagePullSecrets:
142-
- name: ${{ defaults.app_name }}
143139
containers:
144140
- name: ${{ defaults.app_name }}
145141
ports:
@@ -251,8 +247,6 @@ spec:
251247
template:
252248
spec:
253249
automountServiceAccountToken: false
254-
imagePullSecrets:
255-
- name: ${{ defaults.app_name }}
256250
containers:
257251
- name: ${{ defaults.app_name }}
258252
env:
@@ -369,8 +363,6 @@ spec:
369363
template:
370364
spec:
371365
automountServiceAccountToken: false
372-
imagePullSecrets:
373-
- name: ${{ defaults.app_name }}
374366
containers:
375367
- name: ${{ defaults.app_name }}
376368
volumeMounts:

skills/docker-to-sealos/references/example-guide.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -263,8 +263,6 @@ spec:
263263
app: ${{ defaults.app_name }}
264264
spec:
265265
automountServiceAccountToken: false
266-
imagePullSecrets:
267-
- name: ${{ defaults.app_name }}
268266
containers:
269267
- name: ${{ defaults.app_name }}
270268
image: yidadaa/chatgpt-next-web:v2.12.4

skills/docker-to-sealos/references/must-rules-map.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ must_rules:
132132
enforcement:
133133
type: rule
134134
target: R015
135-
- must: "Managed app workloads must reference the app-scoped image pull Secret `${{ defaults.app_name }}` via `template.spec.imagePullSecrets`."
135+
- must: "Public-image managed app workloads must omit `template.spec.imagePullSecrets`; private-registry workloads may reference only the app-scoped pull Secret `${{ defaults.app_name }}`."
136136
enforcement:
137137
type: rule
138138
target: R035
@@ -172,7 +172,7 @@ must_rules:
172172
enforcement:
173173
type: rule
174174
target: R007
175-
- must: "A dedicated app-scoped registry pull Secret is allowed and should be referenced only through `template.spec.imagePullSecrets`."
175+
- must: "A dedicated app-scoped registry pull Secret is allowed only for private-registry images and must be referenced only through `template.spec.imagePullSecrets`; public images must not add pull secrets."
176176
enforcement:
177177
type: rule
178178
target: R035

skills/docker-to-sealos/references/rules-registry.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ rules:
119119
description: Managed app workloads must explicitly set automountServiceAccountToken to false unless Kubernetes API/service account token usage is evidenced by integration settings, serviceAccountName, or a service-account-token reason annotation.
120120
severity: error
121121
- id: R035
122-
description: Managed app workloads must reference the app-scoped image pull secret name via template.spec.imagePullSecrets.
122+
description: Managed app workloads that declare imagePullSecrets must reference only the app-scoped pull secret name via template.spec.imagePullSecrets; public-image workloads may omit imagePullSecrets.
123123
severity: error
124124
- id: R011
125125
description: All PVC storage requests must be concrete values and less than or equal to 1Gi.

skills/docker-to-sealos/references/sealos-specs.md

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -453,8 +453,6 @@ spec:
453453
template:
454454
spec:
455455
automountServiceAccountToken: false
456-
imagePullSecrets:
457-
- name: ${{ defaults.app_name }}
458456
containers:
459457
- name: ${{ defaults.app_name }}
460458
volumeMounts:
@@ -832,17 +830,17 @@ All application Deployments or StatefulSets must include the following configura
832830

833831
1. **automountServiceAccountToken**: Must be set to `false` to avoid unnecessary permission exposure. Set it to `true` only when the application explicitly needs the Kubernetes API/service account token, evidenced by Kubernetes integration settings, `serviceAccountName`, or `sealos.io/service-account-token-reason` in workload annotations.
834832
2. **revisionHistoryLimit**: Must be set to `1` to reduce resources consumed by historical revisions
835-
3. **imagePullSecrets**: Must reference the app-scoped image pull Secret `${{ defaults.app_name }}`
833+
3. **imagePullSecrets**: Omit for public images. For private-registry images, reference only the app-scoped pull Secret `${{ defaults.app_name }}`
836834
4. **metadata.annotations**: Must include the following annotations:
837835
- `originImageName`: Original image name
838836
- `deploy.cloud.sealos.io/minReplicas`: Minimum replica count, typically set to `'1'`
839837
- `deploy.cloud.sealos.io/maxReplicas`: Maximum replica count, typically set to `'1'`
840838

841839
Recommended registry pull Secret model:
842840

843-
- Managed workloads reference `${{ defaults.app_name }}` in `imagePullSecrets`
844-
- `sealos-deploy` creates or refreshes that Secret automatically from local `gh` CLI credentials when the image is a private GHCR image
845-
- If the template is deployed outside `sealos-deploy`, the operator must create the Secret manually before applying the workload
841+
- Public-image managed workloads omit `imagePullSecrets`
842+
- For private GHCR images, `sealos-deploy` creates or refreshes `${{ defaults.app_name }}` from local `gh` CLI credentials and the workload may reference it through `imagePullSecrets`
843+
- If a private-registry template is deployed outside `sealos-deploy`, the operator must create the Secret manually before applying the workload
846844

847845
```yaml
848846
apiVersion: apps/v1
@@ -861,8 +859,6 @@ spec:
861859
template:
862860
spec:
863861
automountServiceAccountToken: false # Default; only set true with evidenced Kubernetes API token need
864-
imagePullSecrets:
865-
- name: ${{ defaults.app_name }}
866862
containers:
867863
- name: ${{ defaults.app_name }}
868864
# Other container configuration...
@@ -894,8 +890,6 @@ spec:
894890
app: ${{ defaults.app_name }}
895891
spec:
896892
automountServiceAccountToken: false # Disable automatic service account token mounting
897-
imagePullSecrets:
898-
- name: ${{ defaults.app_name }}
899893
containers:
900894
- name: ${{ defaults.app_name }}
901895
image: example/app:1.0.0

skills/docker-to-sealos/scripts/check_consistency_rules_app.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1826,6 +1826,12 @@ def check_image_pull_secret_refs(context: ScanContext) -> List[Violation]:
18261826
template_spec = get_template_spec(doc.data)
18271827
image_pull_secrets = template_spec.get("imagePullSecrets") if isinstance(template_spec, dict) else None
18281828

1829+
# Public images should omit imagePullSecrets entirely. When a private-registry
1830+
# workload does declare pull secrets, only the app-scoped runtime-managed
1831+
# secret is allowed so templates do not depend on undeclared custom secrets.
1832+
if image_pull_secrets is None:
1833+
continue
1834+
18291835
referenced_names: List[str] = []
18301836
if isinstance(image_pull_secrets, list):
18311837
for item in image_pull_secrets:
@@ -1835,7 +1841,7 @@ def check_image_pull_secret_refs(context: ScanContext) -> List[Violation]:
18351841
if isinstance(name, str) and name.strip():
18361842
referenced_names.append(name.strip())
18371843

1838-
if "${{ defaults.app_name }}" in referenced_names:
1844+
if referenced_names == ["${{ defaults.app_name }}"]:
18391845
continue
18401846

18411847
add_doc_violation(
@@ -1845,8 +1851,9 @@ def check_image_pull_secret_refs(context: ScanContext) -> List[Violation]:
18451851
pattern=r"^\s*imagePullSecrets\s*:",
18461852
default_pattern=r"^\s*template\s*:",
18471853
message=(
1848-
"managed app workloads must reference the app-scoped image pull secret "
1849-
"`${{ defaults.app_name }}` via template.spec.imagePullSecrets"
1854+
"imagePullSecrets may be omitted for public images; if declared for "
1855+
"private-registry workloads, it must reference only the app-scoped "
1856+
"secret `${{ defaults.app_name }}`"
18501857
),
18511858
)
18521859

skills/docker-to-sealos/scripts/compose_to_template.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2065,7 +2065,6 @@ def build_env_entries(
20652065
def build_workload(
20662066
*,
20672067
workload_name: str,
2068-
pull_secret_name: str,
20692068
image: str,
20702069
ports: Sequence[int],
20712070
env_entries: Sequence[Dict[str, Any]],
@@ -2077,7 +2076,6 @@ def build_workload(
20772076
kind = "StatefulSet" if is_stateful else "Deployment"
20782077
template_spec: Dict[str, Any] = {
20792078
"automountServiceAccountToken": False,
2080-
"imagePullSecrets": [{"name": pull_secret_name}],
20812079
"containers": [
20822080
{
20832081
"name": workload_name,
@@ -2319,7 +2317,6 @@ def build_documents(
23192317
app_services = service_items[:1]
23202318

23212319
db_hosts = {name: DB_FQDN_BY_TYPE[db_type] for name, db_type in db_services.items() if db_type in DB_FQDN_BY_TYPE}
2322-
pull_secret_name = "${{ defaults.app_name }}"
23232320

23242321
docs: List[Dict[str, Any]] = []
23252322
docs.append(build_template_resource(meta))
@@ -2369,7 +2366,6 @@ def build_documents(
23692366
probes = build_probe_pair(service, image, ports, command_args)
23702367
workload = build_workload(
23712368
workload_name=workload_name,
2372-
pull_secret_name=pull_secret_name,
23732369
image=image,
23742370
ports=ports,
23752371
env_entries=env_entries,

skills/docker-to-sealos/scripts/test_check_consistency.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2498,7 +2498,7 @@ def test_allows_registry_pull_secret_via_image_pull_secrets(self):
24982498
)
24992499
self.assertFalse(any(item.rule_id in {"R007", "R035"} for item in violations))
25002500

2501-
def test_detects_missing_registry_pull_secret_reference(self):
2501+
def test_allows_public_image_without_image_pull_secrets(self):
25022502
violations = self.run_checker(
25032503
"""
25042504
```yaml
@@ -2526,6 +2526,38 @@ def test_detects_missing_registry_pull_secret_reference(self):
25262526
```
25272527
"""
25282528
)
2529+
self.assertFalse(any(item.rule_id == "R035" for item in violations))
2530+
2531+
def test_detects_invalid_registry_pull_secret_reference(self):
2532+
violations = self.run_checker(
2533+
"""
2534+
```yaml
2535+
apiVersion: apps/v1
2536+
kind: Deployment
2537+
metadata:
2538+
name: demo
2539+
labels:
2540+
app: demo
2541+
cloud.sealos.io/app-deploy-manager: demo
2542+
annotations:
2543+
originImageName: registry.example.com/private/demo:1.0.0
2544+
spec:
2545+
revisionHistoryLimit: 1
2546+
template:
2547+
metadata:
2548+
labels:
2549+
app: demo
2550+
spec:
2551+
automountServiceAccountToken: false
2552+
imagePullSecrets:
2553+
- name: custom-pull-secret
2554+
containers:
2555+
- name: demo
2556+
image: registry.example.com/private/demo:1.0.0
2557+
imagePullPolicy: IfNotPresent
2558+
```
2559+
"""
2560+
)
25292561
self.assertTrue(any(item.rule_id == "R035" for item in violations))
25302562

25312563
def test_detects_object_storage_secret_misuse_on_non_s3_env(self):

skills/docker-to-sealos/scripts/test_compose_to_template.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -125,10 +125,7 @@ def test_generates_template_and_passes_consistency_rules(self):
125125
ingress["metadata"]["labels"]["cloud.sealos.io/app-deploy-manager"],
126126
)
127127
workload = next(doc for doc in docs if doc.get("kind") == "Deployment")
128-
self.assertEqual(
129-
[{"name": "${{ defaults.app_name }}"}],
130-
workload["spec"]["template"]["spec"]["imagePullSecrets"],
131-
)
128+
self.assertNotIn("imagePullSecrets", workload["spec"]["template"]["spec"])
132129
self.assertEqual(
133130
{
134131
"kubernetes.io/ingress.class": "nginx",

0 commit comments

Comments
 (0)