Package: kubernetes-square
Squareis a tool to reconcile local manifests to/from a Kubernetes
cluster.It facilitates a lightweight and CLI based GitOps workflow, but developers can
also use it as library to drive the Kubernetes state at scale.It is important to understand thatSquareis neither a version manager, nor a
package manager nor a render engine nor an authoring tool for manifests. Its
sole purpose is to show the difference between the manifests on the local file
system vs those on a cluster, and reconcile the difference in either direction.
The plan/apply workflow will be familiar to Terraform users.Squareis safe to use alongside other tools likeHelm,Kustomizeorkubectlsince it never modifies any manifests it touches. This includes theapp.kubernetes.io/managed-byannotation. Similarly, it does not create anySecretsor other resource to track its own state because it has no state.InstallationGrab abinary releaseor
usepipto install it into a Python 3.10+ environment:foo@bar:~$pipinstallkubernetes-square--upgradefoo@bar:~$squareversion2.1.2QuickstartRunsquare configto create a.square.yamland open it in an editor. It
comes with sensible defaults but you probably want to double check the values
forkubeconfigandkubecontextnear the top of the file. You can also tweak
theselectors.kinds,selectors.labelsandselectors.namespacesto target
specific resources, labels and namespaces.A typicalSquareworkflow is this:#Createa`.square.yaml`configurationfileandadjustatleastthe`kubeconfig`value.square config#Downloadallresourcesthatmatchtheselectorsinthe`.square.yaml`file.square get#Showthedifferencesandexit.square plan#Showthedifferencesandgiveusertheoptiontoreconcileit.square applyCommand line arguments take precedence over the values defined in.square.yaml:#Plantheresourcesbasedon`.square.yaml`.square plan#Limittheplantothe`default`and`foo`namespaces.square plan -n default foo#LimittheplantoConfigMapsandDeploymentsonly.square plan configmap deployment#Limittheplantoresourcesthathavethe`app=foo`label.square plan -l app=foo#Limitplantoalldeploymentscalled`foo`.square plan deploy/foo#Mixandmatchseveraloptions.square plan configmap deploy/foo -n default -l app=fooThese commands will also work with thesquare applycommand.Example WorkflowThis section shows how to useSquareto import resources from a cluster as
well as plan and apply changes.SetupAssuming you have a valid Kubeconfig you can setup the demo as follows:#Deploythedemoresourceswith`kubectl`.WearenotusingSquarehere#todemonstratehowtoimportexistingresourceswithit.foo@bar:~$kubectlapply-fintegration-test-cluster/test-resources.yaml...#Createanewfolderfortheexperiment.foo@bar:~$mkdirtry_squarefoo@bar:~$cdtry_square#CreataavanillaSquareconfigurationfile`.square.yaml`.foo@bar:~$squareconfigCreated configuration file <.square.yaml>.Please open the file in an editor and adjust the values, most notably `kubeconfig` and `selectors.[kinds | namespaces | labels]`.#Open`.square.yaml`inyoureditorandchangethevaluesfor`kubeconfig`and#(optionally)`kubecontext`topointtoyourKubeconfigforyourcluster.foo@bar:~$emacs.square.yaml...Get Current Cluster StateDownload allNamespace- andDeploymentmanifests from the cluster and save
them to./manifests:#Importalltheresourcesspecifiedin`.square.yaml`.foo@bar:~$squareget--groupbynskindfoo@bar:~$tree.└── manifests├── default│   ├── configmap.yaml│   ├── namespace.yaml│   └── service.yaml├── _global_│   ├── clusterrolebinding.yaml│   ├── clusterrole.yaml│   └── customresourcedefinition.yaml├── kube-node-lease│   ├── configmap.yaml│   └── namespace.yaml├── kube-public│   ├── configmap.yaml│   ├── namespace.yaml│   ├── rolebinding.yaml│   └── role.yaml├── kube-system│   ├── configmap.yaml│   ├── daemonset.yaml│   ├── deployment.yaml│   ├── namespace.yaml│   ├── rolebinding.yaml│   ├── role.yaml│   ├── secret.yaml│   ├── serviceaccount.yaml│   └── service.yaml├── local-path-storage│   ├── configmap.yaml│   ├── deployment.yaml│   ├── namespace.yaml│   └── serviceaccount.yaml├── square-tests-1│   ├── configmap.yaml│   ├── cronjob.yaml│   ├── daemonset.yaml│   ├── deployment.yaml│   ├── horizontalpodautoscaler.yaml│   ├── ingress.yaml│   ├── namespace.yaml│   ├── persistentvolumeclaim.yaml│   ├── poddisruptionbudget.yaml│   ├── rolebinding.yaml│   ├── role.yaml│   ├── secret.yaml│   ├── serviceaccount.yaml│   ├── service.yaml│   └── statefulset.yaml└── square-tests-2├── configmap.yaml├── cronjob.yaml├── daemonset.yaml├── deployment.yaml├── horizontalpodautoscaler.yaml├── ingress.yaml├── namespace.yaml├── persistentvolumeclaim.yaml├── rolebinding.yaml├── role.yaml├── secret.yaml├── serviceaccount.yaml├── service.yaml└── statefulset.yaml9 directories, 54 filesThe--groupbydetermines the layout of themanifests/folder. In
this case, Square created one folder for each namespace and grouped the
resources type within those folders. Non-namespaced resources, likeClusterRolewill be in the_global_folder.It is important to note that Square does not attach any meaning to the
directory layout. Internally, it will compile all manifests into a flat list.
You can therefore rename and move files as you see fit, and even move
individual manifests between files. Furthermore,square getis smart
enough to update the right manifests in the right files.Group By LabelSquarecan also group manifests based on a _singlelabel. For instance,
here are theintegration test clustermanifests
grouped byNamespaceand theapplabel:foo@bar:~$rm-rfmanifestsfoo@bar:~$squareget--groupbynslabel=appkindfoo@bar:~$tree.└── manifests├── default│   └── _other│       ├── configmap.yaml│       ├── namespace.yaml│       └── service.yaml├── _global_│   ├── demoapp-1│   │   ├── clusterrolebinding.yaml│   │   ├── clusterrole.yaml│   │   └── customresourcedefinition.yaml│   └── _other│       ├── clusterrolebinding.yaml│       └── clusterrole.yaml├── kube-node-lease│   └── _other│       ├── configmap.yaml│       └── namespace.yaml├── kube-public│   └── _other│       ├── configmap.yaml│       ├── namespace.yaml│       ├── rolebinding.yaml│       └── role.yaml├── kube-system│   ├── kindnet│   │   └── daemonset.yaml│   ├── kube-proxy│   │   └── configmap.yaml│   └── _other│       ├── configmap.yaml│       ├── daemonset.yaml│       ├── deployment.yaml│       ├── namespace.yaml│       ├── rolebinding.yaml│       ├── role.yaml│       ├── secret.yaml│       ├── serviceaccount.yaml│       └── service.yaml├── local-path-storage│   └── _other│       ├── configmap.yaml│       ├── deployment.yaml│       ├── namespace.yaml│       └── serviceaccount.yaml├── square-tests-1│   ├── demoapp-1│   │   ├── configmap.yaml│   │   ├── cronjob.yaml│   │   ├── daemonset.yaml│   │   ├── deployment.yaml│   │   ├── horizontalpodautoscaler.yaml│   │   ├── ingress.yaml│   │   ├── namespace.yaml│   │   ├── persistentvolumeclaim.yaml│   │   ├── rolebinding.yaml│   │   ├── role.yaml│   │   ├── secret.yaml│   │   ├── serviceaccount.yaml│   │   ├── service.yaml│   │   └── statefulset.yaml│   └── _other│       ├── configmap.yaml│       ├── poddisruptionbudget.yaml│       └── secret.yaml└── square-tests-2├── demoapp-1│   ├── configmap.yaml│   ├── cronjob.yaml│   ├── daemonset.yaml│   ├── deployment.yaml│   ├── horizontalpodautoscaler.yaml│   ├── ingress.yaml│   ├── namespace.yaml│   ├── persistentvolumeclaim.yaml│   ├── rolebinding.yaml│   ├── role.yaml│   ├── secret.yaml│   ├── serviceaccount.yaml│   ├── service.yaml│   └── statefulset.yaml└── _other├── configmap.yaml└── secret.yaml22 directories, 62 filesNote that resources without anapplabel are in the catch-all folder_other.Create A PlanThe plan is currently clean because we just imported the manifests from the
cluster. To very:foo@bar:~$squareplan--------------------------------------------------------------------------------Plan: 0 to add, 0 to change, 0 to destroy.To make this more interesting we will add afoolabel to theNamespacemanifest inmanifests/square-tests-1/demoapp-1/namespace.yaml. It should look something like this:apiVersion:v1kind:Namespacemetadata:labels:app:demoapp-1foo:barkubernetes.io/metadata.name:square-tests-1name:square-tests-1spec:finalizers:-kubernetesNow the plane will show a difference between the local and server manifests:foo@bar:~$squareplannsPatch NAMESPACE None/square-tests-1 (v1)---+++@@ -3,6 +3,7 @@metadata:labels:app: demoapp-1+    foo: barkubernetes.io/metadata.name: square-tests-1name: square-tests-1spec:--------------------------------------------------------------------------------Plan: 0 to add, 1 to change, 0 to destroy.We could now usesquare get nsto make the local manifest match the cluster
state orsquare apply nsto make the cluster state match the local manifests.
We will do the latter:foo@bar:~$squareapplynsPatch NAMESPACE None/square-tests-1 (v1)---+++@@ -3,6 +3,7 @@metadata:labels:app: demoapp-1+    foo: barkubernetes.io/metadata.name: square-tests-1name: square-tests-1spec:--------------------------------------------------------------------------------Plan: 0 to add, 1 to change, 0 to destroy.Type yes to apply the plan.Your answer: yesPatching NAMESPACE None/square-tests-1We can usekubectlto verify that thesquare-tests-1Namespace now has thefoo=barlabel:foo@bar:~$kubectldescribenssquare-tests-1Name:         defaultLabels:       foo=barAnnotations:  <none>Status:       ActiveNo resource quota.No resource limits.The plan is now clean again because the local files and the
cluster are in sync again:foo@bar:~$squareplanns--------------------------------------------------------------------------------Plan: 0 to add, 0 to change, 0 to destroy.Create and Destroy ResourcesSo far we have only imported and modified existing resource butsquare applywill also create and remove resources as necessary. For instance, to add a new
resource we add its manifest to themanifests/folder, either in a new file
or added to an existing one.foo@bar:~$cat>manifests/additional_manifest.yaml<<EOFapiVersion: v1kind: Namespacemetadata:name: dummy---apiVersion: v1kind: ServiceAccountmetadata:name: random-usernamespace: dummyEOFfoo@bar:~$squareapplyCreate NAMESPACE None/dummy (v1)apiVersion: v1kind: Namespacemetadata:name: dummyCreate SERVICEACCOUNT dummy/random-user (v1)apiVersion: v1kind: ServiceAccountmetadata:name: random-usernamespace: dummy--------------------------------------------------------------------------------Plan: 2 to add, 0 to change, 0 to destroy.Type yes to apply the plan.Your answer: yesCreating NAMESPACE None/dummyCreating SERVICEACCOUNT dummy/random-userSimilarly, to remove those resources again we merely delete the manifest file
and runsquare applyagain:foo@bar:~$rmmanifests/additional_manifest.yamlfoo@bar:~$squareapplyDelete CONFIGMAP dummy/kube-root-ca.crt (v1)Delete NAMESPACE None/dummy (v1)Delete SERVICEACCOUNT dummy/random-user (v1)--------------------------------------------------------------------------------Plan: 0 to add, 0 to change, 3 to destroy.Type yes to apply the plan.Your answer: yesDeleting SERVICEACCOUNT dummy/random-userDeleting CONFIGMAP dummy/kube-root-ca.crtDeleting NAMESPACE None/dummyUse It As A LibraryYou can also useSquareas a library. In fact, the CLI commands explained
here are just thin wrappers around that library. Seeherefor how to build a basic version of the CLI with
only a few lines of code.Automated TestsSquareships with a comprehensive set of unit tests:pipenv run pytestTo run the integration tests as well you need to haveKinDand start it with:cd integration-test-cluster
./start_cluster.sh