├── .github └── workflows │ ├── ci.yml │ └── release.yml ├── .gitignore ├── .golangci.yml ├── .goreleaser.yml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── ROADMAP.md ├── cmd ├── changes.go ├── record.go ├── root.go ├── status.go ├── trace.go └── version.go ├── examples ├── trivial-pulumi-example │ ├── .gitignore │ ├── Pulumi.yaml │ ├── README.md │ ├── index.ts │ ├── package.json │ ├── tsconfig.json │ └── yaml │ │ └── nginx.yaml └── trivial-service-trace-example │ ├── .gitignore │ ├── Pulumi.yaml │ ├── README.md │ ├── index.ts │ ├── package.json │ ├── tsconfig.json │ └── yaml │ └── nginx.yaml ├── go.mod ├── go.sum ├── images ├── 1-created.png ├── 2-scheduled.png ├── 3-creating.png ├── 4-running.png ├── changes.gif ├── status.cast ├── status.gif ├── trace-deployment │ ├── 1-deployment-found.gif │ ├── 2-replicaset-created.gif │ ├── 3-rollout-succeeds.gif │ ├── pod-killed.gif │ ├── pod-relabeled.gif │ └── trace-deployment-rollout.gif └── trace-service │ ├── 1-trace-success-create-svc.gif │ ├── 2-trace-success-pods-ready.gif │ ├── 3-trace-success-ip-allocated.gif │ ├── become-alive.cast │ ├── become-alive.gif │ ├── trace-success.cast │ └── trace-success.gif ├── k8sconfig └── k8sconfig.go ├── k8sobject └── k8sobject.go ├── kubespy.go ├── pods └── pods.go ├── print └── print.go ├── version └── version.go └── watch └── watch.go /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | on: 3 | pull_request: 4 | branches: 5 | - master 6 | push: 7 | branches: 8 | - master 9 | jobs: 10 | ci: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v2 15 | - name: Install Go 1.19 16 | uses: actions/setup-go@v2 17 | with: 18 | go-version: '1.19.x' 19 | - name: Run Go Build 20 | run: make build 21 | - name: Run tests 22 | run: make test_all 23 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | permissions: write-all # Equivalent to default permissions plus id-token: write 2 | name: release 3 | on: 4 | push: 5 | tags: ["v*.[0-99]"] 6 | 7 | env: 8 | ESC_ACTION_OIDC_AUTH: true 9 | ESC_ACTION_OIDC_ORGANIZATION: pulumi 10 | ESC_ACTION_OIDC_REQUESTED_TOKEN_TYPE: urn:pulumi:token-type:access_token:organization 11 | ESC_ACTION_ENVIRONMENT: imports/github-secrets 12 | ESC_ACTION_EXPORT_ENVIRONMENT_VARIABLES: GITHUB_TOKEN=PULUMI_BOT_TOKEN 13 | 14 | jobs: 15 | goreleaser: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Fetch secrets from ESC 19 | id: esc-secrets 20 | uses: pulumi/esc-action@v1 21 | - name: Checkout 22 | uses: actions/checkout@v2 23 | - name: Unshallow clone 24 | run: git fetch --prune --unshallow --tags 25 | - name: Install pulumictl 26 | uses: jaxxstorm/action-install-gh-release@v1.5.0 27 | with: 28 | repo: pulumi/pulumictl 29 | - name: Install Go 1.19 30 | uses: actions/setup-go@v2 31 | with: 32 | go-version: '1.19.x' 33 | - name: Goreleaser publish 34 | uses: goreleaser/goreleaser-action@v2 35 | with: 36 | version: latest 37 | args: release --rm-dist 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | /bin/ 3 | /node_modules/ 4 | .pulumi 5 | Pulumi.*.yaml 6 | /pkg/gen/openapi-specs 7 | /pack/nodejs/bin 8 | /pack/nodejs/node_modules 9 | kubespy 10 | releases 11 | .idea/ 12 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | linters: 2 | enable: 3 | - deadcode 4 | - errcheck 5 | - gas 6 | - goconst 7 | - gofmt 8 | - golint 9 | - gosimple 10 | - ineffassign 11 | - interfacer 12 | - lll 13 | - misspell 14 | - staticcheck 15 | - structcheck 16 | - unconvert 17 | - varcheck 18 | - vet 19 | - vetshadow 20 | enable-all: false 21 | timeout: 10m 22 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | builds: 2 | - env: 3 | - CGO_ENABLED=0 4 | goos: 5 | - linux 6 | - windows 7 | - darwin 8 | ldflags: 9 | - -X github.com/pulumi/kubespy/version.Version={{.Tag}} 10 | goarch: 11 | - amd64 12 | - arm64 13 | binary: kubespy 14 | main: ./kubespy.go 15 | archives: 16 | - name_template: "{{ .Binary }}-{{ .Tag }}-{{ .Os }}-{{ .Arch }}" 17 | checksum: 18 | name_template: "checksums.txt" 19 | snapshot: 20 | name_template: "{{ .Tag }}-next" 21 | changelog: 22 | skip: true 23 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at team@pulumi.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to `kubespy` 2 | 3 | First, thanks for contributing to Pulumi and helping make it better. We appreciate the help! If you're looking for an issue to start with, we've tagged some issues with the [help-wanted](https://github.com/pulumi/pulumi/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22) tag but feel free to pick up any issue that looks interesting to you or fix a bug you stumble across in the course of using Pulumi. No matter the size, we welcome all improvements. 4 | 5 | For larger features, we'd appreciate it if you open a [new issue](https://github.com/pulumi/pulumi/issues/new) before doing a ton of work to discuss the feature before you start writing a lot of code. 6 | 7 | ## Hacking on `kubespy` 8 | 9 | To hack on `kubespy`, you'll need to get a development environment set up. You'll want to install the following on your machine: 10 | 11 | - Go 1.11 or Later (we use [Go modules](https://github.com/golang/go/wiki/Modules)) 12 | - [Gometalinter](https://github.com/alecthomas/gometalinter) 13 | - `make` 14 | 15 | ## Getting Help 16 | 17 | We're sure there are rough edges and we appreciate you helping out. If you want to talk with other folks hacking on Pulumi (or members of the Pulumi team!) come hang out `#contribute` channel in the [Pulumi Community Slack](https://slack.pulumi.io/). 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PROJECT := github.com/pulumi/kubespy 2 | TESTPARALLELISM := 10 3 | 4 | ensure:: 5 | go mod tidy 6 | 7 | build:: 8 | go build $(PROJECT) 9 | 10 | lint:: 11 | golangci-lint run 12 | 13 | install:: 14 | go install $(PROJECT) 15 | 16 | test_all:: 17 | go test -v -cover -timeout 1h -parallel ${TESTPARALLELISM} ./... 18 | 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kubespy: tools for observing Kubernetes resources in real time 2 | 3 | What happens when you boot up a `Pod`? What happens to a `Service` before it is allocated a public 4 | IP address? How often is a `Deployment`'s status changing? 5 | 6 | **`kubespy` is a small tool that makes it easy to observe how Kubernetes resources change in real 7 | time,** derived from the work we did to make Kubernetes deployments predictable in [Pulumi's CLI](https://www.pulumi.com/kubernetes/). Run `kubespy` at any point in time, and it will watch and report information about a 8 | Kubernetes resource continuously until you kill it. 9 | 10 | ## Examples 11 | 12 | `kubespy trace deployment nginx` will "trace" the complex changes a complex Kubernetes resource 13 | makes in the cluster (in this case, a `Deployment` called `nginx`), and aggregate them into a 14 | high-level summary, which is updated in real time. 15 | 16 | ![Changes](images/trace-deployment/trace-deployment-rollout.gif "Changes a Deployment rolls out a new change, in real time") 17 | 18 | `kubespy status v1 Pod nginx` will wait for a `Pod` called `nginx` to be created, and then continuously emit changes made to its `.status` field, as syntax-highlighted JSON diffs: 19 | 20 | ![Changes](images/status.gif "Changes a Pod undergoes as it starts, in real time") 21 | 22 | ## Installation 23 | 24 | You can install kubespy in the following ways: 25 | 26 | ### Homebrew (Mac) 27 | 28 | Prerequisite: [homebrew](https://docs.brew.sh/Installation) 29 | 30 | ```bash 31 | brew install kubespy 32 | ``` 33 | 34 | ### Binary 35 | 36 | Get the [latest release](https://github.com/pulumi/kubespy/releases), 37 | rename it to `kubespy`, run `chmod +x kubespy` to make it executable and move it in your path (can be /usr/local/bin). 38 | 39 | ### Kubectl Plugin 40 | 41 | Prerequisite: kubectl v1.12.0 or later 42 | 43 | With kubectl v1.12.0 introducing [easy pluggability](https://kubernetes.io/docs/tasks/extend-kubectl/kubectl-plugins/) of external functions, kubespy can be invoked as `kubectl spy` just by renaming it to `kubectl-spy` and having it available in your path. 44 | 45 | ### via Golang 46 | 47 | Prerequisite: [Go](https://golang.org/) version 1.19 or later 48 | 49 | ```sh 50 | go install github.com/pulumi/kubespy@latest 51 | ``` 52 | 53 | ## Usage 54 | 55 | `kubespy` has four commands: 56 | 57 | - `status [/]`, which in real time emits all changes made to 58 | the `.status` field of an arbitrary Kubernetes resource, as a JSON diff. 59 | - `changes [/]`, which in real time emits all changes to any 60 | field in a Kubernetes resource, as a JSON diff. 61 | - `trace [/]`, which "traces" the changes a complex Kubernetes resource 62 | makes throughout a cluster, and aggregates them into a high-level summary, which is updated in 63 | real time. 64 | - `record [/]`, which in real time emits all changes to any 65 | field in a Kubernetes resource, as a JSON array. 66 | 67 | Several more commands are planned as well. 68 | 69 | ## Examples 70 | 71 | For a concrete example you can run using either `Pulumi CLI` or `kubectl`, check out [examples/trivial-pulumi-example](https://github.com/pulumi/kubespy/tree/master/examples/trivial-pulumi-example). 72 | 73 | ## Features 74 | 75 | - [x] Supports any resources the API server knows about, including CRDs (_i.e._, uses the discovery 76 | client to discover the available API resources, and allows users to query any of them). 77 | - [x] Displays changes to API objects in real time. 78 | - [ ] Supports case-insensitive aliases (_e.g._ `kubespy status v1 pod ` instead of 79 | `kubespy status v1 Pod `). 80 | - [ ] Supports status updates from regex and/or fuzzy matching (_i.e._, make it easy to watch the 81 | status of `Pod`s generated by `Deployment`s and `ReplicaSet`s). 82 | -------------------------------------------------------------------------------- /ROADMAP.md: -------------------------------------------------------------------------------- 1 | # `kubespy` roadmap 2 | 3 | The mission of `kubespy` is to provide tools for understanding what is happening to Kubernetes 4 | resources in real time. This document contains ideas for what we think that _should_ involve, but if 5 | you see something missing we'd love to hear about it, and we encourage you to [open an 6 | issue][new-issue] to tell us about it! 7 | 8 | ## Make `kubespy` "a real project" 9 | 10 | `kubespy` began life as a tiny tool to make gifs that show how Kubernetes works. We did not expect 11 | users to find this interesting enough to use. 12 | 13 | But, now they do, and the time has come to turn this project from a one-off, to something with 14 | engineering processes more befitting the trust users place in it: 15 | 16 | - Standard contrib docs (`CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`, `ROADMAP.md`) 17 | - CI integration 18 | - Tests 19 | 20 | ## Make the tool usable to non-experts 21 | 22 | We were surprised when people wanted to use `kubespy`. We were even more surprised when the people 23 | who wanted to use `kubespy` were confused by things that we (foolishly!) took for granted. Stuff 24 | like: users don't know what `dep` is, or why `v1` must go in front of `Pod`, or whether the 25 | kubeconfig file can have multiple clusters. In short, users who have to use Kubernetes, but who are 26 | not experts. 27 | 28 | To make this tool effective for this audience it is critical that we make it simple to install and 29 | use: 30 | 31 | - Install must be turn-key on all common platforms. `brew`, `apt`, `chocolatey`, and binary 32 | releases are a must. 33 | - CLI experience must take a minimal amount of context to produce sensible output. 34 | `kubespy status po` should work, as should `kubespy status v1 Pod`. 35 | - Errors should prescribe action where appropriate. 36 | 37 | ## Expand `kubespy trace` to include all commonly-used "complex" types 38 | 39 | `kubespy trace` is meant to display a real-time, human-readable, high-level picture of the status of 40 | "complex" Kubernetes resource types. For example, the status of a `Service` is distributed across 41 | the `Service` object itself, an `Endpoints` object of the same name that contains information about 42 | how to direct traffic to `Pod`s, and the `Pod`s themselves. 43 | 44 | Currently `kubespy trace` supports _only_ `Service` and `Deployment`. But also of interest to our users are: 45 | 46 | - `ReplicaSet` 47 | - `Ingress` 48 | - `StatefulSet` 49 | 50 | [new-issue]: https://github.com/pulumi/kubespy/issues/new 51 | -------------------------------------------------------------------------------- /cmd/changes.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "log" 7 | 8 | "github.com/fatih/color" 9 | "github.com/pulumi/kubespy/watch" 10 | "github.com/spf13/cobra" 11 | "github.com/yudai/gojsondiff" 12 | "github.com/yudai/gojsondiff/formatter" 13 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 14 | apiwatch "k8s.io/apimachinery/pkg/watch" 15 | ) 16 | 17 | func init() { 18 | rootCmd.AddCommand(changesCmd) 19 | } 20 | 21 | var changesCmd = &cobra.Command{ 22 | Use: "changes [/]", 23 | Short: "Displays changes made to a Kubernetes resource in real time. Emitted as JSON diffs", 24 | Args: cobra.ExactArgs(3), 25 | Run: func(cmd *cobra.Command, args []string) { 26 | namespace, name, err := parseObjID(args[2]) 27 | if err != nil { 28 | log.Fatal(err) 29 | } 30 | 31 | events, err := watch.Forever(args[0], args[1], watch.ThisObject(namespace, name)) 32 | if err != nil { 33 | log.Fatal(err) 34 | } 35 | 36 | fmt.Println(color.GreenString("Watching for changes on %s %s %s", args[0], args[1], args[2])) 37 | 38 | heading := color.New(color.FgBlue, color.Bold) 39 | 40 | var last *unstructured.Unstructured 41 | for { 42 | select { 43 | case e := <-events: 44 | o := e.Object.(*unstructured.Unstructured) 45 | switch e.Type { 46 | case apiwatch.Added: 47 | heading.Println("CREATED") 48 | 49 | ojson, err := json.MarshalIndent(o.Object, "", " ") 50 | if err != nil { 51 | log.Fatal(err) 52 | } 53 | fmt.Println(color.GreenString(string(ojson))) 54 | case apiwatch.Modified: 55 | heading.Println(string(e.Type)) 56 | 57 | diff := gojsondiff.New().CompareObjects(last.Object, o.Object) 58 | if diff.Modified() { 59 | fcfg := formatter.AsciiFormatterConfig{Coloring: true} 60 | formatter := formatter.NewAsciiFormatter(last.Object, fcfg) 61 | text, err := formatter.Format(diff) 62 | if err != nil { 63 | log.Fatal(err) 64 | } 65 | fmt.Println(text) 66 | } 67 | case apiwatch.Deleted: 68 | heading.Println(string(e.Type)) 69 | } 70 | last = o 71 | } 72 | } 73 | }, 74 | } 75 | -------------------------------------------------------------------------------- /cmd/record.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2019, Pulumi Corporation. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cmd 16 | 17 | import ( 18 | "encoding/json" 19 | "fmt" 20 | "log" 21 | "os" 22 | "os/signal" 23 | "syscall" 24 | 25 | "github.com/pulumi/kubespy/watch" 26 | "github.com/spf13/cobra" 27 | "github.com/yudai/gojsondiff" 28 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 29 | apiwatch "k8s.io/apimachinery/pkg/watch" 30 | ) 31 | 32 | func init() { 33 | rootCmd.AddCommand(recordCmd) 34 | } 35 | 36 | // SetupCloseHandler catches the ctrl+c signal and properly terminates the JSON array before exiting. 37 | func SetupCloseHandler() { 38 | c := make(chan os.Signal, 2) 39 | signal.Notify(c, os.Interrupt, syscall.SIGTERM) 40 | go func() { 41 | <-c 42 | fmt.Println("\n]") 43 | os.Exit(0) 44 | }() 45 | } 46 | 47 | var recordCmd = &cobra.Command{ 48 | Use: "record [/]", 49 | Short: "Displays events generated by a Kubernetes resource in real time. Emitted as a JSON array.", 50 | Args: cobra.ExactArgs(3), 51 | Run: func(cmd *cobra.Command, args []string) { 52 | namespace, name, err := parseObjID(args[2]) 53 | if err != nil { 54 | log.Fatal(err) 55 | } 56 | 57 | events, err := watch.Forever(args[0], args[1], watch.ThisObject(namespace, name)) 58 | if err != nil { 59 | log.Fatal(err) 60 | } 61 | 62 | fmt.Print("[\n ") 63 | 64 | var last *unstructured.Unstructured 65 | for { 66 | select { 67 | case e := <-events: 68 | o := e.Object.(*unstructured.Unstructured) 69 | switch e.Type { 70 | case apiwatch.Added: 71 | if last != nil { 72 | fmt.Println(",") 73 | } 74 | 75 | SetupCloseHandler() 76 | 77 | if output, err := json.MarshalIndent(o.Object, " ", " "); err != nil { 78 | log.Fatal(err) 79 | } else { 80 | fmt.Print(string(output)) 81 | } 82 | case apiwatch.Modified: 83 | diff := gojsondiff.New().CompareObjects(last.Object, o.Object) 84 | if diff.Modified() { 85 | if last != nil { 86 | fmt.Println(",") 87 | } 88 | fmt.Print(" ") 89 | if output, err := json.MarshalIndent(o.Object, " ", " "); err != nil { 90 | log.Fatal(err) 91 | } else { 92 | fmt.Print(string(output)) 93 | } 94 | } 95 | case apiwatch.Deleted: 96 | // Nothing to print. 97 | } 98 | last = o 99 | } 100 | } 101 | }, 102 | } 103 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | 8 | "github.com/pulumi/kubespy/k8sconfig" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | var rootCmd = &cobra.Command{ 13 | Use: "kubespy ", 14 | Short: "Spy on your Kubernetes resources", 15 | } 16 | 17 | func Execute() { 18 | if err := rootCmd.Execute(); err != nil { 19 | fmt.Println(err) 20 | os.Exit(1) 21 | } 22 | } 23 | 24 | func parseObjID(objID string) (namespace, name string, _ error) { 25 | split := strings.Split(objID, "/") 26 | if l := len(split); l == 1 { 27 | kubeconfig := k8sconfig.New() 28 | ns, _, err := kubeconfig.Namespace() 29 | if err != nil { 30 | return "", "", err 31 | } 32 | return ns, split[0], nil 33 | } else if l == 2 { 34 | return split[0], split[1], nil 35 | } 36 | return "", "", fmt.Errorf( 37 | "Object ID must be of the form or /, got: %s", objID) 38 | } 39 | -------------------------------------------------------------------------------- /cmd/status.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "log" 7 | 8 | "github.com/fatih/color" 9 | "github.com/pulumi/kubespy/watch" 10 | "github.com/spf13/cobra" 11 | "github.com/yudai/gojsondiff" 12 | "github.com/yudai/gojsondiff/formatter" 13 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 14 | ) 15 | 16 | func init() { 17 | rootCmd.AddCommand(statusCmd) 18 | } 19 | 20 | var statusCmd = &cobra.Command{ 21 | Use: "status [/]", 22 | Short: "Displays changes to a Kubernetes resources's status in real time. Emitted as JSON diffs", 23 | Args: cobra.ExactArgs(3), 24 | Run: func(cmd *cobra.Command, args []string) { 25 | namespace, name, err := parseObjID(args[2]) 26 | if err != nil { 27 | log.Fatal(err) 28 | } 29 | 30 | events, err := watch.Forever(args[0], args[1], watch.ThisObject(namespace, name)) 31 | if err != nil { 32 | log.Fatal(err) 33 | } 34 | 35 | fmt.Println(color.GreenString("Watching status of %s %s %s", args[0], args[1], args[2])) 36 | 37 | heading := color.New(color.FgBlue, color.Bold) 38 | 39 | var lastStatus map[string]interface{} 40 | for { 41 | select { 42 | case e := <-events: 43 | o := e.Object.(*unstructured.Unstructured) 44 | var currStatus map[string]interface{} 45 | if status, hasStatus := o.Object["status"]; !hasStatus { 46 | currStatus = make(map[string]interface{}) 47 | } else if status, isMap := status.(map[string]interface{}); !isMap { 48 | currStatus = make(map[string]interface{}) 49 | } else { 50 | currStatus = status 51 | } 52 | 53 | if lastStatus == nil { 54 | heading.Println("CREATED") 55 | 56 | ojson, err := json.MarshalIndent(currStatus, "", " ") 57 | if err != nil { 58 | log.Fatal(err) 59 | } 60 | fmt.Println(color.GreenString(string(ojson))) 61 | } else { 62 | heading.Println(string(e.Type)) 63 | 64 | diff := gojsondiff.New().CompareObjects(lastStatus, currStatus) 65 | if diff.Modified() { 66 | fcfg := formatter.AsciiFormatterConfig{Coloring: true} 67 | formatter := formatter.NewAsciiFormatter(lastStatus, fcfg) 68 | text, err := formatter.Format(diff) 69 | if err != nil { 70 | log.Fatal(err) 71 | } 72 | fmt.Println(text) 73 | } 74 | } 75 | lastStatus = currStatus 76 | } 77 | } 78 | }, 79 | } 80 | -------------------------------------------------------------------------------- /cmd/trace.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "strings" 7 | "time" 8 | 9 | "github.com/fatih/color" 10 | "github.com/mbrlabs/uilive" 11 | "github.com/pulumi/kubespy/print" 12 | "github.com/pulumi/kubespy/watch" 13 | "github.com/spf13/cobra" 14 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 15 | k8sWatch "k8s.io/apimachinery/pkg/watch" 16 | ) 17 | 18 | const ( 19 | v1Endpoints = "v1/Endpoints" 20 | v1Service = "v1/Service" 21 | v1Pod = "v1/Pod" 22 | deployment = "Deployment" 23 | v1ReplicaSet = "v1/ReplicaSet" 24 | ) 25 | 26 | func init() { 27 | rootCmd.AddCommand(traceCmd) 28 | } 29 | 30 | var traceCmd = &cobra.Command{ 31 | Use: "trace [/]", 32 | Short: "Traces status of complex API objects", 33 | Long: `Traces status of complex API objects. Accepted types are: 34 | - service (aliases: {svc}) 35 | - deployment (aliases: {deploy})`, 36 | Args: cobra.ExactArgs(2), 37 | Run: func(cmd *cobra.Command, args []string) { 38 | namespace, name, err := parseObjID(args[1]) 39 | if err != nil { 40 | log.Fatal(err) 41 | } 42 | 43 | switch t := strings.ToLower(args[0]); t { 44 | case "service", "svc": 45 | traceService(namespace, name) 46 | case "deployment", "deploy": 47 | traceDeployment(namespace, name) 48 | default: 49 | msg := "Unknown resource type '%s'. The following resources are available:\n" + 50 | " - service (aliases: {svc})\n" + 51 | " - deployment (aliases: {deploy})" 52 | log.Fatalf(msg, t) 53 | } 54 | }, 55 | } 56 | 57 | func traceService(namespace, name string) { 58 | serviceEvents, err := watch.Forever("v1", "Service", watch.ThisObject(namespace, name)) 59 | if err != nil { 60 | log.Fatal(err) 61 | } 62 | 63 | // NOTE: We can use the same watch opts here because the `Endpoints` object will have the same 64 | // name and be in the same namespace. 65 | endpointEvents, err := watch.Forever("v1", "Endpoints", watch.ThisObject(namespace, name)) 66 | if err != nil { 67 | log.Fatal(err) 68 | } 69 | 70 | writer := uilive.New() 71 | writer.RefreshInterval = time.Minute * 1 72 | writer.Start() // Start listening for updates, render. 73 | defer writer.Stop() // Flush buffers, stop rendering. 74 | 75 | // Initial message. 76 | fmt.Fprintln(writer, color.New(color.FgCyan, color.Bold).Sprintf("Waiting for Service '%s/%s'", 77 | namespace, name)) 78 | writer.Flush() 79 | 80 | table := map[string][]k8sWatch.Event{} 81 | 82 | for { 83 | select { 84 | case e := <-serviceEvents: 85 | if e.Type == k8sWatch.Deleted { 86 | o := e.Object.(*unstructured.Unstructured) 87 | delete(o.Object, "spec") 88 | delete(o.Object, "status") 89 | } 90 | table[v1Service] = []k8sWatch.Event{e} 91 | case e := <-endpointEvents: 92 | if e.Type == k8sWatch.Deleted { 93 | o := e.Object.(*unstructured.Unstructured) 94 | delete(o.Object, "spec") 95 | delete(o.Object, "status") 96 | delete(o.Object, "subsets") 97 | } 98 | table[v1Endpoints] = []k8sWatch.Event{e} 99 | } 100 | print.ServiceWatchTable(writer, table) 101 | } 102 | } 103 | 104 | func traceDeployment(namespace, name string) { 105 | // API server should rewrite this to apps/v1beta2, apps/v1beta2, or apps/v1 as appropriate. 106 | deploymentEvents, err := watch.Forever("apps/v1", "Deployment", 107 | watch.ThisObject(namespace, name)) 108 | if err != nil { 109 | log.Fatal(err) 110 | } 111 | 112 | replicaSetEvents, err := watch.Forever("apps/v1", "ReplicaSet", 113 | watch.ObjectsOwnedBy(namespace, name)) 114 | if err != nil { 115 | log.Fatal(err) 116 | } 117 | 118 | podEvents, err := watch.Forever("v1", "Pod", watch.All(namespace)) 119 | if err != nil { 120 | log.Fatal(err) 121 | } 122 | 123 | writer := uilive.New() 124 | writer.RefreshInterval = time.Minute * 1 125 | writer.Start() // Start listening for updates, render. 126 | defer writer.Stop() // Flush buffers, stop rendering. 127 | 128 | // Initial message. 129 | fmt.Fprintln(writer, color.New(color.FgCyan, color.Bold).Sprintf("Waiting for Deployment '%s/%s'", 130 | namespace, name)) 131 | writer.Flush() 132 | 133 | table := map[string][]k8sWatch.Event{} // apiVersion/Kind -> []k8sWatch.Event 134 | repSets := map[string]k8sWatch.Event{} // Deployment name -> Pod 135 | pods := map[string]k8sWatch.Event{} // ReplicaSet name -> Pod 136 | 137 | for { 138 | select { 139 | case e := <-deploymentEvents: 140 | if e.Type == k8sWatch.Deleted { 141 | o := e.Object.(*unstructured.Unstructured) 142 | delete(o.Object, "spec") 143 | delete(o.Object, "status") 144 | } 145 | table[deployment] = []k8sWatch.Event{e} 146 | case e := <-replicaSetEvents: 147 | o := e.Object.(*unstructured.Unstructured) 148 | if e.Type == k8sWatch.Deleted { 149 | delete(repSets, o.GetName()) 150 | } else { 151 | repSets[o.GetName()] = e 152 | } 153 | table[v1ReplicaSet] = []k8sWatch.Event{} 154 | for _, rsEvent := range repSets { 155 | table[v1ReplicaSet] = append(table[v1ReplicaSet], rsEvent) 156 | } 157 | case e := <-podEvents: 158 | o := e.Object.(*unstructured.Unstructured) 159 | if e.Type == k8sWatch.Deleted { 160 | delete(pods, o.GetName()) 161 | } else { 162 | pods[o.GetName()] = e 163 | } 164 | 165 | table[v1Pod] = []k8sWatch.Event{} 166 | for _, podEvent := range pods { 167 | table[v1Pod] = append(table[v1Pod], podEvent) 168 | } 169 | } 170 | print.DeploymentWatchTable(writer, table) 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /cmd/version.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/pulumi/kubespy/version" 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | func init() { 11 | rootCmd.AddCommand(versionCmd) 12 | } 13 | 14 | var versionCmd = &cobra.Command{ 15 | Use: "version", 16 | Short: "Displays version information for this tool", 17 | Args: cobra.ExactArgs(0), 18 | Run: func(cmd *cobra.Command, args []string) { 19 | fmt.Println(version.Version) 20 | }, 21 | } 22 | -------------------------------------------------------------------------------- /examples/trivial-pulumi-example/.gitignore: -------------------------------------------------------------------------------- 1 | /bin/ 2 | /node_modules/ 3 | -------------------------------------------------------------------------------- /examples/trivial-pulumi-example/Pulumi.yaml: -------------------------------------------------------------------------------- 1 | name: trivial-nginx 2 | runtime: nodejs 3 | description: Basic example of an Kubernetes sample based on \ 4 | https://kubernetes.io/docs/user-guide/walkthrough/ 5 | -------------------------------------------------------------------------------- /examples/trivial-pulumi-example/README.md: -------------------------------------------------------------------------------- 1 | # Example: What happens when you boot up a Pod? 2 | 3 | In this example, we: 4 | 5 | - Install a simple `Pod` to a Kubernetes cluster. 6 | - Use `kubespy` to watch what happens to that `Pod` as it starts. 7 | 8 | You'll need: 9 | 10 | - **Access to a Kubernetes cluster.** If you are using Pulumi, you can trivially boot an [GKE](https://github.com/pulumi/examples/tree/master/gcp-ts-gke), [AKS](https://github.com/pulumi/examples/tree/master/azure-ts-aks-mean), or [EKS](https://github.com/pulumi/examples/tree/master/aws-ts-eks) cluster. You can also use [minikube](https://github.com/kubernetes/minikube). 11 | - **Either the Pulumi CLI or `kubectl`.** There is nothing Pulumi-specific in this example, so you can use `kubectl`, but we hope you'll give Pulumi a shot! The CLI installation instructions [here](https://pulumi.io/quickstart/install.html). Pulumi works anywhere `kubectl` works (i.e., anywhere you have a kubeconfig file), so it should "just work" if you already have a Kubernetes cluster running. 12 | - **`kubespy`.** Installation is a handful of commands, which you can find in the [README](https://github.com/pulumi/kubespy#installation). 13 | 14 | Once these are complete, you'll want to do two things: 15 | 16 | 1. **Run `kubespy`.** Once you've installed it, this should be as simple as 17 | `kubespy status v1 Pod nginx`. `kubespy` will dutifully wait for you to 18 | deploy a `Pod` called `nginx` to your cluster. 19 | 2. **Run the example.** `kubespy` repository contains a tiny example Pod that 20 | deploys an NGINX container. 21 | 22 | ```sh 23 | # With Pulumi CLI. 24 | $ git clone git@github.com:pulumi/kubespy.git 25 | $ cd kubespy/examples/trivial-pulumi-example 26 | $ npm install 27 | $ pulumi up 28 | 29 | # With kubectl 30 | kubectl create -f https://github.com/pulumi/kubespy/raw/master/examples/trivial-pulumi-example/yaml/nginx.yaml 31 | ``` 32 | 33 | Once done, `kubespy` should display something like this: 34 | 35 | ![Changes](../../images/changes.gif "Changes a Pod undergoes as it starts, in real time") 36 | -------------------------------------------------------------------------------- /examples/trivial-pulumi-example/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2018, Pulumi Corporation. All rights reserved. 2 | 3 | import * as k8s from "@pulumi/kubernetes"; 4 | 5 | // Create an nginx pod 6 | let nginx = new k8s.yaml.ConfigFile("yaml/nginx.yaml"); 7 | -------------------------------------------------------------------------------- /examples/trivial-pulumi-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodeserver", 3 | "version": "0.1.0", 4 | "dependencies": { 5 | "@pulumi/pulumi": "^3.0.0" 6 | }, 7 | "devDependencies": { 8 | "@types/node": "^9.3.0", 9 | "@pulumi/kubernetes": "latest", 10 | "typescript": "^2.5.3" 11 | }, 12 | "license": "MIT" 13 | } 14 | -------------------------------------------------------------------------------- /examples/trivial-pulumi-example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "bin", 4 | "target": "es6", 5 | "module": "commonjs", 6 | "moduleResolution": "node", 7 | "declaration": true, 8 | "sourceMap": true, 9 | "stripInternal": true, 10 | "experimentalDecorators": true, 11 | "pretty": true, 12 | "noFallthroughCasesInSwitch": true, 13 | "noImplicitAny": true, 14 | "noImplicitReturns": true, 15 | "forceConsistentCasingInFileNames": true, 16 | "strictNullChecks": true 17 | }, 18 | "files": [ 19 | "index.ts" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /examples/trivial-pulumi-example/yaml/nginx.yaml: -------------------------------------------------------------------------------- 1 | kind: Pod 2 | apiVersion: v1 3 | metadata: 4 | name: nginx 5 | labels: 6 | app: nginx 7 | spec: 8 | containers: 9 | - name: nginx 10 | image: nginx:1.7.9 11 | ports: 12 | - containerPort: 80 13 | -------------------------------------------------------------------------------- /examples/trivial-service-trace-example/.gitignore: -------------------------------------------------------------------------------- 1 | /bin/ 2 | /node_modules/ 3 | -------------------------------------------------------------------------------- /examples/trivial-service-trace-example/Pulumi.yaml: -------------------------------------------------------------------------------- 1 | name: trivial-nginx 2 | runtime: nodejs 3 | description: Basic example of an Kubernetes sample based on \ 4 | https://kubernetes.io/docs/user-guide/walkthrough/ 5 | -------------------------------------------------------------------------------- /examples/trivial-service-trace-example/README.md: -------------------------------------------------------------------------------- 1 | # Example: What happens when a `Service` starts? 2 | 3 | In this example, we: 4 | 5 | - Install a `Deployment` replicating an `nginx` `Pod` 3 times in a Kubernetes cluster. 6 | - Install a `Service` exposing those `nginx` `Pod`s to the Internet. 7 | - Use `kubespy trace` to watch what happens to that `Pod` as it starts. 8 | 9 | You'll need: 10 | 11 | - **Access to a Kubernetes cluster.** If you are using Pulumi, you can 12 | trivially boot an 13 | [GKE](https://github.com/pulumi/examples/tree/master/gcp-ts-gke), 14 | [AKS](https://github.com/pulumi/examples/tree/master/azure-ts-aks-mean), or 15 | [EKS](https://github.com/pulumi/examples/tree/master/aws-ts-eks) cluster. 16 | **NOTE:** If you use [minikube](https://github.com/kubernetes/minikube), 17 | you'll need to change the `Service`'s type to be `ClusterIP`, as minikube 18 | does not support type `LoadBalancer`. 19 | - **Either the Pulumi CLI or `kubectl`.** There is nothing Pulumi-specific in 20 | this example, so you can use `kubectl`, but we hope you'll give Pulumi a 21 | shot! The CLI installation instructions 22 | [here](https://pulumi.io/quickstart/install.html). Pulumi works anywhere 23 | `kubectl` works (i.e., anywhere you have a kubeconfig file), so it should 24 | "just work" if you already have a Kubernetes cluster running. 25 | - **`kubespy`.** Installation is a handful of commands, which you can find in the [README](https://github.com/pulumi/kubespy#installation). 26 | 27 | Once these are complete, you'll want to do two things: 28 | 29 | 1. **Run `kubespy`.** Once you've installed it, this should be as simple as 30 | `kubespy status v1 Pod nginx`. `kubespy` will dutifully wait for you to 31 | deploy a `Pod` called `nginx` to your cluster. 32 | 2. **Run the example.** `kubespy` repository contains a tiny example Pod that 33 | deploys an NGINX container. 34 | 35 | ```sh 36 | # With Pulumi CLI. 37 | $ git clone git@github.com:pulumi/kubespy.git 38 | $ cd kubespy/examples/trivial-service-trace-example 39 | $ npm install 40 | $ pulumi up 41 | 42 | # With kubectl. 43 | kubectl create -f https://github.com/pulumi/kubespy/raw/master/examples/trivial-service-trace-example/yaml/nginx.yaml 44 | ``` 45 | 46 | Once done, running `kubespy trace service nginx` should display something like this: 47 | 48 | ![Changes](../../images/trace/trace-success.gif "Changes a Service undergoes as it starts, in real time") 49 | -------------------------------------------------------------------------------- /examples/trivial-service-trace-example/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2018, Pulumi Corporation. All rights reserved. 2 | 3 | import * as k8s from "@pulumi/kubernetes"; 4 | 5 | // Create an nginx pod 6 | let nginx = new k8s.yaml.ConfigFile("yaml/nginx.yaml"); 7 | -------------------------------------------------------------------------------- /examples/trivial-service-trace-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodeserver", 3 | "version": "0.1.0", 4 | "dependencies": { 5 | "@pulumi/pulumi": "^3.39.1" 6 | }, 7 | "devDependencies": { 8 | "@types/node": "^9.3.0", 9 | "@pulumi/kubernetes": "latest", 10 | "typescript": "^2.5.3" 11 | }, 12 | "license": "MIT" 13 | } 14 | -------------------------------------------------------------------------------- /examples/trivial-service-trace-example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "bin", 4 | "target": "es6", 5 | "module": "commonjs", 6 | "moduleResolution": "node", 7 | "declaration": true, 8 | "sourceMap": true, 9 | "stripInternal": true, 10 | "experimentalDecorators": true, 11 | "pretty": true, 12 | "noFallthroughCasesInSwitch": true, 13 | "noImplicitAny": true, 14 | "noImplicitReturns": true, 15 | "forceConsistentCasingInFileNames": true, 16 | "strictNullChecks": true 17 | }, 18 | "files": [ 19 | "index.ts" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /examples/trivial-service-trace-example/yaml/nginx.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2 2 | kind: Deployment 3 | metadata: 4 | name: nginx-deployment 5 | spec: 6 | selector: 7 | matchLabels: 8 | app: nginx 9 | replicas: 3 # tells deployment to run 3 pods matching the template 10 | template: 11 | metadata: 12 | labels: 13 | app: nginx 14 | spec: 15 | containers: 16 | - name: nginx 17 | image: nginx:1.7.9 18 | ports: 19 | - containerPort: 80 20 | --- 21 | kind: Service 22 | apiVersion: v1 23 | metadata: 24 | name: nginx 25 | labels: 26 | app: nginx 27 | spec: 28 | type: LoadBalancer 29 | ports: 30 | - port: 80 31 | targetPort: 80 32 | protocol: "TCP" 33 | selector: 34 | app: nginx 35 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/pulumi/kubespy 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/fatih/color v1.13.0 7 | github.com/mbrlabs/uilive v0.0.0-20170420192653-e481c8e66f15 8 | github.com/pulumi/pulumi-kubernetes/provider/v3 v3.0.0-20220901210459-0b37d2b30290 9 | github.com/spf13/cobra v1.5.0 10 | github.com/yudai/gojsondiff v1.0.0 11 | k8s.io/apimachinery v0.25.0 12 | k8s.io/client-go v0.25.0 13 | ) 14 | 15 | require ( 16 | cloud.google.com/go/compute v1.19.1 // indirect 17 | cloud.google.com/go/compute/metadata v0.2.3 // indirect 18 | dario.cat/mergo v1.0.0 // indirect 19 | github.com/Azure/go-autorest v14.2.0+incompatible // indirect 20 | github.com/Azure/go-autorest/autorest v0.11.28 // indirect 21 | github.com/Azure/go-autorest/autorest/adal v0.9.21 // indirect 22 | github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect 23 | github.com/Azure/go-autorest/logger v0.2.1 // indirect 24 | github.com/Azure/go-autorest/tracing v0.6.0 // indirect 25 | github.com/Microsoft/go-winio v0.6.1 // indirect 26 | github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect 27 | github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect 28 | github.com/agext/levenshtein v1.2.3 // indirect 29 | github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect 30 | github.com/blang/semver v3.5.1+incompatible // indirect 31 | github.com/cheggaaa/pb v1.0.29 // indirect 32 | github.com/cloudflare/circl v1.3.7 // indirect 33 | github.com/cyphar/filepath-securejoin v0.2.4 // indirect 34 | github.com/davecgh/go-spew v1.1.1 // indirect 35 | github.com/djherbis/times v1.5.0 // indirect 36 | github.com/edsrzf/mmap-go v1.1.0 // indirect 37 | github.com/emicklei/go-restful/v3 v3.9.0 // indirect 38 | github.com/emirpasic/gods v1.18.1 // indirect 39 | github.com/evanphx/json-patch v5.6.0+incompatible // indirect 40 | github.com/go-errors/errors v1.4.2 // indirect 41 | github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect 42 | github.com/go-git/go-billy/v5 v5.5.0 // indirect 43 | github.com/go-git/go-git/v5 v5.11.0 // indirect 44 | github.com/go-logr/logr v1.2.3 // indirect 45 | github.com/go-openapi/jsonpointer v0.19.5 // indirect 46 | github.com/go-openapi/jsonreference v0.20.0 // indirect 47 | github.com/go-openapi/swag v0.22.3 // indirect 48 | github.com/gofrs/uuid v4.2.0+incompatible // indirect 49 | github.com/gogo/protobuf v1.3.2 // indirect 50 | github.com/golang-jwt/jwt/v4 v4.4.2 // indirect 51 | github.com/golang/glog v1.1.0 // indirect 52 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 53 | github.com/golang/protobuf v1.5.3 // indirect 54 | github.com/google/gnostic v0.6.9 // indirect 55 | github.com/google/gofuzz v1.2.0 // indirect 56 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect 57 | github.com/gosuri/uilive v0.0.0-20170323041506-ac356e6e42cd // indirect 58 | github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 // indirect 59 | github.com/hashicorp/errwrap v1.1.0 // indirect 60 | github.com/hashicorp/go-multierror v1.1.1 // indirect 61 | github.com/hashicorp/hcl/v2 v2.14.0 // indirect 62 | github.com/imdario/mergo v0.3.13 // indirect 63 | github.com/inconshreveable/mousetrap v1.0.1 // indirect 64 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect 65 | github.com/josharian/intern v1.0.0 // indirect 66 | github.com/json-iterator/go v1.1.12 // indirect 67 | github.com/kevinburke/ssh_config v1.2.0 // indirect 68 | github.com/mailru/easyjson v0.7.7 // indirect 69 | github.com/mattn/go-colorable v0.1.13 // indirect 70 | github.com/mattn/go-isatty v0.0.16 // indirect 71 | github.com/mattn/go-runewidth v0.0.13 // indirect 72 | github.com/mitchellh/go-ps v1.0.0 // indirect 73 | github.com/mitchellh/go-wordwrap v1.0.1 // indirect 74 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 75 | github.com/modern-go/reflect2 v1.0.2 // indirect 76 | github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect 77 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 78 | github.com/natefinch/atomic v1.0.1 // indirect 79 | github.com/onsi/ginkgo v1.16.5 // indirect 80 | github.com/opentracing/basictracer-go v1.1.0 // indirect 81 | github.com/opentracing/opentracing-go v1.2.0 // indirect 82 | github.com/pgavlin/goldmark v1.1.33-0.20200616210433-b5eb04559386 // indirect 83 | github.com/pjbgf/sha1cd v0.3.0 // indirect 84 | github.com/pkg/errors v0.9.1 // indirect 85 | github.com/pkg/term v1.1.0 // indirect 86 | github.com/pulumi/pulumi/pkg/v3 v3.39.1 // indirect 87 | github.com/pulumi/pulumi/sdk/v3 v3.39.1 // indirect 88 | github.com/rivo/uniseg v0.3.4 // indirect 89 | github.com/rogpeppe/go-internal v1.11.0 // indirect 90 | github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 // indirect 91 | github.com/santhosh-tekuri/jsonschema/v5 v5.0.0 // indirect 92 | github.com/segmentio/asm v1.2.0 // indirect 93 | github.com/segmentio/encoding v0.3.5 // indirect 94 | github.com/sergi/go-diff v1.2.0 // indirect 95 | github.com/skeema/knownhosts v1.2.1 // indirect 96 | github.com/spf13/pflag v1.0.5 // indirect 97 | github.com/stretchr/objx v0.4.0 // indirect 98 | github.com/texttheater/golang-levenshtein v1.0.1 // indirect 99 | github.com/tweekmonster/luser v0.0.0-20161003172636-3fa38070dbd7 // indirect 100 | github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect 101 | github.com/uber/jaeger-lib v2.4.1+incompatible // indirect 102 | github.com/xanzy/ssh-agent v0.3.3 // indirect 103 | github.com/xlab/treeprint v1.1.0 // indirect 104 | github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect 105 | github.com/yudai/pp v2.0.1+incompatible // indirect 106 | github.com/zclconf/go-cty v1.11.0 // indirect 107 | go.starlark.net v0.0.0-20220817180228-f738f5508c12 // indirect 108 | go.uber.org/atomic v1.10.0 // indirect 109 | golang.org/x/crypto v0.17.0 // indirect 110 | golang.org/x/mod v0.12.0 // indirect 111 | golang.org/x/net v0.19.0 // indirect 112 | golang.org/x/oauth2 v0.7.0 // indirect 113 | golang.org/x/sys v0.15.0 // indirect 114 | golang.org/x/term v0.15.0 // indirect 115 | golang.org/x/text v0.14.0 // indirect 116 | golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 // indirect 117 | golang.org/x/tools v0.13.0 // indirect 118 | google.golang.org/appengine v1.6.7 // indirect 119 | google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect 120 | google.golang.org/grpc v1.56.3 // indirect 121 | google.golang.org/protobuf v1.33.0 // indirect 122 | gopkg.in/inf.v0 v0.9.1 // indirect 123 | gopkg.in/warnings.v0 v0.1.2 // indirect 124 | gopkg.in/yaml.v2 v2.4.0 // indirect 125 | gopkg.in/yaml.v3 v3.0.1 // indirect 126 | k8s.io/api v0.25.0 // indirect 127 | k8s.io/cli-runtime v0.25.0 // indirect 128 | k8s.io/klog/v2 v2.80.0 // indirect 129 | k8s.io/kube-openapi v0.0.0-20220803164354-a70c9af30aea // indirect 130 | k8s.io/kubectl v0.25.0 // indirect 131 | k8s.io/utils v0.0.0-20220823124924-e9cbc92d1a73 // indirect 132 | lukechampine.com/frand v1.4.2 // indirect 133 | sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect 134 | sigs.k8s.io/kustomize/api v0.12.1 // indirect 135 | sigs.k8s.io/kustomize/kyaml v0.13.9 // indirect 136 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect 137 | sigs.k8s.io/yaml v1.3.0 // indirect 138 | sourcegraph.com/sourcegraph/appdash v0.0.0-20211028080628-e2786a622600 // indirect 139 | ) 140 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | cloud.google.com/go/compute v1.19.1 h1:am86mquDUgjGNWxiGn+5PGLbmgiWXlE/yNWpIpNvuXY= 4 | cloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE= 5 | cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= 6 | cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= 7 | dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= 8 | dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= 9 | github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= 10 | github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= 11 | github.com/Azure/go-autorest/autorest v0.11.28 h1:ndAExarwr5Y+GaHE6VCaY1kyS/HwwGGyuimVhWsHOEM= 12 | github.com/Azure/go-autorest/autorest v0.11.28/go.mod h1:MrkzG3Y3AH668QyF9KRk5neJnGgmhQ6krbhR8Q5eMvA= 13 | github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ= 14 | github.com/Azure/go-autorest/autorest/adal v0.9.21 h1:jjQnVFXPfekaqb8vIsv2G1lxshoW+oGv4MDlhRtnYZk= 15 | github.com/Azure/go-autorest/autorest/adal v0.9.21/go.mod h1:zua7mBUaCc5YnSLKYgGJR/w5ePdMDA6H56upLsHzA9U= 16 | github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= 17 | github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= 18 | github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= 19 | github.com/Azure/go-autorest/autorest/mocks v0.4.2 h1:PGN4EDXnuQbojHbU0UWoNvmu9AGVwYHG9/fkDYhtAfw= 20 | github.com/Azure/go-autorest/autorest/mocks v0.4.2/go.mod h1:Vy7OitM9Kei0i1Oj+LvyAWMXJHeKH1MVlzFugfVrmyU= 21 | github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= 22 | github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= 23 | github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= 24 | github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= 25 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 26 | github.com/HdrHistogram/hdrhistogram-go v1.1.2 h1:5IcZpTvzydCQeHzK4Ef/D5rrSqwxob0t8PQPMybUNFM= 27 | github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= 28 | github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= 29 | github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= 30 | github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= 31 | github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg= 32 | github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= 33 | github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY= 34 | github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= 35 | github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= 36 | github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= 37 | github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= 38 | github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= 39 | github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw= 40 | github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= 41 | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= 42 | github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= 43 | github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= 44 | github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= 45 | github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= 46 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 47 | github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= 48 | github.com/cheggaaa/pb v1.0.29 h1:FckUN5ngEk2LpvuG0fw1GEFx6LtyY2pWI/Z2QgCnEYo= 49 | github.com/cheggaaa/pb v1.0.29/go.mod h1:W40334L7FMC5JKWldsTWbdGjLo0RxUKK73K+TuPxX30= 50 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 51 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 52 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 53 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 54 | github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= 55 | github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= 56 | github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= 57 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 58 | github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 59 | github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 60 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 61 | github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= 62 | github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= 63 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 64 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 65 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 66 | github.com/djherbis/times v1.5.0 h1:79myA211VwPhFTqUk8xehWrsEO+zcIZj0zT8mXPVARU= 67 | github.com/djherbis/times v1.5.0/go.mod h1:5q7FDLvbNg1L/KaBmPcWlVR9NmoKo3+ucqUA3ijQhA0= 68 | github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= 69 | github.com/edsrzf/mmap-go v1.1.0 h1:6EUwBLQ/Mcr1EYLE4Tn1VdW1A4ckqCQWZBw8Hr0kjpQ= 70 | github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q= 71 | github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU= 72 | github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= 73 | github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= 74 | github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= 75 | github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= 76 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 77 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 78 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 79 | github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= 80 | github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= 81 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 82 | github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= 83 | github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= 84 | github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= 85 | github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= 86 | github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= 87 | github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0= 88 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 89 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= 90 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 91 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 92 | github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= 93 | github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= 94 | github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= 95 | github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= 96 | github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= 97 | github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= 98 | github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= 99 | github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= 100 | github.com/go-git/go-git/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3cA4= 101 | github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY= 102 | github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= 103 | github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 104 | github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= 105 | github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 106 | github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= 107 | github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= 108 | github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= 109 | github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= 110 | github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= 111 | github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= 112 | github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= 113 | github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= 114 | github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= 115 | github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0= 116 | github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= 117 | github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= 118 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 119 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 120 | github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= 121 | github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= 122 | github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs= 123 | github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= 124 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 125 | github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE= 126 | github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= 127 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= 128 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 129 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 130 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 131 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 132 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 133 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 134 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 135 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 136 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 137 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 138 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 139 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 140 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 141 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 142 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 143 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 144 | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= 145 | github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 146 | github.com/google/gnostic v0.6.9 h1:ZK/5VhkoX835RikCHpSUJV9a+S3e1zLh59YnyWeBW+0= 147 | github.com/google/gnostic v0.6.9/go.mod h1:Nm8234We1lq6iB9OmlgNv3nH91XLLVZHCDayfA3xq+E= 148 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 149 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 150 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 151 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 152 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 153 | github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 154 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 155 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 156 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 157 | github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= 158 | github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 159 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= 160 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= 161 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 162 | github.com/gosuri/uilive v0.0.0-20170323041506-ac356e6e42cd h1:1e+0Z+T4t1mKL5xxvxXh5FkjuiToQGKreCobLu7lR3Y= 163 | github.com/gosuri/uilive v0.0.0-20170323041506-ac356e6e42cd/go.mod h1:qkLSc0A5EXSP6B04TrN4oQoxqFI7A8XvoXSlJi8cwk8= 164 | github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= 165 | github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 h1:MJG/KsmcqMwFAkh8mTnAwhyKoB+sTAnY4CACC110tbU= 166 | github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw= 167 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 168 | github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= 169 | github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 170 | github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= 171 | github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= 172 | github.com/hashicorp/hcl/v2 v2.14.0 h1:jX6+Q38Ly9zaAJlAjnFVyeNSNCKKW8D0wvyg7vij5Wc= 173 | github.com/hashicorp/hcl/v2 v2.14.0/go.mod h1:e4z5nxYlWNPdDSNYX+ph14EvWYMFm3eP0zIUqPc2jr0= 174 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 175 | github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= 176 | github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= 177 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 178 | github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= 179 | github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 180 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= 181 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= 182 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 183 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 184 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 185 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 186 | github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= 187 | github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= 188 | github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= 189 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 190 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 191 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 192 | github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 193 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 194 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 195 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 196 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 197 | github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 198 | github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 199 | github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= 200 | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 201 | github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 202 | github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= 203 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 204 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 205 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 206 | github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= 207 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 208 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 209 | github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= 210 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 211 | github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= 212 | github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= 213 | github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 214 | github.com/mbrlabs/uilive v0.0.0-20170420192653-e481c8e66f15 h1:5AXdlShDW5T1s7PEnhRbA66CwVx+4uTroumVqA9ZEY4= 215 | github.com/mbrlabs/uilive v0.0.0-20170420192653-e481c8e66f15/go.mod h1:4d9auAn1Z/LVhIAV74OQ4uVXFGGL8Z0L1+txzWK3r7Y= 216 | github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= 217 | github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= 218 | github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= 219 | github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= 220 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 221 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 222 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 223 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 224 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 225 | github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= 226 | github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= 227 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 228 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 229 | github.com/natefinch/atomic v1.0.1 h1:ZPYKxkqQOx3KZ+RsbnP/YsgvxWQPGxjC0oBt2AhwV0A= 230 | github.com/natefinch/atomic v1.0.1/go.mod h1:N/D/ELrljoqDyT3rZrsUmtsuzvHkeB/wWjHV22AZRbM= 231 | github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= 232 | github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= 233 | github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= 234 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 235 | github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= 236 | github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= 237 | github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= 238 | github.com/onsi/ginkgo/v2 v2.1.4 h1:GNapqRSid3zijZ9H77KrgVG4/8KqiyRsxcSxe+7ApXY= 239 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= 240 | github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= 241 | github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= 242 | github.com/opentracing/basictracer-go v1.1.0 h1:Oa1fTSBvAl8pa3U+IJYqrKm0NALwH9OsgwOqDv4xJW0= 243 | github.com/opentracing/basictracer-go v1.1.0/go.mod h1:V2HZueSJEp879yv285Aap1BS69fQMD+MNP1mRs6mBQc= 244 | github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= 245 | github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= 246 | github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= 247 | github.com/pgavlin/goldmark v1.1.33-0.20200616210433-b5eb04559386 h1:LoCV5cscNVWyK5ChN/uCoIFJz8jZD63VQiGJIRgr6uo= 248 | github.com/pgavlin/goldmark v1.1.33-0.20200616210433-b5eb04559386/go.mod h1:MRxHTJrf9FhdfNQ8Hdeh9gmHevC9RJE/fu8M3JIGjoE= 249 | github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= 250 | github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= 251 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 252 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 253 | github.com/pkg/term v1.1.0 h1:xIAAdCMh3QIAy+5FrE8Ad8XoDhEU4ufwbaSozViP9kk= 254 | github.com/pkg/term v1.1.0/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw= 255 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 256 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 257 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 258 | github.com/pulumi/pulumi-kubernetes/provider/v3 v3.0.0-20220901210459-0b37d2b30290 h1:K8zkfIB7B5tIrLYjYOtjKm13ZF/NCn2xMDV4wlSm1bY= 259 | github.com/pulumi/pulumi-kubernetes/provider/v3 v3.0.0-20220901210459-0b37d2b30290/go.mod h1:jM3hq0pT4rUL3vQnHCRQ4qX1iP0Ias4Z5SED/JaVQk0= 260 | github.com/pulumi/pulumi/pkg/v3 v3.39.1 h1:dZ5BpOeIDNQ4rzYpLxLx5g5bzAP09+pfdMvMeRNDXUk= 261 | github.com/pulumi/pulumi/pkg/v3 v3.39.1/go.mod h1:mhOxT4/LQ4UwIC31gvgmf9L9HT+4uxUU7f9ZJgA5I6s= 262 | github.com/pulumi/pulumi/sdk/v3 v3.39.1 h1:y6O5H91/lA7PTE3/Z3tcu79jZI0E/dLVakfU7AfKJK4= 263 | github.com/pulumi/pulumi/sdk/v3 v3.39.1/go.mod h1:Fw52iyR/4T9xWm7cTcshy4rGEXyPwhXKKEalczKZ8RY= 264 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 265 | github.com/rivo/uniseg v0.3.4 h1:3Z3Eu6FGHZWSfNKJTOUiPatWwfc7DzJRU04jFUqJODw= 266 | github.com/rivo/uniseg v0.3.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 267 | github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= 268 | github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= 269 | github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= 270 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 271 | github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 h1:OkMGxebDjyw0ULyrTYWeN0UNCCkmCWfjPnIA2W6oviI= 272 | github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06/go.mod h1:+ePHsJ1keEjQtpvf9HHw0f4ZeJ0TLRsxhunSI2hYJSs= 273 | github.com/santhosh-tekuri/jsonschema/v5 v5.0.0 h1:TToq11gyfNlrMFZiYujSekIsPd9AmsA2Bj/iv+s4JHE= 274 | github.com/santhosh-tekuri/jsonschema/v5 v5.0.0/go.mod h1:FKdcjfQW6rpZSnxxUvEA5H/cDPdvJ/SZJQLWWXWGrZ0= 275 | github.com/segmentio/asm v1.1.3/go.mod h1:Ld3L4ZXGNcSLRg4JBsZ3//1+f/TjYl0Mzen/DQy1EJg= 276 | github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= 277 | github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= 278 | github.com/segmentio/encoding v0.3.5 h1:UZEiaZ55nlXGDL92scoVuw00RmiRCazIEmvPSbSvt8Y= 279 | github.com/segmentio/encoding v0.3.5/go.mod h1:n0JeuIqEQrQoPDGsjo8UNd1iA0U8d8+oHAA4E3G3OxM= 280 | github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= 281 | github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= 282 | github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= 283 | github.com/skeema/knownhosts v1.2.1 h1:SHWdIUa82uGZz+F+47k8SY4QhhI291cXCpopT1lK2AQ= 284 | github.com/skeema/knownhosts v1.2.1/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= 285 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 286 | github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= 287 | github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= 288 | github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= 289 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 290 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 291 | github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= 292 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 293 | github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4= 294 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 295 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 296 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 297 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 298 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 299 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 300 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 301 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 302 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 303 | github.com/texttheater/golang-levenshtein v1.0.1 h1:+cRNoVrfiwufQPhoMzB6N0Yf/Mqajr6t1lOv8GyGE2U= 304 | github.com/texttheater/golang-levenshtein v1.0.1/go.mod h1:PYAKrbF5sAiq9wd+H82hs7gNaen0CplQ9uvm6+enD/8= 305 | github.com/tweekmonster/luser v0.0.0-20161003172636-3fa38070dbd7 h1:X9dsIWPuuEJlPX//UmRKophhOKCGXc46RVIGuttks68= 306 | github.com/tweekmonster/luser v0.0.0-20161003172636-3fa38070dbd7/go.mod h1:UxoP3EypF8JfGEjAII8jx1q8rQyDnX8qdTCs/UQBVIE= 307 | github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o= 308 | github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= 309 | github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg= 310 | github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= 311 | github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= 312 | github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= 313 | github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= 314 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= 315 | github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= 316 | github.com/xlab/treeprint v1.1.0 h1:G/1DjNkPpfZCFt9CSh6b5/nY4VimlbHF3Rh4obvtzDk= 317 | github.com/xlab/treeprint v1.1.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= 318 | github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= 319 | github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= 320 | github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= 321 | github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= 322 | github.com/yudai/pp v2.0.1+incompatible h1:Q4//iY4pNF6yPLZIigmvcl7k/bPgrcTPIFIcmawg5bI= 323 | github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= 324 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 325 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 326 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 327 | github.com/zclconf/go-cty v1.11.0 h1:726SxLdi2SDnjY+BStqB9J1hNp4+2WlzyXLuimibIe0= 328 | github.com/zclconf/go-cty v1.11.0/go.mod h1:s9IfD1LK5ccNMSWCVFCE2rJfHiZgi7JijgeWIMfhLvA= 329 | go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= 330 | go.starlark.net v0.0.0-20220817180228-f738f5508c12 h1:xOBJXWGEDwU5xSDxH6macxO11Us0AH2fTa9rmsbbF7g= 331 | go.starlark.net v0.0.0-20220817180228-f738f5508c12/go.mod h1:VZcBMdr3cT3PnBoWunTabuSEXwVAH+ZJ5zxfs3AdASk= 332 | go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= 333 | go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= 334 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 335 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 336 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 337 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 338 | golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 339 | golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 340 | golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= 341 | golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= 342 | golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= 343 | golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= 344 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 345 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 346 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 347 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 348 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 349 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 350 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 351 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 352 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 353 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 354 | golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= 355 | golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 356 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 357 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 358 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 359 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 360 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 361 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 362 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 363 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 364 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 365 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 366 | golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 367 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 368 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 369 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 370 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 371 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 372 | golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 373 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 374 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 375 | golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= 376 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 377 | golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= 378 | golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= 379 | golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= 380 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 381 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 382 | golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g= 383 | golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= 384 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 385 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 386 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 387 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 388 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 389 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 390 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 391 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 392 | golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= 393 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 394 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 395 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 396 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 397 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 398 | golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 399 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 400 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 401 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 402 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 403 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 404 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 405 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 406 | golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 407 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 408 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 409 | golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 410 | golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 411 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 412 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 413 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 414 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 415 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 416 | golang.org/x/sys v0.0.0-20211110154304-99a53858aa08/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 417 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 418 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 419 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 420 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 421 | golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 422 | golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 423 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 424 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 425 | golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= 426 | golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 427 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 428 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 429 | golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= 430 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 431 | golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= 432 | golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= 433 | golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= 434 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 435 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 436 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 437 | golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 438 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 439 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 440 | golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 441 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 442 | golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 443 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= 444 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 445 | golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 h1:ftMN5LMiBFjbzleLqtoBZk7KdJwhuybIU+FckUHgoyQ= 446 | golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 447 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 448 | golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 449 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 450 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 451 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 452 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 453 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 454 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 455 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 456 | golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 457 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 458 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 459 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 460 | golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= 461 | golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 462 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 463 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 464 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 465 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 466 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 467 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 468 | google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= 469 | google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 470 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 471 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 472 | google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 473 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 474 | google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= 475 | google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= 476 | google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= 477 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 478 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 479 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 480 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 481 | google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= 482 | google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= 483 | google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= 484 | google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc= 485 | google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= 486 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 487 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 488 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 489 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 490 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 491 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 492 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 493 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 494 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 495 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 496 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 497 | google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 498 | google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= 499 | google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 500 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 501 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 502 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 503 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 504 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 505 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 506 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 507 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 508 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 509 | gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= 510 | gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= 511 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 512 | gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 513 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 514 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 515 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 516 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 517 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 518 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 519 | gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 520 | gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 521 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 522 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 523 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 524 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 525 | k8s.io/api v0.25.0 h1:H+Q4ma2U/ww0iGB78ijZx6DRByPz6/733jIuFpX70e0= 526 | k8s.io/api v0.25.0/go.mod h1:ttceV1GyV1i1rnmvzT3BST08N6nGt+dudGrquzVQWPk= 527 | k8s.io/apimachinery v0.25.0 h1:MlP0r6+3XbkUG2itd6vp3oxbtdQLQI94fD5gCS+gnoU= 528 | k8s.io/apimachinery v0.25.0/go.mod h1:qMx9eAk0sZQGsXGu86fab8tZdffHbwUfsvzqKn4mfB0= 529 | k8s.io/cli-runtime v0.25.0 h1:XBnTc2Fi+w818jcJGzhiJKQuXl8479sZ4FhtV5hVJ1Q= 530 | k8s.io/cli-runtime v0.25.0/go.mod h1:bHOI5ZZInRHhbq12OdUiYZQN8ml8aKZLwQgt9QlLINw= 531 | k8s.io/client-go v0.25.0 h1:CVWIaCETLMBNiTUta3d5nzRbXvY5Hy9Dpl+VvREpu5E= 532 | k8s.io/client-go v0.25.0/go.mod h1:lxykvypVfKilxhTklov0wz1FoaUZ8X4EwbhS6rpRfN8= 533 | k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= 534 | k8s.io/klog/v2 v2.80.0 h1:lyJt0TWMPaGoODa8B8bUuxgHS3W/m/bNr2cca3brA/g= 535 | k8s.io/klog/v2 v2.80.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= 536 | k8s.io/kube-openapi v0.0.0-20220803164354-a70c9af30aea h1:3QOH5+2fGsY8e1qf+GIFpg+zw/JGNrgyZRQR7/m6uWg= 537 | k8s.io/kube-openapi v0.0.0-20220803164354-a70c9af30aea/go.mod h1:C/N6wCaBHeBHkHUesQOQy2/MZqGgMAFPqGsGQLdbZBU= 538 | k8s.io/kubectl v0.25.0 h1:/Wn1cFqo8ik3iee1EvpxYre3bkWsGLXzLQI6uCCAkQc= 539 | k8s.io/kubectl v0.25.0/go.mod h1:n16ULWsOl2jmQpzt2o7Dud1t4o0+Y186ICb4O+GwKAU= 540 | k8s.io/utils v0.0.0-20220823124924-e9cbc92d1a73 h1:H9TCJUUx+2VA0ZiD9lvtaX8fthFsMoD+Izn93E/hm8U= 541 | k8s.io/utils v0.0.0-20220823124924-e9cbc92d1a73/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= 542 | lukechampine.com/frand v1.4.2 h1:RzFIpOvkMXuPMBb9maa4ND4wjBn71E1Jpf8BzJHMaVw= 543 | lukechampine.com/frand v1.4.2/go.mod h1:4S/TM2ZgrKejMcKMbeLjISpJMO+/eZ1zu3vYX9dtj3s= 544 | pgregory.net/rapid v0.4.7 h1:MTNRktPuv5FNqOO151TM9mDTa+XHcX6ypYeISDVD14g= 545 | sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k= 546 | sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= 547 | sigs.k8s.io/kustomize/api v0.12.1 h1:7YM7gW3kYBwtKvoY216ZzY+8hM+lV53LUayghNRJ0vM= 548 | sigs.k8s.io/kustomize/api v0.12.1/go.mod h1:y3JUhimkZkR6sbLNwfJHxvo1TCLwuwm14sCYnkH6S1s= 549 | sigs.k8s.io/kustomize/kyaml v0.13.9 h1:Qz53EAaFFANyNgyOEJbT/yoIHygK40/ZcvU3rgry2Tk= 550 | sigs.k8s.io/kustomize/kyaml v0.13.9/go.mod h1:QsRbD0/KcU+wdk0/L0fIp2KLnohkVzs6fQ85/nOXac4= 551 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= 552 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= 553 | sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= 554 | sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= 555 | sourcegraph.com/sourcegraph/appdash v0.0.0-20211028080628-e2786a622600 h1:hfyJ5ku9yFtLVOiSxa3IN+dx5eBQT9mPmKFypAmg8XM= 556 | sourcegraph.com/sourcegraph/appdash v0.0.0-20211028080628-e2786a622600/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= 557 | -------------------------------------------------------------------------------- /images/1-created.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulumi/kubespy/e593ecad4e99f9a68118acd8e9d61b6c05ae4685/images/1-created.png -------------------------------------------------------------------------------- /images/2-scheduled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulumi/kubespy/e593ecad4e99f9a68118acd8e9d61b6c05ae4685/images/2-scheduled.png -------------------------------------------------------------------------------- /images/3-creating.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulumi/kubespy/e593ecad4e99f9a68118acd8e9d61b6c05ae4685/images/3-creating.png -------------------------------------------------------------------------------- /images/4-running.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulumi/kubespy/e593ecad4e99f9a68118acd8e9d61b6c05ae4685/images/4-running.png -------------------------------------------------------------------------------- /images/changes.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulumi/kubespy/e593ecad4e99f9a68118acd8e9d61b6c05ae4685/images/changes.gif -------------------------------------------------------------------------------- /images/status.cast: -------------------------------------------------------------------------------- 1 | {"version": 2, "width": 100, "height": 30, "timestamp": 1537208657, "env": {"SHELL": "/bin/zsh", "TERM": "xterm-256color"}} 2 | [0.27429, "o", "\u001b[1m\u001b[7m%\u001b[27m\u001b[1m\u001b[0m \r \r"] 3 | [0.275156, "o", "\u001b]2;alex@fabienne-2: ~/go/src/github.com/pulumi-labs/kubespy\u0007\u001b]1;..-labs/kubespy\u0007"] 4 | [0.320051, "o", "\r\u001b[0m\u001b[27m\u001b[24m\u001b[J\u001b[01;32m➜ \u001b[36mkubespy\u001b[00m \u001b[01;34mgit:(\u001b[31mmaster\u001b[34m) \u001b[33m✗\u001b[00m \u001b[K"] 5 | [0.320366, "o", "\u001b[?1h\u001b=\u001b[?2004h"] 6 | [1.194562, "o", "\r\r\nbck-i-search: _\u001b[K\u001b[A\u001b[11C"] 7 | [1.425189, "o", "\u001b[4mk\u001b[24mubespy status v1 Pod nginx\u001b[1B\u001b[39Dk_\u001b[A\u001b[10C"] 8 | [1.626793, "o", "\u001b[4mk\u001b[4mu\u001b[24m\u001b[1B\u001b[13Du_\u001b[A\u001b[9C"] 9 | [1.660115, "o", "\u001b[4mk\u001b[4mu\u001b[4mb\u001b[24m\u001b[1B\u001b[13Db_\u001b[A\u001b[8C"] 10 | [2.705657, "o", "\u001b[24mk\u001b[24mu\u001b[24mb\u001b[1B\r\u001b[K\u001b[A\u001b[26C"] 11 | [2.70571, "o", "\u001b[?1l\u001b>"] 12 | [2.705942, "o", "\u001b[?2004l\u001b[1B\r"] 13 | [2.706567, "o", "\u001b]2;kubespy status v1 Pod nginx\u0007\u001b]1;kubespy\u0007"] 14 | [2.751704, "o", "\u001b[32mWatching status of v1 Pod nginx\u001b[0m\r\n"] 15 | [3.4137, "o", "\u001b[34;1mCREATED\r\n\u001b[0m\u001b[32m{\r\n \"phase\": \"Pending\",\r\n \"qosClass\": \"BestEffort\"\r\n}\u001b[0m\r\n"] 16 | [7.420981, "o", "\u001b[34;1mMODIFIED\r\n\u001b[0m"] 17 | [7.421181, "o", " {\r\n \"phase\": \"Pending\",\r\n \"qosClass\": \"BestEffort\"\r\n\u001b[30;42m+ \"conditions\": [\u001b[0m\r\n\u001b[30;42m+ {\u001b[0m\r\n\u001b[30;42m+ \"lastProbeTime\": null,\u001b[0m\r\n\u001b[30;42m+ \"lastTransitionTime\": \"2018-09-17T18:24:24Z\",\u001b[0m\r\n\u001b[30;42m+ \"status\": \"True\",\u001b[0m\r\n\u001b[30;42m+ \"type\": \"PodScheduled\"\u001b[0m\r\n\u001b[30;42m+ }\u001b[0m\r\n\u001b[30;42m+ ]\u001b[0m\r\n }\r\n\r\n"] 18 | [10.434512, "o", "\u001b[34;1mMODIFIED\r\n\u001b[0m"] 19 | [10.434773, "o", " {\r\n \"conditions\": [\r\n\u001b[30;42m+ {\u001b[0m\r\n\u001b[30;42m+ \"lastProbeTime\": null,\u001b[0m\r\n\u001b[30;42m+ \"lastTransitionTime\": \"2018-09-17T18:24:25Z\",\u001b[0m\r\n\u001b[30;42m+ \"status\": \"True\",\u001b[0m\r\n\u001b[30;42m+ \"type\": \"Initialized\"\u001b[0m\r\n\u001b[30;42m+ }\u001b[0m\r\n\u001b[30;42m+ {\u001b[0m\r\n\u001b[30;42m+ \"lastProbeTime\": null,\u001b[0m\r\n\u001b[30;42m+ \"lastTransitionTime\": \"2018-09-17T18:24:25Z\",\u001b[0m\r\n\u001b[30;42m+ \"message\": \"containers with unready status: [nginx]\",\u001b[0m\r\n\u001b[30;42m+ \"reason\": \"ContainersNotReady\",\u001b[0m\r\n\u001b[30;42m+ \"status\": \"False\",\u001b[0m\r\n\u001b[30;42m+ \"type\": \"Ready\"\u001b[0m\r\n\u001b[30;42m+ }\u001b[0m\r\n ],\r\n \"phase\": \"Pending\",\r\n \"qosClass\": \"BestEffort\"\r\n\u001b[30;42m+ \"containerStatuses\": [\u001b[0m\r\n\u001b[30;42m+ {\u001b[0m\r\n\u001b[30;42m+ \"image\": \"xsdijsdldsjldjsklksd\",\u001b[0m\r\n\u001b[30;42m+ \"imageID\": \"\",\u001b[0m\r\n\u001b[30;42m+ \"lastState\": {\u001b[0m\r\n\u001b[30;42m+ },\u001b[0m\r\n\u001b[30;42m+ \"name\": \"nginx\",\u001b[0m\r\n\u001b[30;42m+ \"ready\": false,\u001b[0m\r\n\u001b[30;42m+ \"restartCount\": 0,\u001b[0m\r\n\u001b[30;42m+ \"state\": {\u001b[0m\r\n\u001b[30;42m+ "] 20 | [10.434984, "o", " \"waiting\": {\u001b[0m\r\n\u001b[30;42m+ \"reason\": \"ContainerCreating\"\u001b[0m\r\n\u001b[30;42m+ }\u001b[0m\r\n\u001b[30;42m+ }\u001b[0m\r\n\u001b[30;42m+ }\u001b[0m\r\n\u001b[30;42m+ ]\u001b[0m\r\n\u001b[30;42m+ \"hostIP\": \"10.0.2.15\"\u001b[0m\r\n\u001b[30;42m+ \"startTime\": \"2018-09-17T18:24:25Z\"\u001b[0m\r\n }\r\n\r\n"] 21 | [15.367308, "o", "\u001b[34;1mMODIFIED\r\n\u001b[0m"] 22 | [15.367546, "o", " {\r\n \"conditions\": [\r\n {\r\n \"lastProbeTime\": null,\r\n \"lastTransitionTime\": \"2018-09-17T18:24:25Z\",\r\n \"status\": \"True\",\r\n \"type\": \"Initialized\"\r\n },\r\n {\r\n \"lastProbeTime\": null,\r\n \"lastTransitionTime\": \"2018-09-17T18:24:25Z\",\r\n \"message\": \"containers with unready status: [nginx]\",\r\n \"reason\": \"ContainersNotReady\",\r\n \"status\": \"False\",\r\n \"type\": \"Ready\"\r\n },\r\n {\r\n \"lastProbeTime\": null,\r\n \"lastTransitionTime\": \"2018-09-17T18:24:24Z\",\r\n \"status\": \"True\",\r\n \"type\": \"PodScheduled\"\r\n }\r\n ],\r\n \"containerStatuses\": [\r\n {\r\n \"image\": \"xsdijsdldsjldjsklksd\",\r\n \"imageID\": \"\",\r\n \"lastState\": {\r\n },\r\n \"name\": \"nginx\",\r\n \"ready\": false,\r\n \"restartCount\": 0,\r\n \"state\": {\r\n \"waiting\": {\r\n\u001b[30;41m- \"reason\": \"ContainerCreating\"\u001b[0m\r\n\u001b[30;42m+ \"reason\": \"ErrImagePull\"\u001b[0m\r\n\u001b[30;42m+ \"message\": \"rpc error: code = Unknown desc = Erro"] 23 | [15.367731, "o", "r response from daemon: pull access denied for xsdijsdldsjldjsklksd, repository does not exist or may require 'docker login'\"\u001b[0m\r\n }\r\n }\r\n }\r\n ],\r\n \"hostIP\": \"10.0.2.15\",\r\n \"phase\": \"Pending\",\r\n \"qosClass\": \"BestEffort\",\r\n \"startTime\": \"2018-09-17T18:24:25Z\"\r\n\u001b[30;42m+ \"podIP\": \"172.17.0.4\"\u001b[0m\r\n }\r\n\r\n"] 24 | [26.678492, "o", "\u001b[34;1mMODIFIED\r\n\u001b[0m"] 25 | [26.679707, "o", " {\r\n \"conditions\": [\r\n {\r\n \"lastProbeTime\": null,\r\n \"lastTransitionTime\": \"2018-09-17T18:24:25Z\",\r\n \"status\": \"True\",\r\n \"type\": \"Initialized\"\r\n },\r\n {\r\n \"lastProbeTime\": null,\r\n \"lastTransitionTime\": \"2018-09-17T18:24:25Z\",\r\n \"message\": \"containers with unready status: [nginx]\",\r\n \"reason\": \"ContainersNotReady\",\r\n \"status\": \"False\",\r\n \"type\": \"Ready\"\r\n },\r\n {\r\n \"lastProbeTime\": null,\r\n \"lastTransitionTime\": \"2018-09-17T18:24:24Z\",\r\n \"status\": \"True\",\r\n \"type\": \"PodScheduled\"\r\n }\r\n ],\r\n \"containerStatuses\": [\r\n {\r\n \"image\": \"xsdijsdldsjldjsklksd\",\r\n \"imageID\": \"\",\r\n \"lastState\": {\r\n },\r\n \"name\": \"nginx\",\r\n \"ready\": false,\r\n \"restartCount\": 0,\r\n \"state\": {\r\n \"waiting\": {\r\n\u001b[30;41m- \"message\": \"rpc error: code = Unknown desc = Error response from daemon: pull access denied for xsdijsdldsjldjsklksd, repository does not exist or may r"] 26 | [26.679868, "o", "equire 'docker login'\",\u001b[0m\r\n\u001b[30;42m+ \"message\": \"Back-off pulling image \"xsdijsdldsjldjsklksd\"\",\u001b[0m\r\n\u001b[30;41m- \"reason\": \"ErrImagePull\"\u001b[0m\r\n\u001b[30;42m+ \"reason\": \"ImagePullBackOff\"\u001b[0m\r\n }\r\n }\r\n }\r\n ],\r\n \"hostIP\": \"10.0.2.15\",\r\n \"phase\": \"Pending\",\r\n \"podIP\": \"172.17.0.4\",\r\n \"qosClass\": \"BestEffort\",\r\n \"startTime\": \"2018-09-17T18:24:25Z\"\r\n }\r\n\r\n"] 27 | [29.198231, "o", "^C"] 28 | [29.200325, "o", "\r\n"] 29 | [29.200415, "o", "\u001b[1m\u001b[7m%\u001b[27m\u001b[1m\u001b[0m \r \r"] 30 | [29.200648, "o", "\u001b]2;alex@fabienne-2: ~/go/src/github.com/pulumi-labs/kubespy\u0007\u001b]1;..-labs/kubespy\u0007"] 31 | [29.237811, "o", "\r\u001b[0m\u001b[27m\u001b[24m\u001b[J\u001b[01;31m➜ \u001b[36mkubespy\u001b[00m \u001b[01;34mgit:(\u001b[31mmaster\u001b[34m) \u001b[33m✗\u001b[00m \u001b[K"] 32 | [29.23811, "o", "\u001b[?1h\u001b=\u001b[?2004h"] 33 | [32.872934, "o", "\u001b[?2004l\r\r\n"] 34 | -------------------------------------------------------------------------------- /images/status.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulumi/kubespy/e593ecad4e99f9a68118acd8e9d61b6c05ae4685/images/status.gif -------------------------------------------------------------------------------- /images/trace-deployment/1-deployment-found.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulumi/kubespy/e593ecad4e99f9a68118acd8e9d61b6c05ae4685/images/trace-deployment/1-deployment-found.gif -------------------------------------------------------------------------------- /images/trace-deployment/2-replicaset-created.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulumi/kubespy/e593ecad4e99f9a68118acd8e9d61b6c05ae4685/images/trace-deployment/2-replicaset-created.gif -------------------------------------------------------------------------------- /images/trace-deployment/3-rollout-succeeds.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulumi/kubespy/e593ecad4e99f9a68118acd8e9d61b6c05ae4685/images/trace-deployment/3-rollout-succeeds.gif -------------------------------------------------------------------------------- /images/trace-deployment/pod-killed.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulumi/kubespy/e593ecad4e99f9a68118acd8e9d61b6c05ae4685/images/trace-deployment/pod-killed.gif -------------------------------------------------------------------------------- /images/trace-deployment/pod-relabeled.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulumi/kubespy/e593ecad4e99f9a68118acd8e9d61b6c05ae4685/images/trace-deployment/pod-relabeled.gif -------------------------------------------------------------------------------- /images/trace-deployment/trace-deployment-rollout.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulumi/kubespy/e593ecad4e99f9a68118acd8e9d61b6c05ae4685/images/trace-deployment/trace-deployment-rollout.gif -------------------------------------------------------------------------------- /images/trace-service/1-trace-success-create-svc.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulumi/kubespy/e593ecad4e99f9a68118acd8e9d61b6c05ae4685/images/trace-service/1-trace-success-create-svc.gif -------------------------------------------------------------------------------- /images/trace-service/2-trace-success-pods-ready.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulumi/kubespy/e593ecad4e99f9a68118acd8e9d61b6c05ae4685/images/trace-service/2-trace-success-pods-ready.gif -------------------------------------------------------------------------------- /images/trace-service/3-trace-success-ip-allocated.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulumi/kubespy/e593ecad4e99f9a68118acd8e9d61b6c05ae4685/images/trace-service/3-trace-success-ip-allocated.gif -------------------------------------------------------------------------------- /images/trace-service/become-alive.cast: -------------------------------------------------------------------------------- 1 | {"version": 2, "width": 80, "height": 22, "timestamp": 1537899665, "env": {"SHELL": "/bin/zsh", "TERM": "xterm-256color"}} 2 | [0.373823, "o", "\u001b[1m\u001b[7m%\u001b[27m\u001b[1m\u001b[0m \r \r\u001b]2;alex@fabienne-2: ~/go/src/github.com/pulumi/kubespy\u0007\u001b]1;..ulumi/kubespy\u0007"] 3 | [0.427165, "o", "\r\u001b[0m\u001b[27m\u001b[24m\u001b[J\u001b[01;32m➜ \u001b[36mkubespy\u001b[00m \u001b[01;34mgit:(\u001b[31mhausdorff/trace-svc\u001b[34m) \u001b[33m✗\u001b[00m \u001b[K"] 4 | [0.427411, "o", "\u001b[?1h\u001b=\u001b[?2004h"] 5 | [1.011325, "o", "\r\r\nbck-i-search: _\u001b[K\u001b[A\u001b[24C"] 6 | [1.28044, "o", "\u001b[4mk\u001b[24mubespy trace svc nginx\u001b[1B\u001b[48Dk_\u001b[A\u001b[23C"] 7 | [1.470678, "o", "\u001b[4mk\u001b[4mu\u001b[24m\u001b[1B\u001b[26Du_\u001b[A\u001b[22C"] 8 | [1.547293, "o", "\u001b[4mk\u001b[4mu\u001b[4mb\u001b[24m\u001b[1B\u001b[26Db_\u001b[A\u001b[21C"] 9 | [2.04713, "o", "\u001b[24mk\u001b[24mu\u001b[24mb\u001b[1B\r\u001b[K\u001b[A\u001b[39C"] 10 | [2.047375, "o", "\u001b[?1l\u001b>\u001b[?2004l"] 11 | [2.047644, "o", "\u001b[1B\r"] 12 | [2.048421, "o", "\u001b]2;kubespy trace svc nginx\u0007\u001b]1;kubespy\u0007"] 13 | [3.023292, "o", "\u001b[36;1mWaiting for Service 'nginx'\u001b[0m\r\n"] 14 | [3.023376, "o", "\u001b[2K\u001b[1A[\u001b[32mADDED\u001b[0m \u001b[36;1mv1/Service\u001b[0m] default/nginx\r\n ❌ Waiting for Endpoints object to be created, to direct traffic to Pods\r\n ❌ Waiting for public IP/host to be allocated\r\n\r\n❌ Waiting for live Pods to be targeted by service\r\n"] 15 | [3.02435, "o", "\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A[\u001b[32mADDED\u001b[0m \u001b[36;1mv1/Service\u001b[0m] default/nginx\r\n ✅ Successfully created Endpoints object 'nginx' to direct traffic to Pods\r\n ❌ Waiting for public IP/host to be allocated\r\n\r\n[\u001b[32mADDED\u001b[0m \u001b[36;1mv1/Endpoints\u001b[0m] default/nginx\r\n ❌ Directs traffic to the following live Pods:\r\n - [\u001b[31;1mNot live\u001b[0m] \u001b[36mnginx-6bfdb7578f-lknrz\u001b[0m @ \u001b[33m10.44.6.129\u001b[0m\r\n - [\u001b[31;1mNot live\u001b[0m] \u001b[36mnginx-6bfdb7578f-lnsc8\u001b[0m @ \u001b[33m10.44.4.150\u001b[0m\r\n - [\u001b[31;1mNot live\u001b[0m] \u001b[36mnginx-6bfdb7578f-rcdlf\u001b[0m @ \u001b[33m10.44.3.165\u001b[0m\r\n"] 16 | [4.778468, "o", "\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A"] 17 | [4.778551, "o", "\u001b[2K\u001b[1A[\u001b[32mADDED\u001b[0m \u001b[36;1mv1/Service\u001b[0m] default/nginx\r\n ✅ Successfully created Endpoints object 'nginx' to direct traffic to Pods\r\n ❌ Waiting for public IP/host to be allocated\r\n\r\n[\u001b[32mMODIFIED\u001b[0m \u001b[36;1mv1/Endpoints\u001b[0m] default/nginx\r\n ❌ Directs traffic to the following live Pods:\r\n - [\u001b[31;1mNot live\u001b[0m] \u001b[36mnginx-6bfdb7578f-lknrz\u001b[0m @ \u001b[33m10.44.6.129\u001b[0m\r\n - [\u001b[31;1mNot live\u001b[0m] \u001b[36mnginx-6bfdb7578f-lnsc8\u001b[0m @ \u001b[33m10.44.4.150\u001b[0m\r\n - [\u001b[31;1mNot live\u001b[0m] \u001b[36mnginx-6bfdb7578f-rcdlf\u001b[0m @ \u001b[33m10.44.3.165\u001b[0m\r\n"] 18 | [5.73934, "o", "\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K"] 19 | [5.7395, "o", "\u001b[1A\u001b[2K\u001b[1A[\u001b[32mADDED\u001b[0m \u001b[36;1mv1/Service\u001b[0m] default/nginx\r\n ✅ Successfully created Endpoints object 'nginx' to direct traffic to Pods\r\n ❌ Waiting for public IP/host to be allocated\r\n\r\n[\u001b[32mMODIFIED\u001b[0m \u001b[36;1mv1/Endpoints\u001b[0m] default/nginx\r\n ❌ Directs traffic to the following live Pods:\r\n - [\u001b[32mReady\u001b[0m] \u001b[36mnginx-5f59cc6ddd-qp9v5\u001b[0m @ \u001b[33m10.44.5.139\u001b[0m\r\n - [\u001b[31;1mNot live\u001b[0m] \u001b[36mnginx-6bfdb7578f-lknrz\u001b[0m @ \u001b[33m10.44.6.129\u001b[0m\r\n - [\u001b[31;1mNot live\u001b[0m] \u001b[36mnginx-6bfdb7578f-lnsc8\u001b[0m @ \u001b[33m10.44.4.150\u001b[0m\r\n - [\u001b[31;1mNot live\u001b[0m] \u001b[36mnginx-6bfdb7578f-rcdlf\u001b[0m @ \u001b[33m10.44.3.165\u001b[0m\r\n"] 20 | [5.760231, "o", "\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K"] 21 | [5.760421, "o", "\u001b[1A\u001b[2K\u001b[1A[\u001b[32mADDED\u001b[0m \u001b[36;1mv1/Service\u001b[0m] default/nginx\r\n ✅ Successfully created Endpoints object 'nginx' to direct traffic to Pods\r\n ❌ Waiting for public IP/host to be allocated\r\n\r\n[\u001b[32mMODIFIED\u001b[0m \u001b[36;1mv1/Endpoints\u001b[0m] default/nginx\r\n ❌ Directs traffic to the following live Pods:\r\n - [\u001b[32mReady\u001b[0m] \u001b[36mnginx-5f59cc6ddd-qp9v5\u001b[0m @ \u001b[33m10.44.5.139\u001b[0m\r\n - [\u001b[31;1mNot live\u001b[0m] \u001b[36mnginx-6bfdb7578f-lnsc8\u001b[0m @ \u001b[33m10.44.4.150\u001b[0m\r\n - [\u001b[31;1mNot live\u001b[0m] \u001b[36mnginx-6bfdb7578f-rcdlf\u001b[0m @ \u001b[33m10.44.3.165\u001b[0m\r\n"] 22 | [6.5242, "o", "\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K"] 23 | [6.524611, "o", "\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A[\u001b[32mADDED\u001b[0m \u001b[36;1mv1/Service\u001b[0m] default/nginx\r\n ✅ Successfully created Endpoints object 'nginx' to direct traffic to Pods\r\n ❌ Waiting for public IP/host to be allocated\r\n\r\n[\u001b[32mMODIFIED\u001b[0m \u001b[36;1mv1/Endpoints\u001b[0m] default/nginx\r\n ❌ Directs traffic to the following live Pods:\r\n - [\u001b[32mReady\u001b[0m] \u001b[36mnginx-5f59cc6ddd-qp9v5\u001b[0m @ \u001b[33m10.44.5.139\u001b[0m\r\n - [\u001b[32mReady\u001b[0m] \u001b[36mnginx-5f59cc6ddd-zwljj\u001b[0m @ \u001b[33m10.44.2.220\u001b[0m\r\n - [\u001b[31;1mNot live\u001b[0m] \u001b[36mnginx-6bfdb7578f-lnsc8\u001b[0m @ \u001b[33m10.44.4.150\u001b[0m\r\n - [\u001b[31;1mNot live\u001b[0m] \u001b[36mnginx-6bfdb7578f-rcdlf\u001b[0m @ \u001b[33m10.44.3.165\u001b[0m\r\n"] 24 | [6.559329, "o", "\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K"] 25 | [6.559553, "o", "\u001b[1A\u001b[2K\u001b[1A[\u001b[32mADDED\u001b[0m \u001b[36;1mv1/Service\u001b[0m] default/nginx\r\n ✅ Successfully created Endpoints object 'nginx' to direct traffic to Pods\r\n ❌ Waiting for public IP/host to be allocated\r\n\r\n[\u001b[32mMODIFIED\u001b[0m \u001b[36;1mv1/Endpoints\u001b[0m] default/nginx\r\n ❌ Directs traffic to the following live Pods:\r\n - [\u001b[32mReady\u001b[0m] \u001b[36mnginx-5f59cc6ddd-qp9v5\u001b[0m @ \u001b[33m10.44.5.139\u001b[0m\r\n - [\u001b[32mReady\u001b[0m] \u001b[36mnginx-5f59cc6ddd-zwljj\u001b[0m @ \u001b[33m10.44.2.220\u001b[0m\r\n - [\u001b[31;1mNot live\u001b[0m] \u001b[36mnginx-6bfdb7578f-lnsc8\u001b[0m @ \u001b[33m10.44.4.150\u001b[0m\r\n"] 26 | [7.966526, "o", "\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A"] 27 | [7.966598, "o", "[\u001b[32mADDED\u001b[0m \u001b[36;1mv1/Service\u001b[0m] default/nginx\r\n ✅ Successfully created Endpoints object 'nginx' to direct traffic to Pods\r\n ❌ Waiting for public IP/host to be allocated\r\n\r\n[\u001b[32mMODIFIED\u001b[0m \u001b[36;1mv1/Endpoints\u001b[0m] default/nginx\r\n ❌ Directs traffic to the following live Pods:\r\n - [\u001b[32mReady\u001b[0m] \u001b[36mnginx-5f59cc6ddd-2cwzt\u001b[0m @ \u001b[33m10.44.3.166\u001b[0m\r\n - [\u001b[32mReady\u001b[0m] \u001b[36mnginx-5f59cc6ddd-qp9v5\u001b[0m @ \u001b[33m10.44.5.139\u001b[0m\r\n - [\u001b[32mReady\u001b[0m] \u001b[36mnginx-5f59cc6ddd-zwljj\u001b[0m @ \u001b[33m10.44.2.220\u001b[0m\r\n - [\u001b[31;1mNot live\u001b[0m] \u001b[36mnginx-6bfdb7578f-lnsc8\u001b[0m @ \u001b[33m10.44.4.150\u001b[0m\r\n"] 28 | [7.998138, "o", "\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A"] 29 | [7.99832, "o", "\u001b[2K\u001b[1A\u001b[2K\u001b[1A[\u001b[32mADDED\u001b[0m \u001b[36;1mv1/Service\u001b[0m] default/nginx\r\n ✅ Successfully created Endpoints object 'nginx' to direct traffic to Pods\r\n ❌ Waiting for public IP/host to be allocated\r\n\r\n[\u001b[32mMODIFIED\u001b[0m \u001b[36;1mv1/Endpoints\u001b[0m] default/nginx\r\n ✅ Directs traffic to the following live Pods:\r\n - [\u001b[32mReady\u001b[0m] \u001b[36mnginx-5f59cc6ddd-2cwzt\u001b[0m @ \u001b[33m10.44.3.166\u001b[0m\r\n - [\u001b[32mReady\u001b[0m] \u001b[36mnginx-5f59cc6ddd-qp9v5\u001b[0m @ \u001b[33m10.44.5.139\u001b[0m\r\n - [\u001b[32mReady\u001b[0m] \u001b[36mnginx-5f59cc6ddd-zwljj\u001b[0m @ \u001b[33m10.44.2.220\u001b[0m\r\n"] 30 | [8.734086, "o", "\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A"] 31 | [8.734356, "o", "[\u001b[32mMODIFIED\u001b[0m \u001b[36;1mv1/Service\u001b[0m] default/nginx\r\n ✅ Successfully created Endpoints object 'nginx' to direct traffic to Pods\r\n ✅ Service allocated the following IPs/hostnames:\r\n - \u001b[36m35.239.201.100\u001b[0m\r\n\r\n[\u001b[32mMODIFIED\u001b[0m \u001b[36;1mv1/Endpoints\u001b[0m] default/nginx\r\n ✅ Directs traffic to the following live Pods:\r\n - [\u001b[32mReady\u001b[0m] \u001b[36mnginx-5f59cc6ddd-2cwzt\u001b[0m @ \u001b[33m10.44.3.166\u001b[0m\r\n - [\u001b[32mReady\u001b[0m] \u001b[36mnginx-5f59cc6ddd-qp9v5\u001b[0m @ \u001b[33m10.44.5.139\u001b[0m\r\n - [\u001b[32mReady\u001b[0m] \u001b[36mnginx-5f59cc6ddd-zwljj\u001b[0m @ \u001b[33m10.44.2.220\u001b[0m\r\n"] 32 | [10.632329, "o", "^C"] 33 | [10.635027, "o", "\r\n"] 34 | [10.635084, "o", "\u001b[1m\u001b[7m%\u001b[27m\u001b[1m\u001b[0m \r \r"] 35 | [10.635245, "o", "\u001b]2;alex@fabienne-2: ~/go/src/github.com/pulumi/kubespy\u0007\u001b]1;..ulumi/kubespy\u0007"] 36 | [10.674052, "o", "\r\u001b[0m\u001b[27m\u001b[24m\u001b[J\u001b[01;31m➜ \u001b[36mkubespy\u001b[00m \u001b[01;34mgit:(\u001b[31mhausdorff/trace-svc\u001b[34m) \u001b[33m✗\u001b[00m \u001b[K"] 37 | [10.67411, "o", "\u001b[?1h\u001b="] 38 | [10.674181, "o", "\u001b[?2004h"] 39 | [11.804356, "o", "\u001b[?2004l\r\r\n"] 40 | -------------------------------------------------------------------------------- /images/trace-service/become-alive.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulumi/kubespy/e593ecad4e99f9a68118acd8e9d61b6c05ae4685/images/trace-service/become-alive.gif -------------------------------------------------------------------------------- /images/trace-service/trace-success.cast: -------------------------------------------------------------------------------- 1 | {"version": 2, "width": 80, "height": 22, "timestamp": 1537898587, "env": {"SHELL": "/bin/zsh", "TERM": "xterm-256color"}} 2 | [0.278894, "o", "\u001b[1m\u001b[7m%\u001b[27m\u001b[1m\u001b[0m \r \r\u001b]2;alex@fabienne-2: ~/go/src/github.com/pulumi/kubespy\u0007"] 3 | [0.279883, "o", "\u001b]1;..ulumi/kubespy\u0007"] 4 | [0.322686, "o", "\r\u001b[0m\u001b[27m\u001b[24m\u001b[J\u001b[01;32m➜ \u001b[36mkubespy\u001b[00m \u001b[01;34mgit:(\u001b[31mhausdorff/trace-svc\u001b[34m) \u001b[33m✗\u001b[00m \u001b[K"] 5 | [0.32296, "o", "\u001b[?1h\u001b=\u001b[?2004h"] 6 | [0.885793, "o", "\r\r\nbck-i-search: _\u001b[K\u001b[A\u001b[24C"] 7 | [1.059909, "o", "\u001b[4mk\u001b[24mubespy trace svc nginx\u001b[1B\u001b[48Dk_\u001b[A\u001b[23C"] 8 | [1.273806, "o", "\u001b[4mk\u001b[4mu\u001b[24m\u001b[1B\u001b[26Du_\u001b[A\u001b[22C"] 9 | [1.328081, "o", "\u001b[4mk\u001b[4mu\u001b[4mb\u001b[24m\u001b[1B\u001b[26Db_\u001b[A\u001b[21C"] 10 | [2.131179, "o", "\u001b[24mk\u001b[24mu\u001b[24mb\u001b[1B\r\u001b[K\u001b[A\u001b[39C"] 11 | [2.131241, "o", "\u001b[?1l\u001b>"] 12 | [2.131467, "o", "\u001b[?2004l\u001b[1B\r"] 13 | [2.132223, "o", "\u001b]2;kubespy trace svc nginx\u0007\u001b]1;kubespy\u0007"] 14 | [3.098252, "o", "\u001b[36;1mWaiting for Service 'nginx'\u001b[0m\r\n"] 15 | [3.858426, "o", "\u001b[2K\u001b[1A[\u001b[32mADDED\u001b[0m \u001b[36;1mv1/Service\u001b[0m] default/nginx\r\n ❌ Waiting for Endpoints object to be created, to direct traffic to Pods\r\n ❌ Waiting for public IP/host to be allocated\r\n\r\n❌ Waiting for live Pods to be targeted by service\r\n"] 16 | [3.862946, "o", "\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A[\u001b[32mADDED\u001b[0m \u001b[36;1mv1/Service\u001b[0m] default/nginx\r\n ✅ Successfully created Endpoints object 'nginx' to direct traffic to Pods\r\n ❌ Waiting for public IP/host to be allocated\r\n\r\n[\u001b[32mADDED\u001b[0m \u001b[36;1mv1/Endpoints\u001b[0m] default/nginx\r\n ❌ Does not direct traffic to any Pods\r\n"] 17 | [4.634172, "o", "\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A[\u001b[32mADDED\u001b[0m \u001b[36;1mv1/Service\u001b[0m] default/nginx\r\n ✅ Successfully created Endpoints object 'nginx' to direct traffic to Pods\r\n ❌ Waiting for public IP/host to be allocated\r\n\r\n[\u001b[32mMODIFIED\u001b[0m \u001b[36;1mv1/Endpoints\u001b[0m] default/nginx\r\n ✅ Directs traffic to the following live Pods:\r\n - [\u001b[32mReady\u001b[0m] \u001b[36mnginx-58687b6766-v66vj\u001b[0m @ \u001b[33m10.44.7.148\u001b[0m\r\n"] 18 | [5.246059, "o", "\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A[\u001b[32mADDED\u001b[0m \u001b[36;1mv1/Service\u001b[0m] default/nginx\r\n ✅ Successfully created Endpoints object 'nginx' to direct traffic to Pods\r\n ❌ Waiting for public IP/host to be allocated\r\n\r\n[\u001b[32mMODIFIED\u001b[0m \u001b[36;1mv1/Endpoints\u001b[0m] default/nginx\r\n ✅ Directs traffic to the following live Pods:\r\n - [\u001b[32mReady\u001b[0m] \u001b[36mnginx-58687b6766-fzg2g\u001b[0m @ \u001b[33m10.44.2.219\u001b[0m\r\n - [\u001b[32mReady\u001b[0m] \u001b[36mnginx-58687b6766-v66vj\u001b[0m @ \u001b[33m10.44.7.148\u001b[0m\r\n"] 19 | [5.248011, "o", "\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A[\u001b[32mADDED\u001b[0m \u001b[36;1mv1/Service\u001b[0m] default/nginx\r\n ✅ Successfully created Endpoints object 'nginx' to direct traffic to Pods\r\n ❌ Waiting for public IP/host to be allocated\r\n\r\n[\u001b[32mMODIFIED\u001b[0m \u001b[36;1mv1/Endpoints\u001b[0m] default/nginx\r\n ✅ Directs traffic to the following live Pods:\r\n - [\u001b[32mReady\u001b[0m] \u001b[36mnginx-58687b6766-fzg2g\u001b[0m @ \u001b[33m10.44.2.219\u001b[0m\r\n - [\u001b[32mReady\u001b[0m] \u001b[36mnginx-58687b6766-k9x22\u001b[0m @ \u001b[33m10.44.6.124\u001b[0m\r\n - [\u001b[32mReady\u001b[0m] \u001b[36mnginx-58687b6766-v66vj\u001b[0m @ \u001b[33m10.44.7.148\u001b[0m\r\n"] 20 | [7.273421, "o", "\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A"] 21 | [7.273861, "o", "\u001b[2K\u001b[1A\u001b[2K\u001b[1A\u001b[2K\u001b[1A[\u001b[32mMODIFIED\u001b[0m \u001b[36;1mv1/Service\u001b[0m] default/nginx\r\n ✅ Successfully created Endpoints object 'nginx' to direct traffic to Pods\r\n ✅ Service allocated the following IPs/hostnames:\r\n - \u001b[36m35.188.135.181\u001b[0m\r\n\r\n[\u001b[32mMODIFIED\u001b[0m \u001b[36;1mv1/Endpoints\u001b[0m] default/nginx\r\n ✅ Directs traffic to the following live Pods:\r\n - [\u001b[32mReady\u001b[0m] \u001b[36mnginx-58687b6766-fzg2g\u001b[0m @ \u001b[33m10.44.2.219\u001b[0m\r\n - [\u001b[32mReady\u001b[0m] \u001b[36mnginx-58687b6766-k9x22\u001b[0m @ \u001b[33m10.44.6.124\u001b[0m\r\n - [\u001b[32mReady\u001b[0m] \u001b[36mnginx-58687b6766-v66vj\u001b[0m @ \u001b[33m10.44.7.148\u001b[0m\r\n"] 22 | [9.513146, "o", "^C"] 23 | [9.515921, "o", "\r\n"] 24 | [9.516107, "o", "\u001b[1m\u001b[7m%\u001b[27m\u001b[1m\u001b[0m \r \r"] 25 | [9.516256, "o", "\u001b]2;alex@fabienne-2: ~/go/src/github.com/pulumi/kubespy\u0007\u001b]1;..ulumi/kubespy\u0007"] 26 | [9.552787, "o", "\r\u001b[0m\u001b[27m\u001b[24m\u001b[J\u001b[01;31m➜ \u001b[36mkubespy\u001b[00m \u001b[01;34mgit:(\u001b[31mhausdorff/trace-svc\u001b[34m) \u001b[33m✗\u001b[00m \u001b[K"] 27 | [9.552843, "o", "\u001b[?1h\u001b="] 28 | [9.552996, "o", "\u001b[?2004h"] 29 | [10.125099, "o", "\u001b[?2004l\r\r\n"] 30 | -------------------------------------------------------------------------------- /images/trace-service/trace-success.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulumi/kubespy/e593ecad4e99f9a68118acd8e9d61b6c05ae4685/images/trace-service/trace-success.gif -------------------------------------------------------------------------------- /k8sconfig/k8sconfig.go: -------------------------------------------------------------------------------- 1 | package k8sconfig 2 | 3 | import ( 4 | "os" 5 | 6 | "k8s.io/client-go/tools/clientcmd" 7 | ) 8 | 9 | // New creates a ClientConfig for kubernetes 10 | func New() clientcmd.ClientConfig { 11 | // Use client-go to resolve the final configuration values for the client. Typically these 12 | // values would would reside in the $KUBECONFIG file, but can also be altered in several 13 | // places, including in env variables, client-go default values, and (if we allowed it) CLI 14 | // flags. 15 | overrides := &clientcmd.ConfigOverrides{} 16 | loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() 17 | loadingRules.DefaultClientConfig = &clientcmd.DefaultClientConfig 18 | return clientcmd.NewInteractiveDeferredLoadingClientConfig(loadingRules, overrides, os.Stdin) 19 | } 20 | -------------------------------------------------------------------------------- /k8sobject/k8sobject.go: -------------------------------------------------------------------------------- 1 | package k8sobject 2 | 3 | import ( 4 | "github.com/pulumi/pulumi-kubernetes/provider/v3/pkg/openapi" 5 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 6 | ) 7 | 8 | func OwnedBy(o *unstructured.Unstructured, apiVersion, kind, ownerName interface{}) bool { 9 | ownerReferencesI, _ := openapi.Pluck(o.Object, "metadata", "ownerReferences") 10 | ownerReferences, isSlice := ownerReferencesI.([]interface{}) 11 | if !isSlice { 12 | return false 13 | } 14 | 15 | for _, refI := range ownerReferences { 16 | ref, isMap := refI.(map[string]interface{}) 17 | if !isMap { 18 | continue 19 | } 20 | 21 | apiVersion := ref["apiVersion"] 22 | if ref["kind"] == kind && apiVersion == ref["apiVersion"] && ref["name"] == ownerName { 23 | return true 24 | } 25 | } 26 | 27 | return false 28 | } 29 | 30 | func PodConditions(pod *unstructured.Unstructured) []interface{} { 31 | statusI, _ := openapi.Pluck(pod.Object, "status") 32 | status, isMap := statusI.(map[string]interface{}) 33 | if !isMap { 34 | return []interface{}{} 35 | } 36 | 37 | conditionsI, _ := status["conditions"] 38 | conditions, isSlice := conditionsI.([]interface{}) 39 | if !isSlice { 40 | return []interface{}{} 41 | } 42 | 43 | return conditions 44 | } 45 | 46 | func PodContainerStatuses(pod *unstructured.Unstructured) []interface{} { 47 | statusI, _ := openapi.Pluck(pod.Object, "status") 48 | status, isMap := statusI.(map[string]interface{}) 49 | if !isMap { 50 | return []interface{}{} 51 | } 52 | 53 | containerStatusesI, _ := status["containerStatuses"] 54 | containerStatuses, isSlice := containerStatusesI.([]interface{}) 55 | if !isSlice { 56 | return []interface{}{} 57 | } 58 | 59 | return containerStatuses 60 | } 61 | -------------------------------------------------------------------------------- /kubespy.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/pulumi/kubespy/cmd" 7 | ) 8 | 9 | func init() { 10 | // Turn off timestamp prefix for `log.Fatal*`. 11 | log.SetFlags(log.Flags() &^ (log.Ldate | log.Ltime)) 12 | } 13 | 14 | func main() { 15 | cmd.Execute() 16 | } 17 | -------------------------------------------------------------------------------- /pods/pods.go: -------------------------------------------------------------------------------- 1 | package pods 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/fatih/color" 7 | "github.com/pulumi/pulumi-kubernetes/provider/v3/pkg/openapi" 8 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 9 | ) 10 | 11 | var ( 12 | greenText = color.New(color.FgGreen) 13 | yellowText = color.New(color.FgYellow) 14 | cyanBoldText = color.New(color.FgCyan, color.Bold) 15 | cyanText = color.New(color.FgCyan) 16 | redBoldText = color.New(color.FgRed, color.Bold) 17 | ) 18 | 19 | func GetReady(o *unstructured.Unstructured) []string { 20 | ready := []string{} 21 | subsetsI, _ := openapi.Pluck(o.Object, "subsets") 22 | subsets, isSlice := subsetsI.([]interface{}) 23 | if !isSlice { 24 | return ready 25 | } 26 | 27 | for _, subsetI := range subsets { 28 | subset, isMap := subsetI.(map[string]interface{}) 29 | if !isMap { 30 | continue 31 | } 32 | 33 | addressesI, _ := subset["addresses"] 34 | addresses, isSlice := addressesI.([]interface{}) 35 | if !isSlice { 36 | continue 37 | } 38 | 39 | for _, addressI := range addresses { 40 | address, isMap := addressI.(map[string]interface{}) 41 | if !isMap { 42 | continue 43 | } 44 | 45 | nameI, _ := openapi.Pluck(address, "targetRef", "name") 46 | name, isString := nameI.(string) 47 | if !isString { 48 | continue 49 | } 50 | 51 | ipI, _ := address["ip"] 52 | ip, isString := ipI.(string) 53 | if !isString { 54 | continue 55 | } 56 | ready = append(ready, fmt.Sprintf("[%s] %s @ %s", greenText.Sprint("Ready"), cyanText.Sprint(name), yellowText.Sprint(ip))) 57 | } 58 | } 59 | return ready 60 | } 61 | 62 | func GetUnready(o *unstructured.Unstructured) []string { 63 | ready := []string{} 64 | subsetsI, _ := openapi.Pluck(o.Object, "subsets") 65 | subsets, isSlice := subsetsI.([]interface{}) 66 | if !isSlice { 67 | return ready 68 | } 69 | 70 | for _, subsetI := range subsets { 71 | subset, isMap := subsetI.(map[string]interface{}) 72 | if !isMap { 73 | continue 74 | } 75 | 76 | addressesI, _ := subset["notReadyAddresses"] 77 | addresses, isSlice := addressesI.([]interface{}) 78 | if !isSlice { 79 | continue 80 | } 81 | 82 | for _, addressI := range addresses { 83 | address, isMap := addressI.(map[string]interface{}) 84 | if !isMap { 85 | continue 86 | } 87 | 88 | nameI, _ := openapi.Pluck(address, "targetRef", "name") 89 | name, isString := nameI.(string) 90 | if !isString { 91 | continue 92 | } 93 | 94 | ipI, _ := address["ip"] 95 | ip, isString := ipI.(string) 96 | if !isString { 97 | continue 98 | } 99 | ready = append(ready, fmt.Sprintf("[%s] %s @ %s", redBoldText.Sprint("Not live"), cyanText.Sprint(name), yellowText.Sprint(ip))) 100 | } 101 | } 102 | return ready 103 | } 104 | -------------------------------------------------------------------------------- /print/print.go: -------------------------------------------------------------------------------- 1 | package print 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "sort" 7 | "strconv" 8 | "strings" 9 | 10 | "github.com/fatih/color" 11 | "github.com/mbrlabs/uilive" 12 | "github.com/pulumi/kubespy/k8sobject" 13 | "github.com/pulumi/kubespy/pods" 14 | "github.com/pulumi/pulumi-kubernetes/provider/v3/pkg/openapi" 15 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 16 | k8sWatch "k8s.io/apimachinery/pkg/watch" 17 | ) 18 | 19 | const ( 20 | v1Endpoints = "v1/Endpoints" 21 | v1Service = "v1/Service" 22 | v1Pod = "v1/Pod" 23 | deployment = "Deployment" 24 | v1ReplicaSet = "v1/ReplicaSet" 25 | 26 | prefix = "\n - " 27 | 28 | deploymentRevisionKey = "deployment.kubernetes.io/revision" 29 | 30 | trueStatus = "True" 31 | statusAvailable = "Available" 32 | ) 33 | 34 | var ( 35 | greenText = color.New(color.FgGreen) 36 | faintGreenText = color.New(color.Faint, color.FgGreen) 37 | yellowText = color.New(color.FgYellow) 38 | cyanBoldText = color.New(color.FgCyan, color.Bold) 39 | cyanText = color.New(color.FgCyan) 40 | redBoldText = color.New(color.FgRed, color.Bold) 41 | whiteBoldText = color.New(color.Bold) 42 | yellowBoldText = color.New(color.FgYellow, color.Bold) 43 | faintText = color.New(color.Faint) 44 | ) 45 | 46 | // SuccessStatusEvent prints a message using the formatting of success status. 47 | func SuccessStatusEvent(w io.Writer, fmtstr string, a ...interface{}) { 48 | fmt.Fprintf(w, " ✅ %s\n", fmt.Sprintf(fmtstr, a...)) 49 | } 50 | 51 | // FailureStatusEvent prints a message using the formatting of a failure status. 52 | func FailureStatusEvent(w io.Writer, fmtstr string, a ...interface{}) { 53 | fmt.Fprintf(w, " ❌ %s\n", fmt.Sprintf(fmtstr, a...)) 54 | } 55 | 56 | // PendingStatusEvent prints a message using the formatting of a pending status. 57 | func PendingStatusEvent(w io.Writer, fmtstr string, a ...interface{}) { 58 | fmt.Fprintf(w, " ⌛ %s\n", fmt.Sprintf(fmtstr, a...)) 59 | } 60 | 61 | // ServiceWatchTable prints the status of a Service, as represented in a table. 62 | func ServiceWatchTable(w *uilive.Writer, table map[string][]k8sWatch.Event) { 63 | var svcType string 64 | if events, hasSvc := table[v1Service]; hasSvc { 65 | o := events[0].Object.(*unstructured.Unstructured) 66 | watchEventHeader(w, events[0].Type, o) 67 | 68 | svcTypeI, _ := openapi.Pluck(o.Object, "spec", "type") 69 | var isString bool 70 | svcType, isString = svcTypeI.(string) 71 | if !isString { 72 | svcType = "ClusterIP" 73 | } 74 | 75 | switch svcType { 76 | case "ClusterIP": 77 | if eps, hasEndpoints := table[v1Endpoints]; hasEndpoints && eps[0].Type != k8sWatch.Deleted { 78 | SuccessStatusEvent(w, "Successfully created Endpoints object '%s' to direct traffic to Pods", 79 | cyanText.Sprint(o.GetName())) 80 | } else { 81 | FailureStatusEvent(w, "Waiting for Endpoints object to be created, to direct traffic to Pods") 82 | } 83 | 84 | clusterIPI, _ := openapi.Pluck(o.Object, "spec", "clusterIP") 85 | if clusterIP, isString := clusterIPI.(string); isString && len(clusterIP) > 0 { 86 | SuccessStatusEvent(w, "Successfully allocated a cluster-internal IP: %s", 87 | cyanText.Sprint(o.GetName())) 88 | } else { 89 | FailureStatusEvent(w, "Waiting for cluster-internal IP to be allocated") 90 | } 91 | case "LoadBalancer": 92 | if eps, hasEndpoints := table[v1Endpoints]; hasEndpoints && eps[0].Type != k8sWatch.Deleted { 93 | SuccessStatusEvent(w, "Successfully created Endpoints object '%s' to direct traffic to Pods", o.GetName()) 94 | } else { 95 | FailureStatusEvent(w, "Waiting for Endpoints object to be created, to direct traffic to Pods") 96 | } 97 | 98 | ingressesI, _ := openapi.Pluck(o.Object, "status", "loadBalancer", "ingress") 99 | if ingresses, isMap := ingressesI.([]interface{}); isMap { 100 | ips := []string{} 101 | for _, ingressI := range ingresses { 102 | if ingress, isMap := ingressI.(map[string]interface{}); isMap { 103 | tmp := []string{} 104 | ipI, _ := ingress["ip"] 105 | ip, isString := ipI.(string) 106 | if isString { 107 | tmp = append(tmp, cyanText.Sprint(ip)) 108 | } 109 | 110 | hostnameI, _ := ingress["hostname"] 111 | hostname, isString := hostnameI.(string) 112 | if isString { 113 | tmp = append(tmp, cyanText.Sprint(hostname)) 114 | } 115 | 116 | ips = append(ips, strings.Join(tmp, "/")) 117 | } 118 | } 119 | 120 | if len(ips) > 0 { 121 | sort.Strings(ips) 122 | li := prefix + strings.Join(ips, prefix) 123 | SuccessStatusEvent(w, "Service allocated the following IPs/hostnames:%s", li) 124 | } 125 | } else { 126 | FailureStatusEvent(w, "Waiting for public IP/host to be allocated") 127 | } 128 | 129 | case "ExternalName": 130 | externalNameI, _ := openapi.Pluck(o.Object, "spec", "externalName") 131 | if externalName, isString := externalNameI.(string); isString && len(externalName) > 0 { 132 | SuccessStatusEvent(w, "Service proxying to %s", cyanText.Sprint(externalName)) 133 | } else { 134 | FailureStatusEvent(w, "Service not given a URI to proxy to in `.spec.externalName`") 135 | } 136 | } 137 | } 138 | 139 | fmt.Fprintln(w) 140 | 141 | if events, hasEPs := table[v1Endpoints]; hasEPs { 142 | o := events[0].Object.(*unstructured.Unstructured) 143 | watchEventHeader(w, events[0].Type, o) 144 | 145 | ready := pods.GetReady(o) 146 | sort.Strings(ready) 147 | unready := pods.GetUnready(o) 148 | sort.Strings(unready) 149 | pods := append(ready, unready...) 150 | 151 | if len(unready) > 0 { 152 | li := prefix + strings.Join(pods, prefix) 153 | FailureStatusEvent(w, "Directs traffic to the following live Pods:%s", li) 154 | } else if len(ready) > 0 { 155 | li := prefix + strings.Join(pods, prefix) 156 | SuccessStatusEvent(w, "Directs traffic to the following live Pods:%s", li) 157 | } else if len(unready) == 0 && len(ready) == 0 { 158 | FailureStatusEvent(w, "Does not direct traffic to any Pods") 159 | } 160 | 161 | } else if svcType != "ExternalName" { 162 | fmt.Fprintln(w, "❌ Waiting for live Pods to be targeted by service") 163 | } 164 | 165 | w.Flush() 166 | } 167 | 168 | // DeploymentWatchTable prints the status of a Deployment, as represented in a table. 169 | func DeploymentWatchTable(w *uilive.Writer, table map[string][]k8sWatch.Event) { 170 | const ( 171 | waitingForControllerCreate = "Waiting for controller to create Deployment" 172 | rolloutNotStarted = "Deployment has not begun to roll out the change" 173 | appUnavailable = "Deployment does not have minimum replicas (%d out of %d)" 174 | ) 175 | 176 | var currentRevision int 177 | if events, hasDepl := table[deployment]; hasDepl { 178 | o := events[0].Object.(*unstructured.Unstructured) 179 | var err error 180 | currentRevision, err = parseRevision(o) 181 | if err != nil { 182 | FailureStatusEvent(w, waitingForControllerCreate) 183 | } 184 | } 185 | 186 | // Get current ReplicaSet. 187 | newReplicaSetAvailable := true 188 | var currRepSet *unstructured.Unstructured 189 | var currRepSetEventType k8sWatch.EventType 190 | type repSet struct { 191 | eventType k8sWatch.EventType 192 | revision int 193 | replicas int64 194 | namespace string 195 | name string 196 | } 197 | var prevRepSet *unstructured.Unstructured 198 | var prevRepSetSpec *repSet 199 | if events, hasRs := table[v1ReplicaSet]; hasRs { 200 | for _, e := range events { 201 | rs := e.Object.(*unstructured.Unstructured) 202 | repSetRevision, err := parseRevision(rs) 203 | if err != nil { 204 | continue 205 | } 206 | if currentRevision == repSetRevision { 207 | currRepSet = rs 208 | currRepSetEventType = e.Type 209 | } else if e.Type != k8sWatch.Deleted { 210 | replicasI, _ := openapi.Pluck(rs.Object, "status", "replicas") 211 | replicas, _ := replicasI.(int64) 212 | 213 | revision, err := parseRevision(rs) 214 | if replicas > 0 && err == nil { 215 | prevRepSet = rs 216 | prevRepSetSpec = &repSet{ 217 | eventType: e.Type, 218 | revision: revision, 219 | replicas: replicas, 220 | namespace: rs.GetNamespace(), 221 | name: rs.GetName()} 222 | } 223 | } 224 | } 225 | } 226 | 227 | // Display `Deployment` status. 228 | if events, hasDepl := table[deployment]; hasDepl { 229 | o := events[0].Object.(*unstructured.Unstructured) 230 | watchEventHeader(w, events[0].Type, o) 231 | 232 | specReplicasI, _ := openapi.Pluck(o.Object, "spec", "replicas") 233 | specReplicas, isInt := specReplicasI.(int) 234 | if !isInt { 235 | specReplicas = 1 236 | } 237 | 238 | availableReplicasI, _ := openapi.Pluck(o.Object, "status", "availableReplicas") 239 | availableReplicas, isInt := availableReplicasI.(int) 240 | if !isInt { 241 | specReplicas = 0 242 | } 243 | 244 | // Check Deployments conditions to see whether new ReplicaSet is available. If it is, we are 245 | // successful. 246 | conditionsI, _ := openapi.Pluck(o.Object, "status", "conditions") 247 | conditions, isSlice := conditionsI.([]interface{}) 248 | if !isSlice { 249 | FailureStatusEvent(w, appUnavailable, 0, specReplicas) 250 | FailureStatusEvent(w, rolloutNotStarted) 251 | } else { 252 | whiteBoldText.Fprintf(w, " Rolling out Deployment revision %d\n", currentRevision) 253 | 254 | var isProgressing bool 255 | var progressingReason string 256 | 257 | var deploymentAvailable bool 258 | var availableReason string 259 | 260 | var rolloutSuccessful bool 261 | 262 | // Success occurs when the ReplicaSet of the `currentGeneration` is marked as available, and 263 | // when the deployment is available. 264 | for _, rawCondition := range conditions { 265 | condition, isMap := rawCondition.(map[string]interface{}) 266 | if !isMap { 267 | continue 268 | } 269 | 270 | if condition["type"] == "Progressing" { 271 | isProgressing = condition["status"] == trueStatus 272 | 273 | reasonI, _ := condition["reason"] 274 | reason, isString := reasonI.(string) 275 | if !isString { 276 | continue 277 | } 278 | messageI, _ := condition["message"] 279 | message, isString := messageI.(string) 280 | if !isString { 281 | continue 282 | } 283 | progressingReason = fmt.Sprintf("[%s] %s", reason, message) 284 | 285 | rolloutSuccessful = condition["reason"] == "NewReplicaSetAvailable" && newReplicaSetAvailable 286 | } 287 | 288 | if condition["type"] == statusAvailable { 289 | deploymentAvailable = condition["status"] == trueStatus 290 | reasonI, _ := condition["reason"] 291 | reason, isString := reasonI.(string) 292 | if !isString { 293 | continue 294 | } 295 | messageI, _ := condition["message"] 296 | message, isString := messageI.(string) 297 | if !isString { 298 | continue 299 | } 300 | availableReason = fmt.Sprintf("[%s] %s", reason, message) 301 | } 302 | } 303 | 304 | if !deploymentAvailable { 305 | FailureStatusEvent(w, "Deployment is failing; %d out of %d Pods are available: %s", 306 | availableReplicas, specReplicas, availableReason) 307 | } else { 308 | SuccessStatusEvent(w, "Deployment is currently available") 309 | } 310 | 311 | if !isProgressing { 312 | FailureStatusEvent(w, "Rollout has failed; controller is no longer rolling forward: %s", 313 | progressingReason) 314 | } else if rolloutSuccessful { 315 | SuccessStatusEvent(w, "Rollout successful: new ReplicaSet marked 'available'") 316 | } else { 317 | PendingStatusEvent(w, "Rollout proceeding: %s", progressingReason) 318 | } 319 | } 320 | } 321 | 322 | fmt.Fprintln(w) 323 | 324 | // Display `ReplicaSet` status. 325 | if currRepSet != nil { 326 | cyanBoldText.Fprintln(w, "ROLLOUT STATUS:") 327 | fmt.Fprintf(w, "- [%s | Revision %d] ", yellowBoldText.Sprint("Current rollout"), currentRevision) 328 | 329 | specReplicasI, _ := openapi.Pluck(currRepSet.Object, "spec", "replicas") 330 | specReplicas, isInt := specReplicasI.(int64) 331 | if !isInt { 332 | specReplicas = 1 333 | } 334 | 335 | availableReplicasI, _ := openapi.Pluck(currRepSet.Object, "status", "availableReplicas") 336 | availableReplicas, isInt := availableReplicasI.(int64) 337 | if !isInt { 338 | availableReplicas = 0 339 | } 340 | 341 | currReplicaSetStatus(w, currRepSetEventType, currentRevision, currRepSet) 342 | if availableReplicas < specReplicas { 343 | PendingStatusEvent(w, 344 | "Waiting for ReplicaSet to attain minimum available Pods (%d available of a %d minimum)", 345 | availableReplicas, specReplicas) 346 | } else { 347 | SuccessStatusEvent(w, 348 | "ReplicaSet is available [%d Pods available of a %d minimum]", 349 | availableReplicas, specReplicas) 350 | } 351 | 352 | printPodStatus(w, 353 | func(w io.Writer, f string, a ...interface{}) { fmt.Fprintf(w, f, a...) }, currRepSet, table) 354 | } else { 355 | fmt.Fprintln(w, "⌛ Waiting for Deployment controller to create ReplicaSet") 356 | } 357 | 358 | if prevRepSetSpec != nil { 359 | fmt.Fprintln(w) 360 | 361 | faintText.Fprintf(w, "- [%s", whiteBoldText.Sprint("Previous ReplicaSet")) 362 | faintText.Fprintf(w, " | Revision %d] [%s", prevRepSetSpec.revision, 363 | greenText.Sprint(prevRepSetSpec.eventType)) 364 | faintText.Fprintf(w, "] %s/%s\n", prevRepSetSpec.namespace, prevRepSetSpec.name) 365 | faintText.Fprintf(w, " ⌛ Waiting for ReplicaSet to scale to 0 Pods (%d currently exist)\n", 366 | prevRepSetSpec.replicas) 367 | 368 | printPodStatus(w, faintText.FprintfFunc(), prevRepSet, table) 369 | 370 | } 371 | 372 | w.Flush() 373 | } 374 | 375 | func printPodStatus(w io.Writer, fprintf func(w io.Writer, f string, a ...interface{}), 376 | rs *unstructured.Unstructured, table map[string][]k8sWatch.Event) { 377 | if podEvents, exists := table[v1Pod]; exists { 378 | for _, e := range podEvents { 379 | pod := e.Object.(*unstructured.Unstructured) 380 | if !k8sobject.OwnedBy(pod, rs.GetAPIVersion(), rs.GetKind(), rs.GetName()) { 381 | continue 382 | } 383 | 384 | conditions := k8sobject.PodConditions(pod) 385 | for _, conditionI := range conditions { 386 | condition, isMap := conditionI.(map[string]interface{}) 387 | if !isMap { 388 | continue 389 | } 390 | 391 | if condition["type"] == "PodScheduled" { 392 | if condition["status"] != trueStatus { 393 | reason, message := errorFromCondition(condition) 394 | printPodContainerError(w, fprintf, pod, reason, message) 395 | } 396 | } 397 | 398 | if condition["type"] == "Initialized" { 399 | if condition["status"] != trueStatus { 400 | reason, message := errorFromCondition(condition) 401 | printPodContainerError(w, fprintf, pod, reason, message) 402 | } 403 | } 404 | 405 | if condition["type"] == "Ready" { 406 | if condition["status"] != trueStatus { 407 | reason, message := errorFromCondition(condition) 408 | printPodContainerError(w, fprintf, pod, reason, message) 409 | } else { 410 | fprintf(w, " - [%s", greenText.Sprint("Ready")) 411 | fprintf(w, "] %s\n", cyanText.Sprint(pod.GetName())) 412 | } 413 | } 414 | } 415 | 416 | // Collect the errors from any containers that are failing. 417 | containerStatuses := k8sobject.PodContainerStatuses(pod) 418 | for _, rawContainerStatus := range containerStatuses { 419 | containerStatus, isMap := rawContainerStatus.(map[string]interface{}) 420 | if !isMap || containerStatus["ready"] == true { 421 | continue 422 | } 423 | 424 | // Process container that's waiting. 425 | rawWaiting, isWaiting := openapi.Pluck(containerStatus, "state", "waiting") 426 | waiting, isMap := rawWaiting.(map[string]interface{}) 427 | if isWaiting && rawWaiting != nil && isMap { 428 | reason, message := checkWaitingContainer(waiting) 429 | printPodContainerError(w, fprintf, pod, reason, message) 430 | } 431 | 432 | // Process container that's terminated. 433 | rawTerminated, isTerminated := openapi.Pluck(containerStatus, "state", "terminated") 434 | terminated, isMap := rawTerminated.(map[string]interface{}) 435 | if isTerminated && rawTerminated != nil && isMap { 436 | reason, message := checkTerminatedContainer(terminated) 437 | printPodContainerError(w, fprintf, pod, reason, message) 438 | } 439 | } 440 | 441 | // Exhausted our knowledge of possible error states for Pods. Return. 442 | } 443 | } 444 | } 445 | 446 | func printPodContainerError(w io.Writer, fprintf func(w io.Writer, f string, a ...interface{}), 447 | pod *unstructured.Unstructured, reason, 448 | message string) { 449 | if reason == "" || message == "" { 450 | return 451 | } 452 | fprintf(w, " - [%s", redBoldText.Sprint(reason)) 453 | fprintf(w, "] %s", cyanText.Sprint(pod.GetName())) 454 | fprintf(w, " %s\n", message) 455 | } 456 | 457 | func errorFromCondition(condition map[string]interface{}) (string, string) { 458 | reasonI, _ := condition["reason"] 459 | reason, isString := reasonI.(string) 460 | if !isString { 461 | return "", "" 462 | } 463 | messageI, _ := condition["message"] 464 | message, isString := messageI.(string) 465 | if !isString { 466 | return "", "" 467 | } 468 | 469 | return reason, message 470 | } 471 | 472 | func checkWaitingContainer(waiting map[string]interface{}) (string, string) { 473 | rawReason, hasReason := waiting["reason"] 474 | reason, isString := rawReason.(string) 475 | if !hasReason || !isString || reason == "" || reason == "ContainerCreating" { 476 | return "", "" 477 | } 478 | 479 | rawMessage, hasMessage := waiting["message"] 480 | message, isString := rawMessage.(string) 481 | if !hasMessage || !isString { 482 | return "", "" 483 | } 484 | 485 | // Image pull error has a bunch of useless junk at the beginning of the error message. Try to 486 | // remove it. 487 | imagePullJunk := "rpc error: code = Unknown desc = Error response from daemon: " 488 | message = strings.TrimPrefix(message, imagePullJunk) 489 | 490 | return reason, message 491 | } 492 | 493 | func checkTerminatedContainer(terminated map[string]interface{}) (string, string) { 494 | reasonI, _ := terminated["reason"] 495 | reason, isString := reasonI.(string) 496 | if !isString || reason == "" { 497 | return "", "" 498 | } 499 | 500 | messageI, _ := terminated["message"] 501 | message, isString := messageI.(string) 502 | if !isString { 503 | message = fmt.Sprintf("Container completed with exit code %d", terminated["exitCode"]) 504 | } 505 | 506 | return reason, message 507 | } 508 | 509 | func watchEventHeader(w io.Writer, eventType k8sWatch.EventType, o *unstructured.Unstructured) { 510 | var eventTypeS string 511 | if eventType == k8sWatch.Deleted { 512 | eventTypeS = redBoldText.Sprint(eventType) 513 | } else { 514 | eventTypeS = greenText.Sprint(eventType) 515 | } 516 | apiType := cyanBoldText.Sprintf("%s/%s", o.GetAPIVersion(), o.GetKind()) 517 | fmt.Fprintf(w, "[%s %s] %s/%s\n", eventTypeS, apiType, o.GetNamespace(), o.GetName()) 518 | } 519 | 520 | func currReplicaSetStatus( 521 | w io.Writer, eventType k8sWatch.EventType, revision int, o *unstructured.Unstructured, 522 | ) { 523 | var eventTypeS string 524 | if eventType == k8sWatch.Deleted { 525 | eventTypeS = redBoldText.Sprint(eventType) 526 | } else { 527 | eventTypeS = greenText.Sprint(eventType) 528 | } 529 | fmt.Fprintf(w, "[%s] %s/%s\n", eventTypeS, o.GetNamespace(), o.GetName()) 530 | } 531 | 532 | func parseRevision(o *unstructured.Unstructured) (int, error) { 533 | revisionI, _ := openapi.Pluck(o.Object, "metadata", "annotations", deploymentRevisionKey) 534 | revisionS, _ := revisionI.(string) 535 | return strconv.Atoi(revisionS) 536 | } 537 | -------------------------------------------------------------------------------- /version/version.go: -------------------------------------------------------------------------------- 1 | package version 2 | 3 | // Version is initialized by the Go linker to contain the semver of this build. // defaults to `dev` 4 | var Version string = "dev" 5 | -------------------------------------------------------------------------------- /watch/watch.go: -------------------------------------------------------------------------------- 1 | package watch 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strings" 7 | 8 | "github.com/fatih/color" 9 | "github.com/pulumi/kubespy/k8sconfig" 10 | "github.com/pulumi/kubespy/k8sobject" 11 | "github.com/pulumi/pulumi-kubernetes/provider/v3/pkg/clients" 12 | "k8s.io/apimachinery/pkg/api/meta" 13 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 14 | "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 15 | "k8s.io/apimachinery/pkg/runtime/schema" 16 | "k8s.io/apimachinery/pkg/watch" 17 | "k8s.io/client-go/discovery" 18 | "k8s.io/client-go/dynamic" 19 | "k8s.io/client-go/restmapper" 20 | 21 | // Load auth plugins. Removing this will likely cause compilation error. 22 | _ "k8s.io/client-go/plugin/pkg/client/auth" 23 | ) 24 | 25 | var ( 26 | greenText = color.New(color.FgGreen) 27 | yellowText = color.New(color.FgYellow) 28 | cyanBoldText = color.New(color.FgCyan, color.Bold) 29 | cyanText = color.New(color.FgCyan) 30 | redBoldText = color.New(color.FgRed, color.Bold) 31 | ) 32 | 33 | type watchType string 34 | 35 | const ( 36 | watchByName watchType = "watchByName" 37 | watchByOwner watchType = "watchByOwner" 38 | watchAll watchType = "watchAll" 39 | ) 40 | 41 | // All configures a watch to look for all objects of a type in a namespace. 42 | func All(namespace string) Opts { 43 | return Opts{watchType: watchAll, namespace: namespace} 44 | } 45 | 46 | // ThisObject configures a watch to look for an object specified by a name and a namespace. 47 | func ThisObject(namespace, name string) Opts { 48 | return Opts{watchType: watchByName, namespace: namespace, name: name} 49 | } 50 | 51 | // ObjectsOwnedBy specifies a watch should look for objects owned by `ownerName` in `namespace`. 52 | func ObjectsOwnedBy(namespace, ownerName string) Opts { 53 | return Opts{watchType: watchByOwner, namespace: namespace, ownerName: ownerName} 54 | } 55 | 56 | // Opts specifies which objects to watch for (e.g., "called this" or "owned by x"). 57 | type Opts struct { 58 | watchType watchType 59 | 60 | // (Optional) name of object to watch for. 61 | name string 62 | 63 | // (Optional) namespace in which to watch for objects. 64 | namespace string 65 | 66 | // (Optional) ID of object that owns the object we're watching for (e.g., ReplicaSet owned by 67 | // some Deployment). 68 | ownerName string 69 | } 70 | 71 | func (opts *Opts) Check(o *unstructured.Unstructured) bool { 72 | switch opts.watchType { 73 | case watchByName: 74 | return o.GetName() == opts.name 75 | case watchByOwner: 76 | return k8sobject.OwnedBy(o, "extensions/v1beta1", "Deployment", opts.ownerName) || 77 | k8sobject.OwnedBy(o, "apps/v1beta1", "Deployment", opts.ownerName) || 78 | k8sobject.OwnedBy(o, "apps/v1beta1", "Deployment", opts.ownerName) || 79 | k8sobject.OwnedBy(o, "apps/v1", "Deployment", opts.ownerName) 80 | case watchAll: 81 | return true 82 | default: 83 | panic("Unknown watch type " + opts.watchType) 84 | } 85 | } 86 | 87 | // Forever will watch a resource forever, emitting `watch.Event` until it is killed. 88 | func Forever(apiVersion, kind string, opts Opts) (<-chan watch.Event, error) { 89 | client, mapper, err := makeClient() 90 | if err != nil { 91 | return nil, err 92 | } 93 | 94 | gv, err := schema.ParseGroupVersion(apiVersion) 95 | if err != nil { 96 | return nil, err 97 | } 98 | 99 | mapping, err := mapper.RESTMapping(schema.GroupKind{Group: gv.Group, Kind: strings.Title(kind)}, gv.Version) 100 | if err != nil { 101 | return nil, err 102 | } 103 | 104 | clientForResource := client.Resource(mapping.Resource).Namespace(opts.namespace) 105 | watcher, err := clientForResource.Watch(context.TODO(), metav1.ListOptions{}) 106 | if err != nil { 107 | return nil, err 108 | } 109 | 110 | out := make(chan watch.Event) 111 | go func() { 112 | for { 113 | select { 114 | case e := <-watcher.ResultChan(): 115 | o, isUnst := e.Object.(*unstructured.Unstructured) 116 | if !isUnst { 117 | break 118 | } 119 | if opts.Check(o) { 120 | out <- e 121 | } 122 | } 123 | } 124 | }() 125 | 126 | return out, nil 127 | } 128 | 129 | func makeClient() (dynamic.Interface, meta.RESTMapper, error) { 130 | kubeconfig := k8sconfig.New() 131 | 132 | // Configure the discovery client. 133 | conf, err := kubeconfig.ClientConfig() 134 | if err != nil { 135 | return nil, nil, fmt.Errorf("Unable to read kubectl config: %v", err) 136 | } 137 | 138 | client, err := dynamic.NewForConfig(conf) 139 | if err != nil { 140 | return nil, nil, err 141 | } 142 | 143 | discoveryClient, err := discovery.NewDiscoveryClientForConfig(conf) 144 | if err != nil { 145 | return nil, nil, err 146 | } 147 | 148 | drm := restmapper.NewDeferredDiscoveryRESTMapper(clients.NewMemCacheClient(discoveryClient)) 149 | return client, drm, nil 150 | } 151 | --------------------------------------------------------------------------------