Ctrl K
ComunidadGuías…

hack/helm-release-size.py

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/usr/bin/env python3
"""Estimate the size of the Secret in which Helm stores a release of a chart.

Helm's Secret storage driver keeps each revision as one Secret whose `release`
value is base64(gzip(json(release))), and the apiserver refuses a Secret whose
data exceeds 1 MiB. A chart that crosses it installs fine at first and then
fails the one upgrade whose rendered manifest is large enough, with "Secret
sh.helm.release.v1.<name>.vN is invalid: data: Too long". The stored release
carries the whole chart, not just what renders: every file under the chart dir
that is not Chart.yaml, values.yaml, values.schema.json, templates/ or charts/
ends up in chart.files, and the production path (the packages OCI artifact
loaded by helm-controller) applies no .helmignore, so tests/, images/ and the
Makefile are stored too.

This reproduces the encoding: the release JSON with the chart's templates and
files as base64 []byte, the rendered manifest read from stdin (`helm template`
output), gzip at Helm's BestCompression, then base64. It does not reproduce
Helm's gzip byte for byte or the runtime fields (info, config); measured
against real release Secrets it lands within about 0.5 percent, which is why
callers compare it to a threshold with headroom rather than to the limit.

Usage: helm template ... | helm-release-size.py <chart-dir> [--exclude GLOB ...]
Prints the estimated stored size in bytes.
"""
import base64
import fnmatch
import gzip
import json
import os
import sys

# Paths Helm keeps out of chart.files: they are stored elsewhere in the chart
# object (metadata, values, schema, templates) or are subcharts.
NOT_FILES = {"Chart.yaml", "Chart.lock", "values.yaml", "values.schema.json"}
NOT_FILE_DIRS = ("templates/", "charts/")


def go_json(obj):
    # encoding/json escapes <, > and & inside strings; match it so the size
    # does not drift with how much HTML-ish text a chart carries.
    s = json.dumps(obj, separators=(",", ":"), ensure_ascii=False)
    return s.replace("<", "\\u003c").replace(">", "\\u003e").replace("&", "\\u0026")


def main(argv):
    if len(argv) < 2:
        sys.stderr.write(__doc__)
        return 2
    chart = argv[1].rstrip("/")
    excludes = []
    rest = argv[2:]
    while rest:
        if rest[0] != "--exclude" or len(rest) < 2:
            sys.stderr.write("unexpected argument: %s\n" % rest[0])
            return 2
        excludes.append(rest[1])
        rest = rest[2:]

    templates, files = [], []
    for root, dirs, names in os.walk(chart):
        dirs.sort()
        for n in sorted(names):
            path = os.path.join(root, n)
            rel = os.path.relpath(path, chart)
            if any(fnmatch.fnmatch(rel, g) for g in excludes):
                continue
            with open(path, "rb") as fh:
                data = base64.b64encode(fh.read()).decode()
            entry = {"name": rel, "data": data}
            if rel.startswith("templates/"):
                templates.append(entry)
            elif rel in NOT_FILES or rel.startswith(NOT_FILE_DIRS):
                continue
            else:
                files.append(entry)

    def read(rel):
        p = os.path.join(chart, rel)
        return open(p).read() if os.path.exists(p) else ""

    release = {
        "name": "release",
        "info": {"status": "deployed", "description": "Upgrade complete"},
        "chart": {
            "metadata": {"name": os.path.basename(chart), "version": "0.0.0", "apiVersion": "v2"},
            "lock": None,
            "templates": templates,
            # Helm stores the parsed values and the user config; raw text with
            # comments and no config errs high (about 6 KB on
            # backupstrategy-controller), the safe side for a budget.
            "values": read("values.yaml"),
            "schema": base64.b64encode(read("values.schema.json").encode()).decode() or None,
            "files": files,
        },
        "manifest": sys.stdin.read(),
        "version": 1,
        "namespace": "default",
    }
    raw = go_json(release).encode()
    stored = base64.b64encode(gzip.compress(raw, compresslevel=9))
    print(len(stored))
    return 0


if __name__ == "__main__":
    sys.exit(main(sys.argv))