{"id":13509854,"url":"https://github.com/cruise-automation/isopod","last_synced_at":"2025-03-29T16:02:21.718Z","repository":{"id":35403330,"uuid":"206711402","full_name":"cruise-automation/isopod","owner":"cruise-automation","description":"An expressive DSL and framework for Kubernetes configuration without YAML","archived":false,"fork":false,"pushed_at":"2023-11-17T05:24:18.000Z","size":12943,"stargazers_count":483,"open_issues_count":20,"forks_count":32,"subscribers_count":24,"default_branch":"master","last_synced_at":"2025-03-22T14:05:21.764Z","etag":null,"topics":["configuration","go","golang","k8s","kubernetes","starlark"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cruise-automation.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-09-06T04:16:08.000Z","updated_at":"2025-03-07T20:19:43.000Z","dependencies_parsed_at":"2023-01-15T20:31:25.398Z","dependency_job_id":"959b134e-52bc-495b-a351-8d05a280a991","html_url":"https://github.com/cruise-automation/isopod","commit_stats":null,"previous_names":[],"tags_count":44,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cruise-automation%2Fisopod","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cruise-automation%2Fisopod/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cruise-automation%2Fisopod/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cruise-automation%2Fisopod/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cruise-automation","download_url":"https://codeload.github.com/cruise-automation/isopod/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246207492,"owners_count":20740723,"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":["configuration","go","golang","k8s","kubernetes","starlark"],"created_at":"2024-08-01T02:01:14.848Z","updated_at":"2025-03-29T16:02:21.687Z","avatar_url":"https://github.com/cruise-automation.png","language":"Go","funding_links":[],"categories":["Go","kubernetes","go","Users","\u003ca name=\"Go\"\u003e\u003c/a\u003eGo","Configuration Management"],"sub_categories":[],"readme":"Isopod\n======\n\n[![CircleCI](https://circleci.com/gh/cruise-automation/isopod.svg?style=shield)](https://circleci.com/gh/cruise-automation/isopod)\n[![Go Report Card](https://goreportcard.com/badge/github.com/cruise-automation/isopod)](https://goreportcard.com/report/github.com/cruise-automation/isopod)\n[![GitHub Release](https://img.shields.io/github/release/cruise-automation/isopod.svg)](https://github.com/cruise-automation/isopod/releases)\n[![GoDoc](https://godoc.org/github.com/cruise-automation/isopod?status.svg)](https://godoc.org/github.com/cruise-automation/isopod)\n\nIsopod is an expressive DSL framework for Kubernetes configuration. Without\nintermediate YAML artifacts, Isopod renders Kubernetes objects as [Protocol\nBuffers](https://github.com/protocolbuffers/protobuf), so they are strongly\ntyped and consumed directly by the Kubernetes API.\n\nWith Isopod, configurations are scripted in\n[Starlark](https://github.com/google/starlark-go), a Python dialect by Google\nalso used by [Bazel](https://github.com/bazelbuild/bazel) and\n[Buck](https://github.com/facebook/buck) build systems. Isopod offers runtime\nbuilt-ins to access services and utilities such as Vault secret management,\nKubernetes apiserver, HTTP requester, Base64 encoder, and UUID generator, etc.\nIsopod uses separate runtime for unit tests to mock all built-ins, providing the\ntest coverage not possible before.\n\nA 5-min read, [this medium](https://medium.com/cruise/isopod-5ad7c565d350) post\nexplains the inefficiency of existing YAML templating tools when dealing with values\nnot statically known and complicated control logics such as loops and branches.\nIt also gives simple code examples to show why Isopod is an expressive,\nhermetic, and extensible solution to configuration management in Kubernetes.\n\n---\n\n- [Isopod](#isopod)\n- [Build](#build)\n- [Main Entryfile](#main-entryfile)\n  - [Clusters](#clusters)\n      - [`gke()`](#gke)\n      - [`onprem()`](#onprem)\n  - [Addons](#addons)\n  - [Generate Addons](#generate-addons)\n- [Load Remote Isopod Modules](#load-remote-isopod-modules)\n- [Built-ins](#built-ins)\n  - [kube](#kube)\n    - [Methods:](#methods)\n      - [`kube.put`](#kubeput)\n      - [`kube.delete`](#kubedelete)\n      - [`kube.put_yaml`](#kubeput_yaml)\n      - [`kube.get`](#kubeget)\n      - [`kube.exists`](#kubeexists)\n      - [`kube.from_str`, `kube.from_int`](#kubefrom_str-kubefrom_int)\n  - [Vault](#vault)\n    - [Methods:](#methods-1)\n      - [`vault.read`](#vaultread)\n      - [`vault.write`](#vaultwrite)\n      - [`vault.exist`](#vaultexist)\n  - [Helm](#helm)\n    - [Methods:](#methods-2)\n      - [`helm.apply`](#helmapply)\n  - [Misc](#misc)\n      - [`base64.{encode, decode}`](#base64encode-decode)\n      - [`uuid.{v3, v4, v5}`](#uuidv3-v4-v5)\n      - [`http.{get, post, patch, put, delete}`](#httpget-post-patch-put-delete)\n      - [`hash.{sha256, sha1, md5}`](#hashsha256-sha1-md5)\n      - [`sleep`](#sleep)\n      - [`error`](#error)\n- [Testing](#testing)\n- [Dry Run Produces YAML Diffs](#dry-run-produces-yaml-diffs)\n  - [Diff filtering](#diff-filtering)\n- [License](#license)\n- [Contributions](#contributions)\n\n---\n\n# Build\n\n```shell\n$ go version\ngo version go1.14 darwin/amd64\n$ GO111MODULE=on go build\n```\n\n# Main Entryfile\n\nIsopod will call the `clusters(ctx)` function in the main Starlark file to get a\nlist of target clusters. For each of such clusters, isopod will call\n`addons(ctx)` to get a list of addons for configuration rollout.\n\nExample:\n\n```python\nCLUSTERS = [\n    onprem(env=\"dev\", cluster=\"minikube\", vaultkubeconfig=\"secret/path\"),\n    gke(\n        env=\"prod\",\n        cluster=\"paas-prod\",\n        location=\"us-west1\",\n        project=\"cruise-paas-prod\",\n        use_internal_ip=\"false\", # default to \"false\", which uses public endpoint\n    ),\n]\n\ndef clusters(ctx):\n    if ctx.cluster != None:\n        return [c for c in CLUSTERS if c.cluster == ctx.cluster]\n    elif ctx.env != None:\n        return [c for c in CLUSTERS if c.env == ctx.env]\n    return CLUSTERS\n\ndef addons(ctx)\n    return [\n        addon(\"ingress\", \"configs/ingress.ipd\", ctx),\n    ]\n```\n\n## Clusters\n\nThe `ctx` argument to `clusters(ctx)` comes from the command line flag\n`--context` to Isopod. This flag takes a comma-separated list of `foo=bar` and\nmakes these values available in Starlark as `ctx.foo` (which gives `\"bar\"`).\nCurrently Isopod supports the following clusters, and could easily be\nextended to cover other Kubernetes vendors, such as EKS and AKS.\n\n#### `gke()`\n\nRepresents a Google Kubernetes Engine. Authenticates using Google Cloud Service Account Credentials or Google Default Application Credentials. Requires the `cluster`, `location` and `project` fields, while optionally takes `use_internal_ip` field to connect API server via private endpoint. Additional fields are allowed.\n\n#### `onprem()`\n\nRepresents an on-premise or self-managed Kubernetes cluster. Authenticates using the `kubeconfig` file or Vault path containing the `kubeconfig`. No fields are required, though setting the `vaultkubeconfig` field to the path in Vault where the KubeConfig exists is necessary to utilize this auth method.\n\n\n## Addons\n\nThe `ctx` argument to `addons(ctx)` contains all fields of the chosen cluster. For example, say the cluster is\n\n```python\ngke(\n    env=\"prod\",\n    cluster=\"paas-prod\",\n    location=\"us-west1\",\n    project=\"cruise-paas-prod\",\n    use_internal_ip=\"false\", # default to \"false\", which uses public endpoint\n),\n```\n\nThen, each addon may access the cluster information as `ctx.env` to get `\"prod\"`\nand `ctx.location` to get `\"us-west1\"`. Accessing nonexistant attribute `ctx.foo` will get `None`.\n\nEach addon is represented using the `addon()` Starlark built-in, which takes\nthree arguments, for example `addon(\"name\", \"entry_file.ipd\", ctx)`. The first\nargument is the addon name, used by the `--match_addon` feature. The thrid\nis optional and represents the `ctx` input to `addons(ctx)` to make the cluster\nattributes available to the addon. Each addon must implement `install(ctx)` and\n`remove(ctx)` functions.\n\nMore advanced examples can be found in the [examples](examples) folder.\n\nExample Nginx addon:\n\n```python\nappsv1 = proto.package(\"k8s.io.api.apps.v1\")\ncorev1 = proto.package(\"k8s.io.api.core.v1\")\nmetav1 = proto.package(\"k8s.io.apimachinery.pkg.apis.meta.v1\")\n\ndef install(ctx):\n    metadata = metav1.ObjectMeta(\n        name=\"nginx\",\n        namespace=\"example\",\n        labels={\"app\": \"nginx\"},\n    )\n\n    nginxContainer = corev1.Container(\n        name=metadata.name,\n        image=\"nginx:1.15.5\",\n        ports=[corev1.ContainerPort(containerPort=80)],\n    ),\n\n    deploySpec = appsv1.DeploymentSpec(\n        replicas=3,\n        selector=metav1.LabelSelector(matchLabels=metadata.labels),\n        template=corev1.PodTemplateSpec(\n            metadata=metadata,\n            spec=corev1.PodSpec(\n                containers=[nginxContainer],\n            ),\n        ),\n    )\n\n    kube.put(\n        name=metadata.name,\n        namespace=metadata.namespace,\n        data=[appsv1.Deployment(\n            metadata=metav1.ObjectMeta(name=metadata.name),\n            spec=deploySpec,\n        )],\n    )\n```\n\n## Generate Addons\n\nYou might come from a place where you have a yaml file, but you want to derive an isopod addon from it. It can be\ncumbersome to re-write huge yaml files in Starlark. So isopod offers a convenience command to generate the Starlark code\nbased on a yaml or json input file containing any [kubernetes API object](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/):\n\n```bash\nisopod generate runtime/testdata/clusterrolebinding.yaml \u003e addon.ipd\n```\n\nFor now all `k8s.io` resources are supported.\n\n\n# Load Remote Isopod Modules\n\nSimilar to Bazel's `WORKSPACE` file, the `isopod.deps` file allows you to define remote\nand versioned git modules to import to local modules. For example,\n\n```python\ngit_repository(\n    name=\"isopod_tools\",\n    commit=\"dbe211be57bc27b947ab3e64568ecc94c23a9439\",\n    remote=\"https://github.com/cruise-automation/isopod.git\",\n)\n```\n\nTo import remote modules, use `load(\"@target_name//path/to/file\", \"foo\", \"bar\")`,\nfor example,\n\n```python\nload(\"@isopod_tools//examples/helpers.ipd\",\n     \"health_probe\", \"env_from_field\", \"container_port\")\n\n...\nspec=corev1.PodSpec(\n    containers=[corev1.Container(\n        name=\"nginx-ingress-controller\",\n        image=\"quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.22.0\",\n        env=[\n            env_from_field(\"POD_NAME\", \"metadata.name\"),\n            env_from_field(\"POD_NAMESPACE\", \"metadata.namespace\"),\n        ],\n        livenessProbe=health_probe(10254),\n        readinessProbe=health_probe(10254),\n        ports=[\n            container_port(\"http\", 80),\n            container_port(\"https\", 443),\n            container_port(\"metrics\", 10254),\n        ],\n    )],\n)\n```\n\nTo import remote addon files, use `addon(\"addon_name\", \"\"@addon_name//path/to/file\", ctx)`,\nfor example,\n```python\nisopod.deps:\n\ngit_repository(\n    name=\"versioned_addon\",\n    commit=\"1.0.0\",\n    remote=\"https://github.com/cruise-automation/addon.git\",\n)\n...\n\nmain.ipd:\n\ndef addons(ctx):\n    if ctx.cluster == None:\n        error(\"`ctx.cluster' not set\")\n    if ctx.foobar != None:\n        error(\"`ctx.foobar' must be `None', got: {foobar}\".format(\n            foobar=ctx.foobar))\n\n    return [\n        addon(\"addon_name\", \"@addon_name//addon/addon.ipd\", ctx),\n    ]\n```\n\nBy default Isopod uses `$(pwd)/isopod.deps`, which you can override with `--deps` flag.\n\n# Built-ins\n\nBuilt-ins are pre-declared packages available in Isopod runtime. Typically they\nperform I/O to Kubernetes, Vault, GCP and other resources but could be used for\nbreak-outs into other operations not supported by the main Starlark interpreter.\n\nCurrently these build-ins are supported:\n\n## kube\n\nBuilt-in for managing Kubernetes objects.\n\n### Methods:\n\n#### `kube.put`\n\nUpdates (creates if it doesn't already exist) object in Kubernetes.\n\n```python\nkube.put(\n    name = \"nginx-role\",\n    namespace = \"nginx-ingress\",\n    # Optional Kubernetes API Group parameter. If not set, will attempt to\n    # deduce the group from message type but since Kubernetes API Group names\n    # are highly irregular, this may fail.\n    api_group = 'rbac.authorization.kubernetes.io',\n    data = [\n        rbacv1.Role(),\n    ],\n)\n```\n\nSupported args:\n  + `name` - Name (`.metadata.name`) of the resource\n  + `namespace` (Optional) - Namespace (`.metadata.namespace`) of the resource\n  + `api_group` (Optional) - API group of the resource. If not provided,\n     Isopod runtime will attempt to deduce the resource from\n     just Proto type name which is unreliable. It is recommended to set this\n     for all objects outside of `core` group. Optionally, version can also be\n     specified after a `/`, example:\n     + `apiextensions.k8s.io` - specify the group only, version is implied from Proto or from runtime.\n     + `apiextensions.k8s.io/v1` - specify both group and version.\n  + `subresource` (Optional) - A subresource specifier (e.g `/status`).\n  + `data` - A list of Protobuf definitions of objects to be created.\n\n---\n\n#### `kube.delete`\n\nDeletes object in Kubernetes.\n\n```python\n# kwarg key is resource name, value is \u003cnamespace\u003e/\u003cname\u003e (just \u003cname\u003e for\n# non-namespaced resources).\nkube.delete(deployment=\"default/nginx\")\n# api_group can optionally be provided to remove ambuguity (if multiple\n# resources by the same name exist in different API Groups or different versions).\nkube.delete(clusterrole=\"nginx\", api_group = \"rbac.authorization.k8s.io/v1\")\n```\n\n---\n\n####  `kube.put_yaml`\n\nSame as `put` but for YAML/JSON data. To be used for CRDs and other custom\ntypes. `kube.put` usage is preferred for the standard set of Kubernetes types.\n\n```python\nark_config = \"\"\"\napiVersion: ark.heptio.com/v1\nkind: Config\nmetadata:\"\n  namespace: ark-backup\n  name: default\nbackupStorageProvider:\n  name: gcp\n  bucket: test-ark-backup\npersistentVolumeProvider:\n  name: gcp\n\"\"\"\n\nkube.put_yaml(\n    name = \"ark-config\",\n    namespace = \"backup\",\n    data = [ark_config])\n\n# Alternatively render from native Starlark struct object via JSON:\nark_config = struct(\n    apiVersion = \"ark.heptio.com/v1\",\n    kind = \"Config\",\n    metadata = struct(\n        name = \"ark-backup\",\n        namespace = \"default\",\n    ),\n    backupStorageProvider = struct(\n        name = \"gcp\",\n        bucket = \"test-ark-backup\",\n    ),\n    persistentVolumeProvider = struct(\n        name = \"gcp\",\n    ),\n)\n\nkube.put_yaml(\n    name = \"ark-config\",\n    namespace = \"backup\",\n    data = [ark_config.to_json()])\n```\n\n---\n\n#### `kube.get`\n\nReads object from API Server. If `wait` argument is set to duration (e.g `10s`)\nwill block until the object is successfully read or timer expires. If\n`json=True` optional argument is provided, will render object as unstructured\nJSON represented as Starlark `dict` at top level. This is useful for CRDs as\nthey typically do not support Protobuf representation.\n\n```python\n# Wait 60s for Service Account token secret.\nsecret = kube.get(secret=namespace+\"/\"+serviceaccount.secrets[0].name, wait=\"60s\")\n\n# Get ClusterRbacSyncConfig CRD.\ncadmin = kube.get(clusterrbacsyncconfig=\"cluster-admin\",\n                  api_group=\"rbacsync.getcruise.com\",\n                  json=True)\n```\n\nIt is also possible to receive a list of kubernetes objects. They can be filtered\nas defined in the [API documentation](https://raw.githubusercontent.com/kubernetes/kubernetes/master/api/openapi-spec/swagger.json).\n\n```python\n# Get all pods in namespace kube-system.\npods = kube.get(pod=\"kube-system/\")\n\n# Get all pods with label component=kube-apiserver\npods = kube.get(pod=\"kube-system/?labelSelector=component=kube-apiserver\")\n```\n\n#### `kube.exists`\n\nChecks whether a resource exists. If `wait` argument is set to duration (e.g\n`10s`) will block until the object is successfully read or timer expires.\n\n```python\n# Assert that the resource doesn't exist.\ne = kube.exists(secret=namespace+\"/\"+serviceaccount.secrets[0].name, wait=\"10s\")\nassert(e != True, \"Fail: resource shouldn't exist\")\n```\n\n---\n\n#### `kube.from_str`, `kube.from_int`\nConvert Starlark `string` and `int` types to corresponding `*instr.IntOrString`\nprotos.\n\n```python\nappsv1.RollingUpdateDaemonSet(\n    maxUnavailable = kube.from_str(\"10%\"),\n)\n```\n\n\n## Vault\n\nVault break-out allows reading/writing values from Enterprise Vault.\n\n### Methods:\n\n#### `vault.read`\n\nReads data from Vault path as Starlark dict\n\n#### `vault.write`\n\nWrites kwargs to Vault path\n\n#### `vault.exist`\n\nChecks if path exists in Vault\n\nExample usage:\n\n```python\nif not vault.exist(\"secret/lidar/stuff\"):\n    vault.write(\"secret/lidar/stuff\", w1=\"hello\", w2=\"world!\")\n\ndata = vault.read(\"secret/infra/myapp\")\nprint(data[\"w1\"] + \" \" + data[\"w2\"])\n```\n\n## Helm\n\nHelm built-in renders Helm charts and applies the resource manifest changes.\n\n### Methods:\n\n#### `helm.apply`\n\nApplies resource changes.\n\n```python\nglobalValues = \"\"\"\nglobal:\n    priorityClassName: \"cluster-critical\"\n\"\"\"\npilotValues = \"\"\"\npilot:\n    replicaCount: 3\n    image: docker.io/istio/pilot:v1.2.3\n    traceSampling: 50.0\n\"\"\"\npilotOverlayValues = {\n    \"pilot\": {\n        \"traceSampling\": 100.0,\n    }\n}\n\nhelm.apply(\n    release_name = \"istio-pilot\",\n    chart = \"//charts/istio/istio-pilot\",\n    namespace = \"istio-system\",\n    values = [\n        yaml.unmarshal(globalValues),\n        yaml.unmarshal(pilotValues),\n        pilotOverlayValues\n    ]\n)\n```\n\nSupported args:\n+ `release_name` - Release Name for the Helm chart.\n+ `chart` - Source Path of the chart. This can be a full path or a path relative\n  to the working directory. Having a leading double-slash (//) will make it\n  relative path.\n+ `namespace` (Optional) - Namespace (`.metadata.namespace`) of the resources\n+ `values` (Optional) - A list of Starlark Values used as input values for the\n   charts. The ordering of a list matters, and the elements get overridden by\n   the trailing values.\n\n\n## Misc\n\nVarious other utilities are available as Starlark built-ins for convenience:\n\n#### `base64.{encode, decode}`\n\nTranslate string values to/from base64\n\n#### `uuid.{v3, v4, v5}`\n\nProduce corresponding flavor of UUID values\n\n#### `http.{get, post, patch, put, delete}`\n\nSends corresponding HTTP request to specified url. Returns response body as\n`string`, if present. Errors out on non-2XX response code. Will follow redirects\n(stops after 10 consecutive requests).\n\nArguments:\n  - `url` - URL to send request to (required).\n  - `headers` - optional header `dict` (values are either `string` for\n    single-value headers or `list` for multiple-value headers).\n  - `data` - optionally send data in the body of the request (takes `string`).\n\n#### `hash.{sha256, sha1, md5}`\n\nReturns an integer hash value. Useful applied to an env var for forcing a\nredeploy when a config or secret changes.\n\n#### `sleep`\n\nPauses execution for specified duration (requires Go duration `string`).\n\n#### `error`\n\nInterrupts execution and return error to the user (requires `string` error\nmessage).\n\n\n# Testing\n\n`isopod test` command allows addon creators to write hermetic unit tests on\ntheir addons.\n\nUnit tests must be contained inside files with a `_test.ipd` suffix and Isopod\nruntime will call every top-level method defined in that file as a separate\ntest, execute it and report the result.\n\nBuilt-in modules that allow external access (like `kube` and `vault`) are\nstubbed (faked) out in unit test mode so that tests are hermetic.\n\nIntended pattern is to import the addon config files from the test, then call\ntheir methods and test the results with `assert` built-in (only supported in\ntest mode).\n\nExample test:\n\n```python\n# Load ingress addon config and expose its \"install\" method.\nload(\"testdata/ingress.ipd\", \"install\")\n\ndef test_install(t):\n    # Test setup code.\n    vault.write(\"secret/car/cert\", crt=\"foobar\")\n    t.ctx.namespace = \"foobar\"\n\n    # Call method we are testing (creates namespace from context).\n    install(t.ctx)\n\n    # Now extract data from our fake \"kube\" module and verify our tests\n    # conditions.\n    ns = kube.get(namespace=\"foobar\")\n    assert(ns.metadata.name == \"foobar\", \"fail\")\n    assert(ns.metadata.labels[\"foo\"] == \"bar\", \"fail\")\n```\n\nThe test command is designed to mimic standard `go test`. As such you can\nexecute all test in subtree by running `isopod test path/...`, all test in a\ndirectory by running `isopod test path/` and all tests from a current working\nsubtree by running just `isopod test`.\n\n\n# Dry Run Produces YAML Diffs\n\nKnowledge regarding the intended actions of any specification change is crucial\nfor migration and everyday configuration updates. It prevents accidental removal\nof the critical fields that is otherwise uncatchable with just the new set of\nconfigurations.\n\nIn dry run mode, Isopod not only verifies the legitimacy of the Starlark scripts\nbut also informs the intended actions of the configuration change, by presenting\nthe YAML diff between live objects in cluster and the generated configurations\ncall \"head\". The result looks like the following.\n\n```diff\n*** service.v1 example/nginx ***\n--- live\n+++ head\n@@ -14,8 +14,9 @@\n     port: 80\n     targetPort: 80\n   selector:\n     app: nginx\n   clusterIP: 192.168.17.77\n-  type: ClusterIP\n+  type: NodePort\n   sessionAffinity: None\n+  externalTrafficPolicy: Cluster\n```\n\n## Diff filtering\n\nMany fields are managed by controllers and updated at runtime, which means they\ndon't match the initially specified resource definition. In order to reduce noise\nwhen evaluating whether a dry-run is safe to apply, some filtering is performed on\nthe current and requested resource definitions.\n\nBy default, Isopod attempts to apply schema defaults and filter fields that are\nset by built-in kubernetes controllers at runtime.\n\nIn addition to the default filters, Isopod users may specify filters in two ways,\nindividually using `--kube_diff_filter` or in bulk with `--kube_diff_filter_file`.\n\nIndividual Filters Example:\n\n```\n$ isopod \\\n  --vault_token \"${vault_token}\" \\\n  --context \"cluster=${cluster}\" \\\n  --dry_run --nospin \\\n  --kube_diff_filter 'metadata.creationTimestamp' \\\n  --kube_diff_filter 'metadata.annotations[\"isopod.getcruise.com/context\"]' \\\n  --kube_diff_filter 'metadata.annotations[\"deployment.kubernetes.io/revision\"]' \\\n  --kube_diff_filter 'metadata.annotations[\"deprecated.daemonset.template.generation\"]' \\\n  --kube_diff_filter 'metadata.annotations[\"autoscaling.alpha.kubernetes.io/conditions\"]' \\\n  --kube_diff_filter 'metadata.annotations[\"cloud.google.com/neg-status\"]' \\\n  --kube_diff_filter 'metadata.annotations[\"runscope.getcruise.com/api-test-ids\"]' \\\n  --kube_diff_filter 'spec.template.spec.serviceAccount' \\\n  --kube_diff_filter 'spec.jobTemplate.spec.template.spec.serviceAccount' \\\n  install \\\n  \"${DEFAULT_CONFIG_PATH}\"\n```\n\nBulk Filters Example:\n\n```\n$ cat \u003e filters.txt \u003c\u003cEOF\nmetadata.creationTimestamp\nmetadata.annotations[\"isopod.getcruise.com/context\"]\nmetadata.annotations[\"deployment.kubernetes.io/revision\"]\nmetadata.annotations[\"deprecated.daemonset.template.generation\"]\nmetadata.annotations[\"autoscaling.alpha.kubernetes.io/conditions\"]\nmetadata.annotations[\"cloud.google.com/neg-status\"]\nmetadata.annotations[\"runscope.getcruise.com/api-test-ids\"]\nspec.template.spec.serviceAccount\nspec.jobTemplate.spec.template.spec.serviceAccount\nEOF\n$ isopod \\\n  --vault_token \"${vault_token}\" \\\n  --context \"cluster=${cluster}\" \\\n  --dry_run --nospin \\\n  --kube_diff_filter_file \"filters.txt\" \\\n  install \\\n  \"${DEFAULT_CONFIG_PATH}\"\n```\n\n\n# License\n\nCopyright 2020 Cruise LLC\n\nLicensed under the [Apache License Version 2.0](LICENSE) (the \"License\");\nyou may not use this project except in compliance with the License.\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n\n\n# Contributions\n\nContributions are welcome! Please see the agreement for contributions in\n[CONTRIBUTING.md](CONTRIBUTING.md).\n\nCommits must be made with a Sign-off (`git commit -s`) certifying that you\nagree to the provisions in [CONTRIBUTING.md](CONTRIBUTING.md).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcruise-automation%2Fisopod","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcruise-automation%2Fisopod","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcruise-automation%2Fisopod/lists"}