{"id":18640513,"url":"https://github.com/rexagod/opapl-iac","last_synced_at":"2025-07-18T16:05:53.345Z","repository":{"id":231434702,"uuid":"656600035","full_name":"rexagod/opapl-iac","owner":"rexagod","description":"Utilise Open Policy Agent Policy Language for Infrastructure as Code","archived":false,"fork":false,"pushed_at":"2023-06-27T16:58:06.000Z","size":31,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-05-17T15:39:02.007Z","etag":null,"topics":["kube-state-metrics"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rexagod.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2023-06-21T09:08:54.000Z","updated_at":"2023-06-27T17:08:18.000Z","dependencies_parsed_at":"2024-04-04T02:52:01.984Z","dependency_job_id":null,"html_url":"https://github.com/rexagod/opapl-iac","commit_stats":null,"previous_names":["rexagod/opapl-iac"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/rexagod/opapl-iac","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rexagod%2Fopapl-iac","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rexagod%2Fopapl-iac/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rexagod%2Fopapl-iac/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rexagod%2Fopapl-iac/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rexagod","download_url":"https://codeload.github.com/rexagod/opapl-iac/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rexagod%2Fopapl-iac/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265791658,"owners_count":23829164,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["kube-state-metrics"],"created_at":"2024-11-07T05:54:47.372Z","updated_at":"2025-07-18T16:05:53.290Z","avatar_url":"https://github.com/rexagod.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"## Utilize Open Policy Agent Policy Language for Infrastructure as Code (opapl-iac)\n\n### Problem statement\n\nThe current [kube-state-metrics]' [custom resource state] featureset is, to an extent, convoluted. It is not\npossible to completely understand every configuration construct that defines this featureset even after going\nthrough the documentation, owing to the side-effects stemming from unrelated changes, or due to the presence\nof multiple constructs that don't work together as expected under such circumstances.\n\n[kube-state-metrics]: https://github.com/kubernetes/kube-state-metrics\n[custom resource state]: https://github.com/kubernetes/kube-state-metrics/blob/main/docs/customresourcestate-metrics.md\n\nAs a result, folks have to [\"guess\"] the behaviour of these constructs to even jot down a basic configuration.\nAs the repository scales and supports more constructs, so will the abstract behaviours that define them, which\nin addition to being cumbersome for the maintainer, have also grown way too complex for the user, who just\nwants to specify a couple of labels and generate metrics entailing them.\n\n[\"guess\"]: https://github.com/kubernetes/kube-state-metrics/issues/2041#issuecomment-1595706154\n\nSuch a use case should not be this difficult to maintain and support.\n\n### Proposed solution\n\nThere have been mulitple suggestions around a solution for this problem. However, this section will talk\nabout the one that this document is a part of, i.e., using Rego in an IaC (infrastructure-as-code) manner to\ndefine the configuration, and give complete control and freedom back to the user, so they can choose to\nextract, format, and manipulate the metric's textual representation as they wish.\n\nThe idea is to expose the resolved GVRs (group-version-resources) that were fetched from the cluster to the\nuser's Rego stub, defined in the configuration. From thereon, users can utilize this data and take advantage of\n[Rego's standard library] to print out metrics exactly how they want.\n\n[Rego's standard library]: https://www.openpolicyagent.org/docs/latest/policy-reference/#rego-standard-library\n\nNonetheless, these metrics _will_ go through a validation check before being served, just so the [OpenMetrics]\nstandards are not violated.\n\n[OpenMetrics]: https://openmetrics.io/\n\n_(I assume we'll need to port the logic for the aforementioned validation, since the current implementation\nthat checks for the same is written in [Python]. For now this lives as a target in the Makefile.)_\n\n[Python]: https://github.com/prometheus/client_python/blob/master/prometheus_client/openmetrics/parser.py#L467\n\n### Usage\n\nThe configuration requires two fields, one that specifies the GVRs to resolve, and the other one that tells the\nparser how to operate on them.\n\n```yaml\ngroupVersionResource:\n  group: \"apps\"\n  version: \"v1\"\n  resource: \"deployments\"\nstub:\n  package stub\n\n  printer {\n      help := \"# HELP foo foo_help\";\n      type := \"# TYPE foo gauge\";\n      metrics := [sprintf(\"foo{namespace=\\\"%s\\\",name=\\\"%s\\\"} %d\", [d[\"metadata\"][\"namespace\"], d[\"metadata\"][\"name\"], d[\"spec\"][\"replicas\"]]) | d := input[_]];\n      out := sprintf(\"%s\\n%s\\n%s\", [help, type, concat(\"\\n\", metrics)]);\n      print(out)\n  }\n```\n\nThis will produce the following metrics.\n\n```yaml\n# HELP foo foo_help\n# TYPE foo gauge\nfoo{namespace=\"kube-system\",name=\"coredns\"} 2\nfoo{namespace=\"local-path-storage\",name=\"local-path-provisioner\"} 1\n# EOF\n```\n\n#### Simulating CRS featureset entirely by utilizing Rego code stubs\n\nA more-exhaustive [example] is shown below that reproduces all constructs defined in\n[`kube-state-metrics/customresourcestate-metrics.md`] using Rego stubs and generates metrics based on that.\n\n[example]: ./examples/ksm-crs.yaml\n[`kube-state-metrics/customresourcestate-metrics.md`]: https://github.com/kubernetes/kube-state-metrics/blob/main/docs/customresourcestate-metrics.md#multiple-metricskitchen-sink\n\n```yaml\ngroupVersionResource:\n  group: \"apps\"\n  version: \"v1\"\n  resource: \"deployments\"\nstub: |\n    package stub\n    import future.keywords.in\n\n    commonLabels := [{\"object_type\": \"native\"}] # For objects native to KSM.\n    labelsFromPathName := {\"name\": [\"metadata\", \"name\"]}\n    printer {\n        familyName := \"replica_count\"\n        familyHelp := sprintf(\"# HELP %s %s\", [familyName, \"number of replicas available\"])\n        familyType := sprintf(\"# TYPE %s %s\", [familyName, \"gauge\"])\n        path := [\"spec\"]\n        labelFromKeyRelative := {\"k8s\": [\"selector\", \"matchLabels\"]}\n        labelsFromPathRelative := {\"desired_count\": [\"replicas\"]}\n        valueFromNonRelative := [\"status\", \"availableReplicas\"]\n        customLabels := array.concat(commonLabels, [{\"custom_metric\": \"yes\"}])\n        unfurlFields := [[\"metadata\", \"labels\"], [\"metadata\", \"annotations\"]]\n        labelFormat := \"%s=\\\"%v\\\"\"\n        validationRegex := \"\\\\.|/|-\" # https://github.com/kubernetes/kube-state-metrics/pull/2004\n        resolvedPaths := [deployment[p] |\n            deployment := input[_]\n            p := path[_]\n        ]\n        resolvedFormattedLabelsFromPathNonRelative := [sprintf(labelFormat, [regex.replace(k, validationRegex, \"_\"), o]) |\n            some k, v in labelsFromPathName\n            o := object.get(input[_], v, false)\n        ]\n        resolvedFormattedLabelsFromPathRelative := [sprintf(labelFormat, [regex.replace(k, validationRegex, \"_\"), o]) |\n            some k, v in labelsFromPathRelative\n            o := object.get(resolvedPaths[_], v, false)\n        ]\n        resolvedFormattedLabelFromKeyRelative := [sprintf(labelFormat, [regex.replace(kk, validationRegex, \"_\"), vv]) | \n          some k, v in labelFromKeyRelative\n          o := object.get(resolvedPaths[_], v, false)\n          some kk, vv in o\n          startswith(kk, k)\n        ]\n        resolvedFormattedCustomLabels := [sprintf(labelFormat, [regex.replace(k, validationRegex, \"_\"), v]) |\n            el := customLabels[_]\n            some k, v in el\n        ]\n            resolvedUnfurlFields := [[o |\n            o := object.get(input[_], el, false)\n        ] |\n            el := unfurlFields[_]\n        ]\n        formattedResolvedUnfurlFields := [sprintf(labelFormat, [regex.replace(k, validationRegex, \"_\"), v]) |\n            el := resolvedUnfurlFields[_]\n            ell := el[_]\n            some k, v in ell\n        ]\n        values := [o | o := object.get(input[_], valueFromNonRelative, false)]\n        # Generate metrics: familyName{\u003clabelsFromPathRelative\u003e, \u003clabelFromKeyRelative\u003e, \u003ccustomLabels\u003e, \u003cunfurlFields\u003e} valueFromNonRelative\n        labelSets := [\n            resolvedFormattedLabelsFromPathRelative,\n            resolvedFormattedLabelFromKeyRelative,\n            resolvedFormattedCustomLabels,\n            formattedResolvedUnfurlFields,\n        ]\n        labelSet := [concat(\",\", arr) |\n            arr := labelSets[_]\n        ]\n        labels := concat(\",\", labelSet)\n        metricSet := [{sprintf(\"%s{%s}\", [familyName, dedup(withDeployment)]): value} | # https://www.openpolicyagent.org/docs/latest/extensions/#custom-built-in-functions-in-go\n            some i, v in resolvedFormattedLabelsFromPathNonRelative\n            value := values[i]\n            withDeployment := concat(\",\", [v, labels])\n        ]\n        metrics := [sprintf(\"%s %d\\n\", [metric, value]) | some metric,value in metricSet[_]]\n        out := sprintf(\"%s\\n%s\\n%s\", [familyHelp, familyType, concat(\"\", metrics)])\n        print(out)\n    }\n```\n\nThis will produce the following metrics.\n\n```yaml\n# HELP replica_count number of replicas available\n# TYPE replica_count gauge\nreplica_count{custom_metric=\"yes\",deployment_kubernetes_io_revision=\"1\",desired_count=\"1\",foo=\"bar\",k8s_app=\"kube-dns\",name=\"coredns\",object_type=\"native\"} 2\nreplica_count{custom_metric=\"yes\",deployment_kubernetes_io_revision=\"1\",desired_count=\"1\",foo=\"bar\",k8s_app=\"kube-dns\",name=\"local-path-provisioner\",object_type=\"native\"} 1\n# EOF\n```\n\n***\n\n###### This proof-of-concept was done for the [kube-state-metrics](https://github.com/kubernetes/kube-state-metrics) project.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frexagod%2Fopapl-iac","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frexagod%2Fopapl-iac","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frexagod%2Fopapl-iac/lists"}