pax_global_header00006660000000000000000000000064147537616100014524gustar00rootroot0000000000000052 comment=6cb934219ac54aa0ddb1d8313adc05304421ccb6 kind-0.27.0/000077500000000000000000000000001475376161000125375ustar00rootroot00000000000000kind-0.27.0/.github/000077500000000000000000000000001475376161000140775ustar00rootroot00000000000000kind-0.27.0/.github/ISSUE_TEMPLATE/000077500000000000000000000000001475376161000162625ustar00rootroot00000000000000kind-0.27.0/.github/ISSUE_TEMPLATE/bug-report.md000066400000000000000000000023601475376161000206730ustar00rootroot00000000000000--- name: Bug Report about: Report a bug encountered using kind labels: kind/bug --- **What happened**: **What you expected to happen**: **How to reproduce it (as _minimally_ and precisely as possible)**: **Anything else we need to know?**: **Environment:** - kind version: (use `kind version`): - Runtime info: (use `docker info`, `podman info` or `nerdctl info`): - OS (e.g. from `/etc/os-release`): - Kubernetes version: (use `kubectl version`): - Any proxies or other special environment settings?: kind-0.27.0/.github/ISSUE_TEMPLATE/cleanup.md000066400000000000000000000004501475376161000202320ustar00rootroot00000000000000--- name: Cleanup about: Pay down technical debt, reduce friction, etc. labels: kind/cleanup --- **What should be cleaned up or changed**: **Why is this needed**: kind-0.27.0/.github/ISSUE_TEMPLATE/documentation.md000066400000000000000000000004021475376161000214510ustar00rootroot00000000000000--- name: Documentation Request about: Suggest what should be documented in kind labels: kind/documentation --- **What would you like to be documented**: **Why is this needed**: kind-0.27.0/.github/ISSUE_TEMPLATE/enhancement.md000066400000000000000000000003501475376161000210670ustar00rootroot00000000000000--- name: Enhancement Request about: Suggest an enhancement to kind labels: kind/feature --- **What would you like to be added**: **Why is this needed**: kind-0.27.0/.github/ISSUE_TEMPLATE/question.md000066400000000000000000000003651475376161000204570ustar00rootroot00000000000000--- name: Question about: Ask a question about using kind labels: kind/support --- kind-0.27.0/.github/actions/000077500000000000000000000000001475376161000155375ustar00rootroot00000000000000kind-0.27.0/.github/actions/setup-env/000077500000000000000000000000001475376161000174655ustar00rootroot00000000000000kind-0.27.0/.github/actions/setup-env/action.yaml000066400000000000000000000017551475376161000216360ustar00rootroot00000000000000name: "Setup environment" description: "Performs common setup operations." runs: using: "composite" steps: - name: Get go version id: golangversion run: | echo "go_version=$(cat .go-version)" >> "$GITHUB_OUTPUT" shell: bash - name: Set up Go id: go uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 with: go-version: ${{ steps.golangversion.outputs.go_version }} check-latest: true - name: Install kind run: sudo make install INSTALL_DIR=/usr/local/bin shell: bash - name: Install kubectl run: | curl -LO https://dl.k8s.io/release/$(curl -sL https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl chmod +x ./kubectl sudo mv ./kubectl /usr/local/bin/kubectl shell: bash - name: Enable ipv4 and ipv6 forwarding run: | sudo sysctl -w net.ipv6.conf.all.forwarding=1 sudo sysctl -w net.ipv4.ip_forward=1 shell: bash kind-0.27.0/.github/dependabot.yml000066400000000000000000000005131475376161000167260ustar00rootroot00000000000000--- version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" labels: - "area/dependency" - "release-note-none" - "ok-to-test" open-pull-requests-limit: 10 groups: actions: update-types: - "minor" - "patch" kind-0.27.0/.github/workflows/000077500000000000000000000000001475376161000161345ustar00rootroot00000000000000kind-0.27.0/.github/workflows/docker.yaml000066400000000000000000000044711475376161000202750ustar00rootroot00000000000000name: Docker on: workflow_dispatch: pull_request: branches: - main paths-ignore: - 'site/**' permissions: contents: read jobs: docker: name: Docker runs-on: ubuntu-20.04 timeout-minutes: 30 strategy: fail-fast: false matrix: ipFamily: [ipv4, ipv6] deployment: [singleNode, multiNode] env: JOB_NAME: "docker-${{ matrix.deployment }}-${{ matrix.ipFamily }}" IP_FAMILY: ${{ matrix.ipFamily }} steps: - name: Check out code into the Go module directory uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - uses: ./.github/actions/setup-env - name: Create single node cluster if: ${{ matrix.deployment == 'singleNode' }} run: | cat <= 1.9.1 is required on Ubuntu 20.04.6 # https://github.com/kubernetes-sigs/kind/issues/3526 curl -Lo ./crun https://github.com/containers/crun/releases/download/1.14.3/crun-1.14.3-linux-amd64 chmod +x ./crun sudo mv ./crun /usr/bin/crun - name: Create single node cluster if: ${{ matrix.deployment == 'singleNode' }} run: | cat <> "$GITHUB_OUTPUT" - name: Set up Go id: go uses: actions/setup-go@f111f3307d8850f501ac008e886eec1fd1932a34 # v5.3.0 with: go-version: ${{ steps.golangversion.outputs.go_version }} check-latest: true - name: "Install QEMU" run: | sudo apt-get update sudo apt-get install -y --no-install-recommends ovmf qemu-system-x86 qemu-utils sudo modprobe kvm # `sudo usermod -aG kvm $(whoami)` does not take an effect on GHA sudo chown $(whoami) /dev/kvm - name: "Install Lima" run: curl -fsSL https://github.com/lima-vm/lima/releases/download/v${LIMA_VERSION}/lima-${LIMA_VERSION}-Linux-x86_64.tar.gz | sudo tar Cxzvf /usr/local - - name: "Cache ~/.cache/lima" uses: actions/cache@v4 with: path: ~/.cache/lima key: lima-${{ env.LIMA_VERSION }} - name: "Start Fedora" # --plain is set to disable file sharing, port forwarding, built-in containerd, etc. run: limactl start --name=default --plain template://fedora - name: "Initialize Fedora" # plain old rsync and ssh are used for the initialization of the guest, # so that people who are not familiar with Lima can understand the initialization steps. run: | set -eux -o pipefail # Initialize SSH mkdir -p -m 0700 ~/.ssh cat ~/.lima/default/ssh.config >> ~/.ssh/config # Sync the current directory to /tmp/kind in the guest rsync -a -e ssh . lima-default:/tmp/kind # Install packages ssh lima-default sudo /tmp/kind/hack/ci/init-fedora.sh # Enable systemd lingering for rootless ssh lima-default sudo loginctl enable-linger "$USER" # Install kind ssh lima-default sudo git config --global --add safe.directory /tmp/kind ssh lima-default sudo make -C /tmp/kind install INSTALL_DIR=/usr/local/bin - name: Set up Rootless Docker if: ${{ matrix.provider == 'docker' && matrix.rootless == 'rootless' }} run: | # Disable the rootful daemon "$HELPER" sudo systemctl disable --now docker # Install the systemd unit "$HELPER" dockerd-rootless-setuptool.sh install # Modify the client config to use the rootless daemon by default "$HELPER" docker context use rootless - name: Set up Rootless Podman if: ${{ matrix.provider == 'podman' && matrix.rootless == 'rootless' }} run: | # Restart the user session to ensure the cgroup delegation # ref: https://github.com/kubernetes-sigs/kind/pull/2754#issuecomment-1124027063 "$HELPER" sudo loginctl terminate-user vagrant || true # We have modprobe ip6_tables in Vagrantfile, but it seems we have to modprobe it once again "$HELPER" sudo modprobe ip6_tables - name: Show provider info run: | "$HELPER" "$KIND_EXPERIMENTAL_PROVIDER" info "$HELPER" "$KIND_EXPERIMENTAL_PROVIDER" version - name: Create a cluster run: | "$HELPER" kind create cluster -v7 --wait 10m --retain - name: Get Cluster status run: | "$HELPER" kubectl wait --for=condition=ready pods --namespace=kube-system -l k8s-app=kube-dns "$HELPER" kubectl get nodes -o wide "$HELPER" kubectl get pods -A - name: Export logs if: always() run: | "$HELPER" kind export logs /tmp/kind/logs mkdir -p /tmp/kind/logs/lima cp -a ~/.lima/default/*.log /tmp/kind/logs/lima || true "$HELPER" tar cC /tmp/kind/logs . | tar xC /tmp/kind/logs - name: Upload logs if: always() uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: kind-logs-${{ env.JOB_NAME }}-${{ github.run_id }} path: /tmp/kind/logs kind-0.27.0/.gitignore000066400000000000000000000003561475376161000145330ustar00rootroot00000000000000# build and test outputs /bin/ /_output/ /_artifacts/ # used for the code generators only /vendor/ # macOS .DS_Store # Vagrant .vagrant # files generated by editors .idea/ *.iml .vscode/ *.swp *.sublime-project *.sublime-workspace *~ kind-0.27.0/.go-version000066400000000000000000000000071475376161000146250ustar00rootroot000000000000001.23.6 kind-0.27.0/CONTRIBUTING.md000066400000000000000000000024441475376161000147740ustar00rootroot00000000000000# Contributing Guidelines Welcome to Kubernetes. We are excited about the prospect of you joining our [community](https://github.com/kubernetes/community)! The Kubernetes community abides by the CNCF [code of conduct](code-of-conduct.md). Here is an excerpt: _As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities._ ## Getting Started We have full documentation on how to get started contributing here: https://kind.sigs.k8s.io/docs/contributing/getting-started/, _please_ read this! A lot of work went into this guide 🙃 ## Mentorship - [Mentoring Initiatives](https://git.k8s.io/community/mentoring) - Kubernetes has a diverse set of mentorship programs available that are always looking for volunteers! kind-0.27.0/LICENSE000066400000000000000000000261351475376161000135530ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright {yyyy} {name of copyright owner} Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. kind-0.27.0/Makefile000066400000000000000000000113401475376161000141760ustar00rootroot00000000000000# Copyright 2019 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Old-skool build tools. # Simple makefile to build kind quickly and reproducibly # # Common uses: # - installing kind: `make install INSTALL_DIR=$HOME/go/bin` # - building: `make build` # - cleaning up and starting over: `make clean` # ################################################################################ # ========================== Capture Environment =============================== # get the repo root and output path REPO_ROOT:=${CURDIR} OUT_DIR=$(REPO_ROOT)/bin # record the source commit in the binary, overridable COMMIT?=$(shell git rev-parse HEAD 2>/dev/null) # count the commits since the last release COMMIT_COUNT?=$(shell git describe --tags | rev | cut -d- -f2 | rev) ################################################################################ # ========================= Setup Go With Gimme ================================ # go version to use for build etc. # setup correct go version with gimme GOTOOLCHAIN:=$(shell . hack/build/gotoolchain.sh && echo "$${GOTOOLCHAIN}") PATH:=$(shell . hack/build/setup-go.sh && echo "$${PATH}") # go1.9+ can autodetect GOROOT, but if some other tool sets it ... GOROOT:= # enable modules GO111MODULE=on # disable CGO by default for static binaries CGO_ENABLED=0 export PATH GOROOT GO111MODULE CGO_ENABLED GOTOOLCHAIN # work around broken PATH export SPACE:=$(subst ,, ) SHELL:=env PATH=$(subst $(SPACE),\$(SPACE),$(PATH)) $(SHELL) ################################################################################ # ============================== OPTIONS ======================================= # install tool INSTALL?=install # install will place binaries here, by default attempts to mimic go install INSTALL_DIR?=$(shell hack/build/goinstalldir.sh) # the output binary name, overridden when cross compiling KIND_BINARY_NAME?=kind # build flags for the kind binary # - reproducible builds: -trimpath and -ldflags=-buildid= # - smaller binaries: -w (trim debugger data, but not panics) # - metadata: -X=... to bake in git commit KIND_VERSION_PKG:=sigs.k8s.io/kind/pkg/cmd/kind/version KIND_BUILD_LD_FLAGS:=-X=$(KIND_VERSION_PKG).gitCommit=$(COMMIT) -X=$(KIND_VERSION_PKG).gitCommitCount=$(COMMIT_COUNT) KIND_BUILD_FLAGS?=-trimpath -ldflags="-buildid= -w $(KIND_BUILD_LD_FLAGS)" ################################################################################ # ================================= Building =================================== # standard "make" target -> builds all: build # builds kind in a container, outputs to $(OUT_DIR) kind: go build -v -o "$(OUT_DIR)/$(KIND_BINARY_NAME)" $(KIND_BUILD_FLAGS) # alias for building kind build: kind # use: make install INSTALL_DIR=/usr/local/bin install: build $(INSTALL) -d $(INSTALL_DIR) $(INSTALL) "$(OUT_DIR)/$(KIND_BINARY_NAME)" "$(INSTALL_DIR)/$(KIND_BINARY_NAME)" ################################################################################ # ================================= Testing ==================================== # unit tests (hermetic) unit: MODE=unit hack/make-rules/test.sh # integration tests integration: MODE=integration hack/make-rules/test.sh # all tests test: hack/make-rules/test.sh ################################################################################ # ================================= Cleanup ==================================== # standard cleanup target clean: rm -rf "$(OUT_DIR)/" ################################################################################ # ============================== Auto-Update =================================== # update generated code, gofmt, etc. update: hack/make-rules/update/all.sh # update generated code generate: hack/make-rules/update/generated.sh # gofmt gofmt: hack/make-rules/update/gofmt.sh ################################################################################ # ================================== Linting =================================== # run linters, ensure generated code, etc. verify: hack/make-rules/verify/all.sh # code linters lint: hack/make-rules/verify/lint.sh # shell linter shellcheck: hack/make-rules/verify/shellcheck.sh ################################################################################# .PHONY: all kind build install unit clean update generate gofmt verify lint shellcheck kind-0.27.0/OWNERS000066400000000000000000000002661475376161000135030ustar00rootroot00000000000000# See the OWNERS docs at https://go.k8s.io/owners reviewers: - aojea - BenTheElder - stmcginnis approvers: - aojea - BenTheElder emeritus_approvers: - amwat - munnerz kind-0.27.0/README.md000066400000000000000000000152261475376161000140240ustar00rootroot00000000000000

kind

# Please see [Our Documentation](https://kind.sigs.k8s.io/docs/user/quick-start/) for more in-depth installation etc. kind is a tool for running local Kubernetes clusters using Docker container "nodes". kind was primarily designed for testing Kubernetes itself, but may be used for local development or CI. If you have [go] 1.16+ and [docker], [podman] or [nerdctl] installed `go install sigs.k8s.io/kind@v0.26.0 && kind create cluster` is all you need! ![](site/static/images/kind-create-cluster.png) kind consists of: - Go [packages][packages] implementing [cluster creation][cluster package], [image build][build package], etc. - A command line interface ([`kind`][kind cli]) built on these packages. - Docker [image(s)][images] written to run systemd, Kubernetes, etc. - [`kubetest`][kubetest] integration also built on these packages (WIP) kind bootstraps each "node" with [kubeadm][kubeadm]. For more details see [the design documentation][design doc]. **NOTE**: kind is still a work in progress, see the [1.0 roadmap]. ## Installation and usage For a complete [install guide] see [the documentation here][install guide]. You can install kind with `go install sigs.k8s.io/kind@v0.26.0`. **NOTE**: please use the latest go to do this. KIND is developed with the latest stable go, see [`.go-version`](./.go-version) for the exact version we're using. This will put `kind` in `$(go env GOPATH)/bin`. If you encounter the error `kind: command not found` after installation then you may need to either add that directory to your `$PATH` as shown [here](https://golang.org/doc/code.html#GOPATH) or do a manual installation by cloning the repo and run `make build` from the repository. Without installing go, kind can be built reproducibly with docker using `make build`. Stable binaries are also available on the [releases] page. Stable releases are generally recommended for CI usage in particular. To install, download the binary for your platform from "Assets" and place this into your `$PATH`: On Linux: ```console # For AMD64 / x86_64 [ $(uname -m) = x86_64 ] && curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.26.0/kind-$(uname)-amd64 # For ARM64 [ $(uname -m) = aarch64 ] && curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.26.0/kind-$(uname)-arm64 chmod +x ./kind sudo mv ./kind /usr/local/bin/kind ``` On macOS via Homebrew: ```console brew install kind ``` On macOS via MacPorts: ```console sudo port selfupdate && sudo port install kind ``` On macOS via Bash: ```console # For Intel Macs [ $(uname -m) = x86_64 ] && curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.26.0/kind-darwin-amd64 # For M1 / ARM Macs [ $(uname -m) = arm64 ] && curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.26.0/kind-darwin-arm64 chmod +x ./kind mv ./kind /some-dir-in-your-PATH/kind ``` On Windows: ```powershell curl.exe -Lo kind-windows-amd64.exe https://kind.sigs.k8s.io/dl/v0.26.0/kind-windows-amd64 Move-Item .\kind-windows-amd64.exe c:\some-dir-in-your-PATH\kind.exe # OR via Chocolatey (https://chocolatey.org/packages/kind) choco install kind ``` To use kind, you will need to [install docker]. Once you have docker running you can create a cluster with: ```console kind create cluster ``` To delete your cluster use: ```console kind delete cluster ``` To create a cluster from Kubernetes source: - ensure that Kubernetes is cloned in `$(go env GOPATH)/src/k8s.io/kubernetes` - build a node image and create a cluster with: ```console kind build node-image kind create cluster --image kindest/node:latest ``` Multi-node clusters and other advanced features may be configured with a config file, for more usage see [the docs][user guide] or run `kind [command] --help` ## Community Please reach out for bugs, feature requests, and other issues! The maintainers of this project are reachable via: - [Kubernetes Slack] in the [#kind] channel - [filing an issue] against this repo - The Kubernetes [SIG-Testing Mailing List] Current maintainers are [@aojea] and [@BenTheElder] - feel free to reach out if you have any questions! Pull Requests are very welcome! If you're planning a new feature, please file an issue to discuss first. Check the [issue tracker] for `help wanted` issues if you're unsure where to start, or feel free to reach out to discuss. 🙂 See also: our own [contributor guide] and the Kubernetes [community page]. ## Why kind? - kind supports multi-node (including HA) clusters - kind supports building Kubernetes release builds from source - support for make / bash or docker, in addition to pre-published builds - kind supports Linux, macOS and Windows - kind is a [CNCF certified conformant Kubernetes installer](https://landscape.cncf.io/?selected=kind) ### Code of conduct Participation in the Kubernetes community is governed by the [Kubernetes Code of Conduct]. [go]: https://golang.org/ [go-supported]: https://golang.org/doc/devel/release.html#policy [docker]: https://www.docker.com/ [podman]: https://podman.io/ [nerdctl]: https://github.com/containerd/nerdctl [community page]: https://kubernetes.io/community/ [Kubernetes Code of Conduct]: code-of-conduct.md [Go Report Card Badge]: https://goreportcard.com/badge/sigs.k8s.io/kind [Go Report Card]: https://goreportcard.com/report/sigs.k8s.io/kind [conformance tests]: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/conformance-tests.md [packages]: ./pkg [cluster package]: ./pkg/cluster [build package]: ./pkg/build [kind cli]: ./main.go [images]: ./images [kubetest]: https://github.com/kubernetes/test-infra/tree/master/kubetest [kubeadm]: https://kubernetes.io/docs/reference/setup-tools/kubeadm/kubeadm/ [design doc]: https://kind.sigs.k8s.io/docs/design/initial [user guide]: https://kind.sigs.k8s.io/docs/user/quick-start [SIG-Testing Mailing List]: https://groups.google.com/forum/#!forum/kubernetes-sig-testing [issue tracker]: https://github.com/kubernetes-sigs/kind/issues [filing an issue]: https://github.com/kubernetes-sigs/kind/issues/new [Kubernetes Slack]: http://slack.k8s.io/ [#kind]: https://kubernetes.slack.com/messages/CEKK1KTN2/ [1.0 roadmap]: https://kind.sigs.k8s.io/docs/contributing/1.0-roadmap [install docker]: https://docs.docker.com/install/ [@BenTheElder]: https://github.com/BenTheElder [@munnerz]: https://github.com/munnerz [@aojea]: https://github.com/aojea [@amwat]: https://github.com/amwat [contributor guide]: https://kind.sigs.k8s.io/docs/contributing/getting-started [releases]: https://github.com/kubernetes-sigs/kind/releases [install guide]: https://kind.sigs.k8s.io/docs/user/quick-start/#installation [modules]: https://github.com/golang/go/wiki/Modules kind-0.27.0/SECURITY_CONTACTS000066400000000000000000000011251475376161000152260ustar00rootroot00000000000000# Defined below are the security contacts for this repo. # # They are the contact point for the Product Security Team to reach out # to for triaging and handling of incoming issues. # # The below names agree to abide by the # [Embargo Policy](https://github.com/kubernetes/sig-release/blob/master/security-release-process-documentation/security-release-process.md#embargo-policy) # and will be removed and replaced if they violate that agreement. # # DO NOT REPORT SECURITY VULNERABILITIES DIRECTLY TO THESE NAMES, FOLLOW THE # INSTRUCTIONS AT https://kubernetes.io/security/ BenTheElder munnerz kind-0.27.0/cmd/000077500000000000000000000000001475376161000133025ustar00rootroot00000000000000kind-0.27.0/cmd/kind/000077500000000000000000000000001475376161000142275ustar00rootroot00000000000000kind-0.27.0/cmd/kind/app/000077500000000000000000000000001475376161000150075ustar00rootroot00000000000000kind-0.27.0/cmd/kind/app/main.go000066400000000000000000000060351475376161000162660ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package app import ( "io" "os" "github.com/spf13/pflag" "sigs.k8s.io/kind/pkg/cmd" "sigs.k8s.io/kind/pkg/cmd/kind" "sigs.k8s.io/kind/pkg/errors" "sigs.k8s.io/kind/pkg/exec" "sigs.k8s.io/kind/pkg/log" ) // Main is the kind main(), it will invoke Run(), if an error is returned // it will then call os.Exit func Main() { if err := Run(cmd.NewLogger(), cmd.StandardIOStreams(), os.Args[1:]); err != nil { os.Exit(1) } } // Run invokes the kind root command, returning the error. // See: sigs.k8s.io/kind/pkg/cmd/kind func Run(logger log.Logger, streams cmd.IOStreams, args []string) error { // NOTE: we handle the quiet flag here so we can fully silence cobra if checkQuiet(args) { // if we are in quiet mode, we want to suppress all status output // only streams.Out should be written to (program output) logger = log.NoopLogger{} streams.ErrOut = io.Discard } // actually run the command c := kind.NewCommand(logger, streams) c.SetArgs(args) if err := c.Execute(); err != nil { logError(logger, err) return err } return nil } // checkQuiet returns true if -q / --quiet was set in args func checkQuiet(args []string) bool { flags := pflag.NewFlagSet("persistent-quiet", pflag.ContinueOnError) flags.ParseErrorsWhitelist.UnknownFlags = true quiet := false flags.BoolVarP( &quiet, "quiet", "q", false, "silence all stderr output", ) // NOTE: pflag will error if -h / --help is specified // We don't care here. That will be handled downstream // It will also call flags.Usage so we're making that no-op flags.Usage = func() {} _ = flags.Parse(args) return quiet } // logError logs the error and the root stacktrace if there is one func logError(logger log.Logger, err error) { colorEnabled := cmd.ColorEnabled(logger) if colorEnabled { logger.Errorf("\x1b[31mERROR\x1b[0m: %v", err) } else { logger.Errorf("ERROR: %v", err) } // Display Output if the error was from running a command ... if err := exec.RunErrorForError(err); err != nil { if colorEnabled { logger.Errorf("\x1b[31mCommand Output\x1b[0m: %s", err.Output) } else { logger.Errorf("\nCommand Output: %s", err.Output) } } // TODO: stacktrace should probably be guarded by a higher level ...? if logger.V(1).Enabled() { // Then display stack trace if any (there should be one...) if trace := errors.StackTrace(err); trace != nil { if colorEnabled { logger.Errorf("\x1b[31mStack Trace\x1b[0m: %+v", trace) } else { logger.Errorf("\nStack Trace: %+v", trace) } } } } kind-0.27.0/cmd/kind/app/main_test.go000066400000000000000000000061201475376161000173200ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package app import ( "testing" "sigs.k8s.io/kind/pkg/cmd" ) func TestCheckQuiet(t *testing.T) { t.Parallel() cases := []struct { Name string Args []string ExpectQuiet bool }{ // normal cases, we expect it to be set { Name: "simply q", Args: []string{"-q"}, ExpectQuiet: true, }, { Name: "simply quiet", Args: []string{"--quiet"}, ExpectQuiet: true, }, { Name: "all quiet on the cli", Args: []string{"all", "quiet", "on", "the", "cli", "--quiet"}, ExpectQuiet: true, }, // pflag will throw an ErrHelp when -h / --help are in args even though // we don't register these as flags, checkQuiet should ignore them { Name: "with ignored help", Args: []string{"--quiet", "--help"}, ExpectQuiet: true, }, { Name: "with ignored h", Args: []string{"--quiet", "-h"}, ExpectQuiet: true, }, // not quiet for these cases ... { Name: "no args", Args: []string{}, ExpectQuiet: false, }, { Name: "loud", Args: []string{"--loud"}, ExpectQuiet: false, }, } for _, tc := range cases { tc := tc t.Run(tc.Name, func(t *testing.T) { t.Parallel() result := checkQuiet(tc.Args) if result != tc.ExpectQuiet { t.Fatalf("fooo") } }) } } func Test_CommandErrReturn(t *testing.T) { t.Parallel() cases := []struct { Name string Command string Subcommand string }{ { Name: "misspelled subcommand for build", Command: "build", Subcommand: "nod-image", }, { Name: "misspelled subcommand for completion", Command: "completion", Subcommand: "zzsh", }, { Name: "misspelled subcommand for create", Command: "create", Subcommand: "clunster", }, { Name: "misspelled subcommand for delete", Command: "delete", Subcommand: "clust", }, { Name: "misspelled subcommand for export", Command: "export", Subcommand: "kubecfg", }, { Name: "misspelled subcommand for get", Command: "get", Subcommand: "nods", }, { Name: "misspelled subcommand for load", Command: "load", Subcommand: "dokker-image", }, } for _, tc := range cases { tc := tc t.Run(tc.Name, func(t *testing.T) { t.Parallel() err := Run(cmd.NewLogger(), cmd.StandardIOStreams(), []string{tc.Command, tc.Subcommand}) if err == nil { t.Errorf("Subcommand should raise an error if not called with correct params") } }) } } kind-0.27.0/cmd/kind/main.go000066400000000000000000000012221475376161000154770ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package main import ( "sigs.k8s.io/kind/cmd/kind/app" ) func main() { app.Main() } kind-0.27.0/code-of-conduct.md000066400000000000000000000002241475376161000160300ustar00rootroot00000000000000# Kubernetes Community Code of Conduct Please refer to our [Kubernetes Community Code of Conduct](https://git.k8s.io/community/code-of-conduct.md) kind-0.27.0/go.mod000066400000000000000000000015601475376161000136470ustar00rootroot00000000000000module sigs.k8s.io/kind // NOTE: This is the go language version, NOT the compiler version. // // This controls the *minimum* required go version and therefore available Go // language features. // // See ./.go-version for the go compiler version used when building binaries // // https://go.dev/doc/modules/gomod-ref#go go 1.17 require ( al.essio.dev/pkg/shellescape v1.5.1 github.com/BurntSushi/toml v1.4.0 github.com/evanphx/json-patch/v5 v5.6.0 github.com/google/safetext v0.0.0-20220905092116-b49f7bc46da2 github.com/mattn/go-isatty v0.0.20 github.com/pelletier/go-toml v1.9.5 github.com/pkg/errors v0.9.1 github.com/spf13/cobra v1.8.0 github.com/spf13/pflag v1.0.5 gopkg.in/yaml.v3 v3.0.1 sigs.k8s.io/yaml v1.4.0 ) require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/kr/text v0.2.0 // indirect golang.org/x/sys v0.6.0 // indirect ) kind-0.27.0/go.sum000066400000000000000000000075361475376161000137050ustar00rootroot00000000000000al.essio.dev/pkg/shellescape v1.5.1 h1:86HrALUujYS/h+GtqoB26SBEdkWfmMI6FubjXlsXyho= al.essio.dev/pkg/shellescape v1.5.1/go.mod h1:6sIqp7X2P6mThCQ7twERpZTuigpr6KbZWtls1U8I890= github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/safetext v0.0.0-20220905092116-b49f7bc46da2 h1:SJ+NtwL6QaZ21U+IrK7d0gGgpjGGvd2kz+FzTHVzdqI= github.com/google/safetext v0.0.0-20220905092116-b49f7bc46da2/go.mod h1:Tv1PlzqC9t8wNnpPdctvtSUOPUUg4SHeE6vR1Ir2hmg= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U= gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= kind-0.27.0/hack/000077500000000000000000000000001475376161000134455ustar00rootroot00000000000000kind-0.27.0/hack/build/000077500000000000000000000000001475376161000145445ustar00rootroot00000000000000kind-0.27.0/hack/build/README.md000066400000000000000000000000611475376161000160200ustar00rootroot00000000000000This directory contains tooling used for buildingkind-0.27.0/hack/build/goinstalldir.sh000077500000000000000000000024001475376161000175720ustar00rootroot00000000000000#!/bin/sh # Copyright 2019 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # this utility prints out the golang install dir, even if go is not installed # IE it prints the directory where `go install ...` would theoretically place # binaries # if we have go, just ask go! if which go >/dev/null 2>&1; then DIR=$(go env GOBIN) if [ -n "${DIR}" ]; then echo "${DIR}" exit 0 fi DIR=$(go env GOPATH) if [ -n "${DIR}" ]; then echo "${DIR}/bin" exit 0 fi fi # mimic go behavior # check if GOBIN is set anyhow if [ -n "${GOBIN}" ]; then echo "${GOBIN}" exit 0 fi # check if GOPATH is set anyhow if [ -n "${GOPATH}" ]; then echo "${GOPATH}/bin" exit 0 fi # finally use default for no $GOPATH or $GOBIN echo "${HOME}/go/bin" kind-0.27.0/hack/build/gotoolchain.sh000077500000000000000000000015101475376161000174060ustar00rootroot00000000000000#!/bin/bash # Copyright 2020 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # script to set GOTOOLCHAIN as needed # MUST BE RUN FROM THE REPO ROOT DIRECTORY # read go-version file unless GO_VERSION is set GO_VERSION="${GO_VERSION:-"$(cat .go-version)"}" GOTOOLCHAIN="go${GO_VERSION}" export GOTOOLCHAIN GO_VERSION kind-0.27.0/hack/build/init-buildx.sh000077500000000000000000000033271475376161000173400ustar00rootroot00000000000000#!/usr/bin/env bash # Copyright 2020 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. set -o errexit -o nounset -o pipefail # TODO: newer buildx releases ship their own qemu copies and don't need any of this # We can skip setup if the current builder already has multi-arch # AND if it isn't the docker driver, which doesn't work current_builder="$(docker buildx inspect)" # linux/amd64, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/arm/v7, linux/arm/v6 if ! grep -q "^Driver: docker$" <<<"${current_builder}" && \ grep -q "linux/amd64" <<<"${current_builder}" && \ grep -q "linux/arm64" <<<"${current_builder}"; then exit 0 fi # Ensure qemu is in binfmt_misc # Docker desktop already has these in versions recent enough to have buildx # We only need to do this setup on linux hosts if [ "$(uname)" == 'Linux' ]; then # NOTE: this is pinned to a digest for a reason! docker run --rm --privileged tonistiigi/binfmt:qemu-v7.0.0-28@sha256:66e11bea77a5ea9d6f0fe79b57cd2b189b5d15b93a2bdb925be22949232e4e55 --install all fi # Ensure we use a builder that can leverage it (the default on linux will not) docker buildx rm kind-builder || true docker buildx create --use --name=kind-builder kind-0.27.0/hack/build/setup-go.sh000077500000000000000000000031411475376161000166450ustar00rootroot00000000000000#!/bin/bash # Copyright 2020 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # script to setup go version with gimme as needed # MUST BE RUN FROM THE REPO ROOT DIRECTORY # read go-version file unless GO_VERSION is set # override GOTOOLCHAIN unless set as well . ./hack/build/gotoolchain.sh # we don't actually care where the .env files are # however, GIMME_SILENT_ENV doesn't trigger re-generating a .env if it # already exists and isn't "silent" (no `go version` command in it) # so we fix that by changing where the .env is written, ensuring ours # is generated from this repo and silent. export GIMME_ENV_PREFIX=./bin/.gimme/ export GIMME_SILENT_ENV=y # only setup go if we haven't set FORCE_HOST_GO, or `go version` doesn't match # go version output looks like: # go version go1.14.5 darwin/amd64 if ! ([ -n "${FORCE_HOST_GO:-}" ] || \ (command -v go >/dev/null && [ "$(go version | cut -d' ' -f3)" = "go${GO_VERSION}" ])); then # eval because the output of this is shell to set PATH etc. eval "$(hack/third_party/gimme/gimme "${GO_VERSION}")" fi # force go modules export GO111MODULE=on kind-0.27.0/hack/ci/000077500000000000000000000000001475376161000140405ustar00rootroot00000000000000kind-0.27.0/hack/ci/README.md000066400000000000000000000002411475376161000153140ustar00rootroot00000000000000This directory contains glue used to test the kind repo in CI. We don't recommend reusing anything from these scripts, and intend to replace them at some point.kind-0.27.0/hack/ci/build-all.sh000077500000000000000000000015701475376161000162470ustar00rootroot00000000000000#!/usr/bin/env bash # Copyright 2018 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # simple CI script to verify kind's own sources # TODO(bentheelder): rename / refactor. consider building kindnetd set -o errexit -o nounset -o pipefail # cd to the repo root REPO_ROOT=$(git rev-parse --show-toplevel) cd "${REPO_ROOT}" # build kind hack/release/build/cross.sh kind-0.27.0/hack/ci/cache-wrapper.sh000077500000000000000000000041401475376161000171170ustar00rootroot00000000000000#!/bin/bash # Copyright 2020 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # USAGE: cache-wrapper.sh hack/go_container.sh some-go-command # $@ will be executed with this script wrapping cache upload / download set -o errexit -o nounset -o pipefail # options for where the cache is stored BUCKET="${BUCKET:-bentheelder-kind-ci-builds}" BRANCH="${BRANCH:-main}" CACHE_SUFFIX="${CACHE_SUFFIX:-"ci-cache/${BRANCH}/gocache.tar"}" CACHE_URL="https://storage.googleapis.com/${BUCKET}/${CACHE_SUFFIX}" CACHE_GS="gs://${BUCKET}/${CACHE_SUFFIX}" # cd to the repo root REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd -P)" cd "${REPO_ROOT}" # default to downloading cache if [ "${DOWNLOAD_CACHE:-true}" = "true" ]; then # NOTE: # - We clean the modcache because we won't be able to write to it if it exists # https://github.com/golang/go/issues/31481 # - All of the relevant go system directories are under /go in KIND's build # - See below for how the cache tarball is created hack/go_container.sh sh -c "go clean -modcache && curl -sSL ${CACHE_URL} | tar -C /go -zxf - --overwrite" fi # run the supplied command and store the exit code for later set +o errexit "$@" res=$? set -o errexit # default to not uploading cache if [ "${UPLOAD_CACHE:-false}" = "true" ]; then # We want to cache: # - XDG_CACHE_HOME / the go build cache, this is /go/cache in KIND's build # - The module cache, ~= $GOPATH/pkg/mod. this /go/pkg/mod in KIND's build hack/go_container.sh sh -c 'tar -C /go -czf - ./cache ./pkg/mod' | gsutil cp - "${CACHE_GS}" fi # preserve the exit code from our real task exit $res kind-0.27.0/hack/ci/e2e-k8s.sh000077500000000000000000000245251475376161000155650ustar00rootroot00000000000000#!/bin/sh # Copyright 2018 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # hack script for running a kind e2e # must be run with a kubernetes checkout in $PWD (IE from the checkout) # Usage: SKIP="ginkgo skip regex" FOCUS="ginkgo focus regex" kind-e2e.sh set -o errexit -o nounset -o xtrace # Settings: # SKIP: ginkgo skip regex # FOCUS: ginkgo focus regex # LABEL_FILTER: ginkgo label query for selecting tests (see "Spec Labels" in https://onsi.github.io/ginkgo/#filtering-specs) # # The default is to focus on conformance tests. Serial tests get skipped when # parallel testing is enabled. Using LABEL_FILTER instead of combining SKIP and # FOCUS is recommended (more expressive, easier to read than regexp). # # GA_ONLY: true - limit to GA APIs/features as much as possible # false - (default) APIs and features left at defaults # FEATURE_GATES: # JSON or YAML encoding of a string/bool map: {"FeatureGateA": true, "FeatureGateB": false} # Enables or disables feature gates in the entire cluster. # Cannot be used when GA_ONLY=true. # RUNTIME_CONFIG: # JSON or YAML encoding of a string/string (!) map: {"apia.example.com/v1alpha1": "true", "apib.example.com/v1beta1": "false"} # Enables API groups in the apiserver via --runtime-config. # Cannot be used when GA_ONLY=true. # cleanup logic for cleanup on exit CLEANED_UP=false cleanup() { if [ "$CLEANED_UP" = "true" ]; then return fi # KIND_CREATE_ATTEMPTED is true once we: kind create if [ "${KIND_CREATE_ATTEMPTED:-}" = true ]; then kind "export" logs "${ARTIFACTS}" || true kind delete cluster || true fi rm -f _output/bin/e2e.test || true # remove our tempdir, this needs to be last, or it will prevent kind delete if [ -n "${TMP_DIR:-}" ]; then rm -rf "${TMP_DIR:?}" fi CLEANED_UP=true } # setup signal handlers # shellcheck disable=SC2317 # this is not unreachable code signal_handler() { if [ -n "${GINKGO_PID:-}" ]; then kill -TERM "$GINKGO_PID" || true fi cleanup } trap signal_handler INT TERM # build kubernetes / node image, e2e binaries build() { # build the node image w/ kubernetes kind build node-image -v 1 # Ginkgo v1 is used by Kubernetes 1.24 and earlier, fallback if v2 is not available. GINKGO_SRC_DIR="vendor/github.com/onsi/ginkgo/v2/ginkgo" if [ ! -d "$GINKGO_SRC_DIR" ]; then GINKGO_SRC_DIR="vendor/github.com/onsi/ginkgo/ginkgo" fi # make sure we have e2e requirements make all WHAT="cmd/kubectl test/e2e/e2e.test ${GINKGO_SRC_DIR}" # Ensure the built kubectl is used instead of system export PATH="${PWD}/_output/bin:$PATH" } check_structured_log_support() { case "${KUBE_VERSION}" in v1.1[0-8].*) echo "$1 is only supported on versions >= v1.19, got ${KUBE_VERSION}" exit 1 ;; esac } # up a cluster with kind create_cluster() { # Grab the version of the cluster we're about to start KUBE_VERSION="$(docker run --rm --entrypoint=cat "kindest/node:latest" /kind/version)" # Default Log level for all components in test clusters KIND_CLUSTER_LOG_LEVEL=${KIND_CLUSTER_LOG_LEVEL:-4} # potentially enable --logging-format CLUSTER_LOG_FORMAT=${CLUSTER_LOG_FORMAT:-} scheduler_extra_args=" \"v\": \"${KIND_CLUSTER_LOG_LEVEL}\"" controllerManager_extra_args=" \"v\": \"${KIND_CLUSTER_LOG_LEVEL}\"" apiServer_extra_args=" \"v\": \"${KIND_CLUSTER_LOG_LEVEL}\"" if [ -n "$CLUSTER_LOG_FORMAT" ]; then check_structured_log_support "CLUSTER_LOG_FORMAT" scheduler_extra_args="${scheduler_extra_args} \"logging-format\": \"${CLUSTER_LOG_FORMAT}\"" controllerManager_extra_args="${controllerManager_extra_args} \"logging-format\": \"${CLUSTER_LOG_FORMAT}\"" apiServer_extra_args="${apiServer_extra_args} \"logging-format\": \"${CLUSTER_LOG_FORMAT}\"" fi kubelet_extra_args=" \"v\": \"${KIND_CLUSTER_LOG_LEVEL}\" \"container-log-max-files\": \"10\" \"container-log-max-size\": \"100Mi\"" KUBELET_LOG_FORMAT=${KUBELET_LOG_FORMAT:-$CLUSTER_LOG_FORMAT} if [ -n "$KUBELET_LOG_FORMAT" ]; then check_structured_log_support "KUBECTL_LOG_FORMAT" kubelet_extra_args="${kubelet_extra_args} \"logging-format\": \"${KUBELET_LOG_FORMAT}\"" fi # JSON or YAML map injected into featureGates config feature_gates="${FEATURE_GATES:-{\}}" # --runtime-config argument value passed to the API server, again as a map runtime_config="${RUNTIME_CONFIG:-{\}}" case "${GA_ONLY:-false}" in false) : ;; true) if [ "${feature_gates}" != "{}" ]; then echo "GA_ONLY=true and FEATURE_GATES=${feature_gates} are mutually exclusive." exit 1 fi if [ "${runtime_config}" != "{}" ]; then echo "GA_ONLY=true and RUNTIME_CONFIG=${runtime_config} are mutually exclusive." exit 1 fi echo "Limiting to GA APIs and features for ${KUBE_VERSION}" feature_gates='{"AllAlpha":false,"AllBeta":false}' runtime_config='{"api/alpha":"false", "api/beta":"false"}' ;; *) echo "\$GA_ONLY set to '${GA_ONLY}'; supported values are true and false (default)" exit 1 ;; esac # create the config file cat < "${ARTIFACTS}/kind-config.yaml" # config for 1 control plane node and 2 workers (necessary for conformance) kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 networking: ipFamily: ${IP_FAMILY:-ipv4} kubeProxyMode: ${KUBE_PROXY_MODE:-iptables} # don't pass through host search paths # TODO: possibly a reasonable default in the future for kind ... dnsSearch: [] nodes: - role: control-plane - role: worker - role: worker featureGates: ${feature_gates} runtimeConfig: ${runtime_config} kubeadmConfigPatches: - | kind: ClusterConfiguration metadata: name: config apiServer: extraArgs: ${apiServer_extra_args} controllerManager: extraArgs: ${controllerManager_extra_args} scheduler: extraArgs: ${scheduler_extra_args} --- kind: InitConfiguration nodeRegistration: kubeletExtraArgs: ${kubelet_extra_args} --- kind: JoinConfiguration nodeRegistration: kubeletExtraArgs: ${kubelet_extra_args} EOF # NOTE: must match the number of workers above NUM_NODES=2 # actually create the cluster # TODO(BenTheElder): settle on verbosity for this script KIND_CREATE_ATTEMPTED=true kind create cluster \ --image=kindest/node:latest \ --retain \ --wait=1m \ -v=3 \ "--config=${ARTIFACTS}/kind-config.yaml" # debug cluster version kubectl version # Patch kube-proxy to set the verbosity level kubectl patch -n kube-system daemonset/kube-proxy \ --type='json' -p='[{"op": "add", "path": "/spec/template/spec/containers/0/command/-", "value": "--v='"${KIND_CLUSTER_LOG_LEVEL}"'" }]' } # run e2es with ginkgo-e2e.sh run_tests() { # IPv6 clusters need some CoreDNS changes in order to work in k8s CI: # 1. k8s CI doesn´t offer IPv6 connectivity, so CoreDNS should be configured # to work in an offline environment: # https://github.com/coredns/coredns/issues/2494#issuecomment-457215452 # 2. k8s CI adds following domains to resolv.conf search field: # c.k8s-prow-builds.internal google.internal. # CoreDNS should handle those domains and answer with NXDOMAIN instead of SERVFAIL # otherwise pods stops trying to resolve the domain. if [ "${IP_FAMILY:-ipv4}" = "ipv6" ]; then # Get the current config original_coredns=$(kubectl get -oyaml -n=kube-system configmap/coredns) echo "Original CoreDNS config:" echo "${original_coredns}" # Patch it fixed_coredns=$( printf '%s' "${original_coredns}" | sed \ -e 's/^.*kubernetes cluster\.local/& internal/' \ -e '/^.*upstream$/d' \ -e '/^.*fallthrough.*$/d' \ -e '/^.*forward . \/etc\/resolv.conf$/d' \ -e '/^.*loop$/d' \ ) echo "Patched CoreDNS config:" echo "${fixed_coredns}" printf '%s' "${fixed_coredns}" | kubectl apply -f - fi # ginkgo regexes and label filter SKIP="${SKIP:-}" FOCUS="${FOCUS:-}" LABEL_FILTER="${LABEL_FILTER:-}" if [ -z "${FOCUS}" ] && [ -z "${LABEL_FILTER}" ]; then FOCUS="\\[Conformance\\]" fi # if we set PARALLEL=true, skip serial tests set --ginkgo-parallel if [ "${PARALLEL:-false}" = "true" ]; then export GINKGO_PARALLEL=y if [ -z "${SKIP}" ]; then SKIP="\\[Serial\\]" else SKIP="\\[Serial\\]|${SKIP}" fi fi # setting this env prevents ginkgo e2e from trying to run provider setup export KUBERNETES_CONFORMANCE_TEST='y' # setting these is required to make RuntimeClass tests work ... :/ export KUBE_CONTAINER_RUNTIME=remote export KUBE_CONTAINER_RUNTIME_ENDPOINT=unix:///run/containerd/containerd.sock export KUBE_CONTAINER_RUNTIME_NAME=containerd # ginkgo can take forever to exit, so we run it in the background and save the # PID, bash will not run traps while waiting on a process, but it will while # running a builtin like `wait`, saving the PID also allows us to forward the # interrupt ./hack/ginkgo-e2e.sh \ '--provider=skeleton' "--num-nodes=${NUM_NODES}" \ "--ginkgo.focus=${FOCUS}" "--ginkgo.skip=${SKIP}" "--ginkgo.label-filter=${LABEL_FILTER}" \ "--report-dir=${ARTIFACTS}" '--disable-log-dump=true' & GINKGO_PID=$! wait "$GINKGO_PID" } main() { # create temp dir and setup cleanup TMP_DIR=$(mktemp -d) # ensure artifacts (results) directory exists when not in CI export ARTIFACTS="${ARTIFACTS:-${PWD}/_artifacts}" mkdir -p "${ARTIFACTS}" # export the KUBECONFIG to a unique path for testing KUBECONFIG="${HOME}/.kube/kind-test-config" export KUBECONFIG echo "exported KUBECONFIG=${KUBECONFIG}" # debug kind version kind version # build kubernetes build # in CI attempt to release some memory after building if [ -n "${KUBETEST_IN_DOCKER:-}" ]; then sync || true echo 1 > /proc/sys/vm/drop_caches || true fi # create the cluster and run tests res=0 create_cluster || res=$? run_tests || res=$? cleanup || res=$? exit $res } main kind-0.27.0/hack/ci/e2e.sh000077500000000000000000000027411475376161000150560ustar00rootroot00000000000000#!/usr/bin/env bash # Copyright 2018 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # hack script for running a kind e2e # must be run with a kubernetes checkout in $PWD (IE from the checkout) # Usage: SKIP="ginkgo skip regex" FOCUS="ginkgo focus regex" kind-e2e.sh set -o errexit -o nounset -o pipefail -o xtrace REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd -P)" # our exit handler (trap) cleanup() { # remove our tempdir, this needs to be last, or it will prevent kind delete [[ -n "${TMP_DIR:-}" ]] && rm -rf "${TMP_DIR:?}" } # install kind to a tempdir GOPATH from this script's kind checkout install_kind() { mkdir -p "${TMP_DIR}/bin" make -C "${REPO_ROOT}" install INSTALL_PATH="${TMP_DIR}/bin" export PATH="${TMP_DIR}/bin:${PATH}" } main() { # create temp dir and setup cleanup TMP_DIR=$(mktemp -d) trap cleanup INT TERM EXIT # install kind install_kind # build kubernetes / e2e test "${REPO_ROOT}/hack/ci/e2e-k8s.sh" } main kind-0.27.0/hack/ci/init-fedora.sh000077500000000000000000000012521475376161000166000ustar00rootroot00000000000000#!/bin/bash set -eux -o pipefail # Ensure network-related modules to be loaded modprobe tap ip_tables iptable_nat ip6_tables ip6table_nat # The moby-engine package included in Fedora lacks support for rootless, # So we need to install docker-ce and docker-ce-rootless-extras from the upstream. curl -fsSL https://get.docker.com | sh dnf install -y golang-go make kubernetes-client podman docker-ce-rootless-extras systemctl enable --now docker # Configuration for rootless: https://kind.sigs.k8s.io/docs/user/rootless/ mkdir -p "/etc/systemd/system/user@.service.d" cat <"/etc/systemd/system/user@.service.d/delegate.conf" [Service] Delegate=yes EOF systemctl daemon-reload kind-0.27.0/hack/ci/lima-helper.sh000077500000000000000000000017021475376161000165760ustar00rootroot00000000000000#!/usr/bin/env bash # Copyright 2021 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. set -o errexit -o nounset -o pipefail : "${LIMA_INSTANCE:=default}" : "${KIND_EXPERIMENTAL_PROVIDER:=docker}" if [ "$ROOTLESS" = "rootless" ]; then exec ssh "lima-${LIMA_INSTANCE}" KIND_EXPERIMENTAL_PROVIDER="$KIND_EXPERIMENTAL_PROVIDER" "${@}" fi exec ssh "lima-${LIMA_INSTANCE}" sudo KIND_EXPERIMENTAL_PROVIDER="$KIND_EXPERIMENTAL_PROVIDER" "${@}" kind-0.27.0/hack/ci/push-latest-cli/000077500000000000000000000000001475376161000170565ustar00rootroot00000000000000kind-0.27.0/hack/ci/push-latest-cli/README.md000066400000000000000000000004301475376161000203320ustar00rootroot00000000000000This tooling is used for our automated builds. These are meant to be consumed *only* by The Kubernetes Project's CI for testing Kubernetes. These builds are currently not supported for other purposes. See github.com/kubernetes/test-infra for the automation that invokes these.kind-0.27.0/hack/ci/push-latest-cli/cloudbuild.yaml000066400000000000000000000004771475376161000221000ustar00rootroot00000000000000# See https://cloud.google.com/cloud-build/docs/build-config options: substitution_option: ALLOW_LOOSE steps: - name: gcr.io/k8s-testimages/krte:latest-master env: - PULL_BASE_SHA=$_PULL_BASE_SHA entrypoint: hack/ci/push-latest-cli/push-latest-cli.sh substitutions: _GIT_TAG: '12345' _PULL_BASE_SHA: 'oops' kind-0.27.0/hack/ci/push-latest-cli/push-latest-cli.sh000077500000000000000000000037141475376161000224400ustar00rootroot00000000000000#!/usr/bin/env bash # Copyright 2020 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. set -o errexit -o nounset -o pipefail set -x; # cd to the repo root REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." &> /dev/null && pwd -P)" cd "${REPO_ROOT}" # pass through git details from prow / image builder if [ -n "${PULL_BASE_SHA:-}" ]; then export COMMIT="${PULL_BASE_SHA:?}" else COMMIT="$(git rev-parse HEAD 2>/dev/null)" export COMMIT fi # short commit is currently 8 characters SHORT_COMMIT="${COMMIT:0:8}" # we upload here BUCKET="${BUCKET:-k8s-staging-kind}" # under each of these VERSIONS=( latest "${SHORT_COMMIT}" ) # build for all platforms hack/release/build/cross.sh # upload to the bucket for f in bin/kind-*; do # make a tarball with this # TODO: eliminate e2e-k8s.sh base="$(basename "$f")" platform="${base#kind-}" tar \ -czvf "bin/${platform}.tgz" \ --transform 's#.*kind.*#kind#' \ --transform 's#.*e2e-k8s.sh#e2e-k8s.sh#' \ --transform='s#^/#./#' \ --mode='755' \ "${f}" \ "hack/ci/e2e-k8s.sh" # copy everything up to each version for version in "${VERSIONS[@]}"; do gsutil cp -P "bin/${platform}.tgz" "gs://${BUCKET}/${version}/${platform}.tgz" gsutil cp -P "$f" "gs://${BUCKET}/${version}/${base}" done done # upload the e2e script so kubernetes CI can consume it for version in "${VERSIONS[@]}"; do gsutil cp -P hack/ci/e2e-k8s.sh "gs://${BUCKET}/${version}/e2e-k8s.sh" done kind-0.27.0/hack/make-rules/000077500000000000000000000000001475376161000155125ustar00rootroot00000000000000kind-0.27.0/hack/make-rules/test.sh000077500000000000000000000041001475376161000170230ustar00rootroot00000000000000#!/usr/bin/env bash # Copyright 2019 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # script to run unit / integration tests, with coverage enabled and junit xml output set -o errexit -o nounset -o pipefail # cd to the repo root and setup go REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd -P)" cd "${REPO_ROOT}" source hack/build/setup-go.sh # set to 'unit' or 'integration' to run a subset MODE="${MODE:-all}" # build gotestsum cd 'hack/tools' go build -o "${REPO_ROOT}/bin/gotestsum" gotest.tools/gotestsum cd "${REPO_ROOT}" go_test_opts=( "-coverprofile=${REPO_ROOT}/bin/${MODE}.cov" '-covermode' 'count' '-coverpkg' 'sigs.k8s.io/kind/...' ) if [[ "${MODE}" = 'unit' ]]; then go_test_opts+=('-short' '-tags=nointegration') elif [[ "${MODE}" = 'integration' ]]; then go_test_opts+=('-run' '^TestIntegration') fi # run unit tests with coverage enabled and junit output ( set -x; "${REPO_ROOT}/bin/gotestsum" --junitfile="${REPO_ROOT}/bin/${MODE}-junit.xml" \ -- "${go_test_opts[@]}" './...' ) # filter out generated files sed '/zz_generated/d' "${REPO_ROOT}/bin/${MODE}.cov" > "${REPO_ROOT}/bin/${MODE}-filtered.cov" # generate cover html go tool cover -html="${REPO_ROOT}/bin/${MODE}-filtered.cov" -o "${REPO_ROOT}/bin/${MODE}-filtered.html" # if we are in CI, copy to the artifact upload location if [[ -n "${ARTIFACTS:-}" ]]; then cp "bin/${MODE}-junit.xml" "${ARTIFACTS:?}/junit.xml" cp "${REPO_ROOT}/bin/${MODE}-filtered.cov" "${ARTIFACTS:?}/filtered.cov" cp "${REPO_ROOT}/bin/${MODE}-filtered.html" "${ARTIFACTS:?}/filtered.html" fi kind-0.27.0/hack/make-rules/update/000077500000000000000000000000001475376161000167745ustar00rootroot00000000000000kind-0.27.0/hack/make-rules/update/README.md000066400000000000000000000001201475376161000202440ustar00rootroot00000000000000This directory contains tools used to update dependencies, generated files, etc.kind-0.27.0/hack/make-rules/update/all.sh000077500000000000000000000016271475376161000201110ustar00rootroot00000000000000#!/usr/bin/env bash # Copyright 2018 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # script to run all update scripts (except deps) set -o errexit -o nounset -o pipefail # cd to the repo root REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." &> /dev/null && pwd -P)" cd "${REPO_ROOT}" hack/make-rules/update/deps.sh hack/make-rules/update/generated.sh hack/make-rules/update/gofmt.sh kind-0.27.0/hack/make-rules/update/deps.sh000077500000000000000000000020771475376161000202740ustar00rootroot00000000000000#!/usr/bin/env bash # Copyright 2018 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Runs go mod tidy, go mod vendor, and then prunes vendor # # Usage: # deps.sh set -o errexit -o nounset -o pipefail # cd to the repo root and setup go REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." &> /dev/null && pwd -P)" cd "${REPO_ROOT}" source hack/build/setup-go.sh # tidy all modules go mod tidy cd "${REPO_ROOT}/hack/tools" go mod tidy # NOTE: kindnetd is only built for linux and uses linux APIs cd "${REPO_ROOT}/images/kindnetd" GOOS=linux go mod tidy kind-0.27.0/hack/make-rules/update/generated.sh000077500000000000000000000031021475376161000212650ustar00rootroot00000000000000#!/usr/bin/env bash # Copyright 2018 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # 'go generate's kind, using tools from vendor (go-bindata) set -o errexit -o nounset -o pipefail # cd to the repo root and setup go REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." &> /dev/null && pwd -P)" cd "${REPO_ROOT}" source hack/build/setup-go.sh # build the generators using the tools module cd "${REPO_ROOT}/hack/tools" go build -o "${REPO_ROOT}/bin/deepcopy-gen" k8s.io/code-generator/cmd/deepcopy-gen # go back to the root cd "${REPO_ROOT}" # turn off module mode before running the generators # https://github.com/kubernetes/code-generator/issues/69 # we also need to populate vendor # run the generators bin/deepcopy-gen --output-file zz_generated.deepcopy.go --go-header-file hack/tools/boilerplate.go.txt ./pkg/internal/apis/config/ bin/deepcopy-gen --output-file zz_generated.deepcopy.go --go-header-file hack/tools/boilerplate.go.txt ./pkg/apis/config/v1alpha4 # set module mode back, return to repo root and gofmt to ensure we format generated code make gofmt kind-0.27.0/hack/make-rules/update/gofmt.sh000077500000000000000000000016231475376161000204510ustar00rootroot00000000000000#!/bin/bash # Copyright 2018 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # script to run gofmt over our code (not vendor) set -o errexit -o nounset -o pipefail # cd to the repo root and setup go REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." &> /dev/null && pwd -P)" cd "${REPO_ROOT}" source hack/build/setup-go.sh find . -name '*.go' -type f -print0 | xargs -0 gofmt -s -w kind-0.27.0/hack/make-rules/verify/000077500000000000000000000000001475376161000170165ustar00rootroot00000000000000kind-0.27.0/hack/make-rules/verify/README.md000066400000000000000000000001121475376161000202670ustar00rootroot00000000000000This directory contains tools to verify the state and quality of the repo.kind-0.27.0/hack/make-rules/verify/all.sh000077500000000000000000000030261475376161000201260ustar00rootroot00000000000000#!/usr/bin/env bash # Copyright 2018 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. set -o errexit -o nounset -o pipefail # cd to the repo root REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." &> /dev/null && pwd -P)" cd "${REPO_ROOT}" # exit code, if a script fails we'll set this to 1 res=0 # run all verify scripts, optionally skipping any of them if [[ "${VERIFY_LINT:-true}" == "true" ]]; then echo "verifying lints ..." hack/make-rules/verify/lint.sh || res=1 cd "${REPO_ROOT}" fi if [[ "${VERIFY_GENERATED:-true}" == "true" ]]; then echo "verifying generated ..." hack/make-rules/verify/generated.sh || res=1 cd "${REPO_ROOT}" fi if [[ "${VERIFY_SHELLCHECK:-true}" == "true" ]]; then echo "verifying shellcheck ..." hack/make-rules/verify/shellcheck.sh || res=1 cd "${REPO_ROOT}" fi # exit based on verify scripts if [[ "${res}" = 0 ]]; then echo "" echo "All verify checks passed, congrats!" else echo "" echo "One or more verify checks failed! See output above..." fi exit "${res}" kind-0.27.0/hack/make-rules/verify/generated.sh000077500000000000000000000042421475376161000213150ustar00rootroot00000000000000#!/usr/bin/env bash # Copyright 2018 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. set -o errexit -o nounset -o pipefail # cd to the repo root and setup go REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." &> /dev/null && pwd -P)" cd "${REPO_ROOT}" source hack/build/setup-go.sh # place to stick temp binaries BINDIR="${REPO_ROOT}/bin" mkdir -p "${BINDIR}" # TMP_REPO is used in make_temp_repo_copy TMP_REPO="$(TMPDIR="${BINDIR}" mktemp -d "${BINDIR}/verify-deps.XXXXX")" # exit trap cleanup for TMP_REPO cleanup() { if [[ -n "${TMP_REPO}" ]]; then rm -rf "${TMP_REPO}" fi } # copies repo into a temp root saved to TMP_REPO make_temp_repo_copy() { # we need to copy everything but bin (and the old _output) (which is .gitignore anyhow) find . \ -mindepth 1 -maxdepth 1 \ \( \ -type d -path "./.git" -o \ -type d -path "./bin" -o \ -type d -path "./_output" \ \) -prune -o \ -exec bash -c 'cp -r "${0}" "${1}/${0}" >/dev/null 2>&1' {} "${TMP_REPO}" \; } main() { trap cleanup EXIT # copy repo root into tempdir under ./_output make_temp_repo_copy # run generated code update script cd "${TMP_REPO}" REPO_ROOT="${TMP_REPO}" make generate # make sure the temp repo has no changes relative to the real repo diff=$(diff -Nupr \ -x ".git" \ -x "bin" \ -x "_output" \ -x "vendor" \ "${REPO_ROOT}" "${TMP_REPO}" 2>/dev/null || true) if [[ -n "${diff}" ]]; then echo "unexpectedly dirty working directory after hack/update/generated.sh" >&2 echo "" >&2 echo "${diff}" >&2 echo "" >&2 echo "please run make generate" >&2 exit 1 fi } main kind-0.27.0/hack/make-rules/verify/lint.sh000077500000000000000000000024161475376161000203260ustar00rootroot00000000000000#!/usr/bin/env bash # Copyright 2019 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # script to run linters set -o errexit -o nounset -o pipefail # cd to the repo root and setup go REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." &> /dev/null && pwd -P)" cd "${REPO_ROOT}" source hack/build/setup-go.sh # build golangci-lint cd "${REPO_ROOT}/hack/tools" go build -o "${REPO_ROOT}"/bin/golangci-lint github.com/golangci/golangci-lint/cmd/golangci-lint cd "${REPO_ROOT}" # first for the repo in general "${REPO_ROOT}"/bin/golangci-lint --config "${REPO_ROOT}/hack/tools/.golangci.yml" run ./... # then for kindnetd module cd "${REPO_ROOT}/images/kindnetd" GOOS=linux "${REPO_ROOT}"/bin/golangci-lint --config "${REPO_ROOT}/hack/tools/.golangci.yml" run ./... kind-0.27.0/hack/make-rules/verify/shellcheck.sh000077500000000000000000000064251475376161000214710ustar00rootroot00000000000000#!/usr/bin/env bash # Copyright 2019 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # CI script to run shellcheck set -o errexit set -o nounset set -o pipefail # cd to the repo root REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." &> /dev/null && pwd -P)" cd "${REPO_ROOT}" # required version for this script, if not installed on the host we will # use the official docker image instead. keep this in sync with SHELLCHECK_IMAGE SHELLCHECK_VERSION="0.9.0" # upstream shellcheck latest stable image as of October 23rd, 2019 SHELLCHECK_IMAGE='docker.io/koalaman/shellcheck-alpine:v0.9.0@sha256:e19ed93c22423970d56568e171b4512c9244fc75dd9114045016b4a0073ac4b7' DOCKER="${DOCKER:-docker}" # detect if the host machine has the required shellcheck version installed # if so, we will use that instead. HAVE_SHELLCHECK=false if which shellcheck &>/dev/null; then detected_version="$(shellcheck --version | grep 'version: .*')" if [[ "${detected_version}" = "version: ${SHELLCHECK_VERSION}" ]]; then HAVE_SHELLCHECK=true fi fi # Find all shell scripts excluding: # - Anything git-ignored - No need to lint untracked files. # - ./.git/* - Ignore anything in the git object store. # - ./vendor/* - Ignore vendored contents. # - ./bin/* - No need to lint output directories. all_shell_scripts=() while IFS=$'\n' read -r script; do git check-ignore -q "$script" || all_shell_scripts+=("$script"); done < <(grep -irl '#!.*sh' . | grep -Ev '(^\./\.git/)|(^\./vendor/)|(^\./hack/third_party/)|(^\./images/.*/scripts/third_party/)|(^\./bin/)|(\.go$)') # common arguments we'll pass to shellcheck SHELLCHECK_OPTIONS=( # allow following sourced files that are not specified in the command, # we need this because we specify one file at a time in order to trivially # detect which files are failing '--external-sources' # disabled lint codes # 2330 - disabled due to https://github.com/koalaman/shellcheck/issues/1162 '--exclude=2230' # 2126 - disabled because grep -c exits error when there are zero matches, # unlike grep | wc -l '--exclude=2126' # set colorized output '--color=auto' ) # actually shellcheck # tell the user which we've selected and lint all scripts # The shellcheck errors are printed to stdout by default, hence they need to be redirected # to stderr in order to be well parsed for Junit representation by juLog function res=0 if ${HAVE_SHELLCHECK}; then >&2 echo "Using host shellcheck ${SHELLCHECK_VERSION} binary." shellcheck "${SHELLCHECK_OPTIONS[@]}" "${all_shell_scripts[@]}" >&2 || res=$? else >&2 echo "Using shellcheck ${SHELLCHECK_VERSION} docker image." "${DOCKER}" run \ --rm -v "${REPO_ROOT}:${REPO_ROOT}" -w "${REPO_ROOT}" \ "${SHELLCHECK_IMAGE}" \ shellcheck "${SHELLCHECK_OPTIONS[@]}" "${all_shell_scripts[@]}" >&2 || res=$? fi exit $res kind-0.27.0/hack/release/000077500000000000000000000000001475376161000150655ustar00rootroot00000000000000kind-0.27.0/hack/release/build/000077500000000000000000000000001475376161000161645ustar00rootroot00000000000000kind-0.27.0/hack/release/build/README.md000066400000000000000000000001161475376161000174410ustar00rootroot00000000000000This directory contains temporary tooling used to make releasing kind easier. kind-0.27.0/hack/release/build/cross.sh000077500000000000000000000036041475376161000176570ustar00rootroot00000000000000#!/usr/bin/env bash # Copyright 2018 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # simple script to build binaries for release set -o errexit -o nounset -o pipefail # cd to the repo root and setup go REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." &> /dev/null && pwd -P)" cd "${REPO_ROOT}" source hack/build/setup-go.sh # controls the number of concurrent builds PARALLELISM=${PARALLELISM:-6} echo "Building in parallel for:" # What we do here: # - use xargs to build in parallel (-P) while collecting a combined exit code # - use cat to supply the individual args to xargs (one line each) # - use env -S to split the line into environment variables and execute # - ... the build # NOTE: the binary name needs to be in single quotes so we delay evaluating # GOOS / GOARCH # NOTE: disable SC2016 because we _intend_ for these to evaluate later # shellcheck disable=SC2016 if xargs -0 -n1 -P "${PARALLELISM}" bash -c 'eval $0; make build KIND_BINARY_NAME=kind-${GOOS}-${GOARCH}'; then echo "Cross build passed!" 1>&2 else echo "Cross build failed!" 1>&2 exit 1 fi < <(cat < "$f".sha256sum; done kind-0.27.0/hack/release/build/push-node.sh000077500000000000000000000055051475376161000204320ustar00rootroot00000000000000#!/usr/bin/env bash # Copyright 2018 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. set -o errexit -o nounset -o pipefail REGISTRY="${REGISTRY:-kindest}" IMAGE_NAME="${IMAGE_NAME:-node}" # cd to the repo root REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." &> /dev/null && pwd -P)" cd "${REPO_ROOT}" # ensure we have up to date kind make build # path to kubernetes sources KUBEROOT="${KUBEROOT:-"$(go env GOPATH)"/src/k8s.io/kubernetes}" # ensure we have qemu setup so we can run cross-arch images # TODO: dedupe specifying this image? docker run --rm --privileged tonistiigi/binfmt:qemu-v7.0.0-28@sha256:66e11bea77a5ea9d6f0fe79b57cd2b189b5d15b93a2bdb925be22949232e4e55 --install all # NOTE: adding platforms is costly in terms of build time # we will consider expanding this in the future, for now the aim is to prove # multi-arch and enable developers working on commonly available hardware # Other users are free to build their own images on additional platforms using # their own time and resources. Please see our docs. ARCHES="${ARCHES:-amd64 arm64}" IFS=" " read -r -a __arches__ <<< "$ARCHES" set -x # ensure clean build (cd "${KUBEROOT}" && make clean) # get kubernetes version version_line="$(cd "${KUBEROOT}"; ./hack/print-workspace-status.sh | grep 'STABLE_DOCKER_TAG')" kube_version="${version_line#"STABLE_DOCKER_TAG "}" # kubernetes build option(s) GOFLAGS="${GOFLAGS:-}" if [ -z "${GOFLAGS}" ]; then # TODO: dockerless only applies to < 1.24, the version selection here is brittle case "${kube_version}" in v1.1[0-8].*) GOFLAGS="-tags=providerless" ;; *) GOFLAGS="-tags=providerless,dockerless" ;; esac fi export GOFLAGS # build for each arch IMAGE="${REGISTRY}/${IMAGE_NAME}:${kube_version}" images=() for arch in "${__arches__[@]}"; do image="${REGISTRY}/${IMAGE_NAME}-${arch}:${kube_version}" "${REPO_ROOT}/bin/kind" build node-image --image="${image}" --arch="${arch}" "${KUBEROOT}" images+=("${image}") done # combine to manifest list tagged with kubernetes version # images must be pushed to be referenced by docker manifest # we push only after all builds have succeeded for image in "${images[@]}"; do docker push "${image}" done docker manifest rm "${IMAGE}" || true docker manifest create "${IMAGE}" "${images[@]}" docker manifest push "${IMAGE}" kind-0.27.0/hack/release/create.sh000077500000000000000000000047371475376161000167020ustar00rootroot00000000000000#!/usr/bin/env bash # Copyright 2018 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # creates a release and following pre-release commit for `kind` # builds binaries between the commits # Use like: create.sh # EG: create.sh 0.3.0 0.4.0 set -o errexit -o nounset -o pipefail UPSTREAM='https://github.com/kubernetes-sigs/kind.git' # cd to the repo root REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd -P)" cd "${REPO_ROOT}" # check for arguments if [ "$#" -ne 2 ]; then echo "Usage: create.sh release-version next-prerelease-version" exit 1 fi # darwin is great SED="sed" if which gsed &>/dev/null; then SED="gsed" fi if ! (${SED} --version 2>&1 | grep -q GNU); then echo "!!! GNU sed is required. If on OS X, use 'brew install gnu-sed'." >&2 exit 1 fi VERSION_FILE="./pkg/cmd/kind/version/version.go" # update core version in go code to $1 and pre-release version to $2 set_version() { ${SED} -i "s/versionCore = .*/versionCore = \"${1}\"/" "${VERSION_FILE}" ${SED} -i "s/versionPreRelease = .*/versionPreRelease = \"${2}\"/" "${VERSION_FILE}" echo "Updated ${VERSION_FILE} for ${1}" } # make a commit denoting the version ($1) make_commit() { git add "${VERSION_FILE}" git commit -m "version ${1}" echo "Created commit for ${1}" } # add a git tag with $1 add_tag() { git tag "${1}" echo "Tagged ${1}" } # create the first version, tag and build it set_version "${1}" "" make_commit "v${1}" add_tag "v${1}" echo "Building ..." make clean && ./hack/release/build/cross.sh # update to the second version set_version "${2}" "alpha" make_commit "v${2}-alpha" add_tag "v${2}-alpha" # print follow-up instructions echo "" echo "Created commits for v${1} and v${2}, you should now:" echo " - git push" echo " - File a PR with these pushed commits" echo " - Merge the PR" echo " - git push ${UPSTREAM} v${1}" echo " - git push ${UPSTREAM} v${2}-alpha" echo " - Create a GitHub release from the pushed tag v${1}" kind-0.27.0/hack/release/get-contributors.sh000077500000000000000000000035271475376161000207450ustar00rootroot00000000000000#!/usr/bin/env bash # Copyright 2020 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # inputs are: # - LAST_VERSION_TAG -- This is the version to get commits since # like: LAST_VERSION_TAG="v0.8.1" # - GITHUB_OATH_TOKEN -- used to avoid hitting API rate limits ORG="kubernetes-sigs" REPO="kind" # query git for contributors since the tag contributors=() while IFS='' read -r line; do contributors+=("$line"); done < <(git log --format="%aN <%aE>" "${LAST_VERSION_TAG:?}.." | sort | uniq) # query github for usernames and output bulleted list contributor_logins=() for contributor in "${contributors[@]}"; do # get a commit for this author commit_for_contributor="$(git log --author="${contributor}" --pretty=format:"%H" -1)" # lookup the commit info to get the login contributor_logins+=("$(curl \ -sG \ ${GITHUB_OAUTH_TOKEN:+-H "Authorization: Bearer ${GITHUB_OAUTH_TOKEN:?}"} \ --data-urlencode "q=${contributor}" \ "https://api.github.com/repos/${ORG}/${REPO}/commits/${commit_for_contributor}" \ | jq -r .author.login )") done echo "Contributors since ${LAST_VERSION_TAG}:" # echo sorted formatted list while IFS='' read -r contributor_login; do echo "- @${contributor_login}" done < <(for c in "${contributor_logins[@]}"; do echo "$c"; done | LC_COLLATE=C sort --ignore-case | uniq) kind-0.27.0/hack/release/push-node.sh000077500000000000000000000051611475376161000173310ustar00rootroot00000000000000#!/usr/bin/env bash # Copyright 2024 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # this script replaces hack/release/build/push-node.sh for Kubernetes v1.31+ # usage: push-node.sh v1.32.0 set -o errexit -o nounset -o pipefail REGISTRY="${REGISTRY:-gcr.io/k8s-staging-kind}" IMAGE_NAME="${IMAGE_NAME:-node}" # cd to the repo root REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." &> /dev/null && pwd -P)" cd "${REPO_ROOT}" VERSION="${1:-}" if [[ -z "${VERSION}" ]]; then echo >&2 "version argument not supplied, looking up current stable ..." VERSION="$(curl -sL https://dl.k8s.io/release/stable.txt)" fi echo >&2 "will build node image for Kubernetes ${VERSION} ..." # ensure we have up to date kind echo >&2 "building kind ..." make build # ensure we have qemu setup so we can run cross-arch images # TODO: dedupe specifying this image? echo >&2 "ensuring binfmt_misc ..." docker run --rm --privileged tonistiigi/binfmt:qemu-v7.0.0-28@sha256:66e11bea77a5ea9d6f0fe79b57cd2b189b5d15b93a2bdb925be22949232e4e55 --install all # NOTE: adding platforms is costly in terms of build time # we will consider expanding this in the future, for now the aim is to prove # multi-arch and enable developers working on commonly available hardware # Other users are free to build their own images on additional platforms using # their own time and resources. Please see our docs. ARCHES="${ARCHES:-amd64 arm64}" IFS=" " read -r -a __arches__ <<< "$ARCHES" set -x # build for each arch IMAGE="${REGISTRY}/${IMAGE_NAME}:${VERSION}" images=() for arch in "${__arches__[@]}"; do image="${REGISTRY}/${IMAGE_NAME}-${arch}:${VERSION}" echo >&2 "building ${image} ..." "${REPO_ROOT}/bin/kind" build node-image --image="${image}" --arch="${arch}" "${VERSION}" images+=("${image}") done # combine to manifest list tagged with kubernetes version # images must be pushed to be referenced by docker manifest # we push only after all builds have succeeded for image in "${images[@]}"; do docker push "${image}" done docker manifest rm "${IMAGE}" || true docker manifest create "${IMAGE}" "${images[@]}" docker manifest push "${IMAGE}" kind-0.27.0/hack/third_party/000077500000000000000000000000001475376161000157765ustar00rootroot00000000000000kind-0.27.0/hack/third_party/gimme/000077500000000000000000000000001475376161000170745ustar00rootroot00000000000000kind-0.27.0/hack/third_party/gimme/LICENSE000066400000000000000000000021021475376161000200740ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2015-2018 gimme contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. kind-0.27.0/hack/third_party/gimme/README.md000066400000000000000000000002171475376161000203530ustar00rootroot00000000000000# gimme This is an unmodified copy of [gimme], so we don't have to download it from the internet. [gimme]: https://github.com/travis-ci/gimmekind-0.27.0/hack/third_party/gimme/gimme000077500000000000000000000662441475376161000201340ustar00rootroot00000000000000#!/usr/bin/env bash # vim:noexpandtab:ts=2:sw=2: # #+ Usage: $(basename $0) [flags] [go-version] [version-prefix] #+ - #+ Version: ${GIMME_VERSION} #+ Copyright: ${GIMME_COPYRIGHT} #+ License URL: ${GIMME_LICENSE_URL} #+ - #+ Install go! There are multiple types of installations available, with 'auto' being the default. #+ If either 'auto' or 'binary' is specified as GIMME_TYPE, gimme will first check for an existing #+ go installation. This behavior may be disabled by providing '-f/--force/force' as first positional #+ argument. #+ - #+ Option flags: #+ -h --help help - show this help text and exit #+ -V --version version - show the version only and exit #+ -f --force force - remove the existing go installation if present prior to install #+ -l --list list - list installed go versions and exit #+ -k --known known - list known go versions and exit #+ --force-known-update - when used with --known, ignores the cache and updates #+ -r --resolve resolve - resolve a version specifier to a version, show that and exit #+ - #+ Influential env vars: #+ - #+ GIMME_GO_VERSION - version to install (*REQUIRED*, may be given as first positional arg) #+ GIMME_VERSION_PREFIX - prefix for installed versions (default '${GIMME_VERSION_PREFIX}', #+ may be given as second positional arg) #+ GIMME_ARCH - arch to install (default '${GIMME_ARCH}') #+ GIMME_BINARY_OSX - darwin-specific binary suffix (default '${GIMME_BINARY_OSX}') #+ GIMME_ENV_PREFIX - prefix for env files (default '${GIMME_ENV_PREFIX}') #+ GIMME_GO_GIT_REMOTE - git remote for git-based install (default '${GIMME_GO_GIT_REMOTE}') #+ GIMME_OS - os to install (default '${GIMME_OS}') #+ GIMME_TMP - temp directory (default '${GIMME_TMP}') #+ GIMME_TYPE - install type to perform ('auto', 'binary', 'source', or 'git') #+ (default '${GIMME_TYPE}') #+ GIMME_INSTALL_RACE - install race directory after compile if non-empty. #+ If the install type is 'binary', this option is ignored. #+ GIMME_DEBUG - enable tracing if non-empty #+ GIMME_NO_ENV_ALIAS - disable creation of env 'alias' file when os and arch match host #+ GIMME_SILENT_ENV - omit the 'go version' line from env file #+ GIMME_CGO_ENABLED - enable build of cgo support #+ GIMME_CC_FOR_TARGET - cross compiler for cgo support #+ GIMME_DOWNLOAD_BASE - override base URL dir for download (default '${GIMME_DOWNLOAD_BASE}') #+ GIMME_LIST_KNOWN - override base URL for known go versions (default '${GIMME_LIST_KNOWN}') #+ GIMME_KNOWN_CACHE_MAX - seconds the cache for --known is valid for (default '${GIMME_KNOWN_CACHE_MAX}') #+ - # set -e shopt -s nullglob shopt -s dotglob shopt -s extglob set -o pipefail [[ ${GIMME_DEBUG} ]] && set -x readonly GIMME_VERSION="v1.5.4" readonly GIMME_COPYRIGHT="Copyright (c) 2015-2020 gimme contributors" readonly GIMME_LICENSE_URL="https://raw.githubusercontent.com/travis-ci/gimme/${GIMME_VERSION}/LICENSE" export GIMME_VERSION export GIMME_COPYRIGHT export GIMME_LICENSE_URL program_name="$(basename "$0")" # shellcheck disable=SC1117 warn() { printf >&2 "%s: %s\n" "${program_name}" "${*}"; } die() { warn "$@" exit 1 } # We don't want to go around hitting Google's servers with requests for # files named HEAD@{date}.tar so we only try binary/source downloads if # it looks like a plausible name to us. # We don't need to support 0. releases of Go. # We don't support 5 digit major-versions of Go (limit back-tracking in RE). # We don't support very long versions # (both to avoid annoying download server operators with attacks and # because regexp backtracking can be pathological). # Per _assert_version_given we do assume 2.0 not 2 ALLOWED_UPSTREAM_VERSION_RE='^[1-9][0-9]{0,3}(\.[0-9][0-9a-zA-Z_-]{0,9})+$' # # The main path which allowed these to leak upstream before has been closed # but a valid git repo tag or branch-name will still reach the point of # being _tried_ upstream. # _do_curl "url" "file" _do_curl() { mkdir -p "$(dirname "${2}")" if command -v curl >/dev/null; then curl -sSLf "${1}" -o "${2}" 2>/dev/null return fi if command -v wget >/dev/null; then wget -q "${1}" -O "${2}" 2>/dev/null return fi if command -v fetch >/dev/null; then fetch -q "${1}" -o "${2}" 2>/dev/null return fi echo >&2 'error: no curl, wget, or fetch found' exit 1 } # _sha256sum "file" _sha256sum() { if command -v sha256sum &>/dev/null; then sha256sum "$@" elif command -v gsha256sum &>/dev/null; then gsha256sum "$@" else shasum -a 256 "$@" fi } # sort versions, handling 1.10 after 1.9, not before 1.2 # FreeBSD sort has --version-sort, none of the others do # Looks like --general-numeric-sort is the safest; checked macOS 10.12.6, FreeBSD 10.3, Ubuntu Trusty if sort --version-sort /dev/null; then _version_sort() { sort --version-sort; } else _version_sort() { # If we go to four-digit minor or patch versions, then extend the padding here # (but in such a world, perhaps --version-sort will have become standard by then?) sed -E 's/\.([0-9](\.|$))/.00\1/g; s/\.([0-9][0-9](\.|$))/.0\1/g' | sort --general-numeric-sort | sed 's/\.00*/./g' } fi # _do_curls "file" "url" ["url"...] _do_curls() { f="${1}" shift if _sha256sum -c "${f}.sha256" &>/dev/null; then return 0 fi for url in "${@}"; do if _do_curl "${url}" "${f}"; then if _do_curl "${url}.sha256" "${f}.sha256"; then echo "$(cat "${f}.sha256") ${f}" >"${f}.sha256.tmp" mv "${f}.sha256.tmp" "${f}.sha256" if ! _sha256sum -c "${f}.sha256" &>/dev/null; then warn "sha256sum failed for '${f}'" warn 'continuing to next candidate URL' continue fi fi return fi done rm -f "${f}" return 1 } # _binary "version" "file.tar.gz" "arch" _binary() { local version=${1} local file=${2} local arch=${3} urls=( "${GIMME_DOWNLOAD_BASE}/go${version}.${GIMME_OS}-${arch}.tar.gz" ) if [[ "${GIMME_OS}" == 'darwin' && "${GIMME_BINARY_OSX}" ]]; then urls=( "${GIMME_DOWNLOAD_BASE}/go${version}.${GIMME_OS}-${arch}-${GIMME_BINARY_OSX}.tar.gz" "${urls[@]}" ) fi if [ "${arch}" = 'arm' ]; then # attempt "armv6l" vs just "arm" first (since that's what's officially published) urls=( "${GIMME_DOWNLOAD_BASE}/go${version}.${GIMME_OS}-${arch}v6l.tar.gz" # go1.6beta2 & go1.6rc1 "${GIMME_DOWNLOAD_BASE}/go${version}.${GIMME_OS}-${arch}6.tar.gz" # go1.6beta1 "${urls[@]}" ) fi if [ "${GIMME_OS}" = 'windows' ]; then urls=( "${GIMME_DOWNLOAD_BASE}/go${version}.${GIMME_OS}-${arch}.zip" ) fi _do_curls "${file}" "${urls[@]}" } # _source "version" "file.src.tar.gz" _source() { urls=( "${GIMME_DOWNLOAD_BASE}/go${1}.src.tar.gz" "https://github.com/golang/go/archive/go${1}.tar.gz" ) _do_curls "${2}" "${urls[@]}" } # _fetch "dir" _fetch() { mkdir -p "$(dirname "${1}")" if [[ -d "${1}/.git" ]]; then ( cd "${1}" git remote set-url origin "${GIMME_GO_GIT_REMOTE}" git fetch -q --all && git fetch -q --tags ) return fi git clone -q "${GIMME_GO_GIT_REMOTE}" "${1}" } # _checkout "version" "dir" # NB: might emit a "renamed version" on stdout _checkout() { local spec="${1:?}" godir="${2:?}" # We are called twice, once during validation that a version was given and # later during build. We don't want to fetch twice, so we are fetching # during the validation only, in the caller. if [[ "${spec}" =~ ^[0-9a-f]{6,}$ ]]; then # We always treat this as a commit sha, whether instead of doing # branch tests etc. It looks like a commit sha and the Go maintainers # aren't daft enough to use pure hex for a tag or branch. git -C "$godir" reset -q --hard "${spec}" || return 1 return 0 fi # If spec looks like HEAD^{something} or HEAD^^^ then trying # origin/$spec would succeed but we'd write junk to the filesystem, # propagating annoying characters out. local retval probe_named disallow rev probe_named=1 disallow='[@^~:{}]' if [[ "${spec}" =~ $disallow ]]; then probe_named=0 [[ "${spec}" != "@" ]] || spec="HEAD" fi try_spec() { git -C "${godir}" reset -q --hard "$@" -- 2>/dev/null; } retval=1 if ((probe_named)); then retval=0 try_spec "origin/${spec}" || try_spec "origin/go${spec}" || { [[ "${spec}" == "tip" ]] && try_spec origin/master; } || try_spec "refs/tags/${spec}" || try_spec "refs/tags/go${spec}" || retval=1 fi if ((retval)); then retval=0 # We're about to reset anyway, if we succeed, so we should reset to a # known state before parsing what might be relative specs try_spec origin/master && rev="$(git -C "${godir}" rev-parse --verify -q "${spec}^{object}")" && try_spec "${rev}" && git -C "${godir}" rev-parse --verify -q --short=12 "${rev}" || retval=1 # that rev-parse prints to stdout, so we can affect the version seen fi unset -f try_spec return $retval } # _extract "file.tar.gz" "dir" _extract() { mkdir -p "${2}" if [[ "${1}" == *.tar.gz ]]; then tar -xf "${1}" -C "${2}" --strip-components 1 else unzip -q "${1}" -d "${2}" mv "${2}"/go/* "${2}" rmdir "${2}"/go fi } # _setup_bootstrap _setup_bootstrap() { local versions=("1.18" "1.17" "1.16" "1.15" "1.14" "1.13" "1.12" "1.11" "1.10" "1.9" "1.8" "1.7" "1.6" "1.5" "1.4") # try existing for v in "${versions[@]}"; do for candidate in "${GIMME_ENV_PREFIX}/go${v}"*".env"; do if [ -s "${candidate}" ]; then # shellcheck source=/dev/null GOROOT_BOOTSTRAP="$(source "${candidate}" 2>/dev/null && go env GOROOT)" export GOROOT_BOOTSTRAP return 0 fi done done # try binary for v in "${versions[@]}"; do if [ -n "$(_try_binary "${v}" "${GIMME_HOSTARCH}")" ]; then export GOROOT_BOOTSTRAP="${GIMME_VERSION_PREFIX}/go${v}.${GIMME_OS}.${GIMME_HOSTARCH}" return 0 fi done echo >&2 "Unable to setup go bootstrap from existing or binary" return 1 } # _compile "dir" _compile() { ( if grep -q GOROOT_BOOTSTRAP "${1}/src/make.bash" &>/dev/null; then _setup_bootstrap || return 1 fi cd "${1}" if [[ -d .git ]]; then git clean -dfx -q fi cd src export GOOS="${GIMME_OS}" GOARCH="${GIMME_ARCH}" export CGO_ENABLED="${GIMME_CGO_ENABLED}" export CC_FOR_TARGET="${GIMME_CC_FOR_TARGET}" local make_log="${1}/make.${GOOS}.${GOARCH}.log" if [[ "${GIMME_DEBUG}" -ge "2" ]]; then ./make.bash -v 2>&1 | tee "${make_log}" 1>&2 || return 1 else ./make.bash &>"${make_log}" || return 1 fi ) } _try_install_race() { if [[ ! "${GIMME_INSTALL_RACE}" ]]; then return 0 fi "${1}/bin/go" install -race std } _can_compile() { cat >"${GIMME_TMP}/test.go" <<'EOF' package main import "os" func main() { os.Exit(0) } EOF "${1}/bin/go" run "${GIMME_TMP}/test.go" } # _env "dir" _env() { [[ -d "${1}/bin" && -x "${1}/bin/go" ]] || return 1 # if we try to run a Darwin binary on Linux, we need to fail so 'auto' can fallback to cross-compiling from source # automatically GOROOT="${1}" GOFLAGS="" "${1}/bin/go" version &>/dev/null || return 1 # https://twitter.com/davecheney/status/431581286918934528 # we have to GOROOT sometimes because we use official release binaries in unofficial locations :( # # Issue 87 leads to: # No, we should _always_ set GOROOT when using official release binaries, and sanest to just always set it. # The "avoid setting it" is _only_ for people using official releases in official locations. # Tools like `gimme` are the reason that GOROOT-in-env exists. echo if [[ "$(GOROOT="${1}" "${1}/bin/go" env GOHOSTOS)" == "${GIMME_OS}" ]]; then echo 'unset GOOS;' else echo 'export GOOS="'"${GIMME_OS}"'";' fi if [[ "$(GOROOT="${1}" "${1}/bin/go" env GOHOSTARCH)" == "${GIMME_ARCH}" ]]; then echo 'unset GOARCH;' else echo 'export GOARCH="'"${GIMME_ARCH}"'";' fi echo "export GOROOT='${1}';" # shellcheck disable=SC2016 echo 'export PATH="'"${1}/bin"':${PATH}";' if [[ -z "${GIMME_SILENT_ENV}" ]]; then echo 'go version >&2;' fi echo } # _env_alias "dir" "env-file" _env_alias() { if [[ "${GIMME_NO_ENV_ALIAS}" ]]; then echo "${2}" return fi if [[ "$(GOROOT="${1}" "${1}/bin/go" env GOHOSTOS)" == "${GIMME_OS}" && "$(GOROOT="${1}" "${1}/bin/go" env GOHOSTARCH)" == "${GIMME_ARCH}" ]]; then # GIMME_GO_VERSION might be a branch, which can contain '/' local dest="${GIMME_ENV_PREFIX}/go${GIMME_GO_VERSION//\//__}.env" cp "${2}" "${dest}" ln -sf "${dest}" "${GIMME_ENV_PREFIX}/latest.env" echo "${dest}" else echo "${2}" fi } _try_existing() { case "${1}" in binary) local existing_ver="${GIMME_VERSION_PREFIX}/go${GIMME_GO_VERSION}.${GIMME_OS}.${GIMME_ARCH}" local existing_env="${GIMME_ENV_PREFIX}/go${GIMME_GO_VERSION}.${GIMME_OS}.${GIMME_ARCH}.env" ;; source) local existing_ver="${GIMME_VERSION_PREFIX}/go${GIMME_GO_VERSION}.src" local existing_env="${GIMME_ENV_PREFIX}/go${GIMME_GO_VERSION}.src.env" ;; *) _try_existing binary || _try_existing source return $? ;; esac if [[ -x "${existing_ver}/bin/go" && -s "${existing_env}" ]]; then # newer envs have existing semi-colon at end of line, because newer gimme # puts them there; envs created before that change lack those semi-colons # and should gain them, to make it easier for people using eval without # double-quoting the command substition. sed -e 's/\([^;]\)$/\1;/' <"${existing_env}" # gimme is the corner-case where GOROOT _should_ be overriden, since if the # ancilliary tooling's system-internal DefaultGoroot exists, and GOROOT is # unset, then it will be used and the wrong golang will be picked up. # Lots of old installs won't have GOROOT; munge it from $PATH if grep -qs '^unset GOROOT' -- "${existing_env}"; then sed -n -e 's/^export PATH="\(.*\)\/bin:.*$/export GOROOT='"'"'\1'"'"';/p' <"${existing_env}" echo fi # Export the same variables whether building new or using existing echo "export GIMME_ENV='${existing_env}';" return fi return 1 } # _try_binary "version" "arch" _try_binary() { local version=${1} local arch=${2} local bin_tgz="${GIMME_TMP}/go${version}.${GIMME_OS}.${arch}.tar.gz" local bin_dir="${GIMME_VERSION_PREFIX}/go${version}.${GIMME_OS}.${arch}" local bin_env="${GIMME_ENV_PREFIX}/go${version}.${GIMME_OS}.${arch}.env" [[ "${version}" =~ ${ALLOWED_UPSTREAM_VERSION_RE} ]] || return 1 if [ "${GIMME_OS}" = 'windows' ]; then bin_tgz=${bin_tgz%.tar.gz}.zip fi _binary "${version}" "${bin_tgz}" "${arch}" || return 1 _extract "${bin_tgz}" "${bin_dir}" || return 1 _env "${bin_dir}" | tee "${bin_env}" || return 1 echo "export GIMME_ENV=\"$(_env_alias "${bin_dir}" "${bin_env}")\"" } _try_source() { local src_tgz="${GIMME_TMP}/go${GIMME_GO_VERSION}.src.tar.gz" local src_dir="${GIMME_VERSION_PREFIX}/go${GIMME_GO_VERSION}.src" local src_env="${GIMME_ENV_PREFIX}/go${GIMME_GO_VERSION}.src.env" [[ "${GIMME_GO_VERSION}" =~ ${ALLOWED_UPSTREAM_VERSION_RE} ]] || return 1 _source "${GIMME_GO_VERSION}" "${src_tgz}" || return 1 _extract "${src_tgz}" "${src_dir}" || return 1 _compile "${src_dir}" || return 1 _try_install_race "${src_dir}" || return 1 _env "${src_dir}" | tee "${src_env}" || return 1 echo "export GIMME_ENV=\"$(_env_alias "${src_dir}" "${src_env}")\"" } # We do _not_ try to use any version caching with _try_existing(), but instead # build afresh each time. We don't want to deal with someone moving the repo # to other-version, doing an install, then resetting it back to # last-version-we-saw and thus introducing conflicts. # # If you want to re-use a built-at-spec version, then avoid moving the repo # and source the generated .env manually. # Note that the env will just refer to the 'go' directory, so it's not safe # to reuse anyway. _try_git() { local git_dir="${GIMME_VERSION_PREFIX}/go" local git_env="${GIMME_ENV_PREFIX}/go.git.${GIMME_OS}.${GIMME_ARCH}.env" local resolved_sha # Any tags should have been resolved when we asserted that we were # given a version, so no need to handle that here. _checkout "${GIMME_GO_VERSION}" "${git_dir}" >/dev/null || return 1 _compile "${git_dir}" || return 1 _try_install_race "${git_dir}" || return 1 _env "${git_dir}" | tee "${git_env}" || return 1 echo "export GIMME_ENV=\"$(_env_alias "${git_dir}" "${git_env}")\"" } _wipe_version() { local env_file="${GIMME_ENV_PREFIX}/go${1}.${GIMME_OS}.${GIMME_ARCH}.env" if [[ -s "${env_file}" ]]; then rm -rf "$(awk -F\" '/GOROOT/ { print $2 }' "${env_file}")" rm -f "${env_file}" fi } _list_versions() { if [ ! -d "${GIMME_VERSION_PREFIX}" ]; then return 0 fi local current_version current_version="$(go env GOROOT 2>/dev/null)" current_version="${current_version##*/go}" current_version="${current_version%%.${GIMME_OS}.*}" # 1.1 1.10 1.2 is bad; zsh has `setopt numeric_glob_sort` but bash # doesn't appear to have anything like that. for d in "${GIMME_VERSION_PREFIX}/go"*".${GIMME_OS}."*; do local cleaned="${d##*/go}" cleaned="${cleaned%%.${GIMME_OS}.*}" echo "${cleaned}" done | _version_sort | while read -r cleaned; do echo -en "${cleaned}" if [[ "${cleaned}" == "${current_version}" ]]; then echo -en ' <= current' >&2 fi echo done } _update_remote_known_list_if_needed() { # shellcheck disable=SC1117 local exp="go([[:alnum:]\.]*)\.src.*" # :alnum: catches beta versions too local list="${GIMME_VERSION_PREFIX}/known-versions.txt" local dlfile="${GIMME_TMP}/known-dl" if [[ -e "${list}" ]] && ! ((force_known_update)) && ! _file_older_than_secs "${list}" "${GIMME_KNOWN_CACHE_MAX}"; then echo "${list}" return 0 fi [[ -d "${GIMME_VERSION_PREFIX:?}" ]] || mkdir -p -- "${GIMME_VERSION_PREFIX}" _do_curl "${GIMME_LIST_KNOWN}" "${dlfile}" while read -r line; do if [[ "${line}" =~ ${exp} ]]; then echo "${BASH_REMATCH[1]}" fi done <"${dlfile}" | _version_sort | uniq >"${list}.new" rm -f "${list}" &>/dev/null mv "${list}.new" "${list}" rm -f "${dlfile}" echo "${list}" return 0 } _list_known() { local knownfile knownfile="$(_update_remote_known_list_if_needed)" ( _list_versions 2>/dev/null cat -- "${knownfile}" ) | grep . | _version_sort | uniq } # For the "invoked on commandline" case, we want to always pass unknown # strings through, so that we can be a uniqueness filter, but for unknown # names we want to exit with a value other than 1, so we document that # we'll exit 2. For use by other functions, 2 is as good as 1. _resolve_version() { case "${1}" in stable) _get_curr_stable return 0 ;; oldstable) _get_old_stable return 0 ;; tip) echo "tip" return 0 ;; *.x) true ;; *) echo "${1}" local GIMME_GO_VERSION="$1" local ASSERT_ABORT='return' if _assert_version_given 2>/dev/null; then return 0 fi warn "version specifier '${1}' unknown" return 2 ;; esac # We have a .x suffix local base="${1%.x}" local ver last='' known known="$(_update_remote_known_list_if_needed)" # will be version-sorted if [[ ! "${base}" =~ ^[0-9.]+$ ]]; then warn "resolve pattern '${base}.x' invalid for .x finding" return 2 fi # The `.x` is optional; "1.10" matches "1.10.x" local search="^${base//./\\.}(\\.[0-9.]+)?\$" # avoid regexp attacks while read -r ver; do [[ "${ver}" =~ $search ]] || continue last="${ver}" done <"$known" if [[ -n "${last}" ]]; then echo "${last}" return 0 fi echo "${1}" warn "given '${1}' but no release for '${base}' found" return 2 } _realpath() { # shellcheck disable=SC2005 [ -d "$1" ] && echo "$(cd "$1" && pwd)" || echo "$(cd "$(dirname "$1")" && pwd)/$(basename "$1")" } _get_curr_stable() { local stable="${GIMME_VERSION_PREFIX}/stable" if _file_older_than_secs "${stable}" 86400; then _update_stable "${stable}" fi cat "${stable}" } _get_old_stable() { local oldstable="${GIMME_VERSION_PREFIX}/oldstable" if _file_older_than_secs "${oldstable}" 86400; then _update_oldstable "${oldstable}" fi cat "${oldstable}" } _update_stable() { local stable="${1}" local url="https://golang.org/VERSION?m=text" _do_curl "${url}" "${stable}" sed -i.old -e 's/^go\(.*\)/\1/' "${stable}" rm -f "${stable}.old" } _update_oldstable() { local oldstable="${1}" local oldstable_x oldstable_x=$(_get_curr_stable | awk -F. '{ $2--; print $1 "." $2 "." "x" }') _resolve_version "${oldstable_x}" >"${oldstable}" } _last_mod_timestamp() { local filename="${1}" case "${GIMME_HOSTOS}" in darwin | *bsd) stat -f %m "${filename}" ;; linux) stat -c %Y "${filename}" ;; esac } _file_older_than_secs() { local file="${1}" local age_secs="${2}" local ts # if the file does not exist, we return true, as the cache needs updating ts="$(_last_mod_timestamp "${file}" 2>/dev/null)" || return 0 ((($(date +%s) - ts) > age_secs)) } _assert_version_given() { # By the time we're called, aliases such as "stable" must have been resolved # but we could be a reference in git. # # Versions can include suffices such as in "1.8beta2", so our assumption is that # there will always be a minor present; the first public release was "1.0" so # we assume "2.0" not "2". if [[ -z "${GIMME_GO_VERSION}" ]]; then echo >&2 'error: no GIMME_GO_VERSION supplied' echo >&2 " ex: GIMME_GO_VERSION=1.4.1 ${0} ${*}" echo >&2 " ex: ${0} 1.4.1 ${*}" ${ASSERT_ABORT:-exit} 1 fi # Note: _resolve_version calls back to us (_assert_version_given), but # only for cases where the version does not end with .x, so this should # be safe. # This should be untangled. PRs accepted, good starter project. if [[ "${GIMME_GO_VERSION}" == *.x ]]; then GIMME_GO_VERSION="$(_resolve_version "${GIMME_GO_VERSION}")" || ${ASSERT_ABORT:-exit} 1 fi if [[ "${GIMME_GO_VERSION}" == +([[:digit:]]).+([[:digit:]])* ]]; then return 0 fi # Here we resolve symbolic references. If we don't, then we get some # random git tag name being accepted as valid and then we try to # curl garbage from upstream. if [[ "${GIMME_TYPE}" == "auto" || "${GIMME_TYPE}" == "git" ]]; then local git_dir="${GIMME_VERSION_PREFIX}/go" local resolved_sha _fetch "${git_dir}" if resolved_sha="$(_checkout "${GIMME_GO_VERSION}" "${git_dir}")"; then if [[ -n "${resolved_sha}" ]]; then # Break our normal silence, this one really needs to be seen on stderr # always; auditability and knowing what version of Go you got wins. warn "resolved '${GIMME_GO_VERSION}' to '${resolved_sha}'" GIMME_GO_VERSION="${resolved_sha}" fi return 0 fi fi echo >&2 'error: GIMME_GO_VERSION not recognized as valid' echo >&2 " got: ${GIMME_GO_VERSION}" ${ASSERT_ABORT:-exit} 1 } _exclude_from_backups() { # Please avoid anything which requires elevated privileges or is obnoxious # enough to offend the invoker case "${GIMME_HOSTOS}" in darwin) # Darwin: Time Machine is "standard", we can add others. The default # mechanism is sticky, as an attribute on the dir, requires no # privileges, is idempotent (and doesn't support -- to end flags). tmutil addexclusion "$@" ;; esac } _versint() { IFS=" " read -r -a args <<<"${1//[^0-9]/ }" printf '1%03d%03d%03d%03d' "${args[@]}" } _to_goarch() { case "${1}" in aarch64) echo "arm64" ;; *) echo "${1}" ;; esac } : "${GIMME_OS:=$(uname -s | tr '[:upper:]' '[:lower:]')}" : "${GIMME_HOSTOS:=$(uname -s | tr '[:upper:]' '[:lower:]')}" : "${GIMME_ARCH:=$(_to_goarch "$(uname -m)")}" : "${GIMME_HOSTARCH:=$(_to_goarch "$(uname -m)")}" : "${GIMME_ENV_PREFIX:=${HOME}/.gimme/envs}" : "${GIMME_VERSION_PREFIX:=${HOME}/.gimme/versions}" : "${GIMME_TMP:=${TMPDIR:-/tmp}/gimme}" : "${GIMME_GO_GIT_REMOTE:=https://github.com/golang/go.git}" : "${GIMME_TYPE:=auto}" # 'auto', 'binary', 'source', or 'git' : "${GIMME_BINARY_OSX:=osx10.8}" : "${GIMME_DOWNLOAD_BASE:=https://dl.google.com/go}" : "${GIMME_LIST_KNOWN:=https://golang.org/dl}" : "${GIMME_KNOWN_CACHE_MAX:=10800}" # The version prefix must be an absolute path case "${GIMME_VERSION_PREFIX}" in /*) true ;; *) echo >&2 " Fixing GIMME_VERSION_PREFIX from relative: $GIMME_VERSION_PREFIX" GIMME_VERSION_PREFIX="$(pwd)/${GIMME_VERSION_PREFIX}" echo >&2 " to: $GIMME_VERSION_PREFIX" ;; esac case "${GIMME_OS}" in mingw* | msys_nt*) # Minimalist GNU for Windows GIMME_OS='windows' if [ "${GIMME_ARCH}" = 'i686' ]; then GIMME_ARCH="386" else GIMME_ARCH="amd64" fi ;; esac force_install=0 force_known_update=0 while [[ $# -gt 0 ]]; do case "${1}" in -h | --help | help | wat) _old_ifs="$IFS" IFS=';' awk '/^#\+ / { sub(/^#\+ /, "", $0) ; sub(/-$/, "", $0) ; print $0 }' "$0" | while read -r line; do eval "echo \"$line\"" done IFS="$_old_ifs" exit 0 ;; -V | --version | version) echo "${GIMME_VERSION}" exit 0 ;; -r | --resolve | resolve) # The normal mkdir of versions is below; we don't want to move it up # to where we create files just if asked our version; thus # _resolve_version has to mkdir the versions dir itself. if [[ $# -ge 2 ]]; then _resolve_version "${2}" elif [[ -n "${GIMME_GO_VERSION:-}" ]]; then _resolve_version "${GIMME_GO_VERSION}" else die "resolve must be given a version to resolve" fi exit $? ;; -l | --list | list) _list_versions exit 0 ;; -k | --known | known) _list_known exit 0 ;; -f | --force | force) force_install=1 ;; --force-known-update | force-known-update) force_known_update=1 ;; -i | install) true # ignore a dummy argument ;; *) break ;; esac shift done if [[ -n "${1}" ]]; then GIMME_GO_VERSION="${1}" fi if [[ -n "${2}" ]]; then GIMME_VERSION_PREFIX="${2}" fi case "${GIMME_ARCH}" in x86_64) GIMME_ARCH=amd64 ;; x86) GIMME_ARCH=386 ;; arm64) if [[ "${GIMME_GO_VERSION}" != master && "$(_versint "${GIMME_GO_VERSION}")" < "$(_versint 1.5)" ]]; then echo >&2 "error: ${GIMME_ARCH} is not supported by this go version" echo >&2 "try go1.5 or newer" exit 1 fi if [[ "${GIMME_HOSTOS}" == "linux" && "${GIMME_HOSTARCH}" != "${GIMME_ARCH}" ]]; then : "${GIMME_CC_FOR_TARGET:="aarch64-linux-gnu-gcc"}" fi ;; arm*) GIMME_ARCH=arm ;; esac case "${GIMME_HOSTARCH}" in x86_64) GIMME_HOSTARCH=amd64 ;; x86) GIMME_HOSTARCH=386 ;; arm64) ;; arm*) GIMME_HOSTARCH=arm ;; esac case "${GIMME_GO_VERSION}" in stable) GIMME_GO_VERSION=$(_get_curr_stable) ;; oldstable) GIMME_GO_VERSION=$(_get_old_stable) ;; esac _assert_version_given "$@" ((force_install)) && _wipe_version "${GIMME_GO_VERSION}" unset GOARCH unset GOBIN unset GOOS unset GOPATH unset GOROOT unset CGO_ENABLED unset CC_FOR_TARGET # GO111MODULE breaks build of Go itself unset GO111MODULE mkdir -p "${GIMME_VERSION_PREFIX}" "${GIMME_ENV_PREFIX}" # The envs dir stays small and provides a record of what had been installed # whereas the versions dir grows by hundreds of MB per version and is not # intended to support local modifications (as that subverts the point of gimme) # _and_ is a cache, so we're unilaterally declaring that the contents of # the versions dir should be excluded from system backups. _exclude_from_backups "${GIMME_VERSION_PREFIX}" GIMME_VERSION_PREFIX="$(_realpath "${GIMME_VERSION_PREFIX}")" GIMME_ENV_PREFIX="$(_realpath "${GIMME_ENV_PREFIX}")" if ! case "${GIMME_TYPE}" in binary) _try_existing binary || _try_binary "${GIMME_GO_VERSION}" "${GIMME_ARCH}" ;; source) _try_existing source || _try_source || _try_git ;; git) _try_git ;; auto) _try_existing || _try_binary "${GIMME_GO_VERSION}" "${GIMME_ARCH}" || _try_source || _try_git ;; *) echo >&2 "I don't know how to '${GIMME_TYPE}'." echo >&2 " Try 'auto', 'binary', 'source', or 'git'." exit 1 ;; esac; then echo >&2 "I don't have any idea what to do with '${GIMME_GO_VERSION}'." echo >&2 " (using download type '${GIMME_TYPE}')" exit 1 fi kind-0.27.0/hack/tools/000077500000000000000000000000001475376161000146055ustar00rootroot00000000000000kind-0.27.0/hack/tools/.golangci.yml000066400000000000000000000012631475376161000171730ustar00rootroot00000000000000run: timeout: 3m linters: disable-all: true enable: - errcheck - gosimple - govet - ineffassign - staticcheck - typecheck - gochecknoinits - gofmt - revive # replaces golint for now - misspell - exportloopref - unparam linters-settings: staticcheck: checks: - all issues: exclude-rules: # this requires renaming all unused parameters, we'd rather leave a preferred # placeholder name in place when implementing an interface etc. # we can revisit this later, right now it's generating a lot of new warnings # after upgrading golangci-lint - text: "^unused-parameter: .*" linters: - revive kind-0.27.0/hack/tools/README.md000066400000000000000000000001701475376161000160620ustar00rootroot00000000000000This directory contains a stub go module used to track version of development tools like the Kubernetes code generators.kind-0.27.0/hack/tools/boilerplate.go.txt000066400000000000000000000010721475376161000202540ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ kind-0.27.0/hack/tools/go.mod000066400000000000000000000234201475376161000157140ustar00rootroot00000000000000module sigs.k8s.io/kind/hack/tools go 1.23 require ( github.com/golangci/golangci-lint v1.62.2 gotest.tools/gotestsum v1.12.0 k8s.io/code-generator v0.31.0 ) require ( 4d63.com/gocheckcompilerdirectives v1.2.1 // indirect 4d63.com/gochecknoglobals v0.2.1 // indirect github.com/4meepo/tagalign v1.3.4 // indirect github.com/Abirdcfly/dupword v0.1.3 // indirect github.com/Antonboom/errname v1.0.0 // indirect github.com/Antonboom/nilnil v1.0.0 // indirect github.com/Antonboom/testifylint v1.5.2 // indirect github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect github.com/Crocmagnon/fatcontext v0.5.3 // indirect github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0 // indirect github.com/Masterminds/semver/v3 v3.3.0 // indirect github.com/OpenPeeDeeP/depguard/v2 v2.2.0 // indirect github.com/alecthomas/go-check-sumtype v0.2.0 // indirect github.com/alexkohler/nakedret/v2 v2.0.5 // indirect github.com/alexkohler/prealloc v1.0.0 // indirect github.com/alingse/asasalint v0.0.11 // indirect github.com/ashanbrown/forbidigo v1.6.0 // indirect github.com/ashanbrown/makezero v1.1.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bitfield/gotestdox v0.2.2 // indirect github.com/bkielbasa/cyclop v1.2.3 // indirect github.com/blizzy78/varnamelen v0.8.0 // indirect github.com/bombsimon/wsl/v4 v4.4.1 // indirect github.com/breml/bidichk v0.3.2 // indirect github.com/breml/errchkjson v0.4.0 // indirect github.com/butuzov/ireturn v0.3.0 // indirect github.com/butuzov/mirror v1.2.0 // indirect github.com/catenacyber/perfsprint v0.7.1 // indirect github.com/ccojocar/zxcvbn-go v1.0.2 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/charithe/durationcheck v0.0.10 // indirect github.com/chavacava/garif v0.1.0 // indirect github.com/ckaznocha/intrange v0.2.1 // indirect github.com/curioswitch/go-reassign v0.2.0 // indirect github.com/daixiang0/gci v0.13.5 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/denis-tingaikin/go-header v0.5.0 // indirect github.com/dnephin/pflag v1.0.7 // indirect github.com/ettle/strcase v0.2.0 // indirect github.com/fatih/color v1.18.0 // indirect github.com/fatih/structtag v1.2.0 // indirect github.com/firefart/nonamedreturns v1.0.5 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fzipp/gocyclo v0.6.0 // indirect github.com/ghostiam/protogetter v0.3.8 // indirect github.com/go-critic/go-critic v0.11.5 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-toolsmith/astcast v1.1.0 // indirect github.com/go-toolsmith/astcopy v1.1.0 // indirect github.com/go-toolsmith/astequal v1.2.0 // indirect github.com/go-toolsmith/astfmt v1.1.0 // indirect github.com/go-toolsmith/astp v1.1.0 // indirect github.com/go-toolsmith/strparse v1.1.0 // indirect github.com/go-toolsmith/typep v1.1.0 // indirect github.com/go-viper/mapstructure/v2 v2.2.1 // indirect github.com/go-xmlfmt/xmlfmt v1.1.2 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/gofrs/flock v0.12.1 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a // indirect github.com/golangci/go-printf-func-name v0.1.0 // indirect github.com/golangci/gofmt v0.0.0-20240816233607-d8596aa466a9 // indirect github.com/golangci/misspell v0.6.0 // indirect github.com/golangci/modinfo v0.3.4 // indirect github.com/golangci/plugin-module-register v0.1.1 // indirect github.com/golangci/revgrep v0.5.3 // indirect github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/gordonklaus/ineffassign v0.1.0 // indirect github.com/gostaticanalysis/analysisutil v0.7.1 // indirect github.com/gostaticanalysis/comment v1.4.2 // indirect github.com/gostaticanalysis/forcetypeassert v0.1.0 // indirect github.com/gostaticanalysis/nilerr v0.1.1 // indirect github.com/hashicorp/go-version v1.7.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/hexops/gotextdiff v1.0.3 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jgautheron/goconst v1.7.1 // indirect github.com/jingyugao/rowserrcheck v1.1.1 // indirect github.com/jjti/go-spancheck v0.6.2 // indirect github.com/julz/importas v0.1.0 // indirect github.com/karamaru-alpha/copyloopvar v1.1.0 // indirect github.com/kisielk/errcheck v1.8.0 // indirect github.com/kkHAIKE/contextcheck v1.1.5 // indirect github.com/kulti/thelper v0.6.3 // indirect github.com/kunwardeep/paralleltest v1.0.10 // indirect github.com/kyoh86/exportloopref v0.1.11 // indirect github.com/lasiar/canonicalheader v1.1.2 // indirect github.com/ldez/gomoddirectives v0.2.4 // indirect github.com/ldez/tagliatelle v0.5.0 // indirect github.com/leonklingele/grouper v1.1.2 // indirect github.com/macabu/inamedparam v0.1.3 // indirect github.com/magiconair/properties v1.8.6 // indirect github.com/maratori/testableexamples v1.0.0 // indirect github.com/maratori/testpackage v1.1.1 // indirect github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/mgechev/revive v1.5.1 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/moricho/tparallel v0.3.2 // indirect github.com/nakabonne/nestif v0.3.1 // indirect github.com/nishanths/exhaustive v0.12.0 // indirect github.com/nishanths/predeclared v0.2.2 // indirect github.com/nunnatsa/ginkgolinter v0.18.3 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/polyfloyd/go-errorlint v1.7.0 // indirect github.com/prometheus/client_golang v1.12.1 // indirect github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.32.1 // indirect github.com/prometheus/procfs v0.7.3 // indirect github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1 // indirect github.com/quasilyte/go-ruleguard/dsl v0.3.22 // indirect github.com/quasilyte/gogrep v0.5.0 // indirect github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect github.com/raeperd/recvcheck v0.1.2 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/rogpeppe/go-internal v1.13.1 // indirect github.com/ryancurrah/gomodguard v1.3.5 // indirect github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect github.com/sanposhiho/wastedassign/v2 v2.0.7 // indirect github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect github.com/sashamelentyev/interfacebloat v1.1.0 // indirect github.com/sashamelentyev/usestdlibvars v1.27.0 // indirect github.com/securego/gosec/v2 v2.21.4 // indirect github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/sivchari/containedctx v1.0.3 // indirect github.com/sivchari/tenv v1.12.1 // indirect github.com/sonatard/noctx v0.1.0 // indirect github.com/sourcegraph/go-diff v0.7.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/spf13/cobra v1.8.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/viper v1.12.0 // indirect github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect github.com/stbenjam/no-sprintf-host-port v0.1.1 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/stretchr/testify v1.10.0 // indirect github.com/subosito/gotenv v1.4.1 // indirect github.com/tdakkota/asciicheck v0.2.0 // indirect github.com/tetafro/godot v1.4.18 // indirect github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966 // indirect github.com/timonwong/loggercheck v0.10.1 // indirect github.com/tomarrell/wrapcheck/v2 v2.9.0 // indirect github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect github.com/ultraware/funlen v0.1.0 // indirect github.com/ultraware/whitespace v0.1.1 // indirect github.com/uudashr/gocognit v1.1.3 // indirect github.com/uudashr/iface v1.2.1 // indirect github.com/xen0n/gosmopolitan v1.2.2 // indirect github.com/yagipy/maintidx v1.0.0 // indirect github.com/yeya24/promlinter v0.3.0 // indirect github.com/ykadowak/zerologlint v0.1.5 // indirect gitlab.com/bosi/decorder v0.4.2 // indirect go-simpler.org/musttag v0.13.0 // indirect go-simpler.org/sloglint v0.7.2 // indirect go.uber.org/atomic v1.7.0 // indirect go.uber.org/automaxprocs v1.6.0 // indirect go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.24.0 // indirect golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect golang.org/x/exp/typeparams v0.0.0-20241108190413-2d47ceb2692f // indirect golang.org/x/mod v0.22.0 // indirect golang.org/x/sync v0.9.0 // indirect golang.org/x/sys v0.27.0 // indirect golang.org/x/term v0.18.0 // indirect golang.org/x/text v0.18.0 // indirect golang.org/x/tools v0.27.0 // indirect google.golang.org/protobuf v1.34.2 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect honnef.co/go/tools v0.5.1 // indirect k8s.io/gengo/v2 v2.0.0-20240228010128-51d4e06bde70 // indirect k8s.io/klog/v2 v2.130.1 // indirect mvdan.cc/gofumpt v0.7.0 // indirect mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f // indirect ) kind-0.27.0/hack/tools/go.sum000066400000000000000000002737411475376161000157560ustar00rootroot000000000000004d63.com/gocheckcompilerdirectives v1.2.1 h1:AHcMYuw56NPjq/2y615IGg2kYkBdTvOaojYCBcRE7MA= 4d63.com/gocheckcompilerdirectives v1.2.1/go.mod h1:yjDJSxmDTtIHHCqX0ufRYZDL6vQtMG7tJdKVeWwsqvs= 4d63.com/gochecknoglobals v0.2.1 h1:1eiorGsgHOFOuoOiJDy2psSrQbRdIHrlge0IJIkUgDc= 4d63.com/gochecknoglobals v0.2.1/go.mod h1:KRE8wtJB3CXCsb1xy421JfTHIIbmT3U5ruxw2Qu8fSU= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/4meepo/tagalign v1.3.4 h1:P51VcvBnf04YkHzjfclN6BbsopfJR5rxs1n+5zHt+w8= github.com/4meepo/tagalign v1.3.4/go.mod h1:M+pnkHH2vG8+qhE5bVc/zeP7HS/j910Fwa9TUSyZVI0= github.com/Abirdcfly/dupword v0.1.3 h1:9Pa1NuAsZvpFPi9Pqkd93I7LIYRURj+A//dFd5tgBeE= github.com/Abirdcfly/dupword v0.1.3/go.mod h1:8VbB2t7e10KRNdwTVoxdBaxla6avbhGzb8sCTygUMhw= github.com/Antonboom/errname v1.0.0 h1:oJOOWR07vS1kRusl6YRSlat7HFnb3mSfMl6sDMRoTBA= github.com/Antonboom/errname v1.0.0/go.mod h1:gMOBFzK/vrTiXN9Oh+HFs+e6Ndl0eTFbtsRTSRdXyGI= github.com/Antonboom/nilnil v1.0.0 h1:n+v+B12dsE5tbAqRODXmEKfZv9j2KcTBrp+LkoM4HZk= github.com/Antonboom/nilnil v1.0.0/go.mod h1:fDJ1FSFoLN6yoG65ANb1WihItf6qt9PJVTn/s2IrcII= github.com/Antonboom/testifylint v1.5.2 h1:4s3Xhuv5AvdIgbd8wOOEeo0uZG7PbDKQyKY5lGoQazk= github.com/Antonboom/testifylint v1.5.2/go.mod h1:vxy8VJ0bc6NavlYqjZfmp6EfqXMtBgQ4+mhCojwC1P8= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs= github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Crocmagnon/fatcontext v0.5.3 h1:zCh/wjc9oyeF+Gmp+V60wetm8ph2tlsxocgg/J0hOps= github.com/Crocmagnon/fatcontext v0.5.3/go.mod h1:XoCQYY1J+XTfyv74qLXvNw4xFunr3L1wkopIIKG7wGM= github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 h1:sHglBQTwgx+rWPdisA5ynNEsoARbiCBOyGcJM4/OzsM= github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0 h1:/fTUt5vmbkAcMBt4YQiuC23cV0kEsN1MVMNqeOW43cU= github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0/go.mod h1:ONJg5sxcbsdQQ4pOW8TGdTidT2TMAUy/2Xhr8mrYaao= github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0= github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/OpenPeeDeeP/depguard/v2 v2.2.0 h1:vDfG60vDtIuf0MEOhmLlLLSzqaRM8EMcgJPdp74zmpA= github.com/OpenPeeDeeP/depguard/v2 v2.2.0/go.mod h1:CIzddKRvLBC4Au5aYP/i3nyaWQ+ClszLIuVocRiCYFQ= github.com/alecthomas/assert/v2 v2.2.2 h1:Z/iVC0xZfWTaFNE6bA3z07T86hd45Xe2eLt6WVy2bbk= github.com/alecthomas/assert/v2 v2.2.2/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ= github.com/alecthomas/go-check-sumtype v0.2.0 h1:Bo+e4DFf3rs7ME9w/0SU/g6nmzJaphduP8Cjiz0gbwY= github.com/alecthomas/go-check-sumtype v0.2.0/go.mod h1:WyYPfhfkdhyrdaligV6svFopZV8Lqdzn5pyVBaV6jhQ= github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk= github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/alexkohler/nakedret/v2 v2.0.5 h1:fP5qLgtwbx9EJE8dGEERT02YwS8En4r9nnZ71RK+EVU= github.com/alexkohler/nakedret/v2 v2.0.5/go.mod h1:bF5i0zF2Wo2o4X4USt9ntUWve6JbFv02Ff4vlkmS/VU= github.com/alexkohler/prealloc v1.0.0 h1:Hbq0/3fJPQhNkN0dR95AVrr6R7tou91y0uHG5pOcUuw= github.com/alexkohler/prealloc v1.0.0/go.mod h1:VetnK3dIgFBBKmg0YnD9F9x6Icjd+9cvfHR56wJVlKE= github.com/alingse/asasalint v0.0.11 h1:SFwnQXJ49Kx/1GghOFz1XGqHYKp21Kq1nHad/0WQRnw= github.com/alingse/asasalint v0.0.11/go.mod h1:nCaoMhw7a9kSJObvQyVzNTPBDbNpdocqrSP7t/cW5+I= github.com/ashanbrown/forbidigo v1.6.0 h1:D3aewfM37Yb3pxHujIPSpTf6oQk9sc9WZi8gerOIVIY= github.com/ashanbrown/forbidigo v1.6.0/go.mod h1:Y8j9jy9ZYAEHXdu723cUlraTqbzjKF1MUyfOKL+AjcU= github.com/ashanbrown/makezero v1.1.1 h1:iCQ87C0V0vSyO+M9E/FZYbu65auqH0lnsOkf5FcB28s= github.com/ashanbrown/makezero v1.1.1/go.mod h1:i1bJLCRSCHOcOa9Y6MyF2FTfMZMFdHvxKHxgO5Z1axI= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bitfield/gotestdox v0.2.2 h1:x6RcPAbBbErKLnapz1QeAlf3ospg8efBsedU93CDsnE= github.com/bitfield/gotestdox v0.2.2/go.mod h1:D+gwtS0urjBrzguAkTM2wodsTQYFHdpx8eqRJ3N+9pY= github.com/bkielbasa/cyclop v1.2.3 h1:faIVMIGDIANuGPWH031CZJTi2ymOQBULs9H21HSMa5w= github.com/bkielbasa/cyclop v1.2.3/go.mod h1:kHTwA9Q0uZqOADdupvcFJQtp/ksSnytRMe8ztxG8Fuo= github.com/blizzy78/varnamelen v0.8.0 h1:oqSblyuQvFsW1hbBHh1zfwrKe3kcSj0rnXkKzsQ089M= github.com/blizzy78/varnamelen v0.8.0/go.mod h1:V9TzQZ4fLJ1DSrjVDfl89H7aMnTvKkApdHeyESmyR7k= github.com/bombsimon/wsl/v4 v4.4.1 h1:jfUaCkN+aUpobrMO24zwyAMwMAV5eSziCkOKEauOLdw= github.com/bombsimon/wsl/v4 v4.4.1/go.mod h1:Xu/kDxGZTofQcDGCtQe9KCzhHphIe0fDuyWTxER9Feo= github.com/breml/bidichk v0.3.2 h1:xV4flJ9V5xWTqxL+/PMFF6dtJPvZLPsyixAoPe8BGJs= github.com/breml/bidichk v0.3.2/go.mod h1:VzFLBxuYtT23z5+iVkamXO386OB+/sVwZOpIj6zXGos= github.com/breml/errchkjson v0.4.0 h1:gftf6uWZMtIa/Is3XJgibewBm2ksAQSY/kABDNFTAdk= github.com/breml/errchkjson v0.4.0/go.mod h1:AuBOSTHyLSaaAFlWsRSuRBIroCh3eh7ZHh5YeelDIk8= github.com/butuzov/ireturn v0.3.0 h1:hTjMqWw3y5JC3kpnC5vXmFJAWI/m31jaCYQqzkS6PL0= github.com/butuzov/ireturn v0.3.0/go.mod h1:A09nIiwiqzN/IoVo9ogpa0Hzi9fex1kd9PSD6edP5ZA= github.com/butuzov/mirror v1.2.0 h1:9YVK1qIjNspaqWutSv8gsge2e/Xpq1eqEkslEUHy5cs= github.com/butuzov/mirror v1.2.0/go.mod h1:DqZZDtzm42wIAIyHXeN8W/qb1EPlb9Qn/if9icBOpdQ= github.com/catenacyber/perfsprint v0.7.1 h1:PGW5G/Kxn+YrN04cRAZKC+ZuvlVwolYMrIyyTJ/rMmc= github.com/catenacyber/perfsprint v0.7.1/go.mod h1:/wclWYompEyjUD2FuIIDVKNkqz7IgBIWXIH3V0Zol50= github.com/ccojocar/zxcvbn-go v1.0.2 h1:na/czXU8RrhXO4EZme6eQJLR4PzcGsahsBOAwU6I3Vg= github.com/ccojocar/zxcvbn-go v1.0.2/go.mod h1:g1qkXtUSvHP8lhHp5GrSmTz6uWALGRMQdw6Qnz/hi60= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/charithe/durationcheck v0.0.10 h1:wgw73BiocdBDQPik+zcEoBG/ob8uyBHf2iyoHGPf5w4= github.com/charithe/durationcheck v0.0.10/go.mod h1:bCWXb7gYRysD1CU3C+u4ceO49LoGOY1C1L6uouGNreQ= github.com/chavacava/garif v0.1.0 h1:2JHa3hbYf5D9dsgseMKAmc/MZ109otzgNFk5s87H9Pc= github.com/chavacava/garif v0.1.0/go.mod h1:XMyYCkEL58DF0oyW4qDjjnPWONs2HBqYKI+UIPD+Gww= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/ckaznocha/intrange v0.2.1 h1:M07spnNEQoALOJhwrImSrJLaxwuiQK+hA2DeajBlwYk= github.com/ckaznocha/intrange v0.2.1/go.mod h1:7NEhVyf8fzZO5Ds7CRaqPEm52Ut83hsTiL5zbER/HYk= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/curioswitch/go-reassign v0.2.0 h1:G9UZyOcpk/d7Gd6mqYgd8XYWFMw/znxwGDUstnC9DIo= github.com/curioswitch/go-reassign v0.2.0/go.mod h1:x6OpXuWvgfQaMGks2BZybTngWjT84hqJfKoO8Tt/Roc= github.com/daixiang0/gci v0.13.5 h1:kThgmH1yBmZSBCh1EJVxQ7JsHpm5Oms0AMed/0LaH4c= github.com/daixiang0/gci v0.13.5/go.mod h1:12etP2OniiIdP4q+kjUGrC/rUagga7ODbqsom5Eo5Yk= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/denis-tingaikin/go-header v0.5.0 h1:SRdnP5ZKvcO9KKRP1KJrhFR3RrlGuD+42t4429eC9k8= github.com/denis-tingaikin/go-header v0.5.0/go.mod h1:mMenU5bWrok6Wl2UsZjy+1okegmwQ3UgWl4V1D8gjlY= github.com/dnephin/pflag v1.0.7 h1:oxONGlWxhmUct0YzKTgrpQv9AUA1wtPBn7zuSjJqptk= github.com/dnephin/pflag v1.0.7/go.mod h1:uxE91IoWURlOiTUIA8Mq5ZZkAv3dPUfZNaT80Zm7OQE= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/ettle/strcase v0.2.0 h1:fGNiVF21fHXpX1niBgk0aROov1LagYsOwV/xqKDKR/Q= github.com/ettle/strcase v0.2.0/go.mod h1:DajmHElDSaX76ITe3/VHVyMin4LWSJN5Z909Wp+ED1A= github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= github.com/firefart/nonamedreturns v1.0.5 h1:tM+Me2ZaXs8tfdDw3X6DOX++wMCOqzYUho6tUTYIdRA= github.com/firefart/nonamedreturns v1.0.5/go.mod h1:gHJjDqhGM4WyPt639SOZs+G89Ko7QKH5R5BhnO6xJhw= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= github.com/ghostiam/protogetter v0.3.8 h1:LYcXbYvybUyTIxN2Mj9h6rHrDZBDwZloPoKctWrFyJY= github.com/ghostiam/protogetter v0.3.8/go.mod h1:WZ0nw9pfzsgxuRsPOFQomgDVSWtDLJRfQJEhsGbmQMA= github.com/go-critic/go-critic v0.11.5 h1:TkDTOn5v7EEngMxu8KbuFqFR43USaaH8XRJLz1jhVYA= github.com/go-critic/go-critic v0.11.5/go.mod h1:wu6U7ny9PiaHaZHcvMDmdysMqvDem162Rh3zWTrqk8M= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-toolsmith/astcast v1.1.0 h1:+JN9xZV1A+Re+95pgnMgDboWNVnIMMQXwfBwLRPgSC8= github.com/go-toolsmith/astcast v1.1.0/go.mod h1:qdcuFWeGGS2xX5bLM/c3U9lewg7+Zu4mr+xPwZIB4ZU= github.com/go-toolsmith/astcopy v1.1.0 h1:YGwBN0WM+ekI/6SS6+52zLDEf8Yvp3n2seZITCUBt5s= github.com/go-toolsmith/astcopy v1.1.0/go.mod h1:hXM6gan18VA1T/daUEHCFcYiW8Ai1tIwIzHY6srfEAw= github.com/go-toolsmith/astequal v1.0.3/go.mod h1:9Ai4UglvtR+4up+bAD4+hCj7iTo4m/OXVTSLnCyTAx4= github.com/go-toolsmith/astequal v1.1.0/go.mod h1:sedf7VIdCL22LD8qIvv7Nn9MuWJruQA/ysswh64lffQ= github.com/go-toolsmith/astequal v1.2.0 h1:3Fs3CYZ1k9Vo4FzFhwwewC3CHISHDnVUPC4x0bI2+Cw= github.com/go-toolsmith/astequal v1.2.0/go.mod h1:c8NZ3+kSFtFY/8lPso4v8LuJjdJiUFVnSuU3s0qrrDY= github.com/go-toolsmith/astfmt v1.1.0 h1:iJVPDPp6/7AaeLJEruMsBUlOYCmvg0MoCfJprsOmcco= github.com/go-toolsmith/astfmt v1.1.0/go.mod h1:OrcLlRwu0CuiIBp/8b5PYF9ktGVZUjlNMV634mhwuQ4= github.com/go-toolsmith/astp v1.1.0 h1:dXPuCl6u2llURjdPLLDxJeZInAeZ0/eZwFJmqZMnpQA= github.com/go-toolsmith/astp v1.1.0/go.mod h1:0T1xFGz9hicKs8Z5MfAqSUitoUYS30pDMsRVIDHs8CA= github.com/go-toolsmith/pkgload v1.2.2 h1:0CtmHq/02QhxcF7E9N5LIFcYFsMR5rdovfqTtRKkgIk= github.com/go-toolsmith/pkgload v1.2.2/go.mod h1:R2hxLNRKuAsiXCo2i5J6ZQPhnPMOVtU+f0arbFPWCus= github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8= github.com/go-toolsmith/strparse v1.1.0 h1:GAioeZUK9TGxnLS+qfdqNbA4z0SSm5zVNtCQiyP2Bvw= github.com/go-toolsmith/strparse v1.1.0/go.mod h1:7ksGy58fsaQkGQlY8WVoBFNyEPMGuJin1rfoPS4lBSQ= github.com/go-toolsmith/typep v1.1.0 h1:fIRYDyF+JywLfqzyhdiHzRop/GQDxxNhLGQ6gFUNHus= github.com/go-toolsmith/typep v1.1.0/go.mod h1:fVIw+7zjdsMxDA3ITWnH1yOiw1rnTQKCsF/sk2H/qig= github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/go-xmlfmt/xmlfmt v1.1.2 h1:Nea7b4icn8s57fTx1M5AI4qQT5HEM3rVUO8MuE6g80U= github.com/go-xmlfmt/xmlfmt v1.1.2/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a h1:w8hkcTqaFpzKqonE9uMCefW1WDie15eSP/4MssdenaM= github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= github.com/golangci/go-printf-func-name v0.1.0 h1:dVokQP+NMTO7jwO4bwsRwLWeudOVUPPyAKJuzv8pEJU= github.com/golangci/go-printf-func-name v0.1.0/go.mod h1:wqhWFH5mUdJQhweRnldEywnR5021wTdZSNgwYceV14s= github.com/golangci/gofmt v0.0.0-20240816233607-d8596aa466a9 h1:/1322Qns6BtQxUZDTAT4SdcoxknUki7IAoK4SAXr8ME= github.com/golangci/gofmt v0.0.0-20240816233607-d8596aa466a9/go.mod h1:Oesb/0uFAyWoaw1U1qS5zyjCg5NP9C9iwjnI4tIsXEE= github.com/golangci/golangci-lint v1.62.2 h1:b8K5K9PN+rZN1+mKLtsZHz2XXS9aYKzQ9i25x3Qnxxw= github.com/golangci/golangci-lint v1.62.2/go.mod h1:ILWWyeFUrctpHVGMa1dg2xZPKoMUTc5OIMgW7HZr34g= github.com/golangci/misspell v0.6.0 h1:JCle2HUTNWirNlDIAUO44hUsKhOFqGPoC4LZxlaSXDs= github.com/golangci/misspell v0.6.0/go.mod h1:keMNyY6R9isGaSAu+4Q8NMBwMPkh15Gtc8UCVoDtAWo= github.com/golangci/modinfo v0.3.4 h1:oU5huX3fbxqQXdfspamej74DFX0kyGLkw1ppvXoJ8GA= github.com/golangci/modinfo v0.3.4/go.mod h1:wytF1M5xl9u0ij8YSvhkEVPP3M5Mc7XLl1pxH3B2aUM= github.com/golangci/plugin-module-register v0.1.1 h1:TCmesur25LnyJkpsVrupv1Cdzo+2f7zX0H6Jkw1Ol6c= github.com/golangci/plugin-module-register v0.1.1/go.mod h1:TTpqoB6KkwOJMV8u7+NyXMrkwwESJLOkfl9TxR1DGFc= github.com/golangci/revgrep v0.5.3 h1:3tL7c1XBMtWHHqVpS5ChmiAAoe4PF/d5+ULzV9sLAzs= github.com/golangci/revgrep v0.5.3/go.mod h1:U4R/s9dlXZsg8uJmaR1GrloUr14D7qDl8gi2iPXJH8k= github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed h1:IURFTjxeTfNFP0hTEi1YKjB/ub8zkpaOqFFMApi2EAs= github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed/go.mod h1:XLXN8bNw4CGRPaqgl3bv/lhz7bsGPh4/xSaMTbo2vkQ= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5 h1:5iH8iuqE5apketRbSFBy+X1V0o+l+8NF1avt4HWl7cA= github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gordonklaus/ineffassign v0.1.0 h1:y2Gd/9I7MdY1oEIt+n+rowjBNDcLQq3RsH5hwJd0f9s= github.com/gordonklaus/ineffassign v0.1.0/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0= github.com/gostaticanalysis/analysisutil v0.7.1 h1:ZMCjoue3DtDWQ5WyU16YbjbQEQ3VuzwxALrpYd+HeKk= github.com/gostaticanalysis/analysisutil v0.7.1/go.mod h1:v21E3hY37WKMGSnbsw2S/ojApNWb6C1//mXO48CXbVc= github.com/gostaticanalysis/comment v1.4.1/go.mod h1:ih6ZxzTHLdadaiSnF5WY3dxUoXfXAlTaRzuaNDlSado= github.com/gostaticanalysis/comment v1.4.2 h1:hlnx5+S2fY9Zo9ePo4AhgYsYHbM2+eAv8m/s1JiCd6Q= github.com/gostaticanalysis/comment v1.4.2/go.mod h1:KLUTGDv6HOCotCH8h2erHKmpci2ZoR8VPu34YA2uzdM= github.com/gostaticanalysis/forcetypeassert v0.1.0 h1:6eUflI3DiGusXGK6X7cCcIgVCpZ2CiZ1Q7jl6ZxNV70= github.com/gostaticanalysis/forcetypeassert v0.1.0/go.mod h1:qZEedyP/sY1lTGV1uJ3VhWZ2mqag3IkWsDHVbplHXak= github.com/gostaticanalysis/nilerr v0.1.1 h1:ThE+hJP0fEp4zWLkWHWcRyI2Od0p7DlgYG3Uqrmrcpk= github.com/gostaticanalysis/nilerr v0.1.1/go.mod h1:wZYb6YI5YAxxq0i1+VJbY0s2YONW0HU0GPE3+5PWN4A= github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M= github.com/gostaticanalysis/testutil v0.4.0 h1:nhdCmubdmDF6VEatUNjgUZBJKWRqugoISdUv3PPQgHY= github.com/gostaticanalysis/testutil v0.4.0/go.mod h1:bLIoPefWXrRi/ssLFWX1dx7Repi5x3CuviD3dgAZaBU= github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jgautheron/goconst v1.7.1 h1:VpdAG7Ca7yvvJk5n8dMwQhfEZJh95kl/Hl9S1OI5Jkk= github.com/jgautheron/goconst v1.7.1/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4= github.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjzq7gFzUs= github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c= github.com/jjti/go-spancheck v0.6.2 h1:iYtoxqPMzHUPp7St+5yA8+cONdyXD3ug6KK15n7Pklk= github.com/jjti/go-spancheck v0.6.2/go.mod h1:+X7lvIrR5ZdUTkxFYqzJ0abr8Sb5LOo80uOhWNqIrYA= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/julz/importas v0.1.0 h1:F78HnrsjY3cR7j0etXy5+TU1Zuy7Xt08X/1aJnH5xXY= github.com/julz/importas v0.1.0/go.mod h1:oSFU2R4XK/P7kNBrnL/FEQlDGN1/6WoxXEjSSXO0DV0= github.com/karamaru-alpha/copyloopvar v1.1.0 h1:x7gNyKcC2vRBO1H2Mks5u1VxQtYvFiym7fCjIP8RPos= github.com/karamaru-alpha/copyloopvar v1.1.0/go.mod h1:u7CIfztblY0jZLOQZgH3oYsJzpC2A7S6u/lfgSXHy0k= github.com/kisielk/errcheck v1.8.0 h1:ZX/URYa7ilESY19ik/vBmCn6zdGQLxACwjAcWbHlYlg= github.com/kisielk/errcheck v1.8.0/go.mod h1:1kLL+jV4e+CFfueBmI1dSK2ADDyQnlrnrY/FqKluHJQ= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkHAIKE/contextcheck v1.1.5 h1:CdnJh63tcDe53vG+RebdpdXJTc9atMgGqdx8LXxiilg= github.com/kkHAIKE/contextcheck v1.1.5/go.mod h1:O930cpht4xb1YQpK+1+AgoM3mFsvxr7uyFptcnWTYUA= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kulti/thelper v0.6.3 h1:ElhKf+AlItIu+xGnI990no4cE2+XaSu1ULymV2Yulxs= github.com/kulti/thelper v0.6.3/go.mod h1:DsqKShOvP40epevkFrvIwkCMNYxMeTNjdWL4dqWHZ6I= github.com/kunwardeep/paralleltest v1.0.10 h1:wrodoaKYzS2mdNVnc4/w31YaXFtsc21PCTdvWJ/lDDs= github.com/kunwardeep/paralleltest v1.0.10/go.mod h1:2C7s65hONVqY7Q5Efj5aLzRCNLjw2h4eMc9EcypGjcY= github.com/kyoh86/exportloopref v0.1.11 h1:1Z0bcmTypkL3Q4k+IDHMWTcnCliEZcaPiIe0/ymEyhQ= github.com/kyoh86/exportloopref v0.1.11/go.mod h1:qkV4UF1zGl6EkF1ox8L5t9SwyeBAZ3qLMd6up458uqA= github.com/lasiar/canonicalheader v1.1.2 h1:vZ5uqwvDbyJCnMhmFYimgMZnJMjwljN5VGY0VKbMXb4= github.com/lasiar/canonicalheader v1.1.2/go.mod h1:qJCeLFS0G/QlLQ506T+Fk/fWMa2VmBUiEI2cuMK4djI= github.com/ldez/gomoddirectives v0.2.4 h1:j3YjBIjEBbqZ0NKtBNzr8rtMHTOrLPeiwTkfUJZ3alg= github.com/ldez/gomoddirectives v0.2.4/go.mod h1:oWu9i62VcQDYp9EQ0ONTfqLNh+mDLWWDO+SO0qSQw5g= github.com/ldez/tagliatelle v0.5.0 h1:epgfuYt9v0CG3fms0pEgIMNPuFf/LpPIfjk4kyqSioo= github.com/ldez/tagliatelle v0.5.0/go.mod h1:rj1HmWiL1MiKQuOONhd09iySTEkUuE/8+5jtPYz9xa4= github.com/leonklingele/grouper v1.1.2 h1:o1ARBDLOmmasUaNDesWqWCIFH3u7hoFlM84YrjT3mIY= github.com/leonklingele/grouper v1.1.2/go.mod h1:6D0M/HVkhs2yRKRFZUoGjeDy7EZTfFBE9gl4kjmIGkA= github.com/macabu/inamedparam v0.1.3 h1:2tk/phHkMlEL/1GNe/Yf6kkR/hkcUdAEY3L0hjYV1Mk= github.com/macabu/inamedparam v0.1.3/go.mod h1:93FLICAIk/quk7eaPPQvbzihUdn/QkGDwIZEoLtpH6I= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/maratori/testableexamples v1.0.0 h1:dU5alXRrD8WKSjOUnmJZuzdxWOEQ57+7s93SLMxb2vI= github.com/maratori/testableexamples v1.0.0/go.mod h1:4rhjL1n20TUTT4vdh3RDqSizKLyXp7K2u6HgraZCGzE= github.com/maratori/testpackage v1.1.1 h1:S58XVV5AD7HADMmD0fNnziNHqKvSdDuEKdPD1rNTU04= github.com/maratori/testpackage v1.1.1/go.mod h1:s4gRK/ym6AMrqpOa/kEbQTV4Q4jb7WeLZzVhVVVOQMc= github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26 h1:gWg6ZQ4JhDfJPqlo2srm/LN17lpybq15AryXIRcWYLE= github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s= github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mgechev/revive v1.5.1 h1:hE+QPeq0/wIzJwOphdVyUJ82njdd8Khp4fUIHGZHW3M= github.com/mgechev/revive v1.5.1/go.mod h1:lC9AhkJIBs5zwx8wkudyHrU+IJkrEKmpCmGMnIJPk4o= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/moricho/tparallel v0.3.2 h1:odr8aZVFA3NZrNybggMkYO3rgPRcqjeQUlBBFVxKHTI= github.com/moricho/tparallel v0.3.2/go.mod h1:OQ+K3b4Ln3l2TZveGCywybl68glfLEwFGqvnjok8b+U= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nakabonne/nestif v0.3.1 h1:wm28nZjhQY5HyYPx+weN3Q65k6ilSBxDb8v5S81B81U= github.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4Ngq6aY7OE= github.com/nishanths/exhaustive v0.12.0 h1:vIY9sALmw6T/yxiASewa4TQcFsVYZQQRUQJhKRf3Swg= github.com/nishanths/exhaustive v0.12.0/go.mod h1:mEZ95wPIZW+x8kC4TgC+9YCUgiST7ecevsVDTgc2obs= github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk= github.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3LMK/HI84Mp280c= github.com/nunnatsa/ginkgolinter v0.18.3 h1:WgS7X3zzmni3vwHSBhvSgqrRgUecN6PQUcfB0j1noDw= github.com/nunnatsa/ginkgolinter v0.18.3/go.mod h1:BE1xyB/PNtXXG1azrvrqJW5eFH0hSRylNzFy8QHPwzs= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo/v2 v2.20.2 h1:7NVCeyIWROIAheY21RLS+3j2bb52W0W82tkberYytp4= github.com/onsi/ginkgo/v2 v2.20.2/go.mod h1:K9gyxPIlb+aIvnZ8bd9Ak+YP18w3APlR+5coaZoE2ag= github.com/onsi/gomega v1.34.2 h1:pNCwDkzrsv7MS9kpaQvVb1aVLahQXyJ/Tv5oAZMI3i8= github.com/onsi/gomega v1.34.2/go.mod h1:v1xfxRgk0KIsG+QOdm7p8UosrOzPYRo60fd3B/1Dukc= github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w= github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/polyfloyd/go-errorlint v1.7.0 h1:Zp6lzCK4hpBDj8y8a237YK4EPrMXQWvOe3nGoH4pFrU= github.com/polyfloyd/go-errorlint v1.7.0/go.mod h1:dGWKu85mGHnegQ2SWpEybFityCg3j7ZbwsVUxAOk9gY= github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk= github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1 h1:+Wl/0aFp0hpuHM3H//KMft64WQ1yX9LdJY64Qm/gFCo= github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1/go.mod h1:GJLgqsLeo4qgavUoL8JeGFNS7qcisx3awV/w9eWTmNI= github.com/quasilyte/go-ruleguard/dsl v0.3.22 h1:wd8zkOhSNr+I+8Qeciml08ivDt1pSXe60+5DqOpCjPE= github.com/quasilyte/go-ruleguard/dsl v0.3.22/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU= github.com/quasilyte/gogrep v0.5.0 h1:eTKODPXbI8ffJMN+W2aE0+oL0z/nh8/5eNdiO34SOAo= github.com/quasilyte/gogrep v0.5.0/go.mod h1:Cm9lpz9NZjEoL1tgZ2OgeUKPIxL1meE7eo60Z6Sk+Ng= github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 h1:TCg2WBOl980XxGFEZSS6KlBGIV0diGdySzxATTWoqaU= github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0= github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 h1:M8mH9eK4OUR4lu7Gd+PU1fV2/qnDNfzT635KRSObncs= github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567/go.mod h1:DWNGW8A4Y+GyBgPuaQJuWiy0XYftx4Xm/y5Jqk9I6VQ= github.com/raeperd/recvcheck v0.1.2 h1:SjdquRsRXJc26eSonWIo8b7IMtKD3OAT2Lb5G3ZX1+4= github.com/raeperd/recvcheck v0.1.2/go.mod h1:n04eYkwIR0JbgD73wT8wL4JjPC3wm0nFtzBnWNocnYU= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryancurrah/gomodguard v1.3.5 h1:cShyguSwUEeC0jS7ylOiG/idnd1TpJ1LfHGpV3oJmPU= github.com/ryancurrah/gomodguard v1.3.5/go.mod h1:MXlEPQRxgfPQa62O8wzK3Ozbkv9Rkqr+wKjSxTdsNJE= github.com/ryanrolds/sqlclosecheck v0.5.1 h1:dibWW826u0P8jNLsLN+En7+RqWWTYrjCB9fJfSfdyCU= github.com/ryanrolds/sqlclosecheck v0.5.1/go.mod h1:2g3dUjoS6AL4huFdv6wn55WpLIDjY7ZgUR4J8HOO/XQ= github.com/sanposhiho/wastedassign/v2 v2.0.7 h1:J+6nrY4VW+gC9xFzUc+XjPD3g3wF3je/NsJFwFK7Uxc= github.com/sanposhiho/wastedassign/v2 v2.0.7/go.mod h1:KyZ0MWTwxxBmfwn33zh3k1dmsbF2ud9pAAGfoLfjhtI= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= github.com/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tMEOsumirXcOJqAw= github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ= github.com/sashamelentyev/usestdlibvars v1.27.0 h1:t/3jZpSXtRPRf2xr0m63i32ZrusyurIGT9E5wAvXQnI= github.com/sashamelentyev/usestdlibvars v1.27.0/go.mod h1:9nl0jgOfHKWNFS43Ojw0i7aRoS4j6EBye3YBhmAIRF8= github.com/securego/gosec/v2 v2.21.4 h1:Le8MSj0PDmOnHJgUATjD96PaXRvCpKC+DGJvwyy0Mlk= github.com/securego/gosec/v2 v2.21.4/go.mod h1:Jtb/MwRQfRxCXyCm1rfM1BEiiiTfUOdyzzAhlr6lUTA= github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c h1:W65qqJCIOVP4jpqPQ0YvHYKwcMEMVWIzWC5iNQQfBTU= github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c/go.mod h1:/PevMnwAxekIXwN8qQyfc5gl2NlkB3CQlkizAbOkeBs= github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sivchari/containedctx v1.0.3 h1:x+etemjbsh2fB5ewm5FeLNi5bUjK0V8n0RB+Wwfd0XE= github.com/sivchari/containedctx v1.0.3/go.mod h1:c1RDvCbnJLtH4lLcYD/GqwiBSSf4F5Qk0xld2rBqzJ4= github.com/sivchari/tenv v1.12.1 h1:+E0QzjktdnExv/wwsnnyk4oqZBUfuh89YMQT1cyuvSY= github.com/sivchari/tenv v1.12.1/go.mod h1:1LjSOUCc25snIr5n3DtGGrENhX3LuWefcplwVGC24mw= github.com/sonatard/noctx v0.1.0 h1:JjqOc2WN16ISWAjAk8M5ej0RfExEXtkEyExl2hLW+OM= github.com/sonatard/noctx v0.1.0/go.mod h1:0RvBxqY8D4j9cTTTWE8ylt2vqj2EPI8fHmrxHdsaZ2c= github.com/sourcegraph/go-diff v0.7.0 h1:9uLlrd5T46OXs5qpp8L/MTltk0zikUGi0sNNyCpA8G0= github.com/sourcegraph/go-diff v0.7.0/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ= github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI= github.com/ssgreg/nlreturn/v2 v2.2.1 h1:X4XDI7jstt3ySqGU86YGAURbxw3oTDPK9sPEi6YEwQ0= github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I= github.com/stbenjam/no-sprintf-host-port v0.1.1 h1:tYugd/yrm1O0dV+ThCbaKZh195Dfm07ysF0U6JQXczc= github.com/stbenjam/no-sprintf-host-port v0.1.1/go.mod h1:TLhvtIvONRzdmkFiio4O8LHsN9N74I+PhRquPsxpL0I= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/tdakkota/asciicheck v0.2.0 h1:o8jvnUANo0qXtnslk2d3nMKTFNlOnJjRrNcj0j9qkHM= github.com/tdakkota/asciicheck v0.2.0/go.mod h1:Qb7Y9EgjCLJGup51gDHFzbI08/gbGhL/UVhYIPWG2rg= github.com/tenntenn/modver v1.0.1 h1:2klLppGhDgzJrScMpkj9Ujy3rXPUspSjAcev9tSEBgA= github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0= github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpRQGxTSkNYKJ51yaw6ChIqO+Je8UqsTKN/cDag= github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY= github.com/tetafro/godot v1.4.18 h1:ouX3XGiziKDypbpXqShBfnNLTSjR8r3/HVzrtJ+bHlI= github.com/tetafro/godot v1.4.18/go.mod h1:2oVxTBSftRTh4+MVfUaUXR6bn2GDXCaMcOG4Dk3rfio= github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966 h1:quvGphlmUVU+nhpFa4gg4yJyTRJ13reZMDHrKwYw53M= github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966/go.mod h1:27bSVNWSBOHm+qRp1T9qzaIpsWEP6TbUnei/43HK+PQ= github.com/timonwong/loggercheck v0.10.1 h1:uVZYClxQFpw55eh+PIoqM7uAOHMrhVcDoWDery9R8Lg= github.com/timonwong/loggercheck v0.10.1/go.mod h1:HEAWU8djynujaAVX7QI65Myb8qgfcZ1uKbdpg3ZzKl8= github.com/tomarrell/wrapcheck/v2 v2.9.0 h1:801U2YCAjLhdN8zhZ/7tdjB3EnAoRlJHt/s+9hijLQ4= github.com/tomarrell/wrapcheck/v2 v2.9.0/go.mod h1:g9vNIyhb5/9TQgumxQyOEqDHsmGYcGsVMOx/xGkqdMo= github.com/tommy-muehle/go-mnd/v2 v2.5.1 h1:NowYhSdyE/1zwK9QCLeRb6USWdoif80Ie+v+yU8u1Zw= github.com/tommy-muehle/go-mnd/v2 v2.5.1/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw= github.com/ultraware/funlen v0.1.0 h1:BuqclbkY6pO+cvxoq7OsktIXZpgBSkYTQtmwhAK81vI= github.com/ultraware/funlen v0.1.0/go.mod h1:XJqmOQja6DpxarLj6Jj1U7JuoS8PvL4nEqDaQhy22p4= github.com/ultraware/whitespace v0.1.1 h1:bTPOGejYFulW3PkcrqkeQwOd6NKOOXvmGD9bo/Gk8VQ= github.com/ultraware/whitespace v0.1.1/go.mod h1:XcP1RLD81eV4BW8UhQlpaR+SDc2givTvyI8a586WjW8= github.com/uudashr/gocognit v1.1.3 h1:l+a111VcDbKfynh+airAy/DJQKaXh2m9vkoysMPSZyM= github.com/uudashr/gocognit v1.1.3/go.mod h1:aKH8/e8xbTRBwjbCkwZ8qt4l2EpKXl31KMHgSS+lZ2U= github.com/uudashr/iface v1.2.1 h1:vHHyzAUmWZ64Olq6NZT3vg/z1Ws56kyPdBOd5kTXDF8= github.com/uudashr/iface v1.2.1/go.mod h1:4QvspiRd3JLPAEXBQ9AiZpLbJlrWWgRChOKDJEuQTdg= github.com/xen0n/gosmopolitan v1.2.2 h1:/p2KTnMzwRexIW8GlKawsTWOxn7UHA+jCMF/V8HHtvU= github.com/xen0n/gosmopolitan v1.2.2/go.mod h1:7XX7Mj61uLYrj0qmeN0zi7XDon9JRAEhYQqAPLVNTeg= github.com/yagipy/maintidx v1.0.0 h1:h5NvIsCz+nRDapQ0exNv4aJ0yXSI0420omVANTv3GJM= github.com/yagipy/maintidx v1.0.0/go.mod h1:0qNf/I/CCZXSMhsRsrEPDZ+DkekpKLXAJfsTACwgXLk= github.com/yeya24/promlinter v0.3.0 h1:JVDbMp08lVCP7Y6NP3qHroGAO6z2yGKQtS5JsjqtoFs= github.com/yeya24/promlinter v0.3.0/go.mod h1:cDfJQQYv9uYciW60QT0eeHlFodotkYZlL+YcPQN+mW4= github.com/ykadowak/zerologlint v0.1.5 h1:Gy/fMz1dFQN9JZTPjv1hxEk+sRWm05row04Yoolgdiw= github.com/ykadowak/zerologlint v0.1.5/go.mod h1:KaUskqF3e/v59oPmdq1U1DnKcuHokl2/K1U4pmIELKg= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= gitlab.com/bosi/decorder v0.4.2 h1:qbQaV3zgwnBZ4zPMhGLW4KZe7A7NwxEhJx39R3shffo= gitlab.com/bosi/decorder v0.4.2/go.mod h1:muuhHoaJkA9QLcYHq4Mj8FJUwDZ+EirSHRiaTcTf6T8= go-simpler.org/assert v0.9.0 h1:PfpmcSvL7yAnWyChSjOz6Sp6m9j5lyK8Ok9pEL31YkQ= go-simpler.org/assert v0.9.0/go.mod h1:74Eqh5eI6vCK6Y5l3PI8ZYFXG4Sa+tkr70OIPJAUr28= go-simpler.org/musttag v0.13.0 h1:Q/YAW0AHvaoaIbsPj3bvEI5/QFP7w696IMUpnKXQfCE= go-simpler.org/musttag v0.13.0/go.mod h1:FTzIGeK6OkKlUDVpj0iQUXZLUO1Js9+mvykDQy9C5yM= go-simpler.org/sloglint v0.7.2 h1:Wc9Em/Zeuu7JYpl+oKoYOsQSy2X560aVueCW/m6IijY= go-simpler.org/sloglint v0.7.2/go.mod h1:US+9C80ppl7VsThQclkM7BkCHQAzuz8kHLsW3ppuluo= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20241108190413-2d47ceb2692f h1:WTyX8eCCyfdqiPYkRGm0MqElSfYFH3yR1+rl/mct9sA= golang.org/x/exp/typeparams v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190321232350-e250d351ecad/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200324003944-a576cf524670/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200329025819-fd4102a86c65/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200724022722-7017fd6b1305/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200820010801-b793a1359eac/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20201023174141-c8cfbd0f21e6/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1-0.20210205202024-ef80cdb6ec6d/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= golang.org/x/tools v0.1.1-0.20210302220138-2ac05c832e1a/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o= golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/gotestsum v1.12.0 h1:CmwtaGDkHxrZm4Ib0Vob89MTfpc3GrEFMJKovliPwGk= gotest.tools/gotestsum v1.12.0/go.mod h1:fAvqkSptospfSbQw26CTYzNwnsE/ztqLeyhP0h67ARY= gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.5.1 h1:4bH5o3b5ZULQ4UrBmP+63W9r7qIkqJClEA9ko5YKx+I= honnef.co/go/tools v0.5.1/go.mod h1:e9irvo83WDG9/irijV44wr3tbhcFeRnfpVlRqVwpzMs= k8s.io/code-generator v0.31.0 h1:w607nrMi1KeDKB3/F/J4lIoOgAwc+gV9ZKew4XRfMp8= k8s.io/code-generator v0.31.0/go.mod h1:84y4w3es8rOJOUUP1rLsIiGlO1JuEaPFXQPA9e/K6U0= k8s.io/gengo/v2 v2.0.0-20240228010128-51d4e06bde70 h1:NGrVE502P0s0/1hudf8zjgwki1X/TByhmAoILTarmzo= k8s.io/gengo/v2 v2.0.0-20240228010128-51d4e06bde70/go.mod h1:VH3AT8AaQOqiGjMF9p0/IM1Dj+82ZwjfxUP1IxaHE+8= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= mvdan.cc/gofumpt v0.7.0 h1:bg91ttqXmi9y2xawvkuMXyvAA/1ZGJqYAEGjXuP0JXU= mvdan.cc/gofumpt v0.7.0/go.mod h1:txVFJy/Sc/mvaycET54pV8SW8gWxTlUuGHVEcncmNUo= mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f h1:lMpcwN6GxNbWtbpI1+xzFLSW8XzX0u72NttUGVFjO3U= mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f/go.mod h1:RSLa7mKKCNeTTMHBw5Hsy2rfJmd6O2ivt9Dw9ZqCQpQ= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= kind-0.27.0/hack/tools/tools.go000066400000000000000000000006361475376161000163010ustar00rootroot00000000000000//go:build tools // +build tools /* Package tools is used to track binary dependencies with go modules https://github.com/golang/go/wiki/Modules#how-can-i-track-tool-dependencies-for-a-module */ package tools import ( // linter(s) _ "github.com/golangci/golangci-lint/cmd/golangci-lint" // kubernetes code generators _ "k8s.io/code-generator/cmd/deepcopy-gen" // test runner _ "gotest.tools/gotestsum" ) kind-0.27.0/images/000077500000000000000000000000001475376161000140045ustar00rootroot00000000000000kind-0.27.0/images/Makefile.common.in000066400000000000000000000035451475376161000173470ustar00rootroot00000000000000# Copyright 2020 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # shared makefile for all images # get image name from directory we're building IMAGE_NAME?=$(notdir $(CURDIR)) # docker image registry, default to upstream REGISTRY?=gcr.io/k8s-staging-kind # for appending build-meta like "_containerd-v1.7.1" TAG_SUFFIX?= # tag based on date-sha TAG?=$(shell echo "$$(date +v%Y%m%d)-$$(git describe --always --dirty)") # the full image tag IMAGE?=$(REGISTRY)/$(IMAGE_NAME):$(TAG)$(TAG_SUFFIX) # Go version to use, respected by images that build go binaries GO_VERSION=$(shell cat $(CURDIR)/../../.go-version | head -n1) # build with buildx PLATFORMS?=linux/amd64,linux/arm64 OUTPUT?= PROGRESS=auto EXTRA_BUILD_OPT?= build: ensure-buildx docker buildx build $(if $(PLATFORMS),--platform=$(PLATFORMS),) $(OUTPUT) --progress=$(PROGRESS) -t ${IMAGE} --pull --build-arg GO_VERSION=$(GO_VERSION) $(EXTRA_BUILD_OPT) . # push the cross built image push: OUTPUT=--push push: build # quick can be used to do a build that will be imported into the local docker # for sanity checking before doing a cross build push # cross builds cannot be imported locally at the moment # https://github.com/docker/buildx/issues/59 quick: PLATFORMS= quick: OUTPUT=--load quick: build # enable buildx ensure-buildx: ./../../hack/build/init-buildx.sh .PHONY: push build quick ensure-buildx kind-0.27.0/images/base/000077500000000000000000000000001475376161000147165ustar00rootroot00000000000000kind-0.27.0/images/base/Dockerfile000066400000000000000000000263561475376161000167240ustar00rootroot00000000000000# Copyright 2018 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # kind node base image # # For systemd + docker configuration used below, see the following references: # https://systemd.io/CONTAINER_INTERFACE/ # start from debian slim, this image is reasonably small as a starting point # for a kubernetes node image, it doesn't contain much (anything?) we don't need # this stage will install basic files and packages ARG BASE_IMAGE=debian:bookworm-slim FROM $BASE_IMAGE AS base # copy in static files # all scripts and directories are 0755 (rwx r-x r-x) # all non-scripts are 0644 (rw- r-- r--) COPY --chmod=0755 files/usr/local/bin/* /usr/local/bin/ COPY --chmod=0644 files/kind/ /kind/ # COPY only applies to files, not the directory itself, so the permissions are # fixed in RUN below with a chmod. COPY --chmod=0755 files/kind/bin/ /kind/bin/ COPY --chmod=0644 files/LICENSES/* /LICENSES/* COPY --chmod=0644 files/etc/* /etc/ COPY --chmod=0644 files/etc/containerd/* /etc/containerd/ COPY --chmod=0644 files/etc/default/* /etc/default/ COPY --chmod=0644 files/etc/sysctl.d/* /etc/sysctl.d/ COPY --chmod=0644 files/etc/systemd/system/* /etc/systemd/system/ COPY --chmod=0644 files/etc/systemd/system/kubelet.service.d/* /etc/systemd/system/kubelet.service.d/ # Install dependencies, first from apt, then from release tarballs. # NOTE: we use one RUN to minimize layers. # # The base image already has a basic userspace + apt but we need to install more packages. # Packages installed are broken down into (each on a line): # - packages needed to run services (systemd) # - packages needed for kubernetes components # - packages needed for networked backed storage with kubernetes # - packages needed by the container runtime # - misc packages kind uses itself # - packages that provide semi-core kubernetes functionality # After installing packages we cleanup by: # - removing unwanted systemd services # - disabling kmsg in journald (these log entries would be confusing) # # Then we install containerd from our nightly build infrastructure, as this # build for multiple architectures and allows us to upgrade to patched releases # more quickly. # # Next we download and extract crictl and CNI plugin binaries from upstream. # # Next we ensure the /etc/kubernetes/manifests directory exists. Normally # a kubeadm debian / rpm package would ensure that this exists but we install # freshly built binaries directly when we build the node image. # # Finally we adjust tempfiles cleanup to be 1 minute after "boot" instead of 15m # This is plenty after we've done initial setup for a node, but before we are # likely to try to export logs etc. RUN chmod 755 /kind/bin && \ echo "Installing Packages ..." \ && DEBIAN_FRONTEND=noninteractive clean-install \ systemd \ conntrack iptables nftables iproute2 ethtool util-linux mount kmod \ libseccomp2 pigz fuse-overlayfs \ nfs-common open-iscsi \ bash ca-certificates curl jq procps \ && find /lib/systemd/system/sysinit.target.wants/ -name "systemd-tmpfiles-setup.service" -delete \ && rm -f /lib/systemd/system/multi-user.target.wants/* \ && rm -f /etc/systemd/system/*.wants/* \ && rm -f /lib/systemd/system/local-fs.target.wants/* \ && rm -f /lib/systemd/system/sockets.target.wants/*udev* \ && rm -f /lib/systemd/system/sockets.target.wants/*initctl* \ && rm -f /lib/systemd/system/basic.target.wants/* \ && echo "ReadKMsg=no" >> /etc/systemd/journald.conf \ && ln -s "$(which systemd)" /sbin/init # NOTE: systemd-binfmt.service will register things into binfmt_misc which is kernel-global RUN echo "Enabling / Disabling services ... " \ && systemctl enable kubelet.service \ && systemctl enable containerd.service \ && systemctl enable undo-mount-hacks.service \ && systemctl mask systemd-binfmt.service RUN echo "Ensuring /etc/kubernetes/manifests" \ && mkdir -p /etc/kubernetes/manifests # shared stage to setup go version for building binaries # NOTE we will be cross-compiling for performance reasons # This is also why we start again FROM the same base image but a different # platform and only the files needed for building # We will copy the built binaries from later stages to the final stage(s) FROM --platform=$BUILDPLATFORM $BASE_IMAGE AS go-build COPY --chmod=0755 files/usr/local/bin/* /usr/local/bin/ COPY --chmod=0755 scripts/third_party/gimme/gimme /usr/local/bin/ COPY --chmod=0755 scripts/target-cc /usr/local/bin/ # tools needed at build-time only # first ensure we can install packages for both architectures RUN dpkg --add-architecture arm64 && dpkg --add-architecture amd64 \ && clean-install bash ca-certificates curl git make pkg-config \ crossbuild-essential-amd64 crossbuild-essential-arm64 \ libseccomp-dev:amd64 libseccomp-dev:arm64 # set by makefile to .go-version ARG GO_VERSION RUN eval "$(gimme "${GO_VERSION}")" \ && export GOTOOLCHAIN="go${GO_VERSION}" \ && GOBIN=/usr/local/bin go install github.com/google/go-licenses@latest # stage for building containerd FROM go-build AS build-containerd ARG TARGETARCH GO_VERSION ARG CONTAINERD_VERSION="v2.0.2" ARG CONTAINERD_CLONE_URL="https://github.com/containerd/containerd" # we don't build with optional snapshotters, we never select any of these # they're not ideal inside kind anyhow, and we save some disk space ARG BUILDTAGS="no_aufs no_zfs no_btrfs no_devmapper" RUN git clone --filter=tree:0 "${CONTAINERD_CLONE_URL}" /containerd \ && cd /containerd \ && git checkout "${CONTAINERD_VERSION}" \ && eval "$(gimme "${GO_VERSION}")" \ && export GOTOOLCHAIN="go${GO_VERSION}" \ && export GOARCH=$TARGETARCH && export CC=$(target-cc) && export CGO_ENABLED=1 \ && make bin/ctr bin/containerd bin/containerd-shim-runc-v2 \ && GOARCH=$TARGETARCH go-licenses save --save_path=/_LICENSES \ ./cmd/ctr ./cmd/containerd ./cmd/containerd-shim-runc-v2 # stage for building runc FROM go-build AS build-runc ARG TARGETARCH GO_VERSION ARG RUNC_VERSION="v1.2.3" ARG RUNC_CLONE_URL="https://github.com/opencontainers/runc" RUN git clone --filter=tree:0 "${RUNC_CLONE_URL}" /runc \ && cd /runc \ && git checkout "${RUNC_VERSION}" \ && eval "$(gimme "${GO_VERSION}")" \ && export GOTOOLCHAIN="go${GO_VERSION}" \ && export GOARCH=$TARGETARCH && export CC=$(target-cc) && export CGO_ENABLED=1 \ && make runc \ && GOARCH=$TARGETARCH go-licenses save --save_path=/_LICENSES . # stage for building crictl FROM go-build AS build-crictl ARG TARGETARCH GO_VERSION ARG CRI_TOOLS_CLONE_URL="https://github.com/kubernetes-sigs/cri-tools" ARG CRICTL_VERSION="v1.32.0" RUN git clone --filter=tree:0 "${CRI_TOOLS_CLONE_URL}" /cri-tools \ && cd /cri-tools \ && git checkout "${CRICTL_VERSION}" \ && eval "$(gimme "${GO_VERSION}")" \ && export GOTOOLCHAIN="go${GO_VERSION}" \ && export GOARCH=$TARGETARCH && export CC=$(target-cc) && export CGO_ENABLED=1 \ && make BUILD_BIN_PATH=./build crictl \ && GOARCH=$TARGETARCH go-licenses save --save_path=/_LICENSES ./cmd/crictl # stage for building cni-plugins FROM go-build AS build-cni ARG TARGETARCH GO_VERSION ARG CNI_PLUGINS_VERSION="v1.6.1" ARG CNI_PLUGINS_CLONE_URL="https://github.com/containernetworking/plugins" RUN git clone --filter=tree:0 "${CNI_PLUGINS_CLONE_URL}" /cni-plugins \ && cd /cni-plugins \ && git checkout "${CNI_PLUGINS_VERSION}" \ && eval "$(gimme "${GO_VERSION}")" \ && export GOTOOLCHAIN="go${GO_VERSION}" \ && mkdir ./bin \ && export GOARCH=$TARGETARCH && export CC=$(target-cc) && export CGO_ENABLED=0 \ && go build -o ./bin/host-local -mod=vendor ./plugins/ipam/host-local \ && go build -o ./bin/loopback -mod=vendor ./plugins/main/loopback \ && go build -o ./bin/ptp -mod=vendor ./plugins/main/ptp \ && go build -o ./bin/portmap -mod=vendor ./plugins/meta/portmap \ && GOARCH=$TARGETARCH go-licenses save --save_path=/_LICENSES \ ./plugins/ipam/host-local \ ./plugins/main/loopback ./plugins/main/ptp \ ./plugins/meta/portmap # stage for building containerd-fuse-overlayfs FROM go-build AS build-fuse-overlayfs ARG TARGETARCH GO_VERSION ARG CONTAINERD_FUSE_OVERLAYFS_VERSION="v2.1.0" ARG CONTAINERD_FUSE_OVERLAYFS_CLONE_URL="https://github.com/containerd/fuse-overlayfs-snapshotter" RUN git clone --filter=tree:0 "${CONTAINERD_FUSE_OVERLAYFS_CLONE_URL}" /fuse-overlayfs-snapshotter \ && cd /fuse-overlayfs-snapshotter \ && git checkout "${CONTAINERD_FUSE_OVERLAYFS_VERSION}" \ && eval "$(gimme "${GO_VERSION}")" \ && export GOTOOLCHAIN="go${GO_VERSION}" \ && export GOARCH=$TARGETARCH && export CC=$(target-cc) && export CGO_ENABLED=1 \ && make bin/containerd-fuse-overlayfs-grpc \ && GOARCH=$TARGETARCH go-licenses save --save_path=/_LICENSES ./cmd/containerd-fuse-overlayfs-grpc # build final image layout from other stages FROM base AS build # copy over containerd build and install COPY --from=build-containerd /containerd/bin/containerd /usr/local/bin/ COPY --from=build-containerd /containerd/bin/ctr /usr/local/bin/ COPY --from=build-containerd /containerd/bin/containerd-shim-runc-v2 /usr/local/bin/ RUN ctr oci spec \ | jq '.hooks.createContainer[.hooks.createContainer| length] |= . + {"path": "/kind/bin/mount-product-files.sh"}' \ | jq 'del(.process.rlimits)' \ > /etc/containerd/cri-base.json \ && containerd --version COPY --from=build-containerd /_LICENSES/* /LICENSES/ # copy over runc build and install COPY --from=build-runc /runc/runc /usr/local/sbin/runc RUN runc --version COPY --from=build-runc /_LICENSES/* /LICENSES/ # copy over crictl build and install COPY --from=build-crictl /cri-tools/build/crictl /usr/local/bin/ COPY --from=build-crictl /_LICENSES/* /LICENSES/ # copy over CNI plugins build and install RUN mkdir -p /opt/cni/bin COPY --from=build-cni /cni-plugins/bin/host-local /opt/cni/bin/ COPY --from=build-cni /cni-plugins/bin/loopback /opt/cni/bin/ COPY --from=build-cni /cni-plugins/bin/ptp /opt/cni/bin/ COPY --from=build-cni /cni-plugins/bin/portmap /opt/cni/bin/ COPY --from=build-cni /_LICENSES/* /LICENSES/ # copy over containerd-fuse-overlayfs and install COPY --from=build-fuse-overlayfs /fuse-overlayfs-snapshotter/bin/containerd-fuse-overlayfs-grpc /usr/local/bin/ COPY --from=build-fuse-overlayfs /_LICENSES/* /LICENSES/ # squash down to one compressed layer, without any lingering whiteout files etc FROM scratch COPY --from=build / / # add metadata, must be done after the squashing # first tell systemd that it is in docker (it will check for the container env) # https://systemd.io/CONTAINER_INTERFACE/ ENV container=docker # systemd exits on SIGRTMIN+3, not SIGTERM (which re-executes it) # https://bugzilla.redhat.com/show_bug.cgi?id=1201657 STOPSIGNAL SIGRTMIN+3 # NOTE: this is *only* for documentation, the entrypoint is overridden later ENTRYPOINT [ "/usr/local/bin/entrypoint", "/sbin/init" ] kind-0.27.0/images/base/Makefile000066400000000000000000000011641475376161000163600ustar00rootroot00000000000000# Copyright 2020 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. include $(CURDIR)/../Makefile.common.in kind-0.27.0/images/base/README.md000066400000000000000000000021061475376161000161740ustar00rootroot00000000000000 # images/base This directory contains sources for building the `kind` base "node" image. The image can be built with `make quick`. ## Maintenance This image needs to do a number of unusual things to support running systemd, nested containers, and Kubernetes. All of what we do and why we do it is documented inline in the [Dockerfile](./Dockerfile). If you make any changes to this image, please continue to document exactly why we do what we do, citing upstream documentation where possible. See also [`pkg/cluster`](./../../pkg/cluster) for logic that interacts with this image. ## Alternate Sources Kind frequently picks up new releases of dependent projects including containerd, runc, cni, and crictl. If you choose to use the provided Dockerfile but use build arguments to specify a different base image or application version for dependencies, be aware that you may possibly encounter bugs and undesired behavior. ## Design See [base-image](https://kind.sigs.k8s.io/docs/design/base-image/) for more design details. kind-0.27.0/images/base/cloudbuild.yaml000066400000000000000000000003671475376161000177360ustar00rootroot00000000000000# See https://cloud.google.com/cloud-build/docs/build-config options: substitution_option: ALLOW_LOOSE machineType: E2_HIGHCPU_32 steps: - name: gcr.io/k8s-testimages/krte:latest-master entrypoint: make args: ['-C', 'images/base', 'push'] kind-0.27.0/images/base/files/000077500000000000000000000000001475376161000160205ustar00rootroot00000000000000kind-0.27.0/images/base/files/LICENSES/000077500000000000000000000000001475376161000172255ustar00rootroot00000000000000kind-0.27.0/images/base/files/LICENSES/README.txt000066400000000000000000000002641475376161000207250ustar00rootroot00000000000000This directory contains license files and notices from binaries built for this image and the dependencies of those binaries, as collected by https://github.com/google/go-licenses. kind-0.27.0/images/base/files/etc/000077500000000000000000000000001475376161000165735ustar00rootroot00000000000000kind-0.27.0/images/base/files/etc/containerd/000077500000000000000000000000001475376161000207215ustar00rootroot00000000000000kind-0.27.0/images/base/files/etc/containerd/config.toml000066400000000000000000000033131475376161000230630ustar00rootroot00000000000000# explicitly use v2 config format version = 2 [proxy_plugins] # fuse-overlayfs is used for rootless [proxy_plugins."fuse-overlayfs"] type = "snapshot" address = "/run/containerd-fuse-overlayfs.sock" [plugins."io.containerd.grpc.v1.cri".containerd] # save disk space when using a single snapshotter discard_unpacked_layers = true # explicitly use default snapshotter so we can sed it in entrypoint snapshotter = "overlayfs" # explicit default here, as we're configuring it below default_runtime_name = "runc" [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc] # set default runtime handler to v2, which has a per-pod shim runtime_type = "io.containerd.runc.v2" # Generated by "ctr oci spec" and modified at base container to mount poduct_uuid base_runtime_spec = "/etc/containerd/cri-base.json" [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options] # use systemd cgroup by default SystemdCgroup = true # Setup a runtime with the magic name ("test-handler") used for Kubernetes # runtime class tests ... [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.test-handler] # same settings as runc runtime_type = "io.containerd.runc.v2" base_runtime_spec = "/etc/containerd/cri-base.json" [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.test-handler.options] SystemdCgroup = true [plugins."io.containerd.grpc.v1.cri"] # use fixed sandbox image sandbox_image = "registry.k8s.io/pause:3.10" # allow hugepages controller to be missing # see https://github.com/containerd/cri/pull/1501 tolerate_missing_hugepages_controller = true # restrict_oom_score_adj needs to be true when running inside UserNS (rootless) restrict_oom_score_adj = false kind-0.27.0/images/base/files/etc/crictl.yaml000066400000000000000000000000701475376161000207340ustar00rootroot00000000000000runtime-endpoint: unix:///run/containerd/containerd.sockkind-0.27.0/images/base/files/etc/default/000077500000000000000000000000001475376161000202175ustar00rootroot00000000000000kind-0.27.0/images/base/files/etc/default/kubelet000066400000000000000000000001051475376161000215710ustar00rootroot00000000000000KUBELET_EXTRA_ARGS=--runtime-cgroups=/system.slice/containerd.servicekind-0.27.0/images/base/files/etc/sysctl.d/000077500000000000000000000000001475376161000203365ustar00rootroot00000000000000kind-0.27.0/images/base/files/etc/sysctl.d/10-network-magic.conf000066400000000000000000000013111475376161000241660ustar00rootroot00000000000000# Do not consider loopback addresses as martian source or destination while routing # - Docker with custom networks uses an embedded DNS server with address 172.0.0.11 # - Kubernetes pods mount the node resolv.conf, so they can't use a loopack address # that is only reachable from the node. # # KIND rewrites the well-known docker DNS address 127.0.0.11 by a non-loopback address. # The DNS traffic coming from a pod will have to route to a localhost address to be NATed, # hence we have to enable route_localnet or pods DNS will not work. # Kubernetes mitigates the possible security issue caused by enabling this option. # ref: https://nvd.nist.gov/vuln/detail/CVE-2020-8558 net.ipv4.conf.all.route_localnet=1 kind-0.27.0/images/base/files/etc/sysctl.d/10-network-security.conf000066400000000000000000000002341475376161000247600ustar00rootroot00000000000000# Turn on Source Address Verification in all interfaces to # prevent some spoofing attacks. net.ipv4.conf.default.rp_filter=1 net.ipv4.conf.all.rp_filter=1 kind-0.27.0/images/base/files/etc/systemd/000077500000000000000000000000001475376161000202635ustar00rootroot00000000000000kind-0.27.0/images/base/files/etc/systemd/system/000077500000000000000000000000001475376161000216075ustar00rootroot00000000000000kind-0.27.0/images/base/files/etc/systemd/system/containerd-fuse-overlayfs.service000066400000000000000000000004531475376161000302710ustar00rootroot00000000000000[Unit] Description=containerd fuse-overlayfs snapshotter PartOf=containerd.service [Service] ExecStart=/usr/local/bin/containerd-fuse-overlayfs-grpc /run/containerd-fuse-overlayfs.sock /var/lib/containerd-fuse-overlayfs Type=notify Restart=always RestartSec=1 [Install] WantedBy=multi-user.target kind-0.27.0/images/base/files/etc/systemd/system/containerd.service000066400000000000000000000013621475376161000253210ustar00rootroot00000000000000# derived containerd systemd service file from the official: # https://github.com/containerd/containerd/blob/master/containerd.service [Unit] Description=containerd container runtime Documentation=https://containerd.io After=network.target local-fs.target # disable rate limiting StartLimitIntervalSec=0 [Service] ExecStartPre=-/sbin/modprobe overlay ExecStart=/usr/local/bin/containerd Type=notify Delegate=yes KillMode=process Restart=always RestartSec=1 # Having non-zero Limit*s causes performance problems due to accounting overhead # in the kernel. We recommend using cgroups to do container-local accounting. LimitNPROC=infinity LimitCORE=infinity LimitNOFILE=infinity TasksMax=infinity OOMScoreAdjust=-999 [Install] WantedBy=multi-user.target kind-0.27.0/images/base/files/etc/systemd/system/kubelet.service000066400000000000000000000015561475376161000246330ustar00rootroot00000000000000# slightly modified from: # https://github.com/kubernetes/kubernetes/blob/ba8fcafaf8c502a454acd86b728c857932555315/build/debs/kubelet.service [Unit] Description=kubelet: The Kubernetes Node Agent Documentation=http://kubernetes.io/docs/ # NOTE: kind deviates from upstream here to avoid crashlooping # This does *not* support altering the kubelet config path though. # We intend to upstream this change but first need to solve the upstream # Packaging problem (all kubernetes versions use the same files out of tree). ConditionPathExists=/var/lib/kubelet/config.yaml [Service] ExecStart=/usr/bin/kubelet Restart=always StartLimitInterval=0 # NOTE: kind deviates from upstream here with a lower RestartSec RestartSec=1s # And by adding the [Service] lines below CPUAccounting=true MemoryAccounting=true Slice=kubelet.slice KillMode=process [Install] WantedBy=multi-user.target kind-0.27.0/images/base/files/etc/systemd/system/kubelet.service.d/000077500000000000000000000000001475376161000251235ustar00rootroot00000000000000kind-0.27.0/images/base/files/etc/systemd/system/kubelet.service.d/10-kubeadm.conf000066400000000000000000000017661475376161000276320ustar00rootroot00000000000000# https://github.com/kubernetes/kubernetes/blob/ba8fcafaf8c502a454acd86b728c857932555315/build/debs/10-kubeadm.conf # Note: This dropin only works with kubeadm and kubelet v1.11+ [Service] Environment="KUBELET_KUBECONFIG_ARGS=--bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf" Environment="KUBELET_CONFIG_ARGS=--config=/var/lib/kubelet/config.yaml" # This is a file that "kubeadm init" and "kubeadm join" generates at runtime, populating the KUBELET_KUBEADM_ARGS variable dynamically EnvironmentFile=-/var/lib/kubelet/kubeadm-flags.env # This is a file that the user can use for overrides of the kubelet args as a last resort. Preferably, the user should use # the .NodeRegistration.KubeletExtraArgs object in the configuration files instead. KUBELET_EXTRA_ARGS should be sourced from this file. EnvironmentFile=-/etc/default/kubelet ExecStart= ExecStart=/usr/bin/kubelet $KUBELET_KUBECONFIG_ARGS $KUBELET_CONFIG_ARGS $KUBELET_KUBEADM_ARGS $KUBELET_EXTRA_ARGS kind-0.27.0/images/base/files/etc/systemd/system/kubelet.service.d/11-kind.conf000066400000000000000000000014271475376161000271420ustar00rootroot00000000000000# kind specific additions go in this file [Service] # On cgroup v1, the /kubelet cgroup is created in the entrypoint script before running systemd. # On cgroup v2, the /kubelet cgroup is created here. (See the comments in the entrypoint script for the reason.) ExecStartPre=/bin/sh -euc "if [ -f /sys/fs/cgroup/cgroup.controllers ]; then /kind/bin/create-kubelet-cgroup-v2.sh; fi" # on WSL2 (and potentially other distros without systemd) /sys/fs/cgroup/systemd is created after the entrypoint, during /sbin/init. # This eventually leads to kubelet failing to start, see: https://github.com/kubernetes-sigs/kind/issues/2323 ExecStartPre=/bin/sh -euc "if [ ! -f /sys/fs/cgroup/cgroup.controllers ] && [ ! -d /sys/fs/cgroup/systemd/kubelet ]; then mkdir -p /sys/fs/cgroup/systemd/kubelet; fi" kind-0.27.0/images/base/files/etc/systemd/system/kubelet.slice000066400000000000000000000002011475376161000242540ustar00rootroot00000000000000[Unit] Description=slice used to run Kubernetes / Kubelet Before=slices.target [Slice] MemoryAccounting=true CPUAccounting=true kind-0.27.0/images/base/files/etc/systemd/system/undo-mount-hacks.service000066400000000000000000000003151475376161000263640ustar00rootroot00000000000000[Unit] Description=Undo KIND mount hacks After=slices.target Before=containerd.service kubelet.service [Service] Type=oneshot ExecStart=/kind/bin/undo-mount-hacks.sh [Install] WantedBy=multi-user.target kind-0.27.0/images/base/files/kind/000077500000000000000000000000001475376161000167455ustar00rootroot00000000000000kind-0.27.0/images/base/files/kind/README.txt000066400000000000000000000002731475376161000204450ustar00rootroot00000000000000This directory is reserved for KIND [^1] internal purposes. Modifying or depending on the contents of this directory is NOT supported. Here be dragons. [^1]: https://kind.sigs.k8s.io/ kind-0.27.0/images/base/files/kind/bin/000077500000000000000000000000001475376161000175155ustar00rootroot00000000000000kind-0.27.0/images/base/files/kind/bin/create-kubelet-cgroup-v2.sh000077500000000000000000000037171475376161000246020ustar00rootroot00000000000000#!/bin/bash # Copyright 2021 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. set -o errexit set -o nounset set -o pipefail if [[ ! -f "/sys/fs/cgroup/cgroup.controllers" ]]; then echo 'ERROR: this script should not be called on cgroup v1 hosts' >&2 exit 1 fi # NOTE: we can't use `test -s` because cgroup.procs is not a regular file. if grep -qv '^0$' /sys/fs/cgroup/cgroup.procs ; then echo 'ERROR: this script needs /sys/fs/cgroup/cgroup.procs to be empty (for writing the top-level cgroup.subtree_control)' >&2 # So, this script needs to be called after launching systemd. # This script cannot be called from /usr/local/bin/entrypoint. exit 1 fi ensure_subtree_control() { local group=$1 # When cgroup.controllers is like "cpu cpuset memory io pids", # cgroup.subtree_control is written with "+cpu +cpuset +memory +io +pids" . sed -e 's/ / +/g' -e 's/^/+/' <"/sys/fs/cgroup/$group/cgroup.controllers" >"/sys/fs/cgroup/$group/cgroup.subtree_control" } # kubelet requires all the controllers (including hugetlb) in /sys/fs/cgroup/cgroup.controllers to be available in # /sys/fs/cgroup/kubelet/cgroup.subtree_control. # # We need to update the top-level cgroup.subtree_controllers as well, because hugetlb is not present in the file by default. ensure_subtree_control / mkdir -p /sys/fs/cgroup/kubelet ensure_subtree_control /kubelet # again for kubelet.slice for systemd cgroup driver mkdir -p /sys/fs/cgroup/kubelet.slice ensure_subtree_control /kubelet.slice kind-0.27.0/images/base/files/kind/bin/mount-product-files.sh000077500000000000000000000046271475376161000240050ustar00rootroot00000000000000#!/bin/bash # Copyright 2021 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # This script is a createContainer hook [1] that replicates the functionality from entrypoint script to mount product_name and product_uuid but from a product_name and product_uuid copied into the contianer rootfs to prevent all the containers from bind mounting the same file. Sharing the same bind mount between all the containers increases the latency accessing the container, preventing it from accessing in some cases. # # [1] https://github.com/opencontainers/runtime-spec/blob/master/config.md#createcontainer-hooks set -o errexit set -o nounset set -o pipefail # Explicitly set PATH so as not to be inherited from the container # We have no reason to be using any binary from the container, and the # container PATH may lack normal "host" (kind node container in this case) # system paths. # # See: https://github.com/kubernetes-sigs/kind/issues/2551 # # All of the binaries this script needs are in /usr/bin currently, but this is # the full normal PATH for this image with pretty standard linux paths. export PATH='/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin' # The bundle represents the dir path to container filesystem, container runtime state [1] is # passed to the hook's stdin # # [1] https://github.com/opencontainers/runtime-spec/blob/master/runtime.md#state # bundle=$(jq -r .bundle) cp /kind/product_* "${bundle:?}/rootfs/" if [[ -f /sys/class/dmi/id/product_name ]]; then mount -o ro,bind "${bundle:?}"/rootfs/product_name "${bundle:?}"/rootfs/sys/class/dmi/id/product_name fi if [[ -f /sys/class/dmi/id/product_uuid ]]; then mount -o ro,bind "${bundle:?}"/rootfs/product_uuid "${bundle:?}"/rootfs/sys/class/dmi/id/product_uuid fi if [[ -f /sys/devices/virtual/dmi/id/product_uuid ]]; then mount -o ro,bind "${bundle:?}"/rootfs/product_uuid "${bundle:?}"/rootfs/sys/devices/virtual/dmi/id/product_uuid fi kind-0.27.0/images/base/files/kind/bin/undo-mount-hacks.sh000077500000000000000000000015321475376161000232510ustar00rootroot00000000000000#!/bin/bash # Copyright 2023 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. set -o errexit set -o nounset set -o pipefail # don't run on cgroups v2 if [[ -f "/sys/fs/cgroup/cgroup.controllers" ]]; then exit 0 fi # on cgroups v1 we want to undo bind mounting over this to hide misc # see entrypoint umount /proc/cgroups || true kind-0.27.0/images/base/files/usr/000077500000000000000000000000001475376161000166315ustar00rootroot00000000000000kind-0.27.0/images/base/files/usr/local/000077500000000000000000000000001475376161000177235ustar00rootroot00000000000000kind-0.27.0/images/base/files/usr/local/bin/000077500000000000000000000000001475376161000204735ustar00rootroot00000000000000kind-0.27.0/images/base/files/usr/local/bin/clean-install000077500000000000000000000022161475376161000231500ustar00rootroot00000000000000#!/bin/sh # Copyright 2017 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # A script encapsulating a common Dockerimage pattern for installing packages # and then cleaning up the unnecessary install artifacts. # e.g. clean-install iptables ebtables conntrack set -o errexit if [ $# = 0 ]; then echo >&2 "No packages specified" exit 1 fi apt-get update apt-get upgrade -y apt-get install -y --no-install-recommends "$@" apt-get clean -y rm -rf \ /var/cache/debconf/* \ /var/lib/apt/lists/* \ /var/log/* \ /tmp/* \ /var/tmp/* \ /usr/share/doc/* \ /usr/share/doc-base/* \ /usr/share/man/* \ /usr/share/local/* kind-0.27.0/images/base/files/usr/local/bin/entrypoint000077500000000000000000000575131475376161000226470ustar00rootroot00000000000000#!/bin/bash # Copyright 2019 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. set -o errexit set -o nounset set -o pipefail # logging helpers log_info() { echo "INFO: $1" >&2 } log_warn() { echo "WARN: $1" >&2 } log_error() { echo "ERROR: $1" >&2 } # If /proc/self/uid_map 4294967295 mappings, we are in the initial user namespace, i.e. the host. # Otherwise we are in a non-initial user namespace. # https://github.com/opencontainers/runc/blob/v1.0.0-rc92/libcontainer/system/linux.go#L109-L118 userns="" if grep -Eqv "0[[:space:]]+0[[:space:]]+4294967295" /proc/self/uid_map; then userns="1" log_info 'running in a user namespace (experimental)' fi grep_allow_nomatch() { # grep exits 0 on match, 1 on no match, 2 on error grep "$@" || [[ $? == 1 ]] } # regex_escape_ip converts IP address string $1 to a regex-escaped literal regex_escape_ip(){ sed -e 's#\.#\\.#g' -e 's#\[#\\[#g' -e 's#\]#\\]#g' <<<"$1" } validate_userns() { if [[ -z "${userns}" ]]; then return fi local nofile_hard nofile_hard="$(ulimit -Hn)" local nofile_hard_expected="64000" if [[ "${nofile_hard}" -lt "${nofile_hard_expected}" ]]; then log_warn "UserNS: expected RLIMIT_NOFILE to be at least ${nofile_hard_expected}, got ${nofile_hard}" fi if [[ -f "/sys/fs/cgroup/cgroup.controllers" ]]; then for f in cpu memory pids; do if ! grep -qw $f /sys/fs/cgroup/cgroup.controllers; then log_error "UserNS: $f controller needs to be delegated" exit 1 fi done fi } overlayfs_preferrable() { if [[ -z "$userns" ]]; then # If we are outside userns, we can always assume overlayfs is preferrable return 0 fi # Debian 10 and 11 supports overlayfs in userns with a "permit_mount_in_userns" kernel patch, # but known to be unstable, so we avoid using it https://github.com/moby/moby/issues/42302 if [[ -e "/sys/module/overlay/parameters/permit_mounts_in_userns" ]]; then log_info "UserNS: kernel seems supporting overlayfs with permit_mounts_in_userns, but avoiding due to instability." return 1 fi # Check overlayfs availability, by attempting to mount it. # # Overlayfs inside userns is known to be available for the following environments: # - Kernel >= 5.11 (but 5.11 and 5.12 have issues on SELinux hosts. Fixed in 5.13.) # - Ubuntu kernel # - Debian kernel (but avoided due to instability, see the /sys/module/overlay/... check above) # - Sysbox tmp=$(mktemp -d) mkdir -p "${tmp}/l" "${tmp}/u" "${tmp}/w" "${tmp}/m" if ! mount -t overlay -o lowerdir="${tmp}/l,upperdir=${tmp}/u,workdir=${tmp}/w" overlay "${tmp}/m"; then log_info "UserNS: kernel does not seem to support overlayfs." rm -rf "${tmp}" return 1 fi umount "${tmp}/m" rm -rf "${tmp}" # Detect whether SELinux is Enforcing (or Permitted) by grepping /proc/self/attr/current . # Note that we cannot use `getenforce` command here because /sys/fs/selinux is typically not mounted for containers. if grep -q "_t:" "/proc/self/attr/current"; then # When the kernel is before v5.13 and SELinux is enforced, fuse-overlayfs might be safer, so we print a warning (but not an error). # https://github.com/torvalds/linux/commit/7fa2e79a6bb924fa4b2de5766dab31f0f47b5ab6 log_warn "UserNS: SELinux might be Enforcing. If you see an error related to overlayfs, try setting \`KIND_EXPERIMENTAL_CONTAINERD_SNAPSHOTTER=fuse-overlayfs\` ." fi return 0 } configure_containerd() { local snapshotter=${KIND_EXPERIMENTAL_CONTAINERD_SNAPSHOTTER:-} # if we have not already overridden the snapshotter, attempt to auto select if [[ -z "$snapshotter" ]]; then # we need to switch to 'native' or 'fuse-overlayfs' on zfs container_filesystem="$(stat -f -c %T /kind)" if [[ "$container_filesystem" == 'zfs' ]]; then # we do not use the ZFS snapshotter because of skew issues vs the host snapshotter="native" # fuse should imply fuse-overlayfs, we should switch to fuse-overlayfs (or native) elif [[ "$container_filesystem" == 'fuseblk' ]]; then snapshotter="fuse-overlayfs" # Otherwise use fuse-overlayfs if overlayfs is not preferrable # https://github.com/kubernetes-sigs/kind/issues/2275 elif [[ -n "$userns" ]] && ! overlayfs_preferrable; then snapshotter="fuse-overlayfs" fi fi # handle userns (rootless) if [[ -n "$userns" ]]; then # enable restrict_oom_score_adj sed -i 's/restrict_oom_score_adj = false/restrict_oom_score_adj = true/' /etc/containerd/config.toml fi # if we've overridden or auto-selected the snapshotter vs the default, update containerd if [[ -n "$snapshotter" ]]; then log_info "changing snapshotter from \"overlayfs\" to \"$snapshotter\"" sed -i "s/snapshotter = \"overlayfs\"/snapshotter = \"$snapshotter\"/" /etc/containerd/config.toml if [[ "$snapshotter" = "fuse-overlayfs" ]]; then log_info 'enabling containerd-fuse-overlayfs service' systemctl enable containerd-fuse-overlayfs fi fi } configure_proxy() { # ensure all processes receive the proxy settings by default # https://www.freedesktop.org/software/systemd/man/systemd-system.conf.html mkdir -p /etc/systemd/system.conf.d/ cat </etc/systemd/system.conf.d/proxy-default-environment.conf [Manager] DefaultEnvironment="HTTP_PROXY=${HTTP_PROXY:-}" "HTTPS_PROXY=${HTTPS_PROXY:-}" "NO_PROXY=${NO_PROXY:-}" EOF } fix_mount() { log_info 'ensuring we can execute mount/umount even with userns-remap' # necessary only when userns-remap is enabled on the host, but harmless # The binary /bin/mount should be owned by root and have the setuid bit chown root:root "$(which mount)" "$(which umount)" chmod -s "$(which mount)" "$(which umount)" # This is a workaround to an AUFS bug that might cause `Text file # busy` on `mount` command below. See more details in # https://github.com/moby/moby/issues/9547 if [[ "$(stat -f -c %T "$(which mount)")" == 'aufs' ]]; then log_info 'detected aufs, calling sync' sync fi log_info 'remounting /sys read-only' # systemd-in-a-container should have read only /sys # https://systemd.io/CONTAINER_INTERFACE/ # however, we need other things from `docker run --privileged` ... # and this flag also happens to make /sys rw, amongst other things # # This step is ignored when running inside UserNS, because it fails with EACCES. if ! mount -o remount,ro /sys; then if [[ -n "$userns" ]]; then log_info 'UserNS: ignoring mount fail' else exit 1 fi fi log_info 'making mounts shared' # for mount propagation mount --make-rshared / } # helper used by mount_kubelet_cgroup_root mount_kubelet_cgroup_root_subsystem() { local cgroup_root=$1 local subsystem=$2 if [ -z "${cgroup_root}" ]; then return 0 fi mkdir -p "${subsystem}/${cgroup_root}" if [ "${subsystem}" == "/sys/fs/cgroup/cpuset" ]; then # This is needed. Otherwise, assigning process to the cgroup # (or any nested cgroup) would result in ENOSPC. cat "${subsystem}/cpuset.cpus" > "${subsystem}/${cgroup_root}/cpuset.cpus" cat "${subsystem}/cpuset.mems" > "${subsystem}/${cgroup_root}/cpuset.mems" fi # We need to perform a self bind mount here because otherwise, # systemd might delete the cgroup unintentionally before the # kubelet starts. mount --bind "${subsystem}/${cgroup_root}" "${subsystem}/${cgroup_root}" } # helper used by fix_cgroup mount_kubelet_cgroup_root() { local cgroup_subsystems=$1 echo "${cgroup_subsystems}" | while IFS= read -r subsystem; do mount_kubelet_cgroup_root_subsystem /kubelet "${subsystem}" mount_kubelet_cgroup_root_subsystem /kubelet.slice "${subsystem}" done # workaround for hosts not running systemd # we only do this for kubelet.slice because it's not relevant when not using # the systemd cgroup driver if [[ ! "${cgroup_subsystems}" = */sys/fs/cgroup/systemd* ]]; then mount_kubelet_cgroup_root_subsystem /kubelet.slice /sys/fs/cgroup/systemd fi } # helper for cgroups v1, to eliminate the misc cgroup # see: https://github.com/kubernetes-sigs/kind/issues/3223 # basically: this cgroup is not very useful for us, and until recently wasn't # supported by runc on cgroups v1 anyhow. # on cgroupsv2 we can leave it, but on v1 the mismatch in support from container # nesting causes problems remove_misc_controller() { local misc_controller='/sys/fs/cgroup/misc' if [[ ! -d $misc_controller ]]; then return 0 fi log_info "removing misc controller" umount $misc_controller || log_warn "failed umount $misc_controller" rmdir $misc_controller || log_warn "failed rmdir $misc_controller" # systemd will discover misc controller is available here and re-mount it # we will pretend it isn't available with a bind mount # this mount will be removed by undo-mount-hacks.service grep -v 'misc' /proc/cgroups >/kind/fake-cgroups mount --bind /kind/fake-cgroups /proc/cgroups } fix_cgroup() { if [[ -f "/sys/fs/cgroup/cgroup.controllers" ]]; then log_info 'detected cgroup v2' # Both Docker and Podman enable CgroupNS on cgroup v2 hosts by default. # # So mostly we do not need to mess around with the cgroup path stuff, # however, we still need to create the "/kubelet" cgroup at least. # (Otherwise kubelet fails with `cgroup-root ["kubelet"] doesn't exist` error, see #1969) # # The "/kubelet" cgroup is created in ExecStartPre of the kubeadm service. # # [FAQ: Why not create "/kubelet" cgroup here?] # We can't create the cgroup with controllers here, because /sys/fs/cgroup/cgroup.subtree_control is empty. # And yet we can't write controllers to /sys/fs/cgroup/cgroup.subtree_control by ourselves either, because # /sys/fs/cgroup/cgroup.procs is not empty at this moment. # # After switching from this entrypoint script to systemd, systemd evacuates the processes in the root # group to "/init.scope" group, so we can write the root subtree_control and create "/kubelet" cgroup. return fi log_info 'detected cgroup v1' # We're looking for the cgroup-path for the cpu controller for the # current process. this tells us what cgroup-path the container is in. local current_cgroup current_cgroup=$(grep -E '^[^:]*:([^:]*,)?cpu(,[^,:]*)?:.*' /proc/self/cgroup | cut -d: -f3) if [ "$current_cgroup" = "/" ]; then log_info 'detected cgroupns' # we don't need or want the misc controller, see comments on this function remove_misc_controller # kubelet will try to manage cgroups / pods that are not owned by it when # "nesting" clusters, unless we instruct it to use a different cgroup root. # We do this, and when doing so we must fixup this alternative root # currently this is hardcoded to be /kubelet # under systemd cgroup driver, kubelet appends .slice local cgroup_subsystems cgroup_subsystems=$(findmnt -lun -o source,target -t cgroup | grep -F "${current_cgroup}" | awk '{print $2}') mount --make-rprivate /sys/fs/cgroup mount_kubelet_cgroup_root "${cgroup_subsystems}" return fi # NOTE The rest of this function deals with the unfortunate situation of # cgroup v1 with no cgroupns enabled. One fine day every user will have # cgroupns enabled (or switch or cgroup v2 which has it enabled by default). # Once that happens, this function can be removed completely. log_warn 'cgroupns not enabled! Please use cgroup v2, or cgroup v1 with cgroupns enabled.' # See: https://d2iq.com/blog/running-kind-inside-a-kubernetes-cluster-for-continuous-integration # Capture initial state before modifying # # Then we collect the subsystems that are active on our current process. # We assume the cpu controller is in use on all node containers, # and other controllers use the same sub-path. # # See: https://man7.org/linux/man-pages/man7/cgroups.7.html log_info 'fixing cgroup mounts for all subsystems' local cgroup_subsystems cgroup_subsystems=$(findmnt -lun -o source,target -t cgroup | grep -F "${current_cgroup}" | awk '{print $2}') # Unmount the cgroup subsystems that are not known to runtime used to # run the container we are in. Those subsystems are not properly scoped # (i.e. the root cgroup is exposed, rather than something like docker/xxxx). # In case a runtime (which is aware of more subsystems -- such as rdma, # misc, or unified) is used inside the container, it may create cgroups for # these subsystems, and as they are not scoped, they will leak to the host # and thus will become non-removable. # # See https://github.com/kubernetes/kubernetes/issues/109182 local unsupported_cgroups unsupported_cgroups=$(findmnt -lun -o source,target -t cgroup | grep_allow_nomatch -v -F "${current_cgroup}" | awk '{print $2}') if [ -n "$unsupported_cgroups" ]; then local mnt echo "$unsupported_cgroups" | while IFS= read -r mnt; do log_info "unmounting and removing $mnt" umount "$mnt" || log_warn "failed to unmount $mnt" rmdir "$mnt" || log_warn "failed to rmdir $mnt" done fi # always remove misc on v1, see comments on this function remove_misc_controller # For each cgroup subsystem, Docker does a bind mount from the current # cgroup to the root of the cgroup subsystem. For instance: # /sys/fs/cgroup/memory/docker/ -> /sys/fs/cgroup/memory # # This will confuse Kubelet and cadvisor and will dump the following error # messages in kubelet log: # `summary_sys_containers.go:47] Failed to get system container stats for ".../kubelet.service"` # # This is because `/proc//cgroup` is not affected by the bind mount. # The following is a workaround to recreate the original cgroup # environment by doing another bind mount for each subsystem. local cgroup_mounts # xref: https://github.com/kubernetes/minikube/pull/9508 # Example inputs: # # Docker: /docker/562a56986a84b3cd38d6a32ac43fdfcc8ad4d2473acf2839cbf549273f35c206 /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime shared:143 master:23 - cgroup devices rw,devices # podman: /libpod_parent/libpod-73a4fb9769188ae5dc51cb7e24b9f2752a4af7b802a8949f06a7b2f2363ab0e9 ... # Cloud Shell: /kubepods/besteffort/pod3d6beaa3004913efb68ce073d73494b0/accdf94879f0a494f317e9a0517f23cdd18b35ff9439efd0175f17bbc56877c4 /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime master:19 - cgroup cgroup rw,memory # GitHub actions #9304: /actions_job/0924fbbcf7b18d2a00c171482b4600747afc367a9dfbeac9d6b14b35cda80399 /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime shared:263 master:24 - cgroup cgroup rw,memory cgroup_mounts=$(grep -E -o '/[[:alnum:]].* /sys/fs/cgroup.*.*cgroup' /proc/self/mountinfo || true) if [[ -n "${cgroup_mounts}" ]]; then local mount_root mount_root=$(head -n 1 <<<"${cgroup_mounts}" | cut -d' ' -f1) for mount_point in $(echo "${cgroup_mounts}" | cut -d' ' -f 2); do # bind mount each mount_point to mount_point + mount_root # mount --bind /sys/fs/cgroup/cpu /sys/fs/cgroup/cpu/docker/fb07bb6daf7730a3cb14fc7ff3e345d1e47423756ce54409e66e01911bab2160 local target="${mount_point}${mount_root}" if ! findmnt "${target}"; then mkdir -p "${target}" mount --bind "${mount_point}" "${target}" fi done fi # kubelet will try to manage cgroups / pods that are not owned by it when # "nesting" clusters, unless we instruct it to use a different cgroup root. # We do this, and when doing so we must fixup this alternative root # currently this is hardcoded to be /kubelet # under systemd cgroup driver, kubelet appends .slice mount --make-rprivate /sys/fs/cgroup cgroup_subsystems=$(findmnt -lun -o source,target -t cgroup | grep -F "${current_cgroup}" | awk '{print $2}') mount_kubelet_cgroup_root "${cgroup_subsystems}" } fix_machine_id() { # Deletes the machine-id embedded in the node image and generates a new one. # This is necessary because both kubelet and other components like weave net # use machine-id internally to distinguish nodes. log_info 'clearing and regenerating /etc/machine-id' rm -f /etc/machine-id systemd-machine-id-setup } fix_product_name() { # this is a small fix to hide the underlying hardware and fix issue #426 # https://github.com/kubernetes-sigs/kind/issues/426 if [[ -f /sys/class/dmi/id/product_name ]]; then log_info 'faking /sys/class/dmi/id/product_name to be "kind"' echo 'kind' > /kind/product_name mount -o ro,bind /kind/product_name /sys/class/dmi/id/product_name fi } fix_product_uuid() { # The system UUID is usually read from DMI via sysfs, the problem is that # in the kind case this means that all (container) nodes share the same # system/product uuid, as they share the same DMI. # Note: The UUID is read from DMI, this tool is overwriting the sysfs files # which should fix the attached issue, but this workaround does not address # the issue if a tool is reading directly from DMI. # https://github.com/kubernetes-sigs/kind/issues/1027 [[ ! -f /kind/product_uuid ]] && cat /proc/sys/kernel/random/uuid > /kind/product_uuid if [[ -f /sys/class/dmi/id/product_uuid ]]; then log_info 'faking /sys/class/dmi/id/product_uuid to be random' mount -o ro,bind /kind/product_uuid /sys/class/dmi/id/product_uuid fi if [[ -f /sys/devices/virtual/dmi/id/product_uuid ]]; then log_info 'faking /sys/devices/virtual/dmi/id/product_uuid as well' mount -o ro,bind /kind/product_uuid /sys/devices/virtual/dmi/id/product_uuid fi } select_iptables() { # based on: https://github.com/kubernetes-sigs/iptables-wrappers/blob/97b01f43a8e8db07840fc4b95e833a37c0d36b12/iptables-wrapper-installer.sh local mode num_legacy_lines num_nft_lines num_legacy_lines=$( (iptables-legacy-save || true; ip6tables-legacy-save || true) 2>/dev/null | grep -c '^-' || true) num_nft_lines=$( (timeout 5 sh -c "iptables-nft-save; ip6tables-nft-save" || true) 2>/dev/null | grep -c '^-' || true) if [ "${num_legacy_lines}" -ge "${num_nft_lines}" ]; then mode=legacy else mode=nft fi log_info "setting iptables to detected mode: ${mode}" update-alternatives --set iptables "/usr/sbin/iptables-${mode}" > /dev/null update-alternatives --set ip6tables "/usr/sbin/ip6tables-${mode}" > /dev/null } fix_certificate() { local apiserver_crt_file="/etc/kubernetes/pki/apiserver.crt" local apiserver_key_file="/etc/kubernetes/pki/apiserver.key" # Skip if this Node doesn't run kube-apiserver if [[ ! -f ${apiserver_crt_file} ]] || [[ ! -f ${apiserver_key_file} ]]; then return fi # Deletes the certificate for kube-apiserver and generates a new one. # This is necessary because the old one doesn't match the current IP. log_info 'clearing and regenerating the certificate for serving the Kubernetes API' rm -f ${apiserver_crt_file} ${apiserver_key_file} kubeadm init phase certs apiserver --config /kind/kubeadm.conf } enable_network_magic(){ # well-known docker embedded DNS is at 127.0.0.11:53 local docker_embedded_dns_ip='127.0.0.11' # first we need to detect an IP to use for reaching the docker host local docker_host_ip docker_host_ip="$( (head -n1 <(timeout 5 getent ahostsv4 'host.docker.internal') | cut -d' ' -f1) || true)" # if the ip doesn't exist or is a loopback address use the default gateway if [[ -z "${docker_host_ip}" ]] || [[ $docker_host_ip =~ ^127\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then docker_host_ip=$(ip -4 route show default | cut -d' ' -f3) fi # patch docker's iptables rules to switch out the DNS IP iptables-save \ | sed \ `# switch docker DNS DNAT rules to our chosen IP` \ -e "s/-d ${docker_embedded_dns_ip}/-d ${docker_host_ip}/g" \ `# we need to also apply these rules to non-local traffic (from pods)` \ -e 's/-A OUTPUT \(.*\) -j DOCKER_OUTPUT/\0\n-A PREROUTING \1 -j DOCKER_OUTPUT/' \ `# switch docker DNS SNAT rules rules to our chosen IP` \ -e "s/--to-source :53/--to-source ${docker_host_ip}:53/g"\ `# nftables incompatibility between 1.8.8 and 1.8.7 omit the --dport flag on DNAT rules` \ `# ensure --dport on DNS rules, due to https://github.com/kubernetes-sigs/kind/issues/3054` \ -e "s/p -j DNAT --to-destination ${docker_embedded_dns_ip}/p --dport 53 -j DNAT --to-destination ${docker_embedded_dns_ip}/g" \ | iptables-restore # now we can ensure that DNS is configured to use our IP cp /etc/resolv.conf /etc/resolv.conf.original replaced="$(sed -e "s/${docker_embedded_dns_ip}/${docker_host_ip}/g" /etc/resolv.conf.original)" if [[ "${KIND_DNS_SEARCH+x}" == "" ]]; then # No DNS search set, just pass through as is echo "$replaced" >/etc/resolv.conf elif [[ -z "$KIND_DNS_SEARCH" ]]; then # Empty search - remove all current search clauses echo "$replaced" | grep -v "^search" >/etc/resolv.conf else # Search set - remove all current search clauses, and add the configured search { echo "search $KIND_DNS_SEARCH"; echo "$replaced" | grep -v "^search"; } >/etc/resolv.conf fi local files_to_update=( /etc/kubernetes/manifests/etcd.yaml /etc/kubernetes/manifests/kube-apiserver.yaml /etc/kubernetes/manifests/kube-controller-manager.yaml /etc/kubernetes/manifests/kube-scheduler.yaml /etc/kubernetes/controller-manager.conf /etc/kubernetes/scheduler.conf /kind/kubeadm.conf /var/lib/kubelet/kubeadm-flags.env ) local should_fix_certificate=false # fixup IPs in manifests ... curr_ipv4="$( (head -n1 <(timeout 5 getent ahostsv4 "$(hostname)") | cut -d' ' -f1) || true)" log_info "detected IPv4 address: ${curr_ipv4}" if [ -f /kind/old-ipv4 ]; then old_ipv4=$(cat /kind/old-ipv4) log_info "detected old IPv4 address: ${old_ipv4}" # sanity check that we have a current address if [[ -z $curr_ipv4 ]]; then log_error "have an old IPv4 address but no current IPv4 address (!)" exit 1 fi if [[ "${old_ipv4}" != "${curr_ipv4}" ]]; then should_fix_certificate=true sed_ipv4_command="s#\b$(regex_escape_ip "${old_ipv4}")\b#${curr_ipv4}#g" for f in "${files_to_update[@]}"; do # kubernetes manifests are only present on control-plane nodes if [[ -f "$f" ]]; then sed -i "${sed_ipv4_command}" "$f" fi done fi fi if [[ -n $curr_ipv4 ]]; then echo -n "${curr_ipv4}" >/kind/old-ipv4 fi # do IPv6 curr_ipv6="$( (head -n1 <(timeout 5 getent ahostsv6 "$(hostname)") | cut -d' ' -f1) || true)" log_info "detected IPv6 address: ${curr_ipv6}" if [ -f /kind/old-ipv6 ]; then old_ipv6=$(cat /kind/old-ipv6) log_info "detected old IPv6 address: ${old_ipv6}" # sanity check that we have a current address if [[ -z $curr_ipv6 ]]; then log_error "have an old IPv6 address but no current IPv6 address (!)" fi if [[ "${old_ipv6}" != "${curr_ipv6}" ]]; then should_fix_certificate=true sed_ipv6_command="s#\b$(regex_escape_ip "${old_ipv6}")\b#${curr_ipv6}#g" for f in "${files_to_update[@]}"; do # kubernetes manifests are only present on control-plane nodes if [[ -f "$f" ]]; then sed -i "${sed_ipv6_command}" "$f" fi done fi fi if [[ -n $curr_ipv6 ]]; then echo -n "${curr_ipv6}" >/kind/old-ipv6 fi if $should_fix_certificate; then fix_certificate fi } # validate state validate_userns # run pre-init fixups # NOTE: it's important that we do configure* first in this order to avoid races configure_containerd configure_proxy fix_mount fix_cgroup fix_machine_id fix_product_name fix_product_uuid select_iptables enable_network_magic # we want the command (expected to be systemd) to be PID1, so exec to it log_info 'starting init' exec "$@" kind-0.27.0/images/base/scripts/000077500000000000000000000000001475376161000164055ustar00rootroot00000000000000kind-0.27.0/images/base/scripts/target-cc000077500000000000000000000015001475376161000202000ustar00rootroot00000000000000#!/usr/bin/env bash # Copyright 2023 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # this script maps buildx TARGETARCH to $CC set -o errexit -o nounset -o pipefail case $TARGETARCH in arm64) echo -n 'aarch64-linux-gnu-gcc' ;; amd64) echo -n 'x86_64-linux-gnu-gcc' ;; *) exit 1 ;; esac kind-0.27.0/images/base/scripts/third_party/000077500000000000000000000000001475376161000207365ustar00rootroot00000000000000kind-0.27.0/images/base/scripts/third_party/gimme/000077500000000000000000000000001475376161000220345ustar00rootroot00000000000000kind-0.27.0/images/base/scripts/third_party/gimme/LICENSE000066400000000000000000000021021475376161000230340ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2015-2018 gimme contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. kind-0.27.0/images/base/scripts/third_party/gimme/README.md000066400000000000000000000002171475376161000233130ustar00rootroot00000000000000# gimme This is an unmodified copy of [gimme], so we don't have to download it from the internet. [gimme]: https://github.com/travis-ci/gimmekind-0.27.0/images/base/scripts/third_party/gimme/gimme000077500000000000000000000662441475376161000230740ustar00rootroot00000000000000#!/usr/bin/env bash # vim:noexpandtab:ts=2:sw=2: # #+ Usage: $(basename $0) [flags] [go-version] [version-prefix] #+ - #+ Version: ${GIMME_VERSION} #+ Copyright: ${GIMME_COPYRIGHT} #+ License URL: ${GIMME_LICENSE_URL} #+ - #+ Install go! There are multiple types of installations available, with 'auto' being the default. #+ If either 'auto' or 'binary' is specified as GIMME_TYPE, gimme will first check for an existing #+ go installation. This behavior may be disabled by providing '-f/--force/force' as first positional #+ argument. #+ - #+ Option flags: #+ -h --help help - show this help text and exit #+ -V --version version - show the version only and exit #+ -f --force force - remove the existing go installation if present prior to install #+ -l --list list - list installed go versions and exit #+ -k --known known - list known go versions and exit #+ --force-known-update - when used with --known, ignores the cache and updates #+ -r --resolve resolve - resolve a version specifier to a version, show that and exit #+ - #+ Influential env vars: #+ - #+ GIMME_GO_VERSION - version to install (*REQUIRED*, may be given as first positional arg) #+ GIMME_VERSION_PREFIX - prefix for installed versions (default '${GIMME_VERSION_PREFIX}', #+ may be given as second positional arg) #+ GIMME_ARCH - arch to install (default '${GIMME_ARCH}') #+ GIMME_BINARY_OSX - darwin-specific binary suffix (default '${GIMME_BINARY_OSX}') #+ GIMME_ENV_PREFIX - prefix for env files (default '${GIMME_ENV_PREFIX}') #+ GIMME_GO_GIT_REMOTE - git remote for git-based install (default '${GIMME_GO_GIT_REMOTE}') #+ GIMME_OS - os to install (default '${GIMME_OS}') #+ GIMME_TMP - temp directory (default '${GIMME_TMP}') #+ GIMME_TYPE - install type to perform ('auto', 'binary', 'source', or 'git') #+ (default '${GIMME_TYPE}') #+ GIMME_INSTALL_RACE - install race directory after compile if non-empty. #+ If the install type is 'binary', this option is ignored. #+ GIMME_DEBUG - enable tracing if non-empty #+ GIMME_NO_ENV_ALIAS - disable creation of env 'alias' file when os and arch match host #+ GIMME_SILENT_ENV - omit the 'go version' line from env file #+ GIMME_CGO_ENABLED - enable build of cgo support #+ GIMME_CC_FOR_TARGET - cross compiler for cgo support #+ GIMME_DOWNLOAD_BASE - override base URL dir for download (default '${GIMME_DOWNLOAD_BASE}') #+ GIMME_LIST_KNOWN - override base URL for known go versions (default '${GIMME_LIST_KNOWN}') #+ GIMME_KNOWN_CACHE_MAX - seconds the cache for --known is valid for (default '${GIMME_KNOWN_CACHE_MAX}') #+ - # set -e shopt -s nullglob shopt -s dotglob shopt -s extglob set -o pipefail [[ ${GIMME_DEBUG} ]] && set -x readonly GIMME_VERSION="v1.5.4" readonly GIMME_COPYRIGHT="Copyright (c) 2015-2020 gimme contributors" readonly GIMME_LICENSE_URL="https://raw.githubusercontent.com/travis-ci/gimme/${GIMME_VERSION}/LICENSE" export GIMME_VERSION export GIMME_COPYRIGHT export GIMME_LICENSE_URL program_name="$(basename "$0")" # shellcheck disable=SC1117 warn() { printf >&2 "%s: %s\n" "${program_name}" "${*}"; } die() { warn "$@" exit 1 } # We don't want to go around hitting Google's servers with requests for # files named HEAD@{date}.tar so we only try binary/source downloads if # it looks like a plausible name to us. # We don't need to support 0. releases of Go. # We don't support 5 digit major-versions of Go (limit back-tracking in RE). # We don't support very long versions # (both to avoid annoying download server operators with attacks and # because regexp backtracking can be pathological). # Per _assert_version_given we do assume 2.0 not 2 ALLOWED_UPSTREAM_VERSION_RE='^[1-9][0-9]{0,3}(\.[0-9][0-9a-zA-Z_-]{0,9})+$' # # The main path which allowed these to leak upstream before has been closed # but a valid git repo tag or branch-name will still reach the point of # being _tried_ upstream. # _do_curl "url" "file" _do_curl() { mkdir -p "$(dirname "${2}")" if command -v curl >/dev/null; then curl -sSLf "${1}" -o "${2}" 2>/dev/null return fi if command -v wget >/dev/null; then wget -q "${1}" -O "${2}" 2>/dev/null return fi if command -v fetch >/dev/null; then fetch -q "${1}" -o "${2}" 2>/dev/null return fi echo >&2 'error: no curl, wget, or fetch found' exit 1 } # _sha256sum "file" _sha256sum() { if command -v sha256sum &>/dev/null; then sha256sum "$@" elif command -v gsha256sum &>/dev/null; then gsha256sum "$@" else shasum -a 256 "$@" fi } # sort versions, handling 1.10 after 1.9, not before 1.2 # FreeBSD sort has --version-sort, none of the others do # Looks like --general-numeric-sort is the safest; checked macOS 10.12.6, FreeBSD 10.3, Ubuntu Trusty if sort --version-sort /dev/null; then _version_sort() { sort --version-sort; } else _version_sort() { # If we go to four-digit minor or patch versions, then extend the padding here # (but in such a world, perhaps --version-sort will have become standard by then?) sed -E 's/\.([0-9](\.|$))/.00\1/g; s/\.([0-9][0-9](\.|$))/.0\1/g' | sort --general-numeric-sort | sed 's/\.00*/./g' } fi # _do_curls "file" "url" ["url"...] _do_curls() { f="${1}" shift if _sha256sum -c "${f}.sha256" &>/dev/null; then return 0 fi for url in "${@}"; do if _do_curl "${url}" "${f}"; then if _do_curl "${url}.sha256" "${f}.sha256"; then echo "$(cat "${f}.sha256") ${f}" >"${f}.sha256.tmp" mv "${f}.sha256.tmp" "${f}.sha256" if ! _sha256sum -c "${f}.sha256" &>/dev/null; then warn "sha256sum failed for '${f}'" warn 'continuing to next candidate URL' continue fi fi return fi done rm -f "${f}" return 1 } # _binary "version" "file.tar.gz" "arch" _binary() { local version=${1} local file=${2} local arch=${3} urls=( "${GIMME_DOWNLOAD_BASE}/go${version}.${GIMME_OS}-${arch}.tar.gz" ) if [[ "${GIMME_OS}" == 'darwin' && "${GIMME_BINARY_OSX}" ]]; then urls=( "${GIMME_DOWNLOAD_BASE}/go${version}.${GIMME_OS}-${arch}-${GIMME_BINARY_OSX}.tar.gz" "${urls[@]}" ) fi if [ "${arch}" = 'arm' ]; then # attempt "armv6l" vs just "arm" first (since that's what's officially published) urls=( "${GIMME_DOWNLOAD_BASE}/go${version}.${GIMME_OS}-${arch}v6l.tar.gz" # go1.6beta2 & go1.6rc1 "${GIMME_DOWNLOAD_BASE}/go${version}.${GIMME_OS}-${arch}6.tar.gz" # go1.6beta1 "${urls[@]}" ) fi if [ "${GIMME_OS}" = 'windows' ]; then urls=( "${GIMME_DOWNLOAD_BASE}/go${version}.${GIMME_OS}-${arch}.zip" ) fi _do_curls "${file}" "${urls[@]}" } # _source "version" "file.src.tar.gz" _source() { urls=( "${GIMME_DOWNLOAD_BASE}/go${1}.src.tar.gz" "https://github.com/golang/go/archive/go${1}.tar.gz" ) _do_curls "${2}" "${urls[@]}" } # _fetch "dir" _fetch() { mkdir -p "$(dirname "${1}")" if [[ -d "${1}/.git" ]]; then ( cd "${1}" git remote set-url origin "${GIMME_GO_GIT_REMOTE}" git fetch -q --all && git fetch -q --tags ) return fi git clone -q "${GIMME_GO_GIT_REMOTE}" "${1}" } # _checkout "version" "dir" # NB: might emit a "renamed version" on stdout _checkout() { local spec="${1:?}" godir="${2:?}" # We are called twice, once during validation that a version was given and # later during build. We don't want to fetch twice, so we are fetching # during the validation only, in the caller. if [[ "${spec}" =~ ^[0-9a-f]{6,}$ ]]; then # We always treat this as a commit sha, whether instead of doing # branch tests etc. It looks like a commit sha and the Go maintainers # aren't daft enough to use pure hex for a tag or branch. git -C "$godir" reset -q --hard "${spec}" || return 1 return 0 fi # If spec looks like HEAD^{something} or HEAD^^^ then trying # origin/$spec would succeed but we'd write junk to the filesystem, # propagating annoying characters out. local retval probe_named disallow rev probe_named=1 disallow='[@^~:{}]' if [[ "${spec}" =~ $disallow ]]; then probe_named=0 [[ "${spec}" != "@" ]] || spec="HEAD" fi try_spec() { git -C "${godir}" reset -q --hard "$@" -- 2>/dev/null; } retval=1 if ((probe_named)); then retval=0 try_spec "origin/${spec}" || try_spec "origin/go${spec}" || { [[ "${spec}" == "tip" ]] && try_spec origin/master; } || try_spec "refs/tags/${spec}" || try_spec "refs/tags/go${spec}" || retval=1 fi if ((retval)); then retval=0 # We're about to reset anyway, if we succeed, so we should reset to a # known state before parsing what might be relative specs try_spec origin/master && rev="$(git -C "${godir}" rev-parse --verify -q "${spec}^{object}")" && try_spec "${rev}" && git -C "${godir}" rev-parse --verify -q --short=12 "${rev}" || retval=1 # that rev-parse prints to stdout, so we can affect the version seen fi unset -f try_spec return $retval } # _extract "file.tar.gz" "dir" _extract() { mkdir -p "${2}" if [[ "${1}" == *.tar.gz ]]; then tar -xf "${1}" -C "${2}" --strip-components 1 else unzip -q "${1}" -d "${2}" mv "${2}"/go/* "${2}" rmdir "${2}"/go fi } # _setup_bootstrap _setup_bootstrap() { local versions=("1.18" "1.17" "1.16" "1.15" "1.14" "1.13" "1.12" "1.11" "1.10" "1.9" "1.8" "1.7" "1.6" "1.5" "1.4") # try existing for v in "${versions[@]}"; do for candidate in "${GIMME_ENV_PREFIX}/go${v}"*".env"; do if [ -s "${candidate}" ]; then # shellcheck source=/dev/null GOROOT_BOOTSTRAP="$(source "${candidate}" 2>/dev/null && go env GOROOT)" export GOROOT_BOOTSTRAP return 0 fi done done # try binary for v in "${versions[@]}"; do if [ -n "$(_try_binary "${v}" "${GIMME_HOSTARCH}")" ]; then export GOROOT_BOOTSTRAP="${GIMME_VERSION_PREFIX}/go${v}.${GIMME_OS}.${GIMME_HOSTARCH}" return 0 fi done echo >&2 "Unable to setup go bootstrap from existing or binary" return 1 } # _compile "dir" _compile() { ( if grep -q GOROOT_BOOTSTRAP "${1}/src/make.bash" &>/dev/null; then _setup_bootstrap || return 1 fi cd "${1}" if [[ -d .git ]]; then git clean -dfx -q fi cd src export GOOS="${GIMME_OS}" GOARCH="${GIMME_ARCH}" export CGO_ENABLED="${GIMME_CGO_ENABLED}" export CC_FOR_TARGET="${GIMME_CC_FOR_TARGET}" local make_log="${1}/make.${GOOS}.${GOARCH}.log" if [[ "${GIMME_DEBUG}" -ge "2" ]]; then ./make.bash -v 2>&1 | tee "${make_log}" 1>&2 || return 1 else ./make.bash &>"${make_log}" || return 1 fi ) } _try_install_race() { if [[ ! "${GIMME_INSTALL_RACE}" ]]; then return 0 fi "${1}/bin/go" install -race std } _can_compile() { cat >"${GIMME_TMP}/test.go" <<'EOF' package main import "os" func main() { os.Exit(0) } EOF "${1}/bin/go" run "${GIMME_TMP}/test.go" } # _env "dir" _env() { [[ -d "${1}/bin" && -x "${1}/bin/go" ]] || return 1 # if we try to run a Darwin binary on Linux, we need to fail so 'auto' can fallback to cross-compiling from source # automatically GOROOT="${1}" GOFLAGS="" "${1}/bin/go" version &>/dev/null || return 1 # https://twitter.com/davecheney/status/431581286918934528 # we have to GOROOT sometimes because we use official release binaries in unofficial locations :( # # Issue 87 leads to: # No, we should _always_ set GOROOT when using official release binaries, and sanest to just always set it. # The "avoid setting it" is _only_ for people using official releases in official locations. # Tools like `gimme` are the reason that GOROOT-in-env exists. echo if [[ "$(GOROOT="${1}" "${1}/bin/go" env GOHOSTOS)" == "${GIMME_OS}" ]]; then echo 'unset GOOS;' else echo 'export GOOS="'"${GIMME_OS}"'";' fi if [[ "$(GOROOT="${1}" "${1}/bin/go" env GOHOSTARCH)" == "${GIMME_ARCH}" ]]; then echo 'unset GOARCH;' else echo 'export GOARCH="'"${GIMME_ARCH}"'";' fi echo "export GOROOT='${1}';" # shellcheck disable=SC2016 echo 'export PATH="'"${1}/bin"':${PATH}";' if [[ -z "${GIMME_SILENT_ENV}" ]]; then echo 'go version >&2;' fi echo } # _env_alias "dir" "env-file" _env_alias() { if [[ "${GIMME_NO_ENV_ALIAS}" ]]; then echo "${2}" return fi if [[ "$(GOROOT="${1}" "${1}/bin/go" env GOHOSTOS)" == "${GIMME_OS}" && "$(GOROOT="${1}" "${1}/bin/go" env GOHOSTARCH)" == "${GIMME_ARCH}" ]]; then # GIMME_GO_VERSION might be a branch, which can contain '/' local dest="${GIMME_ENV_PREFIX}/go${GIMME_GO_VERSION//\//__}.env" cp "${2}" "${dest}" ln -sf "${dest}" "${GIMME_ENV_PREFIX}/latest.env" echo "${dest}" else echo "${2}" fi } _try_existing() { case "${1}" in binary) local existing_ver="${GIMME_VERSION_PREFIX}/go${GIMME_GO_VERSION}.${GIMME_OS}.${GIMME_ARCH}" local existing_env="${GIMME_ENV_PREFIX}/go${GIMME_GO_VERSION}.${GIMME_OS}.${GIMME_ARCH}.env" ;; source) local existing_ver="${GIMME_VERSION_PREFIX}/go${GIMME_GO_VERSION}.src" local existing_env="${GIMME_ENV_PREFIX}/go${GIMME_GO_VERSION}.src.env" ;; *) _try_existing binary || _try_existing source return $? ;; esac if [[ -x "${existing_ver}/bin/go" && -s "${existing_env}" ]]; then # newer envs have existing semi-colon at end of line, because newer gimme # puts them there; envs created before that change lack those semi-colons # and should gain them, to make it easier for people using eval without # double-quoting the command substition. sed -e 's/\([^;]\)$/\1;/' <"${existing_env}" # gimme is the corner-case where GOROOT _should_ be overriden, since if the # ancilliary tooling's system-internal DefaultGoroot exists, and GOROOT is # unset, then it will be used and the wrong golang will be picked up. # Lots of old installs won't have GOROOT; munge it from $PATH if grep -qs '^unset GOROOT' -- "${existing_env}"; then sed -n -e 's/^export PATH="\(.*\)\/bin:.*$/export GOROOT='"'"'\1'"'"';/p' <"${existing_env}" echo fi # Export the same variables whether building new or using existing echo "export GIMME_ENV='${existing_env}';" return fi return 1 } # _try_binary "version" "arch" _try_binary() { local version=${1} local arch=${2} local bin_tgz="${GIMME_TMP}/go${version}.${GIMME_OS}.${arch}.tar.gz" local bin_dir="${GIMME_VERSION_PREFIX}/go${version}.${GIMME_OS}.${arch}" local bin_env="${GIMME_ENV_PREFIX}/go${version}.${GIMME_OS}.${arch}.env" [[ "${version}" =~ ${ALLOWED_UPSTREAM_VERSION_RE} ]] || return 1 if [ "${GIMME_OS}" = 'windows' ]; then bin_tgz=${bin_tgz%.tar.gz}.zip fi _binary "${version}" "${bin_tgz}" "${arch}" || return 1 _extract "${bin_tgz}" "${bin_dir}" || return 1 _env "${bin_dir}" | tee "${bin_env}" || return 1 echo "export GIMME_ENV=\"$(_env_alias "${bin_dir}" "${bin_env}")\"" } _try_source() { local src_tgz="${GIMME_TMP}/go${GIMME_GO_VERSION}.src.tar.gz" local src_dir="${GIMME_VERSION_PREFIX}/go${GIMME_GO_VERSION}.src" local src_env="${GIMME_ENV_PREFIX}/go${GIMME_GO_VERSION}.src.env" [[ "${GIMME_GO_VERSION}" =~ ${ALLOWED_UPSTREAM_VERSION_RE} ]] || return 1 _source "${GIMME_GO_VERSION}" "${src_tgz}" || return 1 _extract "${src_tgz}" "${src_dir}" || return 1 _compile "${src_dir}" || return 1 _try_install_race "${src_dir}" || return 1 _env "${src_dir}" | tee "${src_env}" || return 1 echo "export GIMME_ENV=\"$(_env_alias "${src_dir}" "${src_env}")\"" } # We do _not_ try to use any version caching with _try_existing(), but instead # build afresh each time. We don't want to deal with someone moving the repo # to other-version, doing an install, then resetting it back to # last-version-we-saw and thus introducing conflicts. # # If you want to re-use a built-at-spec version, then avoid moving the repo # and source the generated .env manually. # Note that the env will just refer to the 'go' directory, so it's not safe # to reuse anyway. _try_git() { local git_dir="${GIMME_VERSION_PREFIX}/go" local git_env="${GIMME_ENV_PREFIX}/go.git.${GIMME_OS}.${GIMME_ARCH}.env" local resolved_sha # Any tags should have been resolved when we asserted that we were # given a version, so no need to handle that here. _checkout "${GIMME_GO_VERSION}" "${git_dir}" >/dev/null || return 1 _compile "${git_dir}" || return 1 _try_install_race "${git_dir}" || return 1 _env "${git_dir}" | tee "${git_env}" || return 1 echo "export GIMME_ENV=\"$(_env_alias "${git_dir}" "${git_env}")\"" } _wipe_version() { local env_file="${GIMME_ENV_PREFIX}/go${1}.${GIMME_OS}.${GIMME_ARCH}.env" if [[ -s "${env_file}" ]]; then rm -rf "$(awk -F\" '/GOROOT/ { print $2 }' "${env_file}")" rm -f "${env_file}" fi } _list_versions() { if [ ! -d "${GIMME_VERSION_PREFIX}" ]; then return 0 fi local current_version current_version="$(go env GOROOT 2>/dev/null)" current_version="${current_version##*/go}" current_version="${current_version%%.${GIMME_OS}.*}" # 1.1 1.10 1.2 is bad; zsh has `setopt numeric_glob_sort` but bash # doesn't appear to have anything like that. for d in "${GIMME_VERSION_PREFIX}/go"*".${GIMME_OS}."*; do local cleaned="${d##*/go}" cleaned="${cleaned%%.${GIMME_OS}.*}" echo "${cleaned}" done | _version_sort | while read -r cleaned; do echo -en "${cleaned}" if [[ "${cleaned}" == "${current_version}" ]]; then echo -en ' <= current' >&2 fi echo done } _update_remote_known_list_if_needed() { # shellcheck disable=SC1117 local exp="go([[:alnum:]\.]*)\.src.*" # :alnum: catches beta versions too local list="${GIMME_VERSION_PREFIX}/known-versions.txt" local dlfile="${GIMME_TMP}/known-dl" if [[ -e "${list}" ]] && ! ((force_known_update)) && ! _file_older_than_secs "${list}" "${GIMME_KNOWN_CACHE_MAX}"; then echo "${list}" return 0 fi [[ -d "${GIMME_VERSION_PREFIX:?}" ]] || mkdir -p -- "${GIMME_VERSION_PREFIX}" _do_curl "${GIMME_LIST_KNOWN}" "${dlfile}" while read -r line; do if [[ "${line}" =~ ${exp} ]]; then echo "${BASH_REMATCH[1]}" fi done <"${dlfile}" | _version_sort | uniq >"${list}.new" rm -f "${list}" &>/dev/null mv "${list}.new" "${list}" rm -f "${dlfile}" echo "${list}" return 0 } _list_known() { local knownfile knownfile="$(_update_remote_known_list_if_needed)" ( _list_versions 2>/dev/null cat -- "${knownfile}" ) | grep . | _version_sort | uniq } # For the "invoked on commandline" case, we want to always pass unknown # strings through, so that we can be a uniqueness filter, but for unknown # names we want to exit with a value other than 1, so we document that # we'll exit 2. For use by other functions, 2 is as good as 1. _resolve_version() { case "${1}" in stable) _get_curr_stable return 0 ;; oldstable) _get_old_stable return 0 ;; tip) echo "tip" return 0 ;; *.x) true ;; *) echo "${1}" local GIMME_GO_VERSION="$1" local ASSERT_ABORT='return' if _assert_version_given 2>/dev/null; then return 0 fi warn "version specifier '${1}' unknown" return 2 ;; esac # We have a .x suffix local base="${1%.x}" local ver last='' known known="$(_update_remote_known_list_if_needed)" # will be version-sorted if [[ ! "${base}" =~ ^[0-9.]+$ ]]; then warn "resolve pattern '${base}.x' invalid for .x finding" return 2 fi # The `.x` is optional; "1.10" matches "1.10.x" local search="^${base//./\\.}(\\.[0-9.]+)?\$" # avoid regexp attacks while read -r ver; do [[ "${ver}" =~ $search ]] || continue last="${ver}" done <"$known" if [[ -n "${last}" ]]; then echo "${last}" return 0 fi echo "${1}" warn "given '${1}' but no release for '${base}' found" return 2 } _realpath() { # shellcheck disable=SC2005 [ -d "$1" ] && echo "$(cd "$1" && pwd)" || echo "$(cd "$(dirname "$1")" && pwd)/$(basename "$1")" } _get_curr_stable() { local stable="${GIMME_VERSION_PREFIX}/stable" if _file_older_than_secs "${stable}" 86400; then _update_stable "${stable}" fi cat "${stable}" } _get_old_stable() { local oldstable="${GIMME_VERSION_PREFIX}/oldstable" if _file_older_than_secs "${oldstable}" 86400; then _update_oldstable "${oldstable}" fi cat "${oldstable}" } _update_stable() { local stable="${1}" local url="https://golang.org/VERSION?m=text" _do_curl "${url}" "${stable}" sed -i.old -e 's/^go\(.*\)/\1/' "${stable}" rm -f "${stable}.old" } _update_oldstable() { local oldstable="${1}" local oldstable_x oldstable_x=$(_get_curr_stable | awk -F. '{ $2--; print $1 "." $2 "." "x" }') _resolve_version "${oldstable_x}" >"${oldstable}" } _last_mod_timestamp() { local filename="${1}" case "${GIMME_HOSTOS}" in darwin | *bsd) stat -f %m "${filename}" ;; linux) stat -c %Y "${filename}" ;; esac } _file_older_than_secs() { local file="${1}" local age_secs="${2}" local ts # if the file does not exist, we return true, as the cache needs updating ts="$(_last_mod_timestamp "${file}" 2>/dev/null)" || return 0 ((($(date +%s) - ts) > age_secs)) } _assert_version_given() { # By the time we're called, aliases such as "stable" must have been resolved # but we could be a reference in git. # # Versions can include suffices such as in "1.8beta2", so our assumption is that # there will always be a minor present; the first public release was "1.0" so # we assume "2.0" not "2". if [[ -z "${GIMME_GO_VERSION}" ]]; then echo >&2 'error: no GIMME_GO_VERSION supplied' echo >&2 " ex: GIMME_GO_VERSION=1.4.1 ${0} ${*}" echo >&2 " ex: ${0} 1.4.1 ${*}" ${ASSERT_ABORT:-exit} 1 fi # Note: _resolve_version calls back to us (_assert_version_given), but # only for cases where the version does not end with .x, so this should # be safe. # This should be untangled. PRs accepted, good starter project. if [[ "${GIMME_GO_VERSION}" == *.x ]]; then GIMME_GO_VERSION="$(_resolve_version "${GIMME_GO_VERSION}")" || ${ASSERT_ABORT:-exit} 1 fi if [[ "${GIMME_GO_VERSION}" == +([[:digit:]]).+([[:digit:]])* ]]; then return 0 fi # Here we resolve symbolic references. If we don't, then we get some # random git tag name being accepted as valid and then we try to # curl garbage from upstream. if [[ "${GIMME_TYPE}" == "auto" || "${GIMME_TYPE}" == "git" ]]; then local git_dir="${GIMME_VERSION_PREFIX}/go" local resolved_sha _fetch "${git_dir}" if resolved_sha="$(_checkout "${GIMME_GO_VERSION}" "${git_dir}")"; then if [[ -n "${resolved_sha}" ]]; then # Break our normal silence, this one really needs to be seen on stderr # always; auditability and knowing what version of Go you got wins. warn "resolved '${GIMME_GO_VERSION}' to '${resolved_sha}'" GIMME_GO_VERSION="${resolved_sha}" fi return 0 fi fi echo >&2 'error: GIMME_GO_VERSION not recognized as valid' echo >&2 " got: ${GIMME_GO_VERSION}" ${ASSERT_ABORT:-exit} 1 } _exclude_from_backups() { # Please avoid anything which requires elevated privileges or is obnoxious # enough to offend the invoker case "${GIMME_HOSTOS}" in darwin) # Darwin: Time Machine is "standard", we can add others. The default # mechanism is sticky, as an attribute on the dir, requires no # privileges, is idempotent (and doesn't support -- to end flags). tmutil addexclusion "$@" ;; esac } _versint() { IFS=" " read -r -a args <<<"${1//[^0-9]/ }" printf '1%03d%03d%03d%03d' "${args[@]}" } _to_goarch() { case "${1}" in aarch64) echo "arm64" ;; *) echo "${1}" ;; esac } : "${GIMME_OS:=$(uname -s | tr '[:upper:]' '[:lower:]')}" : "${GIMME_HOSTOS:=$(uname -s | tr '[:upper:]' '[:lower:]')}" : "${GIMME_ARCH:=$(_to_goarch "$(uname -m)")}" : "${GIMME_HOSTARCH:=$(_to_goarch "$(uname -m)")}" : "${GIMME_ENV_PREFIX:=${HOME}/.gimme/envs}" : "${GIMME_VERSION_PREFIX:=${HOME}/.gimme/versions}" : "${GIMME_TMP:=${TMPDIR:-/tmp}/gimme}" : "${GIMME_GO_GIT_REMOTE:=https://github.com/golang/go.git}" : "${GIMME_TYPE:=auto}" # 'auto', 'binary', 'source', or 'git' : "${GIMME_BINARY_OSX:=osx10.8}" : "${GIMME_DOWNLOAD_BASE:=https://dl.google.com/go}" : "${GIMME_LIST_KNOWN:=https://golang.org/dl}" : "${GIMME_KNOWN_CACHE_MAX:=10800}" # The version prefix must be an absolute path case "${GIMME_VERSION_PREFIX}" in /*) true ;; *) echo >&2 " Fixing GIMME_VERSION_PREFIX from relative: $GIMME_VERSION_PREFIX" GIMME_VERSION_PREFIX="$(pwd)/${GIMME_VERSION_PREFIX}" echo >&2 " to: $GIMME_VERSION_PREFIX" ;; esac case "${GIMME_OS}" in mingw* | msys_nt*) # Minimalist GNU for Windows GIMME_OS='windows' if [ "${GIMME_ARCH}" = 'i686' ]; then GIMME_ARCH="386" else GIMME_ARCH="amd64" fi ;; esac force_install=0 force_known_update=0 while [[ $# -gt 0 ]]; do case "${1}" in -h | --help | help | wat) _old_ifs="$IFS" IFS=';' awk '/^#\+ / { sub(/^#\+ /, "", $0) ; sub(/-$/, "", $0) ; print $0 }' "$0" | while read -r line; do eval "echo \"$line\"" done IFS="$_old_ifs" exit 0 ;; -V | --version | version) echo "${GIMME_VERSION}" exit 0 ;; -r | --resolve | resolve) # The normal mkdir of versions is below; we don't want to move it up # to where we create files just if asked our version; thus # _resolve_version has to mkdir the versions dir itself. if [[ $# -ge 2 ]]; then _resolve_version "${2}" elif [[ -n "${GIMME_GO_VERSION:-}" ]]; then _resolve_version "${GIMME_GO_VERSION}" else die "resolve must be given a version to resolve" fi exit $? ;; -l | --list | list) _list_versions exit 0 ;; -k | --known | known) _list_known exit 0 ;; -f | --force | force) force_install=1 ;; --force-known-update | force-known-update) force_known_update=1 ;; -i | install) true # ignore a dummy argument ;; *) break ;; esac shift done if [[ -n "${1}" ]]; then GIMME_GO_VERSION="${1}" fi if [[ -n "${2}" ]]; then GIMME_VERSION_PREFIX="${2}" fi case "${GIMME_ARCH}" in x86_64) GIMME_ARCH=amd64 ;; x86) GIMME_ARCH=386 ;; arm64) if [[ "${GIMME_GO_VERSION}" != master && "$(_versint "${GIMME_GO_VERSION}")" < "$(_versint 1.5)" ]]; then echo >&2 "error: ${GIMME_ARCH} is not supported by this go version" echo >&2 "try go1.5 or newer" exit 1 fi if [[ "${GIMME_HOSTOS}" == "linux" && "${GIMME_HOSTARCH}" != "${GIMME_ARCH}" ]]; then : "${GIMME_CC_FOR_TARGET:="aarch64-linux-gnu-gcc"}" fi ;; arm*) GIMME_ARCH=arm ;; esac case "${GIMME_HOSTARCH}" in x86_64) GIMME_HOSTARCH=amd64 ;; x86) GIMME_HOSTARCH=386 ;; arm64) ;; arm*) GIMME_HOSTARCH=arm ;; esac case "${GIMME_GO_VERSION}" in stable) GIMME_GO_VERSION=$(_get_curr_stable) ;; oldstable) GIMME_GO_VERSION=$(_get_old_stable) ;; esac _assert_version_given "$@" ((force_install)) && _wipe_version "${GIMME_GO_VERSION}" unset GOARCH unset GOBIN unset GOOS unset GOPATH unset GOROOT unset CGO_ENABLED unset CC_FOR_TARGET # GO111MODULE breaks build of Go itself unset GO111MODULE mkdir -p "${GIMME_VERSION_PREFIX}" "${GIMME_ENV_PREFIX}" # The envs dir stays small and provides a record of what had been installed # whereas the versions dir grows by hundreds of MB per version and is not # intended to support local modifications (as that subverts the point of gimme) # _and_ is a cache, so we're unilaterally declaring that the contents of # the versions dir should be excluded from system backups. _exclude_from_backups "${GIMME_VERSION_PREFIX}" GIMME_VERSION_PREFIX="$(_realpath "${GIMME_VERSION_PREFIX}")" GIMME_ENV_PREFIX="$(_realpath "${GIMME_ENV_PREFIX}")" if ! case "${GIMME_TYPE}" in binary) _try_existing binary || _try_binary "${GIMME_GO_VERSION}" "${GIMME_ARCH}" ;; source) _try_existing source || _try_source || _try_git ;; git) _try_git ;; auto) _try_existing || _try_binary "${GIMME_GO_VERSION}" "${GIMME_ARCH}" || _try_source || _try_git ;; *) echo >&2 "I don't know how to '${GIMME_TYPE}'." echo >&2 " Try 'auto', 'binary', 'source', or 'git'." exit 1 ;; esac; then echo >&2 "I don't have any idea what to do with '${GIMME_GO_VERSION}'." echo >&2 " (using download type '${GIMME_TYPE}')" exit 1 fi kind-0.27.0/images/haproxy/000077500000000000000000000000001475376161000154765ustar00rootroot00000000000000kind-0.27.0/images/haproxy/Dockerfile000066400000000000000000000050761475376161000175000ustar00rootroot00000000000000# Copyright 2019 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # This image is a haproxy image + minimal config so the container will not exit # while we rewrite the config at runtime and signal haproxy to reload. ARG BASE="registry.k8s.io/build-image/debian-base:bullseye-v1.4.3" FROM ${BASE} AS build # NOTE: copyrights.tar.gz is a quirk of Kubernetes's debian-base image # We extract these here so we can grab the relevant files are easily # staged for copying into our final image. RUN [ ! -f /usr/share/copyrights.tar.gz ] || tar -C / -xzvf /usr/share/copyrights.tar.gz # install: # - haproxy (see: https://haproxy.debian.net/) # - bash (ldd is a bash script and debian-base removes bash) # - procps (for `kill` which kind needs) RUN apt update && \ apt install -y --no-install-recommends haproxy=2.2.\* \ procps bash # copy in script for staging distro provided binary to distroless COPY --chmod=0755 stage-binary-and-deps.sh /usr/local/bin/ # stage everything for copying into the final image # NOTE: kind currently also uses "mkdir" and "cp" to write files within the container # TODO: mkdir especially should be unnecessary, with a little refactoring # NOTE: kill is used to signal haproxy to reload ARG STAGE_DIR="/opt/stage" RUN mkdir -p "${STAGE_DIR}" && \ stage-binary-and-deps.sh haproxy "${STAGE_DIR}" && \ stage-binary-and-deps.sh cp "${STAGE_DIR}" && \ stage-binary-and-deps.sh mkdir "${STAGE_DIR}" && \ stage-binary-and-deps.sh kill "${STAGE_DIR}" && \ find "${STAGE_DIR}" ################################################################################ # See: https://github.com/GoogleContainerTools/distroless/tree/main/base # This has /etc/passwd, tzdata, cacerts FROM "gcr.io/distroless/static-debian11" ARG STAGE_DIR="/opt/stage" # copy staged binary + deps + copyright COPY --from=build "${STAGE_DIR}/" / # add our minimal config COPY haproxy.cfg /usr/local/etc/haproxy/haproxy.cfg # below roughly matches the standard haproxy image STOPSIGNAL SIGUSR1 ENTRYPOINT ["haproxy", "-W", "-db", "-f", "/usr/local/etc/haproxy/haproxy.cfg"] kind-0.27.0/images/haproxy/Makefile000066400000000000000000000000471475376161000171370ustar00rootroot00000000000000include $(CURDIR)/../Makefile.common.inkind-0.27.0/images/haproxy/README.md000066400000000000000000000007131475376161000167560ustar00rootroot00000000000000# haproxy This image is used internally by kind to implement kubeadm's "HA" mode, specifically to load balance the API server. We cannot merely use the upstream haproxy image as haproxy will exit without a minimal config, so we introduce one that will list on the intended port and hot reload it at runtime with the actual desired config. ## Building You can `make quick` in this directory to build a test image. To push an actual image use `make push`. kind-0.27.0/images/haproxy/cloudbuild.yaml000066400000000000000000000003711475376161000205110ustar00rootroot00000000000000# See https://cloud.google.com/cloud-build/docs/build-config options: substitution_option: ALLOW_LOOSE machineType: E2_HIGHCPU_8 steps: - name: gcr.io/k8s-testimages/krte:latest-master entrypoint: make args: ['-C', 'images/haproxy', 'push'] kind-0.27.0/images/haproxy/haproxy.cfg000077500000000000000000000016241475376161000176570ustar00rootroot00000000000000# Copyright 2019 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # minimal config file to avoid haproxy exiting due to invalid / missing config # kind will rewrite this config at runtime global # limit memory usage to approximately 18 MB maxconn 100000 frontend controlPlane bind 0.0.0.0:6443 mode tcp default_backend kube-apiservers backend kube-apiservers mode tcp kind-0.27.0/images/haproxy/stage-binary-and-deps.sh000077500000000000000000000063401475376161000221160ustar00rootroot00000000000000#!/bin/bash # Copyright 2021 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # USAGE: stage-binary-and-deps.sh haproxy /opt/stage # # Stages $1 and it's dependencies + their copyright files to $2 # # This is intended to be used in a multi-stage docker build with a distroless/base # or distroless/cc image. set -o errexit set -o nounset set -o pipefail # file_to_package identifies the debian package that provided the file $1 file_to_package() { # `dpkg-query --search $file-pattern` outputs lines with the format: "$package: $file-path" # where $file-path belongs to $package # https://manpages.debian.org/jessie/dpkg/dpkg-query.1.en.html dpkg-query --search "$(realpath "${1}")" | cut -d':' -f1 } # package_to_copyright gives the path to the copyright file for the package $1 package_to_copyright() { echo "/usr/share/doc/${1}/copyright" } # stage_file stages the filepath $1 to $2, following symlinks # and staging copyrights stage_file() { cp -a --parents "${1}" "${2}" # recursively follow symlinks if [[ -L "${1}" ]]; then stage_file "$(cd "$(dirname "${1}")"; realpath -s "$(readlink "${1}")")" "${2}" fi # get the package so we can stage package metadata as well package="$(file_to_package "${1}")" # stage the copyright for the file cp -a --parents "$(package_to_copyright "${package}")" "${2}" # stage the package status mimicking bazel # https://github.com/bazelbuild/rules_docker/commit/f5432b813e0a11491cf2bf83ff1a923706b36420 # instead of parsing the control file, we can just get the actual package status with dpkg dpkg -s "${package}" > "${2}/var/lib/dpkg/status.d/${package}" } # binary_to_libraries identifies the library files needed by the binary $1 with ldd binary_to_libraries() { # see: https://man7.org/linux/man-pages/man1/ldd.1.html ldd "${1}" \ `# strip the leading '${name} => ' if any so only '/lib-foo.so (0xf00)' remains` \ | sed -E 's#.* => /#/#' \ `# we want only the path remaining, not the (0x${LOCATION})` \ | awk '{print $1}' \ `# linux-vdso.so.1 is a special virtual shared object from the kernel` \ `# see: http://man7.org/linux/man-pages/man7/vdso.7.html` \ | grep -v 'linux-vdso.so.1' } # main script logic main(){ local BINARY=$1 local STAGE_DIR="${2}/" # locate the path to the binary local binary_path binary_path="$(which "${BINARY}")" # ensure package metadata dir mkdir -p "${STAGE_DIR}"/var/lib/dpkg/status.d/ # stage the binary itself stage_file "${binary_path}" "${STAGE_DIR}" # stage the dependencies of the binary while IFS= read -r c_dep; do stage_file "${c_dep}" "${STAGE_DIR}" done < <(binary_to_libraries "${binary_path}") } main "$@" kind-0.27.0/images/kindnetd/000077500000000000000000000000001475376161000156045ustar00rootroot00000000000000kind-0.27.0/images/kindnetd/Dockerfile000066400000000000000000000031511475376161000175760ustar00rootroot00000000000000# Copyright 2019 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # first stage build kindnetd binary # NOTE: the actual go version will be overridden FROM --platform=$BUILDPLATFORM docker.io/library/golang:latest WORKDIR /go/src COPY --chmod=0755 scripts/third_party/gimme/gimme /usr/local/bin/ # make deps fetching cacheable COPY go.mod go.sum ./ # set by makefile to .go-version ARG GO_VERSION RUN eval "$(gimme "${GO_VERSION}")" \ && export GOTOOLCHAIN="go${GO_VERSION}" \ && go mod download \ && GOBIN=/usr/local/bin go install github.com/google/go-licenses@latest # build COPY . . ARG TARGETARCH RUN eval "$(gimme "${GO_VERSION}")" \ && export GOTOOLCHAIN="go${GO_VERSION}" \ && CGO_ENABLED=0 GOARCH=$TARGETARCH go build -o ./kindnetd ./cmd/kindnetd \ && GOARCH=$TARGETARCH go-licenses save --save_path=/_LICENSES ./cmd/kindnetd # build real kindnetd image FROM registry.k8s.io/build-image/distroless-iptables:v0.5.5 COPY --from=0 --chown=root:root ./go/src/kindnetd /bin/kindnetd COPY --from=0 /_LICENSES/* /LICENSES/ COPY --chmod=0644 files/LICENSES/* /LICENSES/* CMD ["/bin/kindnetd"] kind-0.27.0/images/kindnetd/LICENSE000066400000000000000000000261351475376161000166200ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright {yyyy} {name of copyright owner} Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. kind-0.27.0/images/kindnetd/Makefile000066400000000000000000000000471475376161000172450ustar00rootroot00000000000000include $(CURDIR)/../Makefile.common.inkind-0.27.0/images/kindnetd/README.md000066400000000000000000000016741475376161000170730ustar00rootroot00000000000000# kindnetd `kindnetd` is a simple networking daemon with the following responsibilities: - IP masquerade (of traffic leaving the nodes that is headed out of the cluster) - Ensuring netlink routes to pod CIDRs via the host node IP for each - Ensuring a simple CNI config based on the standard [ptp] / [host-local] [plugins] and the node's pod CIDR kindnetd is based on [aojea/kindnet] which is in turn based on [leblancd/kube-v6-test]. We use this to implement KIND's standard CNI / cluster networking configuration. ## Building cd to this directory on mac / linux with docker installed and run `make quick`. To push an image run `make push`. [ptp]: https://www.cni.dev/plugins/current/main/ptp/ [host-local]: https://www.cni.dev/plugins/current/ipam/host-local/ [plugins]: https://github.com/containernetworking/plugins [aojea/kindnet]: https://github.com/aojea/kindnet [leblancd/kube-v6-test]: https://github.com/leblancd/kube-v6-test/tree/master kind-0.27.0/images/kindnetd/cloudbuild.yaml000066400000000000000000000003731475376161000206210ustar00rootroot00000000000000# See https://cloud.google.com/cloud-build/docs/build-config options: substitution_option: ALLOW_LOOSE machineType: E2_HIGHCPU_32 steps: - name: gcr.io/k8s-testimages/krte:latest-master entrypoint: make args: ['-C', 'images/kindnetd', 'push'] kind-0.27.0/images/kindnetd/cmd/000077500000000000000000000000001475376161000163475ustar00rootroot00000000000000kind-0.27.0/images/kindnetd/cmd/kindnetd/000077500000000000000000000000001475376161000201475ustar00rootroot00000000000000kind-0.27.0/images/kindnetd/cmd/kindnetd/cni.go000066400000000000000000000101231475376161000212440ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package main import ( "errors" "fmt" "io" stdnet "net" "os" "reflect" "text/template" corev1 "k8s.io/api/core/v1" ) /* cni config management */ // CNIConfigInputs is supplied to the CNI config template type CNIConfigInputs struct { PodCIDRs []string DefaultRoutes []string Mtu int } // ComputeCNIConfigInputs computes the template inputs for CNIConfigWriter func ComputeCNIConfigInputs(node *corev1.Node) CNIConfigInputs { defaultRoutes := []string{"0.0.0.0/0", "::/0"} // check if is a dualstack cluster if len(node.Spec.PodCIDRs) > 1 { return CNIConfigInputs{ PodCIDRs: node.Spec.PodCIDRs, DefaultRoutes: defaultRoutes, } } // the cluster is single stack // we use the legacy node.Spec.PodCIDR for backwards compatibility podCIDRs := []string{node.Spec.PodCIDR} // This is a single stack cluster defaultRoute := defaultRoutes[:1] if isIPv6CIDRString(podCIDRs[0]) { defaultRoute = defaultRoutes[1:] } return CNIConfigInputs{ PodCIDRs: podCIDRs, DefaultRoutes: defaultRoute, } } // computeBridgeMTU finds the mtu for the eth0 interface // otherwise it defaults to ptp default behavior of being set by kernel func computeBridgeMTU() (int, error) { interfaces, err := stdnet.Interfaces() if err != nil { return 0, err } for _, inter := range interfaces { if inter.Name == "eth0" { return inter.MTU, nil } } return 0, errors.New("Found no eth0 device") } // cniConfigPath is where kindnetd will write the computed CNI config const cniConfigPath = "/etc/cni/net.d/10-kindnet.conflist" const cniConfigTemplate = ` { "cniVersion": "0.3.1", "name": "kindnet", "plugins": [ { "type": "ptp", "ipMasq": false, "ipam": { "type": "host-local", "dataDir": "/run/cni-ipam-state", "routes": [ {{$first := true}} {{- range $route := .DefaultRoutes}} {{if $first}}{{$first = false}}{{else}},{{end}} { "dst": "{{ $route }}" } {{- end}} ], "ranges": [ {{$first := true}} {{- range $cidr := .PodCIDRs}} {{if $first}}{{$first = false}}{{else}},{{end}} [ { "subnet": "{{ $cidr }}" } ] {{- end}} ] } {{if .Mtu}}, "mtu": {{ .Mtu }} {{end}} }, { "type": "portmap", "capabilities": { "portMappings": true } } ] } ` // CNIConfigWriter no-ops re-writing config with the same inputs // NOTE: should only be called from a single goroutine type CNIConfigWriter struct { path string lastInputs CNIConfigInputs mtu int } // Write will write the config based on func (c *CNIConfigWriter) Write(inputs CNIConfigInputs) error { inputs.Mtu = c.mtu if reflect.DeepEqual(inputs, c.lastInputs) { return nil } // use an extension not recognized by CNI to write the contents initially // https://github.com/containerd/go-cni/blob/891c2a41e18144b2d7921f971d6c9789a68046b2/opts.go#L170 // then we can rename to atomically make the file appear f, err := os.Create(c.path + ".temp") if err != nil { return err } // actually write the config if err := writeCNIConfig(f, cniConfigTemplate, inputs); err != nil { f.Close() os.Remove(f.Name()) return err } _ = f.Sync() _ = f.Close() // then we can rename to the target config path if err := os.Rename(f.Name(), c.path); err != nil { return err } // we're safely done now, record the inputs c.lastInputs = inputs return nil } func writeCNIConfig(w io.Writer, rawTemplate string, data CNIConfigInputs) error { t, err := template.New("cni-json").Parse(rawTemplate) if err != nil { return fmt.Errorf("failed to parse cni template: %w", err) } return t.Execute(w, &data) } kind-0.27.0/images/kindnetd/cmd/kindnetd/main.go000066400000000000000000000272611475376161000214320ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package main import ( "context" "flag" "fmt" "net" "os" "os/signal" "strings" "syscall" "time" "golang.org/x/sys/unix" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" "k8s.io/klog/v2" "sigs.k8s.io/kube-network-policies/pkg/networkpolicy" ) const ( probeTCPtimeout = 1 * time.Second ) // kindnetd is a simple networking daemon to complete kind's CNI implementation // kindnetd will ensure routes to the other node's PodCIDR via their InternalIP // kindnetd will ensure pod to pod communication will not be masquerade // kindnetd will also write a templated cni config supplied with PodCIDR // // input envs: // - HOST_IP: should be populated by downward API // - POD_IP: should be populated by downward API // - CNI_CONFIG_TEMPLATE: the cni .conflist template, run with {{ .PodCIDR }} // - CONTROL_PLANE_ENDPOINT: control-plane endpoint format host:port // TODO: improve logging & error handling // IPFamily defines kindnet networking operating model type IPFamily string const ( // IPv4Family sets IPFamily to ipv4 IPv4Family IPFamily = "ipv4" // IPv6Family sets IPFamily to ipv6 IPv6Family IPFamily = "ipv6" // DualStackFamily sets ClusterIPFamily to DualStack DualStackFamily IPFamily = "dualstack" ) func main() { // enable logging klog.InitFlags(nil) _ = flag.Set("logtostderr", "true") flag.Parse() // create a Kubernetes client config, err := rest.InClusterConfig() if err != nil { panic(err.Error()) } // use protobuf to improve performance config.AcceptContentTypes = "application/vnd.kubernetes.protobuf,application/json" config.ContentType = "application/vnd.kubernetes.protobuf" // override the internal apiserver endpoint to avoid // waiting for kube-proxy to install the services rules. // If the endpoint is not reachable, fallback the internal endpoint controlPlaneEndpoint := os.Getenv("CONTROL_PLANE_ENDPOINT") if controlPlaneEndpoint != "" { // check that the apiserver is reachable before continue // to fail fast and avoid waiting until the client operations timeout var ok bool for i := 0; i < 5; i++ { ok = probeTCP(controlPlaneEndpoint, probeTCPtimeout) if ok { config.Host = "https://" + controlPlaneEndpoint break } klog.Infof("apiserver not reachable, attempt %d ... retrying", i) time.Sleep(time.Second * time.Duration(i)) } } // create the clientset to connect the apiserver clientset, err := kubernetes.NewForConfig(config) if err != nil { panic(err.Error()) } klog.Infof("connected to apiserver: %s", config.Host) // trap Ctrl+C and call cancel on the context ctx := context.Background() ctx, cancel := context.WithCancel(ctx) // Enable signal handler signalCh := make(chan os.Signal, 2) defer func() { close(signalCh) cancel() }() signal.Notify(signalCh, os.Interrupt, unix.SIGINT) go func() { select { case <-signalCh: klog.Infof("Exiting: received signal") cancel() case <-ctx.Done(): } }() informersFactory := informers.NewSharedInformerFactory(clientset, 0) nodeInformer := informersFactory.Core().V1().Nodes() nodeLister := nodeInformer.Lister() // obtain the host and pod ip addresses // if both ips are different we are not using the host network hostIP, podIP := os.Getenv("HOST_IP"), os.Getenv("POD_IP") klog.Infof("hostIP = %s\npodIP = %s\n", hostIP, podIP) if hostIP != podIP { klog.Warningf( "hostIP(= %q) != podIP(= %q) but must be running with host network: ", hostIP, podIP, ) } mtu, err := computeBridgeMTU() klog.Infof("setting mtu %d for CNI \n", mtu) if err != nil { klog.Infof("Failed to get MTU size from interface eth0, using kernel default MTU size error:%v", err) } // used to track if the cni config inputs changed and write the config cniConfigWriter := &CNIConfigWriter{ path: cniConfigPath, mtu: mtu, } // enforce ip masquerade rules podSubnetEnv := os.Getenv("POD_SUBNET") if podSubnetEnv == "" { panic("missing environment variable POD_SUBNET") } podSubnetEnv = strings.TrimSpace(podSubnetEnv) podSubnets := strings.Split(podSubnetEnv, ",") clusterIPv4Subnets, clusterIPv6Subnets := splitCIDRs(podSubnets) // detect the cluster IP family based on the Cluster CIDR aka PodSubnet var ipFamily IPFamily if len(clusterIPv4Subnets) > 0 && len(clusterIPv6Subnets) > 0 { ipFamily = DualStackFamily } else if len(clusterIPv6Subnets) > 0 { ipFamily = IPv6Family } else if len(clusterIPv4Subnets) > 0 { ipFamily = IPv4Family } else { panic(fmt.Sprintf("podSubnets ClusterCIDR/Pod_Subnet: %v", podSubnetEnv)) } klog.Infof("kindnetd IP family: %q", ipFamily) // create an ipMasqAgent for IPv4 if len(clusterIPv4Subnets) > 0 { klog.Infof("noMask IPv4 subnets: %v", clusterIPv4Subnets) masqAgentIPv4, err := NewIPMasqAgent(false, clusterIPv4Subnets) if err != nil { panic(err.Error()) } go func() { if err := masqAgentIPv4.SyncRulesForever(ctx, time.Second*60); err != nil { panic(err) } }() } // create an ipMasqAgent for IPv6 if len(clusterIPv6Subnets) > 0 { klog.Infof("noMask IPv6 subnets: %v", clusterIPv6Subnets) masqAgentIPv6, err := NewIPMasqAgent(true, clusterIPv6Subnets) if err != nil { panic(err.Error()) } go func() { if err := masqAgentIPv6.SyncRulesForever(ctx, time.Second*60); err != nil { panic(err) } }() } // setup nodes reconcile function, closes over arguments reconcileNodes := makeNodesReconciler(cniConfigWriter, hostIP, ipFamily) // network policies // on kind nodes the hostname matches the node name nodeName, err := os.Hostname() if err != nil { klog.Fatalf("couldn't determine hostname: %v", err) } cfg := networkpolicy.Config{ FailOpen: true, QueueID: 101, NodeName: nodeName, NetfilterBug1766Fix: true, NFTableName: "kindnet-network-policies", } networkPolicyController, err := networkpolicy.NewController( clientset, informersFactory.Networking().V1().NetworkPolicies(), informersFactory.Core().V1().Namespaces(), informersFactory.Core().V1().Pods(), nodeInformer, nil, nil, nil, cfg) if err != nil { klog.Infof("Error creating network policy controller: %v, skipping network policies", err) } else { go func() { _ = networkPolicyController.Run(ctx) }() } // main control loop informersFactory.Start(ctx.Done()) ticker := time.NewTicker(10 * time.Second) defer ticker.Stop() for { var nodes []*corev1.Node var err error for i := 0; i < 5; i++ { nodes, err = nodeLister.List(labels.Everything()) if err == nil { break } klog.Infof("Failed to get nodes, retrying after error: %v", err) time.Sleep(time.Second * time.Duration(i)) } if err != nil { panic("Reached maximum retries obtaining node list: " + err.Error()) } // reconcile the nodes with retries for i := 0; i < 5; i++ { err = reconcileNodes(nodes) if err == nil { break } klog.Infof("Failed to reconcile routes, retrying after error: %v", err) time.Sleep(time.Second * time.Duration(i)) } if err != nil { panic("Maximum retries reconciling node routes: " + err.Error()) } // rate limit select { case <-ctx.Done(): // grace period to cleanup resources time.Sleep(1 * time.Second) return case <-ticker.C: } } } // nodeNodesReconciler returns a reconciliation func for nodes func makeNodesReconciler(cniConfig *CNIConfigWriter, hostIP string, ipFamily IPFamily) func([]*corev1.Node) error { // reconciles a node reconcileNode := func(node *corev1.Node) error { // first get this node's IPs // we don't support more than one IP address per IP family for simplification nodeIPs := internalIPs(node) klog.Infof("Handling node with IPs: %v\n", nodeIPs) // This is our node. We don't need to add routes, // but we might need to update the cni config if nodeIPs.Has(hostIP) { klog.Info("handling current node\n") // compute the current cni config inputs if err := cniConfig.Write( ComputeCNIConfigInputs(node), ); err != nil { return err } // we're done handling this node return nil } // This is another node. Add routes to the POD subnets in the other nodes // don't do anything unless there is a non-empty PodCIDR var podCIDRs []string if ipFamily == DualStackFamily { podCIDRs = node.Spec.PodCIDRs } else if node.Spec.PodCIDR != "" { podCIDRs = []string{node.Spec.PodCIDR} } if len(podCIDRs) == 0 { fmt.Printf("Node %v has no CIDR, ignoring\n", node.Name) return nil } klog.Infof("Node %v has CIDR %s \n", node.Name, podCIDRs) podCIDRsv4, podCIDRsv6 := splitCIDRs(podCIDRs) // obtain the PodCIDR gateway var nodeIPv4, nodeIPv6 string for _, ip := range nodeIPs.UnsortedList() { if isIPv6String(ip) { nodeIPv6 = ip } else { nodeIPv4 = ip } } if nodeIPv4 != "" && len(podCIDRsv4) > 0 { if err := syncRoute(nodeIPv4, podCIDRsv4); err != nil { return err } } if nodeIPv6 != "" && len(podCIDRsv6) > 0 { if err := syncRoute(nodeIPv6, podCIDRsv6); err != nil { return err } } return nil } // return a reconciler for all the nodes return func(nodes []*corev1.Node) error { for _, node := range nodes { if err := reconcileNode(node); err != nil { return err } } return nil } } // internalIPs returns the internal IP addresses for node func internalIPs(node *corev1.Node) sets.Set[string] { ips := sets.New[string]() // check the node.Status.Addresses for _, address := range node.Status.Addresses { if address.Type == "InternalIP" { ips.Insert(address.Address) } } return ips } // splitCIDRs given a slice of strings with CIDRs it returns 2 slice of strings per IP family // The order returned is always v4 v6 func splitCIDRs(cidrs []string) ([]string, []string) { var v4subnets, v6subnets []string for _, subnet := range cidrs { if isIPv6CIDRString(subnet) { v6subnets = append(v6subnets, subnet) } else { v4subnets = append(v4subnets, subnet) } } return v4subnets, v6subnets } // Modified from agnhost connect command in k/k // https://github.com/kubernetes/kubernetes/blob/c241a237f9a635286c76c20d07b103a663b1cfa4/test/images/agnhost/connect/connect.go#L66 func probeTCP(address string, timeout time.Duration) bool { klog.Infof("probe TCP address %s", address) if _, err := net.ResolveTCPAddr("tcp", address); err != nil { klog.Warningf("DNS problem %s: %v", address, err) return false } conn, err := net.DialTimeout("tcp", address, timeout) if err == nil { conn.Close() return true } if opErr, ok := err.(*net.OpError); ok { if opErr.Timeout() { klog.Warningf("TIMEOUT %s", address) } else if syscallErr, ok := opErr.Err.(*os.SyscallError); ok { if syscallErr.Err == syscall.ECONNREFUSED { klog.Warningf("REFUSED %s", address) } } return false } klog.Warningf("OTHER %s: %v", address, err) return false } // isIPv6String returns if ip is IPv6. func isIPv6String(ip string) bool { netIP := net.ParseIP(ip) return netIP != nil && netIP.To4() == nil } // isIPv6CIDRString returns if cidr is IPv6. // This assumes cidr is a valid CIDR. func isIPv6CIDRString(cidr string) bool { ip, _, _ := net.ParseCIDR(cidr) return ip != nil && ip.To4() == nil } kind-0.27.0/images/kindnetd/cmd/kindnetd/masq.go000066400000000000000000000072771475376161000214540ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package main import ( "context" "errors" "fmt" "time" "github.com/coreos/go-iptables/iptables" ) // NewIPMasqAgent returns a new IPMasqAgent func NewIPMasqAgent(ipv6 bool, noMasqueradeCIDRs []string) (*IPMasqAgent, error) { protocol := iptables.ProtocolIPv4 if ipv6 { protocol = iptables.ProtocolIPv6 } ipt, err := iptables.NewWithProtocol(protocol) if err != nil { return nil, err } // TODO: validate cidrs return &IPMasqAgent{ iptables: ipt, masqChain: masqChainName, noMasqueradeCIDRs: noMasqueradeCIDRs, }, nil } // IPMasqAgent is based on https://github.com/kubernetes-incubator/ip-masq-agent // but collapsed into kindnetd and made ipv6 aware in an opinionated and simplified // fashion using "github.com/coreos/go-iptables" type IPMasqAgent struct { iptables *iptables.IPTables masqChain string noMasqueradeCIDRs []string } // SyncRulesForever syncs ip masquerade rules forever // these rules only needs to be installed once, but we run it periodically to check that are // not deleted by an external program. It fails if can't sync the rules during 3 iterations func (ma *IPMasqAgent) SyncRulesForever(ctx context.Context, interval time.Duration) error { var errs []error ticker := time.NewTicker(interval) defer ticker.Stop() for { if err := ma.SyncRules(); err != nil { errs = append(errs, fmt.Errorf("failed to synchronize rules at %s: %v", time.Now(), err)) if len(errs) > 3 { return fmt.Errorf("Can't synchronize rules after 3 attempts: %w", err) } } else { errs = errs[:0] } select { case <-ctx.Done(): return errors.Join(errs...) case <-ticker.C: } } } // name of nat chain for iptables masquerade rules const masqChainName = "KIND-MASQ-AGENT" // SyncRules syncs ip masquerade rules func (ma *IPMasqAgent) SyncRules() error { // make sure our custom chain for non-masquerade exists exists := false chains, err := ma.iptables.ListChains("nat") if err != nil { return fmt.Errorf("failed to list chains: %v", err) } for _, ch := range chains { if ch == ma.masqChain { exists = true break } } if !exists { if err = ma.iptables.NewChain("nat", ma.masqChain); err != nil { return err } } // Packets to this network should not be masquerade, pods should be able to talk to other pods for _, cidr := range ma.noMasqueradeCIDRs { if err := ma.iptables.AppendUnique("nat", ma.masqChain, "-d", cidr, "-j", "RETURN", "-m", "comment", "--comment", "kind-masq-agent: local traffic is not subject to MASQUERADE"); err != nil { return err } } // Masquerade all the other traffic if err := ma.iptables.AppendUnique("nat", ma.masqChain, "-j", "MASQUERADE", "-m", "comment", "--comment", "kind-masq-agent: outbound traffic is subject to MASQUERADE (must be last in chain)"); err != nil { return err } // Send all non-LOCAL destination traffic to our custom KIND-MASQ-AGENT chain return ma.iptables.AppendUnique("nat", "POSTROUTING", "-m", "addrtype", "!", "--dst-type", "LOCAL", "-j", ma.masqChain, "-m", "comment", "--comment", "kind-masq-agent: ensure nat POSTROUTING directs all non-LOCAL destination traffic to our custom KIND-MASQ-AGENT chain") } kind-0.27.0/images/kindnetd/cmd/kindnetd/routes.go000066400000000000000000000034211475376161000220170ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package main import ( "net" "github.com/vishvananda/netlink" "github.com/vishvananda/netlink/nl" "k8s.io/klog/v2" ) func syncRoute(nodeIP string, podCIDRs []string) error { ip := net.ParseIP(nodeIP) for _, podCIDR := range podCIDRs { // parse subnet dst, err := netlink.ParseIPNet(podCIDR) if err != nil { return err } // Declare the wanted route. routeToDst := netlink.Route{Dst: dst, Gw: ip} // List all routes which have the same dst set. // RouteListFiltered ignores the gw for filtering because of the passed filterMask. routes, err := netlink.RouteListFiltered(nl.GetIPFamily(ip), &routeToDst, netlink.RT_FILTER_DST) if err != nil { return err } // Check if the wanted route exists and delete wrong routes found := false for _, route := range routes { if route.Gw.Equal(ip) { found = true continue } // Delete wrong route because of invalid gateway. klog.Infof("Removing invalid route %v\n", route) if err := netlink.RouteDel(&route); err != nil { return err } } // Add route if not present if !found { klog.Infof("Adding route %v \n", routeToDst) if err := netlink.RouteAdd(&routeToDst); err != nil { return err } } } return nil } kind-0.27.0/images/kindnetd/files/000077500000000000000000000000001475376161000167065ustar00rootroot00000000000000kind-0.27.0/images/kindnetd/files/LICENSES/000077500000000000000000000000001475376161000201135ustar00rootroot00000000000000kind-0.27.0/images/kindnetd/files/LICENSES/README.txt000066400000000000000000000002641475376161000216130ustar00rootroot00000000000000This directory contains license files and notices from binaries built for this image and the dependencies of those binaries, as collected by https://github.com/google/go-licenses. kind-0.27.0/images/kindnetd/go.mod000066400000000000000000000053701475376161000167170ustar00rootroot00000000000000module sigs.k8s.io/kind/images/kindnetd go 1.22.0 require ( github.com/coreos/go-iptables v0.8.0 github.com/vishvananda/netlink v1.3.0 golang.org/x/sys v0.26.0 k8s.io/api v0.31.1 k8s.io/apimachinery v0.31.1 k8s.io/client-go v0.31.1 k8s.io/klog/v2 v2.130.1 sigs.k8s.io/kube-network-policies v0.6.1-0.20241023163654-4320aa92e3f0 ) require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emicklei/go-restful/v3 v3.12.1 // indirect github.com/florianl/go-nfqueue v1.3.2 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/gnostic-models v0.6.8 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/josharian/native v1.1.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mdlayher/netlink v1.7.2 // indirect github.com/mdlayher/socket v0.5.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/prometheus/client_golang v1.20.5 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.60.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/vishvananda/netns v0.0.4 // indirect github.com/x448/float16 v0.8.4 // indirect golang.org/x/net v0.30.0 // indirect golang.org/x/oauth2 v0.23.0 // indirect golang.org/x/sync v0.8.0 // indirect golang.org/x/term v0.25.0 // indirect golang.org/x/text v0.19.0 // indirect golang.org/x/time v0.7.0 // indirect google.golang.org/protobuf v1.35.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/kube-openapi v0.0.0-20240903163716-9e1beecbcb38 // indirect k8s.io/utils v0.0.0-20240921022957-49e7df575cb6 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/knftables v0.0.17 // indirect sigs.k8s.io/network-policy-api v0.1.5 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect sigs.k8s.io/yaml v1.4.0 // indirect ) kind-0.27.0/images/kindnetd/go.sum000066400000000000000000000461761475376161000167550ustar00rootroot00000000000000github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/coreos/go-iptables v0.8.0 h1:MPc2P89IhuVpLI7ETL/2tx3XZ61VeICZjYqDEgNsPRc= github.com/coreos/go-iptables v0.8.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/florianl/go-nfqueue v1.3.2 h1:8DPzhKJHywpHJAE/4ktgcqveCL7qmMLsEsVD68C4x4I= github.com/florianl/go-nfqueue v1.3.2/go.mod h1:eSnAor2YCfMCVYrVNEhkLGN/r1L+J4uDjc0EUy0tfq4= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k= github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lithammer/dedent v1.1.0 h1:VNzHMVCBNG1j0fh3OrsFRkVUwStdDArbgBWoPAffktY= github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mdlayher/netlink v1.6.0/go.mod h1:0o3PlBmGst1xve7wQ7j/hwpNaFaH4qCRyWCdcZk8/vA= github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= github.com/mdlayher/socket v0.1.1/go.mod h1:mYV5YIZAfHh4dzDVzI8x8tWLWCliuX8Mon5Awbj+qDs= github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos= github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.60.0 h1:+V9PAREWNvJMAuJ1x1BaWl9dewMW4YrHZQbx0sJNllA= github.com/prometheus/common v0.60.0/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/vishvananda/netlink v1.3.0 h1:X7l42GfcV4S6E4vHTsw48qbrV+9PVojNfIhZcwQdrZk= github.com/vishvananda/netlink v1.3.0/go.mod h1:i6NetklAujEcC6fK0JPjT8qSwWyO0HLn4UKG+hGqeJs= github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= k8s.io/api v0.31.1 h1:Xe1hX/fPW3PXYYv8BlozYqw63ytA92snr96zMW9gWTU= k8s.io/api v0.31.1/go.mod h1:sbN1g6eY6XVLeqNsZGLnI5FwVseTrZX7Fv3O26rhAaI= k8s.io/apimachinery v0.31.1 h1:mhcUBbj7KUjaVhyXILglcVjuS4nYXiwC+KKFBgIVy7U= k8s.io/apimachinery v0.31.1/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= k8s.io/client-go v0.31.1 h1:f0ugtWSbWpxHR7sjVpQwuvw9a3ZKLXX0u0itkFXufb0= k8s.io/client-go v0.31.1/go.mod h1:sKI8871MJN2OyeqRlmA4W4KM9KBdBUpDLu/43eGemCg= k8s.io/component-base v0.31.1 h1:UpOepcrX3rQ3ab5NB6g5iP0tvsgJWzxTyAo20sgYSy8= k8s.io/component-base v0.31.1/go.mod h1:WGeaw7t/kTsqpVTaCoVEtillbqAhF2/JgvO0LDOMa0w= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20240903163716-9e1beecbcb38 h1:1dWzkmJrrprYvjGwh9kEUxmcUV/CtNU8QM7h1FLWQOo= k8s.io/kube-openapi v0.0.0-20240903163716-9e1beecbcb38/go.mod h1:coRQXBK9NxO98XUv3ZD6AK3xzHCxV6+b7lrquKwaKzA= k8s.io/utils v0.0.0-20240921022957-49e7df575cb6 h1:MDF6h2H/h4tbzmtIKTuctcwZmY0tY9mD9fNT47QO6HI= k8s.io/utils v0.0.0-20240921022957-49e7df575cb6/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/knftables v0.0.17 h1:wGchTyRF/iGTIjd+vRaR1m676HM7jB8soFtyr/148ic= sigs.k8s.io/knftables v0.0.17/go.mod h1:f/5ZLKYEUPUhVjUCg6l80ACdL7CIIyeL0DxfgojGRTk= sigs.k8s.io/kube-network-policies v0.6.1-0.20241023163654-4320aa92e3f0 h1:dvhG3ROzBOFbqbdIGN8LlxIiB1rhwzn2qupjYeeJeJ4= sigs.k8s.io/kube-network-policies v0.6.1-0.20241023163654-4320aa92e3f0/go.mod h1:mKmJImovCB9a9bfoqzW3Tp0iy+GkXqOwjRO8GMiukGs= sigs.k8s.io/network-policy-api v0.1.5 h1:xyS7VAaM9EfyB428oFk7WjWaCK6B129i+ILUF4C8l6E= sigs.k8s.io/network-policy-api v0.1.5/go.mod h1:D7Nkr43VLNd7iYryemnj8qf0N/WjBzTZDxYA+g4u1/Y= sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= kind-0.27.0/images/kindnetd/scripts/000077500000000000000000000000001475376161000172735ustar00rootroot00000000000000kind-0.27.0/images/kindnetd/scripts/third_party/000077500000000000000000000000001475376161000216245ustar00rootroot00000000000000kind-0.27.0/images/kindnetd/scripts/third_party/gimme/000077500000000000000000000000001475376161000227225ustar00rootroot00000000000000kind-0.27.0/images/kindnetd/scripts/third_party/gimme/LICENSE000066400000000000000000000021021475376161000237220ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2015-2018 gimme contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. kind-0.27.0/images/kindnetd/scripts/third_party/gimme/README.md000066400000000000000000000002171475376161000242010ustar00rootroot00000000000000# gimme This is an unmodified copy of [gimme], so we don't have to download it from the internet. [gimme]: https://github.com/travis-ci/gimmekind-0.27.0/images/kindnetd/scripts/third_party/gimme/gimme000077500000000000000000000662441475376161000237620ustar00rootroot00000000000000#!/usr/bin/env bash # vim:noexpandtab:ts=2:sw=2: # #+ Usage: $(basename $0) [flags] [go-version] [version-prefix] #+ - #+ Version: ${GIMME_VERSION} #+ Copyright: ${GIMME_COPYRIGHT} #+ License URL: ${GIMME_LICENSE_URL} #+ - #+ Install go! There are multiple types of installations available, with 'auto' being the default. #+ If either 'auto' or 'binary' is specified as GIMME_TYPE, gimme will first check for an existing #+ go installation. This behavior may be disabled by providing '-f/--force/force' as first positional #+ argument. #+ - #+ Option flags: #+ -h --help help - show this help text and exit #+ -V --version version - show the version only and exit #+ -f --force force - remove the existing go installation if present prior to install #+ -l --list list - list installed go versions and exit #+ -k --known known - list known go versions and exit #+ --force-known-update - when used with --known, ignores the cache and updates #+ -r --resolve resolve - resolve a version specifier to a version, show that and exit #+ - #+ Influential env vars: #+ - #+ GIMME_GO_VERSION - version to install (*REQUIRED*, may be given as first positional arg) #+ GIMME_VERSION_PREFIX - prefix for installed versions (default '${GIMME_VERSION_PREFIX}', #+ may be given as second positional arg) #+ GIMME_ARCH - arch to install (default '${GIMME_ARCH}') #+ GIMME_BINARY_OSX - darwin-specific binary suffix (default '${GIMME_BINARY_OSX}') #+ GIMME_ENV_PREFIX - prefix for env files (default '${GIMME_ENV_PREFIX}') #+ GIMME_GO_GIT_REMOTE - git remote for git-based install (default '${GIMME_GO_GIT_REMOTE}') #+ GIMME_OS - os to install (default '${GIMME_OS}') #+ GIMME_TMP - temp directory (default '${GIMME_TMP}') #+ GIMME_TYPE - install type to perform ('auto', 'binary', 'source', or 'git') #+ (default '${GIMME_TYPE}') #+ GIMME_INSTALL_RACE - install race directory after compile if non-empty. #+ If the install type is 'binary', this option is ignored. #+ GIMME_DEBUG - enable tracing if non-empty #+ GIMME_NO_ENV_ALIAS - disable creation of env 'alias' file when os and arch match host #+ GIMME_SILENT_ENV - omit the 'go version' line from env file #+ GIMME_CGO_ENABLED - enable build of cgo support #+ GIMME_CC_FOR_TARGET - cross compiler for cgo support #+ GIMME_DOWNLOAD_BASE - override base URL dir for download (default '${GIMME_DOWNLOAD_BASE}') #+ GIMME_LIST_KNOWN - override base URL for known go versions (default '${GIMME_LIST_KNOWN}') #+ GIMME_KNOWN_CACHE_MAX - seconds the cache for --known is valid for (default '${GIMME_KNOWN_CACHE_MAX}') #+ - # set -e shopt -s nullglob shopt -s dotglob shopt -s extglob set -o pipefail [[ ${GIMME_DEBUG} ]] && set -x readonly GIMME_VERSION="v1.5.4" readonly GIMME_COPYRIGHT="Copyright (c) 2015-2020 gimme contributors" readonly GIMME_LICENSE_URL="https://raw.githubusercontent.com/travis-ci/gimme/${GIMME_VERSION}/LICENSE" export GIMME_VERSION export GIMME_COPYRIGHT export GIMME_LICENSE_URL program_name="$(basename "$0")" # shellcheck disable=SC1117 warn() { printf >&2 "%s: %s\n" "${program_name}" "${*}"; } die() { warn "$@" exit 1 } # We don't want to go around hitting Google's servers with requests for # files named HEAD@{date}.tar so we only try binary/source downloads if # it looks like a plausible name to us. # We don't need to support 0. releases of Go. # We don't support 5 digit major-versions of Go (limit back-tracking in RE). # We don't support very long versions # (both to avoid annoying download server operators with attacks and # because regexp backtracking can be pathological). # Per _assert_version_given we do assume 2.0 not 2 ALLOWED_UPSTREAM_VERSION_RE='^[1-9][0-9]{0,3}(\.[0-9][0-9a-zA-Z_-]{0,9})+$' # # The main path which allowed these to leak upstream before has been closed # but a valid git repo tag or branch-name will still reach the point of # being _tried_ upstream. # _do_curl "url" "file" _do_curl() { mkdir -p "$(dirname "${2}")" if command -v curl >/dev/null; then curl -sSLf "${1}" -o "${2}" 2>/dev/null return fi if command -v wget >/dev/null; then wget -q "${1}" -O "${2}" 2>/dev/null return fi if command -v fetch >/dev/null; then fetch -q "${1}" -o "${2}" 2>/dev/null return fi echo >&2 'error: no curl, wget, or fetch found' exit 1 } # _sha256sum "file" _sha256sum() { if command -v sha256sum &>/dev/null; then sha256sum "$@" elif command -v gsha256sum &>/dev/null; then gsha256sum "$@" else shasum -a 256 "$@" fi } # sort versions, handling 1.10 after 1.9, not before 1.2 # FreeBSD sort has --version-sort, none of the others do # Looks like --general-numeric-sort is the safest; checked macOS 10.12.6, FreeBSD 10.3, Ubuntu Trusty if sort --version-sort /dev/null; then _version_sort() { sort --version-sort; } else _version_sort() { # If we go to four-digit minor or patch versions, then extend the padding here # (but in such a world, perhaps --version-sort will have become standard by then?) sed -E 's/\.([0-9](\.|$))/.00\1/g; s/\.([0-9][0-9](\.|$))/.0\1/g' | sort --general-numeric-sort | sed 's/\.00*/./g' } fi # _do_curls "file" "url" ["url"...] _do_curls() { f="${1}" shift if _sha256sum -c "${f}.sha256" &>/dev/null; then return 0 fi for url in "${@}"; do if _do_curl "${url}" "${f}"; then if _do_curl "${url}.sha256" "${f}.sha256"; then echo "$(cat "${f}.sha256") ${f}" >"${f}.sha256.tmp" mv "${f}.sha256.tmp" "${f}.sha256" if ! _sha256sum -c "${f}.sha256" &>/dev/null; then warn "sha256sum failed for '${f}'" warn 'continuing to next candidate URL' continue fi fi return fi done rm -f "${f}" return 1 } # _binary "version" "file.tar.gz" "arch" _binary() { local version=${1} local file=${2} local arch=${3} urls=( "${GIMME_DOWNLOAD_BASE}/go${version}.${GIMME_OS}-${arch}.tar.gz" ) if [[ "${GIMME_OS}" == 'darwin' && "${GIMME_BINARY_OSX}" ]]; then urls=( "${GIMME_DOWNLOAD_BASE}/go${version}.${GIMME_OS}-${arch}-${GIMME_BINARY_OSX}.tar.gz" "${urls[@]}" ) fi if [ "${arch}" = 'arm' ]; then # attempt "armv6l" vs just "arm" first (since that's what's officially published) urls=( "${GIMME_DOWNLOAD_BASE}/go${version}.${GIMME_OS}-${arch}v6l.tar.gz" # go1.6beta2 & go1.6rc1 "${GIMME_DOWNLOAD_BASE}/go${version}.${GIMME_OS}-${arch}6.tar.gz" # go1.6beta1 "${urls[@]}" ) fi if [ "${GIMME_OS}" = 'windows' ]; then urls=( "${GIMME_DOWNLOAD_BASE}/go${version}.${GIMME_OS}-${arch}.zip" ) fi _do_curls "${file}" "${urls[@]}" } # _source "version" "file.src.tar.gz" _source() { urls=( "${GIMME_DOWNLOAD_BASE}/go${1}.src.tar.gz" "https://github.com/golang/go/archive/go${1}.tar.gz" ) _do_curls "${2}" "${urls[@]}" } # _fetch "dir" _fetch() { mkdir -p "$(dirname "${1}")" if [[ -d "${1}/.git" ]]; then ( cd "${1}" git remote set-url origin "${GIMME_GO_GIT_REMOTE}" git fetch -q --all && git fetch -q --tags ) return fi git clone -q "${GIMME_GO_GIT_REMOTE}" "${1}" } # _checkout "version" "dir" # NB: might emit a "renamed version" on stdout _checkout() { local spec="${1:?}" godir="${2:?}" # We are called twice, once during validation that a version was given and # later during build. We don't want to fetch twice, so we are fetching # during the validation only, in the caller. if [[ "${spec}" =~ ^[0-9a-f]{6,}$ ]]; then # We always treat this as a commit sha, whether instead of doing # branch tests etc. It looks like a commit sha and the Go maintainers # aren't daft enough to use pure hex for a tag or branch. git -C "$godir" reset -q --hard "${spec}" || return 1 return 0 fi # If spec looks like HEAD^{something} or HEAD^^^ then trying # origin/$spec would succeed but we'd write junk to the filesystem, # propagating annoying characters out. local retval probe_named disallow rev probe_named=1 disallow='[@^~:{}]' if [[ "${spec}" =~ $disallow ]]; then probe_named=0 [[ "${spec}" != "@" ]] || spec="HEAD" fi try_spec() { git -C "${godir}" reset -q --hard "$@" -- 2>/dev/null; } retval=1 if ((probe_named)); then retval=0 try_spec "origin/${spec}" || try_spec "origin/go${spec}" || { [[ "${spec}" == "tip" ]] && try_spec origin/master; } || try_spec "refs/tags/${spec}" || try_spec "refs/tags/go${spec}" || retval=1 fi if ((retval)); then retval=0 # We're about to reset anyway, if we succeed, so we should reset to a # known state before parsing what might be relative specs try_spec origin/master && rev="$(git -C "${godir}" rev-parse --verify -q "${spec}^{object}")" && try_spec "${rev}" && git -C "${godir}" rev-parse --verify -q --short=12 "${rev}" || retval=1 # that rev-parse prints to stdout, so we can affect the version seen fi unset -f try_spec return $retval } # _extract "file.tar.gz" "dir" _extract() { mkdir -p "${2}" if [[ "${1}" == *.tar.gz ]]; then tar -xf "${1}" -C "${2}" --strip-components 1 else unzip -q "${1}" -d "${2}" mv "${2}"/go/* "${2}" rmdir "${2}"/go fi } # _setup_bootstrap _setup_bootstrap() { local versions=("1.18" "1.17" "1.16" "1.15" "1.14" "1.13" "1.12" "1.11" "1.10" "1.9" "1.8" "1.7" "1.6" "1.5" "1.4") # try existing for v in "${versions[@]}"; do for candidate in "${GIMME_ENV_PREFIX}/go${v}"*".env"; do if [ -s "${candidate}" ]; then # shellcheck source=/dev/null GOROOT_BOOTSTRAP="$(source "${candidate}" 2>/dev/null && go env GOROOT)" export GOROOT_BOOTSTRAP return 0 fi done done # try binary for v in "${versions[@]}"; do if [ -n "$(_try_binary "${v}" "${GIMME_HOSTARCH}")" ]; then export GOROOT_BOOTSTRAP="${GIMME_VERSION_PREFIX}/go${v}.${GIMME_OS}.${GIMME_HOSTARCH}" return 0 fi done echo >&2 "Unable to setup go bootstrap from existing or binary" return 1 } # _compile "dir" _compile() { ( if grep -q GOROOT_BOOTSTRAP "${1}/src/make.bash" &>/dev/null; then _setup_bootstrap || return 1 fi cd "${1}" if [[ -d .git ]]; then git clean -dfx -q fi cd src export GOOS="${GIMME_OS}" GOARCH="${GIMME_ARCH}" export CGO_ENABLED="${GIMME_CGO_ENABLED}" export CC_FOR_TARGET="${GIMME_CC_FOR_TARGET}" local make_log="${1}/make.${GOOS}.${GOARCH}.log" if [[ "${GIMME_DEBUG}" -ge "2" ]]; then ./make.bash -v 2>&1 | tee "${make_log}" 1>&2 || return 1 else ./make.bash &>"${make_log}" || return 1 fi ) } _try_install_race() { if [[ ! "${GIMME_INSTALL_RACE}" ]]; then return 0 fi "${1}/bin/go" install -race std } _can_compile() { cat >"${GIMME_TMP}/test.go" <<'EOF' package main import "os" func main() { os.Exit(0) } EOF "${1}/bin/go" run "${GIMME_TMP}/test.go" } # _env "dir" _env() { [[ -d "${1}/bin" && -x "${1}/bin/go" ]] || return 1 # if we try to run a Darwin binary on Linux, we need to fail so 'auto' can fallback to cross-compiling from source # automatically GOROOT="${1}" GOFLAGS="" "${1}/bin/go" version &>/dev/null || return 1 # https://twitter.com/davecheney/status/431581286918934528 # we have to GOROOT sometimes because we use official release binaries in unofficial locations :( # # Issue 87 leads to: # No, we should _always_ set GOROOT when using official release binaries, and sanest to just always set it. # The "avoid setting it" is _only_ for people using official releases in official locations. # Tools like `gimme` are the reason that GOROOT-in-env exists. echo if [[ "$(GOROOT="${1}" "${1}/bin/go" env GOHOSTOS)" == "${GIMME_OS}" ]]; then echo 'unset GOOS;' else echo 'export GOOS="'"${GIMME_OS}"'";' fi if [[ "$(GOROOT="${1}" "${1}/bin/go" env GOHOSTARCH)" == "${GIMME_ARCH}" ]]; then echo 'unset GOARCH;' else echo 'export GOARCH="'"${GIMME_ARCH}"'";' fi echo "export GOROOT='${1}';" # shellcheck disable=SC2016 echo 'export PATH="'"${1}/bin"':${PATH}";' if [[ -z "${GIMME_SILENT_ENV}" ]]; then echo 'go version >&2;' fi echo } # _env_alias "dir" "env-file" _env_alias() { if [[ "${GIMME_NO_ENV_ALIAS}" ]]; then echo "${2}" return fi if [[ "$(GOROOT="${1}" "${1}/bin/go" env GOHOSTOS)" == "${GIMME_OS}" && "$(GOROOT="${1}" "${1}/bin/go" env GOHOSTARCH)" == "${GIMME_ARCH}" ]]; then # GIMME_GO_VERSION might be a branch, which can contain '/' local dest="${GIMME_ENV_PREFIX}/go${GIMME_GO_VERSION//\//__}.env" cp "${2}" "${dest}" ln -sf "${dest}" "${GIMME_ENV_PREFIX}/latest.env" echo "${dest}" else echo "${2}" fi } _try_existing() { case "${1}" in binary) local existing_ver="${GIMME_VERSION_PREFIX}/go${GIMME_GO_VERSION}.${GIMME_OS}.${GIMME_ARCH}" local existing_env="${GIMME_ENV_PREFIX}/go${GIMME_GO_VERSION}.${GIMME_OS}.${GIMME_ARCH}.env" ;; source) local existing_ver="${GIMME_VERSION_PREFIX}/go${GIMME_GO_VERSION}.src" local existing_env="${GIMME_ENV_PREFIX}/go${GIMME_GO_VERSION}.src.env" ;; *) _try_existing binary || _try_existing source return $? ;; esac if [[ -x "${existing_ver}/bin/go" && -s "${existing_env}" ]]; then # newer envs have existing semi-colon at end of line, because newer gimme # puts them there; envs created before that change lack those semi-colons # and should gain them, to make it easier for people using eval without # double-quoting the command substition. sed -e 's/\([^;]\)$/\1;/' <"${existing_env}" # gimme is the corner-case where GOROOT _should_ be overriden, since if the # ancilliary tooling's system-internal DefaultGoroot exists, and GOROOT is # unset, then it will be used and the wrong golang will be picked up. # Lots of old installs won't have GOROOT; munge it from $PATH if grep -qs '^unset GOROOT' -- "${existing_env}"; then sed -n -e 's/^export PATH="\(.*\)\/bin:.*$/export GOROOT='"'"'\1'"'"';/p' <"${existing_env}" echo fi # Export the same variables whether building new or using existing echo "export GIMME_ENV='${existing_env}';" return fi return 1 } # _try_binary "version" "arch" _try_binary() { local version=${1} local arch=${2} local bin_tgz="${GIMME_TMP}/go${version}.${GIMME_OS}.${arch}.tar.gz" local bin_dir="${GIMME_VERSION_PREFIX}/go${version}.${GIMME_OS}.${arch}" local bin_env="${GIMME_ENV_PREFIX}/go${version}.${GIMME_OS}.${arch}.env" [[ "${version}" =~ ${ALLOWED_UPSTREAM_VERSION_RE} ]] || return 1 if [ "${GIMME_OS}" = 'windows' ]; then bin_tgz=${bin_tgz%.tar.gz}.zip fi _binary "${version}" "${bin_tgz}" "${arch}" || return 1 _extract "${bin_tgz}" "${bin_dir}" || return 1 _env "${bin_dir}" | tee "${bin_env}" || return 1 echo "export GIMME_ENV=\"$(_env_alias "${bin_dir}" "${bin_env}")\"" } _try_source() { local src_tgz="${GIMME_TMP}/go${GIMME_GO_VERSION}.src.tar.gz" local src_dir="${GIMME_VERSION_PREFIX}/go${GIMME_GO_VERSION}.src" local src_env="${GIMME_ENV_PREFIX}/go${GIMME_GO_VERSION}.src.env" [[ "${GIMME_GO_VERSION}" =~ ${ALLOWED_UPSTREAM_VERSION_RE} ]] || return 1 _source "${GIMME_GO_VERSION}" "${src_tgz}" || return 1 _extract "${src_tgz}" "${src_dir}" || return 1 _compile "${src_dir}" || return 1 _try_install_race "${src_dir}" || return 1 _env "${src_dir}" | tee "${src_env}" || return 1 echo "export GIMME_ENV=\"$(_env_alias "${src_dir}" "${src_env}")\"" } # We do _not_ try to use any version caching with _try_existing(), but instead # build afresh each time. We don't want to deal with someone moving the repo # to other-version, doing an install, then resetting it back to # last-version-we-saw and thus introducing conflicts. # # If you want to re-use a built-at-spec version, then avoid moving the repo # and source the generated .env manually. # Note that the env will just refer to the 'go' directory, so it's not safe # to reuse anyway. _try_git() { local git_dir="${GIMME_VERSION_PREFIX}/go" local git_env="${GIMME_ENV_PREFIX}/go.git.${GIMME_OS}.${GIMME_ARCH}.env" local resolved_sha # Any tags should have been resolved when we asserted that we were # given a version, so no need to handle that here. _checkout "${GIMME_GO_VERSION}" "${git_dir}" >/dev/null || return 1 _compile "${git_dir}" || return 1 _try_install_race "${git_dir}" || return 1 _env "${git_dir}" | tee "${git_env}" || return 1 echo "export GIMME_ENV=\"$(_env_alias "${git_dir}" "${git_env}")\"" } _wipe_version() { local env_file="${GIMME_ENV_PREFIX}/go${1}.${GIMME_OS}.${GIMME_ARCH}.env" if [[ -s "${env_file}" ]]; then rm -rf "$(awk -F\" '/GOROOT/ { print $2 }' "${env_file}")" rm -f "${env_file}" fi } _list_versions() { if [ ! -d "${GIMME_VERSION_PREFIX}" ]; then return 0 fi local current_version current_version="$(go env GOROOT 2>/dev/null)" current_version="${current_version##*/go}" current_version="${current_version%%.${GIMME_OS}.*}" # 1.1 1.10 1.2 is bad; zsh has `setopt numeric_glob_sort` but bash # doesn't appear to have anything like that. for d in "${GIMME_VERSION_PREFIX}/go"*".${GIMME_OS}."*; do local cleaned="${d##*/go}" cleaned="${cleaned%%.${GIMME_OS}.*}" echo "${cleaned}" done | _version_sort | while read -r cleaned; do echo -en "${cleaned}" if [[ "${cleaned}" == "${current_version}" ]]; then echo -en ' <= current' >&2 fi echo done } _update_remote_known_list_if_needed() { # shellcheck disable=SC1117 local exp="go([[:alnum:]\.]*)\.src.*" # :alnum: catches beta versions too local list="${GIMME_VERSION_PREFIX}/known-versions.txt" local dlfile="${GIMME_TMP}/known-dl" if [[ -e "${list}" ]] && ! ((force_known_update)) && ! _file_older_than_secs "${list}" "${GIMME_KNOWN_CACHE_MAX}"; then echo "${list}" return 0 fi [[ -d "${GIMME_VERSION_PREFIX:?}" ]] || mkdir -p -- "${GIMME_VERSION_PREFIX}" _do_curl "${GIMME_LIST_KNOWN}" "${dlfile}" while read -r line; do if [[ "${line}" =~ ${exp} ]]; then echo "${BASH_REMATCH[1]}" fi done <"${dlfile}" | _version_sort | uniq >"${list}.new" rm -f "${list}" &>/dev/null mv "${list}.new" "${list}" rm -f "${dlfile}" echo "${list}" return 0 } _list_known() { local knownfile knownfile="$(_update_remote_known_list_if_needed)" ( _list_versions 2>/dev/null cat -- "${knownfile}" ) | grep . | _version_sort | uniq } # For the "invoked on commandline" case, we want to always pass unknown # strings through, so that we can be a uniqueness filter, but for unknown # names we want to exit with a value other than 1, so we document that # we'll exit 2. For use by other functions, 2 is as good as 1. _resolve_version() { case "${1}" in stable) _get_curr_stable return 0 ;; oldstable) _get_old_stable return 0 ;; tip) echo "tip" return 0 ;; *.x) true ;; *) echo "${1}" local GIMME_GO_VERSION="$1" local ASSERT_ABORT='return' if _assert_version_given 2>/dev/null; then return 0 fi warn "version specifier '${1}' unknown" return 2 ;; esac # We have a .x suffix local base="${1%.x}" local ver last='' known known="$(_update_remote_known_list_if_needed)" # will be version-sorted if [[ ! "${base}" =~ ^[0-9.]+$ ]]; then warn "resolve pattern '${base}.x' invalid for .x finding" return 2 fi # The `.x` is optional; "1.10" matches "1.10.x" local search="^${base//./\\.}(\\.[0-9.]+)?\$" # avoid regexp attacks while read -r ver; do [[ "${ver}" =~ $search ]] || continue last="${ver}" done <"$known" if [[ -n "${last}" ]]; then echo "${last}" return 0 fi echo "${1}" warn "given '${1}' but no release for '${base}' found" return 2 } _realpath() { # shellcheck disable=SC2005 [ -d "$1" ] && echo "$(cd "$1" && pwd)" || echo "$(cd "$(dirname "$1")" && pwd)/$(basename "$1")" } _get_curr_stable() { local stable="${GIMME_VERSION_PREFIX}/stable" if _file_older_than_secs "${stable}" 86400; then _update_stable "${stable}" fi cat "${stable}" } _get_old_stable() { local oldstable="${GIMME_VERSION_PREFIX}/oldstable" if _file_older_than_secs "${oldstable}" 86400; then _update_oldstable "${oldstable}" fi cat "${oldstable}" } _update_stable() { local stable="${1}" local url="https://golang.org/VERSION?m=text" _do_curl "${url}" "${stable}" sed -i.old -e 's/^go\(.*\)/\1/' "${stable}" rm -f "${stable}.old" } _update_oldstable() { local oldstable="${1}" local oldstable_x oldstable_x=$(_get_curr_stable | awk -F. '{ $2--; print $1 "." $2 "." "x" }') _resolve_version "${oldstable_x}" >"${oldstable}" } _last_mod_timestamp() { local filename="${1}" case "${GIMME_HOSTOS}" in darwin | *bsd) stat -f %m "${filename}" ;; linux) stat -c %Y "${filename}" ;; esac } _file_older_than_secs() { local file="${1}" local age_secs="${2}" local ts # if the file does not exist, we return true, as the cache needs updating ts="$(_last_mod_timestamp "${file}" 2>/dev/null)" || return 0 ((($(date +%s) - ts) > age_secs)) } _assert_version_given() { # By the time we're called, aliases such as "stable" must have been resolved # but we could be a reference in git. # # Versions can include suffices such as in "1.8beta2", so our assumption is that # there will always be a minor present; the first public release was "1.0" so # we assume "2.0" not "2". if [[ -z "${GIMME_GO_VERSION}" ]]; then echo >&2 'error: no GIMME_GO_VERSION supplied' echo >&2 " ex: GIMME_GO_VERSION=1.4.1 ${0} ${*}" echo >&2 " ex: ${0} 1.4.1 ${*}" ${ASSERT_ABORT:-exit} 1 fi # Note: _resolve_version calls back to us (_assert_version_given), but # only for cases where the version does not end with .x, so this should # be safe. # This should be untangled. PRs accepted, good starter project. if [[ "${GIMME_GO_VERSION}" == *.x ]]; then GIMME_GO_VERSION="$(_resolve_version "${GIMME_GO_VERSION}")" || ${ASSERT_ABORT:-exit} 1 fi if [[ "${GIMME_GO_VERSION}" == +([[:digit:]]).+([[:digit:]])* ]]; then return 0 fi # Here we resolve symbolic references. If we don't, then we get some # random git tag name being accepted as valid and then we try to # curl garbage from upstream. if [[ "${GIMME_TYPE}" == "auto" || "${GIMME_TYPE}" == "git" ]]; then local git_dir="${GIMME_VERSION_PREFIX}/go" local resolved_sha _fetch "${git_dir}" if resolved_sha="$(_checkout "${GIMME_GO_VERSION}" "${git_dir}")"; then if [[ -n "${resolved_sha}" ]]; then # Break our normal silence, this one really needs to be seen on stderr # always; auditability and knowing what version of Go you got wins. warn "resolved '${GIMME_GO_VERSION}' to '${resolved_sha}'" GIMME_GO_VERSION="${resolved_sha}" fi return 0 fi fi echo >&2 'error: GIMME_GO_VERSION not recognized as valid' echo >&2 " got: ${GIMME_GO_VERSION}" ${ASSERT_ABORT:-exit} 1 } _exclude_from_backups() { # Please avoid anything which requires elevated privileges or is obnoxious # enough to offend the invoker case "${GIMME_HOSTOS}" in darwin) # Darwin: Time Machine is "standard", we can add others. The default # mechanism is sticky, as an attribute on the dir, requires no # privileges, is idempotent (and doesn't support -- to end flags). tmutil addexclusion "$@" ;; esac } _versint() { IFS=" " read -r -a args <<<"${1//[^0-9]/ }" printf '1%03d%03d%03d%03d' "${args[@]}" } _to_goarch() { case "${1}" in aarch64) echo "arm64" ;; *) echo "${1}" ;; esac } : "${GIMME_OS:=$(uname -s | tr '[:upper:]' '[:lower:]')}" : "${GIMME_HOSTOS:=$(uname -s | tr '[:upper:]' '[:lower:]')}" : "${GIMME_ARCH:=$(_to_goarch "$(uname -m)")}" : "${GIMME_HOSTARCH:=$(_to_goarch "$(uname -m)")}" : "${GIMME_ENV_PREFIX:=${HOME}/.gimme/envs}" : "${GIMME_VERSION_PREFIX:=${HOME}/.gimme/versions}" : "${GIMME_TMP:=${TMPDIR:-/tmp}/gimme}" : "${GIMME_GO_GIT_REMOTE:=https://github.com/golang/go.git}" : "${GIMME_TYPE:=auto}" # 'auto', 'binary', 'source', or 'git' : "${GIMME_BINARY_OSX:=osx10.8}" : "${GIMME_DOWNLOAD_BASE:=https://dl.google.com/go}" : "${GIMME_LIST_KNOWN:=https://golang.org/dl}" : "${GIMME_KNOWN_CACHE_MAX:=10800}" # The version prefix must be an absolute path case "${GIMME_VERSION_PREFIX}" in /*) true ;; *) echo >&2 " Fixing GIMME_VERSION_PREFIX from relative: $GIMME_VERSION_PREFIX" GIMME_VERSION_PREFIX="$(pwd)/${GIMME_VERSION_PREFIX}" echo >&2 " to: $GIMME_VERSION_PREFIX" ;; esac case "${GIMME_OS}" in mingw* | msys_nt*) # Minimalist GNU for Windows GIMME_OS='windows' if [ "${GIMME_ARCH}" = 'i686' ]; then GIMME_ARCH="386" else GIMME_ARCH="amd64" fi ;; esac force_install=0 force_known_update=0 while [[ $# -gt 0 ]]; do case "${1}" in -h | --help | help | wat) _old_ifs="$IFS" IFS=';' awk '/^#\+ / { sub(/^#\+ /, "", $0) ; sub(/-$/, "", $0) ; print $0 }' "$0" | while read -r line; do eval "echo \"$line\"" done IFS="$_old_ifs" exit 0 ;; -V | --version | version) echo "${GIMME_VERSION}" exit 0 ;; -r | --resolve | resolve) # The normal mkdir of versions is below; we don't want to move it up # to where we create files just if asked our version; thus # _resolve_version has to mkdir the versions dir itself. if [[ $# -ge 2 ]]; then _resolve_version "${2}" elif [[ -n "${GIMME_GO_VERSION:-}" ]]; then _resolve_version "${GIMME_GO_VERSION}" else die "resolve must be given a version to resolve" fi exit $? ;; -l | --list | list) _list_versions exit 0 ;; -k | --known | known) _list_known exit 0 ;; -f | --force | force) force_install=1 ;; --force-known-update | force-known-update) force_known_update=1 ;; -i | install) true # ignore a dummy argument ;; *) break ;; esac shift done if [[ -n "${1}" ]]; then GIMME_GO_VERSION="${1}" fi if [[ -n "${2}" ]]; then GIMME_VERSION_PREFIX="${2}" fi case "${GIMME_ARCH}" in x86_64) GIMME_ARCH=amd64 ;; x86) GIMME_ARCH=386 ;; arm64) if [[ "${GIMME_GO_VERSION}" != master && "$(_versint "${GIMME_GO_VERSION}")" < "$(_versint 1.5)" ]]; then echo >&2 "error: ${GIMME_ARCH} is not supported by this go version" echo >&2 "try go1.5 or newer" exit 1 fi if [[ "${GIMME_HOSTOS}" == "linux" && "${GIMME_HOSTARCH}" != "${GIMME_ARCH}" ]]; then : "${GIMME_CC_FOR_TARGET:="aarch64-linux-gnu-gcc"}" fi ;; arm*) GIMME_ARCH=arm ;; esac case "${GIMME_HOSTARCH}" in x86_64) GIMME_HOSTARCH=amd64 ;; x86) GIMME_HOSTARCH=386 ;; arm64) ;; arm*) GIMME_HOSTARCH=arm ;; esac case "${GIMME_GO_VERSION}" in stable) GIMME_GO_VERSION=$(_get_curr_stable) ;; oldstable) GIMME_GO_VERSION=$(_get_old_stable) ;; esac _assert_version_given "$@" ((force_install)) && _wipe_version "${GIMME_GO_VERSION}" unset GOARCH unset GOBIN unset GOOS unset GOPATH unset GOROOT unset CGO_ENABLED unset CC_FOR_TARGET # GO111MODULE breaks build of Go itself unset GO111MODULE mkdir -p "${GIMME_VERSION_PREFIX}" "${GIMME_ENV_PREFIX}" # The envs dir stays small and provides a record of what had been installed # whereas the versions dir grows by hundreds of MB per version and is not # intended to support local modifications (as that subverts the point of gimme) # _and_ is a cache, so we're unilaterally declaring that the contents of # the versions dir should be excluded from system backups. _exclude_from_backups "${GIMME_VERSION_PREFIX}" GIMME_VERSION_PREFIX="$(_realpath "${GIMME_VERSION_PREFIX}")" GIMME_ENV_PREFIX="$(_realpath "${GIMME_ENV_PREFIX}")" if ! case "${GIMME_TYPE}" in binary) _try_existing binary || _try_binary "${GIMME_GO_VERSION}" "${GIMME_ARCH}" ;; source) _try_existing source || _try_source || _try_git ;; git) _try_git ;; auto) _try_existing || _try_binary "${GIMME_GO_VERSION}" "${GIMME_ARCH}" || _try_source || _try_git ;; *) echo >&2 "I don't know how to '${GIMME_TYPE}'." echo >&2 " Try 'auto', 'binary', 'source', or 'git'." exit 1 ;; esac; then echo >&2 "I don't have any idea what to do with '${GIMME_GO_VERSION}'." echo >&2 " (using download type '${GIMME_TYPE}')" exit 1 fi kind-0.27.0/images/local-path-helper/000077500000000000000000000000001475376161000173055ustar00rootroot00000000000000kind-0.27.0/images/local-path-helper/Dockerfile000066400000000000000000000036101475376161000212770ustar00rootroot00000000000000# Copyright 2022 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # This image is contains the binaries needed for the local-path-provisioner # helper pod. Currently that means: sh, rm, mkdir ARG BASE="registry.k8s.io/build-image/debian-base:bullseye-v1.4.3" FROM ${BASE} AS build # NOTE: copyrights.tar.gz is a quirk of Kubernetes's debian-base image # We extract these here so we can grab the relevant files are easily # staged for copying into our final image. RUN [ ! -f /usr/share/copyrights.tar.gz ] || tar -C / -xzvf /usr/share/copyrights.tar.gz # we need bash for stage-binary-and-deps.sh RUN apt update && apt install -y --no-install-recommends bash # replace sh with bash RUN ln -sf /bin/bash /bin/sh # copy in script for staging distro provided binary to distroless COPY --chmod=0755 stage-binary-and-deps.sh /usr/local/bin/ # local-path-provisioner needs these things for the helper pod # TODO: we could probably coerce local-path-provisioner to use a small binary # for these instead ARG STAGE_DIR="/opt/stage" RUN mkdir -p "${STAGE_DIR}" && \ stage-binary-and-deps.sh sh "${STAGE_DIR}" && \ stage-binary-and-deps.sh rm "${STAGE_DIR}" && \ stage-binary-and-deps.sh mkdir "${STAGE_DIR}" && \ find "${STAGE_DIR}" # copy staged binary + deps + copyright into distroless FROM "gcr.io/distroless/static-debian11" ARG STAGE_DIR="/opt/stage" COPY --from=build "${STAGE_DIR}/" / kind-0.27.0/images/local-path-helper/Makefile000066400000000000000000000011631475376161000207460ustar00rootroot00000000000000# Copyright 2022 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. include $(CURDIR)/../Makefile.common.inkind-0.27.0/images/local-path-helper/README.md000066400000000000000000000003551475376161000205670ustar00rootroot00000000000000# local-path-helper This image is the image used for the https://github.com/rancher/local-path-provisioner helper pod. ## Building You can `make quick` in this directory to build a test image. To push an actual image use `make push`.kind-0.27.0/images/local-path-helper/cloudbuild.yaml000066400000000000000000000004031475376161000223140ustar00rootroot00000000000000# See https://cloud.google.com/cloud-build/docs/build-config options: substitution_option: ALLOW_LOOSE machineType: E2_HIGHCPU_8 steps: - name: gcr.io/k8s-testimages/krte:latest-master entrypoint: make args: ['-C', 'images/local-path-helper', 'push'] kind-0.27.0/images/local-path-helper/stage-binary-and-deps.sh000077500000000000000000000063411475376161000237260ustar00rootroot00000000000000#!/bin/bash # Copyright 2021 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # USAGE: stage-binary-and-deps.sh haproxy /opt/stage # # Stages $1 and it's dependencies + their copyright files to $2 # # This is intended to be used in a multi-stage docker build with a distroless/base # or distroless/cc image. set -o errexit set -o nounset set -o pipefail # file_to_package identifies the debian package that provided the file $1 file_to_package() { # `dpkg-query --search $file-pattern` outputs lines with the format: "$package: $file-path" # where $file-path belongs to $package # https://manpages.debian.org/jessie/dpkg/dpkg-query.1.en.html dpkg-query --search "$(realpath "${1}")" | cut -d':' -f1 } # package_to_copyright gives the path to the copyright file for the package $1 package_to_copyright() { echo "/usr/share/doc/${1}/copyright" } # stage_file stages the filepath $1 to $2, following symlinks # and staging copyrights stage_file() { cp -a --parents "${1}" "${2}" # recursively follow symlinks if [[ -L "${1}" ]]; then stage_file "$(cd "$(dirname "${1}")"; realpath -s "$(readlink "${1}")")" "${2}" fi # get the package so we can stage package metadata as well package="$(file_to_package "${1}")" # stage the copyright for the file cp -a --parents "$(package_to_copyright "${package}")" "${2}" # stage the package status mimicking bazel # https://github.com/bazelbuild/rules_docker/commit/f5432b813e0a11491cf2bf83ff1a923706b36420 # instead of parsing the control file, we can just get the actual package status with dpkg dpkg -s "${package}" >> "${2}/var/lib/dpkg/status.d/${package}" } # binary_to_libraries identifies the library files needed by the binary $1 with ldd binary_to_libraries() { # see: https://man7.org/linux/man-pages/man1/ldd.1.html ldd "${1}" \ `# strip the leading '${name} => ' if any so only '/lib-foo.so (0xf00)' remains` \ | sed -E 's#.* => /#/#' \ `# we want only the path remaining, not the (0x${LOCATION})` \ | awk '{print $1}' \ `# linux-vdso.so.1 is a special virtual shared object from the kernel` \ `# see: http://man7.org/linux/man-pages/man7/vdso.7.html` \ | grep -v 'linux-vdso.so.1' } # main script logic main(){ local BINARY=$1 local STAGE_DIR="${2}/" # locate the path to the binary local binary_path binary_path="$(which "${BINARY}")" # ensure package metadata dir mkdir -p "${STAGE_DIR}"/var/lib/dpkg/status.d/ # stage the binary itself stage_file "${binary_path}" "${STAGE_DIR}" # stage the dependencies of the binary while IFS= read -r c_dep; do stage_file "${c_dep}" "${STAGE_DIR}" done < <(binary_to_libraries "${binary_path}") } main "$@" kind-0.27.0/images/local-path-provisioner/000077500000000000000000000000001475376161000204055ustar00rootroot00000000000000kind-0.27.0/images/local-path-provisioner/Dockerfile000066400000000000000000000032521475376161000224010ustar00rootroot00000000000000# Copyright 2022 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # NOTE the actual go version will be overridden FROM --platform=$BUILDPLATFORM docker.io/library/golang:latest COPY --chmod=0755 scripts/third_party/gimme/gimme /usr/local/bin/ RUN git clone --filter=tree:0 https://github.com/rancher/local-path-provisioner ARG VERSION # set by makefile to .go-version # TODO: scripts/build builds for multiple platforms, so we waste a little time here ARG TARGETARCH GO_VERSION RUN eval "$(gimme "${GO_VERSION}")" \ && export GOTOOLCHAIN="go${GO_VERSION}" \ && cd local-path-provisioner \ && git fetch && git checkout "${VERSION}" \ && GOARCH=$TARGETARCH scripts/build \ && mv bin/local-path-provisioner-$TARGETARCH /usr/local/bin/local-path-provisioner \ && GOBIN=/usr/local/bin go install github.com/google/go-licenses@latest \ && GOARCH=$TARGETARCH go-licenses save --save_path=/_LICENSES . FROM gcr.io/distroless/base-debian11 COPY --from=0 /usr/local/bin/local-path-provisioner /usr/local/bin/local-path-provisioner COPY --from=0 /_LICENSES/* /LICENSES/ COPY --chmod=0644 files/LICENSES/* /LICENSES/* ENTRYPOINT /usr/local/bin/local-path-provisioner kind-0.27.0/images/local-path-provisioner/Makefile000066400000000000000000000012611475376161000220450ustar00rootroot00000000000000# Copyright 2022 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. VERSION=v0.0.30 EXTRA_BUILD_OPT=--build-arg=VERSION=$(VERSION) include $(CURDIR)/../Makefile.common.inkind-0.27.0/images/local-path-provisioner/README.md000066400000000000000000000006251475376161000216670ustar00rootroot00000000000000# local-path-provisioner This image packages https://github.com/rancher/local-path-provisioner to meet our requirements. - Not based on alpine (see: https://github.com/kubernetes/kubernetes/issues/109406#issuecomment-1103479928) - Control over building with current patched go version ## Building You can `make quick` in this directory to build a test image. To push an actual image use `make push`. kind-0.27.0/images/local-path-provisioner/cloudbuild.yaml000066400000000000000000000004101475376161000234120ustar00rootroot00000000000000# See https://cloud.google.com/cloud-build/docs/build-config options: substitution_option: ALLOW_LOOSE machineType: E2_HIGHCPU_8 steps: - name: gcr.io/k8s-testimages/krte:latest-master entrypoint: make args: ['-C', 'images/local-path-provisioner', 'push'] kind-0.27.0/images/local-path-provisioner/files/000077500000000000000000000000001475376161000215075ustar00rootroot00000000000000kind-0.27.0/images/local-path-provisioner/files/LICENSES/000077500000000000000000000000001475376161000227145ustar00rootroot00000000000000kind-0.27.0/images/local-path-provisioner/files/LICENSES/README.txt000066400000000000000000000002641475376161000244140ustar00rootroot00000000000000This directory contains license files and notices from binaries built for this image and the dependencies of those binaries, as collected by https://github.com/google/go-licenses. kind-0.27.0/images/local-path-provisioner/scripts/000077500000000000000000000000001475376161000220745ustar00rootroot00000000000000kind-0.27.0/images/local-path-provisioner/scripts/third_party/000077500000000000000000000000001475376161000244255ustar00rootroot00000000000000kind-0.27.0/images/local-path-provisioner/scripts/third_party/gimme/000077500000000000000000000000001475376161000255235ustar00rootroot00000000000000kind-0.27.0/images/local-path-provisioner/scripts/third_party/gimme/LICENSE000066400000000000000000000021021475376161000265230ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2015-2018 gimme contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. kind-0.27.0/images/local-path-provisioner/scripts/third_party/gimme/README.md000066400000000000000000000002171475376161000270020ustar00rootroot00000000000000# gimme This is an unmodified copy of [gimme], so we don't have to download it from the internet. [gimme]: https://github.com/travis-ci/gimmekind-0.27.0/images/local-path-provisioner/scripts/third_party/gimme/gimme000077500000000000000000000662441475376161000265630ustar00rootroot00000000000000#!/usr/bin/env bash # vim:noexpandtab:ts=2:sw=2: # #+ Usage: $(basename $0) [flags] [go-version] [version-prefix] #+ - #+ Version: ${GIMME_VERSION} #+ Copyright: ${GIMME_COPYRIGHT} #+ License URL: ${GIMME_LICENSE_URL} #+ - #+ Install go! There are multiple types of installations available, with 'auto' being the default. #+ If either 'auto' or 'binary' is specified as GIMME_TYPE, gimme will first check for an existing #+ go installation. This behavior may be disabled by providing '-f/--force/force' as first positional #+ argument. #+ - #+ Option flags: #+ -h --help help - show this help text and exit #+ -V --version version - show the version only and exit #+ -f --force force - remove the existing go installation if present prior to install #+ -l --list list - list installed go versions and exit #+ -k --known known - list known go versions and exit #+ --force-known-update - when used with --known, ignores the cache and updates #+ -r --resolve resolve - resolve a version specifier to a version, show that and exit #+ - #+ Influential env vars: #+ - #+ GIMME_GO_VERSION - version to install (*REQUIRED*, may be given as first positional arg) #+ GIMME_VERSION_PREFIX - prefix for installed versions (default '${GIMME_VERSION_PREFIX}', #+ may be given as second positional arg) #+ GIMME_ARCH - arch to install (default '${GIMME_ARCH}') #+ GIMME_BINARY_OSX - darwin-specific binary suffix (default '${GIMME_BINARY_OSX}') #+ GIMME_ENV_PREFIX - prefix for env files (default '${GIMME_ENV_PREFIX}') #+ GIMME_GO_GIT_REMOTE - git remote for git-based install (default '${GIMME_GO_GIT_REMOTE}') #+ GIMME_OS - os to install (default '${GIMME_OS}') #+ GIMME_TMP - temp directory (default '${GIMME_TMP}') #+ GIMME_TYPE - install type to perform ('auto', 'binary', 'source', or 'git') #+ (default '${GIMME_TYPE}') #+ GIMME_INSTALL_RACE - install race directory after compile if non-empty. #+ If the install type is 'binary', this option is ignored. #+ GIMME_DEBUG - enable tracing if non-empty #+ GIMME_NO_ENV_ALIAS - disable creation of env 'alias' file when os and arch match host #+ GIMME_SILENT_ENV - omit the 'go version' line from env file #+ GIMME_CGO_ENABLED - enable build of cgo support #+ GIMME_CC_FOR_TARGET - cross compiler for cgo support #+ GIMME_DOWNLOAD_BASE - override base URL dir for download (default '${GIMME_DOWNLOAD_BASE}') #+ GIMME_LIST_KNOWN - override base URL for known go versions (default '${GIMME_LIST_KNOWN}') #+ GIMME_KNOWN_CACHE_MAX - seconds the cache for --known is valid for (default '${GIMME_KNOWN_CACHE_MAX}') #+ - # set -e shopt -s nullglob shopt -s dotglob shopt -s extglob set -o pipefail [[ ${GIMME_DEBUG} ]] && set -x readonly GIMME_VERSION="v1.5.4" readonly GIMME_COPYRIGHT="Copyright (c) 2015-2020 gimme contributors" readonly GIMME_LICENSE_URL="https://raw.githubusercontent.com/travis-ci/gimme/${GIMME_VERSION}/LICENSE" export GIMME_VERSION export GIMME_COPYRIGHT export GIMME_LICENSE_URL program_name="$(basename "$0")" # shellcheck disable=SC1117 warn() { printf >&2 "%s: %s\n" "${program_name}" "${*}"; } die() { warn "$@" exit 1 } # We don't want to go around hitting Google's servers with requests for # files named HEAD@{date}.tar so we only try binary/source downloads if # it looks like a plausible name to us. # We don't need to support 0. releases of Go. # We don't support 5 digit major-versions of Go (limit back-tracking in RE). # We don't support very long versions # (both to avoid annoying download server operators with attacks and # because regexp backtracking can be pathological). # Per _assert_version_given we do assume 2.0 not 2 ALLOWED_UPSTREAM_VERSION_RE='^[1-9][0-9]{0,3}(\.[0-9][0-9a-zA-Z_-]{0,9})+$' # # The main path which allowed these to leak upstream before has been closed # but a valid git repo tag or branch-name will still reach the point of # being _tried_ upstream. # _do_curl "url" "file" _do_curl() { mkdir -p "$(dirname "${2}")" if command -v curl >/dev/null; then curl -sSLf "${1}" -o "${2}" 2>/dev/null return fi if command -v wget >/dev/null; then wget -q "${1}" -O "${2}" 2>/dev/null return fi if command -v fetch >/dev/null; then fetch -q "${1}" -o "${2}" 2>/dev/null return fi echo >&2 'error: no curl, wget, or fetch found' exit 1 } # _sha256sum "file" _sha256sum() { if command -v sha256sum &>/dev/null; then sha256sum "$@" elif command -v gsha256sum &>/dev/null; then gsha256sum "$@" else shasum -a 256 "$@" fi } # sort versions, handling 1.10 after 1.9, not before 1.2 # FreeBSD sort has --version-sort, none of the others do # Looks like --general-numeric-sort is the safest; checked macOS 10.12.6, FreeBSD 10.3, Ubuntu Trusty if sort --version-sort /dev/null; then _version_sort() { sort --version-sort; } else _version_sort() { # If we go to four-digit minor or patch versions, then extend the padding here # (but in such a world, perhaps --version-sort will have become standard by then?) sed -E 's/\.([0-9](\.|$))/.00\1/g; s/\.([0-9][0-9](\.|$))/.0\1/g' | sort --general-numeric-sort | sed 's/\.00*/./g' } fi # _do_curls "file" "url" ["url"...] _do_curls() { f="${1}" shift if _sha256sum -c "${f}.sha256" &>/dev/null; then return 0 fi for url in "${@}"; do if _do_curl "${url}" "${f}"; then if _do_curl "${url}.sha256" "${f}.sha256"; then echo "$(cat "${f}.sha256") ${f}" >"${f}.sha256.tmp" mv "${f}.sha256.tmp" "${f}.sha256" if ! _sha256sum -c "${f}.sha256" &>/dev/null; then warn "sha256sum failed for '${f}'" warn 'continuing to next candidate URL' continue fi fi return fi done rm -f "${f}" return 1 } # _binary "version" "file.tar.gz" "arch" _binary() { local version=${1} local file=${2} local arch=${3} urls=( "${GIMME_DOWNLOAD_BASE}/go${version}.${GIMME_OS}-${arch}.tar.gz" ) if [[ "${GIMME_OS}" == 'darwin' && "${GIMME_BINARY_OSX}" ]]; then urls=( "${GIMME_DOWNLOAD_BASE}/go${version}.${GIMME_OS}-${arch}-${GIMME_BINARY_OSX}.tar.gz" "${urls[@]}" ) fi if [ "${arch}" = 'arm' ]; then # attempt "armv6l" vs just "arm" first (since that's what's officially published) urls=( "${GIMME_DOWNLOAD_BASE}/go${version}.${GIMME_OS}-${arch}v6l.tar.gz" # go1.6beta2 & go1.6rc1 "${GIMME_DOWNLOAD_BASE}/go${version}.${GIMME_OS}-${arch}6.tar.gz" # go1.6beta1 "${urls[@]}" ) fi if [ "${GIMME_OS}" = 'windows' ]; then urls=( "${GIMME_DOWNLOAD_BASE}/go${version}.${GIMME_OS}-${arch}.zip" ) fi _do_curls "${file}" "${urls[@]}" } # _source "version" "file.src.tar.gz" _source() { urls=( "${GIMME_DOWNLOAD_BASE}/go${1}.src.tar.gz" "https://github.com/golang/go/archive/go${1}.tar.gz" ) _do_curls "${2}" "${urls[@]}" } # _fetch "dir" _fetch() { mkdir -p "$(dirname "${1}")" if [[ -d "${1}/.git" ]]; then ( cd "${1}" git remote set-url origin "${GIMME_GO_GIT_REMOTE}" git fetch -q --all && git fetch -q --tags ) return fi git clone -q "${GIMME_GO_GIT_REMOTE}" "${1}" } # _checkout "version" "dir" # NB: might emit a "renamed version" on stdout _checkout() { local spec="${1:?}" godir="${2:?}" # We are called twice, once during validation that a version was given and # later during build. We don't want to fetch twice, so we are fetching # during the validation only, in the caller. if [[ "${spec}" =~ ^[0-9a-f]{6,}$ ]]; then # We always treat this as a commit sha, whether instead of doing # branch tests etc. It looks like a commit sha and the Go maintainers # aren't daft enough to use pure hex for a tag or branch. git -C "$godir" reset -q --hard "${spec}" || return 1 return 0 fi # If spec looks like HEAD^{something} or HEAD^^^ then trying # origin/$spec would succeed but we'd write junk to the filesystem, # propagating annoying characters out. local retval probe_named disallow rev probe_named=1 disallow='[@^~:{}]' if [[ "${spec}" =~ $disallow ]]; then probe_named=0 [[ "${spec}" != "@" ]] || spec="HEAD" fi try_spec() { git -C "${godir}" reset -q --hard "$@" -- 2>/dev/null; } retval=1 if ((probe_named)); then retval=0 try_spec "origin/${spec}" || try_spec "origin/go${spec}" || { [[ "${spec}" == "tip" ]] && try_spec origin/master; } || try_spec "refs/tags/${spec}" || try_spec "refs/tags/go${spec}" || retval=1 fi if ((retval)); then retval=0 # We're about to reset anyway, if we succeed, so we should reset to a # known state before parsing what might be relative specs try_spec origin/master && rev="$(git -C "${godir}" rev-parse --verify -q "${spec}^{object}")" && try_spec "${rev}" && git -C "${godir}" rev-parse --verify -q --short=12 "${rev}" || retval=1 # that rev-parse prints to stdout, so we can affect the version seen fi unset -f try_spec return $retval } # _extract "file.tar.gz" "dir" _extract() { mkdir -p "${2}" if [[ "${1}" == *.tar.gz ]]; then tar -xf "${1}" -C "${2}" --strip-components 1 else unzip -q "${1}" -d "${2}" mv "${2}"/go/* "${2}" rmdir "${2}"/go fi } # _setup_bootstrap _setup_bootstrap() { local versions=("1.18" "1.17" "1.16" "1.15" "1.14" "1.13" "1.12" "1.11" "1.10" "1.9" "1.8" "1.7" "1.6" "1.5" "1.4") # try existing for v in "${versions[@]}"; do for candidate in "${GIMME_ENV_PREFIX}/go${v}"*".env"; do if [ -s "${candidate}" ]; then # shellcheck source=/dev/null GOROOT_BOOTSTRAP="$(source "${candidate}" 2>/dev/null && go env GOROOT)" export GOROOT_BOOTSTRAP return 0 fi done done # try binary for v in "${versions[@]}"; do if [ -n "$(_try_binary "${v}" "${GIMME_HOSTARCH}")" ]; then export GOROOT_BOOTSTRAP="${GIMME_VERSION_PREFIX}/go${v}.${GIMME_OS}.${GIMME_HOSTARCH}" return 0 fi done echo >&2 "Unable to setup go bootstrap from existing or binary" return 1 } # _compile "dir" _compile() { ( if grep -q GOROOT_BOOTSTRAP "${1}/src/make.bash" &>/dev/null; then _setup_bootstrap || return 1 fi cd "${1}" if [[ -d .git ]]; then git clean -dfx -q fi cd src export GOOS="${GIMME_OS}" GOARCH="${GIMME_ARCH}" export CGO_ENABLED="${GIMME_CGO_ENABLED}" export CC_FOR_TARGET="${GIMME_CC_FOR_TARGET}" local make_log="${1}/make.${GOOS}.${GOARCH}.log" if [[ "${GIMME_DEBUG}" -ge "2" ]]; then ./make.bash -v 2>&1 | tee "${make_log}" 1>&2 || return 1 else ./make.bash &>"${make_log}" || return 1 fi ) } _try_install_race() { if [[ ! "${GIMME_INSTALL_RACE}" ]]; then return 0 fi "${1}/bin/go" install -race std } _can_compile() { cat >"${GIMME_TMP}/test.go" <<'EOF' package main import "os" func main() { os.Exit(0) } EOF "${1}/bin/go" run "${GIMME_TMP}/test.go" } # _env "dir" _env() { [[ -d "${1}/bin" && -x "${1}/bin/go" ]] || return 1 # if we try to run a Darwin binary on Linux, we need to fail so 'auto' can fallback to cross-compiling from source # automatically GOROOT="${1}" GOFLAGS="" "${1}/bin/go" version &>/dev/null || return 1 # https://twitter.com/davecheney/status/431581286918934528 # we have to GOROOT sometimes because we use official release binaries in unofficial locations :( # # Issue 87 leads to: # No, we should _always_ set GOROOT when using official release binaries, and sanest to just always set it. # The "avoid setting it" is _only_ for people using official releases in official locations. # Tools like `gimme` are the reason that GOROOT-in-env exists. echo if [[ "$(GOROOT="${1}" "${1}/bin/go" env GOHOSTOS)" == "${GIMME_OS}" ]]; then echo 'unset GOOS;' else echo 'export GOOS="'"${GIMME_OS}"'";' fi if [[ "$(GOROOT="${1}" "${1}/bin/go" env GOHOSTARCH)" == "${GIMME_ARCH}" ]]; then echo 'unset GOARCH;' else echo 'export GOARCH="'"${GIMME_ARCH}"'";' fi echo "export GOROOT='${1}';" # shellcheck disable=SC2016 echo 'export PATH="'"${1}/bin"':${PATH}";' if [[ -z "${GIMME_SILENT_ENV}" ]]; then echo 'go version >&2;' fi echo } # _env_alias "dir" "env-file" _env_alias() { if [[ "${GIMME_NO_ENV_ALIAS}" ]]; then echo "${2}" return fi if [[ "$(GOROOT="${1}" "${1}/bin/go" env GOHOSTOS)" == "${GIMME_OS}" && "$(GOROOT="${1}" "${1}/bin/go" env GOHOSTARCH)" == "${GIMME_ARCH}" ]]; then # GIMME_GO_VERSION might be a branch, which can contain '/' local dest="${GIMME_ENV_PREFIX}/go${GIMME_GO_VERSION//\//__}.env" cp "${2}" "${dest}" ln -sf "${dest}" "${GIMME_ENV_PREFIX}/latest.env" echo "${dest}" else echo "${2}" fi } _try_existing() { case "${1}" in binary) local existing_ver="${GIMME_VERSION_PREFIX}/go${GIMME_GO_VERSION}.${GIMME_OS}.${GIMME_ARCH}" local existing_env="${GIMME_ENV_PREFIX}/go${GIMME_GO_VERSION}.${GIMME_OS}.${GIMME_ARCH}.env" ;; source) local existing_ver="${GIMME_VERSION_PREFIX}/go${GIMME_GO_VERSION}.src" local existing_env="${GIMME_ENV_PREFIX}/go${GIMME_GO_VERSION}.src.env" ;; *) _try_existing binary || _try_existing source return $? ;; esac if [[ -x "${existing_ver}/bin/go" && -s "${existing_env}" ]]; then # newer envs have existing semi-colon at end of line, because newer gimme # puts them there; envs created before that change lack those semi-colons # and should gain them, to make it easier for people using eval without # double-quoting the command substition. sed -e 's/\([^;]\)$/\1;/' <"${existing_env}" # gimme is the corner-case where GOROOT _should_ be overriden, since if the # ancilliary tooling's system-internal DefaultGoroot exists, and GOROOT is # unset, then it will be used and the wrong golang will be picked up. # Lots of old installs won't have GOROOT; munge it from $PATH if grep -qs '^unset GOROOT' -- "${existing_env}"; then sed -n -e 's/^export PATH="\(.*\)\/bin:.*$/export GOROOT='"'"'\1'"'"';/p' <"${existing_env}" echo fi # Export the same variables whether building new or using existing echo "export GIMME_ENV='${existing_env}';" return fi return 1 } # _try_binary "version" "arch" _try_binary() { local version=${1} local arch=${2} local bin_tgz="${GIMME_TMP}/go${version}.${GIMME_OS}.${arch}.tar.gz" local bin_dir="${GIMME_VERSION_PREFIX}/go${version}.${GIMME_OS}.${arch}" local bin_env="${GIMME_ENV_PREFIX}/go${version}.${GIMME_OS}.${arch}.env" [[ "${version}" =~ ${ALLOWED_UPSTREAM_VERSION_RE} ]] || return 1 if [ "${GIMME_OS}" = 'windows' ]; then bin_tgz=${bin_tgz%.tar.gz}.zip fi _binary "${version}" "${bin_tgz}" "${arch}" || return 1 _extract "${bin_tgz}" "${bin_dir}" || return 1 _env "${bin_dir}" | tee "${bin_env}" || return 1 echo "export GIMME_ENV=\"$(_env_alias "${bin_dir}" "${bin_env}")\"" } _try_source() { local src_tgz="${GIMME_TMP}/go${GIMME_GO_VERSION}.src.tar.gz" local src_dir="${GIMME_VERSION_PREFIX}/go${GIMME_GO_VERSION}.src" local src_env="${GIMME_ENV_PREFIX}/go${GIMME_GO_VERSION}.src.env" [[ "${GIMME_GO_VERSION}" =~ ${ALLOWED_UPSTREAM_VERSION_RE} ]] || return 1 _source "${GIMME_GO_VERSION}" "${src_tgz}" || return 1 _extract "${src_tgz}" "${src_dir}" || return 1 _compile "${src_dir}" || return 1 _try_install_race "${src_dir}" || return 1 _env "${src_dir}" | tee "${src_env}" || return 1 echo "export GIMME_ENV=\"$(_env_alias "${src_dir}" "${src_env}")\"" } # We do _not_ try to use any version caching with _try_existing(), but instead # build afresh each time. We don't want to deal with someone moving the repo # to other-version, doing an install, then resetting it back to # last-version-we-saw and thus introducing conflicts. # # If you want to re-use a built-at-spec version, then avoid moving the repo # and source the generated .env manually. # Note that the env will just refer to the 'go' directory, so it's not safe # to reuse anyway. _try_git() { local git_dir="${GIMME_VERSION_PREFIX}/go" local git_env="${GIMME_ENV_PREFIX}/go.git.${GIMME_OS}.${GIMME_ARCH}.env" local resolved_sha # Any tags should have been resolved when we asserted that we were # given a version, so no need to handle that here. _checkout "${GIMME_GO_VERSION}" "${git_dir}" >/dev/null || return 1 _compile "${git_dir}" || return 1 _try_install_race "${git_dir}" || return 1 _env "${git_dir}" | tee "${git_env}" || return 1 echo "export GIMME_ENV=\"$(_env_alias "${git_dir}" "${git_env}")\"" } _wipe_version() { local env_file="${GIMME_ENV_PREFIX}/go${1}.${GIMME_OS}.${GIMME_ARCH}.env" if [[ -s "${env_file}" ]]; then rm -rf "$(awk -F\" '/GOROOT/ { print $2 }' "${env_file}")" rm -f "${env_file}" fi } _list_versions() { if [ ! -d "${GIMME_VERSION_PREFIX}" ]; then return 0 fi local current_version current_version="$(go env GOROOT 2>/dev/null)" current_version="${current_version##*/go}" current_version="${current_version%%.${GIMME_OS}.*}" # 1.1 1.10 1.2 is bad; zsh has `setopt numeric_glob_sort` but bash # doesn't appear to have anything like that. for d in "${GIMME_VERSION_PREFIX}/go"*".${GIMME_OS}."*; do local cleaned="${d##*/go}" cleaned="${cleaned%%.${GIMME_OS}.*}" echo "${cleaned}" done | _version_sort | while read -r cleaned; do echo -en "${cleaned}" if [[ "${cleaned}" == "${current_version}" ]]; then echo -en ' <= current' >&2 fi echo done } _update_remote_known_list_if_needed() { # shellcheck disable=SC1117 local exp="go([[:alnum:]\.]*)\.src.*" # :alnum: catches beta versions too local list="${GIMME_VERSION_PREFIX}/known-versions.txt" local dlfile="${GIMME_TMP}/known-dl" if [[ -e "${list}" ]] && ! ((force_known_update)) && ! _file_older_than_secs "${list}" "${GIMME_KNOWN_CACHE_MAX}"; then echo "${list}" return 0 fi [[ -d "${GIMME_VERSION_PREFIX:?}" ]] || mkdir -p -- "${GIMME_VERSION_PREFIX}" _do_curl "${GIMME_LIST_KNOWN}" "${dlfile}" while read -r line; do if [[ "${line}" =~ ${exp} ]]; then echo "${BASH_REMATCH[1]}" fi done <"${dlfile}" | _version_sort | uniq >"${list}.new" rm -f "${list}" &>/dev/null mv "${list}.new" "${list}" rm -f "${dlfile}" echo "${list}" return 0 } _list_known() { local knownfile knownfile="$(_update_remote_known_list_if_needed)" ( _list_versions 2>/dev/null cat -- "${knownfile}" ) | grep . | _version_sort | uniq } # For the "invoked on commandline" case, we want to always pass unknown # strings through, so that we can be a uniqueness filter, but for unknown # names we want to exit with a value other than 1, so we document that # we'll exit 2. For use by other functions, 2 is as good as 1. _resolve_version() { case "${1}" in stable) _get_curr_stable return 0 ;; oldstable) _get_old_stable return 0 ;; tip) echo "tip" return 0 ;; *.x) true ;; *) echo "${1}" local GIMME_GO_VERSION="$1" local ASSERT_ABORT='return' if _assert_version_given 2>/dev/null; then return 0 fi warn "version specifier '${1}' unknown" return 2 ;; esac # We have a .x suffix local base="${1%.x}" local ver last='' known known="$(_update_remote_known_list_if_needed)" # will be version-sorted if [[ ! "${base}" =~ ^[0-9.]+$ ]]; then warn "resolve pattern '${base}.x' invalid for .x finding" return 2 fi # The `.x` is optional; "1.10" matches "1.10.x" local search="^${base//./\\.}(\\.[0-9.]+)?\$" # avoid regexp attacks while read -r ver; do [[ "${ver}" =~ $search ]] || continue last="${ver}" done <"$known" if [[ -n "${last}" ]]; then echo "${last}" return 0 fi echo "${1}" warn "given '${1}' but no release for '${base}' found" return 2 } _realpath() { # shellcheck disable=SC2005 [ -d "$1" ] && echo "$(cd "$1" && pwd)" || echo "$(cd "$(dirname "$1")" && pwd)/$(basename "$1")" } _get_curr_stable() { local stable="${GIMME_VERSION_PREFIX}/stable" if _file_older_than_secs "${stable}" 86400; then _update_stable "${stable}" fi cat "${stable}" } _get_old_stable() { local oldstable="${GIMME_VERSION_PREFIX}/oldstable" if _file_older_than_secs "${oldstable}" 86400; then _update_oldstable "${oldstable}" fi cat "${oldstable}" } _update_stable() { local stable="${1}" local url="https://golang.org/VERSION?m=text" _do_curl "${url}" "${stable}" sed -i.old -e 's/^go\(.*\)/\1/' "${stable}" rm -f "${stable}.old" } _update_oldstable() { local oldstable="${1}" local oldstable_x oldstable_x=$(_get_curr_stable | awk -F. '{ $2--; print $1 "." $2 "." "x" }') _resolve_version "${oldstable_x}" >"${oldstable}" } _last_mod_timestamp() { local filename="${1}" case "${GIMME_HOSTOS}" in darwin | *bsd) stat -f %m "${filename}" ;; linux) stat -c %Y "${filename}" ;; esac } _file_older_than_secs() { local file="${1}" local age_secs="${2}" local ts # if the file does not exist, we return true, as the cache needs updating ts="$(_last_mod_timestamp "${file}" 2>/dev/null)" || return 0 ((($(date +%s) - ts) > age_secs)) } _assert_version_given() { # By the time we're called, aliases such as "stable" must have been resolved # but we could be a reference in git. # # Versions can include suffices such as in "1.8beta2", so our assumption is that # there will always be a minor present; the first public release was "1.0" so # we assume "2.0" not "2". if [[ -z "${GIMME_GO_VERSION}" ]]; then echo >&2 'error: no GIMME_GO_VERSION supplied' echo >&2 " ex: GIMME_GO_VERSION=1.4.1 ${0} ${*}" echo >&2 " ex: ${0} 1.4.1 ${*}" ${ASSERT_ABORT:-exit} 1 fi # Note: _resolve_version calls back to us (_assert_version_given), but # only for cases where the version does not end with .x, so this should # be safe. # This should be untangled. PRs accepted, good starter project. if [[ "${GIMME_GO_VERSION}" == *.x ]]; then GIMME_GO_VERSION="$(_resolve_version "${GIMME_GO_VERSION}")" || ${ASSERT_ABORT:-exit} 1 fi if [[ "${GIMME_GO_VERSION}" == +([[:digit:]]).+([[:digit:]])* ]]; then return 0 fi # Here we resolve symbolic references. If we don't, then we get some # random git tag name being accepted as valid and then we try to # curl garbage from upstream. if [[ "${GIMME_TYPE}" == "auto" || "${GIMME_TYPE}" == "git" ]]; then local git_dir="${GIMME_VERSION_PREFIX}/go" local resolved_sha _fetch "${git_dir}" if resolved_sha="$(_checkout "${GIMME_GO_VERSION}" "${git_dir}")"; then if [[ -n "${resolved_sha}" ]]; then # Break our normal silence, this one really needs to be seen on stderr # always; auditability and knowing what version of Go you got wins. warn "resolved '${GIMME_GO_VERSION}' to '${resolved_sha}'" GIMME_GO_VERSION="${resolved_sha}" fi return 0 fi fi echo >&2 'error: GIMME_GO_VERSION not recognized as valid' echo >&2 " got: ${GIMME_GO_VERSION}" ${ASSERT_ABORT:-exit} 1 } _exclude_from_backups() { # Please avoid anything which requires elevated privileges or is obnoxious # enough to offend the invoker case "${GIMME_HOSTOS}" in darwin) # Darwin: Time Machine is "standard", we can add others. The default # mechanism is sticky, as an attribute on the dir, requires no # privileges, is idempotent (and doesn't support -- to end flags). tmutil addexclusion "$@" ;; esac } _versint() { IFS=" " read -r -a args <<<"${1//[^0-9]/ }" printf '1%03d%03d%03d%03d' "${args[@]}" } _to_goarch() { case "${1}" in aarch64) echo "arm64" ;; *) echo "${1}" ;; esac } : "${GIMME_OS:=$(uname -s | tr '[:upper:]' '[:lower:]')}" : "${GIMME_HOSTOS:=$(uname -s | tr '[:upper:]' '[:lower:]')}" : "${GIMME_ARCH:=$(_to_goarch "$(uname -m)")}" : "${GIMME_HOSTARCH:=$(_to_goarch "$(uname -m)")}" : "${GIMME_ENV_PREFIX:=${HOME}/.gimme/envs}" : "${GIMME_VERSION_PREFIX:=${HOME}/.gimme/versions}" : "${GIMME_TMP:=${TMPDIR:-/tmp}/gimme}" : "${GIMME_GO_GIT_REMOTE:=https://github.com/golang/go.git}" : "${GIMME_TYPE:=auto}" # 'auto', 'binary', 'source', or 'git' : "${GIMME_BINARY_OSX:=osx10.8}" : "${GIMME_DOWNLOAD_BASE:=https://dl.google.com/go}" : "${GIMME_LIST_KNOWN:=https://golang.org/dl}" : "${GIMME_KNOWN_CACHE_MAX:=10800}" # The version prefix must be an absolute path case "${GIMME_VERSION_PREFIX}" in /*) true ;; *) echo >&2 " Fixing GIMME_VERSION_PREFIX from relative: $GIMME_VERSION_PREFIX" GIMME_VERSION_PREFIX="$(pwd)/${GIMME_VERSION_PREFIX}" echo >&2 " to: $GIMME_VERSION_PREFIX" ;; esac case "${GIMME_OS}" in mingw* | msys_nt*) # Minimalist GNU for Windows GIMME_OS='windows' if [ "${GIMME_ARCH}" = 'i686' ]; then GIMME_ARCH="386" else GIMME_ARCH="amd64" fi ;; esac force_install=0 force_known_update=0 while [[ $# -gt 0 ]]; do case "${1}" in -h | --help | help | wat) _old_ifs="$IFS" IFS=';' awk '/^#\+ / { sub(/^#\+ /, "", $0) ; sub(/-$/, "", $0) ; print $0 }' "$0" | while read -r line; do eval "echo \"$line\"" done IFS="$_old_ifs" exit 0 ;; -V | --version | version) echo "${GIMME_VERSION}" exit 0 ;; -r | --resolve | resolve) # The normal mkdir of versions is below; we don't want to move it up # to where we create files just if asked our version; thus # _resolve_version has to mkdir the versions dir itself. if [[ $# -ge 2 ]]; then _resolve_version "${2}" elif [[ -n "${GIMME_GO_VERSION:-}" ]]; then _resolve_version "${GIMME_GO_VERSION}" else die "resolve must be given a version to resolve" fi exit $? ;; -l | --list | list) _list_versions exit 0 ;; -k | --known | known) _list_known exit 0 ;; -f | --force | force) force_install=1 ;; --force-known-update | force-known-update) force_known_update=1 ;; -i | install) true # ignore a dummy argument ;; *) break ;; esac shift done if [[ -n "${1}" ]]; then GIMME_GO_VERSION="${1}" fi if [[ -n "${2}" ]]; then GIMME_VERSION_PREFIX="${2}" fi case "${GIMME_ARCH}" in x86_64) GIMME_ARCH=amd64 ;; x86) GIMME_ARCH=386 ;; arm64) if [[ "${GIMME_GO_VERSION}" != master && "$(_versint "${GIMME_GO_VERSION}")" < "$(_versint 1.5)" ]]; then echo >&2 "error: ${GIMME_ARCH} is not supported by this go version" echo >&2 "try go1.5 or newer" exit 1 fi if [[ "${GIMME_HOSTOS}" == "linux" && "${GIMME_HOSTARCH}" != "${GIMME_ARCH}" ]]; then : "${GIMME_CC_FOR_TARGET:="aarch64-linux-gnu-gcc"}" fi ;; arm*) GIMME_ARCH=arm ;; esac case "${GIMME_HOSTARCH}" in x86_64) GIMME_HOSTARCH=amd64 ;; x86) GIMME_HOSTARCH=386 ;; arm64) ;; arm*) GIMME_HOSTARCH=arm ;; esac case "${GIMME_GO_VERSION}" in stable) GIMME_GO_VERSION=$(_get_curr_stable) ;; oldstable) GIMME_GO_VERSION=$(_get_old_stable) ;; esac _assert_version_given "$@" ((force_install)) && _wipe_version "${GIMME_GO_VERSION}" unset GOARCH unset GOBIN unset GOOS unset GOPATH unset GOROOT unset CGO_ENABLED unset CC_FOR_TARGET # GO111MODULE breaks build of Go itself unset GO111MODULE mkdir -p "${GIMME_VERSION_PREFIX}" "${GIMME_ENV_PREFIX}" # The envs dir stays small and provides a record of what had been installed # whereas the versions dir grows by hundreds of MB per version and is not # intended to support local modifications (as that subverts the point of gimme) # _and_ is a cache, so we're unilaterally declaring that the contents of # the versions dir should be excluded from system backups. _exclude_from_backups "${GIMME_VERSION_PREFIX}" GIMME_VERSION_PREFIX="$(_realpath "${GIMME_VERSION_PREFIX}")" GIMME_ENV_PREFIX="$(_realpath "${GIMME_ENV_PREFIX}")" if ! case "${GIMME_TYPE}" in binary) _try_existing binary || _try_binary "${GIMME_GO_VERSION}" "${GIMME_ARCH}" ;; source) _try_existing source || _try_source || _try_git ;; git) _try_git ;; auto) _try_existing || _try_binary "${GIMME_GO_VERSION}" "${GIMME_ARCH}" || _try_source || _try_git ;; *) echo >&2 "I don't know how to '${GIMME_TYPE}'." echo >&2 " Try 'auto', 'binary', 'source', or 'git'." exit 1 ;; esac; then echo >&2 "I don't have any idea what to do with '${GIMME_GO_VERSION}'." echo >&2 " (using download type '${GIMME_TYPE}')" exit 1 fi kind-0.27.0/images/node/000077500000000000000000000000001475376161000147315ustar00rootroot00000000000000kind-0.27.0/images/node/README.md000066400000000000000000000014611475376161000162120ustar00rootroot00000000000000## images/node See: [`pkg/build/nodeimage/build.go`][pkg/build/nodeimage/build.go] and [`pkg/build/nodeimage/buildcontext.go`][pkg/build/nodeimage/buildcontext.go], this image is built programmatically with docker run / exec / commit for performance reasons with large artifacts. Roughly this image is [the base image](./../base), with the addition of: - installing the Kubernetes packages / binaries - placing the Kubernetes docker images in `/kind/images/*.tar` - placing a file in `/kind/version` containing the Kubernetes semver See [`node-image`][node-image.md] for more design details. [pkg/build/nodeimage/build.go]: ./../../pkg/build/nodeimage/build.go [pkg/build/nodeimage/buildcontext.go]: ./../../pkg/build/nodeimage/buildcontext.go [node-image.md]: https://kind.sigs.k8s.io/docs/design/node-image kind-0.27.0/logo/000077500000000000000000000000001475376161000134775ustar00rootroot00000000000000kind-0.27.0/logo/LICENSE000077500000000000000000000002061475376161000145050ustar00rootroot00000000000000# The kind logo files are licensed under a choice of either Apache-2.0 or CC-BY-4.0 (Creative Commons Attribution 4.0 International). kind-0.27.0/logo/logo.png000077500000000000000000002472211475376161000151600ustar00rootroot00000000000000PNG  IHDRR6 pHYsod IDATxO&QA:,wQ>)JQQQO} 6:H;,-r'L6eLf2)8Ν~V2YSN7YrDTJ* ֺZ%`Wdjt/7XQFH"B"PHR.U*"q\z 홥zKr;dΩ/LoB~ar_@bX-)\y8|IxWqw ; nJǿՖl=XdBݫ1X_A f+Sv:"[P:Bq23E(҃/ezkC x3z0UNUyxޤ o8LRd# T  AT}rl"*0xúM4@E|[W@aa֯-n}Sqqln5§7>PQFC٪#^l U ~d::j㘊}X"a ^IQ@㣈cN 7Fn%'ň+* +{3;g xW'bdM% .H6?%͗vWHD@ٗ׫o_tƩÑ)I+,7E,+2 Âӛg+'Po_rTM|"-5 og38;}͸uLQܕel}xN&qKjM=9HcQ3i<'?L!CG~.#T8 mL⍸O:C^7І'E#V6KYe~(#c3Q"3X_A+C!WO` LKB+l_EuFf%uW9Tv؛6S7)G<[El:_Ful{:~@*n(36xɽ(<-l\.sV(տ?~t5v64»L {! 2>2? NxlCPis%22>8f`lsҢE&Վp6:mjl!(:/,y.ĉ4ʇZ&NzMcXaPoa}I\^ g`d*< K"B? 'kT;w-sgؕF+\ךJ%оO]RҴOKRn;Fqۇ+P}WT"pDAv@X{w.eR="&tu<\*=P缷.J'ú*)1)0f9VIެML&ce){P{! 9nqZ2qr7O_f^ܛpovPsCƧkttrdh-|8KΆ!s'ߕל (t \ٚqxӱ;1(U>oƦ \x…t]:JJ*upC(wbFJ;Oέ}zo.LPL:124w}9꽷3>図ۯc$ໞ8@hg -+(<TTV'A* 2:[cS7J!5+oYilgR{oKr!Plq R6R/V'&~~y8ekiK>bLPgbl(l"EՕi)uX=Kޙ‚yE8co0fʧv|ң eʐ7 &l:HAƤ|>s 26bڸ)k6]fG] +<2 l(# _ gÁnNg2f!l2Cv,t tiXD)HEeP nROk,zkhIיaUB"ڶJ4B >F" %p:UNǕ^ zezKŪjb.i4@kq>rSm7t.n%q#l!kx8]d H<]%9rBg& imc(ћ/JH{56HT)u\<|:0?_FwP[<@;ί6] _F6Y\:/D(حȂިE Δ  &ALS_-4vԍ?A<`cP76nBA+-ݖ;3fl⾌K6 Nyk tBWf͟{۾»;Yx4T22 yMh#3@Oi@>ACF2ZDHwMirqhOYFF|Tjsd Fw]i3%MNUnegD[)BcKh׺a;~rPx8>$L|&oxc7KPÿB|]蜻3K i[hcehgPfpgˋat?e}2Tގ8F?5kެ涢),&]^ l\ 5x@}ߟVRCmW&"b)/@fj*K+\Z.8l?Heg핰%bX5ajbs:x鲂Zc/+]10vF%smlFc tI=ȲmΫ"|\*˵欒+lX*Rq/?QktaQN; cଌO8Y6QsR1Z5p_Ko&Ý d.azC^|jxr€ &$ g~tP+ï. ~ha|VՒOyFEF8 <"\FEfpdNp7h|bF-CAԟ{;+=-_xPV?=a=z3Segc͟7*p% Ւ}q]Glv.Is395oR3k.t#_~؅5HfNwC#V^{r Pe>uǃTgʾ"rbU3]BF Jbo;?\:43zGY_#j.l/Auإzi?aB=h7uš\!CƚxoIeؐ?.ދ7Mq˵yn.hvekhtXq܉bht>p.&מˏ~*$8T>iP^iB^ۿ<N_Ӭ.@.Y|L܏vڝhiwV>*.>2D.Χ*4fE^ rX;帑R70%lcu]&A ʼ._%2ȫ՞[wC#|O0&#,u5 IR>?s>O%2R*'8!=}&`|otyiK>y?ڳ*v*|^䟜*s{_wC'^'0qV[ a{q˟VI{FaFjn `B#GA`/>Sϔ]=sGKY Q(!Eq |eb[׋- Z!it%"^ !'%Z')NEKlfWq7h//׳p'W*S"áx<`cG޸VK@ƎvUyc)gT6LX>zǏ.!"l(;Ʀbvc2b& .&HuYyj ݉09MʧNGK[<1|Óh#ce⢹)B)76&2Z-60Z6'76&T%1H$,&eª& SBrLZb. G\}Ek+pVbk^[LxOr"z g'h?o_1Bp#MD@s+N_(A;[PC*Z˾nM~\"ًN t@LD]4IJt -q'(pv>0%[i,qѿ-)ŭF (tU7qőm\·]Zw U@zM-9^]?^SkB1P•_#>Pt'ݗIDouzqsي%pgUn0yYs!kgsuF\-Kw?~'2 A% ܅FUutoDtbiH60|b4$*jG!1}/l? ZeE֒|th[ I +')a+tPHz?;^qfjܽKZu%^:)FS`i5O^3£Ks",ɭo#b eJ_[\Ta^u D"t QWr.ZuA}4D"1w"˫J ؜G,HtRo `bq|`yg^h z=Hv-`ű3m8 "q^a?0Y]{.]CP} 8w$j}I29uY} S虝;/랺{,n7+͂ΪkDc?|o NX٘Vx'5qҳ*|ݦkf _r++#1;{q'/qyC=OWj; * {y2]MpChS6E+}mU-Ըuu7a=6(:\fuepfG #6Fynvt ?z  Hn-;HIj lzӽK0O_7„w<@lqr9,[5ScX` ;T'F$a\FHC6#Dk&Cjg5[-гKIgQ9=g rEˎ]`ώ=?.l'e0 ` QE5臿zQQ4o5b}mؘh-%#V} )BLJ'NݴE+.̋ey`.&lpc0g֗*aoBl—HʆY;& 78yr_#v#Oz94eI'S 2RXkE IDAT+s&"_5tl&|nʉd9m/}Z NADMUDdB$ R͛5e̸g 7­׫P,sFSSgl}Z8kj@ 76>pkoF@LDb7lܾ R@~a}$Xtz=vQyz2Ūd6N߫K:p}~]aHvlp-;%L]`F 3 [DȕQm6I=GUVyM$TvgcױD4GC ޓk*(2 [ Fܙ$hs \-j+eL]{w#ab0˅  PN7w=Q }K.}ō 8#7gYsF$`\Tn"6*C\ RPk`?Y' ى-!E"jq31^ךl=Qm UF`,Su%klmoo[=Qf͛(" %e *cA*1svm}4m&m嫏;:B`$29(҈ <*Sd ߮6>R``j{J1^e enٙrUulUט 5fk q䱭mu A*:Fפ3? %V]$F** 42q M*c K\ݶl5M_:HulA|懄NH=6N)`[>\Dpq_ۙV%"arjX"ϯ _72޳ bHٌaуh3!Qr XLōD(WjK`0\U0K4E:3}y 0k {LkZ\՚l ͮj*yyA")ܑ=&pм8R`by]5 1xT;p-6ML`QcBToFFXX~⭮q21TqJ;JTر,t _^-enn`/ /շ)`+hP&Xؘ8exAXeу{G¾Qe2! [zS@U\jd3<%KPޗg-P}5v&A!1</Cǽj }$b6qoc H͒cb" U,6~ܫZd,lLQ7VJL>HXؘ51i YmiWW%>c ž7ioH&aRHcc"TњIMpD4 ,C ;1g cLPJP#t:Ֆk C'S7LՑ ai-@:6vn8'xHF-"tB0POmfn&☨aP؜Mk^ 5#r~R/Xؘbko(/ W*$ ezܿid?–cIY{թʭ} u@;^yqmI ?`ac&yU<'s| ; /3EB'1au [Ž߁0F.4>"o \+jhb:BWY)7E -\d:ypɒKsaUۇp ,ڼ9Yt0Ҽ*Ka0\l,f54XΔk(dFWcn)t"0lVӯTmx[R*i~Љ`BYkB--ҙEO҂x.l[,2:UҢǂJ:+b@) -*r?|&1J>ZAù3©4qlj ]e87 c/RxДRp͓66Մ)oy7Hx/O?I (TF8 9:GCd/g>?]N*%n ;"Z4,yߕo={E'v1Q-pLĸB ѿ{UK;_f2^]J#x8b8s=/KCQ-p$#F(1Gǝ_j|2д\4wm'/v6% q>pF>ULW[&r|J[@Bh"_  O<DJ**ɭ2ecaa!Z4U:λL3^Sl9v@TH`㛩`!ܹ8[-#.Jۺ5yDgiB'v^N0sF;Wke[{C\s|ɻm RbCꞽyT%pxw2l~`Hus!+2h:Eqnۃ\>s/8}nGo8/ɾ7zW=K+G=vsF@X\MgnҒ܀8eIެzF,lkۘCgO$*ڢ.(0󸱰CRԡҭQaۢ4,n/|SL-Ə^+tZ QƤْmH::Ν?X!ƒvC}RΐYD\-Du4I.qԳPnׯx΢FX&%C65Gh`\XeAw^3 [HTU9xK ʪ#,l*"c"jnM6NJa4WlbG ek[hnZ|ZtHP(8MC8{O~-l|a16C~*yЇUɭXV>Y\e76핰/s;rAV@S>g_iNHhȇ b+S=@IڣhԴ Ȕq@$g=uӡ+y`0ק |QO^bQ#|W=e[܄5?ϋ Nk,d5֪{߽SZ'%Ųwi:t{) &A Tk^8A o?1-篎dmzPOͻiء*W_Vl`[)\V;wtP =Z*>۾"tIYYa+S~X4p΂ ={n׊5΅+/:JA0͜*x<|KMaV੝1T<.@/n:p!ÎŮ5df!zVA#lrN)ޚ0h>^NCvqEN:޼w+P c7m.EN_ק‘0.W]KO8gg9'hˮ~mcLZ_OI?8v@!kʝ3*\ׄ=7KO̬`-;g$ԑ߳}6:FRy\GA}]S1X_ŻcHC{EfA{hPݸq`&AcŒ1.{QU|QIoV.𨉍IMJ&&AsZHzV +OXs.b] 7hWIYGra?k#RW,+®WЪ^ >Ľx<2`abzXl;)l4p7[+֗i. wP"* 0vSYzZ\!ʮiU^HLwGa' NiI"Z)틽)>Jҗ?-!vljhpyJrO8Zzz\yF͑nC8kxó-;~쪆kY`YN߇8KhN!*fJHEetÆP=AT)|?} ϶=}ٕi3LYB+9?,̦%j!3hJ IA1 Rng2[ߎsfe3uH{8X ;hﵫKQ5jr7 =ϭ,g]ˈ'pTT[GjXS͢kTߓWj/̀m7B{2\fꖩJ1)O$"lʘ> ix>\EOzi4CCb-cwҹvڻ(宂Y=b Kv0Ot="ݵĆxno^~@$k} i"Q4Q!?y&0;"Azp٭d%O$RzmT&w4{vn6`ڎ%:]Qdm@y'u[ 'k9Sjb$y5)*(1K_k֛@Jٻ(f21RPT uýg0Mr}:ϻ4eh hg/ݏ?gmz~IBΌi@@¾AZ0,skL;Slzu$O'ׁWPdm. jW۵b e2cފ xLky̱bh灇`/$aoC39\Ndx[w$xK/7G2^ƲI)zڑ0@z@/ib*cq b/n kf-.NT1._zz#VQW+U}v 8Le{,*F Ѧ;qD%}%!Rh&+z5֏޽mW|?Lg] 9|r~'C~$T"gc8({kf$"_&_13zb0UB9N(b;^a+*v,B"4TBR܁Vgח_n'q(,lî[U . |a E"QD p%n4'Y*j+˸`ZEfGN=߯' w}%K_|}|»pX;2eIq3~ ϯ,[>DžGszB fOaYc|@X5߂Ba( gw#O.jkѱ# o*.1+%ķ&p8wHCsʑ'}?27 + z-;>}'/A \}TЮe k? ?w}!ًu{oGOb(a~peTW<&| 8xr3FV;PnwؔG7قWi-JwFolzo{t3L8u4{3-LŽpu91߸é:eMd%sbzG5 Bj% v:_oظo7W\{&kKvѩ]8t<`sO%`p-xHQ':+$)ėz@*&~9k>ЖeNqбM({\lSHL\f¥ذi+LyQs-_lo/߾g?TW6HT]%^= V'ZPgQӅ5⳥oCۨsT(nwqo;Iiޞ .aG,1B(֍9K>R-a~e fSF6H*x{uΚ,S.3'\u^o_N(3z/xpkr#[wzZB$΢v1#FgQi`Pw8,s&zG ԄͥK(ŐbHqGe~i:X4^t{Yq=N\M&=[#_qx6ouC&! *J8H+TeaEQ6a}Yo:JQ`&?`? ~5̄ykϾoPmoWn (Y]68x>~^ F^7aG_~6ڼ5¯ k"QV1S?"Χ +a{[bw ml6bIA{/;b|އp8W:0Y-2۝W:d Yr:?r vlG>幐6"\*!Q\ Us֨B˾/]|q.dxv Ɔ9/g?}ٜ8/ym 47zwDe21ÑG=Tҗ2H&EFMe-{ }vh྽+>q7wIOz g%l &:5!d/T`0¦(SQkt g*r-l jQpTG$g򐜐!gձFۿY1~J1t5jfҤc-bz0L]+q9@M"QG Ixod`2<yaLFV9Hبr~/pvC:.Z3a?^mn?T"w92l`oHl.. IDATC,/.8F+\EA#C}lXkNU,:tUHJз›;yY`Sޛ:L^*ޚ<޼W  s[bR@R϶ՅƶㄙPv&SNlcU+JXm`cͭ}^Wf,F~g+D$\ad Sq2\:Ē73o$(gvT7:5RNjͻB$FHdbD/SP !v(lLdmYow(h3:j/)o͹؆R}{ dXɑ[db H;Zm\$g'DCVt#76z 1.,# {O: L!˯(t0KW9IB#RTUWḾsD,lTػx䔐`"W`M\ &bucW/vʚF\w(E0a޸]wc,76&l^~mg)ύ@C,l &QW(B#JY^NF:FXl4HdWĄ$eyM',]iRfC4)ylrsaaG&e7y <NtVYjX$jomc\vUF"R.W*̽7e]*7$B "MlMTWK7 Lx:7)[?Kn6`0HbJơH$M9};!҆ ?;%T/11<  VUpK^{e㥛L&tL D;Hl|K|a`0R'hCouëbh9m6 xgJUWlhNF:_0`0캩 m2.XӃ .~"h! Nfb"޶4* L#͂n]GAPx1jsbȅ5V0$xLaz'mED .cs8b8&75mo4m̖]h-'g"t0ii 9>u  :(S .cl5ȩ42&]5 1ـ#N{՚E I`Jbؚ'zL^ךB)dhfx\ B=Nu8A c04PKȯT7Di{Ix9 'xu\-$b1Lr[Htf+5)m[$"qa7\q W/N@i6 a Rb3Us:id|^J70MdY`0"Kߨږlo"uN;ܚMd ֵ qM7ި{ Ӣn^c3#p8z\cZ%|Ix!q8sx3 ᗬT9Uq>Nl^6鋏0E C|sXkpUyhacEƱl"~lޤ voFa0 ;W#̜Ƌg/Q 6Nx;1 & *e"n*@.5ci#c1/`0,A &+Zq[m0[N4bL@pn.`0@ˇHnVq[U^my̎Faή4waI `0!iUGf[;[]3Sǵ4b +\7D'U`0Ak?-(qԘ8QuhrNFa>Wf"TB0 8Rdp( zn'aQiZvKmr1x,D?nwn͢;p16Se&%`0Ft95@[]4H?:ӈ1p8l"hN`BruS5&ngN4ZR(Ei8U;p'4  ]zU9zЙuܶ43Uv0Ӿ5r  mzS\*!sjEFi,Vj且`0@ 9*0@)K{b<=^';m  :x Xqr9;mܳNA֙u &`9W*kUXp潍ԕyvv vܑ(%85K͚Yy< D#)J19z֙񸝛]c&  1麎SO c^Qs-mN`9m7\3G?m v]eN` Qi%F^GoWQj-N`%3NBl|U/2AۼΚTTK"Ӯb`t`0L$ˏ:ڼ?`E!/ q_'N;iDžlhUFf^ܸkZR3JvሕIaLj!Ъ(U7.SH&i"W[1mo91=nLmk{}|$2. 9}73d6#62ڷVĄ]nVy=S}56YU'qǽJ ;UsnLx/; 騆xPEgJN6-o/'*T;AIn\$:q$F9 ZD) @ ]0BkV+HCD s 6Yԋ[G +yMdC۱͔x{Y-" ?"J#tXQ{;m -iTuZJNߧNY$S@2|{.'=^;i0 ZwQlڼG K"ݫ-\KMm\iCgWa"D'Pw5vCjǗyed<"W4Hl$_\Nj1 "B_mACx}bYx5DTu#Kdr-\)-[pf F@cJ<M.Eܯ(KזIvJXwɉ2/XH%M%v8p̺;W(؟.U%^F&ʉ Ԓ}'  mWI/qϐuaݽԲ+(7 @g?X dMq7LfD &.Lk'ҚM Pdn&8Hm6[Or>[j2}F%V~oޠ]7S8u )ݩk>|~Pk&2sJ!usR I-D p f8P'ɊT4rtN=UXc!z)2U$1;[쪆k)]J bV<˵sS@[uӇAKF82)J^0 FGCVvlsr7`U51sY=(d;'/1@gϨ} s-?NG+2OG|: [YcJx՝܆j<n!n#Ts$s0Ûy3b$"Ϛ,Lr:^j_#t񿿫wxi{mwfA f?.p0?.B"hi0`* b!)Fqi_t].H%g4;mL1p7 Y Ue.o_[ï^H JgSv?R*۪?.4/GǺ\;~zfB D?r0-+gM&䙖LeI7ǵΪk7¾`Ҫ!GJߕMYi+:vZ$.W,P %~3| _B6;vich,"7[ɫF41t5 IK0Oա;m ̼iG[uc;xor0N_|ٗӔOȀXbsf=L餄WMъJ<"npQ/]<}UFqc"Jg9}mLCQߔvvJ0wVφTšΰeRH9v4DY봜"1edX ^ε; &$Y1(m%y9tW@_YNzPNC/[鸩(qSbPe@Q7%se΀^[*t2"|b#c ZF=g{vw޷,w{漽Lڢr1ﯫ}gXUS WŠ6gUa[ s }yMN o-&NϺi݌ O;YkBI uUN&;cO8j ٠s .pjØ |Nl4<\d{vڑ=x^OL*&Æ5wj5f(fk%9Q{8.FO 9 cYg g4oX(?(W~ILE&J5c`i{  焾Ӷ}HO ;o, aʢ;9tύ+6vءIc]֨L8ppP{?t6?ahp#ZJsc\~oky=2~a"I ɐ^&WW72} h"1 ]k+i9mA eܮjb[pډ % 1&UGkM:(oju>im11< :>mjk-}A핰1NZ {4މzƁ;MًuX=2 I~~רONLxڦ-nk~5u+elFw{d7I綐vL~zH9lį7I M&ҾoƤI̧f!}`V2 ;͸ KUM.,_fS7!';e``"$_Q0 {lh ?B v5T/)֕zO II$ vݺY]9!L BS"-ApXێ`2xllVcR! h:vO_8j>ug`8K&G纕¤y}&|8^?_}lҶp-SPǣmzx AD9mTK(%rn;{7pwlh8ymݜTL=M1>b׻3~_qZoVg({uT:׶JwfVE'yahQ|9/B6齗8Vdhѐ4E_& X^xjTgj35^ !0 bėwP_8!NCaÄNkt_cL$H% Whި"B۫<3c+͝fx {ڳq,zkED/6\0r2QJEi9/:wLl Fw~P<{Oq͚)<:%0>?{w'ULl'K7!H) GHX(i!tJt)v7-;=Λ=﹓]3{ω_X~9ƪ0%s̫/h7S58˷ @T6:=&Sol!V0GM-S-'KEhYؖ.C`!6!Aw72up#̉uBE")J.(S9ZbՂT ;0woC +mZԔ@F2hZ] l"5^ȅsj\C˚ݞU* u*S})tj(2!޷-x.1],[_r%gC6yyݯ ~jmR{ZzyX{(Ҿx1"^KdwYIl`v(M/gEp^!m۲6 5[rJ_ƇßO~b>؄+)GlKmExD𮎶QCjy3_~R{85^fS׿MEo')uxU(N;[&h\t4h1ep%'|lu'6x׾&pY9z]ł!M[{\ʅ>,jË$)l7T*)9v큦 h7 &{v:/vǬR ;ōg1XgR Ck" fQ@ּs?CAd~_0!}GhkܜweteF8O횺?z)v6,i518:ͧU7GiGU<<=)H…7:NQRWaזk0 \|Z5E`+8hVc ݀ Gߨ1`#OaڼW 8QOfÕBůC #!%S]gXgcBAj99_a˒`J@x/_s]SԶFMqPojCz'5T<!ߠvf| IDATwM[xж+`ϯn!Mמch)%Y@m#/4n=uZG.7|j H$8۝e<{ɰ`rk gtWS|{G2>n#lݾ#8,]u7LКx-|.u1r!/N||,xo]@ȱ̔[z3G S4}}D^7?mmIf'C&vR6#W0uм>SNAsG4i<](x:/TN#J㥌G|K )_Pqdu9?  I4E/Np{vabpcז,[NP/"1mոgmIЦ!bGmbܜ8M<7fÿmϤ$bdk}Ўg=;bS˲ddt[[832+i#@y9[p1_cu72ѡ (?[0)F3 Ո3.9 ɀ }``{>'IF;3ȢHML|cfTvS6mAp^"k5zmtz퍟/bg΃R9Ocp?Q9' Lwm u~o`*i|_*WKHFݿȁ[5kpYזx_әm̊NETs7裡%*(0ogqM\g;7LaGR~ʁ{{Vt4Yd.ߪi#x}˯Y;' px[a=c@}%Yy&3?vm%cRZmc/̭.ݡ^[_/OVC{9m8 x>"tvv<"h9A:C$4Vss͔.g oӼ|d ݘ/XĉQYLi.`s5=?dql\;Z?4ݳM*5ǀ,p-&"*U:io˚ Mo-V,o/?O|T&-#|LhR?o꓌]U{\_hG n.7vd ĩ ,2f{'p?+\lRFOf4QeadUARN2ǙM'h7in¬+8Y1=A懒T>zI&αx&ڼ8_a!%x{@ {A`[ĠEº>oއ3?#awiaGyҪ_?jer͚\泌P>'8#F6D[\bjN/ܵkT?‰s>WB7ag*==#v;: իT^k{[o/`G@&2ˊo.QZQwVaFJC*nqx̓(o(5Q|8])@x:~mYod fSlYdNj3#S6"Ba۷_}™S.ݵŀ /S Wv̝2:iiW>qזQ^pC:y\25d1G`J˿O ~:ZcUGzn&Auxusӷ\wÄD"f-=t$ssւd-yzUO&ʛ%vq.Zd󵾨 H`OϾ dlq\~eZWս\97OP5EYKރCLvhjgvR!-z[wm-`o2v;džf x<$| gK 6m"WbӜ fȨ} [i=:2.5i&ܸsQҨU{h|W7CmSqZ?גba0-ҙFrP^AKC߂';X=nc!n=22nrvAJ}u*3|]xv3hu7X^K&lʲDAavz0B,\ŶLCDyvjV _-]Qf=EnPUlg#XRW_W+DkxMyoۯDJN+b)?Ϩ^1ePlLג=j ~l]^fk{v K*o ~AVO^uWd;;iH}.DsXM7\R"hwrtո55LWzv/E]Ժ%09u4%)]Y}xfk:nhu'|6@ֻ4c]c_baAVc9 \tƖ>_u7,"%597\8װsptVhVju:f4ٺTP((<ϜgncJt/B\d;;DjjvQSއ+Jm SA3y]VgZ._2t|>ztlGwuxS}d碲:rf*nݸ UOk搔]۷1@$xv!OEU~e7(EJ* s3UC=h́u / 𗝻M܌ c &X &+Z2{YM&v% ߗ7{C& eHP6O1j)S|5l߽1δhfD#5wlZ?e${WN8bG洦LJ`gVmӖ_6YW|Q\f' \A6-5rr /[њ'zVMn_FJU틉hNw9gaש!yg ZpǦ)xb|Ck" m6CW>}`BINũ!CMwFqFrbc'A2;Hy;י*uPƏmG!wjӭkJo"|*Pz^l~gd[6ѱ+pp' ^t~8[U b:ARz]/4y/aFQưt)O}#26p2(5 ovE" Lwgv"[.p!DP$s_XMV|m J+ReٸQ՜vb~lA0E|h-v 1~|r! J+ߢH w\Oт tﰺKjgiL枲iflT9h٩<9B)ɹz@lh#Yګt@'ob5WOBg(!ƟaR(Į @.oBմu҅Fou?߾N֒oõ5o675:TI#9L}vXF jvO$B &u?BǪ=VB#YB< :'JNgnrN`5 \yWJimm):{6dnd0O D[B^}B?yrt)'M5-^GGt_s -#9<b E\ۼdMnze[AdnW  3It{tt\!>mnڦkkȫa6ks~ ytQڽ6\ \!ga6N9\!D߉oț+*]paf:h-> o{:/+~ R/MI*9?X]k\!Gʠ!!<8Sr;3uJ](3B/7o@q/?`Fޣm_@((39mOy.nAV-x؁l۝mFFѕ:}@k2h#OS aFyU2> 5 !*2*("pA!T>Ay ,RDrV)W=J]V)i*mKZӪ\!{JqHZܓej9F}8}@^B^eJc}4JgA< O+s|Pv4 !y9tw[7uoKtXwyCmҖ"/M$P3aRr 9u\?FQ6 fzj&Bg_L$*|!h.жd\y/4mo+d3!kuv?- w$?z|x@.zCq*`)32T2B!_җ[w-k?]5Hx`D-^8Fp8`'k,yFgB4@ F*^޺q(`+p !ĮtPߩq:2ˡ=LF Pi2Da@5,v쥗BY>켗lWE6 ɚ~l0H‡"cTD 9盿Yj$-\u&^VJ_is~ j6~;uBRB%AXeK.!#=*u@N|~UuZsD["hrRfFy \c t C:jmU1upDŀ}?S7l4Bx@!sLBRmUw/}6.j]aF&E["fەd gsehbMM_W**XىB\ƑD5j6'A{̓q :-Е0 B3ە0/%i QDh91tʢab"Wil};MK/~%\,#Q ؑrv!׫$dZx I #dn=1bـWvA5BS3XǪݚek [<иuf6X'D|oBh-fUN ĥ^l~kl L9B6ma$Ořx5)ܳ6UDduf,]Sww!Jfb&`M[FvΏP?bـDkLc~B"EH r$ˈ~2*[o08z\}B+dUl궮H9lr1\އՀBu $˒FgS*J9 Vaj@"Dp) b5|{Z8UbԏE!Ğ"8g 5q%{eB!"J @Mi!׉R9bM$Kșe !h#mZz) |%v\3 IqBާ};]۵zp/ z6G!zgl1Kk=8lB"COmڣ,]rTG.+U: ,Bu=+ ITþ}ՠQM)CHx=C!8Ks ~BVƑX B/swp!SaGI\j !/\ȇl-S.e]^2@ze/ [ sBޤM:SSP>H{z%@1nB!wrȬB-7\!TPdZC\Kbq4L#0%BRehYow̻s0rIv6T!P-Nz!ƑSLL]s:B91Rv?ygVE4 #, !mjL>Jg`'ɇbԅB[Q~bbiUXo9$`w\u!ꅊ\}eC^: ̑0@# 1Kca6G!0\{#7V NUhQ0i By R3%՗gkznn3 pBI㏲M?Ƒ] 4mgP`3(>e/u}RCZ7Roi:G!<(;!GWSoG6lS d !<ST( h(zȦEgBG0?bҗO%#p6B DnGe>Lz* !ZEegk!X/\ B>^F!OE>Ir\-;|0y #\ ءRBuPektybfƑYBȇ ȵl!Lc6䵨U N lq\&&hF4}Kqd J!™7l|Z6IrNa_wi 1DrB4z'gkdC4 0;KǀB>V !V hYo6BeOg$mHqa1`#Pi''J IDATv_CaG)q!Ć^;~ :)׽,uܪvJ&\:ۼ)q*ejF* k)]ۯ#Kg#l6_BSӵϧ żGTNP=a;J\,`3!ܣUNĪ=\CgӋX!"7QSއJTË$ !:>J)Kh^JW P9A)l r8%Bލ,EƒL:N]ȼSqN#L/`ejvR逍S!ڕѧteQGoWF JG8%B>A*ATɹt캟^ 1!8%BG3N,8gB1ږ1JSĴڅ-; #l6Bk2!ZqBo0]Ⱥ35*'qNlB z\~B!I`C: Uҵ8l !Jh%qg_LTw+/I$>Ƨv n)!f_'L^6GȖJU>G 7C/UUG/#4T|1 oaF0~-pyWvs@r@ecNCZOGO^SޛI\z3M#![oQr?"Tj >3iHK%=,٤=]A>N`pJg_癟ߥ-䅌A>VVOgq#Ek?Y'G$y?wٱy8; śV 'aFp.OQ<:XSf A]ʙO| VȀ ZxТ& i no_nu,+TVqJ]ݙ32?nv; ~2wՃ|*f35=㻹r\wSÂ>7{S::mZcWszh"3y Gw`st:Zgr"0FP `tWYz12ۯtOn겛8rL(+{CcL7[;4r gܻ1Ǧ0 WRAXt!Vh}*IGdE ꍿ|a|>{.8L#{/Jn0~H as+{m`]aL/әHj<S#G*QMU{2N&G}5Y7loj_bM퍴5EbD͐@_ KVDH`'{㥖r5t;dwdAS{@yNerhmw6#҄ i+1GWh^d=§V{ԄSn ,2 sIz&P'm),ez"T]@FՎ Jg"Bnja&}*u ȵQ/Wf4m LU;&*SWT~st';v5eEiJs" u$aqlīXlw\\"1zawT~RqgIۡ zhqj/\/ӥ\cky@жv2]Mn[E6&@{ꖓ;XXVvKk"ľ>w &Y6@&3;2CAÅݙ \uQ (5cr Z%rb~lʲwsk',x5IBrQ7I3&Ȩtg/}؁Gn/)_e-6BwPBߧ󾵺zΦ4b4{ڔcεSHUFW=Ե4`6Bv!Ȯy "d؎,`7:iVe?bt]F1v,Wt˞VdOETs&{Z#; UJcDe\jww07oVǀ ֬:0ܿZ(mCm!Kx \"P!:+E2Ci_N OJ[r8L\>lvB w0jU{d[%+51>vΚٴ=IB LJm" @^8Zv^L'0)*e2/!T T> ~@rV4] rlJS8&OQ$)uewAG:0`#dc` J<mpt \h*cZ dydrQwb áˮxHYwP_-O8c0`#6M8ScMPcRƆ5q1`#dAP?1L~%!d|%ͩFPf/XtY9΍-KZ \.kgy|9(`e@L⍇$©9UܾԵg:_2Qa:a߂H/Ll8i$rٕ+6rt YJ$*OeXr E 3gW쬸Lzu7|N7$hq*(6j\H`;!veN'tc ')BM/bۜSKk"5˵k㷟hzLGu0`#jƅgfm 站Bg𪀭i YQ2R RD2Oz3] ?-kI`1h[Ǒ`9+%*a,12B&bUˈཾм]##Th%sU0ʵTdv,e ̩)gG$j*nVAũNk6 >~)^yh(+sW2cJǯx1h/bvzh8/>5k:8YG_ 9PS|/i qT@f} ՍH:)"7M`BOx4DԶM.ɵuBQUǮ izxCBƇL;h<8sە1am*ھkK6R»nj_6rY^dvEZ6ΌV/RNY ZNmyx =:V%M@A {[n-`Ѿ=HHq`aOxCUxƚ_gl9(V:zQr"y<W. ȚJu*ncsGwfj7_NY|.Dw_4-~ *EB3X~@^^.&e2M2ldIX X'-Dă_HD}s'K>_({!eAZ7P!iA&Hϒ%צ$iLp6SOlX jgR ]@v*/so&Y|n•/(`dwk[{v"Ħcd3^ ,8=?-2-~KW!=`1J8uӵgָZU !{ĚkLʧaws   NT]p;.HpSݿ0]5L@+޶W9۱k}[əQ&ĕ8(ޅ*'JG6 2 3n /z‚dMʔQ{W;)Y-cǚ}Nkˎw-vѩKω೰)p1cNvOl\!-RlI},)唈fpl;bh6 hg~NnKo 9^կ IM(n¾̚%#[Eep$m@r3 0`#d/V0BG9xw4ϝCqpvBґ]du+wdX ( `KΗEAd=0A[tTfw€ѨrU ns7#۾j <Ϊ]gr`F /dҥ]Lv>=fxrlq*~>"r,@cᯏ#!^j6=~u}Q@[vV*qn 2 ؿ+$&8V6\"҂۴ ~4ny߻όgrc/^sNw4XGܲڜƿO7z6z~sӂ5 \wىj簝뜦4@-ދt^\{;%8x) `c!EQG|.`{X WD[W.P5VKq%ásπVTB5{W)AӺ#6ށI-J9;uuh ~Ȍm܋ ԡۨ"[d;3 WY_7̂EoWE鲌~ni JΩMB_ 2&9GX_jW>}%' ד]19):XsG$.AZl,Gun(ͯ^6Ҿ" )Pb:-rg;'97/Hk,D9Sp?~?<ږ,`<?s~2,4r ;i D>7mJgAZq~tZɧO\|1}rS[arH)UEƾg&\;2Mg,x[^mTǀFd}>Q. xʾe6Y.tW\!xZsWFK螊QڑzRqI*Xk+]頲\.B @^]Mn|3ļuaPѯ$pܭhk6'd?WRX_ mj܈_7TsW899^?P^A2#Opޕv_cW+ȞvVÀ  IDATJU tԵЎ~v,rs1@[ 8m m'%a^kRSQTcVwl u%cXh.+dR&s$@F*Yi 68mb~싕녊~fAe6F[ƸvFi4 -^ ~jaIJdvnK{3slC]>E#٫սiJSJY{By6EnF"[׿@CE?n`%`w,bℑr5 #M(ɼ͇alzqɃvuO/W: ,٠9ߎ:ǯJpWe;Wd,l9,ؕp Ȓso%oe'zf|}&+:j;OqB\HHC4 Z×%sG83 li^ԩj&7xs:Z]4XG*gX$aDSBFJmdazVӀhG$V%aeY&μ\=fl)`ᴚGa]jM(;&@͂;w19uTai&YfK q[VwY/*.rUl n{49wu}˦.dr: Ae ddq(^>_Pb"d|j Xl 6oK:hك E rnw~+pH{Pxci]sG}P[/V̜ӗ>wͺr~ ; e]KѼDkhO:ZB7>Q i#ĕnC&Z2զv̶#lہ.@n{ֺݙ%( ή|=Vi92JjOmMGwI?YSxZ.*Jο%\, opYbak ÖqEstp"[a8|~bqR;?|}cq$nmv]OoE_)̗S3Nh nלJ>Mʡ ,D)4U֩(bF$ K3?,ȊM\,в̂*L&jƀg-`]M->Y^8fwz^ݹ 'VJdQg-2&5EqBX7b ,M#=|[pHt8{靳áx'y- !OΆkGYOt(!?ٓU"utq$t)s@;wĥݾ,IaJ-zxu hT6s?ȏϒӗd2#ְ Ώ kSfխ=;:֎5-x&22 nkd}쪊Y΅S+LϚ|A&$Y&&nw6a?CbG;tv by@"λK*S~N ۑmg[Lx;d5]IG b XZ2YiuZ8T-c##3Ѝ@X'e,W{/h ><1?Vmjdu'u80~J<(sݿX\G[Ւ|ͤ=?J?F}nT氅ӸITEVԓ3܍Tmt He/ipSz[>l`7'p)tֳ̿KYi !W:vmE-Bk>!Npm~&Ҁ,2X/8鋽<-X_L7N]v/%_%Ҷ]zbF!=#f/Y'+x(h7E`Hے~w`FYpr_fk93 'IH̡3'\I!8FR |Ok1;, Bi-1E];/u >ܗKx$h[eGೝ`JB3vmJ/6f?Sqקx)iZphO7 {~Z{b̹{7]٦:7~ {ͤ8XNs^˘#I= ܋2[2R42HAB9_gŸҝoE[:x.^YѿLkBR&/`y\08mwDˎ1`#cEh A*2vbDezb`Y]]jt`rj_A^vѮ ?*BFnq牆9KAbM*.G#s[B2Mghz0ݍ5彩5^ =a[g7|z +sraɌX &r 5Q" ~iɬS9e*T ̞\6KHQJå#l ygM&}7JRnzӷ`xIБj -I4x+]6 Iw4^\vj/[ajY䙦/Xn~yuTCr~sR]rt@0Q6x-[`ZEMv;YS? 7zerqz'Jzko_ZX*qGߨ0"e"im@ ݻj0/ignمuIҗȚWUBPr5|q%Fu+}r_HSRaט(U\]>J)q^/ß?zjި>,sֿQ9a=jk 6jV/-w%8)r{d=/­uۮ&UԏGJ|M u럿j Zwv~65n݁_Ka_5l1;2< X5Tld={R.aط ~O5jڨ XJ֒ä9{\&!Ǫ-el[d,WqSDiH5C^9@xTz{P^ y'f"eMd>Leno/(If,\ׯ+lM?3gH6}}1`=]roپ {hz:VuSi`T`LβCϾGdltl$(m+pr.Q=H.9d3쯚 .\d$6n#q^svqtu//IadY;7h?vkڮGl}G9dc6 oApx[aJ]21Ҭ].oRY?ٓov3@vr">mxVx dpΥe%4L0 'G 9zviO`]0_&t:=|pNcj;JL]/,, <0wky}"z\=fTF%䍰\:!+RM?q +7noByYeᵅ-54+Rc*nJ}d;b9N-7]d6zYM}Z?|, #,;^l\޵e=b۲5BM!,$/I2BMd1'CzRhPY k̄5k X6S“,sM}G׍jИsx{FcEy* #mxT&m;z}P.ǔaP32hդ z8u] Ъ9t22'Z\M\r6)O.j+V;33M,B\́ -<|99<ɣD-|/%Ƞ•?92`7:h(d.XF)lO?L*)?㓥p-[& YiOL<9Pݽ3W70j=tǍm~{&(n6 muˏ]tgœ9/X괩gP4w7+r^'cF6 G͋΃bDZߙta$/R-N~_J>dXǛ}-uQ[Xٸ_OO=3ū-;v҇_|Mg}vW͸Odv+$)iwf4[Cu"od`#:u;&U'oYv}=?/zu^fݫ[؝uSMw;^yƏNsLP3b`L߇O2{BRrwCAȮd]%yp"铦Î'߿:i5oV˫_NҼ*e+i픛Eӏ;莇oW|_m;wӰA̽qt"%Wo{HƬ)f\S;wϹ)$3+;.P0R57~ʮ 0ЦCL4uݳ;E"B1>/u֓f+mvj_L^|<8oj|ϻX֨h }폤j駏㻌d{Lqaw޺1\[J> fƣ4mlٜnU09Kn Yk !;a瘸f҂_KOSJ5kli1gIeɿ,Y~nbRSЙ3I\2+Ncc؉+o7YALB#2TvIf:’bj_|${˰vCo(53jnKӵ^mImGݾlk 8隋@Il6 8ܗfӴ{Gdvͷw]x"uH]LBXbÎUltKY'vѵ7qf Þy-/MyIk^}N*G7L~8w;Gy!b1wD~$90ęTUng#]wE2W z6Z2(8'Lmw\ ܩ7vPλ~8Ϝy^@&VU\JߓTnB _`DҘx)&waK('Ϥ/~EsΚy䮛[-zLw0Jlb's&qC:M4&jaL=MMMn[s)+r% 岉 :TP,A TLuء`_v5xpdնFll6;DLs{M˳ה/eeGݩ|],=p"It7U$agbÎGl|2)_t(_ޤ"JJW$i~^i#2lnRfמW:|"4n0zʅƊ:JKIn|.mRIDuMwBIfϟd[G/ maHfޝ*rL^4]CDﰍa5.:>6KV'˽3a,=oa 7)֤s 8'^ݕE{M;Hsp~I/MqjvQɰ"uxų+l3g/߽"I_ZM>h,>\t▨<eg8ĊJ!n7I`S4/cRfm; OVduΧE#wnm㎞5yg2D};W )͐::[=~<ÀvJ˔zKO [r#P v?2eLzJzvųΌaURcݸM6mi7͛+Uh1-S4]*Ɂ]]8q("mgpI so^zA_Wč2%7A=} 㯾mZZj } R>~Υ Evahٴu;قeMPߞi[D_Z.nh֬69kkwa>^?i"]Q]2m׽.˹ZV%i涸K=tGc궶׫؂_VNY I e4ERi?QcT;RIh6'_93%^q$eÎ?s6}[Z^A{eB Ӏe?' H}L3$90DE\u}v56}=ަr2i4{} R]S׆|z,x)tзv WkY@.J_Y&l˒YW~>OFMw=2+n7=f?9ȤSοĬ&ٗy:>-V=칹gxt?fs[ko;5H[7 eY@,,E7z9b\/Þ] P73řӚ%?ƛ{('[_|Jg-n2fiպ mo#M5vss@^d6i5a}yO2-7M.WT6lپ3ĊKo_7H UA:lH:l3t'~l6:W͜şkև}E3M߼]ofo (Eo0ֳR`_ gw̍;`vD'.nAM>VR ?Јq8[N$+9mJ Mr.YX$Aun'uYFuxn<9psQMKY_52R@T5;g Ʊ{HRlȱ@}H~_%*HV9MYEw-P-9iK4[f6uHk'j WcШqv0v q^'v6qdDUqpƹ@6CNN/OH{ k[Vv[ IDATv2SlY휮IүO4ayYs$9R$y+sv24UzΌ[k}tLvn>+\:w-1 fƪtRWͲ"h=(CA91 z^B`ծQ.]ղ1*tv4MDe-gٝ$?$6V]1Z[XSLG'iVXu}~45+KduΧ-Ҹ⮇u>)ʁf7l8"XJxģ8rجov反q+chdV-qEz`6YM.#@*^w{emR/aD{;;9>Ю?."$SçK~H,=[}eM$EvvO@-vأtfc 0?dtUΞ9!Nvٔ}Jbͩ(j َ3F5 вJbHspH8#&L3gV !ns⮑c蒭4)a࢒}u t9'wռL8 Z 5-ʼnK~IVQT=G j"vj~ӯﺁlbsL b#JSR:(0^5I:V;9g[T.dނ!c%Wm*m&\u*אW uIBGBTQkI>umW&{Z8@v؃ҵ)0S WO8@ZmvӵΔ#8@-d͐)y-gqS}^{^y;:{h{}g'&Z#tkNKNYc#sZTyH\IĭQ:^e1[ӔdFEp ZU@hMW:WHcأu/(̑:h}3s dO!uاPjy1y/,vu J >")ߪUW:PV]w,-\ҥ ~*5-CsL:c:{F&eܴ}$brgmJ%)KVݼy^ 3'w3\t"tX]E; D1)Wش/qqi W?jamީ 14l{S:B-h*t ͛0%:lP:y63t.C@z~wE7ڕ  aJXU?QJ/\cyyS:_6qZS8‘҇.^t _pϏU:p$lm/O@6Q:p%dt }K w쉯)@͛)(kO?+5BXBpmڒ!^$\?)Ćmǝ$Y+JuRqmi⋫>MB;J\JcJQ[t{ $:l~A8pvݖ.qݸwp $:lW46~r9S8.nö@"RM,q' aKa8TF v_|B ;lnTvJq\)$\[kG$:lD 2lxx62l/찥%sMRa0 a Hu Oƭ5 my甎ƒ  ;l?R' a( 6āѧ{?p*^| p| p 7x xux~E >N2g;P`^^lN/ytPJKҪR^֒lzS'6Ǥ^A{ΚJ?=T<|n'ƶ~Z,H,Ꙣwl#A8)dw|ᅢz(%ϛzghRܞYXKիv+"tp%N1=68 ШCnG{A"@ y]>α dK}.p<_jx5}+pteNE^#Y:Ǣ1R j݁Ow4<~X כ5 c I]df R@=Xc_L8xYۮt\d,^v>.}xU6L;/qdW:.-QM\`wc<&@Y]95e،ƫFfi_Q:& QK7!D¶ c8?N/gԛ.c:5mS:.PNT:f8@<*w>~Syft7\5:[1d\4^Y듪^3j)ȣI2ϥl #\=uu-W~~uMR/Q:&VF zf}Z8gx ci+^\깒V0T*ՁG*:֠U|A%~F19+[5~7yo򞖞|sa~֏#qD^k-q[szo ?k|m1yE1@t/*Zȅ215>*R#38ɚ| #/|/Vn[͙Xu;kӵtpIQ:&h6ѕ.hMNE5 ͨQ8&z:.!wxnv_@|Ke][ֹ$ig>E[I2Ϝh ziɬER  6ÏtCev!s`ۺ CDư"i]%sO.m땎 Q-4`" IՈ NE)_W*6$mcsYUE'8+.${E+$kGrT D<ȉHl=y@]cJ6w,Esd[ˑCmeʽH8v҉S >BZԿ/KYѶRg 7d{=S?\tp)U:&hYIg}#&lݡ-cѪU[#k]+@}TJ{[w-K<::jN7IH(R )uzPдYst8H/q_s9#vƫHmШhx&l8(?M+߳jv;XW'yT5t,U̎dbb#q*9 lm +R6xO}q ˈ,#Ix:2"¶yӣ7iLkJБd׸No ]`9rr <,f#۬"]613 * @GR]d:Ql"9[]ro\V#_V tcBJKART?5>[S:mNv'( @ ).uHH#FEfT8'UCV5-+_ڻmϭwv$cg!&ْlIzV]FsS:xf /#I6t=S4٬eegT{[wk_`6@dL*$0U<~?VS]M ] dMT  rr(P;~*5omq~xnoJoBH*`$7}nҐޯiٳiШqGx8Ѩlm8>{{ rYcE&Ev(ha@1jT4`/о{|'SJk6$%9:ZR?ˊֽt,$s,zfb=6$.VEcY]V:}LJ+ZLf0]&.Dm458]ұĂc SȄ$:Rҡ-}̮~iϕ@i-&[k}FhTVOfg3;Eұ()h]T= EhdVOF716$IZT9@3R  ?(f#65ڐ&rNgzd.$C3u"~mFd}MX4a%gl ihhGۜql9cS$ *$Z%$ڃ3t2~Ow4< ab'J fIv?H0 lGӵʫt(![qo7N?t,rhd: Bɬ!oFߥt,Rkd׺r 9@rD6VOcN6}rWÍJ IG$֩1 <ˁD{SM$ڿsiFaұHY]'H6F"&*F/ct,R2]G '؈6O6U{=ƾBÒl?Oz7%SHڋCSTWm?nKˑCx$ȕF cDIY:<~m'ޕ&T K>[$-eh{エyrw7hÓl//TqlcK2iY[PBo8li,R!vxc&1x٘lJ*EFԘ uN:ZUJ_pɎ6TNDc"ujbmX6U_KxO{IhI[w{$[ Ė\ /_DžxϮ/ LpԠtL$٘.ȌP+t8agmY󄮆ۧP:HLʌdc8@LR #tT8ѷ{\},cF4ΔzLi9f5eմCoj,?.`^ 1>TlǪ藍z?mQboĝlҴ c)|J[5ٲ$/Ij1qC *hcw-Kr(0S:%FugH\=StTn6%7fhIM\]N@| ѪU4ʝңt8[R꾜5GȾ&wEqQUIU%%%'OYğ:;u|J I GבݱVF1:舲LM7к*/:F+,g0^i-br;PYn]KsZ<.',6`Pcp@k1Ь>dPGoO2::{KH?,s@*ROF,Xj,iQQ:XVU9kMRSu/Z`*o~C#;4եEkJ\N;_S>CetN&]6ޣ+UݨvyIYkԅdzx1]z|fzx'5z$>:WO5?|^nN5p/km6(SXY=@ zi2֦w3^?q9-b-٘.ަkQ]eat(kYykNtȁ46'_| IDATߕ}I-uRymh7UB>ΙlZj` SS{B]ߵBbJ֎Y5@Vׯg_ֽtj*\J%7xe"oq,XY9JlLo +)/IYdI&@8!ɽo!ԇ%m|4;.fE\bk1&Ҷ,Y; qzc.zLH:l/6io6_M-/e͠VNa;(]1IǓ v|ъ2EV|]JIL;>}kcDM>CH(n(9=[PFW Cg&N{:fs5ӻ8pbS-3J%'C}^Gm;ɍ:d'Hs I6Km[ΆXSTY׎7eթw-?ob뉙˔5boX q2vmw TW]t jP:s;g=ӸN)coϗ9QU bk4fg}Z/ߒL!6Scg |ӫU4&V$mC|֕59V-.\yjwÕJђI[$S Nݯ]3/Ma}vf~X޷kưbjdedSf.!6M.?dRiz{9KJXk?@qj$p/1jNE 3uvjg|J0b79l^oM~:26ܢSSW u*;6Ξol{_`U\F_@g]#IrG;TeKZ:JgN2IO7\C%V0p5 bk4PGk ~JSqѣB\vj @"aQXkZ*,W0ֿ}dlM:[O Ѵyt4&'#ϕ͟R8r 7ePr/a;?S#V0?iԭLCưbj%&.gWW[?a_Yʬ3/y$ +G7uqx86y:)kg$Mb[a뢣{6QQ}VlLNΙbvZYϊ]եVLm=[i]}\QvG;_"ԇn CrLB"+uí}\'6Ƥ茉&:wR-S[(FWze~cW!ILE0CL}~˝*:XM}Z^Nw@ѪUtH#we/:a{u@56d@Te gL%.Ӈ.RmK7|-eN1ғ-9bc9k.$J~]fiU~j,H j8.ػN~eǚj .󑽁'٬DW +bcdi葋RG [D\`3TH^+S;||7k+]ŖsSWFY6$chv–_WzTD{@u*Ô|-љVPU-V{ϰ6_mUkhēע/ioC(#*;5#L/^@g/;:ΤJ:.ꮫ\m]$팞ZL]^rډfsuTBF ɢ߭ϗ:e u==}yZ&ҨtZQ[@TJ[A;63Cc3RVSm)3 P4 )+v9u:ߞ+$$72;}oQ-Y jL4҈$[l> Ҵ??((5?_MzQ PvUGujJQ73hekȉl(q g^l]}~p@fFѤEKjREaxTk^Z#4/顊:L/Ӄ/8)HN?0 eUhDlQO^Vb]t5Qh~1r$$3H]H ;,F Z@ֽټ(BQ]}ر~Q;CQ;+8W7gǼ6gZL<ͦϰ%=^$–y}ӥo 3Ǜ(wZQ>_)- }$:y˩Cbԩ &1"rTSJޤOJS:o0 BymlhbzZ%dЅSmnկ-.^L̆EK&^POȉ$Ɏ3 Q3ؘtZϪGz:\gR.[_>Q-~FzIv$GcQQӓN_ ؖ 1qIvί1QnJ^ &禮lW ɎjvoP: Tg33rɒUhgҨ7KXX[]ؘӬbAEJZ<椧JGSNjkSN*  K`6ilV]{. &+?ۨ3Y Gl>Cd@nmsgh+/% ljԢ%OUM E{Iv;5TmNy}4PJ[?Ak8kx۾S)fϹrG:^FY]RXqO8nE%'N>R"v%dJ)yb$ԐF^C $ƈmPFR[#uт$^7Jv)@p۪i0<9Ƨjn eӝ?;y#TɚlMfKY!3r5̼4ˈ.wU'RYM{5B cLj3~c;)k:㾨Iv;)bd'ii"wGA⛋bk+uhaoԒ^'nH?}U>q_99z̈h[Rc<҄ih`F*Q *Xu3RVGCmPJGYG|*3͘(Iv|.Д nc[|ŏN  ;^qDS%\"o })T%;"..W=u~ uny8X_iXR'͛^U܅K ouUtcBZӐK?{SPBZklMx[:F*K Ɏ1(O<{ߠϗuAm3&=>]IjFثgIdeh_Ѐn^IC?=M3UAwӫ|G:wݲxUĿ}bU"jRIS;h!qdGH}yKղ/^Zֽ%%Eg ܼ҆A_{ǎ37_% l-6@wQսsR"z$G#|K*)+~2*[)ENfwD-ZL`ضWThoruݱmfh'xm`O["ɎaV,/&@ΛE:JyJ鿗Qtlzu7:ٯ fFaN1d&sd5qd 1@wQܚ:'O׼PMvк^H::\Ng~ٌ+-^aXzMhGgW{O:ڲ-*1^,1Mԉ~?5y-ַ~mi^+mt|7T^*W ,£\dǨtCo + \TXvxS؟+YU(~O>R!NMHOӾfc^mol4D3#4%i4U-PYK\BH[GW>WM嵇\=*mo/\N'Y׉:貓OOhVzceFG,~fTHnMMLC!?GkuHcTffП6ߠt(18tDpfuN G- GHŦPš)ީی&d1ыNE,6?5./z:~tQwҊ'sOK7iQ~o IDATo ;ih\?=N*~^ ?I-i]eX9oV2)?^9\by͍#u5TMiq5Qh;3gd*$1lҤ4tZ/$U.ޔVѣPP:k +h^zY]x\d8ܖϖ:ֿ{I*DqJ5K6M*o˳_BߓmiaU?&׉#3lsB{Iv$=*R *e;h'{#>FƙwE%AٍWAx[09 Ɏqlc($)94$)x"O=}T 夆f, ok {QҚ$ݖ\Vr7Cz qՠZI@?胿}Fpͪ)% ڼWKQ#2FU-GyΚKo&ҕkQkIS$S<Iӥ{W=wJO4@,\䒬#J@ƛmʩinPl އ.:>FGߝJS"εcE Gi v%8#2D}=Ŗ0 jȠJehSf"miONu+&f}d39V"#Q Ä>#t@ *M11H} yFkf4w/SDIvbETB +oUs[SJ3 Mj'yW/L=]]^<.1LIi,#/(&. w_J}oprk{yaAkcqmiFhU D4kאd{*UPVK'O:%˩e:Q\+lDZ=hÙ& ӫTSj ={ݟ+<^8`R5}~Z7yK9Nw97f&<1[Clhwn5*"p!~(lLP R_l o[ÇKؼLa/byȋ9+%e\QjŕVHjoggY$ql{q-ޚot.~x%uʨ )f^eTIvɐ j]m|CrDk2}5q|D"1Xm*J*I1Ys@&Ei}ɭ"Ԗ39'!*Mc/~$B"nQl&1JB+GF;sÒK6vlϭE,?\pOs슞^SY)t-] I6$<_NHlW0O?ZiMOOҫB`/F.yB i%*ڼ}y?uP o R O";O/"bbyU-1y|QnQ딠3kBf&s.\Ujy^:%?M3 ShycT 7¢l6Ϯ(.J(OB2[K\0RNec\bI5X]Tv!կD?_k tK|'(zZi~95n#ϗubedhlzѧ ibł;˷[s|>ꏿt]Ž+WP|Md>PS9Q_nKi]B\VL> ɞ} ht/NeR>&@vК@]  xak/1pۋ):*)½\9Ž@{\X=FZİs4jJԉx֏# '`ջ:Ѫ?YUD?hL\t2T-ZпH*Ŕ?*u#"܇Tnw ѼnGe©<_ Z72©<}^qیo?eU\a[)oa[ޱiJ]gmP24I/rX[ O-$^^b6+t<|z'F5)Φy-XO^V--!;7&r>^7:m2iuKl]&lOgب2P! GɩR ]* DP:-JNuh:MVǮ_QMӺ[<]0w΅qs 9;ׂg-XG2yfdRp t8 }3XTi+*XL xΚ\3PA5nT@5͂EhMXZIH~bJF(E}x.W{d~R>j/?KWZSH\P.l\mUvDrszgWmg,ƍ;b+X Jw_uM0>F~Jxz`}Ylg mE}>#\;~\`^BfZK{;ߎHss1%}B`nMc͟)J#Ϯ$e_VG.l[>ok/$W*wZGl vCN=Ogz6ogUii2,qh3oTVyGtk_+A-笎/w~'GN֨$\f>p*o53$a&&_Z٢7hͰl\ji"˿@f:kiCitַXk /G9,7R(Eب.[O;Ӷە"dkl6wdî YVoVΝFQYGTӮQٯşX,G$[ŵrf2S0RFߍ/%ږ=v&VٻHsftk+3,s>aT+KcCܺo<$ QCAk?cT%݆vսTz$Ҵ:=.,TJ2Fw k™t b Զz4anErկ;{Ho3_b[ԯi@k@@ncӲ?^WR 'l2f<ٌʐ4cCctSpԎg?sρO6}ūhG)M; ]?k*u^#*Ĉ<@O.ne-*E87 8]ܵUm`XZ2y4=߻т)jrYn FNxnT֛}5ۦu sGl)-й0c~NِHMNJEHihlo̢T 'y6׬M۠S 8CTRG35WZzuXJJ)֛xAZxyR~D !I|-\-<~*(Z͞B*eulТh*9;#hJ k%4}0on%W.ϭ$Q|)W,16*EFɧmeztN hH{'$>?x;),tln\* P+FzBXIȬJ=^*5Hrt ɖRj*Q-.fпhՠ1Сx{:q#nYngU7C4pY<=m}H6LBpn1IZhI>L3-αhɳy7gc=BpX }c%H6OU jOCzoK$LCEʹ)0 k%A4M\H򽵵 ;:MWso'WKl'_Q=ϕFnb}7:0OL.t_6:v}j~}ѡ}t̓܇\j_Th쾪LK2SEs^V(6lH%gl |cxw1R?Ueρ'h\zpW~2-ZwLJU˨e(t,dM,Zku!CxFStHXǦ>x(tHϑZc(4$ҴZzv%w9xoՑ&L r;+fX56Pŏ x9B%}1$lbjg|5@7p۷rk|BdnJ65gZB~*)I&'DL\0OsbP8U+n{kUzH\ݟqóc0\?z& 5n&`gz IDATtZoWҡ-h|{R|<)N[(@4k$;o5xgc2iey95T}{R,E7|K~CRٯxQ?xO}GOqe_.k79S),$8 +hhm^56(}&{^lu=vɣ^ŏ軏"HQEmFȻvDD=ӿWZf=QӦ{lxTsKB f 3f$۳DO֤3G Gڧ;iVҬOXRn'T('(0Y桽ч&Й xhV|DwPCYZbE>Ԧ46 NW-.Q\x3{(5ʡ1>-~ȻgSe[F"!R.i<= Vt^,1RRx juS9&x$=-ΥU/td$ylZoI35/Mh՗zMO4qg߃zvg v&ڰnmZf7G~48XT<-M?.&c"(@H&Q?+ٯIg+O4ux' *!ㄭwASlx݄C:3O DbX1f~QcȍlBۜɇZӋO"Qj̑Bʕ*}*+MwC41rJ8" d$\,-:faI ?D|#WP'BW~1/hߩGwqk[O{`}tVLB e4ltQM6:MQĉ'V$ɣ TH  @n=Z_Ա|=qM_ uLd?}({0ef˰us!*-O~r 6v4cdm#W+]IJ쯶r|⟒~E5DD@>N拣FB9kLt}8r<]yǨE-/NK3IC묂\B9A'J &k_1\IlOԢ=PTdD}Xqū{ !wbVV͛Ny"³^vϋi^کa]h<ӈ ج/bb-.@AѡYgcd|w\x7MB{&: tv+$ӯ=pFd3f*A`^mOH6=S˷ߤ}{gX@VuNc$z]ө8-$Kod3}>sw`Ӏԩ?4 Sq3gQrSٳ|z^xb;2`_yl6hX@=8p,d 77+D%Qy|WaBipϮTo޾Kg;y"hHnTF犠L&ںs-]'%;Jݻpm͞-̥7h]ܺo{[k4+թvTB9 qt)wA{(B[jQݚըfJ'plloIc6hI6^]>1(~1ҙ$:OGd7zfL&&oр>v4|NR޷y7nm~JeTJ횿C=:#L_mX:g*:p5~6ho6* c^4Owz|oԴ4w ZKZ{b, QZ[<+"HFGEXOXf}ٰ{t0t+Y< vb~֊R:wmh_>H5+h6OX9_ok1Aԥ]+Zrѫ{&/\ -~wK0K_Ժ[tyڶk/w@hZ8e˝߼/dj?;81z`=s%-nmk7y@8I))Զz4~(Zm#|WWLeڿ|RtYZ=&E\UZ&۸!}0x]y;׏^\K4>ujۂg7=y=6oXURzL?d[[sl`xX S>ynOj: ^&طE`(ߤT|qͲa>w#6SkBc?g9l fQ.~/N1bL*SϝF~WVҁ4am"m9Jr*wC©LAˑVcڂZtC?:6._d5+}MoAFS_X"[op:m-Woޢu>S-m3KoD=d|~uO[g?i(*ժǍb3 vx xXlqvU*ue;^IPyBtmgC͚BaߗUx9mk AcϥCOڵ?t;}g6z@nm# ,n/RMow&>y 8tz81}?x| ; 賙XᬕCH5s\fd;!4$?MNMQSfөsa_f}/lJ|U_EѴ L @ e )_qIKru{ָ\16ǂt 25ެv/S1dBk8O`a4`aJl v :d4&Pv$;((f;U^4uռu=:qO#-W2eź'َ\c}_]nAS)P߃Z<tcxﯵ@3>A~ұ#i<M< ΫSFE> #a8FΒll!{dIk}_D v u  Od"M# F_NPЂ)Fe[kԹ]+=LLR)ѼV7lo(z ̈́'W(Ytc-T1fcTTIvZz~l]vRG&7m$ۍ"i_҇TkE2s(ɶOMA[J_D ϿѪOfPY[ ҟ3::SȽHJZ/ m pT :C[`8-lʱoS5>I3I6/].,2UYךh:ŪV7VZ<{d׍b3ՙ;}ݺHæ0lD#*s&!ED: &`ZIZ[- 6o$;/̬h7KJN:8<> p ա㧨Ai>ԬQL8h㤎>ߚLQܣʨm] ujcbͤQ8'65 /%t9x!#|jwIUFzfWw!8g!דy7*VPl:k:C [h0I!9]$&`f+o6ѲSziPQm/JY(!LF,>Z]{l7̀aD.PtB;~_mp R*5ob5Nы#H$k:Oll1W԰Y$/y_4O[^h&k'/kWr=>hoݡE ٽuܮ,֗{̝4B)QI85?K3/spM[Q: ^{=iE] 6a$ۅCB Bq(~3Q-.z9G48go}ޟѷ'M?=;|w-_hդ1s4tC\`ߥX f[/,Tѿf~cd3mX#x~rqV԰y:{?cvG:{-6mCU+CF$B!׿v]Hg}}ު*mݹeq̧3&94ug>lTL)\,収"j+:::s2?rL4u,TN:| t0X]D[`/oF.O3& $[H5Iv)t(te )_c*H~u4 af.^NSGu~ ŊЧu !Ak-#ȭS.p+XIz E;z2dP&#_5zvxOd+|3S+7hښ,fq4;3T*勸)|ڱg?'ԶCc6j@C'MsN@g׃{vf3xED /t8N/tvC 8{K7$[L- rhv÷i߅Ehchd^Fw>TTqd[4k޸Cc3V͛ΝgE6l~s; 'hF#B@XAwtŝO{@$yi^#>ՉT39`[Ɍ5¹2Ut%hp_׼TP G=%+wy"iXܖ&ߝ *8d>POR%n,\1z>1c'~RK6eбK<~2^7 oHiABlkb6[6LHNIcy޸EׅX0.l/ST\ϙܧ_$m@{k??Iﷱᓧs|itn+DxD*Qpel1P$rA;{>qʝ =GMptL?VO,I$UG8xH6s?Leye~r^}]B2FLCs&~l4\`0RéCԿ[۸+'_~7Zz>ޮh}kw7z"d9ij{и\JGNk7ՇnlXKd6$RB957Ǿu[^ZLЮwuIL`|17#8cRI誁%[B f/Me҅ͅyPkfx:cwEIB2{zGiL:nwt9'$7nwԶ%ui׊*ct:2+z=7zs0{'+T(U5{jGQիqI+Q]OcX髕jMwQ(ϮtU:}xСإtjԼc7>Ӛ:!wG_cIMB98 IDATNYujyk껻# {{(u* j۽ݽyb G}& 7*g(vPK)c85]'ZIv{&n+$! Thd4M7Y\+L)vS%o5 v*!unjk.91WT4rڕt|n~*vDI5 |J$N4+&$t,`l h劝wt#]PҍfG.80+Xs/Ț[ϳߕ)==ggJ_دhnTigQ%Cz٦M#tmyn-->-f,Z)ɔ'TjTn=Z?NWY+\<={ VYĨ 8&WjG>$ە#V5.|t,ȟm)2uMK&]:.=zg.ҟz٠˸zJXbZRJ$,=MXUOVe*ݞy>3E2 /xz[UW,T%^7=t]x# t$Y$ߐNf۸zYyvQd_vۓ{8b29 IJ;U(3Vv]F^xrB%Ϋd@Ia)N?uV8@g" ܐܦ#0[=wzHLF۞=YL?{?Ó<.ƚdJ2.daT*'ɓRyyl_d,{rٺ':2Rީd%5ʆ:p\$oQoӾpE@@_/C\;oe Onc-O/2|,sX^xN˓GX^hm$d?oFɓ./]f{; >s{eRPR]J3I&'?3H X;ܮ5-B(쏕^274\*IoRH=^^"cqٜe5L?|Ko1ĮvjYr)ieIL0B@'RY,;Kxʑʍ,TH  ˥M5ғc{,lscx*LԨzJ|Eg;qy ƔZAKObD"1o&Sj'<[t$TѿDc/ϥlXPiDK*ʇ7Y@=%J#=-t< tY;l>ӝ*E^V [L f|馫e:sA> 'AJraˇ7 zj-^LC}T-0OY873~rI"f[`G IBUkoŨƳ텛$:KLT66\TAC5+H;ғSdGg |`Iv,ByJ~TmN)KT$ ':E.k?dMT!*-v3T%nn+%2+=<1vJB :$t,hǒn],tlac^oq&9`x/ .Tpp2$rt/';w{-w9?哩2 Ɇl?~|qmF6EUc H;W*_5%ݺT9+u[XI6`LO >0KB| cuHE}3{dτw-gЂbJ:_$s1?*)tฃmo02_б*$fǝ\89 kU5W!Νwu(y||3-/qćBd=vSkg~!t[]nBkd8}rb )tZ^ŏ  /A,fپ=С\!4:UX|lvh?1BaԦZ0WFxEX|luK]8P8uve; /t,IsSM87܏_cvH}L#oz~N"KWбx3$>$=!:Ɓm0|RR+t, Iʽ3zM7QmJȡбx+$\>I+:?oi<  :o$jƚw xPCx^ㄎ y.É~H$bH-Uvbl߯a"x׀Y2\b݊$V"3oL$_Xi[q%O:1~}D`dԤMNtZm_PW=dKot&gP%7B#S„^)aL.t JONw1A#X*t SҦ(-[ :뇨hF%l$n$:}+1}pP-d1Ugt",1}榧 7B"gScHFq I5Fl0;$i$ p?}iAY.JBMdU51$f~A!qBMdYfg.lBd_f10zH}ؒVZx6\WC6}bHui!*бKll #뺔0$ &E jH`PtB(F,(OޫB`/1 1fO$ˉ:}qNGLWC$AxTMYKM :G6!r \l6bb["XYovݳr[AjygcrN1^{BEO'D*5 /qqDj#IENDB`kind-0.27.0/logo/logo.svg000077500000000000000000001772231475376161000151770ustar00rootroot00000000000000 kind-0.27.0/main.go000066400000000000000000000013121475376161000140070ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // This package is a stub main wrapping cmd/kind.Main() package main import ( "sigs.k8s.io/kind/cmd/kind/app" ) func main() { app.Main() } kind-0.27.0/netlify.toml000066400000000000000000000007061475376161000151110ustar00rootroot00000000000000# netlify configuration [build] base = "site/" publish = "site/public/" command = "hugo" [build.environment] HUGO_VERSION = "0.111.3" [context.production.environment] # this controls our robots.txt HUGO_ENV = "production" HUGO_BASEURL = "https://kind.sigs.k8s.io/" [context.deploy-preview] command = "hugo --enableGitInfo --buildFuture -b $DEPLOY_PRIME_URL" [context.branch-deploy] command = "hugo --enableGitInfo --buildFuture -b $DEPLOY_PRIME_URL" kind-0.27.0/pkg/000077500000000000000000000000001475376161000133205ustar00rootroot00000000000000kind-0.27.0/pkg/apis/000077500000000000000000000000001475376161000142545ustar00rootroot00000000000000kind-0.27.0/pkg/apis/config/000077500000000000000000000000001475376161000155215ustar00rootroot00000000000000kind-0.27.0/pkg/apis/config/defaults/000077500000000000000000000000001475376161000173305ustar00rootroot00000000000000kind-0.27.0/pkg/apis/config/defaults/image.go000066400000000000000000000015201475376161000207370ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package defaults contains cross-api-version configuration defaults package defaults // Image is the default for the Config.Image field, aka the default node image. const Image = "kindest/node:v1.32.2@sha256:f226345927d7e348497136874b6d207e0b32cc52154ad8323129352923a3142f" kind-0.27.0/pkg/apis/config/v1alpha4/000077500000000000000000000000001475376161000171415ustar00rootroot00000000000000kind-0.27.0/pkg/apis/config/v1alpha4/default.go000066400000000000000000000057201475376161000211200ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package v1alpha4 import ( "sigs.k8s.io/kind/pkg/apis/config/defaults" ) // SetDefaultsCluster sets uninitialized fields to their default value. func SetDefaultsCluster(obj *Cluster) { // default to a one node cluster if len(obj.Nodes) == 0 { obj.Nodes = []Node{ { Image: defaults.Image, Role: ControlPlaneRole, }, } } // default the nodes for i := range obj.Nodes { a := &obj.Nodes[i] SetDefaultsNode(a) } if obj.Networking.IPFamily == "" { obj.Networking.IPFamily = IPv4Family } // default to listening on 127.0.0.1:randomPort on ipv4 // and [::1]:randomPort on ipv6 if obj.Networking.APIServerAddress == "" { obj.Networking.APIServerAddress = "127.0.0.1" if obj.Networking.IPFamily == IPv6Family { obj.Networking.APIServerAddress = "::1" } } // default the pod CIDR if obj.Networking.PodSubnet == "" { obj.Networking.PodSubnet = "10.244.0.0/16" if obj.Networking.IPFamily == IPv6Family { // node-mask cidr default is /64 so we need a larger subnet, we use /56 following best practices // xref: https://www.ripe.net/publications/docs/ripe-690#4--size-of-end-user-prefix-assignment---48---56-or-something-else- obj.Networking.PodSubnet = "fd00:10:244::/56" } if obj.Networking.IPFamily == DualStackFamily { obj.Networking.PodSubnet = "10.244.0.0/16,fd00:10:244::/56" } } // default the service CIDR using a different subnet than kubeadm default // https://github.com/kubernetes/kubernetes/blob/746404f82a28e55e0b76ffa7e40306fb88eb3317/cmd/kubeadm/app/apis/kubeadm/v1beta2/defaults.go#L32 // Note: kubeadm is using a /12 subnet, that may allocate a 2^20 bitmap in etcd // we allocate a /16 subnet that allows 65535 services (current Kubernetes tested limit is O(10k) services) if obj.Networking.ServiceSubnet == "" { obj.Networking.ServiceSubnet = "10.96.0.0/16" if obj.Networking.IPFamily == IPv6Family { obj.Networking.ServiceSubnet = "fd00:10:96::/112" } if obj.Networking.IPFamily == DualStackFamily { obj.Networking.ServiceSubnet = "10.96.0.0/16,fd00:10:96::/112" } } // default the KubeProxyMode using iptables as it's already the default if obj.Networking.KubeProxyMode == "" { obj.Networking.KubeProxyMode = IPTablesProxyMode } } // SetDefaultsNode sets uninitialized fields to their default value. func SetDefaultsNode(obj *Node) { if obj.Image == "" { obj.Image = defaults.Image } if obj.Role == "" { obj.Role = ControlPlaneRole } } kind-0.27.0/pkg/apis/config/v1alpha4/doc.go000066400000000000000000000013061475376161000202350ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package v1alpha4 implements the v1alpha4 apiVersion of kind's cluster // configuration // // +k8s:deepcopy-gen=package package v1alpha4 kind-0.27.0/pkg/apis/config/v1alpha4/types.go000066400000000000000000000336511475376161000206440ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package v1alpha4 // Cluster contains kind cluster configuration type Cluster struct { TypeMeta `yaml:",inline" json:",inline"` // The cluster name. // Optional, this will be overridden by --name / KIND_CLUSTER_NAME Name string `yaml:"name,omitempty" json:"name,omitempty"` // Nodes contains the list of nodes defined in the `kind` Cluster // If unset this will default to a single control-plane node // Note that if more than one control plane is specified, an external // control plane load balancer will be provisioned implicitly Nodes []Node `yaml:"nodes,omitempty" json:"nodes,omitempty"` /* Advanced fields */ // Networking contains cluster wide network settings Networking Networking `yaml:"networking,omitempty" json:"networking,omitempty"` // FeatureGates contains a map of Kubernetes feature gates to whether they // are enabled. The feature gates specified here are passed to all Kubernetes components as flags or in config. // // https://kubernetes.io/docs/reference/command-line-tools-reference/feature-gates/ FeatureGates map[string]bool `yaml:"featureGates,omitempty" json:"featureGates,omitempty"` // RuntimeConfig Keys and values are translated into --runtime-config values for kube-apiserver, separated by commas. // // Use this to enable alpha APIs. RuntimeConfig map[string]string `yaml:"runtimeConfig,omitempty" json:"runtimeConfig,omitempty"` // KubeadmConfigPatches are applied to the generated kubeadm config as // merge patches. The `kind` field must match the target object, and // if `apiVersion` is specified it will only be applied to matching objects. // // This should be an inline yaml blob-string // // https://tools.ietf.org/html/rfc7386 // // The cluster-level patches are applied before the node-level patches. KubeadmConfigPatches []string `yaml:"kubeadmConfigPatches,omitempty" json:"kubeadmConfigPatches,omitempty"` // KubeadmConfigPatchesJSON6902 are applied to the generated kubeadm config // as JSON 6902 patches. The `kind` field must match the target object, and // if group or version are specified it will only be objects matching the // apiVersion: group+"/"+version // // Name and Namespace are now ignored, but the fields continue to exist for // backwards compatibility of parsing the config. The name of the generated // config was/is always fixed as is the namespace so these fields have // always been a no-op. // // https://tools.ietf.org/html/rfc6902 // // The cluster-level patches are applied before the node-level patches. KubeadmConfigPatchesJSON6902 []PatchJSON6902 `yaml:"kubeadmConfigPatchesJSON6902,omitempty" json:"kubeadmConfigPatchesJSON6902,omitempty"` // ContainerdConfigPatches are applied to every node's containerd config // in the order listed. // These should be toml stringsto be applied as merge patches ContainerdConfigPatches []string `yaml:"containerdConfigPatches,omitempty" json:"containerdConfigPatches,omitempty"` // ContainerdConfigPatchesJSON6902 are applied to every node's containerd config // in the order listed. // These should be YAML or JSON formatting RFC 6902 JSON patches ContainerdConfigPatchesJSON6902 []string `yaml:"containerdConfigPatchesJSON6902,omitempty" json:"containerdConfigPatchesJSON6902,omitempty"` } // TypeMeta partially copies apimachinery/pkg/apis/meta/v1.TypeMeta // No need for a direct dependence; the fields are stable. type TypeMeta struct { Kind string `yaml:"kind,omitempty" json:"kind,omitempty"` APIVersion string `yaml:"apiVersion,omitempty" json:"apiVersion,omitempty"` } // Node contains settings for a node in the `kind` Cluster. // A node in kind config represent a container that will be provisioned with all the components // required for the assigned role in the Kubernetes cluster type Node struct { // Role defines the role of the node in the Kubernetes cluster // created by kind // // Defaults to "control-plane" Role NodeRole `yaml:"role,omitempty" json:"role,omitempty"` // Image is the node image to use when creating this node // If unset a default image will be used, see defaults.Image Image string `yaml:"image,omitempty" json:"image,omitempty"` // Labels are the labels with which the respective node will be labeled Labels map[string]string `yaml:"labels,omitempty" json:"labels,omitempty"` /* Advanced fields */ // TODO: cri-like types should be inline instead // ExtraMounts describes additional mount points for the node container // These may be used to bind a hostPath ExtraMounts []Mount `yaml:"extraMounts,omitempty" json:"extraMounts,omitempty"` // ExtraPortMappings describes additional port mappings for the node container // binded to a host Port ExtraPortMappings []PortMapping `yaml:"extraPortMappings,omitempty" json:"extraPortMappings,omitempty"` // KubeadmConfigPatches are applied to the generated kubeadm config as // merge patches. The `kind` field must match the target object, and // if `apiVersion` is specified it will only be applied to matching objects. // // This should be an inline yaml blob-string // // https://tools.ietf.org/html/rfc7386 // // The node-level patches will be applied after the cluster-level patches // have been applied. (See Cluster.KubeadmConfigPatches) KubeadmConfigPatches []string `yaml:"kubeadmConfigPatches,omitempty" json:"kubeadmConfigPatches,omitempty"` // KubeadmConfigPatchesJSON6902 are applied to the generated kubeadm config // as JSON 6902 patches. The `kind` field must match the target object, and // if group or version are specified it will only be objects matching the // apiVersion: group+"/"+version // // Name and Namespace are now ignored, but the fields continue to exist for // backwards compatibility of parsing the config. The name of the generated // config was/is always fixed as is the namespace so these fields have // always been a no-op. // // https://tools.ietf.org/html/rfc6902 // // The node-level patches will be applied after the cluster-level patches // have been applied. (See Cluster.KubeadmConfigPatchesJSON6902) KubeadmConfigPatchesJSON6902 []PatchJSON6902 `yaml:"kubeadmConfigPatchesJSON6902,omitempty" json:"kubeadmConfigPatchesJSON6902,omitempty"` } // NodeRole defines possible role for nodes in a Kubernetes cluster managed by `kind` type NodeRole string const ( // ControlPlaneRole identifies a node that hosts a Kubernetes control-plane. // NOTE: in single node clusters, control-plane nodes act also as a worker // nodes, in which case the taint will be removed. see: // https://kubernetes.io/docs/setup/independent/create-cluster-kubeadm/#control-plane-node-isolation ControlPlaneRole NodeRole = "control-plane" // WorkerRole identifies a node that hosts a Kubernetes worker WorkerRole NodeRole = "worker" ) // Networking contains cluster wide network settings type Networking struct { // IPFamily is the network cluster model, currently it can be ipv4 or ipv6 IPFamily ClusterIPFamily `yaml:"ipFamily,omitempty" json:"ipFamily,omitempty"` // APIServerPort is the listen port on the host for the Kubernetes API Server // Defaults to a random port on the host obtained by kind // // NOTE: if you set the special value of `-1` then the node backend // (docker, podman...) will be left to pick the port instead. // This is potentially useful for remote hosts, BUT it means when the container // is restarted it will be randomized. Leave this unset to allow kind to pick it. APIServerPort int32 `yaml:"apiServerPort,omitempty" json:"apiServerPort,omitempty"` // APIServerAddress is the listen address on the host for the Kubernetes // API Server. This should be an IP address. // // Defaults to 127.0.0.1 APIServerAddress string `yaml:"apiServerAddress,omitempty" json:"apiServerAddress,omitempty"` // PodSubnet is the CIDR used for pod IPs // kind will select a default if unspecified PodSubnet string `yaml:"podSubnet,omitempty" json:"podSubnet,omitempty"` // ServiceSubnet is the CIDR used for services VIPs // kind will select a default if unspecified for IPv6 ServiceSubnet string `yaml:"serviceSubnet,omitempty" json:"serviceSubnet,omitempty"` // If DisableDefaultCNI is true, kind will not install the default CNI setup. // Instead the user should install their own CNI after creating the cluster. DisableDefaultCNI bool `yaml:"disableDefaultCNI,omitempty" json:"disableDefaultCNI,omitempty"` // KubeProxyMode defines if kube-proxy should operate in iptables, ipvs or nftables mode // Defaults to 'iptables' mode KubeProxyMode ProxyMode `yaml:"kubeProxyMode,omitempty" json:"kubeProxyMode,omitempty"` // DNSSearch defines the DNS search domain to use for nodes. If not set, this will be inherited from the host. DNSSearch *[]string `yaml:"dnsSearch,omitempty" json:"dnsSearch,omitempty"` } // ClusterIPFamily defines cluster network IP family type ClusterIPFamily string const ( // IPv4Family sets ClusterIPFamily to ipv4 IPv4Family ClusterIPFamily = "ipv4" // IPv6Family sets ClusterIPFamily to ipv6 IPv6Family ClusterIPFamily = "ipv6" // DualStackFamily sets ClusterIPFamily to dual DualStackFamily ClusterIPFamily = "dual" ) // ProxyMode defines a proxy mode for kube-proxy type ProxyMode string const ( // IPTablesProxyMode sets ProxyMode to iptables IPTablesProxyMode ProxyMode = "iptables" // IPVSProxyMode sets ProxyMode to ipvs IPVSProxyMode ProxyMode = "ipvs" // NFTablesProxyMode sets ProxyMode to nftables NFTablesProxyMode ProxyMode = "nftables" ) // PatchJSON6902 represents an inline kustomize json 6902 patch // https://tools.ietf.org/html/rfc6902 type PatchJSON6902 struct { // these fields specify the patch target resource Group string `yaml:"group" json:"group"` Version string `yaml:"version" json:"version"` Kind string `yaml:"kind" json:"kind"` // Patch should contain the contents of the json patch as a string Patch string `yaml:"patch" json:"patch"` } /* These types are from https://github.com/kubernetes/kubernetes/blob/063e7ff358fdc8b0916e6f39beedc0d025734cb1/pkg/kubelet/apis/cri/runtime/v1alpha2/api.pb.go#L183 */ // Mount specifies a host volume to mount into a container. // This is a close copy of the upstream cri Mount type // see: k8s.io/kubernetes/pkg/kubelet/apis/cri/runtime/v1alpha2 // It additionally serializes the "propagation" field with the string enum // names on disk as opposed to the int32 values, and the serialized field names // have been made closer to core/v1 VolumeMount field names // In yaml this looks like: // // containerPath: /foo // hostPath: /bar // readOnly: true // selinuxRelabel: false // propagation: None // // Propagation may be one of: None, HostToContainer, Bidirectional type Mount struct { // Path of the mount within the container. ContainerPath string `yaml:"containerPath,omitempty" json:"containerPath,omitempty"` // Path of the mount on the host. If the hostPath doesn't exist, then runtimes // should report error. If the hostpath is a symbolic link, runtimes should // follow the symlink and mount the real destination to container. HostPath string `yaml:"hostPath,omitempty" json:"hostPath,omitempty"` // If set, the mount is read-only. Readonly bool `yaml:"readOnly,omitempty" json:"readOnly,omitempty"` // If set, the mount needs SELinux relabeling. SelinuxRelabel bool `yaml:"selinuxRelabel,omitempty" json:"selinuxRelabel,omitempty"` // Requested propagation mode. Propagation MountPropagation `yaml:"propagation,omitempty" json:"propagation,omitempty"` } // PortMapping specifies a host port mapped into a container port. // In yaml this looks like: // // containerPort: 80 // hostPort: 8000 // listenAddress: 127.0.0.1 // protocol: TCP type PortMapping struct { // Port within the container. ContainerPort int32 `yaml:"containerPort,omitempty" json:"containerPort,omitempty"` // Port on the host. // // If unset, a random port will be selected. // // NOTE: if you set the special value of `-1` then the node backend // (docker, podman...) will be left to pick the port instead. // This is potentially useful for remote hosts, BUT it means when the container // is restarted it will be randomized. Leave this unset to allow kind to pick it. HostPort int32 `yaml:"hostPort,omitempty" json:"hostPort,omitempty"` // TODO: add protocol (tcp/udp) and port-ranges ListenAddress string `yaml:"listenAddress,omitempty" json:"listenAddress,omitempty"` // Protocol (TCP/UDP/SCTP) Protocol PortMappingProtocol `yaml:"protocol,omitempty" json:"protocol,omitempty"` } // MountPropagation represents an "enum" for mount propagation options, // see also Mount. type MountPropagation string const ( // MountPropagationNone specifies that no mount propagation // ("private" in Linux terminology). MountPropagationNone MountPropagation = "None" // MountPropagationHostToContainer specifies that mounts get propagated // from the host to the container ("rslave" in Linux). MountPropagationHostToContainer MountPropagation = "HostToContainer" // MountPropagationBidirectional specifies that mounts get propagated from // the host to the container and from the container to the host // ("rshared" in Linux). MountPropagationBidirectional MountPropagation = "Bidirectional" ) // PortMappingProtocol represents an "enum" for port mapping protocol options, // see also PortMapping. type PortMappingProtocol string const ( // PortMappingProtocolTCP specifies TCP protocol PortMappingProtocolTCP PortMappingProtocol = "TCP" // PortMappingProtocolUDP specifies UDP protocol PortMappingProtocolUDP PortMappingProtocol = "UDP" // PortMappingProtocolSCTP specifies SCTP protocol PortMappingProtocolSCTP PortMappingProtocol = "SCTP" ) kind-0.27.0/pkg/apis/config/v1alpha4/yaml.go000066400000000000000000000041201475376161000204270ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package v1alpha4 import ( "strings" "sigs.k8s.io/kind/pkg/errors" ) /* Custom YAML (de)serialization for these types */ // UnmarshalYAML implements custom decoding YAML // https://godoc.org/gopkg.in/yaml.v3 func (m *Mount) UnmarshalYAML(unmarshal func(interface{}) error) error { // first unmarshal in the alias type (to avoid a recursion loop on unmarshal) type MountAlias Mount var a MountAlias if err := unmarshal(&a); err != nil { return err } // now handle propagation switch a.Propagation { case "": // unset, will be defaulted case MountPropagationNone: case MountPropagationHostToContainer: case MountPropagationBidirectional: default: return errors.Errorf("Unknown MountPropagation: %q", a.Propagation) } // and copy over the fields *m = Mount(a) return nil } // UnmarshalYAML implements custom decoding YAML // https://godoc.org/gopkg.in/yaml.v3 func (p *PortMapping) UnmarshalYAML(unmarshal func(interface{}) error) error { // first unmarshal in the alias type (to avoid a recursion loop on unmarshal) type PortMappingAlias PortMapping var a PortMappingAlias if err := unmarshal(&a); err != nil { return err } // now handle the protocol field a.Protocol = PortMappingProtocol(strings.ToUpper(string(a.Protocol))) switch a.Protocol { case "": // unset, will be defaulted case PortMappingProtocolTCP: case PortMappingProtocolUDP: case PortMappingProtocolSCTP: default: return errors.Errorf("Unknown PortMappingProtocol: %q", a.Protocol) } // and copy over the fields *p = PortMapping(a) return nil } kind-0.27.0/pkg/apis/config/v1alpha4/zz_generated.deepcopy.go000066400000000000000000000134501475376161000237630ustar00rootroot00000000000000//go:build !ignore_autogenerated // +build !ignore_autogenerated /* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Code generated by deepcopy-gen. DO NOT EDIT. package v1alpha4 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Cluster) DeepCopyInto(out *Cluster) { *out = *in out.TypeMeta = in.TypeMeta if in.Nodes != nil { in, out := &in.Nodes, &out.Nodes *out = make([]Node, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } in.Networking.DeepCopyInto(&out.Networking) if in.FeatureGates != nil { in, out := &in.FeatureGates, &out.FeatureGates *out = make(map[string]bool, len(*in)) for key, val := range *in { (*out)[key] = val } } if in.RuntimeConfig != nil { in, out := &in.RuntimeConfig, &out.RuntimeConfig *out = make(map[string]string, len(*in)) for key, val := range *in { (*out)[key] = val } } if in.KubeadmConfigPatches != nil { in, out := &in.KubeadmConfigPatches, &out.KubeadmConfigPatches *out = make([]string, len(*in)) copy(*out, *in) } if in.KubeadmConfigPatchesJSON6902 != nil { in, out := &in.KubeadmConfigPatchesJSON6902, &out.KubeadmConfigPatchesJSON6902 *out = make([]PatchJSON6902, len(*in)) copy(*out, *in) } if in.ContainerdConfigPatches != nil { in, out := &in.ContainerdConfigPatches, &out.ContainerdConfigPatches *out = make([]string, len(*in)) copy(*out, *in) } if in.ContainerdConfigPatchesJSON6902 != nil { in, out := &in.ContainerdConfigPatchesJSON6902, &out.ContainerdConfigPatchesJSON6902 *out = make([]string, len(*in)) copy(*out, *in) } return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Cluster. func (in *Cluster) DeepCopy() *Cluster { if in == nil { return nil } out := new(Cluster) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Mount) DeepCopyInto(out *Mount) { *out = *in return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Mount. func (in *Mount) DeepCopy() *Mount { if in == nil { return nil } out := new(Mount) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Networking) DeepCopyInto(out *Networking) { *out = *in if in.DNSSearch != nil { in, out := &in.DNSSearch, &out.DNSSearch *out = new([]string) if **in != nil { in, out := *in, *out *out = make([]string, len(*in)) copy(*out, *in) } } return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Networking. func (in *Networking) DeepCopy() *Networking { if in == nil { return nil } out := new(Networking) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Node) DeepCopyInto(out *Node) { *out = *in if in.Labels != nil { in, out := &in.Labels, &out.Labels *out = make(map[string]string, len(*in)) for key, val := range *in { (*out)[key] = val } } if in.ExtraMounts != nil { in, out := &in.ExtraMounts, &out.ExtraMounts *out = make([]Mount, len(*in)) copy(*out, *in) } if in.ExtraPortMappings != nil { in, out := &in.ExtraPortMappings, &out.ExtraPortMappings *out = make([]PortMapping, len(*in)) copy(*out, *in) } if in.KubeadmConfigPatches != nil { in, out := &in.KubeadmConfigPatches, &out.KubeadmConfigPatches *out = make([]string, len(*in)) copy(*out, *in) } if in.KubeadmConfigPatchesJSON6902 != nil { in, out := &in.KubeadmConfigPatchesJSON6902, &out.KubeadmConfigPatchesJSON6902 *out = make([]PatchJSON6902, len(*in)) copy(*out, *in) } return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Node. func (in *Node) DeepCopy() *Node { if in == nil { return nil } out := new(Node) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PatchJSON6902) DeepCopyInto(out *PatchJSON6902) { *out = *in return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PatchJSON6902. func (in *PatchJSON6902) DeepCopy() *PatchJSON6902 { if in == nil { return nil } out := new(PatchJSON6902) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PortMapping) DeepCopyInto(out *PortMapping) { *out = *in return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PortMapping. func (in *PortMapping) DeepCopy() *PortMapping { if in == nil { return nil } out := new(PortMapping) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TypeMeta) DeepCopyInto(out *TypeMeta) { *out = *in return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TypeMeta. func (in *TypeMeta) DeepCopy() *TypeMeta { if in == nil { return nil } out := new(TypeMeta) in.DeepCopyInto(out) return out } kind-0.27.0/pkg/build/000077500000000000000000000000001475376161000144175ustar00rootroot00000000000000kind-0.27.0/pkg/build/nodeimage/000077500000000000000000000000001475376161000163475ustar00rootroot00000000000000kind-0.27.0/pkg/build/nodeimage/build.go000066400000000000000000000100161475376161000177730ustar00rootroot00000000000000/* Copyright 2020 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package nodeimage import ( "fmt" "net/url" "os" "runtime" "sigs.k8s.io/kind/pkg/build/nodeimage/internal/kube" "sigs.k8s.io/kind/pkg/errors" "sigs.k8s.io/kind/pkg/internal/version" "sigs.k8s.io/kind/pkg/log" ) // Build builds a node image using the supplied options func Build(options ...Option) error { // default options ctx := &buildContext{ image: DefaultImage, baseImage: DefaultBaseImage, logger: log.NoopLogger{}, arch: runtime.GOARCH, } // apply user options for _, option := range options { if err := option.apply(ctx); err != nil { return err } } // verify that we're using a supported arch if !supportedArch(ctx.arch) { ctx.logger.Warnf("unsupported architecture %q", ctx.arch) } if ctx.buildType == "" { ctx.buildType = detectBuildType(ctx.kubeParam) if ctx.buildType != "" { ctx.logger.V(0).Infof("Detected build type: %q", ctx.buildType) } } if ctx.buildType == "url" { ctx.logger.V(0).Infof("Building using URL: %q", ctx.kubeParam) builder, err := kube.NewURLBuilder(ctx.logger, ctx.kubeParam) if err != nil { return err } ctx.builder = builder } if ctx.buildType == "file" { ctx.logger.V(0).Infof("Building using local file: %q", ctx.kubeParam) if info, err := os.Stat(ctx.kubeParam); err == nil && info.Mode().IsRegular() { builder, err := kube.NewTarballBuilder(ctx.logger, ctx.kubeParam) if err != nil { return err } ctx.builder = builder } } if ctx.buildType == "release" { ctx.logger.V(0).Infof("Building using release %q artifacts", ctx.kubeParam) kubever, err := version.ParseSemantic(ctx.kubeParam) if err == nil { builder, err := kube.NewReleaseBuilder(ctx.logger, "v"+kubever.String(), ctx.arch) if err != nil { return err } ctx.builder = builder } else { if _, err := os.Stat(ctx.kubeParam); err != nil { ctx.logger.V(0).Infof("%s is not a valid kubernetes version", ctx.kubeParam) return fmt.Errorf("%s is not a valid kubernetes version", ctx.kubeParam) } } } if ctx.builder == nil { // locate sources if no kubernetes source was specified if ctx.kubeParam == "" { kubeRoot, err := kube.FindSource() if err != nil { return errors.Wrap(err, "error finding kuberoot") } ctx.kubeParam = kubeRoot } ctx.logger.V(0).Infof("Building using source: %q", ctx.kubeParam) // initialize bits builder, err := kube.NewDockerBuilder(ctx.logger, ctx.kubeParam, ctx.arch) if err != nil { return err } ctx.builder = builder } // do the actual build return ctx.Build() } // detectBuildType detect the type of build required based on the param passed in the following order // url: if the param is a valid http or https url // file: if the param refers to an existing regular file // source: if the param refers to an existing directory // release: if the param is a semantic version expression (does this require the v preprended? func detectBuildType(param string) string { u, err := url.ParseRequestURI(param) if err == nil { if u.Scheme == "http" || u.Scheme == "https" { return "url" } } if info, err := os.Stat(param); err == nil { if info.Mode().IsRegular() { return "file" } if info.Mode().IsDir() { return "source" } } _, err = version.ParseSemantic(param) if err == nil { return "release" } return "" } func supportedArch(arch string) bool { switch arch { default: return false // currently we nominally support building node images for these case "amd64": case "arm64": } return true } kind-0.27.0/pkg/build/nodeimage/buildcontext.go000066400000000000000000000300011475376161000213740ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package nodeimage import ( "fmt" "math/rand" "os" "path" "strings" "time" "sigs.k8s.io/kind/pkg/errors" "sigs.k8s.io/kind/pkg/exec" "sigs.k8s.io/kind/pkg/log" "sigs.k8s.io/kind/pkg/build/nodeimage/internal/container/docker" "sigs.k8s.io/kind/pkg/build/nodeimage/internal/kube" "sigs.k8s.io/kind/pkg/internal/sets" "sigs.k8s.io/kind/pkg/internal/version" ) const ( // httpProxy is the HTTP_PROXY environment variable key httpProxy = "HTTP_PROXY" // httpsProxy is the HTTPS_PROXY environment variable key httpsProxy = "HTTPS_PROXY" // noProxy is the NO_PROXY environment variable key noProxy = "NO_PROXY" ) // buildContext is used to build the kind node image, and contains // build configuration type buildContext struct { // option fields image string baseImage string logger log.Logger arch string buildType string kubeParam string // non-option fields builder kube.Builder } // Build builds the cluster node image, the source dir must be set on // the buildContext func (c *buildContext) Build() (err error) { // ensure kubernetes build is up-to-date first c.logger.V(0).Info("Starting to build Kubernetes") bits, err := c.builder.Build() if err != nil { c.logger.Errorf("Failed to build Kubernetes: %v", err) return errors.Wrap(err, "failed to build kubernetes") } c.logger.V(0).Info("Finished building Kubernetes") // then perform the actual docker image build c.logger.V(0).Info("Building node image ...") return c.buildImage(bits) } func (c *buildContext) buildImage(bits kube.Bits) error { // create build container // NOTE: we are using docker run + docker commit, so we can install // debian packages without permanently copying them into the image. // if docker gets proper squash support, we can rm them instead // This also allows the KubeBit implementations to programmatically // install in the image containerID, err := c.createBuildContainer() cmder := docker.ContainerCmder(containerID) // ensure we will delete it if containerID != "" { defer func() { _ = exec.Command("docker", "rm", "-f", "-v", containerID).Run() }() } if err != nil { c.logger.Errorf("Image build Failed! Failed to create build container: %v", err) return err } c.logger.V(0).Info("Building in container: " + containerID) // copy artifacts in for _, binary := range bits.BinaryPaths() { // TODO: probably should be /usr/local/bin, but the existing kubelet // service file expects /usr/bin/kubelet nodePath := "/usr/bin/" + path.Base(binary) if err := exec.Command("docker", "cp", binary, containerID+":"+nodePath).Run(); err != nil { return err } if err := cmder.Command("chmod", "+x", nodePath).Run(); err != nil { return err } if err := cmder.Command("chown", "root:root", nodePath).Run(); err != nil { return err } } // write version // TODO: support grabbing version from a binary instead? // This may or may not be a good idea ... rawVersion := bits.Version() parsedVersion, err := version.ParseSemantic(rawVersion) if err != nil { return errors.Wrap(err, "invalid Kubernetes version") } if err := createFile(cmder, "/kind/version", rawVersion); err != nil { return err } // pre-pull images that were not part of the build and write CNI / storage // manifests if _, err = c.prePullImagesAndWriteManifests(bits, parsedVersion, containerID); err != nil { c.logger.Errorf("Image build Failed! Failed to pull Images: %v", err) return err } // Save the image changes to a new image if err = exec.Command( "docker", "commit", // we need to put this back after changing it when running the image "--change", `ENTRYPOINT [ "/usr/local/bin/entrypoint", "/sbin/init" ]`, // remove proxy settings since they're for the building process // and should not be carried with the built image "--change", `ENV HTTP_PROXY="" HTTPS_PROXY="" NO_PROXY=""`, containerID, c.image, ).Run(); err != nil { c.logger.Errorf("Image build Failed! Failed to save image: %v", err) return err } c.logger.V(0).Infof("Image %q build completed.", c.image) return nil } // returns a set of image tags that will be side-loaded func (c *buildContext) getBuiltImages(bits kube.Bits) (sets.String, error) { images := sets.NewString() for _, path := range bits.ImagePaths() { tags, err := docker.GetArchiveTags(path) if err != nil { return nil, err } images.Insert(tags...) } return images, nil } // must be run after kubernetes has been installed on the node func (c *buildContext) prePullImagesAndWriteManifests(bits kube.Bits, parsedVersion *version.Version, containerID string) ([]string, error) { // first get the images we actually built builtImages, err := c.getBuiltImages(bits) if err != nil { c.logger.Errorf("Image build Failed! Failed to get built images: %v", err) return nil, err } // helpers to run things in the build container cmder := docker.ContainerCmder(containerID) // For kubernetes v1.15+ (actually 1.16 alpha versions) we may need to // drop the arch suffix from images to get the expected image archSuffix := "-" + c.arch fixRepository := func(repository string) string { if strings.HasSuffix(repository, archSuffix) { fixed := strings.TrimSuffix(repository, archSuffix) c.logger.V(1).Info("fixed: " + repository + " -> " + fixed) repository = fixed } return repository } // Determine accurate built tags using the logic that will be applied // when rewriting tags during archive loading fixedImages := sets.NewString() fixedImagesMap := make(map[string]string, builtImages.Len()) // key: original images, value: fixed images for _, image := range builtImages.List() { registry, tag, err := docker.SplitImage(image) if err != nil { return nil, err } registry = fixRepository(registry) fixedImage := registry + ":" + tag fixedImages.Insert(fixedImage) fixedImagesMap[image] = fixedImage } builtImages = fixedImages c.logger.V(1).Info("Detected built images: " + strings.Join(builtImages.List(), ", ")) // gets the list of images required by kubeadm requiredImages, err := exec.OutputLines(cmder.Command( "kubeadm", "config", "images", "list", "--kubernetes-version", bits.Version(), )) if err != nil { return nil, err } // replace pause image with our own containerdConfig, err := exec.Output(cmder.Command("cat", containerdConfigPath)) if err != nil { return nil, err } pauseImage, err := findSandboxImage(string(containerdConfig)) if err != nil { return nil, err } n := 0 for _, image := range requiredImages { if !strings.Contains(image, "pause") { requiredImages[n] = image n++ } } requiredImages = append(requiredImages[:n], pauseImage) if parsedVersion.LessThan(version.MustParseSemantic("v1.24.0")) { if err := configureContainerdSystemdCgroupFalse(cmder, string(containerdConfig)); err != nil { return nil, err } } // write the default CNI manifest if err := createFile(cmder, defaultCNIManifestLocation, defaultCNIManifest); err != nil { c.logger.Errorf("Image build Failed! Failed write default CNI Manifest: %v", err) return nil, err } // all builds should install the default CNI images from the above manifest currently requiredImages = append(requiredImages, defaultCNIImages...) // write the default Storage manifest if err := createFile(cmder, defaultStorageManifestLocation, defaultStorageManifest); err != nil { c.logger.Errorf("Image build Failed! Failed write default Storage Manifest: %v", err) return nil, err } // all builds should install the default storage driver images currently requiredImages = append(requiredImages, defaultStorageImages...) // setup image importer importer := newContainerdImporter(cmder) if err := importer.Prepare(); err != nil { c.logger.Errorf("Image build Failed! Failed to prepare containerd to load images %v", err) return nil, err } // TODO: return this error? defer func() { if err := importer.End(); err != nil { c.logger.Errorf("Image build Failed! Failed to tear down containerd after loading images %v", err) } }() fns := []func() error{} osArch := dockerBuildOsAndArch(c.arch) for _, image := range requiredImages { image := image // https://golang.org/doc/faq#closures_and_goroutines if !builtImages.Has(image) { fns = append(fns, func() error { if err = importer.Pull(image, osArch); err != nil { c.logger.Warnf("Failed to pull %s with error: %v", image, err) runE := exec.RunErrorForError(err) c.logger.Warn(string(runE.Output)) c.logger.Warnf("Retrying %s pull after 1s ...", image) time.Sleep(time.Second) return importer.Pull(image, osArch) } return nil }) } } // Wait for containerd socket to be ready, which may take 1s when running under emulation if err := importer.WaitForReady(); err != nil { c.logger.Errorf("Image build failed, containerd did not become ready %v", err) return nil, err } if err := errors.AggregateConcurrent(fns); err != nil { return nil, err } // create a plan of image loading loadFns := []func() error{} for _, image := range bits.ImagePaths() { image := image // capture loop var loadFns = append(loadFns, func() error { f, err := os.Open(image) if err != nil { return err } defer f.Close() return importer.LoadCommand().SetStdout(os.Stdout).SetStderr(os.Stderr).SetStdin(f).Run() // we will rewrite / correct the tags in tagFns below }) } // run all image loading concurrently until one fails or all succeed if err := errors.UntilErrorConcurrent(loadFns); err != nil { c.logger.Errorf("Image build Failed! Failed to load images %v", err) return nil, err } // create a plan of image re-tagging tagFns := []func() error{} for unfixed, fixed := range fixedImagesMap { unfixed, fixed := unfixed, fixed // capture loop var if unfixed != fixed { tagFns = append(tagFns, func() error { return importer.Tag(unfixed, fixed) }) } } // run all image re-tagging concurrently until one fails or all succeed if err := errors.UntilErrorConcurrent(tagFns); err != nil { c.logger.Errorf("Image build Failed! Failed to re-tag images %v", err) return nil, err } return importer.ListImported() } func (c *buildContext) createBuildContainer() (id string, err error) { // attempt to explicitly pull the image if it doesn't exist locally // errors here are non-critical; we'll proceed with execution, which includes a pull operation _ = docker.Pull(c.logger, c.baseImage, dockerBuildOsAndArch(c.arch), 4) // this should be good enough: a specific prefix, the current unix time, // and a little random bits in case we have multiple builds simultaneously random := rand.New(rand.NewSource(time.Now().UnixNano())).Int31() id = fmt.Sprintf("kind-build-%d-%d", time.Now().UTC().Unix(), random) runArgs := []string{ "-d", // make the client exit while the container continues to run "--entrypoint=sleep", // the container should hang forever, so we can exec in it "--name=" + id, "--platform=" + dockerBuildOsAndArch(c.arch), "--security-opt", "seccomp=unconfined", } // pass proxy settings from environment variables to the building container // to make them work during the building process for _, name := range []string{httpProxy, httpsProxy, noProxy} { val := os.Getenv(name) if val == "" { val = os.Getenv(strings.ToLower(name)) } if val != "" { runArgs = append(runArgs, "--env", name+"="+val) } } err = docker.Run( c.baseImage, runArgs, []string{ "infinity", // sleep infinitely to keep container running indefinitely }, ) if err != nil { return id, errors.Wrap(err, "failed to create build container") } return id, nil } kind-0.27.0/pkg/build/nodeimage/const.go000066400000000000000000000016261475376161000200310ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package nodeimage // these are well known paths within the node image const ( // TODO: refactor kubernetesVersionLocation to a common internal package kubernetesVersionLocation = "/kind/version" defaultCNIManifestLocation = "/kind/manifests/default-cni.yaml" defaultStorageManifestLocation = "/kind/manifests/default-storage.yaml" ) kind-0.27.0/pkg/build/nodeimage/const_cni.go000066400000000000000000000065531475376161000206660ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package nodeimage /* The default CNI manifest and images are our own tiny kindnet */ const kindnetdImage = "docker.io/kindest/kindnetd:v20250214-acbabc1a" var defaultCNIImages = []string{kindnetdImage} // TODO: migrate to fully patching and deprecate the template const defaultCNIManifest = ` # kindnetd networking manifest # would you kindly template this file # would you kindly patch this file --- kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 metadata: name: kindnet rules: - apiGroups: - policy resources: - podsecuritypolicies verbs: - use resourceNames: - kindnet - apiGroups: - "" resources: - nodes - pods - namespaces verbs: - list - watch - apiGroups: - "networking.k8s.io" resources: - networkpolicies verbs: - list - watch --- kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: kindnet roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: kindnet subjects: - kind: ServiceAccount name: kindnet namespace: kube-system --- apiVersion: v1 kind: ServiceAccount metadata: name: kindnet namespace: kube-system --- apiVersion: apps/v1 kind: DaemonSet metadata: name: kindnet namespace: kube-system labels: tier: node app: kindnet k8s-app: kindnet spec: selector: matchLabels: app: kindnet template: metadata: labels: tier: node app: kindnet k8s-app: kindnet spec: hostNetwork: true nodeSelector: kubernetes.io/os: linux tolerations: - operator: Exists serviceAccountName: kindnet containers: - name: kindnet-cni image: ` + kindnetdImage + ` env: - name: HOST_IP valueFrom: fieldRef: fieldPath: status.hostIP - name: POD_IP valueFrom: fieldRef: fieldPath: status.podIP - name: POD_SUBNET value: {{ .PodSubnet }} volumeMounts: - name: cni-cfg mountPath: /etc/cni/net.d - name: xtables-lock mountPath: /run/xtables.lock readOnly: false - name: lib-modules mountPath: /lib/modules readOnly: true resources: requests: cpu: "100m" memory: "50Mi" limits: cpu: "100m" memory: "50Mi" securityContext: privileged: false capabilities: add: ["NET_RAW", "NET_ADMIN"] volumes: - name: cni-cfg hostPath: path: /etc/cni/net.d - name: xtables-lock hostPath: path: /run/xtables.lock type: FileOrCreate - name: lib-modules hostPath: path: /lib/modules --- ` kind-0.27.0/pkg/build/nodeimage/const_storage.go000066400000000000000000000131371475376161000215550ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package nodeimage /* The default PV driver manifest and images are provisionally rancher.io/local-path-provisioner NOTE: we have customized it in the following ways: - storage is under /var instead of /opt - our own image and helper image - schedule to linux nodes only - install as the default storage class - tolerate control plane scheduling taints */ const storageProvisionerImage = "docker.io/kindest/local-path-provisioner:v20250214-acbabc1a" const storageHelperImage = "docker.io/kindest/local-path-helper:v20241212-8ac705d0" // image we need to preload var defaultStorageImages = []string{storageProvisionerImage, storageHelperImage} const defaultStorageManifest = ` apiVersion: v1 kind: Namespace metadata: name: local-path-storage --- apiVersion: v1 kind: ServiceAccount metadata: name: local-path-provisioner-service-account namespace: local-path-storage --- apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: local-path-provisioner-role namespace: local-path-storage rules: - apiGroups: [""] resources: ["pods"] verbs: ["get", "list", "watch", "create", "patch", "update", "delete"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: local-path-provisioner-role rules: - apiGroups: [""] resources: ["nodes", "persistentvolumeclaims", "configmaps", "pods", "pods/log"] verbs: ["get", "list", "watch"] - apiGroups: [""] resources: ["persistentvolumes"] verbs: ["get", "list", "watch", "create", "patch", "update", "delete"] - apiGroups: [""] resources: ["events"] verbs: ["create", "patch"] - apiGroups: ["storage.k8s.io"] resources: ["storageclasses"] verbs: ["get", "list", "watch"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: local-path-provisioner-bind namespace: local-path-storage roleRef: apiGroup: rbac.authorization.k8s.io kind: Role name: local-path-provisioner-role subjects: - kind: ServiceAccount name: local-path-provisioner-service-account namespace: local-path-storage --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: local-path-provisioner-bind roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: local-path-provisioner-role subjects: - kind: ServiceAccount name: local-path-provisioner-service-account namespace: local-path-storage --- apiVersion: apps/v1 kind: Deployment metadata: name: local-path-provisioner namespace: local-path-storage spec: replicas: 1 selector: matchLabels: app: local-path-provisioner template: metadata: labels: app: local-path-provisioner spec: nodeSelector: kubernetes.io/os: linux # TODO: Remove the "master" taint after kubeadm nodes no longer have it. # This can be done once kind no longer supports kubeadm 1.24. # https://github.com/kubernetes-sigs/kind/issues/1699 tolerations: - key: node-role.kubernetes.io/control-plane operator: Equal effect: NoSchedule - key: node-role.kubernetes.io/master operator: Equal effect: NoSchedule serviceAccountName: local-path-provisioner-service-account containers: - name: local-path-provisioner image: ` + storageProvisionerImage + ` imagePullPolicy: IfNotPresent command: - local-path-provisioner - --debug - start - --helper-image - ` + storageHelperImage + ` - --config - /etc/config/config.json volumeMounts: - name: config-volume mountPath: /etc/config/ env: - name: POD_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace - name: CONFIG_MOUNT_PATH value: /etc/config/ volumes: - name: config-volume configMap: name: local-path-config --- apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: standard namespace: kube-system annotations: storageclass.kubernetes.io/is-default-class: "true" provisioner: rancher.io/local-path volumeBindingMode: WaitForFirstConsumer reclaimPolicy: Delete --- kind: ConfigMap apiVersion: v1 metadata: name: local-path-config namespace: local-path-storage data: config.json: |- { "nodePathMap":[ { "node":"DEFAULT_PATH_FOR_NON_LISTED_NODES", "paths":["/var/local-path-provisioner"] } ] } setup: |- #!/bin/sh set -eu mkdir -m 0777 -p "$VOL_DIR" teardown: |- #!/bin/sh set -eu rm -rf "$VOL_DIR" helperPod.yaml: |- apiVersion: v1 kind: Pod metadata: name: helper-pod spec: priorityClassName: system-node-critical tolerations: - key: node.kubernetes.io/disk-pressure operator: Exists effect: NoSchedule containers: - name: helper-pod image: ` + storageHelperImage + ` imagePullPolicy: IfNotPresent ` kind-0.27.0/pkg/build/nodeimage/containerd.go000066400000000000000000000030171475376161000210250ustar00rootroot00000000000000/* Copyright 2022 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package nodeimage import ( "strings" "sigs.k8s.io/kind/pkg/errors" "sigs.k8s.io/kind/pkg/exec" "sigs.k8s.io/kind/pkg/internal/patch" ) const containerdConfigPath = "/etc/containerd/config.toml" const containerdConfigPatchSystemdCgroupFalse = ` [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options] SystemdCgroup = false [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.test-handler.options] SystemdCgroup = false ` func configureContainerdSystemdCgroupFalse(containerCmdr exec.Cmder, config string) error { patched, err := patch.TOML(config, []string{containerdConfigPatchSystemdCgroupFalse}, []string{}) if err != nil { return errors.Wrap(err, "failed to configure containerd SystemdCgroup=false") } err = containerCmdr.Command( "cp", "/dev/stdin", containerdConfigPath, ).SetStdin(strings.NewReader(patched)).Run() if err != nil { return errors.Wrap(err, "failed to configure containerd SystemdCgroup=false") } return nil } kind-0.27.0/pkg/build/nodeimage/defaults.go000066400000000000000000000016311475376161000205060ustar00rootroot00000000000000/* Copyright 2020 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package nodeimage // DefaultImage is the default name:tag for the built image const DefaultImage = "kindest/node:latest" // DefaultBaseImage is the default base image used // TODO: come up with a reasonable solution to digest pinning // https://github.com/moby/moby/issues/43188 const DefaultBaseImage = "docker.io/kindest/base:v20250214-acbabc1a" kind-0.27.0/pkg/build/nodeimage/doc.go000066400000000000000000000012301475376161000174370ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package nodeimage implements functionality to build the kind node image package nodeimage kind-0.27.0/pkg/build/nodeimage/helpers.go000066400000000000000000000032221475376161000203370ustar00rootroot00000000000000/* Copyright 2020 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package nodeimage import ( "path" "regexp" "strings" "sigs.k8s.io/kind/pkg/errors" "sigs.k8s.io/kind/pkg/exec" ) // createFile creates the file at filePath in the container, // ensuring the directory exists and writing contents to the file func createFile(containerCmder exec.Cmder, filePath, contents string) error { // NOTE: the paths inside the container should use the path package // and not filepath (!), we want posixy paths in the linux container, NOT // whatever path format the host uses. For paths on the host we use filepath if err := containerCmder.Command("mkdir", "-p", path.Dir(filePath)).Run(); err != nil { return err } return containerCmder.Command( "cp", "/dev/stdin", filePath, ).SetStdin( strings.NewReader(contents), ).Run() } func findSandboxImage(config string) (string, error) { match := regexp.MustCompile(`sandbox_image\s+=\s+"([^\n]+)"`).FindStringSubmatch(config) if len(match) < 2 { return "", errors.New("failed to parse sandbox_image from config") } return match[1], nil } func dockerBuildOsAndArch(arch string) string { return "linux/" + arch } kind-0.27.0/pkg/build/nodeimage/imageimporter.go000066400000000000000000000051761475376161000215530ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package nodeimage import ( "io" "sigs.k8s.io/kind/pkg/errors" "sigs.k8s.io/kind/pkg/exec" ) type containerdImporter struct { containerCmder exec.Cmder } func newContainerdImporter(containerCmder exec.Cmder) *containerdImporter { return &containerdImporter{ containerCmder: containerCmder, } } func (c *containerdImporter) Prepare() error { if err := c.containerCmder.Command( "bash", "-c", "nohup containerd > /dev/null 2>&1 &", ).Run(); err != nil { return err } return nil } func (c *containerdImporter) WaitForReady() error { // ctr doesn't respect timeouts when the socket doesn't exist // so we'll look for the socket to exist ourselves, THEN attempt ctr info // TODO: we are assuming the socket path, and this is kind of hacky if err := c.containerCmder.Command( "bash", "-c", `set -e # wait for socket to exist for i in {0..3}; do if [ -S /run/containerd/containerd.sock ]; then break fi sleep "$i" done # check healthy ctr info `, ).Run(); err != nil { return errors.Wrap(err, "failed to wait for containerd to become ready") } return nil } func (c *containerdImporter) End() error { return c.containerCmder.Command("pkill", "containerd").Run() } func (c *containerdImporter) Pull(image, platform string) error { return c.containerCmder.Command( "ctr", "--namespace=k8s.io", "content", "fetch", "--platform="+platform, image, ).SetStdout(io.Discard).SetStderr(io.Discard).Run() } func (c *containerdImporter) LoadCommand() exec.Cmd { return c.containerCmder.Command( // TODO: ideally we do not need this in the future. we have fixed at least one image "ctr", "--namespace=k8s.io", "images", "import", "--label=io.cri-containerd.pinned=pinned", "--all-platforms", "--no-unpack", "--digests", "-", ) } func (c *containerdImporter) Tag(src, target string) error { return c.containerCmder.Command( "ctr", "--namespace=k8s.io", "images", "tag", "--force", src, target, ).Run() } func (c *containerdImporter) ListImported() ([]string, error) { return exec.OutputLines(c.containerCmder.Command("ctr", "--namespace=k8s.io", "images", "list", "-q")) } kind-0.27.0/pkg/build/nodeimage/internal/000077500000000000000000000000001475376161000201635ustar00rootroot00000000000000kind-0.27.0/pkg/build/nodeimage/internal/container/000077500000000000000000000000001475376161000221455ustar00rootroot00000000000000kind-0.27.0/pkg/build/nodeimage/internal/container/docker/000077500000000000000000000000001475376161000234145ustar00rootroot00000000000000kind-0.27.0/pkg/build/nodeimage/internal/container/docker/archive.go000066400000000000000000000064771475376161000254020ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package docker contains helpers for working with docker // This package has no stability guarantees whatsoever! package docker import ( "archive/tar" "encoding/json" "fmt" "io" "os" "sigs.k8s.io/kind/pkg/errors" ) // GetArchiveTags obtains a list of "repo:tag" docker image tags from a // given docker image archive (tarball) path // compatible with all known specs: // https://github.com/moby/moby/blob/master/image/spec/v1.md // https://github.com/moby/moby/blob/master/image/spec/v1.1.md // https://github.com/moby/moby/blob/master/image/spec/v1.2.md func GetArchiveTags(path string) ([]string, error) { // open the archive and find the repositories entry f, err := os.Open(path) if err != nil { return nil, err } defer f.Close() tr := tar.NewReader(f) var hdr *tar.Header for { hdr, err = tr.Next() if err == io.EOF { return nil, errors.New("could not find image metadata") } if err != nil { return nil, err } if hdr.Name == "manifest.json" || hdr.Name == "repositories" { break } } // read and parse the tags b, err := io.ReadAll(tr) if err != nil { return nil, err } res := []string{} // parse if hdr.Name == "repositories" { repoTags, err := parseRepositories(b) if err != nil { return nil, err } // convert to tags in the docker CLI sense for repo, tags := range repoTags { for tag := range tags { res = append(res, fmt.Sprintf("%s:%s", repo, tag)) } } } else if hdr.Name == "manifest.json" { manifest, err := parseDockerV1Manifest(b) if err != nil { return nil, err } res = append(res, manifest[0].RepoTags...) } return res, nil } // archiveRepositories represents repository:tag:ref // // https://github.com/moby/moby/blob/master/image/spec/v1.md // https://github.com/moby/moby/blob/master/image/spec/v1.1.md // https://github.com/moby/moby/blob/master/image/spec/v1.2.md type archiveRepositories map[string]map[string]string // https://github.com/moby/moby/blob/master/image/spec/v1.2.md#combined-image-json--filesystem-changeset-format type metadataEntry struct { Config string `json:"Config"` RepoTags []string `json:"RepoTags"` Layers []string `json:"Layers"` } // returns repository:tag:ref func parseRepositories(data []byte) (archiveRepositories, error) { var repoTags archiveRepositories if err := json.Unmarshal(data, &repoTags); err != nil { return nil, err } return repoTags, nil } // parseDockerV1Manifest parses Docker Image Spec v1 manifest (not OCI Image Spec manifest) // https://github.com/moby/moby/blob/v20.10.22/image/spec/v1.2.md#combined-image-json--filesystem-changeset-format func parseDockerV1Manifest(data []byte) ([]metadataEntry, error) { var entries []metadataEntry if err := json.Unmarshal(data, &entries); err != nil { return nil, err } return entries, nil } kind-0.27.0/pkg/build/nodeimage/internal/container/docker/doc.go000066400000000000000000000012751475376161000245150ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package docker contains helpers for working with docker // This package has no stability guarantees whatsoever! package docker kind-0.27.0/pkg/build/nodeimage/internal/container/docker/exec.go000066400000000000000000000056461475376161000247020ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package docker import ( "context" "io" "sigs.k8s.io/kind/pkg/exec" ) // containerCmder implements exec.Cmder for docker containers type containerCmder struct { nameOrID string } // ContainerCmder creates a new exec.Cmder against a docker container func ContainerCmder(containerNameOrID string) exec.Cmder { return &containerCmder{ nameOrID: containerNameOrID, } } func (c *containerCmder) Command(command string, args ...string) exec.Cmd { return &containerCmd{ nameOrID: c.nameOrID, command: command, args: args, } } func (c *containerCmder) CommandContext(ctx context.Context, command string, args ...string) exec.Cmd { return &containerCmd{ nameOrID: c.nameOrID, command: command, args: args, ctx: ctx, } } // containerCmd implements exec.Cmd for docker containers type containerCmd struct { nameOrID string // the container name or ID command string args []string env []string stdin io.Reader stdout io.Writer stderr io.Writer ctx context.Context } func (c *containerCmd) Run() error { args := []string{ "exec", // run with privileges so we can remount etc.. // this might not make sense in the most general sense, but it is // important to many kind commands "--privileged", } if c.stdin != nil { args = append(args, "-i", // interactive so we can supply input ) } // set env for _, env := range c.env { args = append(args, "-e", env) } // specify the container and command, after this everything will be // args the command in the container rather than to docker args = append( args, c.nameOrID, // ... against the container c.command, // with the command specified ) args = append( args, // finally, with the caller args c.args..., ) var cmd exec.Cmd if c.ctx != nil { cmd = exec.CommandContext(c.ctx, "docker", args...) } else { cmd = exec.Command("docker", args...) } if c.stdin != nil { cmd.SetStdin(c.stdin) } if c.stderr != nil { cmd.SetStderr(c.stderr) } if c.stdout != nil { cmd.SetStdout(c.stdout) } return cmd.Run() } func (c *containerCmd) SetEnv(env ...string) exec.Cmd { c.env = env return c } func (c *containerCmd) SetStdin(r io.Reader) exec.Cmd { c.stdin = r return c } func (c *containerCmd) SetStdout(w io.Writer) exec.Cmd { c.stdout = w return c } func (c *containerCmd) SetStderr(w io.Writer) exec.Cmd { c.stderr = w return c } kind-0.27.0/pkg/build/nodeimage/internal/container/docker/image.go000066400000000000000000000055741475376161000250400ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package docker import ( "fmt" "strings" "sigs.k8s.io/kind/pkg/errors" "sigs.k8s.io/kind/pkg/exec" ) // SplitImage splits an image into (registry,tag) following these cases: // // alpine -> (alpine, latest) // // alpine:latest -> (alpine, latest) // // alpine@sha256:28ef97b8686a0b5399129e9b763d5b7e5ff03576aa5580d6f4182a49c5fe1913 -> (alpine, latest@sha256:28ef97b8686a0b5399129e9b763d5b7e5ff03576aa5580d6f4182a49c5fe1913) // // alpine:latest@sha256:28ef97b8686a0b5399129e9b763d5b7e5ff03576aa5580d6f4182a49c5fe1913 -> (alpine, latest@sha256:28ef97b8686a0b5399129e9b763d5b7e5ff03576aa5580d6f4182a49c5fe1913) // // NOTE: for our purposes we consider the sha to be part of the tag, and we // resolve the implicit :latest func SplitImage(image string) (registry, tag string, err error) { // we are looking for ':' and '@' firstColon := strings.IndexByte(image, 58) firstAt := strings.IndexByte(image, 64) // there should be a registry before the tag, and @/: should not be the last // character, these cases are assumed not to exist by the rest of the code if firstColon == 0 || firstAt == 0 || firstColon+1 == len(image) || firstAt+1 == len(image) { return "", "", fmt.Errorf("unexpected image: %q", image) } // NOTE: The order of these cases matters // case: alpine if firstColon == -1 && firstAt == -1 { return image, "latest", nil } // case: alpine@sha256:28ef97b8686a0b5399129e9b763d5b7e5ff03576aa5580d6f4182a49c5fe1913 if firstAt != -1 && firstAt < firstColon { return image[:firstAt], "latest" + image[firstAt:], nil } // case: alpine:latest // case: alpine:latest@sha256:28ef97b8686a0b5399129e9b763d5b7e5ff03576aa5580d6f4182a49c5fe1913 return image[:firstColon], image[firstColon+1:], nil } // ImageInspect return low-level information on containers images func ImageInspect(containerNameOrID, format string) ([]string, error) { cmd := exec.Command("docker", "image", "inspect", "-f", format, containerNameOrID, // ... against the container ) return exec.OutputLines(cmd) } // ImageID return the Id of the container image func ImageID(containerNameOrID string) (string, error) { lines, err := ImageInspect(containerNameOrID, "{{ .Id }}") if err != nil { return "", err } if len(lines) != 1 { return "", errors.Errorf("Docker image ID should only be one line, got %d lines", len(lines)) } return lines[0], nil } kind-0.27.0/pkg/build/nodeimage/internal/container/docker/image_test.go000066400000000000000000000100541475376161000260640ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package docker import "testing" func TestSplitImage(t *testing.T) { t.Parallel() /* alpine -> (alpine, latest) alpine:latest -> (alpine, latest) alpine@sha256:28ef97b8686a0b5399129e9b763d5b7e5ff03576aa5580d6f4182a49c5fe1913 -> (alpine, latest@sha256:28ef97b8686a0b5399129e9b763d5b7e5ff03576aa5580d6f4182a49c5fe1913) alpine:latest@sha256:28ef97b8686a0b5399129e9b763d5b7e5ff03576aa5580d6f4182a49c5fe1913 -> (alpine, latest@sha256:28ef97b8686a0b5399129e9b763d5b7e5ff03576aa5580d6f4182a49c5fe1913) */ cases := []struct { Image string ExpectedRegistry string ExpectedTag string ExpectError bool }{ { Image: "alpine", ExpectedRegistry: "alpine", ExpectedTag: "latest", ExpectError: false, }, { Image: "alpine:latest", ExpectedRegistry: "alpine", ExpectedTag: "latest", ExpectError: false, }, { Image: "alpine@sha256:28ef97b8686a0b5399129e9b763d5b7e5ff03576aa5580d6f4182a49c5fe1913", ExpectedRegistry: "alpine", ExpectedTag: "latest@sha256:28ef97b8686a0b5399129e9b763d5b7e5ff03576aa5580d6f4182a49c5fe1913", ExpectError: false, }, { Image: "alpine:latest@sha256:28ef97b8686a0b5399129e9b763d5b7e5ff03576aa5580d6f4182a49c5fe1913", ExpectedRegistry: "alpine", ExpectedTag: "latest@sha256:28ef97b8686a0b5399129e9b763d5b7e5ff03576aa5580d6f4182a49c5fe1913", ExpectError: false, }, { Image: "registry.k8s.io/coredns:1.1.3", ExpectedRegistry: "registry.k8s.io/coredns", ExpectedTag: "1.1.3", ExpectError: false, }, { Image: "registry.k8s.io/coredns:1.1.3@sha256:28ef97b8686a0b5399129e9b763d5b7e5ff03576aa5580d6f4182a49c5fe1913", ExpectedRegistry: "registry.k8s.io/coredns", ExpectedTag: "1.1.3@sha256:28ef97b8686a0b5399129e9b763d5b7e5ff03576aa5580d6f4182a49c5fe1913", ExpectError: false, }, { Image: "registry.k8s.io/coredns:latest@sha256:28ef97b8686a0b5399129e9b763d5b7e5ff03576aa5580d6f4182a49c5fe1913", ExpectedRegistry: "registry.k8s.io/coredns", ExpectedTag: "latest@sha256:28ef97b8686a0b5399129e9b763d5b7e5ff03576aa5580d6f4182a49c5fe1913", ExpectError: false, }, { Image: "registry.k8s.io/coredns@sha256:28ef97b8686a0b5399129e9b763d5b7e5ff03576aa5580d6f4182a49c5fe1913", ExpectedRegistry: "registry.k8s.io/coredns", ExpectedTag: "latest@sha256:28ef97b8686a0b5399129e9b763d5b7e5ff03576aa5580d6f4182a49c5fe1913", ExpectError: false, }, { Image: ":", ExpectedRegistry: "", ExpectedTag: "", ExpectError: true, }, { Image: "@", ExpectedRegistry: "", ExpectedTag: "", ExpectError: true, }, { Image: "a@", ExpectedRegistry: "", ExpectedTag: "", ExpectError: true, }, { Image: "a:", ExpectedRegistry: "", ExpectedTag: "", ExpectError: true, }, } for _, tc := range cases { tc := tc // capture tc t.Run(tc.Image, func(t *testing.T) { t.Parallel() registry, tag, err := SplitImage(tc.Image) if err != nil && !tc.ExpectError { t.Fatalf("Unexpected error: %q", err) } else if err == nil && tc.ExpectError { t.Fatalf("Expected error but got nil") } if registry != tc.ExpectedRegistry { t.Fatalf("ExpectedRegistry %q != %q", tc.ExpectedRegistry, registry) } if tag != tc.ExpectedTag { t.Fatalf("ExpectedTag %q != %q", tc.ExpectedTag, tag) } }) } } kind-0.27.0/pkg/build/nodeimage/internal/container/docker/pull.go000066400000000000000000000025161475376161000247230ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package docker import ( "time" "sigs.k8s.io/kind/pkg/exec" "sigs.k8s.io/kind/pkg/log" ) // Pull pulls an image, retrying up to retries times func Pull(logger log.Logger, image string, platform string, retries int) error { logger.V(1).Infof("Pulling image: %s for platform %s ...", image, platform) err := exec.Command("docker", "pull", "--platform="+platform, image).Run() // retry pulling up to retries times if necessary if err != nil { for i := 0; i < retries; i++ { time.Sleep(time.Second * time.Duration(i+1)) logger.V(1).Infof("Trying again to pull image: %q ... %v", image, err) // TODO(bentheelder): add some backoff / sleep? err = exec.Command("docker", "pull", "--platform="+platform, image).Run() if err == nil { break } } } return err } kind-0.27.0/pkg/build/nodeimage/internal/container/docker/run.go000066400000000000000000000017461475376161000245570ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package docker import ( "sigs.k8s.io/kind/pkg/exec" ) // Run creates a container with "docker run", with some error handling func Run(image string, runArgs []string, containerArgs []string) error { // construct the actual docker run argv args := []string{"run"} args = append(args, runArgs...) args = append(args, image) args = append(args, containerArgs...) cmd := exec.Command("docker", args...) return cmd.Run() } kind-0.27.0/pkg/build/nodeimage/internal/container/docker/save.go000066400000000000000000000014151475376161000247020ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package docker import ( "sigs.k8s.io/kind/pkg/exec" ) // Save saves image to dest, as in `docker save` func Save(image, dest string) error { return exec.Command("docker", "save", "-o", dest, image).Run() } kind-0.27.0/pkg/build/nodeimage/internal/kube/000077500000000000000000000000001475376161000211115ustar00rootroot00000000000000kind-0.27.0/pkg/build/nodeimage/internal/kube/bits.go000066400000000000000000000026101475376161000224000ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package kube // Bits provides the locations of Kubernetes Binaries / Images // needed on the cluster nodes // Implementations should be registered with RegisterNamedBits type Bits interface { // BinaryPaths returns a list of paths to binaries on the host machine that // should be added to PATH in the Node image BinaryPaths() []string // ImagePaths returns a list of paths to image archives to be loaded into // the Node ImagePaths() []string // Version Version() string } // shared real bits implementation for now type bits struct { // computed at build time binaryPaths []string imagePaths []string version string } var _ Bits = &bits{} func (b *bits) BinaryPaths() []string { return b.binaryPaths } func (b *bits) ImagePaths() []string { return b.imagePaths } func (b *bits) Version() string { return b.version } kind-0.27.0/pkg/build/nodeimage/internal/kube/builder.go000066400000000000000000000016421475376161000230710ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package kube // Builder represents and implementation of building Kubernetes // building may constitute downloading a release type Builder interface { // Build returns a Bits and any errors encountered while building Kubernetes. // Some implementations (upstream binaries) may use this step to obtain // an existing build instead Build() (Bits, error) } kind-0.27.0/pkg/build/nodeimage/internal/kube/builder_docker.go000066400000000000000000000102401475376161000244120ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package kube import ( "os" "path/filepath" "strings" "sigs.k8s.io/kind/pkg/errors" "sigs.k8s.io/kind/pkg/exec" "sigs.k8s.io/kind/pkg/log" "sigs.k8s.io/kind/pkg/internal/version" ) // TODO(bentheelder): plumb through arch // dockerBuilder implements Bits for a local docker-ized make / bash build type dockerBuilder struct { kubeRoot string arch string logger log.Logger } var _ Builder = &dockerBuilder{} // NewDockerBuilder returns a new Bits backed by the docker-ized build, // given kubeRoot, the path to the kubernetes source directory func NewDockerBuilder(logger log.Logger, kubeRoot, arch string) (Builder, error) { return &dockerBuilder{ kubeRoot: kubeRoot, arch: arch, logger: logger, }, nil } // Build implements Bits.Build func (b *dockerBuilder) Build() (Bits, error) { // cd to k8s source cwd, err := os.Getwd() if err != nil { return nil, err } // make sure we cd back when done defer func() { // TODO(bentheelder): set return error? _ = os.Chdir(cwd) }() if err := os.Chdir(b.kubeRoot); err != nil { return nil, err } // capture version info sourceVersionRaw, err := sourceVersion(b.kubeRoot) if err != nil { return nil, err } kubeVersion, err := version.ParseSemantic(sourceVersionRaw) if err != nil { return nil, errors.Wrap(err, "failed to parse source version") } makeVars := []string{ // ensure the build isn't especially noisy.. "KUBE_VERBOSE=0", // we don't want to build these images as we don't use them ... "KUBE_BUILD_HYPERKUBE=n", "KUBE_BUILD_CONFORMANCE=n", // build for the host platform "KUBE_BUILD_PLATFORMS=" + dockerBuildOsAndArch(b.arch), } // we will pass through the environment variables, prepending defaults // NOTE: if env are specified multiple times the last one wins // NOTE: currently there are no defaults so this is essentially a deep copy env := append([]string{}, os.Environ()...) // binaries we want to build what := []string{ // binaries we use directly "cmd/kubeadm", "cmd/kubectl", "cmd/kubelet", } // build images + binaries (binaries only on 1.21+) cmd := exec.Command("make", append( []string{ "quick-release-images", "KUBE_EXTRA_WHAT=" + strings.Join(what, " "), }, makeVars..., )..., ).SetEnv(env...) exec.InheritOutput(cmd) if err := cmd.Run(); err != nil { return nil, errors.Wrap(err, "failed to build images") } // KUBE_EXTRA_WHAT added in this commit // https://github.com/kubernetes/kubernetes/commit/35061acc28a666569fdd4d1c8a7693e3c01e14be if kubeVersion.LessThan(version.MustParseSemantic("v1.21.0-beta.1.153+35061acc28a666")) { // on older versions we still need to build binaries separately cmd = exec.Command( "build/run.sh", append( []string{ "make", "all", "WHAT=" + strings.Join(what, " "), }, makeVars..., )..., ).SetEnv(env...) exec.InheritOutput(cmd) if err := cmd.Run(); err != nil { return nil, errors.Wrap(err, "failed to build binaries") } } binDir := filepath.Join(b.kubeRoot, "_output", "dockerized", "bin", "linux", b.arch, ) imageDir := filepath.Join(b.kubeRoot, "_output", "release-images", b.arch, ) return &bits{ binaryPaths: []string{ filepath.Join(binDir, "kubeadm"), filepath.Join(binDir, "kubelet"), filepath.Join(binDir, "kubectl"), }, imagePaths: []string{ filepath.Join(imageDir, "kube-apiserver.tar"), filepath.Join(imageDir, "kube-controller-manager.tar"), filepath.Join(imageDir, "kube-scheduler.tar"), filepath.Join(imageDir, "kube-proxy.tar"), }, version: sourceVersionRaw, }, nil } func dockerBuildOsAndArch(arch string) string { return "linux/" + arch } kind-0.27.0/pkg/build/nodeimage/internal/kube/builder_remote.go000066400000000000000000000113761475376161000244510ustar00rootroot00000000000000/* Copyright 2024 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package kube import ( "context" "fmt" "io" "net" "net/http" "os" "path/filepath" "strings" "time" "sigs.k8s.io/kind/pkg/errors" "sigs.k8s.io/kind/pkg/log" ) type remoteBuilder struct { version string logger log.Logger url string } var _ Builder = &remoteBuilder{} // NewURLBuilder used to specify a complete url to a gzipped tarball func NewURLBuilder(logger log.Logger, url string) (Builder, error) { return &remoteBuilder{ version: "", logger: logger, url: url, }, nil } // NewReleaseBuilder used to specify a release semver and constructs a url to release artifacts func NewReleaseBuilder(logger log.Logger, version, arch string) (Builder, error) { url := "https://dl.k8s.io/" + version + "/kubernetes-server-linux-" + arch + ".tar.gz" return &remoteBuilder{ version: version, logger: logger, url: url, }, nil } // Build implements Bits.Build func (b *remoteBuilder) Build() (Bits, error) { tmpDir, err := os.MkdirTemp(os.TempDir(), "k8s-tar-extract-") if err != nil { return nil, fmt.Errorf("error creating temporary directory for tar extraction: %w", err) } tgzFile := filepath.Join(tmpDir, "kubernetes-"+b.version+"-server-linux-amd64.tar.gz") err = b.downloadURL(b.url, tgzFile) if err != nil { return nil, fmt.Errorf("error downloading file: %w", err) } err = extractTarball(tgzFile, tmpDir, b.logger) if err != nil { return nil, fmt.Errorf("error extracting tgz file: %w", err) } binDir := filepath.Join(tmpDir, "kubernetes/server/bin") contents, err := os.ReadFile(filepath.Join(tmpDir, "kubernetes/version")) // fallback for Kubernetes < v1.31 which doesn't have the version file // this approach only works for release tags as the format happens to match // for pre-release builds the docker tag is mangled and not valid semver if err != nil && os.IsNotExist(err) { b.logger.Warn("WARNING: Using fallback version detection due to missing version file (This command works best with Kubernetes v1.31+)") contents, err = os.ReadFile(filepath.Join(binDir, "kube-apiserver.docker_tag")) } if err != nil { return nil, errors.Wrap(err, "failed to get version") } sourceVersionRaw := strings.TrimSpace(string(contents)) return &bits{ binaryPaths: []string{ filepath.Join(binDir, "kubeadm"), filepath.Join(binDir, "kubelet"), filepath.Join(binDir, "kubectl"), }, imagePaths: []string{ filepath.Join(binDir, "kube-apiserver.tar"), filepath.Join(binDir, "kube-controller-manager.tar"), filepath.Join(binDir, "kube-scheduler.tar"), filepath.Join(binDir, "kube-proxy.tar"), }, version: sourceVersionRaw, }, nil } func (b *remoteBuilder) downloadURL(url string, destPath string) error { output, err := os.Create(destPath) if err != nil { return fmt.Errorf("error creating file for download %q: %v", destPath, err) } defer output.Close() b.logger.V(0).Infof("Downloading %q", url) // Create a client with custom timeouts // to avoid idle downloads to hang the program httpClient := &http.Client{ Transport: &http.Transport{ Proxy: http.ProxyFromEnvironment, DialContext: (&net.Dialer{ Timeout: 30 * time.Second, KeepAlive: 30 * time.Second, }).DialContext, TLSHandshakeTimeout: 10 * time.Second, ResponseHeaderTimeout: 30 * time.Second, IdleConnTimeout: 30 * time.Second, }, } // this will stop slow downloads after 10 minutes // and interrupt reading of the Response.Body ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute) defer cancel() req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return fmt.Errorf("cannot create request: %v", err) } response, err := httpClient.Do(req) if err != nil { return fmt.Errorf("error doing HTTP fetch of %q: %v", url, err) } defer response.Body.Close() if response.StatusCode >= 400 { return fmt.Errorf("error response from %q: HTTP %v", url, response.StatusCode) } start := time.Now() defer func() { b.logger.V(2).Infof("Copying %q to %q took %q", url, destPath, time.Since(start)) }() // TODO: we should add some sort of progress indicator _, err = io.Copy(output, response.Body) if err != nil { return fmt.Errorf("error downloading HTTP content from %q: %v", url, err) } return nil } kind-0.27.0/pkg/build/nodeimage/internal/kube/builder_tarball.go000066400000000000000000000054541475376161000245770ustar00rootroot00000000000000/* Copyright 2024 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package kube import ( "fmt" "os" "path/filepath" "strings" "sigs.k8s.io/kind/pkg/errors" "sigs.k8s.io/kind/pkg/log" ) // TODO(bentheelder): plumb through arch // directoryBuilder implements Bits for a local docker-ized make / bash build type directoryBuilder struct { tarballPath string logger log.Logger } var _ Builder = &directoryBuilder{} // NewTarballBuilder returns a new Bits backed by the docker-ized build, // given kubeRoot, the path to the kubernetes source directory func NewTarballBuilder(logger log.Logger, tarballPath string) (Builder, error) { return &directoryBuilder{ tarballPath: tarballPath, logger: logger, }, nil } // Build implements Bits.Build func (b *directoryBuilder) Build() (Bits, error) { tmpDir, err := os.MkdirTemp(os.TempDir(), "k8s-tar-extract-") if err != nil { return nil, fmt.Errorf("error creating temporary directory for tar extraction: %w", err) } b.logger.V(0).Infof("Extracting %q", b.tarballPath) err = extractTarball(b.tarballPath, tmpDir, b.logger) if err != nil { return nil, fmt.Errorf("error extracting tar file: %w", err) } binDir := filepath.Join(tmpDir, "kubernetes/server/bin") contents, err := os.ReadFile(filepath.Join(tmpDir, "kubernetes/version")) // fallback for Kubernetes < v1.31 which doesn't have the version file // this approach only works for release tags as the format happens to match // for pre-release builds the docker tag is mangled and not valid semver if err != nil && os.IsNotExist(err) { b.logger.Warn("WARNING: Using fallback version detection due to missing version file (This command works best with Kubernetes v1.31+)") contents, err = os.ReadFile(filepath.Join(binDir, "kube-apiserver.docker_tag")) } if err != nil { return nil, errors.Wrap(err, "failed to get version") } sourceVersionRaw := strings.TrimSpace(string(contents)) return &bits{ binaryPaths: []string{ filepath.Join(binDir, "kubeadm"), filepath.Join(binDir, "kubelet"), filepath.Join(binDir, "kubectl"), }, imagePaths: []string{ filepath.Join(binDir, "kube-apiserver.tar"), filepath.Join(binDir, "kube-controller-manager.tar"), filepath.Join(binDir, "kube-scheduler.tar"), filepath.Join(binDir, "kube-proxy.tar"), }, version: sourceVersionRaw, }, nil } kind-0.27.0/pkg/build/nodeimage/internal/kube/doc.go000066400000000000000000000013001475376161000221770ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package kube implements functionality to build Kubernetes for the purposes // of installing into the kind node image package kube kind-0.27.0/pkg/build/nodeimage/internal/kube/source.go000066400000000000000000000062531475376161000227460ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package kube import ( "fmt" "go/build" "os" "path/filepath" "strings" "al.essio.dev/pkg/shellescape" "sigs.k8s.io/kind/pkg/errors" "sigs.k8s.io/kind/pkg/exec" ) // FindSource attempts to locate a kubernetes checkout using go's build package func FindSource() (root string, err error) { // check current working directory first wd, err := os.Getwd() if err != nil { return "", fmt.Errorf("failed to locate kubernetes source, could not current working directory: %w", err) } if probablyKubeDir(wd) { return wd, nil } // then look under GOPATH gopath := build.Default.GOPATH if gopath == "" { return "", errors.New("could not find Kubernetes source under current working directory and GOPATH is not set") } // try k8s.io/kubernetes first (old canonical GOPATH locaation) if dir := filepath.Join(gopath, "src", "k8s.io", "kubernetes"); probablyKubeDir(dir) { return dir, nil } // then try github.com/kubernetes/kubernetes (CI without path_alias set) if dir := filepath.Join(gopath, "src", "github.com", "kubernetes", "kubernetes"); probablyKubeDir(dir) { return dir, nil } return "", fmt.Errorf("could not find Kubernetes source under current working directory or GOPATH=%s", build.Default.GOPATH) } // probablyKubeDir returns true if the dir looks plausibly like a kubernetes // source directory func probablyKubeDir(dir string) bool { // TODO: should we do more checks? // NOTE: go.mod with this line has existed since Kubernetes 1.15 const sentinelLine = "module k8s.io/kubernetes" contents, err := os.ReadFile(filepath.Join(dir, "go.mod")) if err != nil { return false } return strings.Contains(string(contents), sentinelLine) } // sourceVersion the kubernetes git version based on hack/print-workspace-status.sh // the raw version is also returned func sourceVersion(kubeRoot string) (string, error) { // get the version output cmd := exec.Command( "sh", "-c", fmt.Sprintf( "cd %s && hack/print-workspace-status.sh", shellescape.Quote(kubeRoot), ), ) output, err := exec.OutputLines(cmd) if err != nil { return "", err } // parse it, and populate it into _output/git_version version := "" for _, line := range output { parts := strings.SplitN(line, " ", 2) if len(parts) != 2 { return "", errors.Errorf("could not parse kubernetes version: %q", strings.Join(output, "\n")) } if parts[0] == "gitVersion" { version = parts[1] return version, nil } } if version == "" { return "", errors.Errorf("could not obtain kubernetes version: %q", strings.Join(output, "\n")) } return "", errors.Errorf("could not find kubernetes version in output: %q", strings.Join(output, "\n")) } kind-0.27.0/pkg/build/nodeimage/internal/kube/tar.go000066400000000000000000000025631475376161000222340ustar00rootroot00000000000000package kube import ( "archive/tar" "compress/gzip" "fmt" "io" "os" "path/filepath" "sigs.k8s.io/kind/pkg/log" ) // extractTarball takes a gzipped-tarball and extracts the contents into a specified directory func extractTarball(tarPath, destDirectory string, logger log.Logger) (err error) { // Open the tar file f, err := os.Open(tarPath) if err != nil { return fmt.Errorf("opening tarball: %w", err) } defer f.Close() gzipReader, err := gzip.NewReader(f) if err != nil { return err } tr := tar.NewReader(gzipReader) numFiles := 0 for { hdr, err := tr.Next() if err == io.EOF { break } if err != nil { return fmt.Errorf("reading tarfile %s: %w", tarPath, err) } if hdr.FileInfo().IsDir() { continue } if err := os.MkdirAll( filepath.Join(destDirectory, filepath.Dir(hdr.Name)), os.FileMode(0o755), ); err != nil { return fmt.Errorf("creating image directory structure: %w", err) } f, err := os.Create(filepath.Join(destDirectory, hdr.Name)) if err != nil { return fmt.Errorf("creating image layer file: %w", err) } if _, err := io.CopyN(f, tr, hdr.Size); err != nil { f.Close() if err == io.EOF { break } return fmt.Errorf("extracting image data: %w", err) } f.Close() numFiles++ } logger.V(2).Infof("Successfully extracted %d files from image tarball %s", numFiles, tarPath) return err } kind-0.27.0/pkg/build/nodeimage/options.go000066400000000000000000000040341475376161000203720ustar00rootroot00000000000000/* Copyright 2020 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package nodeimage import ( "sigs.k8s.io/kind/pkg/log" ) // Option is a configuration option supplied to Build type Option interface { apply(*buildContext) error } type optionAdapter func(*buildContext) error func (c optionAdapter) apply(o *buildContext) error { return c(o) } // WithImage configures a build to tag the built image with `image` func WithImage(image string) Option { return optionAdapter(func(b *buildContext) error { b.image = image return nil }) } // WithBaseImage configures a build to use `image` as the base image func WithBaseImage(image string) Option { return optionAdapter(func(b *buildContext) error { b.baseImage = image return nil }) } // WithKubeParam sets the path to the Kubernetes source directory (if empty, the path will be autodetected) func WithKubeParam(root string) Option { return optionAdapter(func(b *buildContext) error { b.kubeParam = root return nil }) } // WithLogger sets the logger func WithLogger(logger log.Logger) Option { return optionAdapter(func(b *buildContext) error { b.logger = logger return nil }) } // WithArch sets the architecture to build for func WithArch(arch string) Option { return optionAdapter(func(b *buildContext) error { if arch != "" { b.arch = arch } return nil }) } // WithArch sets the architecture to build for func WithBuildType(buildType string) Option { return optionAdapter(func(b *buildContext) error { if buildType != "" { b.buildType = buildType } return nil }) } kind-0.27.0/pkg/cluster/000077500000000000000000000000001475376161000150015ustar00rootroot00000000000000kind-0.27.0/pkg/cluster/constants/000077500000000000000000000000001475376161000170155ustar00rootroot00000000000000kind-0.27.0/pkg/cluster/constants/constants.go000066400000000000000000000032731475376161000213650ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package constants contains well known constants for kind clusters package constants // DefaultClusterName is the default cluster Context name const DefaultClusterName = "kind" /* node role value constants */ const ( // ControlPlaneNodeRoleValue identifies a node that hosts a Kubernetes // control-plane. // // NOTE: in single node clusters, control-plane nodes act as worker nodes ControlPlaneNodeRoleValue string = "control-plane" // WorkerNodeRoleValue identifies a node that hosts a Kubernetes worker WorkerNodeRoleValue string = "worker" // ExternalLoadBalancerNodeRoleValue identifies a node that hosts an // external load balancer for the API server in HA configurations. // // Please note that `kind` nodes hosting external load balancer are not // kubernetes nodes ExternalLoadBalancerNodeRoleValue string = "external-load-balancer" // ExternalEtcdNodeRoleValue identifies a node that hosts an external-etcd // instance. // // WARNING: this node type is not yet implemented! // // Please note that `kind` nodes hosting external etcd are not // kubernetes nodes ExternalEtcdNodeRoleValue string = "external-etcd" ) kind-0.27.0/pkg/cluster/createoption.go000066400000000000000000000102441475376161000200250ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package cluster import ( "time" "sigs.k8s.io/kind/pkg/apis/config/v1alpha4" internalcreate "sigs.k8s.io/kind/pkg/cluster/internal/create" internalencoding "sigs.k8s.io/kind/pkg/internal/apis/config/encoding" ) // CreateOption is a Provider.Create option type CreateOption interface { apply(*internalcreate.ClusterOptions) error } type createOptionAdapter func(*internalcreate.ClusterOptions) error func (c createOptionAdapter) apply(o *internalcreate.ClusterOptions) error { return c(o) } // CreateWithConfigFile configures the config file path to use func CreateWithConfigFile(path string) CreateOption { return createOptionAdapter(func(o *internalcreate.ClusterOptions) error { var err error o.Config, err = internalencoding.Load(path) return err }) } // CreateWithRawConfig configures the config to use from raw (yaml) bytes func CreateWithRawConfig(raw []byte) CreateOption { return createOptionAdapter(func(o *internalcreate.ClusterOptions) error { var err error o.Config, err = internalencoding.Parse(raw) return err }) } // CreateWithV1Alpha4Config configures the cluster with a v1alpha4 config func CreateWithV1Alpha4Config(config *v1alpha4.Cluster) CreateOption { return createOptionAdapter(func(o *internalcreate.ClusterOptions) error { o.Config = internalencoding.V1Alpha4ToInternal(config) return nil }) } // CreateWithNodeImage overrides the image on all nodes in config // as an easy way to change the Kubernetes version func CreateWithNodeImage(nodeImage string) CreateOption { return createOptionAdapter(func(o *internalcreate.ClusterOptions) error { o.NodeImage = nodeImage return nil }) } // CreateWithRetain disables deletion of nodes and any other cleanup // that would normally occur after a failure to create // This is mainly used for debugging purposes func CreateWithRetain(retain bool) CreateOption { return createOptionAdapter(func(o *internalcreate.ClusterOptions) error { o.Retain = retain return nil }) } // CreateWithWaitForReady configures a maximum wait time for the control plane // node(s) to be ready. By default no waiting is performed func CreateWithWaitForReady(waitTime time.Duration) CreateOption { return createOptionAdapter(func(o *internalcreate.ClusterOptions) error { o.WaitForReady = waitTime return nil }) } // CreateWithKubeconfigPath sets the explicit --kubeconfig path func CreateWithKubeconfigPath(explicitPath string) CreateOption { return createOptionAdapter(func(o *internalcreate.ClusterOptions) error { o.KubeconfigPath = explicitPath return nil }) } // CreateWithStopBeforeSettingUpKubernetes enables skipping setting up // kubernetes (kubeadm init etc.) after creating node containers // This generally shouldn't be used and is only lightly supported, but allows // provisioning node containers for experimentation func CreateWithStopBeforeSettingUpKubernetes(stopBeforeSettingUpKubernetes bool) CreateOption { return createOptionAdapter(func(o *internalcreate.ClusterOptions) error { o.StopBeforeSettingUpKubernetes = stopBeforeSettingUpKubernetes return nil }) } // CreateWithDisplayUsage enables displaying usage if displayUsage is true func CreateWithDisplayUsage(displayUsage bool) CreateOption { return createOptionAdapter(func(o *internalcreate.ClusterOptions) error { o.DisplayUsage = displayUsage return nil }) } // CreateWithDisplaySalutation enables display a salutation at the end of create // cluster if displaySalutation is true func CreateWithDisplaySalutation(displaySalutation bool) CreateOption { return createOptionAdapter(func(o *internalcreate.ClusterOptions) error { o.DisplaySalutation = displaySalutation return nil }) } kind-0.27.0/pkg/cluster/doc.go000066400000000000000000000012261475376161000160760ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package cluster implements kind kubernetes-in-docker cluster management package cluster kind-0.27.0/pkg/cluster/internal/000077500000000000000000000000001475376161000166155ustar00rootroot00000000000000kind-0.27.0/pkg/cluster/internal/create/000077500000000000000000000000001475376161000200605ustar00rootroot00000000000000kind-0.27.0/pkg/cluster/internal/create/actions/000077500000000000000000000000001475376161000215205ustar00rootroot00000000000000kind-0.27.0/pkg/cluster/internal/create/actions/action.go000066400000000000000000000040651475376161000233310ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package actions import ( "sync" "sigs.k8s.io/kind/pkg/cluster/nodes" "sigs.k8s.io/kind/pkg/internal/apis/config" "sigs.k8s.io/kind/pkg/internal/cli" "sigs.k8s.io/kind/pkg/log" "sigs.k8s.io/kind/pkg/cluster/internal/providers" ) // Action defines a step of bringing up a kind cluster after initial node // container creation type Action interface { Execute(ctx *ActionContext) error } // ActionContext is data supplied to all actions type ActionContext struct { Logger log.Logger Status *cli.Status Config *config.Cluster Provider providers.Provider cache *cachedData } // NewActionContext returns a new ActionContext func NewActionContext( logger log.Logger, status *cli.Status, provider providers.Provider, cfg *config.Cluster, ) *ActionContext { return &ActionContext{ Logger: logger, Status: status, Provider: provider, Config: cfg, cache: &cachedData{}, } } type cachedData struct { mu sync.RWMutex nodes []nodes.Node } func (cd *cachedData) getNodes() []nodes.Node { cd.mu.RLock() defer cd.mu.RUnlock() return cd.nodes } func (cd *cachedData) setNodes(n []nodes.Node) { cd.mu.Lock() defer cd.mu.Unlock() cd.nodes = n } // Nodes returns the list of cluster nodes, this is a cached call func (ac *ActionContext) Nodes() ([]nodes.Node, error) { cachedNodes := ac.cache.getNodes() if cachedNodes != nil { return cachedNodes, nil } n, err := ac.Provider.ListNodes(ac.Config.Name) if err != nil { return nil, err } ac.cache.setNodes(n) return n, nil } kind-0.27.0/pkg/cluster/internal/create/actions/config/000077500000000000000000000000001475376161000227655ustar00rootroot00000000000000kind-0.27.0/pkg/cluster/internal/create/actions/config/config.go000066400000000000000000000226111475376161000245630ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package config implements the kubeadm config action package config import ( "bytes" "fmt" "net" "strings" "sigs.k8s.io/kind/pkg/cluster/constants" "sigs.k8s.io/kind/pkg/cluster/nodes" "sigs.k8s.io/kind/pkg/errors" "sigs.k8s.io/kind/pkg/cluster/internal/create/actions" "sigs.k8s.io/kind/pkg/cluster/internal/kubeadm" "sigs.k8s.io/kind/pkg/cluster/internal/providers/common" "sigs.k8s.io/kind/pkg/cluster/nodeutils" "sigs.k8s.io/kind/pkg/internal/apis/config" "sigs.k8s.io/kind/pkg/internal/patch" ) // Action implements action for creating the node config files type Action struct{} // NewAction returns a new action for creating the config files func NewAction() actions.Action { return &Action{} } // Execute runs the action func (a *Action) Execute(ctx *actions.ActionContext) error { ctx.Status.Start("Writing configuration 📜") defer ctx.Status.End(false) providerInfo, err := ctx.Provider.Info() if err != nil { return err } allNodes, err := ctx.Nodes() if err != nil { return err } controlPlaneEndpoint, err := ctx.Provider.GetAPIServerInternalEndpoint(ctx.Config.Name) if err != nil { return err } // create kubeadm init config fns := []func() error{} provider := fmt.Sprintf("%s", ctx.Provider) configData := kubeadm.ConfigData{ NodeProvider: provider, ClusterName: ctx.Config.Name, ControlPlaneEndpoint: controlPlaneEndpoint, APIBindPort: common.APIServerInternalPort, APIServerAddress: ctx.Config.Networking.APIServerAddress, Token: kubeadm.Token, PodSubnet: ctx.Config.Networking.PodSubnet, KubeProxyMode: string(ctx.Config.Networking.KubeProxyMode), ServiceSubnet: ctx.Config.Networking.ServiceSubnet, ControlPlane: true, IPFamily: ctx.Config.Networking.IPFamily, FeatureGates: ctx.Config.FeatureGates, RuntimeConfig: ctx.Config.RuntimeConfig, RootlessProvider: providerInfo.Rootless, } kubeadmConfigPlusPatches := func(node nodes.Node, data kubeadm.ConfigData) func() error { return func() error { data.NodeName = node.String() kubeadmConfig, err := getKubeadmConfig(ctx.Config, data, node, provider) if err != nil { // TODO(bentheelder): logging here return errors.Wrap(err, "failed to generate kubeadm config content") } ctx.Logger.V(2).Infof("Using the following kubeadm config for node %s:\n%s", node.String(), kubeadmConfig) return writeKubeadmConfig(kubeadmConfig, node) } } // create the kubeadm join configuration for the kubernetes cluster nodes only kubeNodes, err := nodeutils.InternalNodes(allNodes) if err != nil { return err } for _, node := range kubeNodes { node := node // capture loop variable configData := configData // copy config data fns = append(fns, kubeadmConfigPlusPatches(node, configData)) } // Create the kubeadm config in all nodes concurrently if err := errors.UntilErrorConcurrent(fns); err != nil { return err } // if we have containerd config, patch all the nodes concurrently if len(ctx.Config.ContainerdConfigPatches) > 0 || len(ctx.Config.ContainerdConfigPatchesJSON6902) > 0 { fns := make([]func() error, len(kubeNodes)) for i, node := range kubeNodes { node := node // capture loop variable fns[i] = func() error { // read and patch the config const containerdConfigPath = "/etc/containerd/config.toml" var buff bytes.Buffer if err := node.Command("cat", containerdConfigPath).SetStdout(&buff).Run(); err != nil { return errors.Wrap(err, "failed to read containerd config from node") } patched, err := patch.TOML(buff.String(), ctx.Config.ContainerdConfigPatches, ctx.Config.ContainerdConfigPatchesJSON6902) if err != nil { return errors.Wrap(err, "failed to patch containerd config") } if err := nodeutils.WriteFile(node, containerdConfigPath, patched); err != nil { return errors.Wrap(err, "failed to write patched containerd config") } // restart containerd now that we've re-configured it // skip if containerd is not running if err := node.Command("bash", "-c", `! pgrep --exact containerd || systemctl restart containerd`).Run(); err != nil { return errors.Wrap(err, "failed to restart containerd after patching config") } return nil } } if err := errors.UntilErrorConcurrent(fns); err != nil { return err } } // mark success ctx.Status.End(true) return nil } // getKubeadmConfig generates the kubeadm config contents for the cluster // by running data through the template and applying patches as needed. func getKubeadmConfig(cfg *config.Cluster, data kubeadm.ConfigData, node nodes.Node, provider string) (path string, err error) { kubeVersion, err := nodeutils.KubeVersion(node) if err != nil { // TODO(bentheelder): logging here return "", errors.Wrap(err, "failed to get kubernetes version from node") } data.KubernetesVersion = kubeVersion // TODO: gross hack! // identify node in config by matching name (since these are named in order) // we should really just streamline the bootstrap code and maintain // this mapping ... something for the next major refactor var configNode *config.Node namer := common.MakeNodeNamer("") for i := range cfg.Nodes { n := &cfg.Nodes[i] nodeSuffix := namer(string(n.Role)) if strings.HasSuffix(node.String(), nodeSuffix) { configNode = n } } if configNode == nil { return "", errors.Errorf("failed to match node %q to config", node.String()) } // get the node ip address nodeAddress, nodeAddressIPv6, err := node.IP() if err != nil { return "", errors.Wrap(err, "failed to get IP for node") } data.NodeAddress = nodeAddress // configure the right protocol addresses if cfg.Networking.IPFamily == config.IPv6Family || cfg.Networking.IPFamily == config.DualStackFamily { if ip := net.ParseIP(nodeAddressIPv6); ip.To16() == nil { return "", errors.Errorf("failed to get IPv6 address for node %s; is %s configured to use IPv6 correctly?", node.String(), provider) } data.NodeAddress = nodeAddressIPv6 if cfg.Networking.IPFamily == config.DualStackFamily { // order matters since the nodeAddress will be used later to configure the apiserver advertise address // Ref: #2484 primaryServiceSubnet := strings.Split(cfg.Networking.ServiceSubnet, ",")[0] ip, _, err := net.ParseCIDR(primaryServiceSubnet) if err != nil { return "", fmt.Errorf("failed to parse primary Service Subnet %s (%s): %w", primaryServiceSubnet, cfg.Networking.ServiceSubnet, err) } if ip.To4() != nil { data.NodeAddress = fmt.Sprintf("%s,%s", nodeAddress, nodeAddressIPv6) } else { data.NodeAddress = fmt.Sprintf("%s,%s", nodeAddressIPv6, nodeAddress) } } } // configure the node labels if len(configNode.Labels) > 0 { data.NodeLabels = hashMapLabelsToCommaSeparatedLabels(configNode.Labels) } // set the node role data.ControlPlane = string(configNode.Role) == constants.ControlPlaneNodeRoleValue // generate the config contents cf, err := kubeadm.Config(data) if err != nil { return "", err } clusterPatches, clusterJSONPatches := allPatchesFromConfig(cfg) // apply cluster-level patches first patchedConfig, err := patch.KubeYAML(cf, clusterPatches, clusterJSONPatches) if err != nil { return "", err } // if needed, apply current node's patches if len(configNode.KubeadmConfigPatches) > 0 || len(configNode.KubeadmConfigPatchesJSON6902) > 0 { patchedConfig, err = patch.KubeYAML(patchedConfig, configNode.KubeadmConfigPatches, configNode.KubeadmConfigPatchesJSON6902) if err != nil { return "", err } } // fix all the patches to have name metadata matching the generated config return removeMetadata(patchedConfig), nil } // trims out the metadata.name we put in the config for kustomize matching, // kubeadm will complain about this otherwise func removeMetadata(kustomized string) string { return strings.Replace( kustomized, `metadata: name: config `, "", -1, ) } func allPatchesFromConfig(cfg *config.Cluster) (patches []string, jsonPatches []config.PatchJSON6902) { return cfg.KubeadmConfigPatches, cfg.KubeadmConfigPatchesJSON6902 } // writeKubeadmConfig writes the kubeadm configuration in the specified node func writeKubeadmConfig(kubeadmConfig string, node nodes.Node) error { // copy the config to the node if err := nodeutils.WriteFile(node, "/kind/kubeadm.conf", kubeadmConfig); err != nil { // TODO(bentheelder): logging here return errors.Wrap(err, "failed to copy kubeadm config to node") } return nil } // hashMapLabelsToCommaSeparatedLabels converts labels in hashmap form to labels in a comma-separated string form like "key1=value1,key2=value2" func hashMapLabelsToCommaSeparatedLabels(labels map[string]string) string { output := "" for key, value := range labels { output += fmt.Sprintf("%s=%s,", key, value) } return strings.TrimSuffix(output, ",") // remove the last character (comma) in the output string } kind-0.27.0/pkg/cluster/internal/create/actions/installcni/000077500000000000000000000000001475376161000236605ustar00rootroot00000000000000kind-0.27.0/pkg/cluster/internal/create/actions/installcni/cni.go000066400000000000000000000076661475376161000247770ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package installcni implements the install CNI action package installcni import ( "bytes" "strings" "github.com/google/safetext/yamltemplate" "sigs.k8s.io/kind/pkg/errors" "sigs.k8s.io/kind/pkg/internal/apis/config" "sigs.k8s.io/kind/pkg/cluster/internal/create/actions" "sigs.k8s.io/kind/pkg/cluster/nodeutils" "sigs.k8s.io/kind/pkg/internal/patch" ) type action struct{} // NewAction returns a new action for installing default CNI func NewAction() actions.Action { return &action{} } // Execute runs the action func (a *action) Execute(ctx *actions.ActionContext) error { ctx.Status.Start("Installing CNI 🔌") defer ctx.Status.End(false) allNodes, err := ctx.Nodes() if err != nil { return err } // get the target node for this task controlPlanes, err := nodeutils.ControlPlaneNodes(allNodes) if err != nil { return err } node := controlPlanes[0] // kind expects at least one always // read the manifest from the node var raw bytes.Buffer if err := node.Command("cat", "/kind/manifests/default-cni.yaml").SetStdout(&raw).Run(); err != nil { return errors.Wrap(err, "failed to read CNI manifest") } manifest := raw.String() // TODO: remove this check? // backwards compatibility for mounting your own manifest file to the default // location // NOTE: this is intentionally undocumented, as an internal implementation // detail. Going forward users should disable the default CNI and install // their own, or use the default. The internal templating mechanism is // not intended for external usage and is unstable. if strings.Contains(manifest, "would you kindly template this file") { t, err := yamltemplate.New("cni-manifest").Parse(manifest) if err != nil { return errors.Wrap(err, "failed to parse CNI manifest template") } var out bytes.Buffer err = t.Execute(&out, &struct { PodSubnet string }{ PodSubnet: ctx.Config.Networking.PodSubnet, }) if err != nil { return errors.Wrap(err, "failed to execute CNI manifest template") } manifest = out.String() } // NOTE: this is intentionally undocumented, as an internal implementation // detail. Going forward users should disable the default CNI and install // their own, or use the default. The internal templating mechanism is // not intended for external usage and is unstable. if strings.Contains(manifest, "would you kindly patch this file") { // Add the controlplane endpoint so kindnet doesn´t have to wait for kube-proxy controlPlaneEndpoint, err := ctx.Provider.GetAPIServerInternalEndpoint(ctx.Config.Name) if err != nil { return err } patchValue := ` - op: add path: /spec/template/spec/containers/0/env/- value: name: CONTROL_PLANE_ENDPOINT value: ` + controlPlaneEndpoint controlPlanePatch6902 := config.PatchJSON6902{ Group: "apps", Version: "v1", Kind: "DaemonSet", Patch: patchValue, } patchedConfig, err := patch.KubeYAML(manifest, nil, []config.PatchJSON6902{controlPlanePatch6902}) if err != nil { return err } manifest = patchedConfig } ctx.Logger.V(5).Infof("Using the following Kindnetd config:\n%s", manifest) // install the manifest if err := node.Command( "kubectl", "create", "--kubeconfig=/etc/kubernetes/admin.conf", "-f", "-", ).SetStdin(strings.NewReader(manifest)).Run(); err != nil { return errors.Wrap(err, "failed to apply overlay network") } // mark success ctx.Status.End(true) return nil } kind-0.27.0/pkg/cluster/internal/create/actions/installstorage/000077500000000000000000000000001475376161000245535ustar00rootroot00000000000000kind-0.27.0/pkg/cluster/internal/create/actions/installstorage/storage.go000066400000000000000000000053541475376161000265550ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package installstorage implements the an action to install a default // storageclass package installstorage import ( "bytes" "strings" "sigs.k8s.io/kind/pkg/cluster/nodes" "sigs.k8s.io/kind/pkg/errors" "sigs.k8s.io/kind/pkg/log" "sigs.k8s.io/kind/pkg/cluster/internal/create/actions" "sigs.k8s.io/kind/pkg/cluster/nodeutils" ) type action struct{} // NewAction returns a new action for installing storage func NewAction() actions.Action { return &action{} } // Execute runs the action func (a *action) Execute(ctx *actions.ActionContext) error { ctx.Status.Start("Installing StorageClass 💾") defer ctx.Status.End(false) allNodes, err := ctx.Nodes() if err != nil { return err } // get the target node for this task controlPlanes, err := nodeutils.ControlPlaneNodes(allNodes) if err != nil { return err } node := controlPlanes[0] // kind expects at least one always // add the default storage class if err := addDefaultStorage(ctx.Logger, node); err != nil { return errors.Wrap(err, "failed to add default storage class") } // mark success ctx.Status.End(true) return nil } // legacy default storage class // we need this for e2es (StatefulSet) // newer kind images ship a storage driver manifest const defaultStorageManifest = `# host-path based default storage class apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: namespace: kube-system name: standard annotations: storageclass.kubernetes.io/is-default-class: "true" provisioner: kubernetes.io/host-path` func addDefaultStorage(logger log.Logger, controlPlane nodes.Node) error { // start with fallback default, and then try to get the newer kind node // storage manifest if present manifest := defaultStorageManifest var raw bytes.Buffer if err := controlPlane.Command("cat", "/kind/manifests/default-storage.yaml").SetStdout(&raw).Run(); err != nil { logger.Warn("Could not read storage manifest, falling back on old k8s.io/host-path default ...") } else { manifest = raw.String() } // apply the manifest in := strings.NewReader(manifest) cmd := controlPlane.Command( "kubectl", "--kubeconfig=/etc/kubernetes/admin.conf", "apply", "-f", "-", ) cmd.SetStdin(in) return cmd.Run() } kind-0.27.0/pkg/cluster/internal/create/actions/kubeadminit/000077500000000000000000000000001475376161000240145ustar00rootroot00000000000000kind-0.27.0/pkg/cluster/internal/create/actions/kubeadminit/init.go000066400000000000000000000136731475376161000253200ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package kubeadminit implements the kubeadm init action package kubeadminit import ( "strings" "sigs.k8s.io/kind/pkg/errors" "sigs.k8s.io/kind/pkg/exec" "sigs.k8s.io/kind/pkg/cluster/nodeutils" "sigs.k8s.io/kind/pkg/cluster/internal/create/actions" "sigs.k8s.io/kind/pkg/internal/apis/config" "sigs.k8s.io/kind/pkg/internal/version" ) // kubeadmInitAction implements action for executing the kubeadm init // and a set of default post init operations like e.g. install the // CNI network plugin. type action struct { skipKubeProxy bool } // NewAction returns a new action for kubeadm init func NewAction(cfg *config.Cluster) actions.Action { return &action{skipKubeProxy: cfg.Networking.KubeProxyMode == config.NoneProxyMode} } // Execute runs the action func (a *action) Execute(ctx *actions.ActionContext) error { ctx.Status.Start("Starting control-plane 🕹️") defer ctx.Status.End(false) allNodes, err := ctx.Nodes() if err != nil { return err } // get the target node for this task // TODO: eliminate the concept of bootstrapcontrolplane node entirely // outside this method node, err := nodeutils.BootstrapControlPlaneNode(allNodes) if err != nil { return err } kubeVersionStr, err := nodeutils.KubeVersion(node) if err != nil { return errors.Wrap(err, "failed to get kubernetes version from node") } kubeVersion, err := version.ParseGeneric(kubeVersionStr) if err != nil { return errors.Wrapf(err, "failed to parse kubernetes version %q", kubeVersionStr) } args := []string{ // init because this is the control plane node "init", // specify our generated config file "--config=/kind/kubeadm.conf", "--skip-token-print", // increase verbosity for debugging "--v=6", } // Newer versions set this in the config file. if kubeVersion.LessThan(version.MustParseSemantic("v1.23.0")) { // Skip preflight to avoid pulling images. // Kind pre-pulls images and preflight may conflict with that. skipPhases := "preflight" if a.skipKubeProxy { skipPhases += ",addon/kube-proxy" } args = append(args, "--skip-phases="+skipPhases) } // run kubeadm cmd := node.Command("kubeadm", args...) lines, err := exec.CombinedOutputLines(cmd) ctx.Logger.V(3).Info(strings.Join(lines, "\n")) if err != nil { return errors.Wrap(err, "failed to init node with kubeadm") } // copy some files to the other control plane nodes otherControlPlanes, err := nodeutils.SecondaryControlPlaneNodes(allNodes) if err != nil { return err } for _, otherNode := range otherControlPlanes { for _, file := range []string{ // copy over admin config so we can use any control plane to get it later "/etc/kubernetes/admin.conf", // copy over certs "/etc/kubernetes/pki/ca.crt", "/etc/kubernetes/pki/ca.key", "/etc/kubernetes/pki/front-proxy-ca.crt", "/etc/kubernetes/pki/front-proxy-ca.key", "/etc/kubernetes/pki/sa.pub", "/etc/kubernetes/pki/sa.key", // TODO: if we gain external etcd support these will be // handled differently "/etc/kubernetes/pki/etcd/ca.crt", "/etc/kubernetes/pki/etcd/ca.key", } { if err := nodeutils.CopyNodeToNode(node, otherNode, file); err != nil { return errors.Wrap(err, "failed to copy admin kubeconfig") } } } // if we are only provisioning one node, remove the control plane taint // https://kubernetes.io/docs/setup/independent/create-cluster-kubeadm/#master-isolation if len(allNodes) == 1 { // TODO: Once kubeadm 1.23 is no longer supported remove the <1.24 handling. // TODO: Once kubeadm 1.24 is no longer supported remove the <1.25 handling. // https://github.com/kubernetes-sigs/kind/issues/1699 rawVersion, err := nodeutils.KubeVersion(node) if err != nil { return errors.Wrap(err, "failed to get Kubernetes version from node") } kubeVersion, err := version.ParseSemantic(rawVersion) if err != nil { return errors.Wrap(err, "could not parse Kubernetes version") } var taints []string if kubeVersion.LessThan(version.MustParseSemantic("v1.24.0-alpha.1.592+370031cadac624")) { // for versions older than 1.24 prerelease remove only the old taint taints = []string{"node-role.kubernetes.io/master-"} } else if kubeVersion.LessThan(version.MustParseSemantic("v1.25.0-alpha.0.557+84c8afeba39ec9")) { // for versions between 1.24 and 1.25 prerelease remove both the old and new taint taints = []string{"node-role.kubernetes.io/control-plane-", "node-role.kubernetes.io/master-"} } else { // for any newer version only remove the new taint taints = []string{"node-role.kubernetes.io/control-plane-"} } taintArgs := []string{"--kubeconfig=/etc/kubernetes/admin.conf", "taint", "nodes", "--all"} taintArgs = append(taintArgs, taints...) if err := node.Command( "kubectl", taintArgs..., ).Run(); err != nil { return errors.Wrap(err, "failed to remove control plane taint") } } // Kubeadm will add `node.kubernetes.io/exclude-from-external-load-balancers` on control plane nodes. // For single node clusters, this means we cannot have a load balancer at all (MetalLB, etc), so remove the label. if len(allNodes) == 1 { labelArgs := []string{"--kubeconfig=/etc/kubernetes/admin.conf", "label", "nodes", "--all", "node.kubernetes.io/exclude-from-external-load-balancers-"} if err := node.Command( "kubectl", labelArgs..., ).Run(); err != nil { return errors.Wrap(err, "failed to remove control plane load balancer label") } } // mark success ctx.Status.End(true) return nil } kind-0.27.0/pkg/cluster/internal/create/actions/kubeadmjoin/000077500000000000000000000000001475376161000240105ustar00rootroot00000000000000kind-0.27.0/pkg/cluster/internal/create/actions/kubeadmjoin/join.go000066400000000000000000000077661475376161000253160ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package kubeadmjoin implements the kubeadm join action package kubeadmjoin import ( "strings" "sigs.k8s.io/kind/pkg/cluster/constants" "sigs.k8s.io/kind/pkg/cluster/nodes" "sigs.k8s.io/kind/pkg/errors" "sigs.k8s.io/kind/pkg/exec" "sigs.k8s.io/kind/pkg/internal/version" "sigs.k8s.io/kind/pkg/log" "sigs.k8s.io/kind/pkg/cluster/nodeutils" "sigs.k8s.io/kind/pkg/cluster/internal/create/actions" ) // Action implements action for creating the kubeadm join // and deploying it on the bootstrap control-plane node. type Action struct{} // NewAction returns a new action for creating the kubeadm jion func NewAction() actions.Action { return &Action{} } // Execute runs the action func (a *Action) Execute(ctx *actions.ActionContext) error { allNodes, err := ctx.Nodes() if err != nil { return err } // join secondary control plane nodes if any secondaryControlPlanes, err := nodeutils.SecondaryControlPlaneNodes(allNodes) if err != nil { return err } if len(secondaryControlPlanes) > 0 { if err := joinSecondaryControlPlanes(ctx, secondaryControlPlanes); err != nil { return err } } // then join worker nodes if any workers, err := nodeutils.SelectNodesByRole(allNodes, constants.WorkerNodeRoleValue) if err != nil { return err } if len(workers) > 0 { if err := joinWorkers(ctx, workers); err != nil { return err } } return nil } func joinSecondaryControlPlanes( ctx *actions.ActionContext, secondaryControlPlanes []nodes.Node, ) error { ctx.Status.Start("Joining more control-plane nodes 🎮") defer ctx.Status.End(false) // TODO(bentheelder): it's too bad we can't do this concurrently // (this is not safe currently) for _, node := range secondaryControlPlanes { node := node // capture loop variable if err := runKubeadmJoin(ctx.Logger, node); err != nil { return err } } ctx.Status.End(true) return nil } func joinWorkers( ctx *actions.ActionContext, workers []nodes.Node, ) error { ctx.Status.Start("Joining worker nodes 🚜") defer ctx.Status.End(false) // create the workers concurrently fns := []func() error{} for _, node := range workers { node := node // capture loop variable fns = append(fns, func() error { return runKubeadmJoin(ctx.Logger, node) }) } if err := errors.UntilErrorConcurrent(fns); err != nil { return err } ctx.Status.End(true) return nil } // runKubeadmJoin executes kubeadm join command func runKubeadmJoin(logger log.Logger, node nodes.Node) error { kubeVersionStr, err := nodeutils.KubeVersion(node) if err != nil { return errors.Wrap(err, "failed to get kubernetes version from node") } kubeVersion, err := version.ParseGeneric(kubeVersionStr) if err != nil { return errors.Wrapf(err, "failed to parse kubernetes version %q", kubeVersionStr) } args := []string{ "join", // the join command uses the config file generated in a well known location "--config", "/kind/kubeadm.conf", // increase verbosity for debugging "--v=6", } // Newer versions set this in the config file. if kubeVersion.LessThan(version.MustParseSemantic("v1.23.0")) { // Skip preflight to avoid pulling images. // Kind pre-pulls images and preflight may conflict with that. args = append(args, "--skip-phases=preflight") } // run kubeadm join cmd := node.Command("kubeadm", args...) lines, err := exec.CombinedOutputLines(cmd) logger.V(3).Info(strings.Join(lines, "\n")) if err != nil { return errors.Wrap(err, "failed to join node with kubeadm") } return nil } kind-0.27.0/pkg/cluster/internal/create/actions/loadbalancer/000077500000000000000000000000001475376161000241275ustar00rootroot00000000000000kind-0.27.0/pkg/cluster/internal/create/actions/loadbalancer/loadbalancer.go000066400000000000000000000057301475376161000270720ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package loadbalancer implements the load balancer configuration action package loadbalancer import ( "fmt" "sigs.k8s.io/kind/pkg/cluster/constants" "sigs.k8s.io/kind/pkg/errors" "sigs.k8s.io/kind/pkg/internal/apis/config" "sigs.k8s.io/kind/pkg/cluster/internal/create/actions" "sigs.k8s.io/kind/pkg/cluster/internal/loadbalancer" "sigs.k8s.io/kind/pkg/cluster/internal/providers/common" "sigs.k8s.io/kind/pkg/cluster/nodeutils" ) // Action implements and action for configuring and starting the // external load balancer in front of the control-plane nodes. type Action struct{} // NewAction returns a new Action for configuring the load balancer func NewAction() actions.Action { return &Action{} } // Execute runs the action func (a *Action) Execute(ctx *actions.ActionContext) error { allNodes, err := ctx.Nodes() if err != nil { return err } // identify external load balancer node loadBalancerNode, err := nodeutils.ExternalLoadBalancerNode(allNodes) if err != nil { return err } // if there's no loadbalancer we're done if loadBalancerNode == nil { return nil } // otherwise notify the user ctx.Status.Start("Configuring the external load balancer ⚖️") defer ctx.Status.End(false) // collect info about the existing controlplane nodes var backendServers = map[string]string{} controlPlaneNodes, err := nodeutils.SelectNodesByRole( allNodes, constants.ControlPlaneNodeRoleValue, ) if err != nil { return err } for _, n := range controlPlaneNodes { backendServers[n.String()] = fmt.Sprintf("%s:%d", n.String(), common.APIServerInternalPort) } // create loadbalancer config data loadbalancerConfig, err := loadbalancer.Config(&loadbalancer.ConfigData{ ControlPlanePort: common.APIServerInternalPort, BackendServers: backendServers, IPv6: ctx.Config.Networking.IPFamily == config.IPv6Family, }) if err != nil { return errors.Wrap(err, "failed to generate loadbalancer config data") } // create loadbalancer config on the node if err := nodeutils.WriteFile(loadBalancerNode, loadbalancer.ConfigPath, loadbalancerConfig); err != nil { // TODO: logging here return errors.Wrap(err, "failed to copy loadbalancer config to node") } // reload the config. haproxy will reload on SIGHUP if err := loadBalancerNode.Command("kill", "-s", "HUP", "1").Run(); err != nil { return errors.Wrap(err, "failed to reload loadbalancer") } ctx.Status.End(true) return nil } kind-0.27.0/pkg/cluster/internal/create/actions/waitforready/000077500000000000000000000000001475376161000242205ustar00rootroot00000000000000kind-0.27.0/pkg/cluster/internal/create/actions/waitforready/waitforready.go000066400000000000000000000103051475376161000272460ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package waitforready implements the wait for ready action package waitforready import ( "fmt" "strings" "time" "sigs.k8s.io/kind/pkg/cluster/internal/create/actions" "sigs.k8s.io/kind/pkg/cluster/nodes" "sigs.k8s.io/kind/pkg/cluster/nodeutils" "sigs.k8s.io/kind/pkg/errors" "sigs.k8s.io/kind/pkg/exec" "sigs.k8s.io/kind/pkg/internal/version" ) // Action implements an action for waiting for the cluster to be ready type Action struct { waitTime time.Duration } // NewAction returns a new action for waiting for the cluster to be ready func NewAction(waitTime time.Duration) actions.Action { return &Action{ waitTime: waitTime, } } // Execute runs the action func (a *Action) Execute(ctx *actions.ActionContext) error { // skip entirely if the wait time is 0 if a.waitTime == time.Duration(0) { return nil } ctx.Status.Start( fmt.Sprintf( "Waiting ≤ %s for control-plane = Ready ⏳", formatDuration(a.waitTime), ), ) allNodes, err := ctx.Nodes() if err != nil { return err } // get a control plane node to use to check cluster status controlPlanes, err := nodeutils.ControlPlaneNodes(allNodes) if err != nil { return err } node := controlPlanes[0] // kind expects at least one always // Wait for the nodes to reach Ready status. startTime := time.Now() // TODO: Remove the below handling once kubeadm 1.23 is no longer supported. // https://github.com/kubernetes-sigs/kind/issues/1699 rawVersion, err := nodeutils.KubeVersion(node) if err != nil { return errors.Wrap(err, "failed to get Kubernetes version from node") } kubeVersion, err := version.ParseSemantic(rawVersion) if err != nil { return errors.Wrap(err, "could not parse Kubernetes version") } selectorLabel := "node-role.kubernetes.io/control-plane" if kubeVersion.LessThan(version.MustParseSemantic("v1.24.0-alpha.1.591+a3d5e5598290df")) { selectorLabel = "node-role.kubernetes.io/master" } isReady := waitForReady(node, startTime.Add(a.waitTime), selectorLabel) if !isReady { ctx.Status.End(false) ctx.Logger.V(0).Info(" • WARNING: Timed out waiting for Ready ⚠️") return nil } // mark success ctx.Status.End(true) ctx.Logger.V(0).Infof(" • Ready after %s 💚", formatDuration(time.Since(startTime))) return nil } // WaitForReady uses kubectl inside the "node" container to check if the // control plane nodes are "Ready". func waitForReady(node nodes.Node, until time.Time, selectorLabel string) bool { return tryUntil(until, func() bool { cmd := node.Command( "kubectl", "--kubeconfig=/etc/kubernetes/admin.conf", "get", "nodes", "--selector="+selectorLabel, // When the node reaches status ready, the status field will be set // to true. "-o=jsonpath='{.items..status.conditions[-1:].status}'", ) lines, err := exec.OutputLines(cmd) if err != nil { return false } // 'lines' will return the status of all nodes labeled as master. For // example, if we have three control plane nodes, and all are ready, // then the status will have the following format: `True True True'. status := strings.Fields(lines[0]) for _, s := range status { // Check node status. If node is ready then this will be 'True', // 'False' or 'Unknown' otherwise. if !strings.Contains(s, "True") { return false } } return true }) } // helper that calls `try()“ in a loop until the deadline `until` // has passed or `try()`returns true, returns whether try ever returned true func tryUntil(until time.Time, try func() bool) bool { for until.After(time.Now()) { if try() { return true } } return false } func formatDuration(duration time.Duration) string { return duration.Round(time.Second).String() } kind-0.27.0/pkg/cluster/internal/create/create.go000066400000000000000000000202501475376161000216510ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package create import ( "fmt" "math/rand" "time" "al.essio.dev/pkg/shellescape" "sigs.k8s.io/kind/pkg/cluster/internal/delete" "sigs.k8s.io/kind/pkg/cluster/internal/providers" "sigs.k8s.io/kind/pkg/errors" "sigs.k8s.io/kind/pkg/internal/apis/config" "sigs.k8s.io/kind/pkg/internal/apis/config/encoding" "sigs.k8s.io/kind/pkg/internal/cli" "sigs.k8s.io/kind/pkg/log" "sigs.k8s.io/kind/pkg/cluster/internal/create/actions" configaction "sigs.k8s.io/kind/pkg/cluster/internal/create/actions/config" "sigs.k8s.io/kind/pkg/cluster/internal/create/actions/installcni" "sigs.k8s.io/kind/pkg/cluster/internal/create/actions/installstorage" "sigs.k8s.io/kind/pkg/cluster/internal/create/actions/kubeadminit" "sigs.k8s.io/kind/pkg/cluster/internal/create/actions/kubeadmjoin" "sigs.k8s.io/kind/pkg/cluster/internal/create/actions/loadbalancer" "sigs.k8s.io/kind/pkg/cluster/internal/create/actions/waitforready" "sigs.k8s.io/kind/pkg/cluster/internal/kubeconfig" ) const ( // Typical host name max limit is 64 characters (https://linux.die.net/man/2/sethostname) // We append -control-plane (14 characters) to the cluster name on the control plane container clusterNameMax = 50 ) // ClusterOptions holds cluster creation options type ClusterOptions struct { Config *config.Cluster NameOverride string // overrides config.Name // NodeImage overrides the nodes' images in Config if non-zero NodeImage string Retain bool WaitForReady time.Duration KubeconfigPath string // see https://github.com/kubernetes-sigs/kind/issues/324 StopBeforeSettingUpKubernetes bool // if false kind should setup kubernetes after creating nodes // Options to control output DisplayUsage bool DisplaySalutation bool } // Cluster creates a cluster func Cluster(logger log.Logger, p providers.Provider, opts *ClusterOptions) error { // validate provider first if err := validateProvider(p); err != nil { return err } // default / process options (namely config) if err := fixupOptions(opts); err != nil { return err } // Check if the cluster name already exists if err := alreadyExists(p, opts.Config.Name); err != nil { return err } // warn if cluster name might typically be too long if len(opts.Config.Name) > clusterNameMax { logger.Warnf("cluster name %q is probably too long, this might not work properly on some systems", opts.Config.Name) } // then validate if err := opts.Config.Validate(); err != nil { return err } // setup a status object to show progress to the user status := cli.StatusForLogger(logger) // we're going to start creating now, tell the user logger.V(0).Infof("Creating cluster %q ...\n", opts.Config.Name) // Create node containers implementing defined config Nodes if err := p.Provision(status, opts.Config); err != nil { // In case of errors nodes are deleted (except if retain is explicitly set) if !opts.Retain { _ = delete.Cluster(logger, p, opts.Config.Name, opts.KubeconfigPath) } return err } // TODO(bentheelder): make this controllable from the command line? actionsToRun := []actions.Action{ loadbalancer.NewAction(), // setup external loadbalancer configaction.NewAction(), // setup kubeadm config } if !opts.StopBeforeSettingUpKubernetes { actionsToRun = append(actionsToRun, kubeadminit.NewAction(opts.Config), // run kubeadm init ) // this step might be skipped, but is next after init if !opts.Config.Networking.DisableDefaultCNI { actionsToRun = append(actionsToRun, installcni.NewAction(), // install CNI ) } // add remaining steps actionsToRun = append(actionsToRun, installstorage.NewAction(), // install StorageClass kubeadmjoin.NewAction(), // run kubeadm join waitforready.NewAction(opts.WaitForReady), // wait for cluster readiness ) } // run all actions actionsContext := actions.NewActionContext(logger, status, p, opts.Config) for _, action := range actionsToRun { if err := action.Execute(actionsContext); err != nil { if !opts.Retain { _ = delete.Cluster(logger, p, opts.Config.Name, opts.KubeconfigPath) } return err } } // skip the rest if we're not setting up kubernetes if opts.StopBeforeSettingUpKubernetes { return nil } // try exporting kubeconfig with backoff for locking failures // TODO: factor out into a public errors API w/ backoff handling? // for now this is easier than coming up with a good API var err error for _, b := range []time.Duration{0, time.Millisecond, time.Millisecond * 50, time.Millisecond * 100} { time.Sleep(b) if err = kubeconfig.Export(p, opts.Config.Name, opts.KubeconfigPath, true); err == nil { break } } if err != nil { return err } // optionally display usage if opts.DisplayUsage { logUsage(logger, opts.Config.Name, opts.KubeconfigPath) } // optionally give the user a friendly salutation if opts.DisplaySalutation { logger.V(0).Info("") logSalutation(logger) } return nil } // alreadyExists returns an error if the cluster name already exists // or if we had an error checking func alreadyExists(p providers.Provider, name string) error { n, err := p.ListNodes(name) if err != nil { return err } if len(n) != 0 { return errors.Errorf("node(s) already exist for a cluster with the name %q", name) } return nil } func logUsage(logger log.Logger, name, explicitKubeconfigPath string) { // construct a sample command for interacting with the cluster kctx := kubeconfig.ContextForCluster(name) sampleCommand := fmt.Sprintf("kubectl cluster-info --context %s", kctx) if explicitKubeconfigPath != "" { // explicit path, include this sampleCommand += " --kubeconfig " + shellescape.Quote(explicitKubeconfigPath) } logger.V(0).Infof(`Set kubectl context to "%s"`, kctx) logger.V(0).Infof("You can now use your cluster with:\n\n" + sampleCommand) } func logSalutation(logger log.Logger) { salutations := []string{ "Have a nice day! 👋", "Thanks for using kind! 😊", "Not sure what to do next? 😅 Check out https://kind.sigs.k8s.io/docs/user/quick-start/", "Have a question, bug, or feature request? Let us know! https://kind.sigs.k8s.io/#community 🙂", } r := rand.New(rand.NewSource(time.Now().UTC().UnixNano())) s := salutations[r.Intn(len(salutations))] logger.V(0).Info(s) } func fixupOptions(opts *ClusterOptions) error { // do post processing for options // first ensure we at least have a default cluster config if opts.Config == nil { cfg, err := encoding.Load("") if err != nil { return err } opts.Config = cfg } if opts.NameOverride != "" { opts.Config.Name = opts.NameOverride } // if NodeImage was set, override the image on all nodes if opts.NodeImage != "" { // Apply image override to all the Nodes defined in Config // TODO(fabrizio pandini): this should be reconsidered when implementing // https://github.com/kubernetes-sigs/kind/issues/133 for i := range opts.Config.Nodes { opts.Config.Nodes[i].Image = opts.NodeImage } } // default config fields (important for usage as a library, where the config // may be constructed in memory rather than from disk) config.SetDefaultsCluster(opts.Config) return nil } func validateProvider(p providers.Provider) error { info, err := p.Info() if err != nil { return err } if info.Rootless { if !info.Cgroup2 { return errors.New("running kind with rootless provider requires cgroup v2, see https://kind.sigs.k8s.io/docs/user/rootless/") } if !info.SupportsMemoryLimit || !info.SupportsPidsLimit || !info.SupportsCPUShares { return errors.New("running kind with rootless provider requires setting systemd property \"Delegate=yes\", see https://kind.sigs.k8s.io/docs/user/rootless/") } } return nil } kind-0.27.0/pkg/cluster/internal/delete/000077500000000000000000000000001475376161000200575ustar00rootroot00000000000000kind-0.27.0/pkg/cluster/internal/delete/delete.go000066400000000000000000000026701475376161000216550ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package delete import ( "sigs.k8s.io/kind/pkg/errors" "sigs.k8s.io/kind/pkg/log" "sigs.k8s.io/kind/pkg/cluster/internal/kubeconfig" "sigs.k8s.io/kind/pkg/cluster/internal/providers" ) // Cluster deletes the cluster identified by ctx // explicitKubeconfigPath is --kubeconfig, following the rules from // https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands func Cluster(logger log.Logger, p providers.Provider, name, explicitKubeconfigPath string) error { n, err := p.ListNodes(name) if err != nil { return errors.Wrap(err, "error listing nodes") } kerr := kubeconfig.Remove(name, explicitKubeconfigPath) if kerr != nil { logger.Errorf("failed to update kubeconfig: %v", kerr) } if len(n) > 0 { err = p.DeleteNodes(n) if err != nil { return err } logger.V(0).Infof("Deleted nodes: %q", n) } if kerr != nil { return kerr } return nil } kind-0.27.0/pkg/cluster/internal/kubeadm/000077500000000000000000000000001475376161000202255ustar00rootroot00000000000000kind-0.27.0/pkg/cluster/internal/kubeadm/config.go000066400000000000000000000425041475376161000220260ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package kubeadm import ( "bytes" "fmt" "sort" "strings" "github.com/google/safetext/yamltemplate" "sigs.k8s.io/kind/pkg/errors" "sigs.k8s.io/kind/pkg/internal/apis/config" "sigs.k8s.io/kind/pkg/internal/version" ) // ConfigData is supplied to the kubeadm config template, with values populated // by the cluster package type ConfigData struct { ClusterName string KubernetesVersion string // The ControlPlaneEndpoint, that is the address of the external loadbalancer // if defined or the bootstrap node ControlPlaneEndpoint string // The Local API Server port APIBindPort int // The API server external listen IP (which we will port forward) APIServerAddress string // this should really be used for the --provider-id flag // ideally cluster config should not depend on the node backend otherwise ... NodeProvider string // ControlPlane flag specifies the node belongs to the control plane ControlPlane bool // The IP address or comma separated list IP addresses of of the node NodeAddress string // The name for the node (not the address) NodeName string // The Token for TLS bootstrap Token string // KubeProxyMode defines the kube-proxy mode between iptables, ipvs or nftables KubeProxyMode string // The subnet used for pods PodSubnet string // The subnet used for services ServiceSubnet string // Kubernetes FeatureGates FeatureGates map[string]bool // Kubernetes API Server RuntimeConfig RuntimeConfig map[string]string // IPFamily of the cluster, it can be IPv4, IPv6 or DualStack IPFamily config.ClusterIPFamily // Labels are the labels, in the format "key1=val1,key2=val2", with which the respective node will be labeled NodeLabels string // RootlessProvider is true if kind is running with rootless mode RootlessProvider bool // DerivedConfigData contains fields computed from the other fields for use // in the config templates and should only be populated by calling Derive() DerivedConfigData } // DerivedConfigData fields are automatically derived by // ConfigData.Derive if they are not specified / zero valued type DerivedConfigData struct { // AdvertiseAddress is the first address in NodeAddress AdvertiseAddress string // DockerStableTag is automatically derived from KubernetesVersion DockerStableTag string // SortedFeatureGates allows us to iterate FeatureGates deterministically SortedFeatureGates []FeatureGate // FeatureGatesString is of the form `Foo=true,Baz=false` FeatureGatesString string // RuntimeConfigString is of the form `Foo=true,Baz=false` RuntimeConfigString string // KubeadmFeatureGates contains Kubeadm only feature gates KubeadmFeatureGates map[string]bool // IPv4 values take precedence over IPv6 by default, if true set IPv6 default values IPv6 bool // kubelet cgroup driver, based on kubernetes version CgroupDriver string // JoinSkipPhases are the skipPhases values for the JoinConfiguration. JoinSkipPhases []string // InitSkipPhases are the skipPhases values for the InitConfiguration. InitSkipPhases []string } type FeatureGate struct { Name string Value bool } // Derive automatically derives DockerStableTag if not specified func (c *ConfigData) Derive() { // default cgroup driver // TODO: refactor and move all deriving logic to this method c.CgroupDriver = "systemd" // get the first address to use it as the API advertised address c.AdvertiseAddress = strings.Split(c.NodeAddress, ",")[0] if c.DockerStableTag == "" { c.DockerStableTag = strings.Replace(c.KubernetesVersion, "+", "_", -1) } // get the IP addresses family for defaulting components c.IPv6 = c.IPFamily == config.IPv6Family // get sorted list of FeatureGate keys featureGateKeys := make([]string, 0, len(c.FeatureGates)) for k := range c.FeatureGates { featureGateKeys = append(featureGateKeys, k) } sort.Strings(featureGateKeys) // create a sorted key=value,... string of FeatureGates c.SortedFeatureGates = make([]FeatureGate, 0, len(c.FeatureGates)) featureGates := make([]string, 0, len(c.FeatureGates)) for _, k := range featureGateKeys { v := c.FeatureGates[k] featureGates = append(featureGates, fmt.Sprintf("%s=%t", k, v)) c.SortedFeatureGates = append(c.SortedFeatureGates, FeatureGate{ Name: k, Value: v, }) } c.FeatureGatesString = strings.Join(featureGates, ",") // create a sorted key=value,... string of RuntimeConfig // first get sorted list of FeatureGate keys runtimeConfigKeys := make([]string, 0, len(c.RuntimeConfig)) for k := range c.RuntimeConfig { runtimeConfigKeys = append(runtimeConfigKeys, k) } sort.Strings(runtimeConfigKeys) // stringify var runtimeConfig []string for _, k := range runtimeConfigKeys { v := c.RuntimeConfig[k] // TODO: do we need to quote / escape these in the future? // Currently runtime config is in practice booleans, no special characters runtimeConfig = append(runtimeConfig, fmt.Sprintf("%s=%s", k, v)) } c.RuntimeConfigString = strings.Join(runtimeConfig, ",") // Skip preflight to avoid pulling images. // Kind pre-pulls images and preflight may conflict with that. // requires kubeadm 1.22+ c.JoinSkipPhases = []string{"preflight"} c.InitSkipPhases = []string{"preflight"} if c.KubeProxyMode == string(config.NoneProxyMode) { c.InitSkipPhases = append(c.InitSkipPhases, "addon/kube-proxy") } } // See docs for these APIs at: // https://godoc.org/k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm#pkg-subdirectories // EG: // https://godoc.org/k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta1 // ConfigTemplateBetaV2 is the kubeadm config template for API version v1beta2 const ConfigTemplateBetaV2 = `# config generated by kind apiVersion: kubeadm.k8s.io/v1beta2 kind: ClusterConfiguration metadata: name: config kubernetesVersion: {{.KubernetesVersion}} clusterName: "{{.ClusterName}}" {{ if .KubeadmFeatureGates}}featureGates: {{ range $key, $value := .KubeadmFeatureGates }} "{{ (StructuralData $key) }}": {{ $value }} {{end}}{{end}} controlPlaneEndpoint: "{{ .ControlPlaneEndpoint }}" # on docker for mac we have to expose the api server via port forward, # so we need to ensure the cert is valid for localhost so we can talk # to the cluster after rewriting the kubeconfig to point to localhost apiServer: certSANs: [localhost, "{{.APIServerAddress}}"] extraArgs: "runtime-config": "{{ .RuntimeConfigString }}" {{ if .FeatureGates }} "feature-gates": "{{ .FeatureGatesString }}" {{ end}} controllerManager: extraArgs: {{ if .FeatureGates }} "feature-gates": "{{ .FeatureGatesString }}" {{ end }} enable-hostpath-provisioner: "true" # configure ipv6 default addresses for IPv6 clusters {{ if .IPv6 -}} bind-address: "::" {{- end }} scheduler: extraArgs: {{ if .FeatureGates }} "feature-gates": "{{ .FeatureGatesString }}" {{ end }} # configure ipv6 default addresses for IPv6 clusters {{ if .IPv6 -}} bind-address: "::1" {{- end }} networking: podSubnet: "{{ .PodSubnet }}" serviceSubnet: "{{ .ServiceSubnet }}" --- apiVersion: kubeadm.k8s.io/v1beta2 kind: InitConfiguration metadata: name: config # we use a well know token for TLS bootstrap bootstrapTokens: - token: "{{ .Token }}" # we use a well know port for making the API server discoverable inside docker network. # from the host machine such port will be accessible via a random local port instead. localAPIEndpoint: advertiseAddress: "{{ .AdvertiseAddress }}" bindPort: {{.APIBindPort}} nodeRegistration: criSocket: "unix:///run/containerd/containerd.sock" kubeletExtraArgs: node-ip: "{{ .NodeAddress }}" provider-id: "kind://{{.NodeProvider}}/{{.ClusterName}}/{{.NodeName}}" node-labels: "{{ .NodeLabels }}" --- # no-op entry that exists solely so it can be patched apiVersion: kubeadm.k8s.io/v1beta2 kind: JoinConfiguration metadata: name: config {{ if .ControlPlane -}} controlPlane: localAPIEndpoint: advertiseAddress: "{{ .AdvertiseAddress }}" bindPort: {{.APIBindPort}} {{- end }} nodeRegistration: criSocket: "unix:///run/containerd/containerd.sock" kubeletExtraArgs: node-ip: "{{ .NodeAddress }}" provider-id: "kind://{{.NodeProvider}}/{{.ClusterName}}/{{.NodeName}}" node-labels: "{{ .NodeLabels }}" discovery: bootstrapToken: apiServerEndpoint: "{{ .ControlPlaneEndpoint }}" token: "{{ .Token }}" unsafeSkipCAVerification: true --- apiVersion: kubelet.config.k8s.io/v1beta1 kind: KubeletConfiguration metadata: name: config cgroupDriver: {{ .CgroupDriver }} cgroupRoot: /kubelet failSwapOn: false # configure ipv6 addresses in IPv6 mode {{ if .IPv6 -}} address: "::" healthzBindAddress: "::" {{- end }} # disable disk resource management by default # kubelet will see the host disk that the inner container runtime # is ultimately backed by and attempt to recover disk space. we don't want that. imageGCHighThresholdPercent: 100 evictionHard: nodefs.available: "0%" nodefs.inodesFree: "0%" imagefs.available: "0%" {{if .FeatureGates}}featureGates: {{ range $index, $gate := .SortedFeatureGates }} "{{ (StructuralData $gate.Name) }}": {{ $gate.Value }} {{end}}{{end}} {{if ne .KubeProxyMode "none"}} --- apiVersion: kubeproxy.config.k8s.io/v1alpha1 kind: KubeProxyConfiguration metadata: name: config mode: "{{ .KubeProxyMode }}" {{if .FeatureGates}}featureGates: {{ range $index, $gate := .SortedFeatureGates }} "{{ (StructuralData $gate.Name) }}": {{ $gate.Value }} {{end}}{{end}} iptables: minSyncPeriod: 1s conntrack: # Skip setting sysctl value "net.netfilter.nf_conntrack_max" # It is a global variable that affects other namespaces maxPerCore: 0 # Set sysctl value "net.netfilter.nf_conntrack_tcp_be_liberal" # for nftables proxy (theoretically for kernels older than 6.1) # xref: https://github.com/kubernetes/kubernetes/issues/117924 {{if and (eq .KubeProxyMode "nftables") (not .RootlessProvider)}} tcpBeLiberal: true {{end}} {{if .RootlessProvider}} # Skip setting "net.netfilter.nf_conntrack_tcp_timeout_established" tcpEstablishedTimeout: 0s # Skip setting "net.netfilter.nf_conntrack_tcp_timeout_close" tcpCloseWaitTimeout: 0s {{end}}{{end}} ` // ConfigTemplateBetaV3 is the kubeadm config template for API version v1beta3 const ConfigTemplateBetaV3 = `# config generated by kind apiVersion: kubeadm.k8s.io/v1beta3 kind: ClusterConfiguration metadata: name: config kubernetesVersion: {{.KubernetesVersion}} clusterName: "{{.ClusterName}}" {{ if .KubeadmFeatureGates}}featureGates: {{ range $key, $value := .KubeadmFeatureGates }} "{{ (StructuralData $key) }}": {{ $value }} {{end}}{{end}} controlPlaneEndpoint: "{{ .ControlPlaneEndpoint }}" # on docker for mac we have to expose the api server via port forward, # so we need to ensure the cert is valid for localhost so we can talk # to the cluster after rewriting the kubeconfig to point to localhost apiServer: certSANs: [localhost, "{{.APIServerAddress}}"] extraArgs: "runtime-config": "{{ .RuntimeConfigString }}" {{ if .FeatureGates }} "feature-gates": "{{ .FeatureGatesString }}" {{ end}} controllerManager: extraArgs: {{ if .FeatureGates }} "feature-gates": "{{ .FeatureGatesString }}" {{ end }} enable-hostpath-provisioner: "true" # configure ipv6 default addresses for IPv6 clusters {{ if .IPv6 -}} bind-address: "::" {{- end }} scheduler: extraArgs: {{ if .FeatureGates }} "feature-gates": "{{ .FeatureGatesString }}" {{ end }} # configure ipv6 default addresses for IPv6 clusters {{ if .IPv6 -}} bind-address: "::1" {{- end }} networking: podSubnet: "{{ .PodSubnet }}" serviceSubnet: "{{ .ServiceSubnet }}" --- apiVersion: kubeadm.k8s.io/v1beta3 kind: InitConfiguration metadata: name: config # we use a well know token for TLS bootstrap bootstrapTokens: - token: "{{ .Token }}" # we use a well know port for making the API server discoverable inside docker network. # from the host machine such port will be accessible via a random local port instead. localAPIEndpoint: advertiseAddress: "{{ .AdvertiseAddress }}" bindPort: {{.APIBindPort}} nodeRegistration: criSocket: "unix:///run/containerd/containerd.sock" kubeletExtraArgs: node-ip: "{{ .NodeAddress }}" provider-id: "kind://{{.NodeProvider}}/{{.ClusterName}}/{{.NodeName}}" node-labels: "{{ .NodeLabels }}" {{ if .InitSkipPhases -}} skipPhases: {{- range $phase := .InitSkipPhases }} - "{{ $phase }}" {{- end }} {{- end }} --- # no-op entry that exists solely so it can be patched apiVersion: kubeadm.k8s.io/v1beta3 kind: JoinConfiguration metadata: name: config {{ if .ControlPlane -}} controlPlane: localAPIEndpoint: advertiseAddress: "{{ .AdvertiseAddress }}" bindPort: {{.APIBindPort}} {{- end }} nodeRegistration: criSocket: "unix:///run/containerd/containerd.sock" kubeletExtraArgs: node-ip: "{{ .NodeAddress }}" provider-id: "kind://{{.NodeProvider}}/{{.ClusterName}}/{{.NodeName}}" node-labels: "{{ .NodeLabels }}" discovery: bootstrapToken: apiServerEndpoint: "{{ .ControlPlaneEndpoint }}" token: "{{ .Token }}" unsafeSkipCAVerification: true {{ if .JoinSkipPhases -}} skipPhases: {{ range $phase := .JoinSkipPhases -}} - "{{ $phase }}" {{- end }} {{- end }} --- apiVersion: kubelet.config.k8s.io/v1beta1 kind: KubeletConfiguration metadata: name: config cgroupDriver: {{ .CgroupDriver }} cgroupRoot: /kubelet failSwapOn: false # configure ipv6 addresses in IPv6 mode {{ if .IPv6 -}} address: "::" healthzBindAddress: "::" {{- end }} # disable disk resource management by default # kubelet will see the host disk that the inner container runtime # is ultimately backed by and attempt to recover disk space. we don't want that. imageGCHighThresholdPercent: 100 evictionHard: nodefs.available: "0%" nodefs.inodesFree: "0%" imagefs.available: "0%" {{if .FeatureGates}}featureGates: {{ range $index, $gate := .SortedFeatureGates }} "{{ (StructuralData $gate.Name) }}": {{ $gate.Value }} {{end}}{{end}} {{if ne .KubeProxyMode "none"}} --- apiVersion: kubeproxy.config.k8s.io/v1alpha1 kind: KubeProxyConfiguration metadata: name: config mode: "{{ .KubeProxyMode }}" {{if .FeatureGates}}featureGates: {{ range $index, $gate := .SortedFeatureGates }} "{{ (StructuralData $gate.Name) }}": {{ $gate.Value }} {{end}}{{end}} iptables: minSyncPeriod: 1s conntrack: # Skip setting sysctl value "net.netfilter.nf_conntrack_max" # It is a global variable that affects other namespaces maxPerCore: 0 # Set sysctl value "net.netfilter.nf_conntrack_tcp_be_liberal" # for nftables proxy (theoretically for kernels older than 6.1) # xref: https://github.com/kubernetes/kubernetes/issues/117924 {{if and (eq .KubeProxyMode "nftables") (not .RootlessProvider)}} tcpBeLiberal: true {{end}} {{if .RootlessProvider}} # Skip setting "net.netfilter.nf_conntrack_tcp_timeout_established" tcpEstablishedTimeout: 0s # Skip setting "net.netfilter.nf_conntrack_tcp_timeout_close" tcpCloseWaitTimeout: 0s {{end}}{{end}} ` // Config returns a kubeadm config generated from config data, in particular // the kubernetes version func Config(data ConfigData) (config string, err error) { ver, err := version.ParseGeneric(data.KubernetesVersion) if err != nil { return "", err } // ensure featureGates is non-nil, as we may add entries if data.FeatureGates == nil { data.FeatureGates = make(map[string]bool) } if data.RootlessProvider { if ver.LessThan(version.MustParseSemantic("v1.22.0")) { // rootless kind v0.12.x supports Kubernetes v1.22 with KubeletInUserNamespace gate. // rootless kind v0.11.x supports older Kubernetes with fake procfs. return "", errors.Errorf("version %q is not compatible with rootless provider (hint: kind v0.11.x may work with this version)", ver) } data.FeatureGates["KubeletInUserNamespace"] = true } // assume the latest API version, then fallback if the k8s version is too low templateSource := ConfigTemplateBetaV3 if ver.LessThan(version.MustParseSemantic("v1.23.0")) { templateSource = ConfigTemplateBetaV2 } t, err := yamltemplate.New("kubeadm-config").Parse(templateSource) if err != nil { return "", errors.Wrap(err, "failed to parse config template") } // derive any automatic fields if not supplied data.Derive() // Kubeadm has its own feature-gate for dual stack // we need to enable it for Kubernetes version 1.20 only // dual-stack is only supported in 1.20+ // TODO: remove this when 1.20 is EOL or we no longer support // dual-stack for 1.20 in KIND if ver.LessThan(version.MustParseSemantic("v1.21.0")) && ver.AtLeast(version.MustParseSemantic("v1.20.0")) { data.KubeadmFeatureGates = make(map[string]bool) data.KubeadmFeatureGates["IPv6DualStack"] = true } // before 1.24 kind uses cgroupfs // after 1.24 kind uses systemd starting in kind v0.13.0 // before kind v0.13.0 kubernetes 1.24 wasn't released yet if ver.LessThan(version.MustParseSemantic("v1.24.0")) { data.CgroupDriver = "cgroupfs" } // execute the template var buff bytes.Buffer err = t.Execute(&buff, data) if err != nil { return "", errors.Wrap(err, "error executing config template") } return buff.String(), nil } kind-0.27.0/pkg/cluster/internal/kubeadm/const.go000066400000000000000000000014761475376161000217120ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package kubeadm // Token defines a dummy, well known token for automating TLS bootstrap process const Token = "abcdef.0123456789abcdef" // ObjectName is the name every generated object will have // I.E. `metadata:\nname: config` const ObjectName = "config" kind-0.27.0/pkg/cluster/internal/kubeadm/doc.go000066400000000000000000000012231475376161000213170ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package kubeadm contains kubeadm related constants and configuration package kubeadm kind-0.27.0/pkg/cluster/internal/kubeconfig/000077500000000000000000000000001475376161000207315ustar00rootroot00000000000000kind-0.27.0/pkg/cluster/internal/kubeconfig/internal/000077500000000000000000000000001475376161000225455ustar00rootroot00000000000000kind-0.27.0/pkg/cluster/internal/kubeconfig/internal/kubeconfig/000077500000000000000000000000001475376161000246615ustar00rootroot00000000000000kind-0.27.0/pkg/cluster/internal/kubeconfig/internal/kubeconfig/encode.go000066400000000000000000000034611475376161000264510ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package kubeconfig import ( "bytes" yaml "gopkg.in/yaml.v3" kubeyaml "sigs.k8s.io/yaml" "sigs.k8s.io/kind/pkg/errors" ) // Encode encodes the cfg to yaml func Encode(cfg *Config) ([]byte, error) { // NOTE: kubernetes's yaml library doesn't handle inline fields very well // so we're not using that to marshal encoded, err := yaml.Marshal(cfg) if err != nil { return nil, errors.Wrap(err, "failed to encode KUBECONFIG") } // normalize with kubernetes's yaml library // this is not strictly necessary, but it ensures minimal diffs when // modifying kubeconfig files, which is nice to have encoded, err = normYaml(encoded) if err != nil { return nil, errors.Wrap(err, "failed to normalize KUBECONFIG encoding") } return encoded, nil } // normYaml round trips yaml bytes through sigs.k8s.io/yaml to normalize them // versus other kubernetes ecosystem yaml output func normYaml(y []byte) ([]byte, error) { var unstructured interface{} if err := kubeyaml.Unmarshal(y, &unstructured); err != nil { return nil, err } encoded, err := kubeyaml.Marshal(&unstructured) if err != nil { return nil, err } // special case: don't write anything when empty if bytes.Equal(encoded, []byte("{}\n")) { return []byte{}, nil } return encoded, nil } kind-0.27.0/pkg/cluster/internal/kubeconfig/internal/kubeconfig/encode_test.go000066400000000000000000000031251475376161000275050ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package kubeconfig import ( "testing" "sigs.k8s.io/kind/pkg/internal/assert" ) func TestEncodeRoundtrip(t *testing.T) { t.Parallel() // test round tripping a kubeconfig const aConfig = `apiVersion: v1 clusters: - cluster: certificate-authority-data: definitelyacert server: https://192.168.9.4:6443 name: kind-kind contexts: - context: cluster: kind-kind user: kind-kind name: kind-kind current-context: kind-kind kind: Config preferences: {} users: - name: kind-kind user: client-certificate-data: seemslegit client-key-data: yup ` cfg, err := KINDFromRawKubeadm(aConfig, "kind", "") if err != nil { t.Fatalf("failed to decode kubeconfig: %v", err) } encoded, err := Encode(cfg) if err != nil { t.Fatalf("failed to encode kubeconfig: %v", err) } assert.StringEqual(t, aConfig, string(encoded)) } func TestEncodeEmpty(t *testing.T) { t.Parallel() encoded, err := Encode(&Config{}) if err != nil { t.Fatalf("failed to encode kubeconfig: %v", err) } assert.StringEqual(t, "", string(encoded)) } kind-0.27.0/pkg/cluster/internal/kubeconfig/internal/kubeconfig/helpers.go000066400000000000000000000025251475376161000266560ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package kubeconfig import ( "sigs.k8s.io/kind/pkg/errors" ) // KINDClusterKey identifies kind clusters in kubeconfig files func KINDClusterKey(clusterName string) string { return "kind-" + clusterName } // checkKubeadmExpectations validates that a kubeadm created KUBECONFIG meets // our expectations, namely on the number of entries func checkKubeadmExpectations(cfg *Config) error { if len(cfg.Clusters) != 1 { return errors.Errorf("kubeadm KUBECONFIG should have one cluster, but read %d", len(cfg.Clusters)) } if len(cfg.Users) != 1 { return errors.Errorf("kubeadm KUBECONFIG should have one user, but read %d", len(cfg.Users)) } if len(cfg.Contexts) != 1 { return errors.Errorf("kubeadm KUBECONFIG should have one context, but read %d", len(cfg.Contexts)) } return nil } kind-0.27.0/pkg/cluster/internal/kubeconfig/internal/kubeconfig/helpers_test.go000066400000000000000000000041011475376161000277050ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package kubeconfig import ( "testing" "sigs.k8s.io/kind/pkg/internal/assert" ) func TestKINDClusterKey(t *testing.T) { t.Parallel() assert.StringEqual(t, "kind-foobar", KINDClusterKey("foobar")) } func TestCheckKubeadmExpectations(t *testing.T) { t.Parallel() cases := []struct { Name string Config *Config ExpectError bool }{ { Name: "too many of all entries", Config: &Config{ Clusters: make([]NamedCluster, 5), Contexts: make([]NamedContext, 5), Users: make([]NamedUser, 5), }, ExpectError: true, }, { Name: "too many users", Config: &Config{ Clusters: make([]NamedCluster, 1), Contexts: make([]NamedContext, 1), Users: make([]NamedUser, 2), }, ExpectError: true, }, { Name: "too many clusters", Config: &Config{ Clusters: make([]NamedCluster, 2), Contexts: make([]NamedContext, 1), Users: make([]NamedUser, 1), }, ExpectError: true, }, { Name: "too many contexts", Config: &Config{ Clusters: make([]NamedCluster, 1), Contexts: make([]NamedContext, 2), Users: make([]NamedUser, 1), }, ExpectError: true, }, { Name: "just right", Config: &Config{ Clusters: make([]NamedCluster, 1), Contexts: make([]NamedContext, 1), Users: make([]NamedUser, 1), }, ExpectError: false, }, } for _, tc := range cases { tc := tc t.Run(tc.Name, func(t *testing.T) { t.Parallel() assert.ExpectError(t, tc.ExpectError, checkKubeadmExpectations(tc.Config)) }) } } kind-0.27.0/pkg/cluster/internal/kubeconfig/internal/kubeconfig/lock.go000066400000000000000000000024271475376161000261450ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package kubeconfig import ( "os" "path/filepath" ) // these are from // https://github.com/kubernetes/client-go/blob/611184f7c43ae2d520727f01d49620c7ed33412d/tools/clientcmd/loader.go#L439-L440 func lockFile(filename string) error { // Make sure the dir exists before we try to create a lock file. dir := filepath.Dir(filename) if _, err := os.Stat(dir); os.IsNotExist(err) { if err = os.MkdirAll(dir, 0755); err != nil { return err } } f, err := os.OpenFile(lockName(filename), os.O_CREATE|os.O_EXCL, 0) if err != nil { return err } f.Close() return nil } func unlockFile(filename string) error { return os.Remove(lockName(filename)) } func lockName(filename string) string { return filename + ".lock" } kind-0.27.0/pkg/cluster/internal/kubeconfig/internal/kubeconfig/merge.go000066400000000000000000000061331475376161000263120ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package kubeconfig import ( "os" "sigs.k8s.io/kind/pkg/errors" ) // WriteMerged writes a kind kubeconfig (see KINDFromRawKubeadm) into configPath // merging with the existing contents if any and setting the current context to // the kind config's current context. func WriteMerged(kindConfig *Config, explicitConfigPath string) error { // figure out what filepath we should use configPath := pathForMerge(explicitConfigPath, os.Getenv) // lock config file the same as client-go if err := lockFile(configPath); err != nil { return errors.Wrap(err, "failed to lock config file") } defer func() { _ = unlockFile(configPath) }() // read in existing existing, err := read(configPath) if err != nil { return errors.Wrap(err, "failed to get kubeconfig to merge") } // merge with kind kubeconfig if err := merge(existing, kindConfig); err != nil { return err } // write back out return write(existing, configPath) } // merge kind config into an existing config func merge(existing, kind *Config) error { // verify assumptions about kubeadm / kind kubeconfigs if err := checkKubeadmExpectations(kind); err != nil { return err } // insert or append cluster entry shouldAppend := true for i := range existing.Clusters { if existing.Clusters[i].Name == kind.Clusters[0].Name { existing.Clusters[i] = kind.Clusters[0] shouldAppend = false } } if shouldAppend { existing.Clusters = append(existing.Clusters, kind.Clusters[0]) } // insert or append user entry shouldAppend = true for i := range existing.Users { if existing.Users[i].Name == kind.Users[0].Name { existing.Users[i] = kind.Users[0] shouldAppend = false } } if shouldAppend { existing.Users = append(existing.Users, kind.Users[0]) } // insert or append context entry shouldAppend = true for i := range existing.Contexts { if existing.Contexts[i].Name == kind.Contexts[0].Name { existing.Contexts[i] = kind.Contexts[0] shouldAppend = false } } if shouldAppend { existing.Contexts = append(existing.Contexts, kind.Contexts[0]) } // set the current context existing.CurrentContext = kind.CurrentContext // TODO: We should not need this, but it allows broken clients that depend // on apiVersion and kind to work. Notably the upstream javascript client. // See: https://github.com/kubernetes-sigs/kind/issues/1242 if len(existing.OtherFields) == 0 { // TODO: Should we be deep-copying? for now we don't need to // and doing so would be a pain (re and de-serialize maybe?) :shrug: existing.OtherFields = kind.OtherFields } return nil } kind-0.27.0/pkg/cluster/internal/kubeconfig/internal/kubeconfig/merge_test.go000066400000000000000000000215741475376161000273570ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package kubeconfig import ( "io" "os" "path/filepath" "reflect" "testing" "sigs.k8s.io/kind/pkg/internal/assert" ) func TestMerge(t *testing.T) { t.Parallel() cases := []struct { Name string Existing *Config Kind *Config Expected *Config ExpectError bool }{ { Name: "bad kind config", Existing: &Config{}, Kind: &Config{}, Expected: &Config{}, ExpectError: true, }, { Name: "empty existing", Existing: &Config{}, Kind: &Config{ Clusters: []NamedCluster{ { Name: "kind-kind", }, }, Users: []NamedUser{ { Name: "kind-kind", }, }, Contexts: []NamedContext{ { Name: "kind-kind", }, }, }, Expected: &Config{ Clusters: []NamedCluster{ { Name: "kind-kind", }, }, Users: []NamedUser{ { Name: "kind-kind", }, }, Contexts: []NamedContext{ { Name: "kind-kind", }, }, }, ExpectError: false, }, { Name: "replace existing", Existing: &Config{ Clusters: []NamedCluster{ { Name: "kind-kind", Cluster: Cluster{ Server: "foo", }, }, }, Users: []NamedUser{ { Name: "kind-kind", }, }, Contexts: []NamedContext{ { Name: "kind-kind", }, }, }, Kind: &Config{ Clusters: []NamedCluster{ { Name: "kind-kind", }, }, Users: []NamedUser{ { Name: "kind-kind", }, }, Contexts: []NamedContext{ { Name: "kind-kind", }, }, }, Expected: &Config{ Clusters: []NamedCluster{ { Name: "kind-kind", }, }, Users: []NamedUser{ { Name: "kind-kind", }, }, Contexts: []NamedContext{ { Name: "kind-kind", }, }, }, ExpectError: false, }, { Name: "add to existing", Existing: &Config{ Clusters: []NamedCluster{ { Name: "kops-blah", Cluster: Cluster{ Server: "foo", }, }, }, Users: []NamedUser{ { Name: "kops-blah", }, }, Contexts: []NamedContext{ { Name: "kops-blah", }, }, }, Kind: &Config{ Clusters: []NamedCluster{ { Name: "kind-kind", }, }, Users: []NamedUser{ { Name: "kind-kind", }, }, Contexts: []NamedContext{ { Name: "kind-kind", }, }, }, Expected: &Config{ Clusters: []NamedCluster{ { Name: "kops-blah", Cluster: Cluster{ Server: "foo", }, }, { Name: "kind-kind", }, }, Users: []NamedUser{ { Name: "kops-blah", }, { Name: "kind-kind", }, }, Contexts: []NamedContext{ { Name: "kops-blah", }, { Name: "kind-kind", }, }, }, ExpectError: false, }, } for _, tc := range cases { tc := tc t.Run(tc.Name, func(t *testing.T) { t.Parallel() err := merge(tc.Existing, tc.Kind) assert.ExpectError(t, tc.ExpectError, err) if !tc.ExpectError && !reflect.DeepEqual(tc.Existing, tc.Expected) { t.Errorf("Merged Config did not equal Expected") t.Errorf("Expected: %+v", tc.Expected) t.Errorf("Actual: %+v", tc.Existing) } }) } } func TestWriteMerged(t *testing.T) { t.Parallel() t.Run("normal merge", testWriteMergedNormal) t.Run("bad kind config", testWriteMergedBogusConfig) t.Run("merge into non-existent file", testWriteMergedNoExistingFile) } func testWriteMergedNormal(t *testing.T) { t.Parallel() dir, err := os.MkdirTemp("", "kind-testwritemerged") if err != nil { t.Fatalf("Failed to create tempdir: %d", err) } defer os.RemoveAll(dir) // create an existing kubeconfig const existingConfig = `clusters: - cluster: certificate-authority-data: definitelyacert server: https://192.168.9.4:6443 name: kind-foo contexts: - context: cluster: kind-foo user: kind-foo name: kind-foo current-context: kind-foo kind: Config apiVersion: v1 preferences: {} users: - name: kind-foo user: client-certificate-data: seemslegit client-key-data: yep ` existingConfigPath := filepath.Join(dir, "existing-kubeconfig") if err := os.WriteFile(existingConfigPath, []byte(existingConfig), os.ModePerm); err != nil { t.Fatalf("Failed to create existing kubeconfig: %d", err) } kindConfig := &Config{ Clusters: []NamedCluster{ { Name: "kind-kind", Cluster: Cluster{ Server: "https://127.0.0.1:6443", OtherFields: map[string]interface{}{ "certificate-authority-data": "definitelyacert", }, }, }, }, Contexts: []NamedContext{ { Name: "kind-kind", Context: Context{ User: "kind-kind", Cluster: "kind-kind", }, }, }, Users: []NamedUser{ { Name: "kind-kind", User: map[string]interface{}{ "client-certificate-data": "seemslegit", "client-key-data": "yep", }, }, }, CurrentContext: "kind-kind", OtherFields: map[string]interface{}{ "apiVersion": "v1", "kind": "Config", "preferences": map[string]interface{}{}, }, } // ensure that we can write this merged config if err := WriteMerged(kindConfig, existingConfigPath); err != nil { t.Fatalf("Failed to write merged kubeconfig: %v", err) } // ensure the output matches expected f, err := os.Open(existingConfigPath) if err != nil { t.Fatalf("Failed to open merged kubeconfig: %v", err) } contents, err := io.ReadAll(f) if err != nil { t.Fatalf("Failed to read merged kubeconfig: %v", err) } expected := `apiVersion: v1 clusters: - cluster: certificate-authority-data: definitelyacert server: https://192.168.9.4:6443 name: kind-foo - cluster: certificate-authority-data: definitelyacert server: https://127.0.0.1:6443 name: kind-kind contexts: - context: cluster: kind-foo user: kind-foo name: kind-foo - context: cluster: kind-kind user: kind-kind name: kind-kind current-context: kind-kind kind: Config preferences: {} users: - name: kind-foo user: client-certificate-data: seemslegit client-key-data: yep - name: kind-kind user: client-certificate-data: seemslegit client-key-data: yep ` assert.StringEqual(t, expected, string(contents)) } func testWriteMergedBogusConfig(t *testing.T) { t.Parallel() dir, err := os.MkdirTemp("", "kind-testwritemerged") if err != nil { t.Fatalf("Failed to create tempdir: %d", err) } defer os.RemoveAll(dir) err = WriteMerged(&Config{}, filepath.Join(dir, "bogus")) assert.ExpectError(t, true, err) } func testWriteMergedNoExistingFile(t *testing.T) { t.Parallel() dir, err := os.MkdirTemp("", "kind-testwritemerged") if err != nil { t.Fatalf("Failed to create tempdir: %d", err) } defer os.RemoveAll(dir) kindConfig := &Config{ Clusters: []NamedCluster{ { Name: "kind-kind", Cluster: Cluster{ Server: "https://127.0.0.1:6443", OtherFields: map[string]interface{}{ "certificate-authority-data": "definitelyacert", }, }, }, }, Contexts: []NamedContext{ { Name: "kind-kind", Context: Context{ User: "kind-kind", Cluster: "kind-kind", }, }, }, Users: []NamedUser{ { Name: "kind-kind", User: map[string]interface{}{ "client-certificate-data": "seemslegit", "client-key-data": "yep", }, }, }, CurrentContext: "kind-kind", OtherFields: map[string]interface{}{ "apiVersion": "v1", "kind": "Config", "preferences": map[string]interface{}{}, }, } nonExistentPath := filepath.Join(dir, "bogus", "extra-bogus") err = WriteMerged(kindConfig, nonExistentPath) assert.ExpectError(t, false, err) // ensure the output matches expected f, err := os.Open(nonExistentPath) if err != nil { t.Fatalf("Failed to open merged kubeconfig: %v", err) } contents, err := io.ReadAll(f) if err != nil { t.Fatalf("Failed to read merged kubeconfig: %v", err) } expected := `apiVersion: v1 clusters: - cluster: certificate-authority-data: definitelyacert server: https://127.0.0.1:6443 name: kind-kind contexts: - context: cluster: kind-kind user: kind-kind name: kind-kind current-context: kind-kind kind: Config preferences: {} users: - name: kind-kind user: client-certificate-data: seemslegit client-key-data: yep ` assert.StringEqual(t, expected, string(contents)) } kind-0.27.0/pkg/cluster/internal/kubeconfig/internal/kubeconfig/paths.go000066400000000000000000000122161475376161000263310ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package kubeconfig import ( "os" "path" "path/filepath" "runtime" "sigs.k8s.io/kind/pkg/internal/sets" ) const kubeconfigEnv = "KUBECONFIG" /* paths returns the list of paths to be considered for kubeconfig files where explicitPath is the value of --kubeconfig # Logic based on kubectl https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands - If the --kubeconfig flag is set, then only that file is loaded. The flag may only be set once and no merging takes place. - If $KUBECONFIG environment variable is set, then it is used as a list of paths (normal path delimiting rules for your system). These paths are merged. When a value is modified, it is modified in the file that defines the stanza. When a value is created, it is created in the first file that exists. - If no files in the chain exist, then it creates the last file in the list. - Otherwise, ${HOME}/.kube/config is used and no merging takes place. */ func paths(explicitPath string, getEnv func(string) string) []string { if explicitPath != "" { return []string{explicitPath} } paths := discardEmptyAndDuplicates( filepath.SplitList(getEnv(kubeconfigEnv)), ) if len(paths) != 0 { return paths } return []string{path.Join(homeDir(runtime.GOOS, getEnv), ".kube", "config")} } // pathForMerge returns the file that kubectl would merge into func pathForMerge(explicitPath string, getEnv func(string) string) string { // find the first file that exists p := paths(explicitPath, getEnv) if len(p) == 1 { return p[0] } for _, filename := range p { if fileExists(filename) { return filename } } // otherwise the last file return p[len(p)-1] } func fileExists(filename string) bool { info, err := os.Stat(filename) if os.IsNotExist(err) { return false } return !info.IsDir() } func discardEmptyAndDuplicates(paths []string) []string { seen := sets.NewString() kept := 0 for _, p := range paths { if p != "" && !seen.Has(p) { paths[kept] = p kept++ seen.Insert(p) } } return paths[:kept] } // homeDir returns the home directory for the current user. // On Windows: // 1. the first of %HOME%, %HOMEDRIVE%%HOMEPATH%, %USERPROFILE% containing a `.kube\config` file is returned. // 2. if none of those locations contain a `.kube\config` file, the first of %HOME%, %USERPROFILE%, %HOMEDRIVE%%HOMEPATH% that exists and is writeable is returned. // 3. if none of those locations are writeable, the first of %HOME%, %USERPROFILE%, %HOMEDRIVE%%HOMEPATH% that exists is returned. // 4. if none of those locations exists, the first of %HOME%, %USERPROFILE%, %HOMEDRIVE%%HOMEPATH% that is set is returned. // NOTE this is from client-go. Rather than pull in client-go for this one // standalone method, we have a fork here. // https://github.com/kubernetes/client-go/blob/6d7018244d72350e2e8c4a19ccdbe4c8083a9143/util/homedir/homedir.go // We've modified this to require injecting os.Getenv and runtime.GOOS as a dependencies for testing purposes func homeDir(GOOS string, getEnv func(string) string) string { if GOOS == "windows" { home := getEnv("HOME") homeDriveHomePath := "" if homeDrive, homePath := getEnv("HOMEDRIVE"), getEnv("HOMEPATH"); len(homeDrive) > 0 && len(homePath) > 0 { homeDriveHomePath = homeDrive + homePath } userProfile := getEnv("USERPROFILE") // Return first of %HOME%, %HOMEDRIVE%/%HOMEPATH%, %USERPROFILE% that contains a `.kube\config` file. // %HOMEDRIVE%/%HOMEPATH% is preferred over %USERPROFILE% for backwards-compatibility. for _, p := range []string{home, homeDriveHomePath, userProfile} { if len(p) == 0 { continue } if _, err := os.Stat(filepath.Join(p, ".kube", "config")); err != nil { continue } return p } firstSetPath := "" firstExistingPath := "" // Prefer %USERPROFILE% over %HOMEDRIVE%/%HOMEPATH% for compatibility with other auth-writing tools for _, p := range []string{home, userProfile, homeDriveHomePath} { if len(p) == 0 { continue } if len(firstSetPath) == 0 { // remember the first path that is set firstSetPath = p } info, err := os.Stat(p) if err != nil { continue } if len(firstExistingPath) == 0 { // remember the first path that exists firstExistingPath = p } if info.IsDir() && info.Mode().Perm()&(1<<(uint(7))) != 0 { // return first path that is writeable return p } } // If none are writeable, return first location that exists if len(firstExistingPath) > 0 { return firstExistingPath } // If none exist, return first location that is set if len(firstSetPath) > 0 { return firstSetPath } // We've got nothing return "" } return getEnv("HOME") } kind-0.27.0/pkg/cluster/internal/kubeconfig/internal/kubeconfig/paths_test.go000066400000000000000000000134771475376161000274020ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package kubeconfig import ( "os" "path" "path/filepath" "strings" "testing" "sigs.k8s.io/kind/pkg/fs" "sigs.k8s.io/kind/pkg/internal/assert" ) func TestPaths(t *testing.T) { t.Parallel() // test explicit kubeconfig t.Run("explicit path", func(t *testing.T) { t.Parallel() explicitPath := "foo" result := paths(explicitPath, func(s string) string { return map[string]string{ "KUBECONFIG": strings.Join([]string{"/foo", "/bar", "", "/foo", "/bar"}, string(filepath.ListSeparator)), "HOME": "/home", }[s] }) expected := []string{explicitPath} assert.DeepEqual(t, expected, result) }) t.Run("KUBECONFIG list", func(t *testing.T) { t.Parallel() result := paths("", func(s string) string { return map[string]string{ "KUBECONFIG": strings.Join([]string{"/foo", "/bar", "", "/foo", "/bar"}, string(filepath.ListSeparator)), "HOME": "/home", }[s] }) expected := []string{"/foo", "/bar"} assert.DeepEqual(t, expected, result) }) t.Run("$HOME/.kube/config", func(t *testing.T) { t.Parallel() result := paths("", func(s string) string { return map[string]string{ "HOME": "/home", }[s] }) expected := []string{"/home/.kube/config"} assert.DeepEqual(t, expected, result) }) } func TestPathForMerge(t *testing.T) { t.Parallel() // create a directory structure with some files to be "kubeconfigs" dir, err := fs.TempDir("", "kind-testwritemerged") if err != nil { t.Fatalf("Failed to create tempdir: %v", err) } defer os.RemoveAll(dir) // create a fake homedir homeDir := filepath.Join(dir, "fake-home") if err := os.Mkdir(homeDir, os.ModePerm); err != nil { t.Fatalf("failed to create fake home dir: %v", err) } // create some fake KUBECONFIG files fakeKubeconfigs := []string{} for _, partialPath := range []string{"foo", "bar", "baz"} { p := filepath.Join(dir, partialPath) fakeKubeconfigs = append(fakeKubeconfigs, p) f, err := os.Create(p) if err != nil { t.Fatalf("failed to create fake kubeconfig file: %v", err) } f.Close() } // test explicit kubeconfig t.Run("explicit path", func(t *testing.T) { explicitPath := "foo" result := pathForMerge(explicitPath, func(s string) string { return map[string]string{ "KUBECONFIG": strings.Join([]string{"/foo", "/bar", "", "/foo", "/bar"}, string(filepath.ListSeparator)), "HOME": "/home", }[s] }) expected := explicitPath assert.StringEqual(t, expected, result) }) t.Run("KUBECONFIG list", func(t *testing.T) { result := pathForMerge("", func(s string) string { return map[string]string{ "KUBECONFIG": strings.Join(fakeKubeconfigs, string(filepath.ListSeparator)), }[s] }) expected := fakeKubeconfigs[0] assert.StringEqual(t, expected, result) }) t.Run("KUBECONFIG select last if none exist", func(t *testing.T) { kubeconfigEnvValue := strings.Join([]string{"/bogus/path", "/bogus/path/two"}, string(filepath.ListSeparator)) result := pathForMerge("", func(s string) string { return map[string]string{ "KUBECONFIG": kubeconfigEnvValue, }[s] }) expected := "/bogus/path/two" assert.StringEqual(t, expected, result) }) } func TestHomeDir(t *testing.T) { t.Parallel() t.Run("windows HOME with .kube/config", func(t *testing.T) { t.Parallel() // create a directory structure with a "kubeconfigs" dir, err := fs.TempDir("", "kind-testwritemerged") if err != nil { t.Fatalf("Failed to create tempdir: %v", err) } defer os.RemoveAll(dir) // create the fake kubeconfig fakeHomeDir := path.Join(dir, "fake-home") fakeKubeConfig := path.Join(fakeHomeDir, ".kube", "config") if err := os.MkdirAll(path.Dir(fakeKubeConfig), os.ModePerm); err != nil { t.Fatalf("Failed to create fake kubeconfig dir: %v", err) } f, err := os.Create(fakeKubeConfig) if err != nil { t.Fatalf("Failed to create tempdir: %v", err) } f.Close() // this should return the fake kubeconfig result := homeDir("windows", func(e string) string { return map[string]string{ "HOME": fakeHomeDir, "HOMEDRIVE": "ZZ:", "HOMEPATH": `ZZ:\Users\fake-user-zzz`, }[e] }) assert.StringEqual(t, fakeHomeDir, result) }) t.Run("windows HOME without .kube/config", func(t *testing.T) { t.Parallel() // create a fake home dir fakeHomeDir, err := fs.TempDir("", "kind-testwritemerged") if err != nil { t.Fatalf("Failed to create tempdir: %v", err) } defer os.RemoveAll(fakeHomeDir) // this should return the fake kubeconfig result := homeDir("windows", func(e string) string { return map[string]string{ "HOME": fakeHomeDir, "HOMEDRIVE": filepath.VolumeName(fakeHomeDir), "HOMEPATH": path.Join("Users", "fake-user-zzz"), }[e] }) assert.StringEqual(t, fakeHomeDir, result) }) t.Run("windows HOME none exist", func(t *testing.T) { t.Parallel() // this should return the fake kubeconfig result := homeDir("windows", func(e string) string { return map[string]string{ "HOME": "Z:/faaaaake", "HOMEDRIVE": "Z:/", "HOMEPATH": path.Join("Users", "fake-user-zzz"), }[e] }) assert.StringEqual(t, "Z:/faaaaake", result) }) t.Run("windows no path", func(t *testing.T) { t.Parallel() // this should return the fake kubeconfig result := homeDir("windows", func(e string) string { return "" }) assert.StringEqual(t, "", result) }) } kind-0.27.0/pkg/cluster/internal/kubeconfig/internal/kubeconfig/read.go000066400000000000000000000041651475376161000261310ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package kubeconfig import ( "io" "os" yaml "gopkg.in/yaml.v3" "sigs.k8s.io/kind/pkg/errors" ) // KINDFromRawKubeadm returns a kind kubeconfig derived from the raw kubeadm kubeconfig, // the kind clusterName, and the server. // server is ignored if unset. func KINDFromRawKubeadm(rawKubeadmKubeConfig, clusterName, server string) (*Config, error) { cfg := &Config{} if err := yaml.Unmarshal([]byte(rawKubeadmKubeConfig), cfg); err != nil { return nil, err } // verify assumptions about kubeadm kubeconfigs if err := checkKubeadmExpectations(cfg); err != nil { return nil, err } // compute unique kubeconfig key for this cluster key := KINDClusterKey(clusterName) // use the unique key for all named references cfg.Clusters[0].Name = key cfg.Users[0].Name = key cfg.Contexts[0].Name = key cfg.Contexts[0].Context.User = key cfg.Contexts[0].Context.Cluster = key cfg.CurrentContext = key // patch server field if server was set if server != "" { cfg.Clusters[0].Cluster.Server = server } return cfg, nil } // read loads a KUBECONFIG file from configPath func read(configPath string) (*Config, error) { // try to open, return default if no such file f, err := os.Open(configPath) if os.IsNotExist(err) { return &Config{}, nil } else if err != nil { return nil, errors.WithStack(err) } // otherwise read in and deserialize cfg := &Config{} rawExisting, err := io.ReadAll(f) if err != nil { return nil, errors.WithStack(err) } if err := yaml.Unmarshal(rawExisting, cfg); err != nil { return nil, errors.WithStack(err) } return cfg, nil } kind-0.27.0/pkg/cluster/internal/kubeconfig/internal/kubeconfig/read_test.go000066400000000000000000000051501475376161000271630ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package kubeconfig import ( "reflect" "testing" "sigs.k8s.io/kind/pkg/internal/assert" ) func TestKINDFromRawKubeadm(t *testing.T) { t.Parallel() // test that a bogus config is caught t.Run("bad config", func(t *testing.T) { t.Parallel() _, err := KINDFromRawKubeadm(" ", "kind", "") assert.ExpectError(t, true, err) }) // test reading a legitimate kubeadm config and converting it to a kind config t.Run("valid config", func(t *testing.T) { const rawConfig = `apiVersion: v1 clusters: - cluster: certificate-authority-data: definitelyacert server: https://192.168.9.4:6443 name: kind contexts: - context: cluster: kind user: kubernetes-admin name: kubernetes-admin@kind current-context: kubernetes-admin@kind kind: Config preferences: {} users: - name: kubernetes-admin user: client-certificate-data: seemslegit client-key-data: yep ` server := "https://127.0.0.1:6443" expected := &Config{ Clusters: []NamedCluster{ { Name: "kind-kind", Cluster: Cluster{ Server: server, OtherFields: map[string]interface{}{ "certificate-authority-data": "definitelyacert", }, }, }, }, Contexts: []NamedContext{ { Name: "kind-kind", Context: Context{ User: "kind-kind", Cluster: "kind-kind", }, }, }, Users: []NamedUser{ { Name: "kind-kind", User: map[string]interface{}{ "client-certificate-data": "seemslegit", "client-key-data": "yep", }, }, }, CurrentContext: "kind-kind", OtherFields: map[string]interface{}{ "apiVersion": "v1", "kind": "Config", "preferences": map[string]interface{}{}, }, } cfg, err := KINDFromRawKubeadm(rawConfig, "kind", server) if err != nil { t.Fatalf("failed to decode kubeconfig: %v", err) } if !reflect.DeepEqual(cfg, expected) { t.Errorf("Read Config did not equal Expected") t.Errorf("Expected: %+v", expected) t.Errorf("Actual: %+v", cfg) t.Errorf("type: %s", reflect.TypeOf(cfg.OtherFields["preferences"])) } }) } kind-0.27.0/pkg/cluster/internal/kubeconfig/internal/kubeconfig/remove.go000066400000000000000000000051141475376161000265060ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package kubeconfig import ( "os" "sigs.k8s.io/kind/pkg/errors" ) // RemoveKIND removes the kind cluster kindClusterName from the KUBECONFIG // files at configPaths func RemoveKIND(kindClusterName string, explicitPath string) error { // remove kind from each if present for _, configPath := range paths(explicitPath, os.Getenv) { if err := func(configPath string) error { // lock before modifying if err := lockFile(configPath); err != nil { return errors.Wrap(err, "failed to lock config file") } defer func(configPath string) { _ = unlockFile(configPath) }(configPath) // read in existing existing, err := read(configPath) if err != nil { return errors.Wrap(err, "failed to read kubeconfig to remove KIND entry") } // remove the kind cluster from the config if remove(existing, kindClusterName) { // write out the updated config if we modified anything if err := write(existing, configPath); err != nil { return err } } return nil }(configPath); err != nil { return err } } return nil } // remove drops kindClusterName entries from the cfg func remove(cfg *Config, kindClusterName string) bool { mutated := false // get kind cluster identifier key := KINDClusterKey(kindClusterName) // filter out kind cluster from clusters kept := 0 for _, c := range cfg.Clusters { if c.Name != key { cfg.Clusters[kept] = c kept++ } else { mutated = true } } cfg.Clusters = cfg.Clusters[:kept] // filter out kind cluster from users kept = 0 for _, u := range cfg.Users { if u.Name != key { cfg.Users[kept] = u kept++ } else { mutated = true } } cfg.Users = cfg.Users[:kept] // filter out kind cluster from contexts kept = 0 for _, c := range cfg.Contexts { if c.Name != key { cfg.Contexts[kept] = c kept++ } else { mutated = true } } cfg.Contexts = cfg.Contexts[:kept] // unset current context if it points to this cluster if cfg.CurrentContext == key { cfg.CurrentContext = "" mutated = true } return mutated } kind-0.27.0/pkg/cluster/internal/kubeconfig/internal/kubeconfig/remove_test.go000066400000000000000000000145011475376161000275450ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package kubeconfig import ( "io" "os" "path/filepath" "testing" "sigs.k8s.io/kind/pkg/internal/assert" ) func TestRemove(t *testing.T) { t.Parallel() cases := []struct { Name string Existing *Config ClusterName string Expected *Config ExpectModified bool }{ { Name: "empty config", Existing: &Config{}, ClusterName: "foo", Expected: &Config{}, ExpectModified: false, }, { Name: "remove kind from only kind", Existing: &Config{ Clusters: []NamedCluster{ { Name: "kind-kind", }, }, Users: []NamedUser{ { Name: "kind-kind", }, }, Contexts: []NamedContext{ { Name: "kind-kind", }, }, }, ClusterName: "kind", Expected: &Config{ Clusters: []NamedCluster{}, Users: []NamedUser{}, Contexts: []NamedContext{}, }, ExpectModified: true, }, { Name: "remove kind, leave kops", Existing: &Config{ Clusters: []NamedCluster{ { Name: "kops-blah", Cluster: Cluster{ Server: "foo", }, }, { Name: "kind-kind", }, }, Users: []NamedUser{ { Name: "kops-blah", }, { Name: "kind-kind", }, }, Contexts: []NamedContext{ { Name: "kops-blah", }, { Name: "kind-kind", }, }, CurrentContext: "kind-kind", }, ClusterName: "kind", Expected: &Config{ Clusters: []NamedCluster{ { Name: "kops-blah", Cluster: Cluster{ Server: "foo", }, }, }, Users: []NamedUser{ { Name: "kops-blah", }, }, Contexts: []NamedContext{ { Name: "kops-blah", }, }, CurrentContext: "", }, ExpectModified: true, }, } for _, tc := range cases { tc := tc t.Run(tc.Name, func(t *testing.T) { t.Parallel() modified := remove(tc.Existing, tc.ClusterName) if modified != tc.ExpectModified { if tc.ExpectModified { t.Errorf("Expected config to be modified but got modified == false") } else { t.Errorf("Expected config to be modified but got modified == true") } } assert.DeepEqual(t, tc.Expected, tc.Existing) }) } } func TestRemoveKIND(t *testing.T) { t.Parallel() t.Run("only kind", testRemoveKINDTrivial) t.Run("leave another cluster", testRemoveKINDKeepOther) } func testRemoveKINDTrivial(t *testing.T) { t.Parallel() dir, err := os.MkdirTemp("", "kind-testremovekind") if err != nil { t.Fatalf("Failed to create tempdir: %d", err) } defer os.RemoveAll(dir) // create an existing kubeconfig const existingConfig = `clusters: - cluster: certificate-authority-data: definitelyacert server: https://192.168.9.4:6443 name: kind-foo contexts: - context: cluster: kind-foo user: kind-foo name: kind-foo current-context: kind-foo kind: Config apiVersion: v1 preferences: {} users: - name: kind-foo user: client-certificate-data: seemslegit client-key-data: yep ` existingConfigPath := filepath.Join(dir, "existing-kubeconfig") if err := os.WriteFile(existingConfigPath, []byte(existingConfig), os.ModePerm); err != nil { t.Fatalf("Failed to create existing kubeconfig: %d", err) } // ensure that we can write this merged config if err := RemoveKIND("foo", existingConfigPath); err != nil { t.Fatalf("Failed to remove kind from kubeconfig: %v", err) } // ensure the output matches expected f, err := os.Open(existingConfigPath) if err != nil { t.Fatalf("Failed to open merged kubeconfig: %v", err) } contents, err := io.ReadAll(f) if err != nil { t.Fatalf("Failed to read merged kubeconfig: %v", err) } expected := `apiVersion: v1 kind: Config preferences: {} ` assert.StringEqual(t, expected, string(contents)) } func testRemoveKINDKeepOther(t *testing.T) { // tests removing a kind cluster but keeping another cluster t.Parallel() dir, err := os.MkdirTemp("", "kind-testremovekind") if err != nil { t.Fatalf("Failed to create tempdir: %d", err) } defer os.RemoveAll(dir) // create an existing kubeconfig const existingConfig = `clusters: - cluster: certificate-authority-data: definitelyacert server: https://192.168.9.4:6443 name: kind-foo - cluster: certificate-authority-data: definitelyacert server: https://192.168.9.4:6443 name: kops-foo contexts: - context: cluster: kind-foo user: kind-foo name: kind-foo - context: cluster: kops-foo user: kops-foo name: kops-foo current-context: kops-foo kind: Config apiVersion: v1 preferences: {} users: - name: kind-foo user: client-certificate-data: seemslegit client-key-data: yep - name: kops-foo user: client-certificate-data: seemslegit client-key-data: yep ` existingConfigPath := filepath.Join(dir, "existing-kubeconfig") if err := os.WriteFile(existingConfigPath, []byte(existingConfig), os.ModePerm); err != nil { t.Fatalf("Failed to create existing kubeconfig: %d", err) } // ensure that we can write this merged config if err := RemoveKIND("foo", existingConfigPath); err != nil { t.Fatalf("Failed to remove kind from kubeconfig: %v", err) } // ensure the output matches expected f, err := os.Open(existingConfigPath) if err != nil { t.Fatalf("Failed to open merged kubeconfig: %v", err) } contents, err := io.ReadAll(f) if err != nil { t.Fatalf("Failed to read merged kubeconfig: %v", err) } expected := `apiVersion: v1 clusters: - cluster: certificate-authority-data: definitelyacert server: https://192.168.9.4:6443 name: kops-foo contexts: - context: cluster: kops-foo user: kops-foo name: kops-foo current-context: kops-foo kind: Config preferences: {} users: - name: kops-foo user: client-certificate-data: seemslegit client-key-data: yep ` assert.StringEqual(t, expected, string(contents)) } kind-0.27.0/pkg/cluster/internal/kubeconfig/internal/kubeconfig/types.go000066400000000000000000000067341475376161000263660ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package kubeconfig /* NOTE: all of these types are based on the upstream v1 types from client-go https://github.com/kubernetes/client-go/blob/0bdba2f9188006fc64057c2f6d82a0f9ee0ee422/tools/clientcmd/api/v1/types.go We've forked them to: - remove types and fields kind does not need to inspect / modify - generically support fields kind doesn't inspect / modify using yaml.v3 - have clearer names (AuthInfo -> User) */ // Config represents a KUBECONFIG, with the fields kind is likely to use // Other fields are handled as unstructured data purely read for writing back // to disk via the OtherFields field type Config struct { // Clusters is a map of referenceable names to cluster configs Clusters []NamedCluster `yaml:"clusters,omitempty"` // Users is a map of referenceable names to user configs Users []NamedUser `yaml:"users,omitempty"` // Contexts is a map of referenceable names to context configs Contexts []NamedContext `yaml:"contexts,omitempty"` // CurrentContext is the name of the context that you would like to use by default CurrentContext string `yaml:"current-context,omitempty"` // OtherFields contains fields kind does not inspect or modify, these are // read purely for writing back OtherFields map[string]interface{} `yaml:",inline,omitempty"` } // NamedCluster relates nicknames to cluster information type NamedCluster struct { // Name is the nickname for this Cluster Name string `yaml:"name"` // Cluster holds the cluster information Cluster Cluster `yaml:"cluster"` } // Cluster contains information about how to communicate with a kubernetes cluster type Cluster struct { // Server is the address of the kubernetes cluster (https://hostname:port). Server string `yaml:"server,omitempty"` // OtherFields contains fields kind does not inspect or modify, these are // read purely for writing back OtherFields map[string]interface{} `yaml:",inline,omitempty"` } // NamedUser relates nicknames to user information type NamedUser struct { // Name is the nickname for this User Name string `yaml:"name"` // User holds the user information // We do not touch this and merely write it back User map[string]interface{} `yaml:"user"` } // NamedContext relates nicknames to context information type NamedContext struct { // Name is the nickname for this Context Name string `yaml:"name"` // Context holds the context information Context Context `yaml:"context"` } // Context is a tuple of references to a cluster (how do I communicate with a kubernetes cluster), a user (how do I identify myself), and a namespace (what subset of resources do I want to work with) type Context struct { // Cluster is the name of the cluster for this context Cluster string `yaml:"cluster"` // User is the name of the User for this context User string `yaml:"user"` // OtherFields contains fields kind does not inspect or modify, these are // read purely for writing back OtherFields map[string]interface{} `yaml:",inline,omitempty"` } kind-0.27.0/pkg/cluster/internal/kubeconfig/internal/kubeconfig/write.go000066400000000000000000000023501475376161000263420ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package kubeconfig import ( "os" "path/filepath" "sigs.k8s.io/kind/pkg/errors" ) // write writes cfg to configPath // it will ensure the directories in the path if necessary func write(cfg *Config, configPath string) error { encoded, err := Encode(cfg) if err != nil { return err } // NOTE: 0755 / 0600 are to match client-go dir := filepath.Dir(configPath) if _, err := os.Stat(dir); os.IsNotExist(err) { if err = os.MkdirAll(dir, 0755); err != nil { return errors.Wrap(err, "failed to create directory for KUBECONFIG") } } if err := os.WriteFile(configPath, encoded, 0600); err != nil { return errors.Wrap(err, "failed to write KUBECONFIG") } return nil } kind-0.27.0/pkg/cluster/internal/kubeconfig/internal/kubeconfig/write_test.go000066400000000000000000000050621475376161000274040ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package kubeconfig import ( "io" "os" "path/filepath" "testing" "sigs.k8s.io/kind/pkg/internal/assert" ) func TestWrite(t *testing.T) { t.Parallel() t.Run("non-existent file", testWriteNoExistingFile) } func testWriteNoExistingFile(t *testing.T) { t.Parallel() dir, err := os.MkdirTemp("", "kind-testwritemerged") if err != nil { t.Fatalf("Failed to create tempdir: %d", err) } defer os.RemoveAll(dir) kindConfig := &Config{ Clusters: []NamedCluster{ { Name: "kind-kind", Cluster: Cluster{ Server: "https://127.0.0.1:6443", OtherFields: map[string]interface{}{ "certificate-authority-data": "definitelyacert", }, }, }, }, Contexts: []NamedContext{ { Name: "kind-kind", Context: Context{ User: "kind-kind", Cluster: "kind-kind", }, }, }, Users: []NamedUser{ { Name: "kind-kind", User: map[string]interface{}{ "client-certificate-data": "seemslegit", "client-key-data": "yep", }, }, }, CurrentContext: "kind-kind", OtherFields: map[string]interface{}{ "apiVersion": "v1", "kind": "Config", "preferences": map[string]interface{}{}, }, } nonExistentPath := filepath.Join(dir, "bogus", "extra-bogus") err = write(kindConfig, nonExistentPath) assert.ExpectError(t, false, err) // ensure the output matches expected f, err := os.Open(nonExistentPath) if err != nil { t.Fatalf("Failed to open merged kubeconfig: %v", err) } contents, err := io.ReadAll(f) if err != nil { t.Fatalf("Failed to read merged kubeconfig: %v", err) } expected := `apiVersion: v1 clusters: - cluster: certificate-authority-data: definitelyacert server: https://127.0.0.1:6443 name: kind-kind contexts: - context: cluster: kind-kind user: kind-kind name: kind-kind current-context: kind-kind kind: Config preferences: {} users: - name: kind-kind user: client-certificate-data: seemslegit client-key-data: yep ` assert.StringEqual(t, expected, string(contents)) } kind-0.27.0/pkg/cluster/internal/kubeconfig/kubeconfig.go000066400000000000000000000064661475376161000234100ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package kubeconfig provides utilities kind uses internally to manage // kind cluster kubeconfigs package kubeconfig import ( "bytes" "sigs.k8s.io/kind/pkg/cluster/nodeutils" "sigs.k8s.io/kind/pkg/errors" // this package has slightly more generic kubeconfig helpers // and minimal dependencies on the rest of kind "sigs.k8s.io/kind/pkg/cluster/internal/kubeconfig/internal/kubeconfig" "sigs.k8s.io/kind/pkg/cluster/internal/providers" ) // Export exports the kubeconfig given the cluster context and a path to write it to // This will always be an external kubeconfig func Export(p providers.Provider, name, explicitPath string, external bool) error { cfg, err := get(p, name, external) if err != nil { return err } return kubeconfig.WriteMerged(cfg, explicitPath) } // Remove removes clusterName from the kubeconfig paths detected based on // either explicitPath being set or $KUBECONFIG or $HOME/.kube/config, following // the rules set by kubectl // clusterName must identify a kind cluster. func Remove(clusterName, explicitPath string) error { return kubeconfig.RemoveKIND(clusterName, explicitPath) } // Get returns the kubeconfig for the cluster // external controls if the internal IP address is used or the host endpoint func Get(p providers.Provider, name string, external bool) (string, error) { cfg, err := get(p, name, external) if err != nil { return "", err } b, err := kubeconfig.Encode(cfg) if err != nil { return "", err } return string(b), err } // ContextForCluster returns the context name for a kind cluster based on // its name. This key is used for all list entries of kind clusters func ContextForCluster(kindClusterName string) string { return kubeconfig.KINDClusterKey(kindClusterName) } func get(p providers.Provider, name string, external bool) (*kubeconfig.Config, error) { // find a control plane node to get the kubeadm config from n, err := p.ListNodes(name) if err != nil { return nil, err } var buff bytes.Buffer nodes, err := nodeutils.ControlPlaneNodes(n) if err != nil { return nil, err } if len(nodes) < 1 { return nil, errors.Errorf("could not locate any control plane nodes for cluster named '%s'. "+ "Use the --name option to select a different cluster", name) } node := nodes[0] // grab kubeconfig version from the node if err := node.Command("cat", "/etc/kubernetes/admin.conf").SetStdout(&buff).Run(); err != nil { return nil, errors.Wrap(err, "failed to get cluster internal kubeconfig") } // if we're doing external we need to override the server endpoint server := "" if external { endpoint, err := p.GetAPIServerEndpoint(name) if err != nil { return nil, err } server = "https://" + endpoint } // actually encode return kubeconfig.KINDFromRawKubeadm(buff.String(), name, server) } kind-0.27.0/pkg/cluster/internal/loadbalancer/000077500000000000000000000000001475376161000212245ustar00rootroot00000000000000kind-0.27.0/pkg/cluster/internal/loadbalancer/config.go000066400000000000000000000044341475376161000230250ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package loadbalancer import ( "bytes" "text/template" "sigs.k8s.io/kind/pkg/errors" ) // ConfigData is supplied to the loadbalancer config template type ConfigData struct { ControlPlanePort int BackendServers map[string]string IPv6 bool } // DefaultConfigTemplate is the loadbalancer config template const DefaultConfigTemplate = `# generated by kind global log /dev/log local0 log /dev/log local1 notice daemon # limit memory usage to approximately 18 MB maxconn 100000 resolvers docker nameserver dns 127.0.0.11:53 defaults log global mode tcp option dontlognull # TODO: tune these timeout connect 5000 timeout client 50000 timeout server 50000 # allow to boot despite dns don't resolve backends default-server init-addr none frontend control-plane bind *:{{ .ControlPlanePort }} {{ if .IPv6 -}} bind :::{{ .ControlPlanePort }}; {{- end }} default_backend kube-apiservers backend kube-apiservers option httpchk GET /healthz # TODO: we should be verifying (!) {{range $server, $address := .BackendServers}} server {{ $server }} {{ $address }} check check-ssl verify none resolvers docker resolve-prefer {{ if $.IPv6 -}} ipv6 {{- else -}} ipv4 {{- end }} {{- end}} ` // Config returns a kubeadm config generated from config data, in particular // the kubernetes version func Config(data *ConfigData) (config string, err error) { t, err := template.New("loadbalancer-config").Parse(DefaultConfigTemplate) if err != nil { return "", errors.Wrap(err, "failed to parse config template") } // execute the template var buff bytes.Buffer err = t.Execute(&buff, data) if err != nil { return "", errors.Wrap(err, "error executing config template") } return buff.String(), nil } kind-0.27.0/pkg/cluster/internal/loadbalancer/const.go000066400000000000000000000014621475376161000227040ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package loadbalancer // Image defines the loadbalancer image:tag const Image = "docker.io/kindest/haproxy:v20230606-42a2262b" // ConfigPath defines the path to the config file in the image const ConfigPath = "/usr/local/etc/haproxy/haproxy.cfg" kind-0.27.0/pkg/cluster/internal/loadbalancer/doc.go000066400000000000000000000012531475376161000223210ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package loadbalancer contains external loadbalancer related constants and configuration package loadbalancer kind-0.27.0/pkg/cluster/internal/logs/000077500000000000000000000000001475376161000175615ustar00rootroot00000000000000kind-0.27.0/pkg/cluster/internal/logs/doc.go000066400000000000000000000012041475376161000206520ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package logs contains tooling for obtaining cluster logs package logs kind-0.27.0/pkg/cluster/internal/logs/logs.go000066400000000000000000000055611475376161000210630ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package logs import ( "archive/tar" "fmt" "io" "os" "path" "path/filepath" "al.essio.dev/pkg/shellescape" "sigs.k8s.io/kind/pkg/cluster/nodes" "sigs.k8s.io/kind/pkg/errors" "sigs.k8s.io/kind/pkg/exec" "sigs.k8s.io/kind/pkg/log" ) // DumpDir dumps the dir nodeDir on the node to the dir hostDir on the host func DumpDir(logger log.Logger, node nodes.Node, nodeDir, hostDir string) (err error) { cmd := node.Command( "sh", "-c", // Tar will exit 1 if a file changed during the archival. // We don't care about this, so we're invoking it in a shell // And masking out 1 as a return value. // Fatal errors will return exit code 2. // http://man7.org/linux/man-pages/man1/tar.1.html#RETURN_VALUE fmt.Sprintf( `tar --hard-dereference -C %s -chf - . || (r=$?; [ $r -eq 1 ] || exit $r)`, shellescape.Quote(path.Clean(nodeDir)+"/"), ), ) return exec.RunWithStdoutReader(cmd, func(outReader io.Reader) error { if err := untar(logger, outReader, hostDir); err != nil { return errors.Wrapf(err, "Untarring %q: %v", nodeDir, err) } return nil }) } // untar reads the tar file from r and writes it into dir. func untar(logger log.Logger, r io.Reader, dir string) (err error) { tr := tar.NewReader(r) for { f, err := tr.Next() switch { case err == io.EOF: // drain the reader, which may have trailing null bytes // we don't want to leave the writer hanging _, err := io.Copy(io.Discard, r) return err case err != nil: return errors.Wrapf(err, "tar reading error: %v", err) case f == nil: continue } rel := filepath.FromSlash(f.Name) abs := filepath.Join(dir, rel) switch f.Typeflag { case tar.TypeReg: wf, err := os.OpenFile(abs, os.O_CREATE|os.O_RDWR, os.FileMode(f.Mode)) if err != nil { return err } n, err := io.Copy(wf, tr) if closeErr := wf.Close(); closeErr != nil && err == nil { err = closeErr } if err != nil { return errors.Errorf("error writing to %s: %v", abs, err) } if n != f.Size { return errors.Errorf("only wrote %d bytes to %s; expected %d", n, abs, f.Size) } case tar.TypeDir: if _, err := os.Stat(abs); err != nil { if err := os.MkdirAll(abs, 0755); err != nil { return err } } default: logger.Warnf("tar file entry %s contained unsupported file type %v", f.Name, f.Typeflag) } } } kind-0.27.0/pkg/cluster/internal/providers/000077500000000000000000000000001475376161000206325ustar00rootroot00000000000000kind-0.27.0/pkg/cluster/internal/providers/common/000077500000000000000000000000001475376161000221225ustar00rootroot00000000000000kind-0.27.0/pkg/cluster/internal/providers/common/cgroups.go000066400000000000000000000054651475376161000241450ustar00rootroot00000000000000/* Copyright 2021 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package common import ( "bufio" "context" "os" "regexp" "sync" "sigs.k8s.io/kind/pkg/errors" "sigs.k8s.io/kind/pkg/exec" ) var nodeReachedCgroupsReadyRegexp *regexp.Regexp var nodeReachedCgroupsReadyRegexpCompileOnce sync.Once // NodeReachedCgroupsReadyRegexp returns a regexp for use with WaitUntilLogRegexpMatches // // This is used to avoid "ERROR: this script needs /sys/fs/cgroup/cgroup.procs to be empty (for writing the top-level cgroup.subtree_control)" // See https://github.com/kubernetes-sigs/kind/issues/2409 // // This pattern matches either "detected cgroupv1" from the kind node image's entrypoint logs // or "Multi-User System" target if is using cgroups v2, // so that `docker exec` can be executed safely without breaking cgroup v2 hierarchy. func NodeReachedCgroupsReadyRegexp() *regexp.Regexp { nodeReachedCgroupsReadyRegexpCompileOnce.Do(func() { // This is an approximation, see: https://github.com/kubernetes-sigs/kind/pull/2421 nodeReachedCgroupsReadyRegexp = regexp.MustCompile("Reached target .*Multi-User System.*|detected cgroup v1") }) return nodeReachedCgroupsReadyRegexp } // WaitUntilLogRegexpMatches waits until logCmd output produces a line matching re. // It will use logCtx to determine if the logCmd deadline was exceeded for producing // the most useful error message in failure cases, logCtx should be the context // supplied to create logCmd with CommandContext func WaitUntilLogRegexpMatches(logCtx context.Context, logCmd exec.Cmd, re *regexp.Regexp) error { pr, pw, err := os.Pipe() if err != nil { return err } logCmd.SetStdout(pw) logCmd.SetStderr(pw) defer pr.Close() cmdErrC := make(chan error, 1) go func() { defer pw.Close() cmdErrC <- logCmd.Run() }() sc := bufio.NewScanner(pr) for sc.Scan() { line := sc.Text() if re.MatchString(line) { return nil } } // when we timeout the process will have been killed due to the timeout, which is not interesting // in other cases if the command errored this may be a useful error if ctxErr := logCtx.Err(); ctxErr != context.DeadlineExceeded { if cmdErr := <-cmdErrC; cmdErr != nil { return errors.Wrap(cmdErr, "failed to read logs") } } // otherwise generic error return errors.Errorf("could not find a log line that matches %q", re.String()) } kind-0.27.0/pkg/cluster/internal/providers/common/constants.go000066400000000000000000000013321475376161000244640ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package common // APIServerInternalPort defines the port where the control plane is listening // _inside_ the node network const APIServerInternalPort = 6443 kind-0.27.0/pkg/cluster/internal/providers/common/doc.go000066400000000000000000000012141475376161000232140ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impliep. See the License for the specific language governing permissions and limitations under the License. */ // Package common contains common code for implementing providers package common kind-0.27.0/pkg/cluster/internal/providers/common/getport.go000066400000000000000000000032251475376161000241370ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impliep. See the License for the specific language governing permissions and limitations under the License. */ package common import ( "net" ) // PortOrGetFreePort is a helper that either returns the provided port // if valid or returns a new free port on listenAddr and a cleanup function func PortOrGetFreePort(port int32, listenAddr string) (int32, func(), error) { // in the case of -1 we actually want to pass 0 to the backend to let it pick if port == -1 { return 0, nil, nil } // in the case of 0 (unset) we want kind to pick one and supply it to the backend if port == 0 { return GetFreePort(listenAddr) } // otherwise keep the port return port, nil, nil } // GetFreePort is a helper used to get a free TCP port on the host // returns the free port and a cleanup function, the cleanup function must be called // after all free ports have been determined to ensure the same port is not returned // multiple times func GetFreePort(listenAddr string) (int32, func(), error) { dummyListener, err := net.Listen("tcp", net.JoinHostPort(listenAddr, "0")) if err != nil { return 0, nil, err } port := dummyListener.Addr().(*net.TCPAddr).Port return int32(port), func() { dummyListener.Close() }, nil } kind-0.27.0/pkg/cluster/internal/providers/common/getport_test.go000066400000000000000000000045331475376161000252010ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impliep. See the License for the specific language governing permissions and limitations under the License. */ package common import "testing" func TestPortOrGetFreePort(t *testing.T) { t.Parallel() tests := []struct { name string port int32 want int32 wantErr bool }{ { name: "Valid port", port: 80, want: 80, wantErr: false, }, { name: "No port", wantErr: false, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() got, releaseHostPortFn, err := PortOrGetFreePort(tt.port, "localhost") if releaseHostPortFn != nil { releaseHostPortFn() } if (err != nil) != tt.wantErr { t.Errorf("PortOrGetFreePort() error = %v, wantErr %v", err, tt.wantErr) return } if tt.want != 0 && got != tt.want { t.Errorf("PortOrGetFreePort() = %v, want %v", got, tt.want) } }) } } func TestGetFreePort(t *testing.T) { tests := []struct { name string listenAddr string wantErr bool }{ { name: "listen on localhost", listenAddr: "localhost", wantErr: false, }, { name: "listen on IPv4 localhost address", listenAddr: "127.0.0.1", wantErr: false, }, { name: "listen on IPv4 non existent address", listenAddr: "88.88.88.0", wantErr: true, }, { name: "listen on IPv6 non existent address", listenAddr: "2112:beaf:beaf:2:3", wantErr: true, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() got, releaseHostPortFn, err := GetFreePort(tt.listenAddr) if releaseHostPortFn != nil { releaseHostPortFn() } if (err != nil) != tt.wantErr { t.Errorf("GetFreePort() error = %v, wantErr %v", err, tt.wantErr) return } if got < 0 || got > 65535 && err != nil { t.Errorf("GetFreePort() = %v is not a valid port number ", got) } }) } } kind-0.27.0/pkg/cluster/internal/providers/common/images.go000066400000000000000000000020561475376161000237210ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impliep. See the License for the specific language governing permissions and limitations under the License. */ package common import ( "sigs.k8s.io/kind/pkg/internal/apis/config" "sigs.k8s.io/kind/pkg/internal/sets" ) // RequiredNodeImages returns the set of _node_ images specified by the config // This does not include the loadbalancer image, and is only used to improve // the UX by explicit pulling the node images prior to running func RequiredNodeImages(cfg *config.Cluster) sets.String { images := sets.NewString() for _, node := range cfg.Nodes { images.Insert(node.Image) } return images } kind-0.27.0/pkg/cluster/internal/providers/common/images_test.go000066400000000000000000000032531475376161000247600ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impliep. See the License for the specific language governing permissions and limitations under the License. */ package common import ( "reflect" "testing" "sigs.k8s.io/kind/pkg/internal/apis/config" "sigs.k8s.io/kind/pkg/internal/sets" ) func TestRequiredNodeImages(t *testing.T) { t.Parallel() tests := []struct { name string cluster *config.Cluster want sets.String }{ { name: "Cluster with different images", cluster: func() *config.Cluster { c := config.Cluster{} n, n2 := config.Node{}, config.Node{} n.Image = "node1" n2.Image = "node2" c.Nodes = []config.Node{n, n2} return &c }(), want: sets.NewString("node1", "node2"), }, { name: "Cluster with nodes with same image", cluster: func() *config.Cluster { c := config.Cluster{} n, n2 := config.Node{}, config.Node{} n.Image = "node1" n2.Image = "node1" c.Nodes = []config.Node{n, n2} return &c }(), want: sets.NewString("node1"), }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() if got := RequiredNodeImages(tt.cluster); !reflect.DeepEqual(got, tt.want) { t.Errorf("RequiredNodeImages() = %v, want %v", got, tt.want) } }) } } kind-0.27.0/pkg/cluster/internal/providers/common/logs.go000066400000000000000000000026071475376161000234220ustar00rootroot00000000000000package common import ( "os" "path/filepath" "sigs.k8s.io/kind/pkg/cluster/nodes" "sigs.k8s.io/kind/pkg/errors" "sigs.k8s.io/kind/pkg/exec" ) // CollectLogs provides the common functionality // to get various debug info from the node func CollectLogs(n nodes.Node, dir string) error { execToPathFn := func(cmd exec.Cmd, path string) func() error { return func() error { f, err := FileOnHost(filepath.Join(dir, path)) if err != nil { return err } defer f.Close() return cmd.SetStdout(f).SetStderr(f).Run() } } return errors.AggregateConcurrent([]func() error{ // record info about the node container execToPathFn( n.Command("cat", "/kind/version"), "kubernetes-version.txt", ), execToPathFn( n.Command("journalctl", "--no-pager"), "journal.log", ), execToPathFn( n.Command("journalctl", "--no-pager", "-u", "kubelet.service"), "kubelet.log", ), execToPathFn( n.Command("journalctl", "--no-pager", "-u", "containerd.service"), "containerd.log", ), execToPathFn( n.Command("crictl", "images"), "images.log", ), }) } // FileOnHost is a helper to create a file at path // even if the parent directory doesn't exist // in which case it will be created with ModePerm func FileOnHost(path string) (*os.File, error) { if err := os.MkdirAll(filepath.Dir(path), os.ModePerm); err != nil { return nil, err } return os.Create(path) } kind-0.27.0/pkg/cluster/internal/providers/common/namer.go000066400000000000000000000020501475376161000235500ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impliep. See the License for the specific language governing permissions and limitations under the License. */ package common import ( "fmt" ) // MakeNodeNamer returns a func(role string)(nodeName string) // used to name nodes based on their role and the clusterName func MakeNodeNamer(clusterName string) func(string) string { counter := make(map[string]int) return func(role string) string { count := 1 suffix := "" if v, ok := counter[role]; ok { count += v suffix = fmt.Sprintf("%d", count) } counter[role] = count return fmt.Sprintf("%s-%s%s", clusterName, role, suffix) } } kind-0.27.0/pkg/cluster/internal/providers/common/namer_test.go000066400000000000000000000036371475376161000246230ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impliep. See the License for the specific language governing permissions and limitations under the License. */ package common import ( "testing" "sigs.k8s.io/kind/pkg/internal/assert" ) func TestMakeNodeNamer(t *testing.T) { t.Parallel() cases := []struct { name string clusterName string nodes []string // list of role nodes that belong to the cluster want []string }{ { name: "Default cluster name one node", clusterName: "kind", nodes: []string{"control-plane"}, want: []string{"kind-control-plane"}, }, { name: "Cluster with 3 nodes", clusterName: "kind-test", nodes: []string{"control-plane", "worker", "worker"}, want: []string{"kind-test-control-plane", "kind-test-worker", "kind-test-worker2"}, }, { name: "Cluster with many nodes", clusterName: "ab1", nodes: []string{"control-plane", "control-plane", "control-plane", "external-load-balancer", "worker", "worker", "worker"}, want: []string{"ab1-control-plane", "ab1-control-plane2", "ab1-control-plane3", "ab1-external-load-balancer", "ab1-worker", "ab1-worker2", "ab1-worker3"}, }, } for _, tc := range cases { tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() var names []string nodeNamer := MakeNodeNamer(tc.clusterName) for _, nodeRole := range tc.nodes { names = append(names, nodeNamer(nodeRole)) } assert.DeepEqual(t, tc.want, names) }) } } kind-0.27.0/pkg/cluster/internal/providers/common/proxy.go000066400000000000000000000034751475376161000236430ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package common import ( "os" "strings" "sigs.k8s.io/kind/pkg/internal/apis/config" ) const ( // HTTPProxy is the HTTP_PROXY environment variable key HTTPProxy = "HTTP_PROXY" // HTTPSProxy is the HTTPS_PROXY environment variable key HTTPSProxy = "HTTPS_PROXY" // NOProxy is the NO_PROXY environment variable key NOProxy = "NO_PROXY" ) // GetProxyEnvs returns a map of proxy environment variables to their values // If proxy settings are set, NO_PROXY is modified to include the cluster subnets func GetProxyEnvs(cfg *config.Cluster) map[string]string { return getProxyEnvs(cfg, os.Getenv) } func getProxyEnvs(cfg *config.Cluster, getEnv func(string) string) map[string]string { envs := make(map[string]string) for _, name := range []string{HTTPProxy, HTTPSProxy, NOProxy} { val := getEnv(name) if val == "" { val = getEnv(strings.ToLower(name)) } if val != "" { envs[name] = val envs[strings.ToLower(name)] = val } } // Specifically add the cluster subnets to NO_PROXY if we are using a proxy if len(envs) > 0 { noProxy := envs[NOProxy] if noProxy != "" { noProxy += "," } noProxy += cfg.Networking.ServiceSubnet + "," + cfg.Networking.PodSubnet envs[NOProxy] = noProxy envs[strings.ToLower(NOProxy)] = noProxy } return envs } kind-0.27.0/pkg/cluster/internal/providers/common/proxy_test.go000066400000000000000000000060251475376161000246740ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package common import ( "testing" "sigs.k8s.io/kind/pkg/internal/apis/config" "sigs.k8s.io/kind/pkg/internal/assert" ) func TestGetProxyEnvs(t *testing.T) { t.Parallel() // first test the public method cfg := &config.Cluster{} config.SetDefaultsCluster(cfg) envs := GetProxyEnvs(cfg) // GetProxyEnvs should always return a valid map if envs == nil { t.Errorf("GetProxyEnvs returned nil but should not") } // now test the internal one (with all of the logic) cases := []struct { name string cluster *config.Cluster env map[string]string want map[string]string }{ { name: "No environment variables", cluster: func() *config.Cluster { c := config.Cluster{} c.Networking.ServiceSubnet = "10.0.0.0/24" c.Networking.PodSubnet = "12.0.0.0/24" return &c }(), want: map[string]string{}, }, { name: "HTTP_PROXY environment variables", cluster: func() *config.Cluster { c := config.Cluster{} c.Networking.ServiceSubnet = "10.0.0.0/24" c.Networking.PodSubnet = "12.0.0.0/24" return &c }(), env: map[string]string{ "HTTP_PROXY": "5.5.5.5", }, want: map[string]string{"HTTP_PROXY": "5.5.5.5", "http_proxy": "5.5.5.5", "NO_PROXY": "10.0.0.0/24,12.0.0.0/24", "no_proxy": "10.0.0.0/24,12.0.0.0/24"}, }, { name: "HTTPS_PROXY environment variables", cluster: func() *config.Cluster { c := config.Cluster{} c.Networking.ServiceSubnet = "10.0.0.0/24" c.Networking.PodSubnet = "12.0.0.0/24" return &c }(), env: map[string]string{ "HTTPS_PROXY": "5.5.5.5", }, want: map[string]string{"HTTPS_PROXY": "5.5.5.5", "https_proxy": "5.5.5.5", "NO_PROXY": "10.0.0.0/24,12.0.0.0/24", "no_proxy": "10.0.0.0/24,12.0.0.0/24"}, }, { name: "NO_PROXY environment variables", cluster: func() *config.Cluster { c := config.Cluster{} c.Networking.ServiceSubnet = "10.0.0.0/24" c.Networking.PodSubnet = "12.0.0.0/24" return &c }(), env: map[string]string{ "HTTPS_PROXY": "5.5.5.5", "NO_PROXY": "8.8.8.8", }, want: map[string]string{"HTTPS_PROXY": "5.5.5.5", "https_proxy": "5.5.5.5", "NO_PROXY": "8.8.8.8,10.0.0.0/24,12.0.0.0/24", "no_proxy": "8.8.8.8,10.0.0.0/24,12.0.0.0/24"}, }, } for _, tc := range cases { tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() result := getProxyEnvs(tc.cluster, func(e string) string { if tc.env == nil { return "" } return tc.env[e] }) assert.DeepEqual(t, tc.want, result) }) } } kind-0.27.0/pkg/cluster/internal/providers/docker/000077500000000000000000000000001475376161000221015ustar00rootroot00000000000000kind-0.27.0/pkg/cluster/internal/providers/docker/OWNERS000066400000000000000000000000371475376161000230410ustar00rootroot00000000000000labels: - area/provider/docker kind-0.27.0/pkg/cluster/internal/providers/docker/constants.go000066400000000000000000000015411475376161000244450ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impliep. See the License for the specific language governing permissions and limitations under the License. */ package docker // clusterLabelKey is applied to each "node" docker container for identification const clusterLabelKey = "io.x-k8s.kind.cluster" // nodeRoleLabelKey is applied to each "node" docker container for categorization // of nodes by role const nodeRoleLabelKey = "io.x-k8s.kind.role" kind-0.27.0/pkg/cluster/internal/providers/docker/images.go000066400000000000000000000060031475376161000236740ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package docker import ( "fmt" "strings" "time" "sigs.k8s.io/kind/pkg/errors" "sigs.k8s.io/kind/pkg/exec" "sigs.k8s.io/kind/pkg/log" "sigs.k8s.io/kind/pkg/cluster/internal/providers/common" "sigs.k8s.io/kind/pkg/internal/apis/config" "sigs.k8s.io/kind/pkg/internal/cli" ) // ensureNodeImages ensures that the node images used by the create // configuration are present func ensureNodeImages(logger log.Logger, status *cli.Status, cfg *config.Cluster) error { // pull each required image for _, image := range common.RequiredNodeImages(cfg).List() { // prints user friendly message friendlyImageName, image := sanitizeImage(image) status.Start(fmt.Sprintf("Ensuring node image (%s) 🖼", friendlyImageName)) if _, err := pullIfNotPresent(logger, image, 4); err != nil { status.End(false) return err } } return nil } // pullIfNotPresent will pull an image if it is not present locally // retrying up to retries times // it returns true if it attempted to pull, and any errors from pulling func pullIfNotPresent(logger log.Logger, image string, retries int) (pulled bool, err error) { // TODO(bentheelder): switch most (all) of the logging here to debug level // once we have configurable log levels // if this did not return an error, then the image exists locally cmd := exec.Command("docker", "inspect", "--type=image", image) if err := cmd.Run(); err == nil { logger.V(1).Infof("Image: %s present locally", image) return false, nil } // otherwise try to pull it return true, pull(logger, image, retries) } // pull pulls an image, retrying up to retries times func pull(logger log.Logger, image string, retries int) error { logger.V(1).Infof("Pulling image: %s ...", image) err := exec.Command("docker", "pull", image).Run() // retry pulling up to retries times if necessary if err != nil { for i := 0; i < retries; i++ { time.Sleep(time.Second * time.Duration(i+1)) logger.V(1).Infof("Trying again to pull image: %q ... %v", image, err) // TODO(bentheelder): add some backoff / sleep? err = exec.Command("docker", "pull", image).Run() if err == nil { break } } } return errors.Wrapf(err, "failed to pull image %q", image) } // sanitizeImage is a helper to return human readable image name and // the docker pullable image name from the provided image func sanitizeImage(image string) (string, string) { if strings.Contains(image, "@sha256:") { return strings.Split(image, "@sha256:")[0], image } return image, image } kind-0.27.0/pkg/cluster/internal/providers/docker/network.go000066400000000000000000000242021475376161000241210ustar00rootroot00000000000000/* Copyright 2020 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package docker import ( "bytes" "crypto/sha1" "encoding/binary" "encoding/json" "fmt" "io" "net" "regexp" "sort" "strconv" "strings" "sigs.k8s.io/kind/pkg/errors" "sigs.k8s.io/kind/pkg/exec" ) // This may be overridden by KIND_EXPERIMENTAL_DOCKER_NETWORK env, // experimentally... // // By default currently picking a single network is equivalent to the previous // behavior *except* that we moved from the default bridge to a user defined // network because the default bridge is actually special versus any other // docker network and lacks the embedded DNS // // For now this also makes it easier for apps to join the same network, and // leaves users with complex networking desires to create and manage their own // networks. const fixedNetworkName = "kind" // ensureNetwork checks if docker network by name exists, if not it creates it func ensureNetwork(name string) error { // check if network exists already and remove any duplicate networks exists, err := removeDuplicateNetworks(name) if err != nil { return err } // network already exists, we're good // TODO: the network might already exist and not have ipv6 ... :| // discussion: https://github.com/kubernetes-sigs/kind/pull/1508#discussion_r414594198 if exists { return nil } // Generate unique subnet per network based on the name // obtained from the ULA fc00::/8 range // Use the MTU configured for the docker default network // Make N attempts with "probing" in case we happen to collide subnet := generateULASubnetFromName(name, 0) mtu := getDefaultNetworkMTU() err = createNetworkNoDuplicates(name, subnet, mtu) if err == nil { // Success! return nil } // On the first try check if ipv6 fails entirely on this machine // https://github.com/kubernetes-sigs/kind/issues/1544 // Otherwise if it's not a pool overlap error, fail // If it is, make more attempts below if isIPv6UnavailableError(err) { // only one attempt, IPAM is automatic in ipv4 only return createNetworkNoDuplicates(name, "", mtu) } if isPoolOverlapError(err) { // pool overlap suggests perhaps another process created the network // check if network exists already and remove any duplicate networks exists, err := checkIfNetworkExists(name) if err != nil { return err } if exists { return nil } // otherwise we'll start trying with different subnets } else { // unknown error ... return err } // keep trying for ipv6 subnets const maxAttempts = 5 for attempt := int32(1); attempt < maxAttempts; attempt++ { subnet := generateULASubnetFromName(name, attempt) err = createNetworkNoDuplicates(name, subnet, mtu) if err == nil { // success! return nil } if isPoolOverlapError(err) { // pool overlap suggests perhaps another process created the network // check if network exists already and remove any duplicate networks exists, err := checkIfNetworkExists(name) if err != nil { return err } if exists { return nil } // otherwise we'll try again continue } // unknown error ... return err } return errors.New("exhausted attempts trying to find a non-overlapping subnet") } func createNetworkNoDuplicates(name, ipv6Subnet string, mtu int) error { if err := createNetwork(name, ipv6Subnet, mtu); err != nil && !isNetworkAlreadyExistsError(err) { return err } _, err := removeDuplicateNetworks(name) return err } func removeDuplicateNetworks(name string) (bool, error) { networks, err := sortedNetworksWithName(name) if err != nil { return false, err } if len(networks) > 1 { if err := deleteNetworks(networks[1:]...); err != nil && !isOnlyErrorNoSuchNetwork(err) { return false, err } } return len(networks) > 0, nil } func createNetwork(name, ipv6Subnet string, mtu int) error { args := []string{"network", "create", "-d=bridge", "-o", "com.docker.network.bridge.enable_ip_masquerade=true", } if mtu > 0 { args = append(args, "-o", fmt.Sprintf("com.docker.network.driver.mtu=%d", mtu)) } if ipv6Subnet != "" { args = append(args, "--ipv6", "--subnet", ipv6Subnet) } args = append(args, name) return exec.Command("docker", args...).Run() } // getDefaultNetworkMTU obtains the MTU from the docker default network func getDefaultNetworkMTU() int { cmd := exec.Command("docker", "network", "inspect", "bridge", "-f", `{{ index .Options "com.docker.network.driver.mtu" }}`) lines, err := exec.OutputLines(cmd) if err != nil || len(lines) != 1 { return 0 } mtu, err := strconv.Atoi(lines[0]) if err != nil { return 0 } return mtu } func sortedNetworksWithName(name string) ([]string, error) { // query which networks exist with the name ids, err := networksWithName(name) if err != nil { return nil, err } // we can skip sorting if there are less than 2 if len(ids) < 2 { return ids, nil } // inspect them to get more detail for sorting networks, err := inspectNetworks(ids) if err != nil { return nil, err } // deterministically sort networks // NOTE: THIS PART IS IMPORTANT! sortNetworkInspectEntries(networks) // return network IDs sortedIDs := make([]string, 0, len(networks)) for i := range networks { sortedIDs = append(sortedIDs, networks[i].ID) } return sortedIDs, nil } func sortNetworkInspectEntries(networks []networkInspectEntry) { sort.Slice(networks, func(i, j int) bool { // we want networks with active containers first if len(networks[i].Containers) > len(networks[j].Containers) { return true } return networks[i].ID < networks[j].ID }) } func inspectNetworks(networkIDs []string) ([]networkInspectEntry, error) { inspectOut, err := exec.Output(exec.Command("docker", append([]string{"network", "inspect"}, networkIDs...)...)) // NOTE: the caller can detect if the network isn't present in the output anyhow // we don't want to fail on this here. if err != nil && !isOnlyErrorNoSuchNetwork(err) { return nil, err } // parse networks := []networkInspectEntry{} if err := json.Unmarshal(inspectOut, &networks); err != nil { return nil, errors.Wrap(err, "failed to decode networks list") } return networks, nil } type networkInspectEntry struct { ID string `json:"Id"` // NOTE: we don't care about the contents here but we need to parse // how many entries exist in the containers map Containers map[string]map[string]string `json:"Containers"` } // networksWithName returns a list of network IDs for networks with this name func networksWithName(name string) ([]string, error) { lsOut, err := exec.Output(exec.Command( "docker", "network", "ls", "--filter=name=^"+regexp.QuoteMeta(name)+"$", "--format={{.ID}}", // output as unambiguous IDs )) if err != nil { return nil, err } cleaned := strings.TrimSuffix(string(lsOut), "\n") if cleaned == "" { // avoid returning []string{""} return nil, nil } return strings.Split(cleaned, "\n"), nil } func checkIfNetworkExists(name string) (bool, error) { out, err := exec.Output(exec.Command( "docker", "network", "ls", "--filter=name=^"+regexp.QuoteMeta(name)+"$", "--format={{.Name}}", )) return strings.HasPrefix(string(out), name), err } func isIPv6UnavailableError(err error) bool { rerr := exec.RunErrorForError(err) if rerr == nil { return false } errorMessage := string(rerr.Output) // we get this error when ipv6 was disabled in docker const dockerIPV6DisabledError = "Error response from daemon: Cannot read IPv6 setup for bridge" // TODO: this is fragile, and only necessary due to docker enabling ipv6 by default // even on hosts that lack ip6tables setup. // Preferably users would either have ip6tables setup properly or else disable ipv6 in docker const dockerIPV6TablesError = "Error response from daemon: Failed to Setup IP tables: Unable to enable NAT rule: (iptables failed: ip6tables" return strings.HasPrefix(errorMessage, dockerIPV6DisabledError) || strings.HasPrefix(errorMessage, dockerIPV6TablesError) } func isPoolOverlapError(err error) bool { rerr := exec.RunErrorForError(err) return rerr != nil && strings.HasPrefix(string(rerr.Output), "Error response from daemon: Pool overlaps with other one on this address space") || strings.Contains(string(rerr.Output), "networks have overlapping") } func isNetworkAlreadyExistsError(err error) bool { rerr := exec.RunErrorForError(err) return rerr != nil && strings.HasPrefix(string(rerr.Output), "Error response from daemon: network with name") && strings.Contains(string(rerr.Output), "already exists") } // returns true if: // - err only contains no such network errors func isOnlyErrorNoSuchNetwork(err error) bool { rerr := exec.RunErrorForError(err) if rerr == nil { return false } // check all lines of output from errored command b := bytes.NewBuffer(rerr.Output) for { l, err := b.ReadBytes('\n') if err == io.EOF { break } else if err != nil { return false } // if the line begins with Error: No such network: it's fine s := string(l) if strings.HasPrefix(s, "Error: No such network:") { continue } // other errors are not fine if strings.HasPrefix(s, "Error: ") { return false } // other line contents should just be network references } return true } func deleteNetworks(networks ...string) error { return exec.Command("docker", append([]string{"network", "rm"}, networks...)...).Run() } // generateULASubnetFromName generate an IPv6 subnet based on the // name and Nth probing attempt func generateULASubnetFromName(name string, attempt int32) string { ip := make([]byte, 16) ip[0] = 0xfc ip[1] = 0x00 h := sha1.New() _, _ = h.Write([]byte(name)) _ = binary.Write(h, binary.LittleEndian, attempt) bs := h.Sum(nil) for i := 2; i < 8; i++ { ip[i] = bs[i] } subnet := &net.IPNet{ IP: net.IP(ip), Mask: net.CIDRMask(64, 128), } return subnet.String() } kind-0.27.0/pkg/cluster/internal/providers/docker/network_integration_test.go000066400000000000000000000037071475376161000275720ustar00rootroot00000000000000//go:build !nointegration // +build !nointegration /* Copyright 2020 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package docker import ( "fmt" "regexp" "testing" "sigs.k8s.io/kind/pkg/errors" "sigs.k8s.io/kind/pkg/exec" "sigs.k8s.io/kind/pkg/internal/integration" ) func TestIntegrationEnsureNetworkConcurrent(t *testing.T) { integration.MaybeSkip(t) testNetworkName := "integration-test-ensure-kind-network" // cleanup cleanup := func() { ids, _ := networksWithName(testNetworkName) if len(ids) > 0 { _ = deleteNetworks(ids...) } } cleanup() defer cleanup() // this is more than enough to trigger race conditions networkConcurrency := 10 // Create multiple networks concurrently errCh := make(chan error, networkConcurrency) for i := 0; i < networkConcurrency; i++ { go func() { errCh <- ensureNetwork(testNetworkName) }() } for i := 0; i < networkConcurrency; i++ { if err := <-errCh; err != nil { t.Errorf("error creating network: %v", err) rerr := exec.RunErrorForError(err) if rerr != nil { t.Errorf("%q", rerr.Output) } t.Errorf("%+v", errors.StackTrace(err)) } } cmd := exec.Command( "docker", "network", "ls", fmt.Sprintf("--filter=name=^%s$", regexp.QuoteMeta(testNetworkName)), "--format={{.Name}}", ) lines, err := exec.OutputLines(cmd) if err != nil { t.Errorf("obtaining the docker networks") } if len(lines) != 1 { t.Errorf("wrong number of networks created: %d", len(lines)) } } kind-0.27.0/pkg/cluster/internal/providers/docker/network_test.go000066400000000000000000000105111475376161000251560ustar00rootroot00000000000000/* Copyright 2020 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package docker import ( "fmt" "testing" "sigs.k8s.io/kind/pkg/internal/assert" ) func Test_generateULASubnetFromName(t *testing.T) { t.Parallel() cases := []struct { name string attempt int32 subnet string }{ { name: "kind", subnet: "fc00:f853:ccd:e793::/64", }, { name: "foo", attempt: 1, subnet: "fc00:8edf:7f02:ec8f::/64", }, { name: "foo", attempt: 2, subnet: "fc00:9968:306b:2c65::/64", }, { name: "kind2", subnet: "fc00:444c:147a:44ab::/64", }, { name: "kin", subnet: "fc00:fcd9:c2be:8e23::/64", }, { name: "mysupernetwork", subnet: "fc00:7ae1:1e0d:b4d4::/64", }, } for _, tc := range cases { tc := tc // capture variable t.Run(fmt.Sprintf("%s,%d", tc.name, tc.attempt), func(t *testing.T) { t.Parallel() subnet := generateULASubnetFromName(tc.name, tc.attempt) if subnet != tc.subnet { t.Errorf("Wrong subnet from %v: expected %v, received %v", tc.name, tc.subnet, subnet) } }) } } func Test_sortNetworkInspectEntries(t *testing.T) { cases := []struct { Name string Networks []networkInspectEntry Sorted []networkInspectEntry }{ { Name: "simple ID sort", Networks: []networkInspectEntry{ { ID: "dc7f897c237215c3b73d2c9ba1d4e116d872793a6c1c0e5bf083762998de8b4e", }, { ID: "1ed9912325a0d08594ee786de91ebd961e631643877b5ee58ec906b640813eae", }, }, Sorted: []networkInspectEntry{ { ID: "1ed9912325a0d08594ee786de91ebd961e631643877b5ee58ec906b640813eae", }, { ID: "dc7f897c237215c3b73d2c9ba1d4e116d872793a6c1c0e5bf083762998de8b4e", }, }, }, { Name: "containers attached sort", Networks: []networkInspectEntry{ { ID: "1ed9912325a0d08594ee786de91ebd961e631643877b5ee58ec906b640813eae", }, { ID: "dc7f897c237215c3b73d2c9ba1d4e116d872793a6c1c0e5bf083762998de8b4e", Containers: map[string]map[string]string{ "a37779e06f3b694eba491dd450aad18bbbaa0a0fce2952e7c9195ea45ae79d41": { "Name": "buildx_buildkit_kind-builder0", "EndpointID": "8f6411fb4360059b2f91028f91ef03130abc96d6381afc265ce53c9df89d5a3d", }, }, }, { ID: "f0445f08b9989921da00250d778975202267fbab364e5fbad0ceb6db24f3f91e", }, { ID: "128154205c7d88c7bb9c255d389bc9e222b58a48cf83619976e7665a48e79918", Containers: map[string]map[string]string{ "aad18bbbaa0a0fce2952e7c9195ea45ae79d41a37779e06f3b694eba491dd450": { "Name": "fakey-fake", "EndpointID": "f03130abc96d6381afc265ce53c9df89d5a3d8f6411fb4360059b2f91028f91e", }, }, }, }, Sorted: []networkInspectEntry{ { ID: "128154205c7d88c7bb9c255d389bc9e222b58a48cf83619976e7665a48e79918", Containers: map[string]map[string]string{ "aad18bbbaa0a0fce2952e7c9195ea45ae79d41a37779e06f3b694eba491dd450": { "Name": "fakey-fake", "EndpointID": "f03130abc96d6381afc265ce53c9df89d5a3d8f6411fb4360059b2f91028f91e", }, }, }, { ID: "dc7f897c237215c3b73d2c9ba1d4e116d872793a6c1c0e5bf083762998de8b4e", Containers: map[string]map[string]string{ "a37779e06f3b694eba491dd450aad18bbbaa0a0fce2952e7c9195ea45ae79d41": { "Name": "buildx_buildkit_kind-builder0", "EndpointID": "8f6411fb4360059b2f91028f91ef03130abc96d6381afc265ce53c9df89d5a3d", }, }, }, { ID: "1ed9912325a0d08594ee786de91ebd961e631643877b5ee58ec906b640813eae", }, { ID: "f0445f08b9989921da00250d778975202267fbab364e5fbad0ceb6db24f3f91e", }, }, }, } for _, tc := range cases { tc := tc t.Run(tc.Name, func(t *testing.T) { toSort := make([]networkInspectEntry, len(tc.Networks)) copy(toSort, tc.Networks) sortNetworkInspectEntries(toSort) assert.DeepEqual(t, tc.Sorted, toSort) }) } } kind-0.27.0/pkg/cluster/internal/providers/docker/node.go000066400000000000000000000077671475376161000233760ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impliep. See the License for the specific language governing permissions and limitations under the License. */ package docker import ( "context" "fmt" "io" "strings" "sigs.k8s.io/kind/pkg/errors" "sigs.k8s.io/kind/pkg/exec" ) // nodes.Node implementation for the docker provider type node struct { name string } func (n *node) String() string { return n.name } func (n *node) Role() (string, error) { cmd := exec.Command("docker", "inspect", "--format", fmt.Sprintf(`{{ index .Config.Labels "%s"}}`, nodeRoleLabelKey), n.name, ) lines, err := exec.OutputLines(cmd) if err != nil { return "", errors.Wrap(err, "failed to get role for node") } if len(lines) != 1 { return "", errors.Errorf("failed to get role for node: output lines %d != 1", len(lines)) } return lines[0], nil } func (n *node) IP() (ipv4 string, ipv6 string, err error) { // retrieve the IP address of the node using docker inspect cmd := exec.Command("docker", "inspect", "-f", "{{range .NetworkSettings.Networks}}{{.IPAddress}},{{.GlobalIPv6Address}}{{end}}", n.name, // ... against the "node" container ) lines, err := exec.OutputLines(cmd) if err != nil { return "", "", errors.Wrap(err, "failed to get container details") } if len(lines) != 1 { return "", "", errors.Errorf("file should only be one line, got %d lines", len(lines)) } ips := strings.Split(lines[0], ",") if len(ips) != 2 { return "", "", errors.Errorf("container addresses should have 2 values, got %d values", len(ips)) } return ips[0], ips[1], nil } func (n *node) Command(command string, args ...string) exec.Cmd { return &nodeCmd{ nameOrID: n.name, command: command, args: args, } } func (n *node) CommandContext(ctx context.Context, command string, args ...string) exec.Cmd { return &nodeCmd{ nameOrID: n.name, command: command, args: args, ctx: ctx, } } // nodeCmd implements exec.Cmd for docker nodes type nodeCmd struct { nameOrID string // the container name or ID command string args []string env []string stdin io.Reader stdout io.Writer stderr io.Writer ctx context.Context } func (c *nodeCmd) Run() error { args := []string{ "exec", // run with privileges so we can remount etc.. // this might not make sense in the most general sense, but it is // important to many kind commands "--privileged", } if c.stdin != nil { args = append(args, "-i", // interactive so we can supply input ) } // set env for _, env := range c.env { args = append(args, "-e", env) } // specify the container and command, after this everything will be // args the command in the container rather than to docker args = append( args, c.nameOrID, // ... against the container c.command, // with the command specified ) args = append( args, // finally, with the caller args c.args..., ) var cmd exec.Cmd if c.ctx != nil { cmd = exec.CommandContext(c.ctx, "docker", args...) } else { cmd = exec.Command("docker", args...) } if c.stdin != nil { cmd.SetStdin(c.stdin) } if c.stderr != nil { cmd.SetStderr(c.stderr) } if c.stdout != nil { cmd.SetStdout(c.stdout) } return cmd.Run() } func (c *nodeCmd) SetEnv(env ...string) exec.Cmd { c.env = env return c } func (c *nodeCmd) SetStdin(r io.Reader) exec.Cmd { c.stdin = r return c } func (c *nodeCmd) SetStdout(w io.Writer) exec.Cmd { c.stdout = w return c } func (c *nodeCmd) SetStderr(w io.Writer) exec.Cmd { c.stderr = w return c } func (n *node) SerialLogs(w io.Writer) error { return exec.Command("docker", "logs", n.name).SetStdout(w).SetStderr(w).Run() } kind-0.27.0/pkg/cluster/internal/providers/docker/provider.go000066400000000000000000000241521475376161000242660ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impliep. See the License for the specific language governing permissions and limitations under the License. */ package docker import ( "encoding/csv" "encoding/json" "fmt" "net" "os" "path/filepath" "strings" "sigs.k8s.io/kind/pkg/cluster/nodes" "sigs.k8s.io/kind/pkg/errors" "sigs.k8s.io/kind/pkg/exec" "sigs.k8s.io/kind/pkg/log" internallogs "sigs.k8s.io/kind/pkg/cluster/internal/logs" "sigs.k8s.io/kind/pkg/cluster/internal/providers" "sigs.k8s.io/kind/pkg/cluster/internal/providers/common" "sigs.k8s.io/kind/pkg/cluster/nodeutils" "sigs.k8s.io/kind/pkg/internal/apis/config" "sigs.k8s.io/kind/pkg/internal/cli" "sigs.k8s.io/kind/pkg/internal/sets" ) // NewProvider returns a new provider based on executing `docker ...` func NewProvider(logger log.Logger) providers.Provider { return &provider{ logger: logger, } } // Provider implements provider.Provider // see NewProvider type provider struct { logger log.Logger info *providers.ProviderInfo } // String implements fmt.Stringer // NOTE: the value of this should not currently be relied upon for anything! // This is only used for setting the Node's providerID func (p *provider) String() string { return "docker" } // Provision is part of the providers.Provider interface func (p *provider) Provision(status *cli.Status, cfg *config.Cluster) (err error) { // TODO: validate cfg // ensure node images are pulled before actually provisioning if err := ensureNodeImages(p.logger, status, cfg); err != nil { return err } // ensure the pre-requisite network exists networkName := fixedNetworkName if n := os.Getenv("KIND_EXPERIMENTAL_DOCKER_NETWORK"); n != "" { p.logger.Warn("WARNING: Overriding docker network due to KIND_EXPERIMENTAL_DOCKER_NETWORK") p.logger.Warn("WARNING: Here be dragons! This is not supported currently.") networkName = n } if err := ensureNetwork(networkName); err != nil { return errors.Wrap(err, "failed to ensure docker network") } // actually provision the cluster icons := strings.Repeat("📦 ", len(cfg.Nodes)) status.Start(fmt.Sprintf("Preparing nodes %s", icons)) defer func() { status.End(err == nil) }() // plan creating the containers createContainerFuncs, err := planCreation(cfg, networkName) if err != nil { return err } // actually create nodes return errors.UntilErrorConcurrent(createContainerFuncs) } // ListClusters is part of the providers.Provider interface func (p *provider) ListClusters() ([]string, error) { cmd := exec.Command("docker", "ps", "-a", // show stopped nodes // filter for nodes with the cluster label "--filter", "label="+clusterLabelKey, // format to include the cluster name "--format", fmt.Sprintf(`{{.Label "%s"}}`, clusterLabelKey), ) lines, err := exec.OutputLines(cmd) if err != nil { return nil, errors.Wrap(err, "failed to list clusters") } return sets.NewString(lines...).List(), nil } // ListNodes is part of the providers.Provider interface func (p *provider) ListNodes(cluster string) ([]nodes.Node, error) { cmd := exec.Command("docker", "ps", "-a", // show stopped nodes // filter for nodes with the cluster label "--filter", fmt.Sprintf("label=%s=%s", clusterLabelKey, cluster), // format to include the cluster name "--format", `{{.Names}}`, ) lines, err := exec.OutputLines(cmd) if err != nil { return nil, errors.Wrap(err, "failed to list nodes") } // convert names to node handles ret := make([]nodes.Node, 0, len(lines)) for _, name := range lines { ret = append(ret, p.node(name)) } return ret, nil } // DeleteNodes is part of the providers.Provider interface func (p *provider) DeleteNodes(n []nodes.Node) error { if len(n) == 0 { return nil } const command = "docker" args := make([]string, 0, len(n)+3) // allocate once args = append(args, "rm", "-f", // force the container to be delete now "-v", // delete volumes ) for _, node := range n { args = append(args, node.String()) } if err := exec.Command(command, args...).Run(); err != nil { return errors.Wrap(err, "failed to delete nodes") } return nil } // GetAPIServerEndpoint is part of the providers.Provider interface func (p *provider) GetAPIServerEndpoint(cluster string) (string, error) { // locate the node that hosts this allNodes, err := p.ListNodes(cluster) if err != nil { return "", errors.Wrap(err, "failed to list nodes") } n, err := nodeutils.APIServerEndpointNode(allNodes) if err != nil { return "", errors.Wrap(err, "failed to get api server endpoint") } // if the 'desktop.docker.io/ports//tcp' label is present, // defer to its value for the api server endpoint // // For example: // "Labels": { // "desktop.docker.io/ports/6443/tcp": "10.0.1.7:6443", // } cmd := exec.Command( "docker", "inspect", "--format", fmt.Sprintf( "{{ index .Config.Labels \"desktop.docker.io/ports/%d/tcp\" }}", common.APIServerInternalPort, ), n.String(), ) lines, err := exec.OutputLines(cmd) if err != nil { return "", errors.Wrap(err, "failed to get api server port") } if len(lines) == 1 && lines[0] != "" { return lines[0], nil } // else, retrieve the specific port mapping via NetworkSettings.Ports cmd = exec.Command( "docker", "inspect", "--format", fmt.Sprintf( "{{ with (index (index .NetworkSettings.Ports \"%d/tcp\") 0) }}{{ printf \"%%s\t%%s\" .HostIp .HostPort }}{{ end }}", common.APIServerInternalPort, ), n.String(), ) lines, err = exec.OutputLines(cmd) if err != nil { return "", errors.Wrap(err, "failed to get api server port") } if len(lines) != 1 { return "", errors.Errorf("network details should only be one line, got %d lines", len(lines)) } parts := strings.Split(lines[0], "\t") if len(parts) != 2 { return "", errors.Errorf("network details should only be two parts, got %d", len(parts)) } // join host and port return net.JoinHostPort(parts[0], parts[1]), nil } // GetAPIServerInternalEndpoint is part of the providers.Provider interface func (p *provider) GetAPIServerInternalEndpoint(cluster string) (string, error) { // locate the node that hosts this allNodes, err := p.ListNodes(cluster) if err != nil { return "", errors.Wrap(err, "failed to list nodes") } n, err := nodeutils.APIServerEndpointNode(allNodes) if err != nil { return "", errors.Wrap(err, "failed to get api server endpoint") } // NOTE: we're using the nodes's hostnames which are their names return net.JoinHostPort(n.String(), fmt.Sprintf("%d", common.APIServerInternalPort)), nil } // node returns a new node handle for this provider func (p *provider) node(name string) nodes.Node { return &node{ name: name, } } // CollectLogs will populate dir with cluster logs and other debug files func (p *provider) CollectLogs(dir string, nodes []nodes.Node) error { execToPathFn := func(cmd exec.Cmd, path string) func() error { return func() error { f, err := common.FileOnHost(path) if err != nil { return err } defer f.Close() return cmd.SetStdout(f).SetStderr(f).Run() } } // construct a slice of methods to collect logs fns := []func() error{ // record info about the host docker execToPathFn( exec.Command("docker", "info"), filepath.Join(dir, "docker-info.txt"), ), } // collect /var/log for each node and plan collecting more logs var errs []error for _, n := range nodes { node := n // https://golang.org/doc/faq#closures_and_goroutines name := node.String() path := filepath.Join(dir, name) if err := internallogs.DumpDir(p.logger, node, "/var/log", path); err != nil { errs = append(errs, err) } fns = append(fns, func() error { return common.CollectLogs(node, path) }, execToPathFn(exec.Command("docker", "inspect", name), filepath.Join(path, "inspect.json")), func() error { f, err := common.FileOnHost(filepath.Join(path, "serial.log")) if err != nil { return err } defer f.Close() return node.SerialLogs(f) }, ) } // run and collect up all errors errs = append(errs, errors.AggregateConcurrent(fns)) return errors.NewAggregate(errs) } // Info returns the provider info. // The info is cached on the first time of the execution. func (p *provider) Info() (*providers.ProviderInfo, error) { var err error if p.info == nil { p.info, err = info() } return p.info, err } // dockerInfo corresponds to `docker info --format '{{json .}}'` type dockerInfo struct { CgroupDriver string `json:"CgroupDriver"` // "systemd", "cgroupfs", "none" CgroupVersion string `json:"CgroupVersion"` // e.g. "2" MemoryLimit bool `json:"MemoryLimit"` PidsLimit bool `json:"PidsLimit"` CPUShares bool `json:"CPUShares"` SecurityOptions []string `json:"SecurityOptions"` } func info() (*providers.ProviderInfo, error) { cmd := exec.Command("docker", "info", "--format", "{{json .}}") out, err := exec.Output(cmd) if err != nil { return nil, errors.Wrap(err, "failed to get docker info") } var dInfo dockerInfo if err := json.Unmarshal(out, &dInfo); err != nil { return nil, err } info := providers.ProviderInfo{ Cgroup2: dInfo.CgroupVersion == "2", } // When CgroupDriver == "none", the MemoryLimit/PidsLimit/CPUShares // values are meaningless and need to be considered false. // https://github.com/moby/moby/issues/42151 if dInfo.CgroupDriver != "none" { info.SupportsMemoryLimit = dInfo.MemoryLimit info.SupportsPidsLimit = dInfo.PidsLimit info.SupportsCPUShares = dInfo.CPUShares } for _, o := range dInfo.SecurityOptions { // o is like "name=seccomp,profile=default", or "name=rootless", csvReader := csv.NewReader(strings.NewReader(o)) sliceSlice, err := csvReader.ReadAll() if err != nil { return nil, err } for _, f := range sliceSlice { for _, ff := range f { if ff == "name=rootless" { info.Rootless = true } } } } return &info, nil } kind-0.27.0/pkg/cluster/internal/providers/docker/provision.go000066400000000000000000000362011475376161000244620ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package docker import ( "context" "fmt" "net" "path/filepath" "strings" "time" "sigs.k8s.io/kind/pkg/cluster/constants" "sigs.k8s.io/kind/pkg/errors" "sigs.k8s.io/kind/pkg/exec" "sigs.k8s.io/kind/pkg/fs" "sigs.k8s.io/kind/pkg/cluster/internal/loadbalancer" "sigs.k8s.io/kind/pkg/cluster/internal/providers/common" "sigs.k8s.io/kind/pkg/internal/apis/config" ) // planCreation creates a slice of funcs that will create the containers func planCreation(cfg *config.Cluster, networkName string) (createContainerFuncs []func() error, err error) { // we need to know all the names for NO_PROXY // compute the names first before any actual node details nodeNamer := common.MakeNodeNamer(cfg.Name) names := make([]string, len(cfg.Nodes)) for i, node := range cfg.Nodes { name := nodeNamer(string(node.Role)) // name the node names[i] = name } haveLoadbalancer := config.ClusterHasImplicitLoadBalancer(cfg) if haveLoadbalancer { names = append(names, nodeNamer(constants.ExternalLoadBalancerNodeRoleValue)) } // these apply to all container creation genericArgs, err := commonArgs(cfg.Name, cfg, networkName, names) if err != nil { return nil, err } // only the external LB should reflect the port if we have multiple control planes apiServerPort := cfg.Networking.APIServerPort apiServerAddress := cfg.Networking.APIServerAddress if haveLoadbalancer { // TODO: picking ports locally is less than ideal with remote docker // but this is supposed to be an implementation detail and NOT picking // them breaks host reboot ... // For now remote docker + multi control plane is not supported apiServerPort = 0 // replaced with random ports apiServerAddress = "127.0.0.1" // only the LB needs to be non-local // only for IPv6 only clusters if cfg.Networking.IPFamily == config.IPv6Family { apiServerAddress = "::1" // only the LB needs to be non-local } // plan loadbalancer node name := names[len(names)-1] createContainerFuncs = append(createContainerFuncs, func() error { args, err := runArgsForLoadBalancer(cfg, name, genericArgs) if err != nil { return err } return createContainer(name, args) }) } // plan normal nodes for i, node := range cfg.Nodes { node := node.DeepCopy() // copy so we can modify name := names[i] // fixup relative paths, docker can only handle absolute paths for m := range node.ExtraMounts { hostPath := node.ExtraMounts[m].HostPath if !fs.IsAbs(hostPath) { absHostPath, err := filepath.Abs(hostPath) if err != nil { return nil, errors.Wrapf(err, "unable to resolve absolute path for hostPath: %q", hostPath) } node.ExtraMounts[m].HostPath = absHostPath } } // plan actual creation based on role switch node.Role { case config.ControlPlaneRole: createContainerFuncs = append(createContainerFuncs, func() error { node.ExtraPortMappings = append(node.ExtraPortMappings, config.PortMapping{ ListenAddress: apiServerAddress, HostPort: apiServerPort, ContainerPort: common.APIServerInternalPort, }, ) args, err := runArgsForNode(node, cfg.Networking.IPFamily, name, genericArgs) if err != nil { return err } return createContainerWithWaitUntilSystemdReachesMultiUserSystem(name, args) }) case config.WorkerRole: createContainerFuncs = append(createContainerFuncs, func() error { args, err := runArgsForNode(node, cfg.Networking.IPFamily, name, genericArgs) if err != nil { return err } return createContainerWithWaitUntilSystemdReachesMultiUserSystem(name, args) }) default: return nil, errors.Errorf("unknown node role: %q", node.Role) } } return createContainerFuncs, nil } // commonArgs computes static arguments that apply to all containers func commonArgs(cluster string, cfg *config.Cluster, networkName string, nodeNames []string) ([]string, error) { // standard arguments all nodes containers need, computed once args := []string{ "--detach", // run the container detached "--tty", // allocate a tty for entrypoint logs // label the node with the cluster ID "--label", fmt.Sprintf("%s=%s", clusterLabelKey, cluster), // user a user defined docker network so we get embedded DNS "--net", networkName, // Docker supports the following restart modes: // - no // - on-failure[:max-retries] // - unless-stopped // - always // https://docs.docker.com/engine/reference/commandline/run/#restart-policies---restart // // What we desire is: // - restart on host / dockerd reboot // - don't restart for any other reason // // This means: // - no is out of the question ... it never restarts // - always is a poor choice, we'll keep trying to restart nodes that were // never going to work // - unless-stopped will also retry failures indefinitely, similar to always // except that it won't restart when the container is `docker stop`ed // - on-failure is not great, we're only interested in restarting on // reboots, not failures. *however* we can limit the number of retries // *and* it forgets all state on dockerd restart and retries anyhow. // - on-failure:0 is what we want .. restart on failures, except max // retries is 0, so only restart on reboots. // however this _actually_ means the same thing as always // so the closest thing is on-failure:1, which will retry *once* "--restart=on-failure:1", // this can be enabled by default in docker daemon.json, so we explicitly // disable it, we want our entrypoint to be PID1, not docker-init / tini "--init=false", // note: requires API v1.41+ from Dec 2020 in Docker 20.10.0 // this is the default with cgroups v2 but not with cgroups v1, unless // overridden in the daemon --default-cgroupns-mode // https://github.com/docker/cli/pull/3699#issuecomment-1191675788 "--cgroupns=private", } // enable IPv6 if necessary if config.ClusterHasIPv6(cfg) { args = append(args, "--sysctl=net.ipv6.conf.all.disable_ipv6=0", "--sysctl=net.ipv6.conf.all.forwarding=1") } // pass proxy environment variables proxyEnv, err := getProxyEnv(cfg, networkName, nodeNames) if err != nil { return nil, errors.Wrap(err, "proxy setup error") } for key, val := range proxyEnv { args = append(args, "-e", fmt.Sprintf("%s=%s", key, val)) } // handle hosts that have user namespace remapping enabled if usernsRemap() { args = append(args, "--userns=host") } // handle Docker on Btrfs or ZFS // https://github.com/kubernetes-sigs/kind/issues/1416#issuecomment-606514724 if mountDevMapper() { args = append(args, "--volume", "/dev/mapper:/dev/mapper") } // enable /dev/fuse explicitly for fuse-overlayfs // (Rootless Docker does not automatically mount /dev/fuse with --privileged) if mountFuse() { args = append(args, "--device", "/dev/fuse") } if cfg.Networking.DNSSearch != nil { args = append(args, "-e", "KIND_DNS_SEARCH="+strings.Join(*cfg.Networking.DNSSearch, " ")) } return args, nil } func runArgsForNode(node *config.Node, clusterIPFamily config.ClusterIPFamily, name string, args []string) ([]string, error) { args = append([]string{ "--hostname", name, // make hostname match container name // label the node with the role ID "--label", fmt.Sprintf("%s=%s", nodeRoleLabelKey, node.Role), // running containers in a container requires privileged // NOTE: we could try to replicate this with --cap-add, and use less // privileges, but this flag also changes some mounts that are necessary // including some ones docker would otherwise do by default. // for now this is what we want. in the future we may revisit this. "--privileged", "--security-opt", "seccomp=unconfined", // also ignore seccomp "--security-opt", "apparmor=unconfined", // also ignore apparmor // runtime temporary storage "--tmpfs", "/tmp", // various things depend on working /tmp "--tmpfs", "/run", // systemd wants a writable /run // runtime persistent storage // this ensures that E.G. pods, logs etc. are not on the container // filesystem, which is not only better for performance, but allows // running kind in kind for "party tricks" // (please don't depend on doing this though!) "--volume", "/var", // some k8s things want to read /lib/modules "--volume", "/lib/modules:/lib/modules:ro", // propagate KIND_EXPERIMENTAL_CONTAINERD_SNAPSHOTTER to the entrypoint script "-e", "KIND_EXPERIMENTAL_CONTAINERD_SNAPSHOTTER", }, args..., ) // convert mounts and port mappings to container run args args = append(args, generateMountBindings(node.ExtraMounts...)...) mappingArgs, err := generatePortMappings(clusterIPFamily, node.ExtraPortMappings...) if err != nil { return nil, err } args = append(args, mappingArgs...) switch node.Role { case config.ControlPlaneRole: args = append(args, "-e", "KUBECONFIG=/etc/kubernetes/admin.conf") } // finally, specify the image to run return append(args, node.Image), nil } func runArgsForLoadBalancer(cfg *config.Cluster, name string, args []string) ([]string, error) { args = append([]string{ "--hostname", name, // make hostname match container name // label the node with the role ID "--label", fmt.Sprintf("%s=%s", nodeRoleLabelKey, constants.ExternalLoadBalancerNodeRoleValue), }, args..., ) // load balancer port mapping mappingArgs, err := generatePortMappings(cfg.Networking.IPFamily, config.PortMapping{ ListenAddress: cfg.Networking.APIServerAddress, HostPort: cfg.Networking.APIServerPort, ContainerPort: common.APIServerInternalPort, }, ) if err != nil { return nil, err } args = append(args, mappingArgs...) // finally, specify the image to run return append(args, loadbalancer.Image), nil } func getProxyEnv(cfg *config.Cluster, networkName string, nodeNames []string) (map[string]string, error) { envs := common.GetProxyEnvs(cfg) // Specifically add the docker network subnets to NO_PROXY if we are using a proxy if len(envs) > 0 { subnets, err := getSubnets(networkName) if err != nil { return nil, err } noProxyList := append(subnets, envs[common.NOProxy]) noProxyList = append(noProxyList, nodeNames...) // Add pod and service dns names to no_proxy to allow in cluster // Note: this is best effort based on the default CoreDNS spec // https://github.com/kubernetes/dns/blob/master/docs/specification.md // Any user created pod/service hostnames, namespaces, custom DNS services // are expected to be no-proxied by the user explicitly. noProxyList = append(noProxyList, ".svc", ".svc.cluster", ".svc.cluster.local") noProxyJoined := strings.Join(noProxyList, ",") envs[common.NOProxy] = noProxyJoined envs[strings.ToLower(common.NOProxy)] = noProxyJoined } return envs, nil } func getSubnets(networkName string) ([]string, error) { format := `{{range (index (index . "IPAM") "Config")}}{{index . "Subnet"}} {{end}}` cmd := exec.Command("docker", "network", "inspect", "-f", format, networkName) lines, err := exec.OutputLines(cmd) if err != nil { return nil, errors.Wrap(err, "failed to get subnets") } return strings.Split(strings.TrimSpace(lines[0]), " "), nil } // generateMountBindings converts the mount list to a list of args for docker // ':[:options]', where 'options' // is a comma-separated list of the following strings: // 'ro', if the path is read only // 'Z', if the volume requires SELinux relabeling func generateMountBindings(mounts ...config.Mount) []string { args := make([]string, 0, len(mounts)) for _, m := range mounts { bind := fmt.Sprintf("%s:%s", m.HostPath, m.ContainerPath) var attrs []string if m.Readonly { attrs = append(attrs, "ro") } // Only request relabeling if the pod provides an SELinux context. If the pod // does not provide an SELinux context relabeling will label the volume with // the container's randomly allocated MCS label. This would restrict access // to the volume to the container which mounts it first. if m.SelinuxRelabel { attrs = append(attrs, "Z") } switch m.Propagation { case config.MountPropagationNone: // noop, private is default case config.MountPropagationBidirectional: attrs = append(attrs, "rshared") case config.MountPropagationHostToContainer: attrs = append(attrs, "rslave") default: // Falls back to "private" } if len(attrs) > 0 { bind = fmt.Sprintf("%s:%s", bind, strings.Join(attrs, ",")) } args = append(args, fmt.Sprintf("--volume=%s", bind)) } return args } // generatePortMappings converts the portMappings list to a list of args for docker func generatePortMappings(clusterIPFamily config.ClusterIPFamily, portMappings ...config.PortMapping) ([]string, error) { args := make([]string, 0, len(portMappings)) for _, pm := range portMappings { // do provider internal defaulting // in a future API revision we will handle this at the API level and remove this if pm.ListenAddress == "" { switch clusterIPFamily { case config.IPv4Family, config.DualStackFamily: pm.ListenAddress = "0.0.0.0" // this is the docker default anyhow case config.IPv6Family: pm.ListenAddress = "::" default: return nil, errors.Errorf("unknown cluster IP family: %v", clusterIPFamily) } } if string(pm.Protocol) == "" { pm.Protocol = config.PortMappingProtocolTCP // TCP is the default } // validate that the provider can handle this binding switch pm.Protocol { case config.PortMappingProtocolTCP: case config.PortMappingProtocolUDP: case config.PortMappingProtocolSCTP: default: return nil, errors.Errorf("unknown port mapping protocol: %v", pm.Protocol) } // get a random port if necessary (port = 0) hostPort, releaseHostPortFn, err := common.PortOrGetFreePort(pm.HostPort, pm.ListenAddress) if err != nil { return nil, errors.Wrap(err, "failed to get random host port for port mapping") } if releaseHostPortFn != nil { defer releaseHostPortFn() } // generate the actual mapping arg protocol := string(pm.Protocol) hostPortBinding := net.JoinHostPort(pm.ListenAddress, fmt.Sprintf("%d", hostPort)) args = append(args, fmt.Sprintf("--publish=%s:%d/%s", hostPortBinding, pm.ContainerPort, protocol)) } return args, nil } func createContainer(name string, args []string) error { return exec.Command("docker", append([]string{"run", "--name", name}, args...)...).Run() } func createContainerWithWaitUntilSystemdReachesMultiUserSystem(name string, args []string) error { if err := exec.Command("docker", append([]string{"run", "--name", name}, args...)...).Run(); err != nil { return err } logCtx, logCancel := context.WithTimeout(context.Background(), 30*time.Second) logCmd := exec.CommandContext(logCtx, "docker", "logs", "-f", name) defer logCancel() return common.WaitUntilLogRegexpMatches(logCtx, logCmd, common.NodeReachedCgroupsReadyRegexp()) } kind-0.27.0/pkg/cluster/internal/providers/docker/util.go000066400000000000000000000051121475376161000234040ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package docker import ( "encoding/json" "strings" "sigs.k8s.io/kind/pkg/exec" ) // IsAvailable checks if docker is available in the system func IsAvailable() bool { cmd := exec.Command("docker", "-v") lines, err := exec.OutputLines(cmd) if err != nil || len(lines) != 1 { return false } return strings.HasPrefix(lines[0], "Docker version") } // usernsRemap checks if userns-remap is enabled in dockerd func usernsRemap() bool { cmd := exec.Command("docker", "info", "--format", "'{{json .SecurityOptions}}'") lines, err := exec.OutputLines(cmd) if err != nil { return false } if len(lines) > 0 { if strings.Contains(lines[0], "name=userns") { return true } } return false } // mountDevMapper checks if the Docker storage driver is Btrfs or ZFS // or if the backing filesystem is Btrfs func mountDevMapper() bool { storage := "" // check the docker storage driver cmd := exec.Command("docker", "info", "-f", "{{.Driver}}") lines, err := exec.OutputLines(cmd) if err != nil || len(lines) != 1 { return false } storage = strings.ToLower(strings.TrimSpace(lines[0])) if storage == "btrfs" || storage == "zfs" || storage == "devicemapper" { return true } // check the backing file system // docker info -f '{{json .DriverStatus }}' // [["Backing Filesystem","extfs"],["Supports d_type","true"],["Native Overlay Diff","true"]] cmd = exec.Command("docker", "info", "-f", "{{json .DriverStatus }}") lines, err = exec.OutputLines(cmd) if err != nil || len(lines) != 1 { return false } var dat [][]string if err := json.Unmarshal([]byte(lines[0]), &dat); err != nil { return false } for _, item := range dat { if item[0] == "Backing Filesystem" { storage = strings.ToLower(item[1]) break } } return storage == "btrfs" || storage == "zfs" || storage == "xfs" } // rootless: use fuse-overlayfs by default // https://github.com/kubernetes-sigs/kind/issues/2275 func mountFuse() bool { i, err := info() if err != nil { return false } if i != nil && i.Rootless { return true } return false } kind-0.27.0/pkg/cluster/internal/providers/nerdctl/000077500000000000000000000000001475376161000222655ustar00rootroot00000000000000kind-0.27.0/pkg/cluster/internal/providers/nerdctl/OWNERS000066400000000000000000000000401475376161000232170ustar00rootroot00000000000000labels: - area/provider/nerdctl kind-0.27.0/pkg/cluster/internal/providers/nerdctl/constants.go000066400000000000000000000015241475376161000246320ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impliep. See the License for the specific language governing permissions and limitations under the License. */ package nerdctl // clusterLabelKey is applied to each "node" container for identification const clusterLabelKey = "io.x-k8s.kind.cluster" // nodeRoleLabelKey is applied to each "node" container for categorization // of nodes by role const nodeRoleLabelKey = "io.x-k8s.kind.role" kind-0.27.0/pkg/cluster/internal/providers/nerdctl/images.go000066400000000000000000000061331475376161000240640ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package nerdctl import ( "fmt" "strings" "time" "sigs.k8s.io/kind/pkg/errors" "sigs.k8s.io/kind/pkg/exec" "sigs.k8s.io/kind/pkg/log" "sigs.k8s.io/kind/pkg/cluster/internal/providers/common" "sigs.k8s.io/kind/pkg/internal/apis/config" "sigs.k8s.io/kind/pkg/internal/cli" ) // ensureNodeImages ensures that the node images used by the create // configuration are present func ensureNodeImages(logger log.Logger, status *cli.Status, cfg *config.Cluster, binaryName string) error { // pull each required image for _, image := range common.RequiredNodeImages(cfg).List() { // prints user friendly message friendlyImageName, image := sanitizeImage(image) status.Start(fmt.Sprintf("Ensuring node image (%s) 🖼", friendlyImageName)) if _, err := pullIfNotPresent(logger, image, 4, binaryName); err != nil { status.End(false) return err } } return nil } // pullIfNotPresent will pull an image if it is not present locally // retrying up to retries times // it returns true if it attempted to pull, and any errors from pulling func pullIfNotPresent(logger log.Logger, image string, retries int, binaryName string) (pulled bool, err error) { // TODO(bentheelder): switch most (all) of the logging here to debug level // once we have configurable log levels // if this did not return an error, then the image exists locally cmd := exec.Command(binaryName, "inspect", "--type=image", image) if err := cmd.Run(); err == nil { logger.V(1).Infof("Image: %s present locally", image) return false, nil } // otherwise try to pull it return true, pull(logger, image, retries, binaryName) } // pull pulls an image, retrying up to retries times func pull(logger log.Logger, image string, retries int, binaryName string) error { logger.V(1).Infof("Pulling image: %s ...", image) err := exec.Command(binaryName, "pull", image).Run() // retry pulling up to retries times if necessary if err != nil { for i := 0; i < retries; i++ { time.Sleep(time.Second * time.Duration(i+1)) logger.V(1).Infof("Trying again to pull image: %q ... %v", image, err) // TODO(bentheelder): add some backoff / sleep? err = exec.Command(binaryName, "pull", image).Run() if err == nil { break } } } return errors.Wrapf(err, "failed to pull image %q", image) } // sanitizeImage is a helper to return human readable image name and // the docker pullable image name from the provided image func sanitizeImage(image string) (string, string) { if strings.Contains(image, "@sha256:") { return strings.Split(image, "@sha256:")[0], image } return image, image } kind-0.27.0/pkg/cluster/internal/providers/nerdctl/network.go000066400000000000000000000130661475376161000243130ustar00rootroot00000000000000/* Copyright 2020 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package nerdctl import ( "crypto/sha1" "encoding/binary" "fmt" "net" "strconv" "strings" "sigs.k8s.io/kind/pkg/errors" "sigs.k8s.io/kind/pkg/exec" ) // This may be overridden by KIND_EXPERIMENTAL_DOCKER_NETWORK env, // experimentally... // // By default currently picking a single network is equivalent to the previous // behavior *except* that we moved from the default bridge to a user defined // network because the default bridge is actually special versus any other // docker network and lacks the embedded DNS // // For now this also makes it easier for apps to join the same network, and // leaves users with complex networking desires to create and manage their own // networks. const fixedNetworkName = "kind" // ensureNetwork checks if docker network by name exists, if not it creates it func ensureNetwork(name, binaryName string) error { // check if network exists already and remove any duplicate networks exists, err := checkIfNetworkExists(name, binaryName) if err != nil { return err } // network already exists, we're good // TODO: the network might already exist and not have ipv6 ... :| // discussion: https://github.com/kubernetes-sigs/kind/pull/1508#discussion_r414594198 if exists { return nil } subnet := generateULASubnetFromName(name, 0) mtu := getDefaultNetworkMTU(binaryName) err = createNetwork(name, subnet, mtu, binaryName) if err == nil { // Success! return nil } // On the first try check if ipv6 fails entirely on this machine // https://github.com/kubernetes-sigs/kind/issues/1544 // Otherwise if it's not a pool overlap error, fail // If it is, make more attempts below if isIPv6UnavailableError(err) { // only one attempt, IPAM is automatic in ipv4 only return createNetwork(name, "", mtu, binaryName) } if isPoolOverlapError(err) { // pool overlap suggests perhaps another process created the network // check if network exists already and remove any duplicate networks exists, err := checkIfNetworkExists(name, binaryName) if err != nil { return err } if exists { return nil } // otherwise we'll start trying with different subnets } else { // unknown error ... return err } // keep trying for ipv6 subnets const maxAttempts = 5 for attempt := int32(1); attempt < maxAttempts; attempt++ { subnet := generateULASubnetFromName(name, attempt) err = createNetwork(name, subnet, mtu, binaryName) if err == nil { // success! return nil } if isPoolOverlapError(err) { // pool overlap suggests perhaps another process created the network // check if network exists already and remove any duplicate networks exists, err := checkIfNetworkExists(name, binaryName) if err != nil { return err } if exists { return nil } // otherwise we'll try again continue } // unknown error ... return err } return errors.New("exhausted attempts trying to find a non-overlapping subnet") } func createNetwork(name, ipv6Subnet string, mtu int, binaryName string) error { args := []string{"network", "create", "-d=bridge"} // TODO: Not supported in nerdctl yet // "-o", "com.docker.network.bridge.enable_ip_masquerade=true", if mtu > 0 { args = append(args, "-o", fmt.Sprintf("com.docker.network.driver.mtu=%d", mtu)) } if ipv6Subnet != "" { args = append(args, "--ipv6", "--subnet", ipv6Subnet) } args = append(args, name) return exec.Command(binaryName, args...).Run() } // getDefaultNetworkMTU obtains the MTU from the docker default network func getDefaultNetworkMTU(binaryName string) int { cmd := exec.Command(binaryName, "network", "inspect", "bridge", "-f", `{{ index .Options "com.docker.network.driver.mtu" }}`) lines, err := exec.OutputLines(cmd) if err != nil || len(lines) != 1 { return 0 } mtu, err := strconv.Atoi(lines[0]) if err != nil { return 0 } return mtu } func checkIfNetworkExists(name, binaryName string) (bool, error) { out, err := exec.Output(exec.Command( binaryName, "network", "inspect", name, "--format={{.Name}}", )) if err != nil { return false, nil } return strings.HasPrefix(string(out), name), err } func isIPv6UnavailableError(err error) bool { rerr := exec.RunErrorForError(err) return rerr != nil && strings.HasPrefix(string(rerr.Output), "Error response from daemon: Cannot read IPv6 setup for bridge") } func isPoolOverlapError(err error) bool { rerr := exec.RunErrorForError(err) return rerr != nil && strings.HasPrefix(string(rerr.Output), "Error response from daemon: Pool overlaps with other one on this address space") || strings.Contains(string(rerr.Output), "networks have overlapping") } // generateULASubnetFromName generate an IPv6 subnet based on the // name and Nth probing attempt func generateULASubnetFromName(name string, attempt int32) string { ip := make([]byte, 16) ip[0] = 0xfc ip[1] = 0x00 h := sha1.New() _, _ = h.Write([]byte(name)) _ = binary.Write(h, binary.LittleEndian, attempt) bs := h.Sum(nil) for i := 2; i < 8; i++ { ip[i] = bs[i] } subnet := &net.IPNet{ IP: net.IP(ip), Mask: net.CIDRMask(64, 128), } return subnet.String() } kind-0.27.0/pkg/cluster/internal/providers/nerdctl/network_test.go000066400000000000000000000030121475376161000253400ustar00rootroot00000000000000/* Copyright 2020 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package nerdctl import ( "fmt" "testing" ) func Test_generateULASubnetFromName(t *testing.T) { t.Parallel() cases := []struct { name string attempt int32 subnet string }{ { name: "kind", subnet: "fc00:f853:ccd:e793::/64", }, { name: "foo", attempt: 1, subnet: "fc00:8edf:7f02:ec8f::/64", }, { name: "foo", attempt: 2, subnet: "fc00:9968:306b:2c65::/64", }, { name: "kind2", subnet: "fc00:444c:147a:44ab::/64", }, { name: "kin", subnet: "fc00:fcd9:c2be:8e23::/64", }, { name: "mysupernetwork", subnet: "fc00:7ae1:1e0d:b4d4::/64", }, } for _, tc := range cases { tc := tc // capture variable t.Run(fmt.Sprintf("%s,%d", tc.name, tc.attempt), func(t *testing.T) { t.Parallel() subnet := generateULASubnetFromName(tc.name, tc.attempt) if subnet != tc.subnet { t.Errorf("Wrong subnet from %v: expected %v, received %v", tc.name, tc.subnet, subnet) } }) } } kind-0.27.0/pkg/cluster/internal/providers/nerdctl/node.go000066400000000000000000000102161475376161000235410ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impliep. See the License for the specific language governing permissions and limitations under the License. */ package nerdctl import ( "context" "fmt" "io" "strings" "sigs.k8s.io/kind/pkg/errors" "sigs.k8s.io/kind/pkg/exec" ) // nodes.Node implementation for the docker provider type node struct { name string binaryName string } func (n *node) String() string { return n.name } func (n *node) Role() (string, error) { cmd := exec.Command(n.binaryName, "inspect", "--format", fmt.Sprintf(`{{ index .Config.Labels "%s"}}`, nodeRoleLabelKey), n.name, ) lines, err := exec.OutputLines(cmd) if err != nil { return "", errors.Wrap(err, "failed to get role for node") } if len(lines) != 1 { return "", errors.Errorf("failed to get role for node: output lines %d != 1", len(lines)) } return lines[0], nil } func (n *node) IP() (ipv4 string, ipv6 string, err error) { // retrieve the IP address of the node using docker inspect cmd := exec.Command(n.binaryName, "inspect", "-f", "{{range .NetworkSettings.Networks}}{{.IPAddress}},{{.GlobalIPv6Address}}{{end}}", n.name, // ... against the "node" container ) lines, err := exec.OutputLines(cmd) if err != nil { return "", "", errors.Wrap(err, "failed to get container details") } if len(lines) != 1 { return "", "", errors.Errorf("file should only be one line, got %d lines", len(lines)) } ips := strings.Split(lines[0], ",") if len(ips) != 2 { return "", "", errors.Errorf("container addresses should have 2 values, got %d values", len(ips)) } return ips[0], ips[1], nil } func (n *node) Command(command string, args ...string) exec.Cmd { return &nodeCmd{ binaryName: n.binaryName, nameOrID: n.name, command: command, args: args, } } func (n *node) CommandContext(ctx context.Context, command string, args ...string) exec.Cmd { return &nodeCmd{ binaryName: n.binaryName, nameOrID: n.name, command: command, args: args, ctx: ctx, } } // nodeCmd implements exec.Cmd for docker nodes type nodeCmd struct { binaryName string nameOrID string // the container name or ID command string args []string env []string stdin io.Reader stdout io.Writer stderr io.Writer ctx context.Context } func (c *nodeCmd) Run() error { args := []string{ "exec", // run with privileges so we can remount etc.. // this might not make sense in the most general sense, but it is // important to many kind commands "--privileged", } if c.stdin != nil { args = append(args, "-i", // interactive so we can supply input ) } // set env for _, env := range c.env { args = append(args, "-e", env) } // specify the container and command, after this everything will be // args the command in the container rather than to docker args = append( args, c.nameOrID, // ... against the container c.command, // with the command specified ) args = append( args, // finally, with the caller args c.args..., ) var cmd exec.Cmd if c.ctx != nil { cmd = exec.CommandContext(c.ctx, c.binaryName, args...) } else { cmd = exec.Command(c.binaryName, args...) } if c.stdin != nil { cmd.SetStdin(c.stdin) } if c.stderr != nil { cmd.SetStderr(c.stderr) } if c.stdout != nil { cmd.SetStdout(c.stdout) } return cmd.Run() } func (c *nodeCmd) SetEnv(env ...string) exec.Cmd { c.env = env return c } func (c *nodeCmd) SetStdin(r io.Reader) exec.Cmd { c.stdin = r return c } func (c *nodeCmd) SetStdout(w io.Writer) exec.Cmd { c.stdout = w return c } func (c *nodeCmd) SetStderr(w io.Writer) exec.Cmd { c.stderr = w return c } func (n *node) SerialLogs(w io.Writer) error { return exec.Command(n.binaryName, "logs", n.name).SetStdout(w).SetStderr(w).Run() } kind-0.27.0/pkg/cluster/internal/providers/nerdctl/provider.go000066400000000000000000000272561475376161000244620ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impliep. See the License for the specific language governing permissions and limitations under the License. */ package nerdctl import ( "encoding/csv" "encoding/json" "fmt" "net" osexec "os/exec" "path/filepath" "strings" "sigs.k8s.io/kind/pkg/cluster/nodes" "sigs.k8s.io/kind/pkg/errors" "sigs.k8s.io/kind/pkg/exec" "sigs.k8s.io/kind/pkg/log" internallogs "sigs.k8s.io/kind/pkg/cluster/internal/logs" "sigs.k8s.io/kind/pkg/cluster/internal/providers" "sigs.k8s.io/kind/pkg/cluster/internal/providers/common" "sigs.k8s.io/kind/pkg/cluster/nodeutils" "sigs.k8s.io/kind/pkg/internal/apis/config" "sigs.k8s.io/kind/pkg/internal/cli" "sigs.k8s.io/kind/pkg/internal/sets" ) // NewProvider returns a new provider based on executing `nerdctl ...` func NewProvider(logger log.Logger, binaryName string) providers.Provider { // if binaryName is unset, do a lookup; we may be here via a // library call to provider.DetectNodeProvider(), which returns // true from nerdctl.IsAvailable() by checking for both finch // and nerdctl. If we don't redo the lookup here, then a finch // install that triggered IsAvailable() to be true would fail // to be used if we default to nerdctl when unset. if binaryName == "" { // default to "nerdctl"; but look for "finch" if // nerctl binary lookup fails binaryName = "nerdctl" if _, err := osexec.LookPath("nerdctl"); err != nil { if _, err := osexec.LookPath("finch"); err == nil { binaryName = "finch" } } } return &provider{ logger: logger, binaryName: binaryName, } } // Provider implements provider.Provider // see NewProvider type provider struct { logger log.Logger binaryName string info *providers.ProviderInfo } // String implements fmt.Stringer // NOTE: the value of this should not currently be relied upon for anything! // This is only used for setting the Node's providerID func (p *provider) String() string { return "nerdctl" } func (p *provider) Binary() string { return p.binaryName } // Provision is part of the providers.Provider interface func (p *provider) Provision(status *cli.Status, cfg *config.Cluster) (err error) { // TODO: validate cfg // ensure node images are pulled before actually provisioning if err := ensureNodeImages(p.logger, status, cfg, p.Binary()); err != nil { return err } // ensure the pre-requisite network exists if err := ensureNetwork(fixedNetworkName, p.Binary()); err != nil { return errors.Wrap(err, "failed to ensure nerdctl network") } // actually provision the cluster icons := strings.Repeat("📦 ", len(cfg.Nodes)) status.Start(fmt.Sprintf("Preparing nodes %s", icons)) defer func() { status.End(err == nil) }() // plan creating the containers createContainerFuncs, err := planCreation(cfg, fixedNetworkName, p.Binary()) if err != nil { return err } // actually create nodes // TODO: remove once nerdctl handles concurrency better // xref: https://github.com/containerd/nerdctl/issues/2908 for _, f := range createContainerFuncs { if err := f(); err != nil { return err } } return nil } // ListClusters is part of the providers.Provider interface func (p *provider) ListClusters() ([]string, error) { cmd := exec.Command(p.Binary(), "ps", "-a", // show stopped nodes // filter for nodes with the cluster label "--filter", "label="+clusterLabelKey, // format to include the cluster name "--format", fmt.Sprintf(`{{.Label "%s"}}`, clusterLabelKey), ) lines, err := exec.OutputLines(cmd) if err != nil { return nil, errors.Wrap(err, "failed to list clusters") } return sets.NewString(lines...).List(), nil } // ListNodes is part of the providers.Provider interface func (p *provider) ListNodes(cluster string) ([]nodes.Node, error) { cmd := exec.Command(p.Binary(), "ps", "-a", // show stopped nodes // filter for nodes with the cluster label "--filter", fmt.Sprintf("label=%s=%s", clusterLabelKey, cluster), // format to include the cluster name "--format", `{{.Names}}`, ) lines, err := exec.OutputLines(cmd) if err != nil { return nil, errors.Wrap(err, "failed to list nodes") } length := len(lines) // convert names to node handles ret := make([]nodes.Node, 0, length) for _, name := range lines { if name != "" { ret = append(ret, p.node(name)) } } return ret, nil } // DeleteNodes is part of the providers.Provider interface func (p *provider) DeleteNodes(n []nodes.Node) error { if len(n) == 0 { return nil } argsNoRestart := make([]string, 0, len(n)+2) argsNoRestart = append(argsNoRestart, "update", "--restart=no", ) argsStop := make([]string, 0, len(n)+1) argsStop = append(argsStop, "stop") argsWait := make([]string, 0, len(n)+1) argsWait = append(argsWait, "wait") argsRm := make([]string, 0, len(n)+3) // allocate once argsRm = append(argsRm, "rm", "-f", "-v", // delete volumes ) for _, node := range n { argsRm = append(argsRm, node.String()) argsStop = append(argsStop, node.String()) argsWait = append(argsWait, node.String()) argsNoRestart = append(argsNoRestart, node.String()) } if err := exec.Command(p.Binary(), argsNoRestart...).Run(); err != nil { return errors.Wrap(err, "failed to update restart policy to 'no'") } if err := exec.Command(p.Binary(), argsStop...).Run(); err != nil { return errors.Wrap(err, "failed to stop nodes") } if err := exec.Command(p.Binary(), argsWait...).Run(); err != nil { return errors.Wrap(err, "failed to wait for node exit") } if err := exec.Command(p.Binary(), argsRm...).Run(); err != nil { return errors.Wrap(err, "failed to delete nodes") } return nil } // GetAPIServerEndpoint is part of the providers.Provider interface func (p *provider) GetAPIServerEndpoint(cluster string) (string, error) { // locate the node that hosts this allNodes, err := p.ListNodes(cluster) if err != nil { return "", errors.Wrap(err, "failed to list nodes") } n, err := nodeutils.APIServerEndpointNode(allNodes) if err != nil { return "", errors.Wrap(err, "failed to get api server endpoint") } // if the 'desktop.docker.io/ports//tcp' label is present, // defer to its value for the api server endpoint // // For example: // "Labels": { // "desktop.docker.io/ports/6443/tcp": "10.0.1.7:6443", // } cmd := exec.Command( p.Binary(), "inspect", "--format", fmt.Sprintf( "{{ index .Config.Labels \"desktop.docker.io/ports/%d/tcp\" }}", common.APIServerInternalPort, ), n.String(), ) lines, err := exec.OutputLines(cmd) if err != nil { return "", errors.Wrap(err, "failed to get api server port") } if len(lines) == 1 && lines[0] != "" { return lines[0], nil } // else, retrieve the specific port mapping via NetworkSettings.Ports cmd = exec.Command( p.Binary(), "inspect", "--format", fmt.Sprintf( "{{ with (index (index .NetworkSettings.Ports \"%d/tcp\") 0) }}{{ printf \"%%s\t%%s\" .HostIp .HostPort }}{{ end }}", common.APIServerInternalPort, ), n.String(), ) lines, err = exec.OutputLines(cmd) if err != nil { return "", errors.Wrap(err, "failed to get api server port") } if len(lines) != 1 { return "", errors.Errorf("network details should only be one line, got %d lines", len(lines)) } parts := strings.Split(lines[0], "\t") if len(parts) != 2 { return "", errors.Errorf("network details should only be two parts, got %d", len(parts)) } // join host and port return net.JoinHostPort(parts[0], parts[1]), nil } // GetAPIServerInternalEndpoint is part of the providers.Provider interface func (p *provider) GetAPIServerInternalEndpoint(cluster string) (string, error) { // locate the node that hosts this allNodes, err := p.ListNodes(cluster) if err != nil { return "", errors.Wrap(err, "failed to list nodes") } n, err := nodeutils.APIServerEndpointNode(allNodes) if err != nil { return "", errors.Wrap(err, "failed to get api server endpoint") } // NOTE: we're using the nodes's hostnames which are their names return net.JoinHostPort(n.String(), fmt.Sprintf("%d", common.APIServerInternalPort)), nil } // node returns a new node handle for this provider func (p *provider) node(name string) nodes.Node { return &node{ binaryName: p.binaryName, name: name, } } // CollectLogs will populate dir with cluster logs and other debug files func (p *provider) CollectLogs(dir string, nodes []nodes.Node) error { execToPathFn := func(cmd exec.Cmd, path string) func() error { return func() error { f, err := common.FileOnHost(path) if err != nil { return err } defer f.Close() return cmd.SetStdout(f).SetStderr(f).Run() } } // construct a slice of methods to collect logs fns := []func() error{ // record info about the host nerdctl execToPathFn( exec.Command(p.Binary(), "info"), filepath.Join(dir, "docker-info.txt"), ), } // collect /var/log for each node and plan collecting more logs var errs []error for _, n := range nodes { node := n // https://golang.org/doc/faq#closures_and_goroutines name := node.String() path := filepath.Join(dir, name) if err := internallogs.DumpDir(p.logger, node, "/var/log", path); err != nil { errs = append(errs, err) } fns = append(fns, func() error { return common.CollectLogs(node, path) }, execToPathFn(exec.Command(p.Binary(), "inspect", name), filepath.Join(path, "inspect.json")), func() error { f, err := common.FileOnHost(filepath.Join(path, "serial.log")) if err != nil { return err } defer f.Close() return node.SerialLogs(f) }, ) } // run and collect up all errors errs = append(errs, errors.AggregateConcurrent(fns)) return errors.NewAggregate(errs) } // Info returns the provider info. // The info is cached on the first time of the execution. func (p *provider) Info() (*providers.ProviderInfo, error) { var err error if p.info == nil { p.info, err = info(p.Binary()) } return p.info, err } // dockerInfo corresponds to `docker info --format '{{json .}}'` type dockerInfo struct { CgroupDriver string `json:"CgroupDriver"` // "systemd", "cgroupfs", "none" CgroupVersion string `json:"CgroupVersion"` // e.g. "2" MemoryLimit bool `json:"MemoryLimit"` PidsLimit bool `json:"PidsLimit"` CPUShares bool `json:"CPUShares"` SecurityOptions []string `json:"SecurityOptions"` } func info(binaryName string) (*providers.ProviderInfo, error) { cmd := exec.Command(binaryName, "info", "--format", "{{json .}}") out, err := exec.Output(cmd) if err != nil { return nil, errors.Wrap(err, "failed to get nerdctl info") } var dInfo dockerInfo if err := json.Unmarshal(out, &dInfo); err != nil { return nil, err } info := providers.ProviderInfo{ Cgroup2: dInfo.CgroupVersion == "2", } // When CgroupDriver == "none", the MemoryLimit/PidsLimit/CPUShares // values are meaningless and need to be considered false. // https://github.com/moby/moby/issues/42151 if dInfo.CgroupDriver != "none" { info.SupportsMemoryLimit = dInfo.MemoryLimit info.SupportsPidsLimit = dInfo.PidsLimit info.SupportsCPUShares = dInfo.CPUShares } for _, o := range dInfo.SecurityOptions { // o is like "name=seccomp,profile=default", or "name=rootless", csvReader := csv.NewReader(strings.NewReader(o)) sliceSlice, err := csvReader.ReadAll() if err != nil { return nil, err } for _, f := range sliceSlice { for _, ff := range f { if ff == "name=rootless" { info.Rootless = true } } } } return &info, nil } kind-0.27.0/pkg/cluster/internal/providers/nerdctl/provision.go000066400000000000000000000335751475376161000246610ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package nerdctl import ( "context" "fmt" "net" "path/filepath" "strings" "time" "sigs.k8s.io/kind/pkg/cluster/constants" "sigs.k8s.io/kind/pkg/errors" "sigs.k8s.io/kind/pkg/exec" "sigs.k8s.io/kind/pkg/fs" "sigs.k8s.io/kind/pkg/cluster/internal/loadbalancer" "sigs.k8s.io/kind/pkg/cluster/internal/providers/common" "sigs.k8s.io/kind/pkg/internal/apis/config" ) // planCreation creates a slice of funcs that will create the containers func planCreation(cfg *config.Cluster, networkName, binaryName string) (createContainerFuncs []func() error, err error) { // we need to know all the names for NO_PROXY // compute the names first before any actual node details nodeNamer := common.MakeNodeNamer(cfg.Name) names := make([]string, len(cfg.Nodes)) for i, node := range cfg.Nodes { name := nodeNamer(string(node.Role)) // name the node names[i] = name } haveLoadbalancer := config.ClusterHasImplicitLoadBalancer(cfg) if haveLoadbalancer { names = append(names, nodeNamer(constants.ExternalLoadBalancerNodeRoleValue)) } // these apply to all container creation genericArgs, err := commonArgs(cfg.Name, cfg, networkName, names, binaryName) if err != nil { return nil, err } // only the external LB should reflect the port if we have multiple control planes apiServerPort := cfg.Networking.APIServerPort apiServerAddress := cfg.Networking.APIServerAddress if haveLoadbalancer { // TODO: picking ports locally is less than ideal with remote docker // but this is supposed to be an implementation detail and NOT picking // them breaks host reboot ... // For now remote docker + multi control plane is not supported apiServerPort = 0 // replaced with random ports apiServerAddress = "127.0.0.1" // only the LB needs to be non-local // only for IPv6 only clusters if cfg.Networking.IPFamily == config.IPv6Family { apiServerAddress = "::1" // only the LB needs to be non-local } // plan loadbalancer node name := names[len(names)-1] createContainerFuncs = append(createContainerFuncs, func() error { args, err := runArgsForLoadBalancer(cfg, name, genericArgs) if err != nil { return err } return createContainer(name, args, binaryName) }) } // plan normal nodes for i, node := range cfg.Nodes { node := node.DeepCopy() // copy so we can modify name := names[i] // fixup relative paths, docker can only handle absolute paths for m := range node.ExtraMounts { hostPath := node.ExtraMounts[m].HostPath if !fs.IsAbs(hostPath) { absHostPath, err := filepath.Abs(hostPath) if err != nil { return nil, errors.Wrapf(err, "unable to resolve absolute path for hostPath: %q", hostPath) } node.ExtraMounts[m].HostPath = absHostPath } } // plan actual creation based on role switch node.Role { case config.ControlPlaneRole: createContainerFuncs = append(createContainerFuncs, func() error { node.ExtraPortMappings = append(node.ExtraPortMappings, config.PortMapping{ ListenAddress: apiServerAddress, HostPort: apiServerPort, ContainerPort: common.APIServerInternalPort, }, ) args, err := runArgsForNode(node, cfg.Networking.IPFamily, name, genericArgs) if err != nil { return err } return createContainerWithWaitUntilSystemdReachesMultiUserSystem(name, args, binaryName) }) case config.WorkerRole: createContainerFuncs = append(createContainerFuncs, func() error { args, err := runArgsForNode(node, cfg.Networking.IPFamily, name, genericArgs) if err != nil { return err } return createContainerWithWaitUntilSystemdReachesMultiUserSystem(name, args, binaryName) }) default: return nil, errors.Errorf("unknown node role: %q", node.Role) } } return createContainerFuncs, nil } // commonArgs computes static arguments that apply to all containers func commonArgs(cluster string, cfg *config.Cluster, networkName string, nodeNames []string, binaryName string) ([]string, error) { // standard arguments all nodes containers need, computed once args := []string{ "--detach", // run the container detached "--tty", // allocate a tty for entrypoint logs // label the node with the cluster ID "--label", fmt.Sprintf("%s=%s", clusterLabelKey, cluster), // user a user defined network so we get embedded DNS "--net", networkName, // containerd supports the following restart modes: // - no // - on-failure[:max-retries] // - unless-stopped // - always // // What we desire is: // - restart on host / container runtime reboot // - don't restart for any other reason // "--restart=on-failure:1", // this can be enabled by default in docker daemon.json, so we explicitly // disable it, we want our entrypoint to be PID1, not docker-init / tini "--init=false", } // enable IPv6 if necessary if config.ClusterHasIPv6(cfg) { args = append(args, "--sysctl=net.ipv6.conf.all.disable_ipv6=0", "--sysctl=net.ipv6.conf.all.forwarding=1") } // pass proxy environment variables proxyEnv, err := getProxyEnv(cfg, networkName, nodeNames, binaryName) if err != nil { return nil, errors.Wrap(err, "proxy setup error") } for key, val := range proxyEnv { args = append(args, "-e", fmt.Sprintf("%s=%s", key, val)) } // enable /dev/fuse explicitly for fuse-overlayfs // (Rootless Docker does not automatically mount /dev/fuse with --privileged) if mountFuse(binaryName) { args = append(args, "--device", "/dev/fuse") } if cfg.Networking.DNSSearch != nil { args = append(args, "-e", "KIND_DNS_SEARCH="+strings.Join(*cfg.Networking.DNSSearch, " ")) } return args, nil } func runArgsForNode(node *config.Node, clusterIPFamily config.ClusterIPFamily, name string, args []string) ([]string, error) { args = append([]string{ "--hostname", name, // make hostname match container name // label the node with the role ID "--label", fmt.Sprintf("%s=%s", nodeRoleLabelKey, node.Role), // running containers in a container requires privileged // NOTE: we could try to replicate this with --cap-add, and use less // privileges, but this flag also changes some mounts that are necessary // including some ones docker would otherwise do by default. // for now this is what we want. in the future we may revisit this. "--privileged", "--security-opt", "seccomp=unconfined", // also ignore seccomp "--security-opt", "apparmor=unconfined", // also ignore apparmor // runtime temporary storage "--tmpfs", "/tmp", // various things depend on working /tmp "--tmpfs", "/run", // systemd wants a writable /run // runtime persistent storage // this ensures that E.G. pods, logs etc. are not on the container // filesystem, which is not only better for performance, but allows // running kind in kind for "party tricks" // (please don't depend on doing this though!) "--volume", "/var", // some k8s things want to read /lib/modules "--volume", "/lib/modules:/lib/modules:ro", // propagate KIND_EXPERIMENTAL_CONTAINERD_SNAPSHOTTER to the entrypoint script "-e", "KIND_EXPERIMENTAL_CONTAINERD_SNAPSHOTTER", }, args..., ) // convert mounts and port mappings to container run args args = append(args, generateMountBindings(node.ExtraMounts...)...) mappingArgs, err := generatePortMappings(clusterIPFamily, node.ExtraPortMappings...) if err != nil { return nil, err } args = append(args, mappingArgs...) switch node.Role { case config.ControlPlaneRole: args = append(args, "-e", "KUBECONFIG=/etc/kubernetes/admin.conf") } // finally, specify the image to run return append(args, node.Image), nil } func runArgsForLoadBalancer(cfg *config.Cluster, name string, args []string) ([]string, error) { args = append([]string{ "--hostname", name, // make hostname match container name // label the node with the role ID "--label", fmt.Sprintf("%s=%s", nodeRoleLabelKey, constants.ExternalLoadBalancerNodeRoleValue), }, args..., ) // load balancer port mapping mappingArgs, err := generatePortMappings(cfg.Networking.IPFamily, config.PortMapping{ ListenAddress: cfg.Networking.APIServerAddress, HostPort: cfg.Networking.APIServerPort, ContainerPort: common.APIServerInternalPort, }, ) if err != nil { return nil, err } args = append(args, mappingArgs...) // finally, specify the image to run return append(args, loadbalancer.Image), nil } func getProxyEnv(cfg *config.Cluster, networkName string, nodeNames []string, binaryName string) (map[string]string, error) { envs := common.GetProxyEnvs(cfg) // Specifically add the docker network subnets to NO_PROXY if we are using a proxy if len(envs) > 0 { subnets, err := getSubnets(networkName, binaryName) if err != nil { return nil, err } noProxyList := append(subnets, envs[common.NOProxy]) noProxyList = append(noProxyList, nodeNames...) // Add pod and service dns names to no_proxy to allow in cluster // Note: this is best effort based on the default CoreDNS spec // https://github.com/kubernetes/dns/blob/master/docs/specification.md // Any user created pod/service hostnames, namespaces, custom DNS services // are expected to be no-proxied by the user explicitly. noProxyList = append(noProxyList, ".svc", ".svc.cluster", ".svc.cluster.local") noProxyJoined := strings.Join(noProxyList, ",") envs[common.NOProxy] = noProxyJoined envs[strings.ToLower(common.NOProxy)] = noProxyJoined } return envs, nil } func getSubnets(networkName, binaryName string) ([]string, error) { format := `{{range (index (index . "IPAM") "Config")}}{{index . "Subnet"}} {{end}}` cmd := exec.Command(binaryName, "network", "inspect", "-f", format, networkName) lines, err := exec.OutputLines(cmd) if err != nil { return nil, errors.Wrap(err, "failed to get subnets") } return strings.Split(strings.TrimSpace(lines[0]), " "), nil } // generateMountBindings converts the mount list to a list of args for docker // ':[:options]', where 'options' // is a comma-separated list of the following strings: // 'ro', if the path is read only // 'Z', if the volume requires SELinux relabeling func generateMountBindings(mounts ...config.Mount) []string { args := make([]string, 0, len(mounts)) for _, m := range mounts { bind := fmt.Sprintf("%s:%s", m.HostPath, m.ContainerPath) var attrs []string if m.Readonly { attrs = append(attrs, "ro") } // Only request relabeling if the pod provides an SELinux context. If the pod // does not provide an SELinux context relabeling will label the volume with // the container's randomly allocated MCS label. This would restrict access // to the volume to the container which mounts it first. if m.SelinuxRelabel { attrs = append(attrs, "Z") } switch m.Propagation { case config.MountPropagationNone: // noop, private is default case config.MountPropagationBidirectional: attrs = append(attrs, "rshared") case config.MountPropagationHostToContainer: attrs = append(attrs, "rslave") default: // Falls back to "private" } if len(attrs) > 0 { bind = fmt.Sprintf("%s:%s", bind, strings.Join(attrs, ",")) } args = append(args, fmt.Sprintf("--volume=%s", bind)) } return args } // generatePortMappings converts the portMappings list to a list of args for docker func generatePortMappings(clusterIPFamily config.ClusterIPFamily, portMappings ...config.PortMapping) ([]string, error) { args := make([]string, 0, len(portMappings)) for _, pm := range portMappings { // do provider internal defaulting // in a future API revision we will handle this at the API level and remove this if pm.ListenAddress == "" { switch clusterIPFamily { case config.IPv4Family, config.DualStackFamily: pm.ListenAddress = "0.0.0.0" // this is the docker default anyhow case config.IPv6Family: pm.ListenAddress = "::" default: return nil, errors.Errorf("unknown cluster IP family: %v", clusterIPFamily) } } if string(pm.Protocol) == "" { pm.Protocol = config.PortMappingProtocolTCP // TCP is the default } // validate that the provider can handle this binding switch pm.Protocol { case config.PortMappingProtocolTCP: case config.PortMappingProtocolUDP: case config.PortMappingProtocolSCTP: default: return nil, errors.Errorf("unknown port mapping protocol: %v", pm.Protocol) } // get a random port if necessary (port = 0) hostPort, releaseHostPortFn, err := common.PortOrGetFreePort(pm.HostPort, pm.ListenAddress) if err != nil { return nil, errors.Wrap(err, "failed to get random host port for port mapping") } if releaseHostPortFn != nil { defer releaseHostPortFn() } // generate the actual mapping arg protocol := string(pm.Protocol) hostPortBinding := net.JoinHostPort(pm.ListenAddress, fmt.Sprintf("%d", hostPort)) args = append(args, fmt.Sprintf("--publish=%s:%d/%s", hostPortBinding, pm.ContainerPort, protocol)) } return args, nil } func createContainer(name string, args []string, binaryName string) error { return exec.Command(binaryName, append([]string{"run", "--name", name}, args...)...).Run() } func createContainerWithWaitUntilSystemdReachesMultiUserSystem(name string, args []string, binaryName string) error { if err := exec.Command(binaryName, append([]string{"run", "--name", name}, args...)...).Run(); err != nil { return err } logCtx, logCancel := context.WithTimeout(context.Background(), 30*time.Second) logCmd := exec.CommandContext(logCtx, binaryName, "logs", "-f", name) defer logCancel() return common.WaitUntilLogRegexpMatches(logCtx, logCmd, common.NodeReachedCgroupsReadyRegexp()) } kind-0.27.0/pkg/cluster/internal/providers/nerdctl/util.go000066400000000000000000000025371475376161000236000ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package nerdctl import ( "strings" "sigs.k8s.io/kind/pkg/exec" ) // IsAvailable checks if nerdctl (or finch) is available in the system func IsAvailable() bool { cmd := exec.Command("nerdctl", "-v") lines, err := exec.OutputLines(cmd) if err != nil || len(lines) != 1 { // check finch cmd = exec.Command("finch", "-v") lines, err = exec.OutputLines(cmd) if err != nil || len(lines) != 1 { return false } return strings.HasPrefix(lines[0], "finch version") } return strings.HasPrefix(lines[0], "nerdctl version") } // rootless: use fuse-overlayfs by default // https://github.com/kubernetes-sigs/kind/issues/2275 func mountFuse(binaryName string) bool { i, err := info(binaryName) if err != nil { return false } if i != nil && i.Rootless { return true } return false } kind-0.27.0/pkg/cluster/internal/providers/podman/000077500000000000000000000000001475376161000221105ustar00rootroot00000000000000kind-0.27.0/pkg/cluster/internal/providers/podman/OWNERS000066400000000000000000000002751475376161000230540ustar00rootroot00000000000000# See the OWNERS docs at https://go.k8s.io/owners reviewers: - aojea - BenTheElder approvers: - aojea - BenTheElder emeritus_approvers: - amwat labels: - area/provider/podman kind-0.27.0/pkg/cluster/internal/providers/podman/constants.go000066400000000000000000000015411475376161000244540ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impliep. See the License for the specific language governing permissions and limitations under the License. */ package podman // clusterLabelKey is applied to each "node" podman container for identification const clusterLabelKey = "io.x-k8s.kind.cluster" // nodeRoleLabelKey is applied to each "node" podman container for categorization // of nodes by role const nodeRoleLabelKey = "io.x-k8s.kind.role" kind-0.27.0/pkg/cluster/internal/providers/podman/images.go000066400000000000000000000071441475376161000237120ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package podman import ( "fmt" "strings" "time" "sigs.k8s.io/kind/pkg/errors" "sigs.k8s.io/kind/pkg/exec" "sigs.k8s.io/kind/pkg/log" "sigs.k8s.io/kind/pkg/cluster/internal/providers/common" "sigs.k8s.io/kind/pkg/internal/apis/config" "sigs.k8s.io/kind/pkg/internal/cli" ) // ensureNodeImages ensures that the node images used by the create // configuration are present func ensureNodeImages(logger log.Logger, status *cli.Status, cfg *config.Cluster) error { // pull each required image for _, image := range common.RequiredNodeImages(cfg).List() { // prints user friendly message friendlyImageName, image := sanitizeImage(image) status.Start(fmt.Sprintf("Ensuring node image (%s) 🖼", friendlyImageName)) if _, err := pullIfNotPresent(logger, image, 4); err != nil { status.End(false) return err } } return nil } // pullIfNotPresent will pull an image if it is not present locally // retrying up to retries times // it returns true if it attempted to pull, and any errors from pulling func pullIfNotPresent(logger log.Logger, image string, retries int) (pulled bool, err error) { // TODO(bentheelder): switch most (all) of the logging here to debug level // once we have configurable log levels // if this did not return an error, then the image exists locally cmd := exec.Command("podman", "inspect", "--type=image", image) if err := cmd.Run(); err == nil { logger.V(1).Infof("Image: %s present locally", image) return false, nil } // otherwise try to pull it return true, pull(logger, image, retries) } // pull pulls an image, retrying up to retries times func pull(logger log.Logger, image string, retries int) error { logger.V(1).Infof("Pulling image: %s ...", image) err := exec.Command("podman", "pull", image).Run() // retry pulling up to retries times if necessary if err != nil { for i := 0; i < retries; i++ { time.Sleep(time.Second * time.Duration(i+1)) logger.V(1).Infof("Trying again to pull image: %q ... %v", image, err) // TODO(bentheelder): add some backoff / sleep? err = exec.Command("podman", "pull", image).Run() if err == nil { break } } } return errors.Wrapf(err, "failed to pull image %q", image) } // sanitizeImage is a helper to return human readable image name and // the podman pullable image name from the provided image func sanitizeImage(image string) (friendlyImageName, pullImageName string) { const ( defaultDomain = "docker.io/" officialRepoName = "library" ) var remainder string if strings.Contains(image, "@sha256:") { splits := strings.Split(image, "@sha256:") friendlyImageName = splits[0] remainder = strings.Split(splits[0], ":")[0] + "@sha256:" + splits[1] } else { friendlyImageName = image remainder = image } if !strings.ContainsRune(remainder, '/') { remainder = officialRepoName + "/" + remainder } i := strings.IndexRune(friendlyImageName, '/') if i == -1 || (!strings.ContainsAny(friendlyImageName[:i], ".:") && friendlyImageName[:i] != "localhost") { pullImageName = defaultDomain + remainder } else { pullImageName = remainder } return } kind-0.27.0/pkg/cluster/internal/providers/podman/images_test.go000066400000000000000000000100201475376161000247340ustar00rootroot00000000000000/* Copyright 2021 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package podman import ( "testing" ) func Test_sanitizeImage(t *testing.T) { cases := []struct { image string friendlyImageName string pullImageName string }{ { image: "kindest/node:v1.21.1@sha256:69860bda5563ac81e3c0057d654b5253219618a22ec3a346306239bba8cfa1a6", friendlyImageName: "kindest/node:v1.21.1", pullImageName: "docker.io/kindest/node@sha256:69860bda5563ac81e3c0057d654b5253219618a22ec3a346306239bba8cfa1a6", }, { image: "kindest/node:v1.21.1", friendlyImageName: "kindest/node:v1.21.1", pullImageName: "docker.io/kindest/node:v1.21.1", }, { image: "kindest/node@sha256:69860bda5563ac81e3c0057d654b5253219618a22ec3a346306239bba8cfa1a6", friendlyImageName: "kindest/node", pullImageName: "docker.io/kindest/node@sha256:69860bda5563ac81e3c0057d654b5253219618a22ec3a346306239bba8cfa1a6", }, { image: "foo.bar/kindest/node:v1.21.1@sha256:69860bda5563ac81e3c0057d654b5253219618a22ec3a346306239bba8cfa1a6", friendlyImageName: "foo.bar/kindest/node:v1.21.1", pullImageName: "foo.bar/kindest/node@sha256:69860bda5563ac81e3c0057d654b5253219618a22ec3a346306239bba8cfa1a6", }, { image: "foo.bar/kindest/node:v1.21.1", friendlyImageName: "foo.bar/kindest/node:v1.21.1", pullImageName: "foo.bar/kindest/node:v1.21.1", }, { image: "foo.bar/kindest/node@sha256:69860bda5563ac81e3c0057d654b5253219618a22ec3a346306239bba8cfa1a6", friendlyImageName: "foo.bar/kindest/node", pullImageName: "foo.bar/kindest/node@sha256:69860bda5563ac81e3c0057d654b5253219618a22ec3a346306239bba8cfa1a6", }, { image: "foo.bar/baz:quux@sha256:69860bda5563ac81e3c0057d654b5253219618a22ec3a346306239bba8cfa1a6", friendlyImageName: "foo.bar/baz:quux", pullImageName: "foo.bar/baz@sha256:69860bda5563ac81e3c0057d654b5253219618a22ec3a346306239bba8cfa1a6", }, { image: "foo.bar/baz:quux", friendlyImageName: "foo.bar/baz:quux", pullImageName: "foo.bar/baz:quux", }, { image: "foo.bar/baz@sha256:69860bda5563ac81e3c0057d654b5253219618a22ec3a346306239bba8cfa1a6", friendlyImageName: "foo.bar/baz", pullImageName: "foo.bar/baz@sha256:69860bda5563ac81e3c0057d654b5253219618a22ec3a346306239bba8cfa1a6", }, { image: "baz:quux@sha256:69860bda5563ac81e3c0057d654b5253219618a22ec3a346306239bba8cfa1a6", friendlyImageName: "baz:quux", pullImageName: "docker.io/library/baz@sha256:69860bda5563ac81e3c0057d654b5253219618a22ec3a346306239bba8cfa1a6", }, { image: "baz:quux", friendlyImageName: "baz:quux", pullImageName: "docker.io/library/baz:quux", }, { image: "baz@sha256:69860bda5563ac81e3c0057d654b5253219618a22ec3a346306239bba8cfa1a6", friendlyImageName: "baz", pullImageName: "docker.io/library/baz@sha256:69860bda5563ac81e3c0057d654b5253219618a22ec3a346306239bba8cfa1a6", }, } for _, tc := range cases { tc := tc // capture variable t.Run(tc.image, func(t *testing.T) { t.Parallel() friendlyImageName, pullImageName := sanitizeImage(tc.image) if friendlyImageName != tc.friendlyImageName { t.Errorf("Wrong friendlyImageName from %v: expected %v, received %v", tc.image, tc.friendlyImageName, friendlyImageName) } if pullImageName != tc.pullImageName { t.Errorf("Wrong pullImageName from %v: expected %v, received %v", tc.image, tc.pullImageName, pullImageName) } }) } } kind-0.27.0/pkg/cluster/internal/providers/podman/network.go000066400000000000000000000100201475376161000241210ustar00rootroot00000000000000/* Copyright 2020 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package podman import ( "crypto/sha1" "encoding/binary" "net" "regexp" "strings" "sigs.k8s.io/kind/pkg/errors" "sigs.k8s.io/kind/pkg/exec" ) // This may be overridden by KIND_EXPERIMENTAL_PODMAN_NETWORK env, // experimentally... // // By default currently picking a single network is equivalent to the previous // behavior *except* that we moved from the default bridge to a user defined // network because the default bridge is actually special versus any other // docker network and lacks the embedded DNS // // For now this also makes it easier for apps to join the same network, and // leaves users with complex networking desires to create and manage their own // networks. const fixedNetworkName = "kind" // ensureNetwork creates a new network // podman only creates IPv6 networks for versions >= 2.2.0 func ensureNetwork(name string) error { // network already exists if checkIfNetworkExists(name) { return nil } // generate unique subnet per network based on the name // obtained from the ULA fc00::/8 range // Make N attempts with "probing" in case we happen to collide subnet := generateULASubnetFromName(name, 0) err := createNetwork(name, subnet) if err == nil { // Success! return nil } if isUnknownIPv6FlagError(err) || isIPv6DisabledError(err) { return createNetwork(name, "") } // Only continue if the error is because of the subnet range // is already allocated if !isPoolOverlapError(err) { return err } // keep trying for ipv6 subnets const maxAttempts = 5 for attempt := int32(1); attempt < maxAttempts; attempt++ { subnet := generateULASubnetFromName(name, attempt) err = createNetwork(name, subnet) if err == nil { // success! return nil } else if !isPoolOverlapError(err) { // unknown error ... return err } } return errors.New("exhausted attempts trying to find a non-overlapping subnet") } func createNetwork(name, ipv6Subnet string) error { if ipv6Subnet == "" { return exec.Command("podman", "network", "create", "-d=bridge", name).Run() } return exec.Command("podman", "network", "create", "-d=bridge", "--ipv6", "--subnet", ipv6Subnet, name).Run() } func checkIfNetworkExists(name string) bool { _, err := exec.Output(exec.Command( "podman", "network", "inspect", regexp.QuoteMeta(name), )) return err == nil } func isUnknownIPv6FlagError(err error) bool { rerr := exec.RunErrorForError(err) return rerr != nil && strings.Contains(string(rerr.Output), "unknown flag: --ipv6") } func isIPv6DisabledError(err error) bool { rerr := exec.RunErrorForError(err) return rerr != nil && strings.Contains(string(rerr.Output), "is ipv6 enabled in the kernel") } func isPoolOverlapError(err error) bool { rerr := exec.RunErrorForError(err) if rerr == nil { return false } output := string(rerr.Output) return strings.Contains(output, "is already used on the host or by another config") || strings.Contains(output, "is being used by a network interface") || strings.Contains(output, "is already being used by a cni configuration") } // generateULASubnetFromName generate an IPv6 subnet based on the // name and Nth probing attempt func generateULASubnetFromName(name string, attempt int32) string { ip := make([]byte, 16) ip[0] = 0xfc ip[1] = 0x00 h := sha1.New() _, _ = h.Write([]byte(name)) _ = binary.Write(h, binary.LittleEndian, attempt) bs := h.Sum(nil) for i := 2; i < 8; i++ { ip[i] = bs[i] } subnet := &net.IPNet{ IP: net.IP(ip), Mask: net.CIDRMask(64, 128), } return subnet.String() } kind-0.27.0/pkg/cluster/internal/providers/podman/node.go000066400000000000000000000077671475376161000234050ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impliep. See the License for the specific language governing permissions and limitations under the License. */ package podman import ( "context" "fmt" "io" "strings" "sigs.k8s.io/kind/pkg/errors" "sigs.k8s.io/kind/pkg/exec" ) // nodes.Node implementation for the podman provider type node struct { name string } func (n *node) String() string { return n.name } func (n *node) Role() (string, error) { cmd := exec.Command("podman", "inspect", "--format", fmt.Sprintf(`{{ index .Config.Labels "%s"}}`, nodeRoleLabelKey), n.name, ) lines, err := exec.OutputLines(cmd) if err != nil { return "", errors.Wrap(err, "failed to get role for node") } if len(lines) != 1 { return "", errors.Errorf("failed to get role for node: output lines %d != 1", len(lines)) } return lines[0], nil } func (n *node) IP() (ipv4 string, ipv6 string, err error) { // retrieve the IP address of the node using podman inspect cmd := exec.Command("podman", "inspect", "-f", "{{range .NetworkSettings.Networks}}{{.IPAddress}},{{.GlobalIPv6Address}}{{end}}", n.name, // ... against the "node" container ) lines, err := exec.OutputLines(cmd) if err != nil { return "", "", errors.Wrap(err, "failed to get container details") } if len(lines) != 1 { return "", "", errors.Errorf("file should only be one line, got %d lines", len(lines)) } ips := strings.Split(lines[0], ",") if len(ips) != 2 { return "", "", errors.Errorf("container addresses should have 2 values, got %d values", len(ips)) } return ips[0], ips[1], nil } func (n *node) Command(command string, args ...string) exec.Cmd { return &nodeCmd{ nameOrID: n.name, command: command, args: args, } } func (n *node) CommandContext(ctx context.Context, command string, args ...string) exec.Cmd { return &nodeCmd{ nameOrID: n.name, command: command, args: args, ctx: ctx, } } // nodeCmd implements exec.Cmd for podman nodes type nodeCmd struct { nameOrID string // the container name or ID command string args []string env []string stdin io.Reader stdout io.Writer stderr io.Writer ctx context.Context } func (c *nodeCmd) Run() error { args := []string{ "exec", // run with privileges so we can remount etc.. // this might not make sense in the most general sense, but it is // important to many kind commands "--privileged", } if c.stdin != nil { args = append(args, "-i", // interactive so we can supply input ) } // set env for _, env := range c.env { args = append(args, "-e", env) } // specify the container and command, after this everything will be // args the command in the container rather than to podman args = append( args, c.nameOrID, // ... against the container c.command, // with the command specified ) args = append( args, // finally, with the caller args c.args..., ) var cmd exec.Cmd if c.ctx != nil { cmd = exec.CommandContext(c.ctx, "podman", args...) } else { cmd = exec.Command("podman", args...) } if c.stdin != nil { cmd.SetStdin(c.stdin) } if c.stderr != nil { cmd.SetStderr(c.stderr) } if c.stdout != nil { cmd.SetStdout(c.stdout) } return cmd.Run() } func (c *nodeCmd) SetEnv(env ...string) exec.Cmd { c.env = env return c } func (c *nodeCmd) SetStdin(r io.Reader) exec.Cmd { c.stdin = r return c } func (c *nodeCmd) SetStdout(w io.Writer) exec.Cmd { c.stdout = w return c } func (c *nodeCmd) SetStderr(w io.Writer) exec.Cmd { c.stderr = w return c } func (n *node) SerialLogs(w io.Writer) error { return exec.Command("podman", "logs", n.name).SetStdout(w).SetStderr(w).Run() } kind-0.27.0/pkg/cluster/internal/providers/podman/provider.go000066400000000000000000000336131475376161000242770ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impliep. See the License for the specific language governing permissions and limitations under the License. */ package podman import ( "encoding/json" "fmt" "net" "os" "path/filepath" "strconv" "strings" "sigs.k8s.io/kind/pkg/cluster/nodes" "sigs.k8s.io/kind/pkg/cluster/nodeutils" "sigs.k8s.io/kind/pkg/errors" "sigs.k8s.io/kind/pkg/exec" "sigs.k8s.io/kind/pkg/log" internallogs "sigs.k8s.io/kind/pkg/cluster/internal/logs" "sigs.k8s.io/kind/pkg/cluster/internal/providers" "sigs.k8s.io/kind/pkg/cluster/internal/providers/common" "sigs.k8s.io/kind/pkg/internal/apis/config" "sigs.k8s.io/kind/pkg/internal/cli" "sigs.k8s.io/kind/pkg/internal/sets" "sigs.k8s.io/kind/pkg/internal/version" ) // NewProvider returns a new provider based on executing `podman ...` func NewProvider(logger log.Logger) providers.Provider { logger.Warn("enabling experimental podman provider") return &provider{ logger: logger, } } // Provider implements provider.Provider // see NewProvider type provider struct { logger log.Logger info *providers.ProviderInfo } // String implements fmt.Stringer // NOTE: the value of this should not currently be relied upon for anything! // This is only used for setting the Node's providerID func (p *provider) String() string { return "podman" } // Provision is part of the providers.Provider interface func (p *provider) Provision(status *cli.Status, cfg *config.Cluster) (err error) { if err := ensureMinVersion(); err != nil { return err } // TODO: validate cfg // ensure node images are pulled before actually provisioning if err := ensureNodeImages(p.logger, status, cfg); err != nil { return err } // ensure the pre-requisite network exists networkName := fixedNetworkName if n := os.Getenv("KIND_EXPERIMENTAL_PODMAN_NETWORK"); n != "" { p.logger.Warn("WARNING: Overriding podman network due to KIND_EXPERIMENTAL_PODMAN_NETWORK") p.logger.Warn("WARNING: Here be dragons! This is not supported currently.") networkName = n } if err := ensureNetwork(networkName); err != nil { return errors.Wrap(err, "failed to ensure podman network") } // actually provision the cluster icons := strings.Repeat("📦 ", len(cfg.Nodes)) status.Start(fmt.Sprintf("Preparing nodes %s", icons)) defer func() { status.End(err == nil) }() // plan creating the containers createContainerFuncs, err := planCreation(cfg, networkName) if err != nil { return err } // actually create nodes return errors.UntilErrorConcurrent(createContainerFuncs) } // ListClusters is part of the providers.Provider interface func (p *provider) ListClusters() ([]string, error) { cmd := exec.Command("podman", "ps", "-a", // show stopped nodes // filter for nodes with the cluster label "--filter", "label="+clusterLabelKey, // format to include the cluster name "--format", fmt.Sprintf(`{{index .Labels "%s"}}`, clusterLabelKey), ) lines, err := exec.OutputLines(cmd) if err != nil { return nil, errors.Wrap(err, "failed to list clusters") } return sets.NewString(lines...).List(), nil } // ListNodes is part of the providers.Provider interface func (p *provider) ListNodes(cluster string) ([]nodes.Node, error) { cmd := exec.Command("podman", "ps", "-a", // show stopped nodes // filter for nodes with the cluster label "--filter", fmt.Sprintf("label=%s=%s", clusterLabelKey, cluster), // format to include the cluster name "--format", `{{.Names}}`, ) lines, err := exec.OutputLines(cmd) if err != nil { return nil, errors.Wrap(err, "failed to list nodes") } // convert names to node handles ret := make([]nodes.Node, 0, len(lines)) for _, name := range lines { ret = append(ret, p.node(name)) } return ret, nil } // DeleteNodes is part of the providers.Provider interface func (p *provider) DeleteNodes(n []nodes.Node) error { if len(n) == 0 { return nil } const command = "podman" args := make([]string, 0, len(n)+3) // allocate once args = append(args, "rm", "-f", // force the container to be delete now "-v", // delete volumes ) for _, node := range n { args = append(args, node.String()) } if err := exec.Command(command, args...).Run(); err != nil { return errors.Wrap(err, "failed to delete nodes") } var nodeVolumes []string for _, node := range n { volumes, err := getVolumes(node.String()) if err != nil { return err } nodeVolumes = append(nodeVolumes, volumes...) } if len(nodeVolumes) == 0 { return nil } return deleteVolumes(nodeVolumes) } // getHostIPOrDefault defaults HostIP to localhost if is not set // xref: https://github.com/kubernetes-sigs/kind/issues/3777 func getHostIPOrDefault(hostIP string) string { if hostIP == "" { return "127.0.0.1" } return hostIP } // GetAPIServerEndpoint is part of the providers.Provider interface func (p *provider) GetAPIServerEndpoint(cluster string) (string, error) { // locate the node that hosts this allNodes, err := p.ListNodes(cluster) if err != nil { return "", errors.Wrap(err, "failed to list nodes") } n, err := nodeutils.APIServerEndpointNode(allNodes) if err != nil { return "", errors.Wrap(err, "failed to get api server endpoint") } // TODO: get rid of this once podman settles on how to get the port mapping using podman inspect // This is only used to get the Kubeconfig server field v, err := getPodmanVersion() if err != nil { return "", errors.Wrap(err, "failed to check podman version") } // podman inspect was broken between 2.2.0 and 3.0.0 // https://github.com/containers/podman/issues/8444 if v.AtLeast(version.MustParseSemantic("2.2.0")) && v.LessThan(version.MustParseSemantic("3.0.0")) { p.logger.Warnf("WARNING: podman version %s not fully supported, please use versions 3.0.0+") cmd := exec.Command( "podman", "inspect", "--format", "{{range .NetworkSettings.Ports }}{{range .}}{{.HostIP}}/{{.HostPort}}{{end}}{{end}}", n.String(), ) lines, err := exec.OutputLines(cmd) if err != nil { return "", errors.Wrap(err, "failed to get api server port") } if len(lines) != 1 { return "", errors.Errorf("network details should only be one line, got %d lines", len(lines)) } // output is in the format IP/Port parts := strings.Split(strings.TrimSpace(lines[0]), "/") if len(parts) != 2 { return "", errors.Errorf("network details should be in the format IP/Port, received: %s", parts) } host := parts[0] port, err := strconv.Atoi(parts[1]) if err != nil { return "", errors.Errorf("network port not an integer: %v", err) } return net.JoinHostPort(host, strconv.Itoa(port)), nil } cmd := exec.Command( "podman", "inspect", "--format", "{{ json .NetworkSettings.Ports }}", n.String(), ) lines, err := exec.OutputLines(cmd) if err != nil { return "", errors.Wrap(err, "failed to get api server port") } if len(lines) != 1 { return "", errors.Errorf("network details should only be one line, got %d lines", len(lines)) } // portMapping19 maps to the standard CNI portmapping capability used in podman 1.9 // see: https://github.com/containernetworking/cni/blob/spec-v0.4.0/CONVENTIONS.md type portMapping19 struct { HostPort int32 `json:"hostPort"` ContainerPort int32 `json:"containerPort"` Protocol string `json:"protocol"` HostIP string `json:"hostIP"` } // portMapping20 maps to the podman 2.0 portmap type // see: https://github.com/containers/podman/blob/05988fc74fc25f2ad2256d6e011dfb7ad0b9a4eb/libpod/define/container_inspect.go#L134-L143 type portMapping20 struct { HostPort string `json:"HostPort"` HostIP string `json:"HostIp"` } portMappings20 := make(map[string][]portMapping20) if err := json.Unmarshal([]byte(lines[0]), &portMappings20); err == nil { for k, v := range portMappings20 { protocol := "tcp" parts := strings.Split(k, "/") if len(parts) == 2 { protocol = strings.ToLower(parts[1]) } containerPort, err := strconv.Atoi(parts[0]) if err != nil { return "", err } for _, pm := range v { if containerPort == common.APIServerInternalPort && protocol == "tcp" { return net.JoinHostPort(getHostIPOrDefault(pm.HostIP), pm.HostPort), nil } } } } var portMappings19 []portMapping19 if err := json.Unmarshal([]byte(lines[0]), &portMappings19); err != nil { return "", errors.Errorf("invalid network details: %v", err) } for _, pm := range portMappings19 { if pm.ContainerPort == common.APIServerInternalPort && pm.Protocol == "tcp" { return net.JoinHostPort(getHostIPOrDefault(pm.HostIP), strconv.Itoa(int(pm.HostPort))), nil } } return "", errors.Errorf("failed to get api server port") } // GetAPIServerInternalEndpoint is part of the providers.Provider interface func (p *provider) GetAPIServerInternalEndpoint(cluster string) (string, error) { // locate the node that hosts this allNodes, err := p.ListNodes(cluster) if err != nil { return "", errors.Wrap(err, "failed to list nodes") } n, err := nodeutils.APIServerEndpointNode(allNodes) if err != nil { return "", errors.Wrap(err, "failed to get apiserver endpoint") } // NOTE: we're using the nodes's hostnames which are their names return net.JoinHostPort(n.String(), fmt.Sprintf("%d", common.APIServerInternalPort)), nil } // node returns a new node handle for this provider func (p *provider) node(name string) nodes.Node { return &node{ name: name, } } // CollectLogs will populate dir with cluster logs and other debug files func (p *provider) CollectLogs(dir string, nodes []nodes.Node) error { execToPathFn := func(cmd exec.Cmd, path string) func() error { return func() error { f, err := common.FileOnHost(path) if err != nil { return err } defer f.Close() return cmd.SetStdout(f).SetStderr(f).Run() } } // construct a slice of methods to collect logs fns := []func() error{ // record info about the host podman execToPathFn( exec.Command("podman", "info"), filepath.Join(dir, "podman-info.txt"), ), } // collect /var/log for each node and plan collecting more logs var errs []error for _, n := range nodes { node := n // https://golang.org/doc/faq#closures_and_goroutines name := node.String() path := filepath.Join(dir, name) if err := internallogs.DumpDir(p.logger, node, "/var/log", path); err != nil { errs = append(errs, err) } fns = append(fns, func() error { return common.CollectLogs(node, path) }, execToPathFn(exec.Command("podman", "inspect", name), filepath.Join(path, "inspect.json")), func() error { f, err := common.FileOnHost(filepath.Join(path, "serial.log")) if err != nil { return err } return node.SerialLogs(f) }, ) } // run and collect up all errors errs = append(errs, errors.AggregateConcurrent(fns)) return errors.NewAggregate(errs) } // Info returns the provider info. // The info is cached on the first time of the execution. func (p *provider) Info() (*providers.ProviderInfo, error) { if p.info == nil { var err error p.info, err = info(p.logger) if err != nil { return p.info, err } } return p.info, nil } // podmanInfo corresponds to `podman info --format 'json`. // The structure is different from `docker info --format '{{json .}}'`, // and lacks information about the availability of the cgroup controllers. type podmanInfo struct { Host struct { CgroupVersion string `json:"cgroupVersion,omitempty"` // "v2" CgroupControllers []string `json:"cgroupControllers,omitempty"` Security struct { Rootless bool `json:"rootless,omitempty"` } `json:"security"` } `json:"host"` } // info detects ProviderInfo by executing `podman info --format json`. func info(logger log.Logger) (*providers.ProviderInfo, error) { const podman = "podman" args := []string{"info", "--format", "json"} cmd := exec.Command(podman, args...) out, err := exec.Output(cmd) if err != nil { return nil, errors.Wrapf(err, "failed to get podman info (%s %s): %q", podman, strings.Join(args, " "), string(out)) } var pInfo podmanInfo if err := json.Unmarshal(out, &pInfo); err != nil { return nil, err } stringSliceContains := func(s []string, str string) bool { for _, v := range s { if v == str { return true } } return false } // Since Podman version before v4.0.0 does not gives controller info. // We assume all the cgroup controllers to be available. // For rootless, this assumption is not always correct, // so we print the warning below. cgroupSupportsMemoryLimit := true cgroupSupportsPidsLimit := true cgroupSupportsCPUShares := true v, err := getPodmanVersion() if err != nil { return nil, errors.Wrap(err, "failed to check podman version") } // Info for controllers must be available after v4.0.0 // via https://github.com/containers/podman/pull/10387 if v.AtLeast(version.MustParseSemantic("4.0.0")) { cgroupSupportsMemoryLimit = stringSliceContains(pInfo.Host.CgroupControllers, "memory") cgroupSupportsPidsLimit = stringSliceContains(pInfo.Host.CgroupControllers, "pids") cgroupSupportsCPUShares = stringSliceContains(pInfo.Host.CgroupControllers, "cpu") } info := &providers.ProviderInfo{ Rootless: pInfo.Host.Security.Rootless, Cgroup2: pInfo.Host.CgroupVersion == "v2", SupportsMemoryLimit: cgroupSupportsMemoryLimit, SupportsPidsLimit: cgroupSupportsPidsLimit, SupportsCPUShares: cgroupSupportsCPUShares, } if info.Rootless && !v.AtLeast(version.MustParseSemantic("4.0.0")) { if logger != nil { logger.Warn("Cgroup controller detection is not implemented for Podman. " + "If you see cgroup-related errors, you might need to set systemd property \"Delegate=yes\", see https://kind.sigs.k8s.io/docs/user/rootless/") } } return info, nil } kind-0.27.0/pkg/cluster/internal/providers/podman/provision.go000066400000000000000000000360001475376161000244660ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package podman import ( "context" "encoding/json" "fmt" "net" "path/filepath" "strings" "time" "sigs.k8s.io/kind/pkg/cluster/constants" "sigs.k8s.io/kind/pkg/errors" "sigs.k8s.io/kind/pkg/exec" "sigs.k8s.io/kind/pkg/cluster/internal/loadbalancer" "sigs.k8s.io/kind/pkg/cluster/internal/providers/common" "sigs.k8s.io/kind/pkg/internal/apis/config" ) // planCreation creates a slice of funcs that will create the containers func planCreation(cfg *config.Cluster, networkName string) (createContainerFuncs []func() error, err error) { // these apply to all container creation nodeNamer := common.MakeNodeNamer(cfg.Name) names := make([]string, len(cfg.Nodes)) for i, node := range cfg.Nodes { name := nodeNamer(string(node.Role)) // name the node names[i] = name } haveLoadbalancer := config.ClusterHasImplicitLoadBalancer(cfg) if haveLoadbalancer { names = append(names, nodeNamer(constants.ExternalLoadBalancerNodeRoleValue)) } genericArgs, err := commonArgs(cfg, networkName, names) if err != nil { return nil, err } // only the external LB should reflect the port if we have multiple control planes apiServerPort := cfg.Networking.APIServerPort apiServerAddress := cfg.Networking.APIServerAddress if config.ClusterHasImplicitLoadBalancer(cfg) { // TODO: picking ports locally is less than ideal with a remote runtime // (does podman have this?) // but this is supposed to be an implementation detail and NOT picking // them breaks host reboot ... // For now remote podman + multi control plane is not supported apiServerPort = 0 // replaced with random ports apiServerAddress = "127.0.0.1" // only the LB needs to be non-local // only for IPv6 only clusters if cfg.Networking.IPFamily == config.IPv6Family { apiServerAddress = "::1" // only the LB needs to be non-local } // plan loadbalancer node name := names[len(names)-1] createContainerFuncs = append(createContainerFuncs, func() error { args, err := runArgsForLoadBalancer(cfg, name, genericArgs) if err != nil { return err } return createContainer(name, args) }) } // plan normal nodes for i, node := range cfg.Nodes { node := node.DeepCopy() // copy so we can modify name := names[i] // fixup relative paths, podman can only handle absolute paths for i := range node.ExtraMounts { hostPath := node.ExtraMounts[i].HostPath absHostPath, err := filepath.Abs(hostPath) if err != nil { return nil, errors.Wrapf(err, "unable to resolve absolute path for hostPath: %q", hostPath) } node.ExtraMounts[i].HostPath = absHostPath } // plan actual creation based on role switch node.Role { case config.ControlPlaneRole: createContainerFuncs = append(createContainerFuncs, func() error { node.ExtraPortMappings = append(node.ExtraPortMappings, config.PortMapping{ ListenAddress: apiServerAddress, HostPort: apiServerPort, ContainerPort: common.APIServerInternalPort, }, ) args, err := runArgsForNode(node, cfg.Networking.IPFamily, name, genericArgs) if err != nil { return err } return createContainerWithWaitUntilSystemdReachesMultiUserSystem(name, args) }) case config.WorkerRole: createContainerFuncs = append(createContainerFuncs, func() error { args, err := runArgsForNode(node, cfg.Networking.IPFamily, name, genericArgs) if err != nil { return err } return createContainerWithWaitUntilSystemdReachesMultiUserSystem(name, args) }) default: return nil, errors.Errorf("unknown node role: %q", node.Role) } } return createContainerFuncs, nil } // commonArgs computes static arguments that apply to all containers func commonArgs(cfg *config.Cluster, networkName string, nodeNames []string) ([]string, error) { // standard arguments all nodes containers need, computed once args := []string{ "--detach", // run the container detached "--tty", // allocate a tty for entrypoint logs "--net", networkName, // attach to its own network // label the node with the cluster ID "--label", fmt.Sprintf("%s=%s", clusterLabelKey, cfg.Name), // specify container implementation to systemd "-e", "container=podman", // this is the default in cgroupsv2 but not in v1 "--cgroupns=private", } // enable IPv6 if necessary if config.ClusterHasIPv6(cfg) { args = append(args, "--sysctl=net.ipv6.conf.all.disable_ipv6=0", "--sysctl=net.ipv6.conf.all.forwarding=1") } // pass proxy environment variables proxyEnv, err := getProxyEnv(cfg, networkName, nodeNames) if err != nil { return nil, errors.Wrap(err, "proxy setup error") } for key, val := range proxyEnv { args = append(args, "-e", fmt.Sprintf("%s=%s", key, val)) } // handle Podman on Btrfs or ZFS same as we do with Docker // https://github.com/kubernetes-sigs/kind/issues/1416#issuecomment-606514724 if mountDevMapper() { args = append(args, "--volume", "/dev/mapper:/dev/mapper") } // rootless: use fuse-overlayfs by default // https://github.com/kubernetes-sigs/kind/issues/2275 if mountFuse() { args = append(args, "--device", "/dev/fuse") } if cfg.Networking.DNSSearch != nil { args = append(args, "-e", "KIND_DNS_SEARCH="+strings.Join(*cfg.Networking.DNSSearch, " ")) } return args, nil } func runArgsForNode(node *config.Node, clusterIPFamily config.ClusterIPFamily, name string, args []string) ([]string, error) { // Pre-create anonymous volumes to enable specifying mount options // during container run time varVolume, err := createAnonymousVolume(name) if err != nil { return nil, err } args = append([]string{ "--hostname", name, // make hostname match container name // label the node with the role ID "--label", fmt.Sprintf("%s=%s", nodeRoleLabelKey, node.Role), // running containers in a container requires privileged // NOTE: we could try to replicate this with --cap-add, and use less // privileges, but this flag also changes some mounts that are necessary // including some ones podman would otherwise do by default. // for now this is what we want. in the future we may revisit this. "--privileged", // runtime temporary storage "--tmpfs", "/tmp", // various things depend on working /tmp "--tmpfs", "/run", // systemd wants a writable /run // runtime persistent storage // this ensures that E.G. pods, logs etc. are not on the container // filesystem, which is not only better for performance, but allows // running kind in kind for "party tricks" // (please don't depend on doing this though!) // also enable default docker volume options // suid: SUID applications on the volume will be able to change their privilege // exec: executables on the volume will be able to executed within the container // dev: devices on the volume will be able to be used by processes within the container "--volume", fmt.Sprintf("%s:/var:suid,exec,dev", varVolume), // some k8s things want to read /lib/modules "--volume", "/lib/modules:/lib/modules:ro", // propagate KIND_EXPERIMENTAL_CONTAINERD_SNAPSHOTTER to the entrypoint script "-e", "KIND_EXPERIMENTAL_CONTAINERD_SNAPSHOTTER", }, args..., ) // convert mounts and port mappings to container run args args = append(args, generateMountBindings(node.ExtraMounts...)...) mappingArgs, err := generatePortMappings(clusterIPFamily, node.ExtraPortMappings...) if err != nil { return nil, err } args = append(args, mappingArgs...) switch node.Role { case config.ControlPlaneRole: args = append(args, "-e", "KUBECONFIG=/etc/kubernetes/admin.conf") } // finally, specify the image to run _, image := sanitizeImage(node.Image) return append(args, image), nil } func runArgsForLoadBalancer(cfg *config.Cluster, name string, args []string) ([]string, error) { args = append([]string{ "--hostname", name, // make hostname match container name // label the node with the role ID "--label", fmt.Sprintf("%s=%s", nodeRoleLabelKey, constants.ExternalLoadBalancerNodeRoleValue), }, args..., ) // load balancer port mapping mappingArgs, err := generatePortMappings(cfg.Networking.IPFamily, config.PortMapping{ ListenAddress: cfg.Networking.APIServerAddress, HostPort: cfg.Networking.APIServerPort, ContainerPort: common.APIServerInternalPort, }, ) if err != nil { return nil, err } args = append(args, mappingArgs...) // finally, specify the image to run _, image := sanitizeImage(loadbalancer.Image) return append(args, image), nil } func getProxyEnv(cfg *config.Cluster, networkName string, nodeNames []string) (map[string]string, error) { envs := common.GetProxyEnvs(cfg) // Specifically add the podman network subnets to NO_PROXY if we are using a proxy if len(envs) > 0 { // kind default bridge is "kind" subnets, err := getSubnets(networkName) if err != nil { return nil, err } noProxyList := append(subnets, envs[common.NOProxy]) noProxyList = append(noProxyList, nodeNames...) // Add pod,service and all the cluster nodes' dns names to no_proxy to allow in cluster // Note: this is best effort based on the default CoreDNS spec // https://github.com/kubernetes/dns/blob/master/docs/specification.md // Any user created pod/service hostnames, namespaces, custom DNS services // are expected to be no-proxied by the user explicitly. noProxyList = append(noProxyList, ".svc", ".svc.cluster", ".svc.cluster.local") noProxyJoined := strings.Join(noProxyList, ",") envs[common.NOProxy] = noProxyJoined envs[strings.ToLower(common.NOProxy)] = noProxyJoined } return envs, nil } type podmanNetworks []struct { // v4+ Subnets []struct { Subnet string `json:"subnet"` Gateway string `json:"gateway"` } `json:"subnets"` // v3 and anything still using CNI/IPAM Plugins []struct { Ipam struct { Ranges [][]struct { Gateway string `json:"gateway"` Subnet string `json:"subnet"` } `json:"ranges"` } `json:"ipam,omitempty"` } `json:"plugins"` } func getSubnets(networkName string) ([]string, error) { cmd := exec.Command("podman", "network", "inspect", networkName) out, err := exec.Output(cmd) if err != nil { return nil, errors.Wrap(err, "failed to get subnets") } networks := podmanNetworks{} jsonErr := json.Unmarshal([]byte(out), &networks) if jsonErr != nil { return nil, errors.Wrap(jsonErr, "failed to get subnets") } subnets := []string{} for _, network := range networks { if len(network.Subnets) > 0 { for _, subnet := range network.Subnets { subnets = append(subnets, subnet.Subnet) } } if len(network.Plugins) > 0 { for _, plugin := range network.Plugins { for _, r := range plugin.Ipam.Ranges { for _, rr := range r { subnets = append(subnets, rr.Subnet) } } } } } return subnets, nil } // generateMountBindings converts the mount list to a list of args for podman // ':[:options]', where 'options' // is a comma-separated list of the following strings: // 'ro', if the path is read only // 'Z', if the volume requires SELinux relabeling func generateMountBindings(mounts ...config.Mount) []string { args := make([]string, 0, len(mounts)) for _, m := range mounts { bind := fmt.Sprintf("%s:%s", m.HostPath, m.ContainerPath) var attrs []string if m.Readonly { attrs = append(attrs, "ro") } // Only request relabeling if the pod provides an SELinux context. If the pod // does not provide an SELinux context relabeling will label the volume with // the container's randomly allocated MCS label. This would restrict access // to the volume to the container which mounts it first. if m.SelinuxRelabel { attrs = append(attrs, "Z") } switch m.Propagation { case config.MountPropagationNone: // noop, private is default case config.MountPropagationBidirectional: attrs = append(attrs, "rshared") case config.MountPropagationHostToContainer: attrs = append(attrs, "rslave") default: // Falls back to "private" } if len(attrs) > 0 { bind = fmt.Sprintf("%s:%s", bind, strings.Join(attrs, ",")) } args = append(args, fmt.Sprintf("--volume=%s", bind)) } return args } // generatePortMappings converts the portMappings list to a list of args for podman func generatePortMappings(clusterIPFamily config.ClusterIPFamily, portMappings ...config.PortMapping) ([]string, error) { args := make([]string, 0, len(portMappings)) for _, pm := range portMappings { // do provider internal defaulting // in a future API revision we will handle this at the API level and remove this if pm.ListenAddress == "" { switch clusterIPFamily { case config.IPv4Family, config.DualStackFamily: pm.ListenAddress = "0.0.0.0" case config.IPv6Family: pm.ListenAddress = "::" default: return nil, errors.Errorf("unknown cluster IP family: %v", clusterIPFamily) } } if string(pm.Protocol) == "" { pm.Protocol = config.PortMappingProtocolTCP // TCP is the default } // validate that the provider can handle this binding switch pm.Protocol { case config.PortMappingProtocolTCP: case config.PortMappingProtocolUDP: case config.PortMappingProtocolSCTP: default: return nil, errors.Errorf("unknown port mapping protocol: %v", pm.Protocol) } // get a random port if necessary (port = 0) hostPort, releaseHostPortFn, err := common.PortOrGetFreePort(pm.HostPort, pm.ListenAddress) if err != nil { return nil, errors.Wrap(err, "failed to get random host port for port mapping") } if releaseHostPortFn != nil { defer releaseHostPortFn() } // generate the actual mapping arg protocol := string(pm.Protocol) hostPortBinding := net.JoinHostPort(pm.ListenAddress, fmt.Sprintf("%d", hostPort)) // Podman expects empty string instead of 0 to assign a random port // https://github.com/containers/libpod/blob/master/pkg/spec/ports.go#L68-L69 if strings.HasSuffix(hostPortBinding, ":0") { hostPortBinding = strings.TrimSuffix(hostPortBinding, "0") } args = append(args, fmt.Sprintf("--publish=%s:%d/%s", hostPortBinding, pm.ContainerPort, strings.ToLower(protocol))) } return args, nil } func createContainer(name string, args []string) error { return exec.Command("podman", append([]string{"run", "--name", name}, args...)...).Run() } func createContainerWithWaitUntilSystemdReachesMultiUserSystem(name string, args []string) error { if err := exec.Command("podman", append([]string{"run", "--name", name}, args...)...).Run(); err != nil { return err } logCtx, logCancel := context.WithTimeout(context.Background(), 30*time.Second) defer logCancel() logCmd := exec.CommandContext(logCtx, "podman", "logs", "-f", name) return common.WaitUntilLogRegexpMatches(logCtx, logCmd, common.NodeReachedCgroupsReadyRegexp()) } kind-0.27.0/pkg/cluster/internal/providers/podman/util.go000066400000000000000000000106631475376161000234220ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package podman import ( "encoding/json" "fmt" "strings" "sigs.k8s.io/kind/pkg/errors" "sigs.k8s.io/kind/pkg/exec" "sigs.k8s.io/kind/pkg/internal/version" ) // IsAvailable checks if podman is available in the system func IsAvailable() bool { cmd := exec.Command("podman", "-v") lines, err := exec.OutputLines(cmd) if err != nil || len(lines) != 1 { return false } return strings.HasPrefix(lines[0], "podman version") } func getPodmanVersion() (*version.Version, error) { cmd := exec.Command("podman", "--version") lines, err := exec.OutputLines(cmd) if err != nil { return nil, err } // output is like `podman version 1.7.1-dev` if len(lines) != 1 { return nil, errors.Errorf("podman version should only be one line, got %d", len(lines)) } parts := strings.Split(lines[0], " ") if len(parts) != 3 { return nil, errors.Errorf("podman --version contents should have 3 parts, got %q", lines[0]) } return version.ParseSemantic(parts[2]) } const ( minSupportedVersion = "1.8.0" ) func ensureMinVersion() error { // ensure that podman version is a compatible version v, err := getPodmanVersion() if err != nil { return errors.Wrap(err, "failed to check podman version") } if !v.AtLeast(version.MustParseSemantic(minSupportedVersion)) { return errors.Errorf("podman version %q is too old, please upgrade to %q or later", v, minSupportedVersion) } return nil } // createAnonymousVolume creates a new anonymous volume // with the specified label=true // returns the name of the volume created func createAnonymousVolume(label string) (string, error) { cmd := exec.Command("podman", "volume", "create", // podman only support filter on key during list // so we use the unique id as key "--label", fmt.Sprintf("%s=true", label)) name, err := exec.Output(cmd) if err != nil { return "", err } return strings.TrimSuffix(string(name), "\n"), nil } // getVolumes gets volume names filtered on specified label func getVolumes(label string) ([]string, error) { cmd := exec.Command("podman", "volume", "ls", "--filter", fmt.Sprintf("label=%s", label), "--quiet") // `output` from the above command is names of all volumes each followed by `\n`. output, err := exec.Output(cmd) if err != nil { return nil, err } if string(output) == "" { // no volumes return nil, nil } // Trim away the last `\n`. trimmedOutput := strings.TrimSuffix(string(output), "\n") // Get names of all volumes by splitting via `\n`. return strings.Split(string(trimmedOutput), "\n"), nil } func deleteVolumes(names []string) error { args := []string{ "volume", "rm", "--force", } args = append(args, names...) cmd := exec.Command("podman", args...) return cmd.Run() } // mountDevMapper checks if the podman storage driver is Btrfs or ZFS func mountDevMapper() bool { cmd := exec.Command("podman", "info", "--format", "json") out, err := exec.Output(cmd) if err != nil { return false } var pInfo podmanStorageInfo if err := json.Unmarshal(out, &pInfo); err != nil { return false } // match docker logic pkg/cluster/internal/providers/docker/util.go if pInfo.Store.GraphDriverName == "btrfs" || pInfo.Store.GraphDriverName == "zfs" || pInfo.Store.GraphDriverName == "devicemapper" || pInfo.Store.GraphStatus.BackingFilesystem == "btrfs" || pInfo.Store.GraphStatus.BackingFilesystem == "xfs" || pInfo.Store.GraphStatus.BackingFilesystem == "zfs" { return true } return false } type podmanStorageInfo struct { Store struct { GraphDriverName string `json:"graphDriverName,omitempty"` GraphStatus struct { BackingFilesystem string `json:"Backing Filesystem,omitempty"` // "v2" } `json:"graphStatus"` } `json:"store"` } // rootless: use fuse-overlayfs by default // https://github.com/kubernetes-sigs/kind/issues/2275 func mountFuse() bool { i, err := info(nil) if err != nil { return false } if i != nil && i.Rootless { return true } return false } kind-0.27.0/pkg/cluster/internal/providers/provider.go000066400000000000000000000042551475376161000230210ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package providers import ( "sigs.k8s.io/kind/pkg/cluster/nodes" "sigs.k8s.io/kind/pkg/internal/apis/config" "sigs.k8s.io/kind/pkg/internal/cli" ) // Provider represents a provider of cluster / node infrastructure // This is an alpha-grade internal API type Provider interface { // Provision should create and start the nodes, just short of // actually starting up Kubernetes, based on the given cluster config Provision(status *cli.Status, cfg *config.Cluster) error // ListClusters discovers the clusters that currently have resources // under this providers ListClusters() ([]string, error) // ListNodes returns the nodes under this provider for the given // cluster name, they may or may not be running correctly ListNodes(cluster string) ([]nodes.Node, error) // DeleteNodes deletes the provided list of nodes // These should be from results previously returned by this provider // E.G. by ListNodes() DeleteNodes([]nodes.Node) error // GetAPIServerEndpoint returns the host endpoint for the cluster's API server GetAPIServerEndpoint(cluster string) (string, error) // GetAPIServerInternalEndpoint returns the internal network endpoint for the cluster's API server GetAPIServerInternalEndpoint(cluster string) (string, error) // CollectLogs will populate dir with cluster logs and other debug files CollectLogs(dir string, nodes []nodes.Node) error // Info returns the provider info Info() (*ProviderInfo, error) } // ProviderInfo is the info of the provider type ProviderInfo struct { Rootless bool Cgroup2 bool SupportsMemoryLimit bool SupportsPidsLimit bool SupportsCPUShares bool } kind-0.27.0/pkg/cluster/nodes/000077500000000000000000000000001475376161000161115ustar00rootroot00000000000000kind-0.27.0/pkg/cluster/nodes/doc.go000066400000000000000000000012201475376161000172000ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package nodes provides a kind specific definition of a cluster node package nodes kind-0.27.0/pkg/cluster/nodes/types.go000066400000000000000000000024251475376161000176070ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package nodes import ( "io" "sigs.k8s.io/kind/pkg/exec" ) // Node represents a kind cluster node type Node interface { // The node should implement exec.Cmder for running commands against the node // see: sigs.k8s.io/kind/pkg/exec exec.Cmder // String should return the node name String() string // see also: fmt.Stringer // Role should return the node's role Role() (string, error) // see also: pkg/cluster/constants // TODO(bentheelder): should return node addresses more generally // Possibly remove this method in favor of obtaining this detail with // exec or from the provider IP() (ipv4 string, ipv6 string, err error) // SerialLogs collects the "node" container logs SerialLogs(writer io.Writer) error } kind-0.27.0/pkg/cluster/nodeutils/000077500000000000000000000000001475376161000170075ustar00rootroot00000000000000kind-0.27.0/pkg/cluster/nodeutils/doc.go000066400000000000000000000013501475376161000201020ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package nodeutils contains functionality for Kubernetes-in-Docker nodes // It mostly exists to break up functionality from sigs.k8s.io/kind/pkg/cluster package nodeutils kind-0.27.0/pkg/cluster/nodeutils/roles.go000066400000000000000000000114611475376161000204650ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package nodeutils import ( "sort" "strings" "sigs.k8s.io/kind/pkg/cluster/constants" "sigs.k8s.io/kind/pkg/cluster/nodes" "sigs.k8s.io/kind/pkg/errors" ) // SelectNodesByRole returns a list of nodes with the matching role // TODO(bentheelder): remove this in favor of specific role select methods // and avoid the unnecessary error handling func SelectNodesByRole(allNodes []nodes.Node, role string) ([]nodes.Node, error) { out := []nodes.Node{} for _, node := range allNodes { nodeRole, err := node.Role() if err != nil { return nil, err } if nodeRole == role { out = append(out, node) } } return out, nil } // InternalNodes returns the list of container IDs for the "nodes" in the cluster // that are ~Kubernetes nodes, as opposed to e.g. the external loadbalancer for HA func InternalNodes(allNodes []nodes.Node) ([]nodes.Node, error) { selectedNodes := []nodes.Node{} for _, node := range allNodes { nodeRole, err := node.Role() if err != nil { return nil, err } if nodeRole == constants.WorkerNodeRoleValue || nodeRole == constants.ControlPlaneNodeRoleValue { selectedNodes = append(selectedNodes, node) } } return selectedNodes, nil } // ExternalLoadBalancerNode returns a node handle for the external control plane // loadbalancer node or nil if there isn't one func ExternalLoadBalancerNode(allNodes []nodes.Node) (nodes.Node, error) { // identify and validate external load balancer node loadBalancerNodes, err := SelectNodesByRole( allNodes, constants.ExternalLoadBalancerNodeRoleValue, ) if err != nil { return nil, err } if len(loadBalancerNodes) < 1 { return nil, nil } if len(loadBalancerNodes) > 1 { return nil, errors.Errorf( "unexpected number of %s nodes %d", constants.ExternalLoadBalancerNodeRoleValue, len(loadBalancerNodes), ) } return loadBalancerNodes[0], nil } // APIServerEndpointNode selects the node from allNodes which hosts the API Server endpoint // This should be the control plane node if there is one control plane node, or a LoadBalancer otherwise. // It returns an error if the node list is invalid (E.G. two control planes and no load balancer) func APIServerEndpointNode(allNodes []nodes.Node) (nodes.Node, error) { if n, err := ExternalLoadBalancerNode(allNodes); err != nil { return nil, errors.Wrap(err, "failed to find api-server endpoint node") } else if n != nil { return n, nil } n, err := ControlPlaneNodes(allNodes) if err != nil { return nil, errors.Wrap(err, "failed to find api-server endpoint node") } if len(n) != 1 { return nil, errors.Errorf("expected one control plane node or a load balancer, not %d and none", len(n)) } return n[0], nil } // ControlPlaneNodes returns all control plane nodes such that the first entry // is the bootstrap control plane node func ControlPlaneNodes(allNodes []nodes.Node) ([]nodes.Node, error) { controlPlaneNodes, err := SelectNodesByRole( allNodes, constants.ControlPlaneNodeRoleValue, ) if err != nil { return nil, err } // pick the first by sorting // TODO(bentheelder): perhaps in the future we should mark this node // specially at container creation time sort.Slice(controlPlaneNodes, func(i, j int) bool { return strings.Compare(controlPlaneNodes[i].String(), controlPlaneNodes[j].String()) < 0 }) return controlPlaneNodes, nil } // BootstrapControlPlaneNode returns a handle to the bootstrap control plane node // TODO(bentheelder): remove this. This node shouldn't be special (fix that first) func BootstrapControlPlaneNode(allNodes []nodes.Node) (nodes.Node, error) { controlPlaneNodes, err := ControlPlaneNodes(allNodes) if err != nil { return nil, err } if len(controlPlaneNodes) < 1 { return nil, errors.Errorf( "expected at least one %s node", constants.ControlPlaneNodeRoleValue, ) } return controlPlaneNodes[0], nil } // SecondaryControlPlaneNodes returns handles to the secondary // control plane nodes and NOT the bootstrap control plane node func SecondaryControlPlaneNodes(allNodes []nodes.Node) ([]nodes.Node, error) { controlPlaneNodes, err := ControlPlaneNodes(allNodes) if err != nil { return nil, err } if len(controlPlaneNodes) < 1 { return nil, errors.Errorf( "expected at least one %s node", constants.ControlPlaneNodeRoleValue, ) } return controlPlaneNodes[1:], nil } kind-0.27.0/pkg/cluster/nodeutils/util.go000066400000000000000000000130471475376161000203200ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package nodeutils import ( "bytes" "encoding/json" "fmt" "io" "path" "strings" "github.com/pelletier/go-toml" "sigs.k8s.io/kind/pkg/cluster/nodes" "sigs.k8s.io/kind/pkg/errors" "sigs.k8s.io/kind/pkg/exec" ) // KubeVersion returns the Kubernetes version installed on the node func KubeVersion(n nodes.Node) (version string, err error) { // grab kubernetes version from the node image cmd := n.Command("cat", "/kind/version") lines, err := exec.OutputLines(cmd) if err != nil { return "", errors.Wrap(err, "failed to get file") } if len(lines) != 1 { return "", errors.Errorf("file should only be one line, got %d lines", len(lines)) } return lines[0], nil } // WriteFile writes content to dest on the node func WriteFile(n nodes.Node, dest, content string) error { // create destination directory err := n.Command("mkdir", "-p", path.Dir(dest)).Run() if err != nil { return errors.Wrapf(err, "failed to create directory %s", path.Dir(dest)) } return n.Command("cp", "/dev/stdin", dest).SetStdin(strings.NewReader(content)).Run() } // CopyNodeToNode copies file from a to b func CopyNodeToNode(a, b nodes.Node, file string) error { // create destination directory err := b.Command("mkdir", "-p", path.Dir(file)).Run() if err != nil { return errors.Wrapf(err, "failed to create directory %q", path.Dir(file)) } // TODO: experiment with streaming instead to avoid the copy // for now we only use this for small files so it's not worth the complexity var buff bytes.Buffer if err := a.Command("cat", file).SetStdout(&buff).Run(); err != nil { return errors.Wrapf(err, "failed to read %q from node", file) } if err := b.Command("cp", "/dev/stdin", file).SetStdin(&buff).Run(); err != nil { return errors.Wrapf(err, "failed to write %q to node", file) } return nil } // LoadImageArchive loads image onto the node, where image is a Reader over an image archive func LoadImageArchive(n nodes.Node, image io.Reader) error { snapshotter, err := getSnapshotter(n) if err != nil { return err } cmd := n.Command("ctr", "--namespace=k8s.io", "images", "import", "--all-platforms", "--digests", "--snapshotter="+snapshotter, "-").SetStdin(image) if err := cmd.Run(); err != nil { return errors.Wrap(err, "failed to load image") } return nil } func getSnapshotter(n nodes.Node) (string, error) { out, err := exec.Output(n.Command("containerd", "config", "dump")) if err != nil { return "", errors.Wrap(err, "failed to detect containerd snapshotter") } return parseSnapshotter(string(out)) } func parseSnapshotter(config string) (string, error) { parsed, err := toml.Load(config) if err != nil { return "", errors.Wrap(err, "failed to detect containerd snapshotter") } configVersion, ok := parsed.Get("version").(int64) if !ok { return "", errors.New("failed to detect containerd config version") } var snapshotter string switch configVersion { case 2: // Introduced in containerd v1.3. Still supported in containerd v2. snapshotter, ok = parsed.GetPath([]string{"plugins", "io.containerd.grpc.v1.cri", "containerd", "snapshotter"}).(string) if !ok { return "", errors.New("failed to detect containerd snapshotter (config version 2)") } case 3: // Introduced in containerd v2.0. snapshotter, ok = parsed.GetPath([]string{"plugins", "io.containerd.cri.v1.images", "snapshotter"}).(string) if !ok { return "", errors.New("failed to detect containerd snapshotter (config version 3)") } default: return "", fmt.Errorf("unknown containerd config version: %d (supported versions: 2 and 3)", configVersion) } return snapshotter, nil } // ImageID returns ID of image on the node with the given image name if present func ImageID(n nodes.Node, image string) (string, error) { var out bytes.Buffer if err := n.Command("crictl", "inspecti", image).SetStdout(&out).Run(); err != nil { return "", err } // we only care about the image ID crictlOut := struct { Status struct { ID string `json:"id"` } `json:"status"` }{} if err := json.Unmarshal(out.Bytes(), &crictlOut); err != nil { return "", err } return crictlOut.Status.ID, nil } // ImageTags is used to perform a reverse lookup of the ImageID to list set of available // RepoTags corresponding to the ImageID in question func ImageTags(n nodes.Node, imageID string) (map[string]bool, error) { var out bytes.Buffer tags := make(map[string]bool, 0) if err := n.Command("crictl", "inspecti", imageID).SetStdout(&out).Run(); err != nil { return tags, err } crictlOut := struct { Status struct { RepoTags []string `json:"repoTags"` } `json:"status"` }{} if err := json.Unmarshal(out.Bytes(), &crictlOut); err != nil { return tags, err } for _, tag := range crictlOut.Status.RepoTags { tags[tag] = true } return tags, nil } // ReTagImage is used to tag an ImageID with a custom tag specified by imageName parameter func ReTagImage(n nodes.Node, imageID, imageName string) error { var out bytes.Buffer return n.Command("ctr", "--namespace=k8s.io", "images", "tag", "--force", imageID, imageName).SetStdout(&out).Run() } kind-0.27.0/pkg/cluster/nodeutils/util_test.go000066400000000000000000000163101475376161000213530ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package nodeutils import ( "testing" ) func TestParseSnapshotter(t *testing.T) { // a real, known kind node containerd config config := `disabled_plugins = [] imports = ["/etc/containerd/config.toml"] oom_score = 0 plugin_dir = "" required_plugins = [] root = "/var/lib/containerd" state = "/run/containerd" version = 2 [cgroup] path = "" [debug] address = "" format = "" gid = 0 level = "" uid = 0 [grpc] address = "/run/containerd/containerd.sock" gid = 0 max_recv_message_size = 16777216 max_send_message_size = 16777216 tcp_address = "" tcp_tls_cert = "" tcp_tls_key = "" uid = 0 [metrics] address = "" grpc_histogram = false [plugins] [plugins."io.containerd.gc.v1.scheduler"] deletion_threshold = 0 mutation_threshold = 100 pause_threshold = 0.02 schedule_delay = "0s" startup_delay = "100ms" [plugins."io.containerd.grpc.v1.cri"] disable_apparmor = false disable_cgroup = false disable_hugetlb_controller = true disable_proc_mount = false disable_tcp_service = true enable_selinux = false enable_tls_streaming = false ignore_image_defined_volumes = false max_concurrent_downloads = 3 max_container_log_line_size = 16384 netns_mounts_under_state_dir = false restrict_oom_score_adj = false sandbox_image = "registry.k8s.io/pause:3.7" selinux_category_range = 1024 stats_collect_period = 10 stream_idle_timeout = "4h0m0s" stream_server_address = "127.0.0.1" stream_server_port = "0" systemd_cgroup = false tolerate_missing_hugetlb_controller = true unset_seccomp_profile = "" [plugins."io.containerd.grpc.v1.cri".cni] bin_dir = "/opt/cni/bin" conf_dir = "/etc/cni/net.d" conf_template = "" max_conf_num = 1 [plugins."io.containerd.grpc.v1.cri".containerd] default_runtime_name = "runc" disable_snapshot_annotations = true discard_unpacked_layers = true no_pivot = false snapshotter = "overlayfs" [plugins."io.containerd.grpc.v1.cri".containerd.default_runtime] base_runtime_spec = "" container_annotations = [] pod_annotations = [] privileged_without_host_devices = false runtime_engine = "" runtime_root = "" runtime_type = "" [plugins."io.containerd.grpc.v1.cri".containerd.default_runtime.options] [plugins."io.containerd.grpc.v1.cri".containerd.runtimes] [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc] base_runtime_spec = "/etc/containerd/cri-base.json" container_annotations = [] pod_annotations = [] privileged_without_host_devices = false runtime_engine = "" runtime_root = "" runtime_type = "io.containerd.runc.v2" [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options] [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.test-handler] base_runtime_spec = "" container_annotations = [] pod_annotations = [] privileged_without_host_devices = false runtime_engine = "" runtime_root = "" runtime_type = "io.containerd.runc.v2" [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.test-handler.options] [plugins."io.containerd.grpc.v1.cri".containerd.untrusted_workload_runtime] base_runtime_spec = "" container_annotations = [] pod_annotations = [] privileged_without_host_devices = false runtime_engine = "" runtime_root = "" runtime_type = "" [plugins."io.containerd.grpc.v1.cri".containerd.untrusted_workload_runtime.options] [plugins."io.containerd.grpc.v1.cri".image_decryption] key_model = "node" [plugins."io.containerd.grpc.v1.cri".registry] config_path = "" [plugins."io.containerd.grpc.v1.cri".registry.auths] [plugins."io.containerd.grpc.v1.cri".registry.configs] [plugins."io.containerd.grpc.v1.cri".registry.headers] [plugins."io.containerd.grpc.v1.cri".registry.mirrors] [plugins."io.containerd.grpc.v1.cri".x509_key_pair_streaming] tls_cert_file = "" tls_key_file = "" [plugins."io.containerd.internal.v1.opt"] path = "/opt/containerd" [plugins."io.containerd.internal.v1.restart"] interval = "10s" [plugins."io.containerd.metadata.v1.bolt"] content_sharing_policy = "shared" [plugins."io.containerd.monitor.v1.cgroups"] no_prometheus = false [plugins."io.containerd.runtime.v1.linux"] no_shim = false runtime = "runc" runtime_root = "" shim = "containerd-shim" shim_debug = false [plugins."io.containerd.runtime.v2.task"] platforms = ["linux/amd64"] [plugins."io.containerd.service.v1.diff-service"] default = ["walking"] [plugins."io.containerd.snapshotter.v1.aufs"] root_path = "" [plugins."io.containerd.snapshotter.v1.btrfs"] root_path = "" [plugins."io.containerd.snapshotter.v1.devmapper"] async_remove = false base_image_size = "" pool_name = "" root_path = "" [plugins."io.containerd.snapshotter.v1.native"] root_path = "" [plugins."io.containerd.snapshotter.v1.overlayfs"] root_path = "" [plugins."io.containerd.snapshotter.v1.zfs"] root_path = "" [proxy_plugins] [proxy_plugins.fuse-overlayfs] address = "/run/containerd-fuse-overlayfs.sock" type = "snapshot" [stream_processors] [stream_processors."io.containerd.ocicrypt.decoder.v1.tar"] accepts = ["application/vnd.oci.image.layer.v1.tar+encrypted"] args = ["--decryption-keys-path", "/etc/containerd/ocicrypt/keys"] env = ["OCICRYPT_KEYPROVIDER_CONFIG=/etc/containerd/ocicrypt/ocicrypt_keyprovider.conf"] path = "ctd-decoder" returns = "application/vnd.oci.image.layer.v1.tar" [stream_processors."io.containerd.ocicrypt.decoder.v1.tar.gzip"] accepts = ["application/vnd.oci.image.layer.v1.tar+gzip+encrypted"] args = ["--decryption-keys-path", "/etc/containerd/ocicrypt/keys"] env = ["OCICRYPT_KEYPROVIDER_CONFIG=/etc/containerd/ocicrypt/ocicrypt_keyprovider.conf"] path = "ctd-decoder" returns = "application/vnd.oci.image.layer.v1.tar+gzip" [timeouts] "io.containerd.timeout.shim.cleanup" = "5s" "io.containerd.timeout.shim.load" = "5s" "io.containerd.timeout.shim.shutdown" = "3s" "io.containerd.timeout.task.state" = "2s" [ttrpc] address = "" gid = 0 uid = 0` snapshotter, err := parseSnapshotter(config) if err != nil { t.Fatalf("unexpected error parsing config: %v", err) } if snapshotter != "overlayfs" { t.Fatalf(`unexpected parsed snapshotter: %q, expected "overlayfs"`, snapshotter) } // sanity check parsing an empty config _, err = parseSnapshotter("") if err == nil { t.Fatal("expected error parsing empty config") } // sanity check parsing invalid toml _, err = parseSnapshotter("aaaa") if err == nil { t.Fatal("expected error parsing invalid config") } } kind-0.27.0/pkg/cluster/provider.go000066400000000000000000000205121475376161000171620ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package cluster import ( "os" "path/filepath" "sort" "sigs.k8s.io/kind/pkg/cmd/kind/version" "sigs.k8s.io/kind/pkg/cluster/constants" "sigs.k8s.io/kind/pkg/cluster/nodes" "sigs.k8s.io/kind/pkg/cluster/nodeutils" "sigs.k8s.io/kind/pkg/errors" "sigs.k8s.io/kind/pkg/log" internalcreate "sigs.k8s.io/kind/pkg/cluster/internal/create" internaldelete "sigs.k8s.io/kind/pkg/cluster/internal/delete" "sigs.k8s.io/kind/pkg/cluster/internal/kubeconfig" internalproviders "sigs.k8s.io/kind/pkg/cluster/internal/providers" "sigs.k8s.io/kind/pkg/cluster/internal/providers/docker" "sigs.k8s.io/kind/pkg/cluster/internal/providers/nerdctl" "sigs.k8s.io/kind/pkg/cluster/internal/providers/podman" ) // DefaultName is the default cluster name const DefaultName = constants.DefaultClusterName // defaultName is a helper that given a name defaults it if unset func defaultName(name string) string { if name == "" { name = DefaultName } return name } // Provider is used to perform cluster operations type Provider struct { provider internalproviders.Provider logger log.Logger } // NewProvider returns a new provider based on the supplied options func NewProvider(options ...ProviderOption) *Provider { p := &Provider{ logger: log.NoopLogger{}, } // Ensure we apply the logger options first, while maintaining the order // otherwise. This way we can trivially init the internal provider with // the logger. sort.SliceStable(options, func(i, j int) bool { _, iIsLogger := options[i].(providerLoggerOption) _, jIsLogger := options[j].(providerLoggerOption) return iIsLogger && !jIsLogger }) for _, o := range options { if o != nil { o.apply(p) } } // ensure a provider if none was set // NOTE: depends on logger being set (see sorting above) if p.provider == nil { // DetectNodeProvider does not fallback to allow callers to determine // this behavior // However for compatibility if the caller of NewProvider supplied no // option and we autodetect internally, we default to the docker provider // for fallback, to avoid a breaking change for now. // This may change in the future. // TODO: consider breaking this API for earlier errors. providerOpt, _ := DetectNodeProvider() if providerOpt == nil { providerOpt = ProviderWithDocker() } providerOpt.apply(p) } return p } // NoNodeProviderDetectedError indicates that we could not autolocate an available // NodeProvider backend on the host var NoNodeProviderDetectedError = errors.NewWithoutStack("failed to detect any supported node provider") // DetectNodeProvider allows callers to autodetect the node provider // *without* fallback to the default. // // Pass the returned ProviderOption to NewProvider to pass the auto-detect Docker // or Podman option explicitly (in the future there will be more options) // // NOTE: The kind *cli* also checks `KIND_EXPERIMENTAL_PROVIDER` for "podman", // "nerctl" or "docker" currently and does not auto-detect / respects this if set. // // This will be replaced with some other mechanism in the future (likely when // podman support is GA), in the meantime though your tool may wish to match this. // // In the future when this is not considered experimental, // that logic will be in a public API as well. func DetectNodeProvider() (ProviderOption, error) { // auto-detect based on each node provider's IsAvailable() function if docker.IsAvailable() { return ProviderWithDocker(), nil } if nerdctl.IsAvailable() { return ProviderWithNerdctl(""), nil } if podman.IsAvailable() { return ProviderWithPodman(), nil } return nil, errors.WithStack(NoNodeProviderDetectedError) } // ProviderOption is an option for configuring a provider type ProviderOption interface { apply(p *Provider) } // providerLoggerOption is a trivial ProviderOption adapter // we use a type specific to logging options so we can handle them first type providerLoggerOption func(p *Provider) func (a providerLoggerOption) apply(p *Provider) { a(p) } var _ ProviderOption = providerLoggerOption(nil) // ProviderWithLogger configures the provider to use Logger logger func ProviderWithLogger(logger log.Logger) ProviderOption { return providerLoggerOption(func(p *Provider) { p.logger = logger }) } // providerRuntimeOption is a trivial ProviderOption adapter // we use a type specific to logging options so we can handle them first type providerRuntimeOption func(p *Provider) func (a providerRuntimeOption) apply(p *Provider) { a(p) } var _ ProviderOption = providerRuntimeOption(nil) // ProviderWithDocker configures the provider to use docker runtime func ProviderWithDocker() ProviderOption { return providerRuntimeOption(func(p *Provider) { p.provider = docker.NewProvider(p.logger) }) } // ProviderWithPodman configures the provider to use podman runtime func ProviderWithPodman() ProviderOption { return providerRuntimeOption(func(p *Provider) { p.provider = podman.NewProvider(p.logger) }) } // ProviderWithNerdctl configures the provider to use the nerdctl runtime func ProviderWithNerdctl(binaryName string) ProviderOption { return providerRuntimeOption(func(p *Provider) { p.provider = nerdctl.NewProvider(p.logger, binaryName) }) } // Create provisions and starts a kubernetes-in-docker cluster func (p *Provider) Create(name string, options ...CreateOption) error { // apply options opts := &internalcreate.ClusterOptions{ NameOverride: name, } for _, o := range options { if err := o.apply(opts); err != nil { return err } } return internalcreate.Cluster(p.logger, p.provider, opts) } // Delete tears down a kubernetes-in-docker cluster func (p *Provider) Delete(name, explicitKubeconfigPath string) error { return internaldelete.Cluster(p.logger, p.provider, defaultName(name), explicitKubeconfigPath) } // List returns a list of clusters for which nodes exist func (p *Provider) List() ([]string, error) { return p.provider.ListClusters() } // KubeConfig returns the KUBECONFIG for the cluster // If internal is true, this will contain the internal IP etc. // If internal is false, this will contain the host IP etc. func (p *Provider) KubeConfig(name string, internal bool) (string, error) { return kubeconfig.Get(p.provider, defaultName(name), !internal) } // ExportKubeConfig exports the KUBECONFIG for the cluster, merging // it into the selected file, following the rules from // https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#config // where explicitPath is the --kubeconfig value. func (p *Provider) ExportKubeConfig(name string, explicitPath string, internal bool) error { return kubeconfig.Export(p.provider, defaultName(name), explicitPath, !internal) } // ListNodes returns the list of container IDs for the "nodes" in the cluster func (p *Provider) ListNodes(name string) ([]nodes.Node, error) { return p.provider.ListNodes(defaultName(name)) } // ListInternalNodes returns the list of container IDs for the "nodes" in the cluster // that are not external func (p *Provider) ListInternalNodes(name string) ([]nodes.Node, error) { n, err := p.provider.ListNodes(name) if err != nil { return nil, err } return nodeutils.InternalNodes(n) } // CollectLogs will populate dir with cluster logs and other debug files func (p *Provider) CollectLogs(name, dir string) error { // TODO: should use ListNodes and Collect should handle nodes differently // based on role ... n, err := p.ListInternalNodes(name) if err != nil { return err } // ensure directory if err := os.MkdirAll(dir, os.ModePerm); err != nil { return errors.Wrap(err, "failed to create logs directory") } // write kind version if err := os.WriteFile( filepath.Join(dir, "kind-version.txt"), []byte(version.DisplayVersion()), 0666, // match os.Create ); err != nil { return errors.Wrap(err, "failed to write kind-version.txt") } // collect and write cluster logs return p.provider.CollectLogs(dir, n) } kind-0.27.0/pkg/cmd/000077500000000000000000000000001475376161000140635ustar00rootroot00000000000000kind-0.27.0/pkg/cmd/doc.go000066400000000000000000000012021475376161000151520ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package cmd provides helpers used by kind's commands / cli package cmd kind-0.27.0/pkg/cmd/iostreams.go000066400000000000000000000022671475376161000164270ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package cmd import ( "io" "os" ) // IOStreams provides the standard names for iostreams. // This is useful for embedding and for unit testing. // Inconsistent and different names make it hard to read and review code // This is based on cli-runtime, but just the nice type without the dependency type IOStreams struct { // In think, os.Stdin In io.Reader // Out think, os.Stdout Out io.Writer // ErrOut think, os.Stderr ErrOut io.Writer } // StandardIOStreams returns an IOStreams from os.Stdin, os.Stdout func StandardIOStreams() IOStreams { return IOStreams{ In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr, } } kind-0.27.0/pkg/cmd/kind/000077500000000000000000000000001475376161000150105ustar00rootroot00000000000000kind-0.27.0/pkg/cmd/kind/build/000077500000000000000000000000001475376161000161075ustar00rootroot00000000000000kind-0.27.0/pkg/cmd/kind/build/build.go000066400000000000000000000024751475376161000175450ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package build implements the `build` command package build import ( "errors" "github.com/spf13/cobra" "sigs.k8s.io/kind/pkg/cmd" "sigs.k8s.io/kind/pkg/cmd/kind/build/nodeimage" "sigs.k8s.io/kind/pkg/log" ) // NewCommand returns a new cobra.Command for building func NewCommand(logger log.Logger, streams cmd.IOStreams) *cobra.Command { cmd := &cobra.Command{ // TODO(bentheelder): more detailed usage Use: "build", Short: "Build one of [node-image]", Long: "Build one of [node-image]", RunE: func(cmd *cobra.Command, args []string) error { err := cmd.Help() if err != nil { return err } return errors.New("Subcommand is required") }, } // add subcommands cmd.AddCommand(nodeimage.NewCommand(logger, streams)) return cmd } kind-0.27.0/pkg/cmd/kind/build/nodeimage/000077500000000000000000000000001475376161000200375ustar00rootroot00000000000000kind-0.27.0/pkg/cmd/kind/build/nodeimage/nodeimage.go000066400000000000000000000046361475376161000223270ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package nodeimage import ( "github.com/spf13/cobra" "sigs.k8s.io/kind/pkg/build/nodeimage" "sigs.k8s.io/kind/pkg/cmd" "sigs.k8s.io/kind/pkg/errors" "sigs.k8s.io/kind/pkg/log" ) type flagpole struct { Source string BuildType string Image string BaseImage string Arch string } // NewCommand returns a new cobra.Command for building the node image func NewCommand(logger log.Logger, streams cmd.IOStreams) *cobra.Command { flags := &flagpole{} cmd := &cobra.Command{ Args: cobra.MaximumNArgs(1), // TODO(bentheelder): more detailed usage Use: "node-image [kubernetes-source]", Short: "Build the node image", Long: "Build the node image which contains Kubernetes build artifacts and other kind requirements", RunE: func(cmd *cobra.Command, args []string) error { return runE(logger, flags, args) }, } cmd.Flags().StringVar( &flags.BuildType, "type", "", "optionally specify one of 'url', 'file', 'release' or 'source' as the type of build", ) cmd.Flags().StringVar( &flags.Image, "image", nodeimage.DefaultImage, "name:tag of the resulting image to be built", ) cmd.Flags().StringVar( &flags.BaseImage, "base-image", nodeimage.DefaultBaseImage, "name:tag of the base image to use for the build", ) cmd.Flags().StringVar( &flags.Arch, "arch", "", "architecture to build for, defaults to the host architecture", ) return cmd } func runE(logger log.Logger, flags *flagpole, args []string) error { sourceSpec := "" if len(args) > 0 { sourceSpec = args[0] } if err := nodeimage.Build( nodeimage.WithImage(flags.Image), nodeimage.WithBaseImage(flags.BaseImage), nodeimage.WithKubeParam(sourceSpec), nodeimage.WithLogger(logger), nodeimage.WithArch(flags.Arch), nodeimage.WithBuildType(flags.BuildType), ); err != nil { return errors.Wrap(err, "error building node image") } return nil } kind-0.27.0/pkg/cmd/kind/completion/000077500000000000000000000000001475376161000171615ustar00rootroot00000000000000kind-0.27.0/pkg/cmd/kind/completion/bash/000077500000000000000000000000001475376161000200765ustar00rootroot00000000000000kind-0.27.0/pkg/cmd/kind/completion/bash/bash.go000066400000000000000000000021331475376161000213410ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package bash implements the `bash` command package bash import ( "github.com/spf13/cobra" "sigs.k8s.io/kind/pkg/cmd" "sigs.k8s.io/kind/pkg/log" ) // NewCommand returns a new cobra.Command for cluster creation func NewCommand(logger log.Logger, streams cmd.IOStreams) *cobra.Command { cmd := &cobra.Command{ Args: cobra.NoArgs, Use: "bash", Short: "Output shell completions for bash", RunE: func(cmd *cobra.Command, args []string) error { return cmd.Parent().Parent().GenBashCompletion(streams.Out) }, } return cmd } kind-0.27.0/pkg/cmd/kind/completion/completion.go000066400000000000000000000050731475376161000216660ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package completion implements the `completion` command package completion import ( "errors" "github.com/spf13/cobra" "sigs.k8s.io/kind/pkg/cmd" "sigs.k8s.io/kind/pkg/cmd/kind/completion/bash" "sigs.k8s.io/kind/pkg/cmd/kind/completion/fish" "sigs.k8s.io/kind/pkg/cmd/kind/completion/powershell" "sigs.k8s.io/kind/pkg/cmd/kind/completion/zsh" "sigs.k8s.io/kind/pkg/log" ) // NewCommand returns a new cobra.Command for cluster creation func NewCommand(logger log.Logger, streams cmd.IOStreams) *cobra.Command { cmd := &cobra.Command{ Use: "completion", Short: "Output shell completion code for the specified shell (bash, zsh or fish)", Long: longDescription, RunE: func(cmd *cobra.Command, args []string) error { err := cmd.Help() if err != nil { return err } return errors.New("Subcommand is required") }, } cmd.AddCommand(zsh.NewCommand(logger, streams)) cmd.AddCommand(bash.NewCommand(logger, streams)) cmd.AddCommand(fish.NewCommand(logger, streams)) cmd.AddCommand(powershell.NewCommand(logger, streams)) return cmd } const longDescription = ` Outputs kind shell completion for the given shell (bash, fish, powershell, or zsh) This depends on the bash-completion binary. Example installation instructions: # for bash users $ kind completion bash > ~/.kind-completion $ source ~/.kind-completion # for zsh users % kind completion zsh > /usr/local/share/zsh/site-functions/_kind % autoload -U compinit && compinit # or if zsh-completion is installed via homebrew % kind completion zsh > "${fpath[1]}/_kind" # or if you use oh-my-zsh (needs zsh-completions plugin) % mkdir $ZSH/completions/ % kind completion zsh > $ZSH/completions/_kind # for fish users % kind completion fish > ~/.config/fish/completions/kind.fish # for powershell users PS> kind completion powershell | Out-String | Invoke-Expression Additionally, you may want to output the completion to a file and source in your .bashrc Note for zsh users: [1] zsh completions are only supported in versions of zsh >= 5.2 ` kind-0.27.0/pkg/cmd/kind/completion/fish/000077500000000000000000000000001475376161000201125ustar00rootroot00000000000000kind-0.27.0/pkg/cmd/kind/completion/fish/fish.go000066400000000000000000000021121475376161000213660ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package fish implements the `fish` command package fish import ( "github.com/spf13/cobra" "sigs.k8s.io/kind/pkg/cmd" "sigs.k8s.io/kind/pkg/log" ) // NewCommand returns a new cobra.Command for cluster creation func NewCommand(logger log.Logger, streams cmd.IOStreams) *cobra.Command { cmd := &cobra.Command{ Use: "fish", Short: "Output shell completions for fish", RunE: func(cmd *cobra.Command, args []string) error { return cmd.Parent().Parent().GenFishCompletion(streams.Out, true) }, } return cmd } kind-0.27.0/pkg/cmd/kind/completion/powershell/000077500000000000000000000000001475376161000213455ustar00rootroot00000000000000kind-0.27.0/pkg/cmd/kind/completion/powershell/powershell.go000066400000000000000000000021771475376161000240670ustar00rootroot00000000000000/* Copyright 2024 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package powershell implements the `powershell` command package powershell import ( "github.com/spf13/cobra" "sigs.k8s.io/kind/pkg/cmd" "sigs.k8s.io/kind/pkg/log" ) // NewCommand returns a new cobra.Command for cluster creation func NewCommand(logger log.Logger, streams cmd.IOStreams) *cobra.Command { cmd := &cobra.Command{ Args: cobra.NoArgs, Use: "powershell", Short: "Output shell completions for powershell", RunE: func(cmd *cobra.Command, args []string) error { return cmd.Parent().Parent().GenPowerShellCompletion(streams.Out) }, } return cmd } kind-0.27.0/pkg/cmd/kind/completion/zsh/000077500000000000000000000000001475376161000177655ustar00rootroot00000000000000kind-0.27.0/pkg/cmd/kind/completion/zsh/zsh.go000066400000000000000000000021251475376161000211200ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package zsh implements the `zsh` command package zsh import ( "github.com/spf13/cobra" "sigs.k8s.io/kind/pkg/cmd" "sigs.k8s.io/kind/pkg/log" ) // NewCommand returns a new cobra.Command for cluster creation func NewCommand(logger log.Logger, streams cmd.IOStreams) *cobra.Command { cmd := &cobra.Command{ Args: cobra.NoArgs, Use: "zsh", Short: "Output shell completions for zsh", RunE: func(cmd *cobra.Command, args []string) error { return cmd.Parent().Parent().GenZshCompletion(streams.Out) }, } return cmd } kind-0.27.0/pkg/cmd/kind/create/000077500000000000000000000000001475376161000162535ustar00rootroot00000000000000kind-0.27.0/pkg/cmd/kind/create/cluster/000077500000000000000000000000001475376161000177345ustar00rootroot00000000000000kind-0.27.0/pkg/cmd/kind/create/cluster/createcluster.go000066400000000000000000000070641475376161000231370ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package cluster implements the `create cluster` command package cluster import ( "io" "time" "github.com/spf13/cobra" "sigs.k8s.io/kind/pkg/cluster" "sigs.k8s.io/kind/pkg/cmd" "sigs.k8s.io/kind/pkg/errors" "sigs.k8s.io/kind/pkg/log" "sigs.k8s.io/kind/pkg/internal/cli" "sigs.k8s.io/kind/pkg/internal/runtime" ) type flagpole struct { Name string Config string ImageName string Retain bool Wait time.Duration Kubeconfig string } // NewCommand returns a new cobra.Command for cluster creation func NewCommand(logger log.Logger, streams cmd.IOStreams) *cobra.Command { flags := &flagpole{} cmd := &cobra.Command{ Args: cobra.NoArgs, Use: "cluster", Short: "Creates a local Kubernetes cluster", Long: "Creates a local Kubernetes cluster using Docker container 'nodes'", RunE: func(cmd *cobra.Command, args []string) error { cli.OverrideDefaultName(cmd.Flags()) return runE(logger, streams, flags) }, } cmd.Flags().StringVarP( &flags.Name, "name", "n", "", "cluster name, overrides KIND_CLUSTER_NAME, config (default kind)", ) cmd.Flags().StringVar( &flags.Config, "config", "", "path to a kind config file", ) cmd.Flags().StringVar( &flags.ImageName, "image", "", "node docker image to use for booting the cluster", ) cmd.Flags().BoolVar( &flags.Retain, "retain", false, "retain nodes for debugging when cluster creation fails", ) cmd.Flags().DurationVar( &flags.Wait, "wait", time.Duration(0), "wait for control plane node to be ready (default 0s)", ) cmd.Flags().StringVar( &flags.Kubeconfig, "kubeconfig", "", "sets kubeconfig path instead of $KUBECONFIG or $HOME/.kube/config", ) return cmd } func runE(logger log.Logger, streams cmd.IOStreams, flags *flagpole) error { provider := cluster.NewProvider( cluster.ProviderWithLogger(logger), runtime.GetDefault(logger), ) // handle config flag, we might need to read from stdin withConfig, err := configOption(flags.Config, streams.In) if err != nil { return err } // create the cluster if err = provider.Create( flags.Name, withConfig, cluster.CreateWithNodeImage(flags.ImageName), cluster.CreateWithRetain(flags.Retain), cluster.CreateWithWaitForReady(flags.Wait), cluster.CreateWithKubeconfigPath(flags.Kubeconfig), cluster.CreateWithDisplayUsage(true), cluster.CreateWithDisplaySalutation(true), ); err != nil { return errors.Wrap(err, "failed to create cluster") } return nil } // configOption converts the raw --config flag value to a cluster creation // option matching it. it will read from stdin if the flag value is `-` func configOption(rawConfigFlag string, stdin io.Reader) (cluster.CreateOption, error) { // if not - then we are using a real file if rawConfigFlag != "-" { return cluster.CreateWithConfigFile(rawConfigFlag), nil } // otherwise read from stdin raw, err := io.ReadAll(stdin) if err != nil { return nil, errors.Wrap(err, "error reading config from stdin") } return cluster.CreateWithRawConfig(raw), nil } kind-0.27.0/pkg/cmd/kind/create/create.go000066400000000000000000000024611475376161000200500ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package create implements the `create` command package create import ( "errors" "github.com/spf13/cobra" "sigs.k8s.io/kind/pkg/cmd" createcluster "sigs.k8s.io/kind/pkg/cmd/kind/create/cluster" "sigs.k8s.io/kind/pkg/log" ) // NewCommand returns a new cobra.Command for cluster creation func NewCommand(logger log.Logger, streams cmd.IOStreams) *cobra.Command { cmd := &cobra.Command{ Use: "create", Short: "Creates one of [cluster]", Long: "Creates one of local Kubernetes cluster (cluster)", RunE: func(cmd *cobra.Command, args []string) error { err := cmd.Help() if err != nil { return err } return errors.New("Subcommand is required") }, } cmd.AddCommand(createcluster.NewCommand(logger, streams)) return cmd } kind-0.27.0/pkg/cmd/kind/delete/000077500000000000000000000000001475376161000162525ustar00rootroot00000000000000kind-0.27.0/pkg/cmd/kind/delete/cluster/000077500000000000000000000000001475376161000177335ustar00rootroot00000000000000kind-0.27.0/pkg/cmd/kind/delete/cluster/deletecluster.go000066400000000000000000000045111475376161000231270ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package cluster implements the `delete` command package cluster import ( "github.com/spf13/cobra" "sigs.k8s.io/kind/pkg/cluster" "sigs.k8s.io/kind/pkg/cmd" "sigs.k8s.io/kind/pkg/errors" "sigs.k8s.io/kind/pkg/log" "sigs.k8s.io/kind/pkg/internal/cli" "sigs.k8s.io/kind/pkg/internal/runtime" ) type flagpole struct { Name string Kubeconfig string } // NewCommand returns a new cobra.Command for cluster deletion func NewCommand(logger log.Logger, streams cmd.IOStreams) *cobra.Command { flags := &flagpole{} cmd := &cobra.Command{ Args: cobra.NoArgs, Use: "cluster", Short: "Deletes a cluster", Long: `Deletes a Kind cluster from the system. This is an idempotent operation, meaning it may be called multiple times without failing (like "rm -f"). If the cluster resources exist they will be deleted, and if the cluster is already gone it will just return success. Errors will only occur if the cluster resources exist and are not able to be deleted. `, RunE: func(cmd *cobra.Command, args []string) error { cli.OverrideDefaultName(cmd.Flags()) return deleteCluster(logger, flags) }, } cmd.Flags().StringVarP( &flags.Name, "name", "n", cluster.DefaultName, "the cluster name", ) cmd.Flags().StringVar( &flags.Kubeconfig, "kubeconfig", "", "sets kubeconfig path instead of $KUBECONFIG or $HOME/.kube/config", ) return cmd } func deleteCluster(logger log.Logger, flags *flagpole) error { provider := cluster.NewProvider( cluster.ProviderWithLogger(logger), runtime.GetDefault(logger), ) // Delete individual cluster logger.V(0).Infof("Deleting cluster %q ...", flags.Name) if err := provider.Delete(flags.Name, flags.Kubeconfig); err != nil { return errors.Wrapf(err, "failed to delete cluster %q", flags.Name) } return nil } kind-0.27.0/pkg/cmd/kind/delete/clusters/000077500000000000000000000000001475376161000201165ustar00rootroot00000000000000kind-0.27.0/pkg/cmd/kind/delete/clusters/deleteclusters.go000066400000000000000000000052501475376161000234760ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package clusters implements the `delete` command for multiple clusters package clusters import ( "github.com/spf13/cobra" "sigs.k8s.io/kind/pkg/cluster" "sigs.k8s.io/kind/pkg/cmd" "sigs.k8s.io/kind/pkg/errors" "sigs.k8s.io/kind/pkg/log" "sigs.k8s.io/kind/pkg/internal/runtime" ) type flagpole struct { Kubeconfig string All bool } // NewCommand returns a new cobra.Command for cluster deletion func NewCommand(logger log.Logger, streams cmd.IOStreams) *cobra.Command { flags := &flagpole{} cmd := &cobra.Command{ Args: cobra.MinimumNArgs(0), Use: "clusters", Short: "Deletes one or more clusters", Long: `Deletes one or more Kind clusters from the system. This is an idempotent operation, meaning it may be called multiple times without failing (like "rm -f"). If the cluster resources exist they will be deleted, and if the cluster is already gone it will just return success. Errors will only occur if the cluster resources exist and are not able to be deleted. `, RunE: func(cmd *cobra.Command, args []string) error { if !flags.All && len(args) == 0 { return errors.New("no cluster names provided") } return deleteClusters(logger, flags, args) }, } cmd.Flags().StringVar( &flags.Kubeconfig, "kubeconfig", "", "sets kubeconfig path instead of $KUBECONFIG or $HOME/.kube/config", ) cmd.Flags().BoolVarP( &flags.All, "all", "A", false, "delete all clusters", ) return cmd } func deleteClusters(logger log.Logger, flags *flagpole, clusters []string) error { provider := cluster.NewProvider( cluster.ProviderWithLogger(logger), runtime.GetDefault(logger), ) var err error if flags.All { //Delete all clusters if clusters, err = provider.List(); err != nil { return errors.Wrap(err, "failed listing clusters for delete") } } var success []string for _, cluster := range clusters { if err = provider.Delete(cluster, flags.Kubeconfig); err != nil { logger.V(0).Infof("%s\n", errors.Wrapf(err, "failed to delete cluster %q", cluster)) continue } success = append(success, cluster) } logger.V(0).Infof("Deleted clusters: %q", success) return nil } kind-0.27.0/pkg/cmd/kind/delete/delete.go000066400000000000000000000027001475376161000200420ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package delete implements the `delete` command package delete import ( "errors" "github.com/spf13/cobra" "sigs.k8s.io/kind/pkg/cmd" deletecluster "sigs.k8s.io/kind/pkg/cmd/kind/delete/cluster" deleteclusters "sigs.k8s.io/kind/pkg/cmd/kind/delete/clusters" "sigs.k8s.io/kind/pkg/log" ) // NewCommand returns a new cobra.Command for cluster deletion func NewCommand(logger log.Logger, streams cmd.IOStreams) *cobra.Command { cmd := &cobra.Command{ // TODO(bentheelder): more detailed usage Use: "delete", Short: "Deletes one of [cluster]", Long: "Deletes one of [cluster]", RunE: func(cmd *cobra.Command, args []string) error { err := cmd.Help() if err != nil { return err } return errors.New("Subcommand is required") }, } cmd.AddCommand(deletecluster.NewCommand(logger, streams)) cmd.AddCommand(deleteclusters.NewCommand(logger, streams)) return cmd } kind-0.27.0/pkg/cmd/kind/export/000077500000000000000000000000001475376161000163315ustar00rootroot00000000000000kind-0.27.0/pkg/cmd/kind/export/export.go000066400000000000000000000026611475376161000202060ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package export implements the `export` command package export import ( "errors" "github.com/spf13/cobra" "sigs.k8s.io/kind/pkg/cmd" "sigs.k8s.io/kind/pkg/cmd/kind/export/kubeconfig" "sigs.k8s.io/kind/pkg/cmd/kind/export/logs" "sigs.k8s.io/kind/pkg/log" ) // NewCommand returns a new cobra.Command for export func NewCommand(logger log.Logger, streams cmd.IOStreams) *cobra.Command { cmd := &cobra.Command{ // TODO(bentheelder): more detailed usage Use: "export", Short: "Exports one of [kubeconfig, logs]", Long: "Exports one of [kubeconfig, logs]", RunE: func(cmd *cobra.Command, args []string) error { err := cmd.Help() if err != nil { return err } return errors.New("Subcommand is required") }, } // add subcommands cmd.AddCommand(logs.NewCommand(logger, streams)) cmd.AddCommand(kubeconfig.NewCommand(logger, streams)) return cmd } kind-0.27.0/pkg/cmd/kind/export/kubeconfig/000077500000000000000000000000001475376161000204455ustar00rootroot00000000000000kind-0.27.0/pkg/cmd/kind/export/kubeconfig/kubeconfig.go000066400000000000000000000042661475376161000231200ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package kubeconfig implements the `kubeconfig` command package kubeconfig import ( "github.com/spf13/cobra" "sigs.k8s.io/kind/pkg/cluster" "sigs.k8s.io/kind/pkg/cmd" "sigs.k8s.io/kind/pkg/log" "sigs.k8s.io/kind/pkg/internal/cli" "sigs.k8s.io/kind/pkg/internal/runtime" ) type flagpole struct { Name string Kubeconfig string Internal bool } // NewCommand returns a new cobra.Command for exporting the kubeconfig func NewCommand(logger log.Logger, streams cmd.IOStreams) *cobra.Command { flags := &flagpole{} cmd := &cobra.Command{ Args: cobra.NoArgs, Use: "kubeconfig", Short: "Exports cluster kubeconfig", Long: "Exports cluster kubeconfig", RunE: func(cmd *cobra.Command, args []string) error { cli.OverrideDefaultName(cmd.Flags()) return runE(logger, flags) }, } cmd.Flags().StringVarP( &flags.Name, "name", "n", cluster.DefaultName, "the cluster context name", ) cmd.Flags().StringVar( &flags.Kubeconfig, "kubeconfig", "", "sets kubeconfig path instead of $KUBECONFIG or $HOME/.kube/config", ) cmd.Flags().BoolVar( &flags.Internal, "internal", false, "use internal address instead of external", ) return cmd } func runE(logger log.Logger, flags *flagpole) error { provider := cluster.NewProvider( cluster.ProviderWithLogger(logger), runtime.GetDefault(logger), ) if err := provider.ExportKubeConfig(flags.Name, flags.Kubeconfig, flags.Internal); err != nil { return err } // TODO: get kind-name from a method? OTOH we probably want to keep this // naming scheme stable anyhow... logger.V(0).Infof(`Set kubectl context to "kind-%s"`, flags.Name) return nil } kind-0.27.0/pkg/cmd/kind/export/logs/000077500000000000000000000000001475376161000172755ustar00rootroot00000000000000kind-0.27.0/pkg/cmd/kind/export/logs/logs.go000066400000000000000000000047501475376161000205760ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package logs implements the `logs` command package logs import ( "fmt" "github.com/spf13/cobra" "sigs.k8s.io/kind/pkg/cluster" "sigs.k8s.io/kind/pkg/cmd" "sigs.k8s.io/kind/pkg/fs" "sigs.k8s.io/kind/pkg/log" "sigs.k8s.io/kind/pkg/internal/cli" "sigs.k8s.io/kind/pkg/internal/runtime" ) type flagpole struct { Name string } // NewCommand returns a new cobra.Command for getting the cluster logs func NewCommand(logger log.Logger, streams cmd.IOStreams) *cobra.Command { flags := &flagpole{} cmd := &cobra.Command{ Args: cobra.MaximumNArgs(1), // TODO(bentheelder): more detailed usage Use: "logs [output-dir]", Short: "Exports logs to a tempdir or [output-dir] if specified", Long: "Exports logs to a tempdir or [output-dir] if specified", RunE: func(cmd *cobra.Command, args []string) error { cli.OverrideDefaultName(cmd.Flags()) return runE(logger, streams, flags, args) }, } cmd.Flags().StringVarP( &flags.Name, "name", "n", cluster.DefaultName, "the cluster context name", ) return cmd } func runE(logger log.Logger, streams cmd.IOStreams, flags *flagpole, args []string) error { provider := cluster.NewProvider( cluster.ProviderWithLogger(logger), runtime.GetDefault(logger), ) // Check if the cluster has any running nodes nodes, err := provider.ListNodes(flags.Name) if err != nil { return err } if len(nodes) == 0 { return fmt.Errorf("unknown cluster %q", flags.Name) } // get the optional directory argument, or create a tempdir var dir string if len(args) == 0 { t, err := fs.TempDir("", "") if err != nil { return err } dir = t } else { dir = args[0] } // NOTE: the path is the output of this command to be captured by calling tools // whereas "Exporting logs..." is info / debug (stderr) logger.V(0).Infof("Exporting logs for cluster %q to:", flags.Name) fmt.Fprintln(streams.Out, dir) // collect the logs return provider.CollectLogs(flags.Name, dir) } kind-0.27.0/pkg/cmd/kind/get/000077500000000000000000000000001475376161000155675ustar00rootroot00000000000000kind-0.27.0/pkg/cmd/kind/get/clusters/000077500000000000000000000000001475376161000174335ustar00rootroot00000000000000kind-0.27.0/pkg/cmd/kind/get/clusters/clusters.go000066400000000000000000000032571475376161000216350ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package clusters implements the `clusters` command package clusters import ( "fmt" "github.com/spf13/cobra" "sigs.k8s.io/kind/pkg/cluster" "sigs.k8s.io/kind/pkg/cmd" "sigs.k8s.io/kind/pkg/log" "sigs.k8s.io/kind/pkg/internal/runtime" ) // NewCommand returns a new cobra.Command for getting the list of clusters func NewCommand(logger log.Logger, streams cmd.IOStreams) *cobra.Command { cmd := &cobra.Command{ Args: cobra.NoArgs, // TODO(bentheelder): more detailed usage Use: "clusters", Short: "Lists existing kind clusters by their name", Long: "Lists existing kind clusters by their name", RunE: func(cmd *cobra.Command, args []string) error { return runE(logger, streams) }, } return cmd } func runE(logger log.Logger, streams cmd.IOStreams) error { provider := cluster.NewProvider( cluster.ProviderWithLogger(logger), runtime.GetDefault(logger), ) clusters, err := provider.List() if err != nil { return err } if len(clusters) == 0 { logger.V(0).Info("No kind clusters found.") return nil } for _, cluster := range clusters { fmt.Fprintln(streams.Out, cluster) } return nil } kind-0.27.0/pkg/cmd/kind/get/get.go000066400000000000000000000030221475376161000166720ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package get implements the `get` command package get import ( "errors" "github.com/spf13/cobra" "sigs.k8s.io/kind/pkg/cmd" "sigs.k8s.io/kind/pkg/cmd/kind/get/clusters" "sigs.k8s.io/kind/pkg/cmd/kind/get/kubeconfig" "sigs.k8s.io/kind/pkg/cmd/kind/get/nodes" "sigs.k8s.io/kind/pkg/log" ) // NewCommand returns a new cobra.Command for get func NewCommand(logger log.Logger, streams cmd.IOStreams) *cobra.Command { cmd := &cobra.Command{ // TODO(bentheelder): more detailed usage Use: "get", Short: "Gets one of [clusters, nodes, kubeconfig]", Long: "Gets one of [clusters, nodes, kubeconfig]", RunE: func(cmd *cobra.Command, args []string) error { err := cmd.Help() if err != nil { return err } return errors.New("Subcommand is required") }, } // add subcommands cmd.AddCommand(clusters.NewCommand(logger, streams)) cmd.AddCommand(nodes.NewCommand(logger, streams)) cmd.AddCommand(kubeconfig.NewCommand(logger, streams)) return cmd } kind-0.27.0/pkg/cmd/kind/get/kubeconfig/000077500000000000000000000000001475376161000177035ustar00rootroot00000000000000kind-0.27.0/pkg/cmd/kind/get/kubeconfig/kubeconfig.go000066400000000000000000000036231475376161000223520ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package kubeconfig implements the `kubeconfig` command package kubeconfig import ( "fmt" "github.com/spf13/cobra" "sigs.k8s.io/kind/pkg/cluster" "sigs.k8s.io/kind/pkg/cmd" "sigs.k8s.io/kind/pkg/log" "sigs.k8s.io/kind/pkg/internal/cli" "sigs.k8s.io/kind/pkg/internal/runtime" ) type flagpole struct { Name string Internal bool } // NewCommand returns a new cobra.Command for getting the kubeconfig func NewCommand(logger log.Logger, streams cmd.IOStreams) *cobra.Command { flags := &flagpole{} cmd := &cobra.Command{ Args: cobra.NoArgs, Use: "kubeconfig", Short: "Prints cluster kubeconfig", Long: "Prints cluster kubeconfig", RunE: func(cmd *cobra.Command, args []string) error { cli.OverrideDefaultName(cmd.Flags()) return runE(logger, streams, flags) }, } cmd.Flags().StringVarP( &flags.Name, "name", "n", cluster.DefaultName, "the cluster context name", ) cmd.Flags().BoolVar( &flags.Internal, "internal", false, "use internal address instead of external", ) return cmd } func runE(logger log.Logger, streams cmd.IOStreams, flags *flagpole) error { provider := cluster.NewProvider( cluster.ProviderWithLogger(logger), runtime.GetDefault(logger), ) cfg, err := provider.KubeConfig(flags.Name, flags.Internal) if err != nil { return err } fmt.Fprintln(streams.Out, cfg) return nil } kind-0.27.0/pkg/cmd/kind/get/nodes/000077500000000000000000000000001475376161000166775ustar00rootroot00000000000000kind-0.27.0/pkg/cmd/kind/get/nodes/nodes.go000066400000000000000000000052471475376161000203460ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package nodes implements the `nodes` command package nodes import ( "fmt" "github.com/spf13/cobra" "sigs.k8s.io/kind/pkg/cluster" "sigs.k8s.io/kind/pkg/cluster/nodes" "sigs.k8s.io/kind/pkg/cmd" "sigs.k8s.io/kind/pkg/log" "sigs.k8s.io/kind/pkg/internal/cli" "sigs.k8s.io/kind/pkg/internal/runtime" ) type flagpole struct { Name string AllClusters bool } // NewCommand returns a new cobra.Command for getting the list of nodes for a given cluster func NewCommand(logger log.Logger, streams cmd.IOStreams) *cobra.Command { flags := &flagpole{} cmd := &cobra.Command{ Args: cobra.NoArgs, Use: "nodes", Short: "Lists existing kind nodes by their name", Long: "Lists existing kind nodes by their name", RunE: func(cmd *cobra.Command, args []string) error { cli.OverrideDefaultName(cmd.Flags()) return runE(logger, streams, flags) }, } cmd.Flags().StringVarP( &flags.Name, "name", "n", cluster.DefaultName, "the cluster context name", ) cmd.Flags().BoolVarP( &flags.AllClusters, "all-clusters", "A", false, "If present, list all the available nodes across all cluster contexts. Current context is ignored even if specified with --name.", ) return cmd } func runE(logger log.Logger, streams cmd.IOStreams, flags *flagpole) error { // List nodes by cluster context name provider := cluster.NewProvider( cluster.ProviderWithLogger(logger), runtime.GetDefault(logger), ) var nodes []nodes.Node var err error if flags.AllClusters { clusters, err := provider.List() if err != nil { return err } for _, clusterName := range clusters { clusterNodes, err := provider.ListNodes(clusterName) if err != nil { return err } nodes = append(nodes, clusterNodes...) } if len(nodes) == 0 { logger.V(0).Infof("No kind nodes for any cluster.") return nil } } else { nodes, err = provider.ListNodes(flags.Name) if err != nil { return err } if len(nodes) == 0 { logger.V(0).Infof("No kind nodes found for cluster %q.", flags.Name) return nil } } for _, node := range nodes { fmt.Fprintln(streams.Out, node.String()) } return nil } kind-0.27.0/pkg/cmd/kind/load/000077500000000000000000000000001475376161000157275ustar00rootroot00000000000000kind-0.27.0/pkg/cmd/kind/load/docker-image/000077500000000000000000000000001475376161000202565ustar00rootroot00000000000000kind-0.27.0/pkg/cmd/kind/load/docker-image/docker-image.go000066400000000000000000000200571475376161000231400ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package load implements the `load` command package load import ( "fmt" "os" "path/filepath" "strings" "github.com/spf13/cobra" "sigs.k8s.io/kind/pkg/cluster" "sigs.k8s.io/kind/pkg/cluster/nodes" "sigs.k8s.io/kind/pkg/cluster/nodeutils" "sigs.k8s.io/kind/pkg/cmd" "sigs.k8s.io/kind/pkg/errors" "sigs.k8s.io/kind/pkg/exec" "sigs.k8s.io/kind/pkg/fs" "sigs.k8s.io/kind/pkg/log" "sigs.k8s.io/kind/pkg/internal/cli" "sigs.k8s.io/kind/pkg/internal/runtime" ) type ( imageTagFetcher func(nodes.Node, string) (map[string]bool, error) ) type flagpole struct { Name string Nodes []string } // NewCommand returns a new cobra.Command for loading an image into a cluster func NewCommand(logger log.Logger, streams cmd.IOStreams) *cobra.Command { flags := &flagpole{} cmd := &cobra.Command{ Args: func(cmd *cobra.Command, args []string) error { if len(args) < 1 { return errors.New("a list of image names is required") } return nil }, Use: "docker-image [IMAGE...]", Short: "Loads docker images from host into nodes", Long: "Loads docker images from host into all or specified nodes by name", RunE: func(cmd *cobra.Command, args []string) error { cli.OverrideDefaultName(cmd.Flags()) return runE(logger, flags, args) }, } cmd.Flags().StringVarP( &flags.Name, "name", "n", cluster.DefaultName, "the cluster context name", ) cmd.Flags().StringSliceVar( &flags.Nodes, "nodes", nil, "comma separated list of nodes to load images into", ) return cmd } func runE(logger log.Logger, flags *flagpole, args []string) error { provider := cluster.NewProvider( cluster.ProviderWithLogger(logger), runtime.GetDefault(logger), ) // Check that the image exists locally and gets its ID, if not return error imageNames := removeDuplicates(args) var imageIDs []string for _, imageName := range imageNames { imageID, err := imageID(imageName) if err != nil { return fmt.Errorf("image: %q not present locally", imageName) } imageIDs = append(imageIDs, imageID) } // Check if the cluster nodes exist nodeList, err := provider.ListInternalNodes(flags.Name) if err != nil { return err } if len(nodeList) == 0 { return fmt.Errorf("no nodes found for cluster %q", flags.Name) } // map cluster nodes by their name nodesByName := map[string]nodes.Node{} for _, node := range nodeList { // TODO(bentheelder): this depends on the fact that ListByCluster() // will have name for nameOrId. nodesByName[node.String()] = node } // pick only the user selected nodes and ensure they exist // the default is all nodes unless flags.Nodes is set candidateNodes := nodeList if len(flags.Nodes) > 0 { candidateNodes = []nodes.Node{} for _, name := range flags.Nodes { node, ok := nodesByName[name] if !ok { return fmt.Errorf("unknown node: %q", name) } candidateNodes = append(candidateNodes, node) } } // pick only the nodes that don't have the image selectedNodes := map[string]nodes.Node{} fns := []func() error{} for i, imageName := range imageNames { imageID := imageIDs[i] processed := false for _, node := range candidateNodes { exists, reTagRequired, sanitizedImageName := checkIfImageReTagRequired(node, imageID, imageName, nodeutils.ImageTags) if exists && !reTagRequired { continue } if reTagRequired { // We will try to re-tag the image. If the re-tag fails, we will fall back to the default behavior of loading // the images into the nodes again logger.V(0).Infof("Image with ID: %s already present on the node %s but is missing the tag %s. re-tagging...", imageID, node.String(), sanitizedImageName) if err := nodeutils.ReTagImage(node, imageID, sanitizedImageName); err != nil { logger.Errorf("failed to re-tag image on the node %s due to an error %s. Will load it instead...", node.String(), err) selectedNodes[node.String()] = node } else { processed = true } continue } id, err := nodeutils.ImageID(node, imageName) if err != nil || id != imageID { selectedNodes[node.String()] = node logger.V(0).Infof("Image: %q with ID %q not yet present on node %q, loading...", imageName, imageID, node.String()) } continue } if len(selectedNodes) == 0 && !processed { logger.V(0).Infof("Image: %q with ID %q found to be already present on all nodes.", imageName, imageID) } } // return early if no node needs the image if len(selectedNodes) == 0 { return nil } // Setup the tar path where the images will be saved dir, err := fs.TempDir("", "images-tar") if err != nil { return errors.Wrap(err, "failed to create tempdir") } defer os.RemoveAll(dir) imagesTarPath := filepath.Join(dir, "images.tar") // Save the images into a tar err = save(imageNames, imagesTarPath) if err != nil { return err } // Load the images on the selected nodes for _, selectedNode := range selectedNodes { selectedNode := selectedNode // capture loop variable fns = append(fns, func() error { return loadImage(imagesTarPath, selectedNode) }) } return errors.UntilErrorConcurrent(fns) } // TODO: we should consider having a cluster method to load images // loads an image tarball onto a node func loadImage(imageTarName string, node nodes.Node) error { f, err := os.Open(imageTarName) if err != nil { return errors.Wrap(err, "failed to open image") } defer f.Close() return nodeutils.LoadImageArchive(node, f) } // save saves images to dest, as in `docker save` func save(images []string, dest string) error { commandArgs := append([]string{"save", "-o", dest}, images...) return exec.Command("docker", commandArgs...).Run() } // imageID return the Id of the container image func imageID(containerNameOrID string) (string, error) { cmd := exec.Command("docker", "image", "inspect", "-f", "{{ .Id }}", containerNameOrID, // ... against the container ) lines, err := exec.OutputLines(cmd) if err != nil { return "", err } if len(lines) != 1 { return "", errors.Errorf("Docker image ID should only be one line, got %d lines", len(lines)) } return lines[0], nil } // removeDuplicates removes duplicates from a string slice func removeDuplicates(slice []string) []string { result := []string{} seenKeys := make(map[string]struct{}) for _, k := range slice { if _, seen := seenKeys[k]; !seen { result = append(result, k) seenKeys[k] = struct{}{} } } return result } // checkIfImageExists makes sure we only perform the reverse lookup of the ImageID to tag map func checkIfImageReTagRequired(node nodes.Node, imageID, imageName string, tagFetcher imageTagFetcher) (exists, reTagRequired bool, sanitizedImage string) { tags, err := tagFetcher(node, imageID) if len(tags) == 0 || err != nil { exists = false return } exists = true sanitizedImage = sanitizeImage(imageName) if ok := tags[sanitizedImage]; ok { reTagRequired = false return } reTagRequired = true return } // sanitizeImage is a helper to return human readable image name // This is a modified version of the same function found under providers/podman/images.go func sanitizeImage(image string) (sanitizedName string) { const ( defaultDomain = "docker.io/" officialRepoName = "library" ) sanitizedName = image if !strings.ContainsRune(image, '/') { sanitizedName = officialRepoName + "/" + image } i := strings.IndexRune(sanitizedName, '/') if i == -1 || (!strings.ContainsAny(sanitizedName[:i], ".:") && sanitizedName[:i] != "localhost") { sanitizedName = defaultDomain + sanitizedName } i = strings.IndexRune(sanitizedName, ':') if i == -1 { sanitizedName += ":latest" } return } kind-0.27.0/pkg/cmd/kind/load/docker-image/docker-image_test.go000066400000000000000000000122151475376161000241740ustar00rootroot00000000000000/* Copyright 2021 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package load import ( "errors" "reflect" "sort" "testing" "sigs.k8s.io/kind/pkg/cluster/nodes" ) func Test_removeDuplicates(t *testing.T) { tests := []struct { name string slice []string want []string }{ { name: "empty", slice: []string{}, want: []string{}, }, { name: "all different", slice: []string{"one", "two"}, want: []string{"one", "two"}, }, { name: "one dup", slice: []string{"one", "two", "two"}, want: []string{"one", "two"}, }, { name: "two dup", slice: []string{"one", "two", "two", "one"}, want: []string{"one", "two"}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := removeDuplicates(tt.slice) sort.Strings(got) sort.Strings(tt.want) if !reflect.DeepEqual(got, tt.want) { t.Errorf("removeDuplicates() = %v, want %v", got, tt.want) } }) } } func Test_sanitizeImage(t *testing.T) { tests := []struct { name string image string sanitizedImage string }{ { image: "ubuntu:18.04", sanitizedImage: "docker.io/library/ubuntu:18.04", }, { image: "custom/ubuntu:18.04", sanitizedImage: "docker.io/custom/ubuntu:18.04", }, { image: "registry.k8s.io/kindest/node:latest", sanitizedImage: "registry.k8s.io/kindest/node:latest", }, { image: "registry.k8s.io/pause:3.6", sanitizedImage: "registry.k8s.io/pause:3.6", }, { image: "baz", sanitizedImage: "docker.io/library/baz:latest", }, { image: "other-registry/baz", sanitizedImage: "docker.io/other-registry/baz:latest", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := sanitizeImage(tt.image) if got != tt.sanitizedImage { t.Errorf("sanitizeImage(%s) = %s, want %s", tt.image, got, tt.sanitizedImage) } }) } } func Test_checkIfImageReTagRequired(t *testing.T) { tests := []struct { name string imageTags struct { tags map[string]bool err error } imageID string imageName string returnValues []bool sanitizedImage string }{ { name: "image is already present", imageTags: struct { tags map[string]bool err error }{ map[string]bool{ "docker.io/library/image1:tag1": true, "k8s.io/image1:tag1": true, }, nil, }, imageID: "sha256:fd3fd9ab134a864eeb7b2c073c0d90192546f597c60416b81fc4166cca47f29a", imageName: "k8s.io/image1:tag1", returnValues: []bool{true, false}, sanitizedImage: "k8s.io/image1:tag1", }, { name: "re-tag is required", imageTags: struct { tags map[string]bool err error }{ map[string]bool{ "docker.io/library/image1:tag1": true, "k8s.io/image1:tag1": true, }, nil, }, imageID: "sha256:fd3fd9ab134a864eeb7b2c073c0d90192546f597c60416b81fc4166cca47f29a", imageName: "k8s.io/image1:tag2", returnValues: []bool{true, true}, sanitizedImage: "k8s.io/image1:tag2", }, { name: "re-tag is required with docker.io prefix", imageTags: struct { tags map[string]bool err error }{ map[string]bool{ "docker.io/foo/image1:tag1": true, }, nil, }, imageID: "sha256:fd3fd9ab134a864eeb7b2c073c0d90192546f597c60416b81fc4166cca47f29a", imageName: "foo/image1:tag2", returnValues: []bool{true, true}, sanitizedImage: "docker.io/foo/image1:tag2", }, { name: "image tag fetch failed", imageTags: struct { tags map[string]bool err error }{ map[string]bool{}, errors.New("some runtime error"), }, imageID: "sha256:fd3fd9ab134a864eeb7b2c073c0d90192546f597c60416b81fc4166cca47f29a", imageName: "k8s.io/image1:tag2", returnValues: []bool{false, false}, sanitizedImage: "", }, } for _, tc := range tests { tc := tc t.Run(tc.name, func(t *testing.T) { // checkIfImageReTagRequired doesn't use the `nodes.Node` type for anything. So // passing a nil value here should be fine as the other two functions that use the // nodes.Node has been stubbed out already exists, reTagRequired, sanitizedImage := checkIfImageReTagRequired(nil, tc.imageID, tc.imageName, func(n nodes.Node, s string) (map[string]bool, error) { return tc.imageTags.tags, tc.imageTags.err }) if exists != tc.returnValues[0] || reTagRequired != tc.returnValues[1] || sanitizedImage != tc.sanitizedImage { t.Errorf("checkIfImageReTagRequired failed. Expected: [%v,%v,%v], got: [%v, %v, %v]", tc.returnValues[0], tc.returnValues[1], tc.sanitizedImage, exists, reTagRequired, sanitizedImage) } }) } } kind-0.27.0/pkg/cmd/kind/load/image-archive/000077500000000000000000000000001475376161000204305ustar00rootroot00000000000000kind-0.27.0/pkg/cmd/kind/load/image-archive/image-archive.go000066400000000000000000000103771475376161000234700ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package load implements the `load` command package load import ( "fmt" "os" "github.com/spf13/cobra" "sigs.k8s.io/kind/pkg/errors" "sigs.k8s.io/kind/pkg/cluster" "sigs.k8s.io/kind/pkg/cluster/nodes" "sigs.k8s.io/kind/pkg/cluster/nodeutils" "sigs.k8s.io/kind/pkg/cmd" "sigs.k8s.io/kind/pkg/log" "sigs.k8s.io/kind/pkg/internal/cli" "sigs.k8s.io/kind/pkg/internal/runtime" ) type flagpole struct { Name string Nodes []string } // NewCommand returns a new cobra.Command for loading an image into a cluster func NewCommand(logger log.Logger, streams cmd.IOStreams) *cobra.Command { flags := &flagpole{} cmd := &cobra.Command{ Args: func(cmd *cobra.Command, args []string) error { if len(args) < 1 { return fmt.Errorf("name of image archive is required") } return nil }, PreRun: func(cmd *cobra.Command, args []string) { if len(args) > 1 { logger.Warn("It is suggested that you save multiple images into a common archive and load that instead of loading multiple archives for better performance") } }, Use: "image-archive ", Short: "Loads docker image from archive into nodes", Long: "Loads docker image from archive into all or specified nodes by name", RunE: func(cmd *cobra.Command, args []string) error { cli.OverrideDefaultName(cmd.Flags()) return runE(logger, flags, args) }, } cmd.Flags().StringVarP( &flags.Name, "name", "n", cluster.DefaultName, "the cluster context name", ) cmd.Flags().StringSliceVar( &flags.Nodes, "nodes", nil, "comma separated list of nodes to load images into", ) return cmd } func runE(logger log.Logger, flags *flagpole, args []string) error { provider := cluster.NewProvider( cluster.ProviderWithLogger(logger), runtime.GetDefault(logger), ) for _, imageTarPath := range args { if _, err := os.Stat(imageTarPath); err != nil { return err } } for _, imageTarPath := range args { if err := loadArchiveToNodes(logger, provider, flags.Name, flags.Nodes, imageTarPath); err != nil { return err } } return nil } func loadArchiveToNodes(logger log.Logger, provider *cluster.Provider, clusterName string, nodeNames []string, imageArchivePath string) error { // Check if the cluster nodes exist nodeList, err := provider.ListInternalNodes(clusterName) if err != nil { return err } if len(nodeList) == 0 { return fmt.Errorf("no nodes found for cluster %q", clusterName) } // map cluster nodes by their name nodesByName := map[string]nodes.Node{} for _, node := range nodeList { // TODO(bentheelder): this depends on the fact that ListByCluster() // will have name for nameOrId. nodesByName[node.String()] = node } // pick only the user selected nodes and ensure they exist // the default is all nodes unless flags.Nodes is set selectedNodes := nodeList if len(nodeNames) > 0 { selectedNodes = []nodes.Node{} for _, name := range nodeNames { node, ok := nodesByName[name] if !ok { return fmt.Errorf("unknown node: %s", name) } selectedNodes = append(selectedNodes, node) } } // Load the image on the selected nodes fns := []func() error{} for _, selectedNode := range selectedNodes { selectedNode := selectedNode // capture loop variable fns = append(fns, func() error { return loadImage(logger, imageArchivePath, selectedNode) }) } return errors.UntilErrorConcurrent(fns) } // loads an image tarball onto a node func loadImage(logger log.Logger, imageTarName string, node nodes.Node) error { f, err := os.Open(imageTarName) if err != nil { return errors.Wrap(err, "failed to open image") } defer f.Close() logger.V(2).Infof("Loading Docker Image from archive %s to node %s", imageTarName, node.String()) return nodeutils.LoadImageArchive(node, f) } kind-0.27.0/pkg/cmd/kind/load/load.go000066400000000000000000000027061475376161000172020ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package load implements the `load` command package load import ( "errors" "github.com/spf13/cobra" "sigs.k8s.io/kind/pkg/cmd" dockerimage "sigs.k8s.io/kind/pkg/cmd/kind/load/docker-image" imagearchive "sigs.k8s.io/kind/pkg/cmd/kind/load/image-archive" "sigs.k8s.io/kind/pkg/log" ) // NewCommand returns a new cobra.Command for get func NewCommand(logger log.Logger, streams cmd.IOStreams) *cobra.Command { cmd := &cobra.Command{ Args: cobra.NoArgs, Use: "load", Short: "Loads images into nodes", Long: "Loads images into node from an archive or image on host", RunE: func(cmd *cobra.Command, args []string) error { err := cmd.Help() if err != nil { return err } return errors.New("Subcommand is required") }, } // add subcommands cmd.AddCommand(dockerimage.NewCommand(logger, streams)) cmd.AddCommand(imagearchive.NewCommand(logger, streams)) return cmd } kind-0.27.0/pkg/cmd/kind/root.go000066400000000000000000000064511475376161000163300ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package kind implements the root kind cobra command, and the cli Main() package kind import ( "io" "github.com/spf13/cobra" "sigs.k8s.io/kind/pkg/cmd" "sigs.k8s.io/kind/pkg/cmd/kind/build" "sigs.k8s.io/kind/pkg/cmd/kind/completion" "sigs.k8s.io/kind/pkg/cmd/kind/create" "sigs.k8s.io/kind/pkg/cmd/kind/delete" "sigs.k8s.io/kind/pkg/cmd/kind/export" "sigs.k8s.io/kind/pkg/cmd/kind/get" "sigs.k8s.io/kind/pkg/cmd/kind/load" "sigs.k8s.io/kind/pkg/cmd/kind/version" "sigs.k8s.io/kind/pkg/log" ) type flagpole struct { Verbosity int32 Quiet bool } // NewCommand returns a new cobra.Command implementing the root command for kind func NewCommand(logger log.Logger, streams cmd.IOStreams) *cobra.Command { flags := &flagpole{} cmd := &cobra.Command{ Use: "kind", Short: "kind is a tool for managing local Kubernetes clusters", Long: "kind creates and manages local Kubernetes clusters using Docker container 'nodes'", PersistentPreRunE: func(cmd *cobra.Command, args []string) error { return runE(logger, flags) }, SilenceUsage: true, SilenceErrors: true, Version: version.Version(), } cmd.SetOut(streams.Out) cmd.SetErr(streams.ErrOut) cmd.PersistentFlags().Int32VarP( &flags.Verbosity, "verbosity", "v", 0, "info log verbosity, higher value produces more output", ) cmd.PersistentFlags().BoolVarP( &flags.Quiet, "quiet", "q", false, "silence all stderr output", ) // add all top level subcommands cmd.AddCommand(build.NewCommand(logger, streams)) cmd.AddCommand(completion.NewCommand(logger, streams)) cmd.AddCommand(create.NewCommand(logger, streams)) cmd.AddCommand(delete.NewCommand(logger, streams)) cmd.AddCommand(export.NewCommand(logger, streams)) cmd.AddCommand(get.NewCommand(logger, streams)) cmd.AddCommand(version.NewCommand(logger, streams)) cmd.AddCommand(load.NewCommand(logger, streams)) return cmd } func runE(logger log.Logger, flags *flagpole) error { // normal logger setup if flags.Quiet { // NOTE: if we are coming from app.Run handling this flag is // redundant, however it doesn't hurt, and this may be called directly. maybeSetWriter(logger, io.Discard) } maybeSetVerbosity(logger, log.Level(flags.Verbosity)) return nil } // maybeSetWriter will call logger.SetWriter(w) if logger has a SetWriter method func maybeSetWriter(logger log.Logger, w io.Writer) { type writerSetter interface { SetWriter(io.Writer) } v, ok := logger.(writerSetter) if ok { v.SetWriter(w) } } // maybeSetVerbosity will call logger.SetVerbosity(verbosity) if logger // has a SetVerbosity method func maybeSetVerbosity(logger log.Logger, verbosity log.Level) { type verboser interface { SetVerbosity(log.Level) } v, ok := logger.(verboser) if ok { v.SetVerbosity(verbosity) } } kind-0.27.0/pkg/cmd/kind/version/000077500000000000000000000000001475376161000164755ustar00rootroot00000000000000kind-0.27.0/pkg/cmd/kind/version/version.go000066400000000000000000000053661475376161000205230ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package version implements the `version` command package version import ( "fmt" "runtime" "github.com/spf13/cobra" "sigs.k8s.io/kind/pkg/cmd" "sigs.k8s.io/kind/pkg/log" ) // Version returns the kind CLI Semantic Version func Version() string { return version(versionCore, versionPreRelease, gitCommit, gitCommitCount) } func version(core, preRelease, commit, commitCount string) string { v := core // add pre-release version info if we have it if preRelease != "" { v += "-" + preRelease // If commitCount was set, add to the pre-release version if commitCount != "" { v += "." + commitCount } // if commit was set, add the + // we only do this for pre-release versions if commit != "" { // NOTE: use 14 character short hash, like Kubernetes v += "+" + truncate(commit, 14) } } return v } // DisplayVersion is Version() display formatted, this is what the version // subcommand prints func DisplayVersion() string { return "kind v" + Version() + " " + runtime.Version() + " " + runtime.GOOS + "/" + runtime.GOARCH } // versionCore is the core portion of the kind CLI version per Semantic Versioning 2.0.0 const versionCore = "0.27.0" // versionPreRelease is the base pre-release portion of the kind CLI version per // Semantic Versioning 2.0.0 var versionPreRelease = "" // gitCommitCount count the commits since the last release. // It is injected at build time. var gitCommitCount = "" // gitCommit is the commit used to build the kind binary, if available. // It is injected at build time. var gitCommit = "" // NewCommand returns a new cobra.Command for version func NewCommand(logger log.Logger, streams cmd.IOStreams) *cobra.Command { cmd := &cobra.Command{ Use: "version", Short: "Prints the kind CLI version", Long: "Prints the kind CLI version", RunE: func(cmd *cobra.Command, args []string) error { if logger.V(0).Enabled() { // if not -q / --quiet, show lots of info fmt.Fprintln(streams.Out, DisplayVersion()) } else { // otherwise only show semver fmt.Fprintln(streams.Out, Version()) } return nil }, } return cmd } func truncate(s string, maxLen int) string { if len(s) < maxLen { return s } return s[:maxLen] } kind-0.27.0/pkg/cmd/kind/version/version_test.go000066400000000000000000000067321475376161000215600ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package version import ( "testing" ) func TestTruncate(t *testing.T) { t.Parallel() cases := []struct { Value string MaxLength int Expected string }{ { Value: "A Really Long String", MaxLength: 1, Expected: "A", }, { Value: "A Short String", MaxLength: 10, Expected: "A Short St", }, { Value: "Under Max Length String", MaxLength: 1000, Expected: "Under Max Length String", }, } for _, tc := range cases { tc := tc // capture range variable t.Run(tc.Value, func(t *testing.T) { t.Parallel() result := truncate(tc.Value, tc.MaxLength) // sanity check length if len(result) > tc.MaxLength { t.Errorf("Result %q longer than Max Length %d!", result, tc.MaxLength) } if tc.Expected != result { t.Errorf("Strings did not match!") t.Errorf("Expected: %q", tc.Expected) t.Errorf("But got: %q", result) } }) } } func TestVersion(t *testing.T) { tests := []struct { name string version string versionPreRelease string gitCommit string gitCommitCount string want string }{ { name: "With git commit count and with commit hash", version: "v0.27.0", versionPreRelease: "alpha", gitCommit: "mocked-hash", gitCommitCount: "mocked-count", want: "v0.27.0-alpha.mocked-count+mocked-hash", }, { name: "Without git commit count and and with hash", version: "v0.27.0", versionPreRelease: "beta", gitCommit: "mocked-hash", gitCommitCount: "", want: "v0.27.0-beta+mocked-hash", }, { name: "Without git commit hash and with commit count", version: "v0.30.0", versionPreRelease: "alpha", gitCommit: "", gitCommitCount: "mocked-count", want: "v0.30.0-alpha.mocked-count", }, { name: "Without git commit hash and without commit count", version: "v0.27.0", versionPreRelease: "alpha", gitCommit: "", gitCommitCount: "", want: "v0.27.0-alpha", }, { name: "Without pre release version", version: "v0.27.0", versionPreRelease: "", gitCommit: "", gitCommitCount: "", want: "v0.27.0", }, { name: "Without pre release version and with git commit hash and count", version: "v0.27.0", versionPreRelease: "", gitCommit: "mocked-commit", gitCommitCount: "mocked-count", want: "v0.27.0", }, } for _, tt := range tests { // TODO: this won't be necessary when we require go 1.22+ tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() if got := version(tt.version, tt.versionPreRelease, tt.gitCommit, tt.gitCommitCount); got != tt.want { t.Errorf("Version() = %v, want %v", got, tt.want) } }) } } kind-0.27.0/pkg/cmd/logger.go000066400000000000000000000023421475376161000156720ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package cmd import ( "io" "os" "sigs.k8s.io/kind/pkg/log" "sigs.k8s.io/kind/pkg/internal/cli" "sigs.k8s.io/kind/pkg/internal/env" ) // NewLogger returns the standard logger used by the kind CLI // This logger writes to os.Stderr func NewLogger() log.Logger { var writer io.Writer = os.Stderr if env.IsSmartTerminal(writer) { writer = cli.NewSpinner(writer) } return cli.NewLogger(writer, 0) } // ColorEnabled returns true if color is enabled for the logger // this should be used to control output func ColorEnabled(logger log.Logger) bool { type maybeColorer interface { ColorEnabled() bool } v, ok := logger.(maybeColorer) return ok && v.ColorEnabled() } kind-0.27.0/pkg/errors/000077500000000000000000000000001475376161000146345ustar00rootroot00000000000000kind-0.27.0/pkg/errors/aggregate.go000066400000000000000000000023221475376161000171100ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package errors // NewAggregate is a k8s.io/apimachinery/pkg/util/errors.NewAggregate compatible wrapper // note that while it returns a StackTrace wrapped Aggregate // That has been Flattened and Reduced func NewAggregate(errlist []error) error { return WithStack( reduce( flatten( newAggregate(errlist), ), ), ) } // Errors returns the deepest Aggregate in a Cause chain func Errors(err error) []error { var errors Aggregate for { if v, ok := err.(Aggregate); ok { errors = v } if causerErr, ok := err.(Causer); ok { err = causerErr.Cause() } else { break } } if errors != nil { return errors.Errors() } return nil } kind-0.27.0/pkg/errors/aggregate_forked.go000066400000000000000000000076501475376161000204530ustar00rootroot00000000000000/* Copyright 2021 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package errors import ( "errors" "sigs.k8s.io/kind/pkg/internal/sets" ) /* The contents of this file are lightly forked from k8s.io/apimachinery/pkg/util/errors Forking makes kind easier to import, and this code is stable. Currently the only source changes are renaming some methods so as to not export them. */ // Aggregate represents an object that contains multiple errors, but does not // necessarily have singular semantic meaning. // The aggregate can be used with `errors.Is()` to check for the occurrence of // a specific error type. // Errors.As() is not supported, because the caller presumably cares about a // specific error of potentially multiple that match the given type. // // NOTE: this type is originally from k8s.io/apimachinery/pkg/util/errors.Aggregate // Since it is an interface, you can use the implementing types interchangeably type Aggregate interface { error Errors() []error Is(error) bool } func newAggregate(errlist []error) Aggregate { if len(errlist) == 0 { return nil } // In case of input error list contains nil var errs []error for _, e := range errlist { if e != nil { errs = append(errs, e) } } if len(errs) == 0 { return nil } return aggregate(errs) } // flatten takes an Aggregate, which may hold other Aggregates in arbitrary // nesting, and flattens them all into a single Aggregate, recursively. func flatten(agg Aggregate) Aggregate { result := []error{} if agg == nil { return nil } for _, err := range agg.Errors() { if a, ok := err.(Aggregate); ok { r := flatten(a) if r != nil { result = append(result, r.Errors()...) } } else { if err != nil { result = append(result, err) } } } return newAggregate(result) } // reduce will return err or, if err is an Aggregate and only has one item, // the first item in the aggregate. func reduce(err error) error { if agg, ok := err.(Aggregate); ok && err != nil { switch len(agg.Errors()) { case 1: return agg.Errors()[0] case 0: return nil } } return err } // This helper implements the error and Errors interfaces. Keeping it private // prevents people from making an aggregate of 0 errors, which is not // an error, but does satisfy the error interface. type aggregate []error // Error is part of the error interface. func (agg aggregate) Error() string { if len(agg) == 0 { // This should never happen, really. return "" } if len(agg) == 1 { return agg[0].Error() } seenerrs := sets.NewString() result := "" agg.visit(func(err error) bool { msg := err.Error() if seenerrs.Has(msg) { return false } seenerrs.Insert(msg) if len(seenerrs) > 1 { result += ", " } result += msg return false }) if len(seenerrs) == 1 { return result } return "[" + result + "]" } func (agg aggregate) Is(target error) bool { return agg.visit(func(err error) bool { return errors.Is(err, target) }) } func (agg aggregate) visit(f func(err error) bool) bool { for _, err := range agg { switch err := err.(type) { case aggregate: if match := err.visit(f); match { return match } case Aggregate: for _, nestedErr := range err.Errors() { if match := f(nestedErr); match { return match } } default: if match := f(err); match { return match } } } return false } // Errors is part of the Aggregate interface. func (agg aggregate) Errors() []error { return []error(agg) } kind-0.27.0/pkg/errors/aggregate_test.go000066400000000000000000000020531475376161000201500ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package errors import ( "testing" "sigs.k8s.io/kind/pkg/internal/assert" ) func TestErrors(t *testing.T) { t.Parallel() t.Run("wrapped aggregate", func(t *testing.T) { t.Parallel() errs := []error{New("foo"), Errorf("bar")} err := Wrapf(NewAggregate(errs), "baz: %s", "quux") result := Errors(err) assert.DeepEqual(t, errs, result) }) t.Run("nil", func(t *testing.T) { t.Parallel() result := Errors(nil) var expected []error assert.DeepEqual(t, expected, result) }) } kind-0.27.0/pkg/errors/concurrent.go000066400000000000000000000031751475376161000173530ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package errors import ( "sync" ) // UntilErrorConcurrent runs all funcs in separate goroutines, returning the // first non-nil error returned from funcs, or nil if all funcs return nil func UntilErrorConcurrent(funcs []func() error) error { errCh := make(chan error, len(funcs)) for _, f := range funcs { f := f // capture f go func() { errCh <- f() }() } for i := 0; i < len(funcs); i++ { if err := <-errCh; err != nil { return err } } return nil } // AggregateConcurrent runs fns concurrently, returning a NewAggregate if there are > 1 errors func AggregateConcurrent(funcs []func() error) error { // run all fns concurrently ch := make(chan error, len(funcs)) var wg sync.WaitGroup for _, f := range funcs { f := f // capture f wg.Add(1) go func() { defer wg.Done() ch <- f() }() } wg.Wait() close(ch) // collect up and return errors errs := []error{} for err := range ch { if err != nil { errs = append(errs, err) } } if len(errs) > 1 { return NewAggregate(errs) } else if len(errs) == 1 { return errs[0] } return nil } kind-0.27.0/pkg/errors/concurrent_test.go000066400000000000000000000046371475376161000204160ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package errors import ( "sort" "testing" "sigs.k8s.io/kind/pkg/internal/assert" ) func TestUntilErrorConcurrent(t *testing.T) { t.Parallel() t.Run("first to return error", func(t *testing.T) { t.Parallel() // test that the first function to return an error is returned expected := New("first") wait := make(chan bool) result := UntilErrorConcurrent([]func() error{ func() error { <-wait return New("second") }, func() error { return expected }, }) wait <- true assert.DeepEqual(t, expected, result) }) t.Run("nil", func(t *testing.T) { t.Parallel() result := UntilErrorConcurrent([]func() error{ func() error { return nil }, }) var expected error assert.DeepEqual(t, expected, result) }) } func TestAggregateConcurrent(t *testing.T) { t.Parallel() t.Run("all errors returned", func(t *testing.T) { t.Parallel() // test that the first function to return an error is returned first := New("first") second := New("second") expected := []error{first, second} result := AggregateConcurrent([]func() error{ func() error { return second }, func() error { return first }, }) resultErrors := Errors(result) // We just want to check if we aggregate all the errors independent of the order sort.SliceStable(resultErrors, func(i, j int) bool { return resultErrors[i].Error() < resultErrors[j].Error() }) assert.DeepEqual(t, expected, resultErrors) }) t.Run("one error", func(t *testing.T) { t.Parallel() expected := New("foo") result := AggregateConcurrent([]func() error{ func() error { return expected }, }) assert.DeepEqual(t, expected, result) }) t.Run("nil", func(t *testing.T) { t.Parallel() result := AggregateConcurrent([]func() error{ func() error { return nil }, }) var expected error assert.DeepEqual(t, expected, result) }) } kind-0.27.0/pkg/errors/doc.go000066400000000000000000000012161475376161000157300ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package errors provides common utilities for dealing with errors package errors kind-0.27.0/pkg/errors/errors.go000066400000000000000000000054221475376161000165020ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package errors import ( stderrors "errors" pkgerrors "github.com/pkg/errors" ) // New returns an error with the supplied message. // New also records the stack trace at the point it was called. func New(message string) error { return pkgerrors.New(message) } // NewWithoutStack is like new but does NOT wrap with a stack // This is useful for exported errors func NewWithoutStack(message string) error { return stderrors.New(message) } // Errorf formats according to a format specifier and returns the string as a // value that satisfies error. Errorf also records the stack trace at the // point it was called. func Errorf(format string, args ...interface{}) error { return pkgerrors.Errorf(format, args...) } // Wrap returns an error annotating err with a stack trace at the point Wrap // is called, and the supplied message. If err is nil, Wrap returns nil. func Wrap(err error, message string) error { return pkgerrors.Wrap(err, message) } // Wrapf returns an error annotating err with a stack trace at the point Wrapf // is called, and the format specifier. If err is nil, Wrapf returns nil. func Wrapf(err error, format string, args ...interface{}) error { return pkgerrors.Wrapf(err, format, args...) } // WithStack annotates err with a stack trace at the point WithStack was called. // If err is nil, WithStack returns nil. func WithStack(err error) error { return pkgerrors.WithStack(err) } // Causer is an interface to github.com/pkg/errors error's Cause() wrapping type Causer interface { // Cause returns the underlying error Cause() error } // StackTracer is an interface to github.com/pkg/errors error's StackTrace() type StackTracer interface { // StackTrace returns the StackTrace ... // TODO: return our own type instead? // https://github.com/pkg/errors#roadmap StackTrace() pkgerrors.StackTrace } // StackTrace returns the deepest StackTrace in a Cause chain // https://github.com/pkg/errors/issues/173 func StackTrace(err error) pkgerrors.StackTrace { var stackErr error for { if _, ok := err.(StackTracer); ok { stackErr = err } if causerErr, ok := err.(Causer); ok { err = causerErr.Cause() } else { break } } if stackErr != nil { return stackErr.(StackTracer).StackTrace() } return nil } kind-0.27.0/pkg/errors/errors_test.go000066400000000000000000000021401475376161000175330ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package errors import ( "testing" pkgerrors "github.com/pkg/errors" "sigs.k8s.io/kind/pkg/internal/assert" ) func TestStackTrace(t *testing.T) { t.Parallel() t.Run("wrapped chain", func(t *testing.T) { t.Parallel() err := New("foo") expected := err.(StackTracer).StackTrace() result := StackTrace(Wrap(Wrap(err, "bar"), "baz")) assert.DeepEqual(t, expected, result) }) t.Run("nil", func(t *testing.T) { t.Parallel() result := StackTrace(nil) var expected pkgerrors.StackTrace assert.DeepEqual(t, expected, result) }) } kind-0.27.0/pkg/exec/000077500000000000000000000000001475376161000142445ustar00rootroot00000000000000kind-0.27.0/pkg/exec/default.go000066400000000000000000000024301475376161000162160ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package exec import "context" // DefaultCmder is a LocalCmder instance used for convenience, packages // originally using os/exec.Command can instead use pkg/kind/exec.Command // which forwards to this instance // TODO(bentheelder): swap this for testing // TODO(bentheelder): consider not using a global for this :^) var DefaultCmder = &LocalCmder{} // Command is a convenience wrapper over DefaultCmder.Command func Command(command string, args ...string) Cmd { return DefaultCmder.Command(command, args...) } // CommandContext is a convenience wrapper over DefaultCmder.CommandContext func CommandContext(ctx context.Context, command string, args ...string) Cmd { return DefaultCmder.CommandContext(ctx, command, args...) } kind-0.27.0/pkg/exec/doc.go000066400000000000000000000014331475376161000153410ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package exec contains an interface for executing commands, along with helpers // TODO(bentheelder): add standardized timeout functionality & a default timeout // so that commands cannot hang indefinitely (!) package exec kind-0.27.0/pkg/exec/helpers.go000066400000000000000000000067461475376161000162520ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package exec import ( "bufio" "bytes" "io" "os" "strings" "al.essio.dev/pkg/shellescape" "sigs.k8s.io/kind/pkg/errors" ) // PrettyCommand takes arguments identical to Cmder.Command, // it returns a pretty printed command that could be pasted into a shell func PrettyCommand(name string, args ...string) string { var out strings.Builder out.WriteString(shellescape.Quote(name)) for _, arg := range args { out.WriteByte(' ') out.WriteString(shellescape.Quote(arg)) } return out.String() } // RunErrorForError returns a RunError if the error contains a RunError. // Otherwise it returns nil func RunErrorForError(err error) *RunError { var runError *RunError for { if rErr, ok := err.(*RunError); ok { runError = rErr } if causerErr, ok := err.(errors.Causer); ok { err = causerErr.Cause() } else { break } } return runError } // CombinedOutputLines is like os/exec's cmd.CombinedOutput(), // but over our Cmd interface, and instead of returning the byte buffer of // stderr + stdout, it scans these for lines and returns a slice of output lines func CombinedOutputLines(cmd Cmd) (lines []string, err error) { var buff bytes.Buffer cmd.SetStdout(&buff) cmd.SetStderr(&buff) err = cmd.Run() scanner := bufio.NewScanner(&buff) for scanner.Scan() { lines = append(lines, scanner.Text()) } return lines, err } // OutputLines is like os/exec's cmd.Output(), // but over our Cmd interface, and instead of returning the byte buffer of // stdout, it scans these for lines and returns a slice of output lines func OutputLines(cmd Cmd) (lines []string, err error) { var buff bytes.Buffer cmd.SetStdout(&buff) err = cmd.Run() scanner := bufio.NewScanner(&buff) for scanner.Scan() { lines = append(lines, scanner.Text()) } return lines, err } // Output is like os/exec's cmd.Output, but over our Cmd interface func Output(cmd Cmd) ([]byte, error) { var buff bytes.Buffer cmd.SetStdout(&buff) err := cmd.Run() return buff.Bytes(), err } // InheritOutput sets cmd's output to write to the current process's stdout and stderr func InheritOutput(cmd Cmd) Cmd { cmd.SetStderr(os.Stderr) cmd.SetStdout(os.Stdout) return cmd } // RunWithStdoutReader runs cmd with stdout piped to readerFunc func RunWithStdoutReader(cmd Cmd, readerFunc func(io.Reader) error) error { pr, pw, err := os.Pipe() if err != nil { return err } cmd.SetStdout(pw) return errors.AggregateConcurrent([]func() error{ func() error { defer pr.Close() return readerFunc(pr) }, func() error { defer pw.Close() return cmd.Run() }, }) } // RunWithStdinWriter runs cmd with writerFunc piped to stdin func RunWithStdinWriter(cmd Cmd, writerFunc func(io.Writer) error) error { pr, pw, err := os.Pipe() if err != nil { return err } cmd.SetStdin(pr) return errors.AggregateConcurrent([]func() error{ func() error { defer pw.Close() return writerFunc(pw) }, func() error { defer pr.Close() return cmd.Run() }, }) } kind-0.27.0/pkg/exec/local.go000066400000000000000000000106041475376161000156660ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package exec import ( "bytes" "context" "io" osexec "os/exec" "sync" "sigs.k8s.io/kind/pkg/errors" ) // LocalCmd wraps os/exec.Cmd, implementing the kind/pkg/exec.Cmd interface type LocalCmd struct { *osexec.Cmd } var _ Cmd = &LocalCmd{} // LocalCmder is a factory for LocalCmd, implementing Cmder type LocalCmder struct{} var _ Cmder = &LocalCmder{} // Command returns a new exec.Cmd backed by Cmd func (c *LocalCmder) Command(name string, arg ...string) Cmd { return &LocalCmd{ Cmd: osexec.Command(name, arg...), } } // CommandContext is like Command but includes a context func (c *LocalCmder) CommandContext(ctx context.Context, name string, arg ...string) Cmd { return &LocalCmd{ Cmd: osexec.CommandContext(ctx, name, arg...), } } // SetEnv sets env func (cmd *LocalCmd) SetEnv(env ...string) Cmd { cmd.Env = env return cmd } // SetStdin sets stdin func (cmd *LocalCmd) SetStdin(r io.Reader) Cmd { cmd.Stdin = r return cmd } // SetStdout set stdout func (cmd *LocalCmd) SetStdout(w io.Writer) Cmd { cmd.Stdout = w return cmd } // SetStderr sets stderr func (cmd *LocalCmd) SetStderr(w io.Writer) Cmd { cmd.Stderr = w return cmd } // Run runs the command // If the returned error is non-nil, it should be of type *RunError func (cmd *LocalCmd) Run() error { // Background: // Go's stdlib will setup and use a shared fd when cmd.Stderr == cmd.Stdout // In any other case, it will use different fds, which will involve // two different io.Copy goroutines writing to cmd.Stderr and cmd.Stdout // // Given this, we must synchronize capturing the output to a buffer // IFF ! interfaceEqual(cmd.Sterr, cmd.Stdout) var combinedOutput bytes.Buffer var combinedOutputWriter io.Writer = &combinedOutput if cmd.Stdout == nil && cmd.Stderr == nil { // Case 1: If stdout and stderr are nil, we can just use the buffer // The buffer will be == and Go will use one fd / goroutine cmd.Stdout = combinedOutputWriter cmd.Stderr = combinedOutputWriter } else if interfaceEqual(cmd.Stdout, cmd.Stderr) { // Case 2: If cmd.Stdout == cmd.Stderr go will still share the fd, // but we need to wrap with a MultiWriter to respect the other writer // and our buffer. // The MultiWriter will be == and Go will use one fd / goroutine cmd.Stdout = io.MultiWriter(cmd.Stdout, combinedOutputWriter) cmd.Stderr = cmd.Stdout } else { // Case 3: If cmd.Stdout != cmd.Stderr, we need to synchronize the // combined output writer. // Go will use different fds / write routines for stdout and stderr combinedOutputWriter = &mutexWriter{ writer: &combinedOutput, } // wrap writers if non-nil if cmd.Stdout != nil { cmd.Stdout = io.MultiWriter(cmd.Stdout, combinedOutputWriter) } else { cmd.Stdout = combinedOutputWriter } if cmd.Stderr != nil { cmd.Stderr = io.MultiWriter(cmd.Stderr, combinedOutputWriter) } else { cmd.Stderr = combinedOutputWriter } } // TODO: should be in the caller or logger should be injected somehow ... if err := cmd.Cmd.Run(); err != nil { return errors.WithStack(&RunError{ Command: cmd.Args, Output: combinedOutput.Bytes(), Inner: err, }) } return nil } // interfaceEqual protects against panics from doing equality tests on // two interfaces with non-comparable underlying types. // This trivial is borrowed from the go stdlib in os/exec // Note that the recover will only happen if a is not comparable to b, // in which case we'll return false // We've lightly modified this to pass errcheck (explicitly ignoring recover) func interfaceEqual(a, b interface{}) bool { defer func() { _ = recover() }() return a == b } // mutexWriter is a simple synchronized wrapper around an io.Writer type mutexWriter struct { writer io.Writer mu sync.Mutex } func (m *mutexWriter) Write(b []byte) (int, error) { m.mu.Lock() defer m.mu.Unlock() n, err := m.writer.Write(b) return n, err } kind-0.27.0/pkg/exec/types.go000066400000000000000000000036661475376161000157520ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package exec import ( "context" "fmt" "io" ) // Cmd abstracts over running a command somewhere, this is useful for testing type Cmd interface { // Run executes the command (like os/exec.Cmd.Run), it should return // a *RunError if there is any error Run() error // Each entry should be of the form "key=value" SetEnv(...string) Cmd SetStdin(io.Reader) Cmd SetStdout(io.Writer) Cmd SetStderr(io.Writer) Cmd } // Cmder abstracts over creating commands type Cmder interface { // command, args..., just like os/exec.Cmd Command(string, ...string) Cmd CommandContext(context.Context, string, ...string) Cmd } // RunError represents an error running a Cmd type RunError struct { Command []string // [Name Args...] Output []byte // Captured Stdout / Stderr of the command Inner error // Underlying error if any } var _ error = &RunError{} func (e *RunError) Error() string { // TODO(BenTheElder): implement formatter, and show output for %+v ? return fmt.Sprintf("command \"%s\" failed with error: %v", e.PrettyCommand(), e.Inner) } // PrettyCommand pretty prints the command in a way that could be pasted // into a shell func (e *RunError) PrettyCommand() string { return PrettyCommand(e.Command[0], e.Command[1:]...) } // Cause mimics github.com/pkg/errors's Cause pattern for errors func (e *RunError) Cause() error { if e.Inner != nil { return e.Inner } return e } kind-0.27.0/pkg/fs/000077500000000000000000000000001475376161000137305ustar00rootroot00000000000000kind-0.27.0/pkg/fs/fs.go000066400000000000000000000076461475376161000147040ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package fs contains utilities for interacting with the host filesystem // in a docker friendly way // TODO(bentheelder): this should be internal package fs import ( "io" "os" "path" "path/filepath" "runtime" "strings" ) // TempDir is like os.MkdirTemp, but more docker friendly func TempDir(dir, prefix string) (name string, err error) { // create a tempdir as normal name, err = os.MkdirTemp(dir, prefix) if err != nil { return "", err } // on macOS $TMPDIR is typically /var/..., which is not mountable // /private/var/... is the mountable equivalent if runtime.GOOS == "darwin" && strings.HasPrefix(name, "/var/") { name = filepath.Join("/private", name) } return name, nil } // IsAbs is like filepath.IsAbs but also considering posix absolute paths // to be absolute even if filepath.IsAbs would not // This fixes the case of Posix paths on Windows func IsAbs(hostPath string) bool { return path.IsAbs(hostPath) || filepath.IsAbs(hostPath) } // Copy recursively directories, symlinks, files copies from src to dst // Copy will make dirs as necessary, and keep file modes // Symlinks will be dereferenced similar to `cp -r src dst` func Copy(src, dst string) error { // get source info info, err := os.Lstat(src) if err != nil { return err } // make sure dest dir exists if err := os.MkdirAll(filepath.Dir(dst), os.ModePerm); err != nil { return err } // do real copy work return copyWithSrcInfo(src, dst, info) } func copyWithSrcInfo(src, dst string, info os.FileInfo) error { if info.Mode()&os.ModeSymlink != 0 { return copySymlink(src, dst) } if info.IsDir() { return copyDir(src, dst, info) } return copyFile(src, dst, info) } // CopyFile copies a file from src to dst func CopyFile(src, dst string) (err error) { // get source information info, err := os.Stat(src) if err != nil { return err } return copyFile(src, dst, info) } func copyFile(src, dst string, info os.FileInfo) error { // open src for reading in, err := os.Open(src) if err != nil { return err } defer in.Close() // create dst file // this is like f, err := os.Create(dst); os.Chmod(f.Name(), src.Mode()) out, err := os.OpenFile(dst, os.O_RDWR|os.O_CREATE|os.O_TRUNC, info.Mode()) if err != nil { return err } // make sure we close the file defer func() { closeErr := out.Close() // if we weren't returning an error if err == nil { err = closeErr } }() // actually copy if _, err = io.Copy(out, in); err != nil { return err } err = out.Sync() return err } // copySymlink dereferences and then copies a symlink func copySymlink(src, dst string) error { // read through the symlink realSrc, err := filepath.EvalSymlinks(src) if err != nil { return err } info, err := os.Lstat(realSrc) if err != nil { return err } // copy the underlying contents return copyWithSrcInfo(realSrc, dst, info) } func copyDir(src, dst string, info os.FileInfo) error { // make sure the target dir exists if err := os.MkdirAll(dst, info.Mode()); err != nil { return err } // copy every source dir entry entries, err := os.ReadDir(src) if err != nil { return err } for _, entry := range entries { entrySrc := filepath.Join(src, entry.Name()) entryDst := filepath.Join(dst, entry.Name()) fileInfo, err := entry.Info() if err != nil { return err } if err := copyWithSrcInfo(entrySrc, entryDst, fileInfo); err != nil { return err } } return nil } kind-0.27.0/pkg/internal/000077500000000000000000000000001475376161000151345ustar00rootroot00000000000000kind-0.27.0/pkg/internal/apis/000077500000000000000000000000001475376161000160705ustar00rootroot00000000000000kind-0.27.0/pkg/internal/apis/config/000077500000000000000000000000001475376161000173355ustar00rootroot00000000000000kind-0.27.0/pkg/internal/apis/config/cluster_util.go000066400000000000000000000021721475376161000224040ustar00rootroot00000000000000/* Copyright 2021 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package config // ClusterHasIPv6 returns true if the cluster should have IPv6 enabled due to either // being IPv6 cluster family or Dual Stack func ClusterHasIPv6(c *Cluster) bool { return c.Networking.IPFamily == IPv6Family || c.Networking.IPFamily == DualStackFamily } // ClusterHasImplicitLoadBalancer returns true if this cluster has an implicit api-server LoadBalancer func ClusterHasImplicitLoadBalancer(c *Cluster) bool { controlPlanes := 0 for _, node := range c.Nodes { if node.Role == ControlPlaneRole { controlPlanes++ } } return controlPlanes > 1 } kind-0.27.0/pkg/internal/apis/config/cluster_util_test.go000066400000000000000000000052211475376161000234410ustar00rootroot00000000000000/* Copyright 2021 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package config import ( "testing" "sigs.k8s.io/kind/pkg/internal/assert" ) func TestClusterHasIPv6(t *testing.T) { cases := []struct { Name string c *Cluster expected bool }{ { Name: "IPv6", c: &Cluster{ Networking: Networking{ IPFamily: IPv6Family, }, }, expected: true, }, { Name: "IPv4", c: &Cluster{ Networking: Networking{ IPFamily: IPv4Family, }, }, expected: false, }, { Name: "DualStack", c: &Cluster{ Networking: Networking{ IPFamily: DualStackFamily, }, }, expected: true, }, } for _, tc := range cases { tc := tc // capture loop var t.Run(tc.Name, func(t *testing.T) { r := ClusterHasIPv6(tc.c) assert.BoolEqual(t, tc.expected, r) }) } } func TestClusterHasImplicitLoadBalancer(t *testing.T) { cases := []struct { Name string c *Cluster expected bool }{ { Name: "One Node", c: &Cluster{ Nodes: []Node{ {Role: ControlPlaneRole}, }, }, expected: false, }, { Name: "Two Control Planes", c: &Cluster{ Nodes: []Node{ {Role: ControlPlaneRole}, {Role: ControlPlaneRole}, }, }, expected: true, }, { Name: "Three Control Planes", c: &Cluster{ Nodes: []Node{ {Role: ControlPlaneRole}, {Role: ControlPlaneRole}, {Role: ControlPlaneRole}, }, }, expected: true, }, { Name: "One Control Plane, Multiple Workers", c: &Cluster{ Nodes: []Node{ {Role: ControlPlaneRole}, {Role: WorkerRole}, {Role: WorkerRole}, {Role: WorkerRole}, }, }, expected: false, }, { Name: "Multiple Control Planes, Multiple Workers", c: &Cluster{ Nodes: []Node{ {Role: ControlPlaneRole}, {Role: ControlPlaneRole}, {Role: ControlPlaneRole}, {Role: WorkerRole}, {Role: WorkerRole}, {Role: WorkerRole}, }, }, expected: true, }, } for _, tc := range cases { tc := tc // capture loop var t.Run(tc.Name, func(t *testing.T) { r := ClusterHasImplicitLoadBalancer(tc.c) assert.BoolEqual(t, tc.expected, r) }) } } kind-0.27.0/pkg/internal/apis/config/convert_v1alpha4.go000066400000000000000000000067521475376161000230560ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package config import ( v1alpha4 "sigs.k8s.io/kind/pkg/apis/config/v1alpha4" ) // Convertv1alpha4 converts a v1alpha4 cluster to a cluster at the internal API version func Convertv1alpha4(in *v1alpha4.Cluster) *Cluster { in = in.DeepCopy() // deep copy first to avoid touching the original out := &Cluster{ Name: in.Name, Nodes: make([]Node, len(in.Nodes)), FeatureGates: in.FeatureGates, RuntimeConfig: in.RuntimeConfig, KubeadmConfigPatches: in.KubeadmConfigPatches, KubeadmConfigPatchesJSON6902: make([]PatchJSON6902, len(in.KubeadmConfigPatchesJSON6902)), ContainerdConfigPatches: in.ContainerdConfigPatches, ContainerdConfigPatchesJSON6902: in.ContainerdConfigPatchesJSON6902, } for i := range in.Nodes { convertv1alpha4Node(&in.Nodes[i], &out.Nodes[i]) } convertv1alpha4Networking(&in.Networking, &out.Networking) for i := range in.KubeadmConfigPatchesJSON6902 { convertv1alpha4PatchJSON6902(&in.KubeadmConfigPatchesJSON6902[i], &out.KubeadmConfigPatchesJSON6902[i]) } return out } func convertv1alpha4Node(in *v1alpha4.Node, out *Node) { out.Role = NodeRole(in.Role) out.Image = in.Image out.Labels = in.Labels out.KubeadmConfigPatches = in.KubeadmConfigPatches out.ExtraMounts = make([]Mount, len(in.ExtraMounts)) out.ExtraPortMappings = make([]PortMapping, len(in.ExtraPortMappings)) out.KubeadmConfigPatchesJSON6902 = make([]PatchJSON6902, len(in.KubeadmConfigPatchesJSON6902)) for i := range in.ExtraMounts { convertv1alpha4Mount(&in.ExtraMounts[i], &out.ExtraMounts[i]) } for i := range in.ExtraPortMappings { convertv1alpha4PortMapping(&in.ExtraPortMappings[i], &out.ExtraPortMappings[i]) } for i := range in.KubeadmConfigPatchesJSON6902 { convertv1alpha4PatchJSON6902(&in.KubeadmConfigPatchesJSON6902[i], &out.KubeadmConfigPatchesJSON6902[i]) } } func convertv1alpha4PatchJSON6902(in *v1alpha4.PatchJSON6902, out *PatchJSON6902) { out.Group = in.Group out.Version = in.Version out.Kind = in.Kind out.Patch = in.Patch } func convertv1alpha4Networking(in *v1alpha4.Networking, out *Networking) { out.IPFamily = ClusterIPFamily(in.IPFamily) out.APIServerPort = in.APIServerPort out.APIServerAddress = in.APIServerAddress out.PodSubnet = in.PodSubnet out.KubeProxyMode = ProxyMode(in.KubeProxyMode) out.ServiceSubnet = in.ServiceSubnet out.DisableDefaultCNI = in.DisableDefaultCNI out.DNSSearch = in.DNSSearch } func convertv1alpha4Mount(in *v1alpha4.Mount, out *Mount) { out.ContainerPath = in.ContainerPath out.HostPath = in.HostPath out.Readonly = in.Readonly out.SelinuxRelabel = in.SelinuxRelabel out.Propagation = MountPropagation(in.Propagation) } func convertv1alpha4PortMapping(in *v1alpha4.PortMapping, out *PortMapping) { out.ContainerPort = in.ContainerPort out.HostPort = in.HostPort out.ListenAddress = in.ListenAddress out.Protocol = PortMappingProtocol(in.Protocol) } kind-0.27.0/pkg/internal/apis/config/default.go000066400000000000000000000064231475376161000213150ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // this comment makes golint ignore this file, feel free to edit the file. // Code generated by not-actually-generated-but-go-away-golint. DO NOT EDIT. // https://github.com/kubernetes/code-generator/issues/30 package config import ( "sigs.k8s.io/kind/pkg/apis/config/defaults" "sigs.k8s.io/kind/pkg/cluster/constants" ) // SetDefaultsCluster sets uninitialized fields to their default value. func SetDefaultsCluster(obj *Cluster) { // default cluster name if obj.Name == "" { obj.Name = constants.DefaultClusterName } // default to a one node cluster if len(obj.Nodes) == 0 { obj.Nodes = []Node{ { Image: defaults.Image, Role: ControlPlaneRole, }, } } // default nodes for i := range obj.Nodes { a := &obj.Nodes[i] SetDefaultsNode(a) } if obj.Networking.IPFamily == "" { obj.Networking.IPFamily = IPv4Family } // default to listening on 127.0.0.1:randomPort on ipv4 // and [::1]:randomPort on ipv6 if obj.Networking.APIServerAddress == "" { obj.Networking.APIServerAddress = "127.0.0.1" if obj.Networking.IPFamily == IPv6Family { obj.Networking.APIServerAddress = "::1" } } // default the pod CIDR if obj.Networking.PodSubnet == "" { obj.Networking.PodSubnet = "10.244.0.0/16" if obj.Networking.IPFamily == IPv6Family { // node-mask cidr default is /64 so we need a larger subnet, we use /56 following best practices // xref: https://www.ripe.net/publications/docs/ripe-690#4--size-of-end-user-prefix-assignment---48---56-or-something-else- obj.Networking.PodSubnet = "fd00:10:244::/56" } if obj.Networking.IPFamily == DualStackFamily { obj.Networking.PodSubnet = "10.244.0.0/16,fd00:10:244::/56" } } // default the service CIDR using the kubeadm default // https://github.com/kubernetes/kubernetes/blob/746404f82a28e55e0b76ffa7e40306fb88eb3317/cmd/kubeadm/app/apis/kubeadm/v1beta2/defaults.go#L32 // Note: kubeadm is using a /12 subnet, that may allocate a 2^20 bitmap in etcd // we allocate a /16 subnet that allows 65535 services (current Kubernetes tested limit is O(10k) services) if obj.Networking.ServiceSubnet == "" { obj.Networking.ServiceSubnet = "10.96.0.0/16" if obj.Networking.IPFamily == IPv6Family { obj.Networking.ServiceSubnet = "fd00:10:96::/112" } if obj.Networking.IPFamily == DualStackFamily { obj.Networking.ServiceSubnet = "10.96.0.0/16,fd00:10:96::/112" } } // default the KubeProxyMode using iptables as it's already the default if obj.Networking.KubeProxyMode == "" { obj.Networking.KubeProxyMode = IPTablesProxyMode } } // SetDefaultsNode sets uninitialized fields to their default value. func SetDefaultsNode(obj *Node) { if obj.Image == "" { obj.Image = defaults.Image } if obj.Role == "" { obj.Role = ControlPlaneRole } } kind-0.27.0/pkg/internal/apis/config/doc.go000066400000000000000000000014721475376161000204350ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package config implements the current apiVersion of the `kind` Config // along with some common abstractions // // +k8s:deepcopy-gen=package // +k8s:conversion-gen=sigs.k8s.io/kind/pkg/internal/apis/config // +k8s:defaulter-gen=TypeMeta package config kind-0.27.0/pkg/internal/apis/config/encoding/000077500000000000000000000000001475376161000211235ustar00rootroot00000000000000kind-0.27.0/pkg/internal/apis/config/encoding/convert.go000066400000000000000000000016041475376161000231330ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package encoding import ( "sigs.k8s.io/kind/pkg/apis/config/v1alpha4" "sigs.k8s.io/kind/pkg/internal/apis/config" ) // V1Alpha4ToInternal converts to the internal API version func V1Alpha4ToInternal(cluster *v1alpha4.Cluster) *config.Cluster { v1alpha4.SetDefaultsCluster(cluster) return config.Convertv1alpha4(cluster) } kind-0.27.0/pkg/internal/apis/config/encoding/doc.go000066400000000000000000000012361475376161000222210ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package encoding implements utilities for decoding from yaml the `kind` Config package encoding kind-0.27.0/pkg/internal/apis/config/encoding/load.go000066400000000000000000000052431475376161000223750ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package encoding import ( "bytes" "os" yaml "gopkg.in/yaml.v3" "sigs.k8s.io/kind/pkg/apis/config/v1alpha4" "sigs.k8s.io/kind/pkg/errors" "sigs.k8s.io/kind/pkg/internal/apis/config" ) // Load reads the file at path and attempts to convert into a `kind` Config; the file // can be one of the different API versions defined in scheme. // If path == "" then the default config is returned // If path == "-" then reads from stdin func Load(path string) (*config.Cluster, error) { // special case: empty path -> default config // TODO(bentheelder): consider removing this if path == "" { out := &config.Cluster{} config.SetDefaultsCluster(out) return out, nil } // read in file raw, err := os.ReadFile(path) if err != nil { return nil, errors.Wrap(err, "error reading file") } return Parse(raw) } // Parse parses a cluster config from raw (yaml) bytes // It will always return the current internal version after defaulting and // conversion from the read version func Parse(raw []byte) (*config.Cluster, error) { // get kind & apiVersion tm := typeMeta{} if err := yaml.Unmarshal(raw, &tm); err != nil { return nil, errors.Wrap(err, "could not determine kind / apiVersion for config") } // decode specific (apiVersion, kind) switch tm.APIVersion { // handle v1alpha4 case "kind.x-k8s.io/v1alpha4": if tm.Kind != "Cluster" { return nil, errors.Errorf("unknown kind %s for apiVersion: %s", tm.Kind, tm.APIVersion) } // load version cfg := &v1alpha4.Cluster{} if err := yamlUnmarshalStrict(raw, cfg); err != nil { return nil, errors.Wrap(err, "unable to decode config") } // apply defaults for version and convert return V1Alpha4ToInternal(cfg), nil } // unknown apiVersion if we haven't already returned ... return nil, errors.Errorf("unknown apiVersion: %s", tm.APIVersion) } // basically metav1.TypeMeta, but with yaml tags type typeMeta struct { Kind string `yaml:"kind,omitempty"` APIVersion string `yaml:"apiVersion,omitempty"` } func yamlUnmarshalStrict(raw []byte, v interface{}) error { d := yaml.NewDecoder(bytes.NewReader(raw)) d.KnownFields(true) return d.Decode(v) } kind-0.27.0/pkg/internal/apis/config/encoding/load_test.go000066400000000000000000000062471475376161000234410ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package encoding import ( "testing" ) func TestLoadCurrent(t *testing.T) { t.Parallel() cases := []struct { TestName string Path string ExpectError bool }{ { TestName: "example config", Path: "./../../../../../site/content/docs/user/kind-example-config.yaml", ExpectError: false, }, { TestName: "no config", Path: "", ExpectError: false, }, { TestName: "v1alpha4 minimal", Path: "./testdata/v1alpha4/valid-minimal.yaml", ExpectError: false, }, { TestName: "v1alpha4 config with 2 nodes", Path: "./testdata/v1alpha4/valid-minimal-two-nodes.yaml", ExpectError: false, }, { TestName: "v1alpha4 full HA", Path: "./testdata/v1alpha4/valid-full-ha.yaml", ExpectError: false, }, { TestName: "v1alpha4 many fields set", Path: "./testdata/v1alpha4/valid-many-fields.yaml", ExpectError: false, }, { TestName: "v1alpha4 config with patches", Path: "./testdata/v1alpha4/valid-kind-patches.yaml", ExpectError: false, }, { TestName: "v1alpha4 config with workers patches", Path: "./testdata/v1alpha4/valid-kind-workers-patches.yaml", ExpectError: false, }, { TestName: "v1alpha4 config with port mapping and mount", Path: "./testdata/v1alpha4/valid-port-and-mount.yaml", ExpectError: false, }, { TestName: "v1alpha4 non-existent field", Path: "./testdata/v1alpha4/invalid-bogus-field.yaml", ExpectError: true, }, { TestName: "v1alpha4 bad indentation", Path: "./testdata/v1alpha4/invalid-bad-indent.yaml", ExpectError: true, }, { TestName: "invalid path", Path: "./testdata/not-a-file.bogus", ExpectError: true, }, { TestName: "Invalid apiversion", Path: "./testdata/invalid-apiversion.yaml", ExpectError: true, }, { TestName: "Invalid kind", Path: "./testdata/invalid-kind.yaml", ExpectError: true, }, { TestName: "Invalid yaml", Path: "./testdata/invalid-yaml.yaml", ExpectError: true, }, } for _, c := range cases { c := c // capture loop variable t.Run(c.TestName, func(t *testing.T) { t.Parallel() _, err := Load(c.Path) // the error can be: // - nil, in which case we should expect no errors or fail if err != nil { if !c.ExpectError { t.Fatalf("unexpected error while Loading config: %v", err) } return } // - not nil, in which case we should expect errors or fail if c.ExpectError { t.Fatalf("unexpected lack or error while Loading config") } }) } } kind-0.27.0/pkg/internal/apis/config/encoding/testdata/000077500000000000000000000000001475376161000227345ustar00rootroot00000000000000kind-0.27.0/pkg/internal/apis/config/encoding/testdata/invalid-apiversion.yaml000066400000000000000000000001401475376161000274160ustar00rootroot00000000000000# this file contains an invalid config api version for testing kind: Node apiVersion: not-valid kind-0.27.0/pkg/internal/apis/config/encoding/testdata/invalid-kind.yaml000066400000000000000000000001521475376161000261670ustar00rootroot00000000000000# this file contains an invalid config kind for testing kind: not-valid apiVersion: kind.x-k8s.io/v1alpha4kind-0.27.0/pkg/internal/apis/config/encoding/testdata/invalid-yaml.yaml000066400000000000000000000000601475376161000262020ustar00rootroot00000000000000# intentionally invalid yaml file for testing ":kind-0.27.0/pkg/internal/apis/config/encoding/testdata/v1alpha4/000077500000000000000000000000001475376161000243545ustar00rootroot00000000000000kind-0.27.0/pkg/internal/apis/config/encoding/testdata/v1alpha4/invalid-bad-indent.yaml000066400000000000000000000005431475376161000306730ustar00rootroot00000000000000kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 networking: ipFamily: ipv6 nodes: - role: control-plane - role: worker extraMounts: - containerPath: /foo hostPath: /bar readOnly: true selinuxRelabel: false propagation: Bidirectional extraPortMappings: - containerPort: 8080 hostPort: 8080 protocol: UDP - role: worker kind-0.27.0/pkg/internal/apis/config/encoding/testdata/v1alpha4/invalid-bogus-field.yaml000066400000000000000000000005511475376161000310650ustar00rootroot00000000000000kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 networking: ipFamily: ipv6 nodes: - role: control-plane - role: worker extraMounts: - containerPath: /foo hostPath: /bar readOnly: true selinuxRelabel: false propagation: Bidirectional extraPortMappings: - containerPort: 8080 hostPort: 8080 protocol: UDP nOtAReaLFielD: bar kind-0.27.0/pkg/internal/apis/config/encoding/testdata/v1alpha4/valid-full-ha.yaml000066400000000000000000000003361475376161000276670ustar00rootroot00000000000000# technically valid, config file with a full ha cluster kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 nodes: - role: control-plane - role: control-plane - role: control-plane - role: worker - role: worker - role: workerkind-0.27.0/pkg/internal/apis/config/encoding/testdata/v1alpha4/valid-kind-patches.yaml000066400000000000000000000016001475376161000307040ustar00rootroot00000000000000# this config file contains all config fields with comments kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 # patch the generated kubeadm config with some extra settings kubeadmConfigPatches: - | apiVersion: kubeadm.k8s.io/v1beta2 kind: ClusterConfiguration metadata: name: config networking: serviceSubnet: 10.0.0.0/16 # patch it further using a JSON 6902 patch kubeadmConfigPatchesJSON6902: - group: kubeadm.k8s.io version: v1beta2 kind: ClusterConfiguration patch: | - op: add path: /apiServer/certSANs/- value: my-hostname # patch containerd config containerdConfigPatches: # configure a local insecure registry - |- [plugins."io.containerd.grpc.v1.cri".registry.mirrors."registry:5000"] # 1 control plane node and 3 workers nodes: # the control plane node config - role: control-plane # the three workers - role: worker - role: worker - role: worker kind-0.27.0/pkg/internal/apis/config/encoding/testdata/v1alpha4/valid-kind-workers-patches.yaml000066400000000000000000000015401475376161000324010ustar00rootroot00000000000000# this config file contains all config fields with comments kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 # 1 control plane node and 3 workers nodes: # the control plane node config - role: control-plane # the three workers - role: worker kubeadmConfigPatches: - | apiVersion: kubeadm.k8s.io/v1beta2 kind: JoinConfiguration nodeRegistration: kubeletExtraArgs: node-labels: "aqua=gateway" authorization-mode: "AlwaysAllow" - role: worker kubeadmConfigPatchesJSON6902: - group: kubelet.config.k8s.io version: v1beta1 kind: KubeletConfiguration patch: | - op: add path: /authentication value: {"anonymous": {"enabled": true}} - op: add path: /tlsCipherSuites value: ["TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256","TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"] - role: worker kind-0.27.0/pkg/internal/apis/config/encoding/testdata/v1alpha4/valid-many-fields.yaml000066400000000000000000000006031475376161000305440ustar00rootroot00000000000000kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 name: not-default featureGates: AllBeta: false networking: ipFamily: ipv6 nodes: - role: control-plane - role: worker extraMounts: - containerPath: /foo hostPath: /bar readOnly: true selinuxRelabel: false propagation: Bidirectional extraPortMappings: - containerPort: 8080 hostPort: 8080 protocol: UDP kind-0.27.0/pkg/internal/apis/config/encoding/testdata/v1alpha4/valid-minimal-two-nodes.yaml000066400000000000000000000002241475376161000316760ustar00rootroot00000000000000# technically valid, minimal config file with two nodes kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 nodes: - role: control-plane - role: workerkind-0.27.0/pkg/internal/apis/config/encoding/testdata/v1alpha4/valid-minimal.yaml000066400000000000000000000001321475376161000277570ustar00rootroot00000000000000# technically valid, minimal config file kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 kind-0.27.0/pkg/internal/apis/config/encoding/testdata/v1alpha4/valid-port-and-mount.yaml000066400000000000000000000004001475376161000312130ustar00rootroot00000000000000kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 nodes: - role: control-plane # map an extra port extraPortMappings: - hostPort: 80 containerPort: 80 # mount an extra path from the host extraMounts: - hostPath: ./foo containerPath: /barkind-0.27.0/pkg/internal/apis/config/types.go000066400000000000000000000242401475376161000210320ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package config /* NOTE: unlike the public types these should not have serialization tags and should stay 100% internal. These are used to pass around the processed public config for internal usage. */ // Cluster contains kind cluster configuration type Cluster struct { // The cluster name. // Optional, this will be overridden by --name / KIND_CLUSTER_NAME Name string // Nodes contains the list of nodes defined in the `kind` Cluster // If unset this will default to a single control-plane node // Note that if more than one control plane is specified, an external // control plane load balancer will be provisioned implicitly Nodes []Node /* Advanced fields */ // Networking contains cluster wide network settings Networking Networking // FeatureGates contains a map of Kubernetes feature gates to whether they // are enabled. The feature gates specified here are passed to all Kubernetes components as flags or in config. // // https://kubernetes.io/docs/reference/command-line-tools-reference/feature-gates/ FeatureGates map[string]bool // RuntimeConfig Keys and values are translated into --runtime-config values for kube-apiserver, separated by commas. // // Use this to enable alpha APIs. RuntimeConfig map[string]string // KubeadmConfigPatches are applied to the generated kubeadm config as // strategic merge patches to `kustomize build` internally // https://github.com/kubernetes/community/blob/a9cf5c8f3380bb52ebe57b1e2dbdec136d8dd484/contributors/devel/sig-api-machinery/strategic-merge-patch.md // This should be an inline yaml blob-string KubeadmConfigPatches []string // KubeadmConfigPatchesJSON6902 are applied to the generated kubeadm config // as patchesJson6902 to `kustomize build` KubeadmConfigPatchesJSON6902 []PatchJSON6902 // ContainerdConfigPatches are applied to every node's containerd config // in the order listed. // These should be toml stringsto be applied as merge patches ContainerdConfigPatches []string // ContainerdConfigPatchesJSON6902 are applied to every node's containerd config // in the order listed. // These should be YAML or JSON formatting RFC 6902 JSON patches ContainerdConfigPatchesJSON6902 []string } // Node contains settings for a node in the `kind` Cluster. // A node in kind config represent a container that will be provisioned with all the components // required for the assigned role in the Kubernetes cluster type Node struct { // Role defines the role of the node in the in the Kubernetes cluster // created by kind // // Defaults to "control-plane" Role NodeRole // Image is the node image to use when creating this node // If unset a default image will be used, see defaults.Image Image string // Labels are the labels with which the respective node will be labeled Labels map[string]string /* Advanced fields */ // ExtraMounts describes additional mount points for the node container // These may be used to bind a hostPath ExtraMounts []Mount // ExtraPortMappings describes additional port mappings for the node container // binded to a host Port ExtraPortMappings []PortMapping // KubeadmConfigPatches are applied to the generated kubeadm config as // strategic merge patches to `kustomize build` internally // https://github.com/kubernetes/community/blob/a9cf5c8f3380bb52ebe57b1e2dbdec136d8dd484/contributors/devel/sig-api-machinery/strategic-merge-patch.md // This should be an inline yaml blob-string KubeadmConfigPatches []string // KubeadmConfigPatchesJSON6902 are applied to the generated kubeadm config // as patchesJson6902 to `kustomize build` KubeadmConfigPatchesJSON6902 []PatchJSON6902 } // NodeRole defines possible role for nodes in a Kubernetes cluster managed by `kind` type NodeRole string const ( // ControlPlaneRole identifies a node that hosts a Kubernetes control-plane. // NOTE: in single node clusters, control-plane nodes act also as a worker // nodes, in which case the taint will be removed. see: // https://kubernetes.io/docs/setup/independent/create-cluster-kubeadm/#control-plane-node-isolation ControlPlaneRole NodeRole = "control-plane" // WorkerRole identifies a node that hosts a Kubernetes worker WorkerRole NodeRole = "worker" ) // Networking contains cluster wide network settings type Networking struct { // IPFamily is the network cluster model, currently it can be ipv4 or ipv6 IPFamily ClusterIPFamily // APIServerPort is the listen port on the host for the Kubernetes API Server // Defaults to a random port on the host obtained by kind // // NOTE: if you set the special value of `-1` then the node backend // (docker, podman...) will be left to pick the port instead. // This is potentially useful for remote hosts, BUT it means when the container // is restarted it will be randomized. Leave this unset to allow kind to pick it. APIServerPort int32 // APIServerAddress is the listen address on the host for the Kubernetes // API Server. This should be an IP address. // // Defaults to 127.0.0.1 APIServerAddress string // PodSubnet is the CIDR used for pod IPs // kind will select a default if unspecified PodSubnet string // ServiceSubnet is the CIDR used for services VIPs // kind will select a default if unspecified ServiceSubnet string // If DisableDefaultCNI is true, kind will not install the default CNI setup. // Instead the user should install their own CNI after creating the cluster. DisableDefaultCNI bool // KubeProxyMode defines if kube-proxy should operate in iptables, ipvs or nftables mode KubeProxyMode ProxyMode // DNSSearch defines the DNS search domain to use for nodes. If not set, this will be inherited from the host. DNSSearch *[]string } // ClusterIPFamily defines cluster network IP family type ClusterIPFamily string const ( // IPv4Family sets ClusterIPFamily to ipv4 IPv4Family ClusterIPFamily = "ipv4" // IPv6Family sets ClusterIPFamily to ipv6 IPv6Family ClusterIPFamily = "ipv6" // DualStackFamily sets ClusterIPFamily to dual DualStackFamily ClusterIPFamily = "dual" ) // ProxyMode defines a proxy mode for kube-proxy type ProxyMode string const ( // IPTablesProxyMode sets ProxyMode to iptables IPTablesProxyMode ProxyMode = "iptables" // IPVSProxyMode sets ProxyMode to ipvs IPVSProxyMode ProxyMode = "ipvs" // NFTablesProxyMode sets ProxyMode to nftables NFTablesProxyMode ProxyMode = "nftables" // NoneProxyMode disables kube-proxy NoneProxyMode ProxyMode = "none" ) // PatchJSON6902 represents an inline kustomize json 6902 patch // https://tools.ietf.org/html/rfc6902 type PatchJSON6902 struct { // these fields specify the patch target resource Group string Version string Kind string // Patch should contain the contents of the json patch as a string Patch string } // Mount specifies a host volume to mount into a container. // This is a close copy of the upstream cri Mount type // see: k8s.io/kubernetes/pkg/kubelet/apis/cri/runtime/v1alpha2 // It additionally serializes the "propagation" field with the string enum // names on disk as opposed to the int32 values, and the serialized field names // have been made closer to core/v1 VolumeMount field names // In yaml this looks like: // // containerPath: /foo // hostPath: /bar // readOnly: true // selinuxRelabel: false // propagation: None // // Propagation may be one of: None, HostToContainer, Bidirectional type Mount struct { // Path of the mount within the container. ContainerPath string // Path of the mount on the host. If the hostPath doesn't exist, then runtimes // should report error. If the hostpath is a symbolic link, runtimes should // follow the symlink and mount the real destination to container. HostPath string // If set, the mount is read-only. Readonly bool // If set, the mount needs SELinux relabeling. SelinuxRelabel bool // Requested propagation mode. Propagation MountPropagation } // PortMapping specifies a host port mapped into a container port. // In yaml this looks like: // // containerPort: 80 // hostPort: 8000 // listenAddress: 127.0.0.1 // protocol: TCP type PortMapping struct { // Port within the container. ContainerPort int32 // Port on the host. // // If unset, a random port will be selected. // // NOTE: if you set the special value of `-1` then the node backend // (docker, podman...) will be left to pick the port instead. // This is potentially useful for remote hosts, BUT it means when the container // is restarted it will be randomized. Leave this unset to allow kind to pick it. HostPort int32 // TODO: add protocol (tcp/udp) and port-ranges ListenAddress string // Protocol (TCP/UDP/SCTP) Protocol PortMappingProtocol } // MountPropagation represents an "enum" for mount propagation options, // see also Mount. type MountPropagation string const ( // MountPropagationNone specifies that no mount propagation // ("private" in Linux terminology). MountPropagationNone MountPropagation = "None" // MountPropagationHostToContainer specifies that mounts get propagated // from the host to the container ("rslave" in Linux). MountPropagationHostToContainer MountPropagation = "HostToContainer" // MountPropagationBidirectional specifies that mounts get propagated from // the host to the container and from the container to the host // ("rshared" in Linux). MountPropagationBidirectional MountPropagation = "Bidirectional" ) // PortMappingProtocol represents an "enum" for port mapping protocol options, // see also PortMapping. type PortMappingProtocol string const ( // PortMappingProtocolTCP specifies TCP protocol PortMappingProtocolTCP PortMappingProtocol = "TCP" // PortMappingProtocolUDP specifies UDP protocol PortMappingProtocolUDP PortMappingProtocol = "UDP" // PortMappingProtocolSCTP specifies SCTP protocol PortMappingProtocolSCTP PortMappingProtocol = "SCTP" ) kind-0.27.0/pkg/internal/apis/config/validate.go000066400000000000000000000214201475376161000214540ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package config import ( "fmt" "net" "regexp" "strings" "sigs.k8s.io/kind/pkg/errors" "sigs.k8s.io/kind/pkg/internal/sets" ) // similar to valid docker container names, but since we will prefix // and suffix this name, we can relax it a little // see NewContext() for usage // https://godoc.org/github.com/docker/docker/daemon/names#pkg-constants var validNameRE = regexp.MustCompile(`^[a-z0-9.-]+$`) // Validate returns a ConfigErrors with an entry for each problem // with the config, or nil if there are none func (c *Cluster) Validate() error { errs := []error{} // validate the name if !validNameRE.MatchString(c.Name) { errs = append(errs, errors.Errorf("'%s' is not a valid cluster name, cluster names must match `%s`", c.Name, validNameRE.String())) } // the api server port only needs checking if we aren't picking a random one // at runtime if c.Networking.APIServerPort != 0 { // validate api server listen port if err := validatePort(c.Networking.APIServerPort); err != nil { errs = append(errs, errors.Wrapf(err, "invalid apiServerPort")) } } // ipFamily should be ipv4, ipv6, or dual if c.Networking.IPFamily != IPv4Family && c.Networking.IPFamily != IPv6Family && c.Networking.IPFamily != DualStackFamily { errs = append(errs, errors.Errorf("invalid ipFamily: %s", c.Networking.IPFamily)) } // podSubnet should be a valid CIDR if err := validateSubnets(c.Networking.PodSubnet, c.Networking.IPFamily); err != nil { errs = append(errs, errors.Errorf("invalid pod subnet %v", err)) } // serviceSubnet should be a valid CIDR if err := validateSubnets(c.Networking.ServiceSubnet, c.Networking.IPFamily); err != nil { errs = append(errs, errors.Errorf("invalid service subnet %v", err)) } // KubeProxyMode should be iptables or ipvs if c.Networking.KubeProxyMode != IPTablesProxyMode && c.Networking.KubeProxyMode != IPVSProxyMode && c.Networking.KubeProxyMode != NoneProxyMode && c.Networking.KubeProxyMode != NFTablesProxyMode { errs = append(errs, errors.Errorf("invalid kubeProxyMode: %s", c.Networking.KubeProxyMode)) } // validate nodes numByRole := make(map[NodeRole]int32) // All nodes in the config should be valid for i, n := range c.Nodes { // validate the node if err := n.Validate(); err != nil { errs = append(errs, errors.Errorf("invalid configuration for node %d: %v", i, err)) } // update role count if num, ok := numByRole[n.Role]; ok { numByRole[n.Role] = 1 + num } else { numByRole[n.Role] = 1 } } // there must be at least one control plane node numControlPlane, anyControlPlane := numByRole[ControlPlaneRole] if !anyControlPlane || numControlPlane < 1 { errs = append(errs, errors.Errorf("must have at least one %s node", string(ControlPlaneRole))) } if len(errs) > 0 { return errors.NewAggregate(errs) } return nil } // Validate returns a ConfigErrors with an entry for each problem // with the Node, or nil if there are none func (n *Node) Validate() error { errs := []error{} // validate node role should be one of the expected values switch n.Role { case ControlPlaneRole, WorkerRole: default: errs = append(errs, errors.Errorf("%q is not a valid node role", n.Role)) } // image should be defined if n.Image == "" { errs = append(errs, errors.New("image is a required field")) } // validate extra port forwards for _, mapping := range n.ExtraPortMappings { if err := validatePort(mapping.HostPort); err != nil { errs = append(errs, errors.Wrapf(err, "invalid hostPort")) } if err := validatePort(mapping.ContainerPort); err != nil { errs = append(errs, errors.Wrapf(err, "invalid containerPort")) } } if err := validatePortMappings(n.ExtraPortMappings); err != nil { errs = append(errs, errors.Wrapf(err, "invalid portMapping")) } if len(errs) > 0 { return errors.NewAggregate(errs) } return nil } func validatePortMappings(portMappings []PortMapping) error { errMsg := "port mapping with same listen address, port and protocol already configured" wildcardAddrIPv4 := net.ParseIP("0.0.0.0") wildcardAddrIPv6 := net.ParseIP("::") // bindMap has the following key-value structure // PORT/PROTOCOL: [ IP ] // { 80/TCP: [ 127.0.0.1, 192.168.2.3 ], 80/UDP: [ 0.0.0.0 ] } bindMap := make(map[string]sets.String) formatPortProtocol := func(port int32, protocol PortMappingProtocol) string { return fmt.Sprintf("%d/%s", port, protocol) } for _, portMapping := range portMappings { if portMapping.HostPort == -1 || portMapping.HostPort == 0 { // Port -1 and 0 cause a random port to be selected, thus duplicates are allowed continue } addr := net.ParseIP(portMapping.ListenAddress) addrString := addr.String() portProtocol := formatPortProtocol(portMapping.HostPort, portMapping.Protocol) possibleErr := fmt.Errorf("%s: %s:%s", errMsg, addrString, portProtocol) // in golang 0.0.0.0 and [::] are equivalent, convert [::] -> 0.0.0.0 // https://github.com/golang/go/issues/48723 if addr.Equal(wildcardAddrIPv6) { addr = wildcardAddrIPv4 addrString = addr.String() } if _, ok := bindMap[portProtocol]; ok { // wildcard address case: // return error if there already exists any listen address for same port and protocol if addr.Equal(wildcardAddrIPv4) { if bindMap[portProtocol].Len() > 0 { return possibleErr } } // direct duplicate & wild card present check: // return error if same combination of ip, port and protocol already exists in bindMap. // return error if wildcard address is already present for same port & protocol if bindMap[portProtocol].Has(addrString) || bindMap[portProtocol].Has(wildcardAddrIPv4.String()) { return possibleErr } } else { // initialize the set bindMap[portProtocol] = sets.NewString() } // add the entry to bindMap bindMap[portProtocol].Insert(addrString) } return nil } func validatePort(port int32) error { // NOTE: -1 is a special value for auto-selecting the port in the container // backend where possible as opposed to in kind itself. if port < -1 || port > 65535 { return errors.Errorf("invalid port number: %d", port) } return nil } func validateSubnets(subnetStr string, ipFamily ClusterIPFamily) error { allErrs := []error{} cidrsString := strings.Split(subnetStr, ",") subnets := make([]*net.IPNet, 0, len(cidrsString)) for _, cidrString := range cidrsString { _, cidr, err := net.ParseCIDR(cidrString) if err != nil { return fmt.Errorf("failed to parse cidr value:%q with error: %v", cidrString, err) } subnets = append(subnets, cidr) } dualstack := ipFamily == DualStackFamily switch { // if no subnets are defined case len(subnets) == 0: allErrs = append(allErrs, errors.New("no subnets defined")) // if DualStack only 2 CIDRs allowed case dualstack && len(subnets) > 2: allErrs = append(allErrs, errors.New("expected one (IPv4 or IPv6) CIDR or two CIDRs from each family for dual-stack networking")) // if DualStack and there are 2 CIDRs validate if there is at least one of each IP family case dualstack && len(subnets) == 2: areDualStackCIDRs, err := isDualStackCIDRs(subnets) if err != nil { allErrs = append(allErrs, err) } else if !areDualStackCIDRs { allErrs = append(allErrs, errors.New("expected one (IPv4 or IPv6) CIDR or two CIDRs from each family for dual-stack networking")) } // if not DualStack only one CIDR allowed case !dualstack && len(subnets) > 1: allErrs = append(allErrs, errors.New("only one CIDR allowed for single-stack networking")) case ipFamily == IPv4Family && subnets[0].IP.To4() == nil: allErrs = append(allErrs, errors.New("expected IPv4 CIDR for IPv4 family")) case ipFamily == IPv6Family && subnets[0].IP.To4() != nil: allErrs = append(allErrs, errors.New("expected IPv6 CIDR for IPv6 family")) } if len(allErrs) > 0 { return errors.NewAggregate(allErrs) } return nil } // isDualStackCIDRs returns if // - all are valid cidrs // - at least one cidr from each family (v4 or v6) func isDualStackCIDRs(cidrs []*net.IPNet) (bool, error) { v4Found := false v6Found := false for _, cidr := range cidrs { if cidr == nil { return false, fmt.Errorf("cidr %v is invalid", cidr) } if v4Found && v6Found { continue } if cidr.IP != nil && cidr.IP.To4() == nil { v6Found = true continue } v4Found = true } return v4Found && v6Found, nil } kind-0.27.0/pkg/internal/apis/config/validate_test.go000066400000000000000000000341041475376161000225160ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package config import ( "fmt" "sigs.k8s.io/kind/pkg/internal/assert" "testing" "sigs.k8s.io/kind/pkg/errors" ) func TestClusterValidate(t *testing.T) { t.Parallel() cases := []struct { Name string Cluster Cluster ExpectErrors int }{ { Name: "Defaulted", Cluster: func() Cluster { c := Cluster{} SetDefaultsCluster(&c) return c }(), }, { Name: "multiple valid nodes", Cluster: func() Cluster { c := Cluster{} SetDefaultsCluster(&c) c.Nodes = append(c.Nodes, newDefaultedNode(WorkerRole), newDefaultedNode(WorkerRole)) return c }(), }, { Name: "default IPv6", Cluster: func() Cluster { c := Cluster{} c.Networking.IPFamily = IPv6Family SetDefaultsCluster(&c) return c }(), }, { Name: "bogus podSubnet", Cluster: func() Cluster { c := Cluster{} SetDefaultsCluster(&c) c.Networking.PodSubnet = "aa" return c }(), ExpectErrors: 1, }, { Name: "bogus serviceSubnet", Cluster: func() Cluster { c := Cluster{} SetDefaultsCluster(&c) c.Networking.ServiceSubnet = "aa" return c }(), ExpectErrors: 1, }, { Name: "bogus apiServerPort", Cluster: func() Cluster { c := Cluster{} SetDefaultsCluster(&c) c.Networking.APIServerPort = 9999999 return c }(), ExpectErrors: 1, }, { Name: "bogus kubeProxyMode", Cluster: func() Cluster { c := Cluster{} SetDefaultsCluster(&c) c.Networking.KubeProxyMode = "notiptables" return c }(), ExpectErrors: 1, }, { Name: "bogus ipFamily", Cluster: func() Cluster { c := Cluster{} SetDefaultsCluster(&c) c.Networking.IPFamily = "ds" return c }(), ExpectErrors: 1, }, { Name: "bogus serviceSubnet", Cluster: func() Cluster { c := Cluster{} SetDefaultsCluster(&c) c.Networking.ServiceSubnet = "aa" return c }(), ExpectErrors: 1, }, { Name: "invalid number of podSubnet", Cluster: func() Cluster { c := Cluster{} SetDefaultsCluster(&c) c.Networking.PodSubnet = "192.168.0.2/24,2.2.2.0/24" return c }(), ExpectErrors: 1, }, { Name: "valid dual stack podSubnet and serviceSubnet", Cluster: func() Cluster { c := Cluster{} SetDefaultsCluster(&c) c.Networking.PodSubnet = "192.168.0.2/24,fd00:1::/25" c.Networking.ServiceSubnet = "192.168.0.2/24,fd00:1::/25" c.Networking.IPFamily = DualStackFamily return c }(), ExpectErrors: 0, }, { Name: "invalid dual stack podSubnet and multiple serviceSubnet", Cluster: func() Cluster { c := Cluster{} SetDefaultsCluster(&c) c.Networking.PodSubnet = "192.168.0.2/24,fd00:1::/25" c.Networking.ServiceSubnet = "192.168.0.2/24,fd00:1::/25,10.0.0.0/16" c.Networking.IPFamily = DualStackFamily return c }(), ExpectErrors: 1, }, { Name: "valid dual stack podSubnet and single stack serviceSubnet", Cluster: func() Cluster { c := Cluster{} SetDefaultsCluster(&c) c.Networking.PodSubnet = "192.168.0.2/24,fd00:1::/25" c.Networking.ServiceSubnet = "192.168.0.2/24" c.Networking.IPFamily = DualStackFamily return c }(), ExpectErrors: 0, }, { Name: "valid dual stack serviceSubnet and single stack podSubnet", Cluster: func() Cluster { c := Cluster{} SetDefaultsCluster(&c) c.Networking.PodSubnet = "192.168.0.2/24" c.Networking.ServiceSubnet = "192.168.0.2/24,fd00:1::/25" c.Networking.IPFamily = DualStackFamily return c }(), ExpectErrors: 0, }, { Name: "bad dual stack podSubnet and serviceSubnet", Cluster: func() Cluster { c := Cluster{} SetDefaultsCluster(&c) c.Networking.PodSubnet = "192.168.0.2/24,2.2.2.0/25" c.Networking.ServiceSubnet = "192.168.0.2/24,2.2.2.0/25" c.Networking.IPFamily = DualStackFamily return c }(), ExpectErrors: 2, }, { Name: "ipv6 family and ipv4 podSubnet", Cluster: func() Cluster { c := Cluster{} SetDefaultsCluster(&c) c.Networking.PodSubnet = "192.168.0.2/24" c.Networking.ServiceSubnet = "192.168.0.2/24" c.Networking.IPFamily = IPv6Family return c }(), ExpectErrors: 2, }, { Name: "ipv4 family and ipv6 podSubnet", Cluster: func() Cluster { c := Cluster{} SetDefaultsCluster(&c) c.Networking.PodSubnet = "fd00:1::/25" c.Networking.ServiceSubnet = "fd00:1::/25" c.Networking.IPFamily = IPv4Family return c }(), ExpectErrors: 2, }, { // This test validates the empty podsubnet check. It should never happen // in real world since defaulting is happening before the validation step. Name: "no pod subnet", Cluster: func() Cluster { c := Cluster{} SetDefaultsCluster(&c) c.Networking.PodSubnet = "" return c }(), ExpectErrors: 1, }, { // This test validates the empty servicesubnet check. It should never happen // in real world since defaulting is happening before the validation step. Name: "no service subnet", Cluster: func() Cluster { c := Cluster{} SetDefaultsCluster(&c) c.Networking.ServiceSubnet = "" return c }(), ExpectErrors: 1, }, { Name: "missing control-plane", Cluster: func() Cluster { c := Cluster{} SetDefaultsCluster(&c) c.Nodes = []Node{} return c }(), ExpectErrors: 1, }, { Name: "bogus node", Cluster: func() Cluster { c := Cluster{} n, n2 := Node{}, Node{} n.Role = "bogus" c.Nodes = []Node{n, n2} SetDefaultsCluster(&c) return c }(), ExpectErrors: 1, }, } for _, tc := range cases { tc := tc //capture loop variable t.Run(tc.Name, func(t *testing.T) { t.Parallel() err := tc.Cluster.Validate() // the error can be: // - nil, in which case we should expect no errors or fail if err == nil { if tc.ExpectErrors != 0 { t.Error("received no errors but expected errors for case") } return } // get the list of errors errs := errors.Errors(err) if errs == nil { errs = []error{err} } // we expect a certain number of errors if len(errs) != tc.ExpectErrors { t.Errorf("expected %d errors but got len(%v) = %d", tc.ExpectErrors, errs, len(errs)) } }) } } func newDefaultedNode(role NodeRole) Node { n := Node{ Role: role, Image: "myImage:latest", } SetDefaultsNode(&n) return n } func TestNodeValidate(t *testing.T) { t.Parallel() cases := []struct { TestName string Node Node ExpectErrors int }{ { TestName: "Canonical node", Node: newDefaultedNode(ControlPlaneRole), ExpectErrors: 0, }, { TestName: "Canonical node 2", Node: newDefaultedNode(WorkerRole), ExpectErrors: 0, }, { TestName: "Empty image field", Node: func() Node { cfg := newDefaultedNode(ControlPlaneRole) cfg.Image = "" return cfg }(), ExpectErrors: 1, }, { TestName: "Empty role field", Node: func() Node { cfg := newDefaultedNode(ControlPlaneRole) cfg.Role = "" return cfg }(), ExpectErrors: 1, }, { TestName: "Unknown role field", Node: func() Node { cfg := newDefaultedNode(ControlPlaneRole) cfg.Role = "ssss" return cfg }(), ExpectErrors: 1, }, { TestName: "Invalid ContainerPort", Node: func() Node { cfg := newDefaultedNode(ControlPlaneRole) cfg.ExtraPortMappings = []PortMapping{ { ContainerPort: 999999999, HostPort: 8080, }, } return cfg }(), ExpectErrors: 1, }, { TestName: "Invalid HostPort", Node: func() Node { cfg := newDefaultedNode(ControlPlaneRole) cfg.ExtraPortMappings = []PortMapping{ { ContainerPort: 8080, HostPort: 999999999, }, } return cfg }(), ExpectErrors: 1, }, { TestName: "Multiple random HostPort", Node: func() Node { cfg := newDefaultedNode(ControlPlaneRole) cfg.ExtraPortMappings = []PortMapping{ { ContainerPort: 80, }, { ContainerPort: 443, }, } return cfg }(), ExpectErrors: 0, }, { TestName: "Multiple random -1 HostPort", Node: func() Node { cfg := newDefaultedNode(ControlPlaneRole) cfg.ExtraPortMappings = []PortMapping{ { ContainerPort: 80, HostPort: -1, }, { ContainerPort: 443, HostPort: -1, }, } return cfg }(), ExpectErrors: 0, }, } for _, tc := range cases { tc := tc //capture loop variable t.Run(tc.TestName, func(t *testing.T) { t.Parallel() err := tc.Node.Validate() // the error can be: // - nil, in which case we should expect no errors or fail if err == nil { if tc.ExpectErrors != 0 { t.Error("received no errors but expected errors for case") } return } // get the list of errors errs := errors.Errors(err) if errs == nil { errs = []error{err} } // we expect a certain number of errors if len(errs) != tc.ExpectErrors { t.Errorf("expected %d errors but got len(%v) = %d", tc.ExpectErrors, errs, len(errs)) } }) } } func TestPortValidate(t *testing.T) { cases := []struct { TestName string Port int32 ExpectError string }{ { TestName: "-1 port", Port: -1, ExpectError: "", }, { TestName: "valid port", Port: 10, ExpectError: "", }, { TestName: "negative port", Port: -2, ExpectError: "invalid port number: -2", }, { TestName: "extra port", Port: 65536, ExpectError: "invalid port number: 65536", }, } for _, tc := range cases { tc := tc //capture loop variable t.Run(tc.TestName, func(t *testing.T) { t.Parallel() err := validatePort(tc.Port) // the error can be: // - nil, in which case we should expect no errors or fail if err == nil && len(tc.ExpectError) > 0 { t.Errorf("Test failed, unexpected error: %s", tc.ExpectError) } if err != nil && err.Error() != tc.ExpectError { t.Errorf("Test failed, error: %s expected error: %s", err, tc.ExpectError) } }) } } func TestValidatePortMappings(t *testing.T) { newPortMapping := func(addr string, port int, protocol string) PortMapping { return PortMapping{ HostPort: int32(port), ListenAddress: addr, Protocol: PortMappingProtocol(protocol), } } errMsg := "port mapping with same listen address, port and protocol already configured" cases := []struct { testName string portMappings []PortMapping expectErr string }{ { testName: "unique port mappings ipv4", portMappings: []PortMapping{ newPortMapping("127.0.0.1", 80, "UDP"), newPortMapping("127.0.0.1", 80, "TCP"), newPortMapping("0.0.0.0", 3000, "UDP"), newPortMapping("0.0.0.0", 5000, "TCP"), }, expectErr: "", }, { testName: "unique port mappings ipv6", portMappings: []PortMapping{ newPortMapping("::1", 80, "UDP"), newPortMapping("::1", 80, "TCP"), newPortMapping("1e3d:6e85:424d:a011:a72e:9780:5f6f:a6fc", 3000, "UDP"), newPortMapping("6516:944d:e070:a1d1:2e91:8437:a6b3:edf9", 5000, "TCP"), }, expectErr: "", }, { testName: "exact duplicate port mappings ipv4", portMappings: []PortMapping{ newPortMapping("127.0.0.1", 80, "TCP"), newPortMapping("127.0.0.1", 80, "UDP"), newPortMapping("127.0.0.1", 80, "TCP"), }, // error expected: exact duplicate expectErr: fmt.Sprintf("%s: 127.0.0.1:80/TCP", errMsg), }, { testName: "exact duplicate port mappings ipv6", portMappings: []PortMapping{ newPortMapping("::1", 80, "TCP"), newPortMapping("::1", 80, "UDP"), newPortMapping("::1", 80, "TCP"), }, // error expected: exact duplicate expectErr: fmt.Sprintf("%s: [::1]:80/TCP", errMsg), }, { testName: "wildcard ipv4 & ipv6", portMappings: []PortMapping{ newPortMapping("127.0.0.1", 80, "TCP"), newPortMapping("0.0.0.0", 80, "UDP"), newPortMapping("::1", 80, "TCP"), newPortMapping("::", 80, "UDP"), }, // error expected: 0.0.0.0 & [::] are same in golang // https://github.com/golang/go/issues/48723 expectErr: fmt.Sprintf("%s: [::]:80/UDP", errMsg), }, { testName: "subset already configured ipv4", portMappings: []PortMapping{ newPortMapping("127.0.0.1", 80, "TCP"), newPortMapping("0.0.0.0", 80, "TCP"), }, // error expected: subset of 0.0.0.0 -> 127.0.0.1 is already defined for same port and protocol expectErr: fmt.Sprintf("%s: 0.0.0.0:80/TCP", errMsg), }, { testName: "subset already configured ipv6", portMappings: []PortMapping{ newPortMapping("::1", 80, "TCP"), newPortMapping("::", 80, "TCP"), }, // error expected: subset of :: -> ::1 is already defined for same port and protocol expectErr: fmt.Sprintf("%s: [::]:80/TCP", errMsg), }, { testName: "port mapping already configured via wildcard ipv4", portMappings: []PortMapping{ newPortMapping("0.0.0.0", 80, "TCP"), newPortMapping("127.0.0.1", 80, "TCP"), }, // error expected: port mapping is already defined for wildcard interface - 0.0.0.0 expectErr: fmt.Sprintf("%s: 127.0.0.1:80/TCP", errMsg), }, { testName: "port mapping already configured via wildcard ipv6", portMappings: []PortMapping{ newPortMapping("::", 80, "SCTP"), newPortMapping("::1", 80, "SCTP"), }, // error expected: port mapping is already defined for wildcard interface - :: expectErr: fmt.Sprintf("%s: [::1]:80/SCTP", errMsg), }, } for _, tc := range cases { tc := tc //capture loop variable t.Run(tc.testName, func(t *testing.T) { t.Parallel() err := validatePortMappings(tc.portMappings) assert.ExpectError(t, len(tc.expectErr) > 0, err) }) } } kind-0.27.0/pkg/internal/apis/config/zz_generated.deepcopy.go000066400000000000000000000125461475376161000241640ustar00rootroot00000000000000//go:build !ignore_autogenerated // +build !ignore_autogenerated /* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Code generated by deepcopy-gen. DO NOT EDIT. package config // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Cluster) DeepCopyInto(out *Cluster) { *out = *in if in.Nodes != nil { in, out := &in.Nodes, &out.Nodes *out = make([]Node, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } in.Networking.DeepCopyInto(&out.Networking) if in.FeatureGates != nil { in, out := &in.FeatureGates, &out.FeatureGates *out = make(map[string]bool, len(*in)) for key, val := range *in { (*out)[key] = val } } if in.RuntimeConfig != nil { in, out := &in.RuntimeConfig, &out.RuntimeConfig *out = make(map[string]string, len(*in)) for key, val := range *in { (*out)[key] = val } } if in.KubeadmConfigPatches != nil { in, out := &in.KubeadmConfigPatches, &out.KubeadmConfigPatches *out = make([]string, len(*in)) copy(*out, *in) } if in.KubeadmConfigPatchesJSON6902 != nil { in, out := &in.KubeadmConfigPatchesJSON6902, &out.KubeadmConfigPatchesJSON6902 *out = make([]PatchJSON6902, len(*in)) copy(*out, *in) } if in.ContainerdConfigPatches != nil { in, out := &in.ContainerdConfigPatches, &out.ContainerdConfigPatches *out = make([]string, len(*in)) copy(*out, *in) } if in.ContainerdConfigPatchesJSON6902 != nil { in, out := &in.ContainerdConfigPatchesJSON6902, &out.ContainerdConfigPatchesJSON6902 *out = make([]string, len(*in)) copy(*out, *in) } return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Cluster. func (in *Cluster) DeepCopy() *Cluster { if in == nil { return nil } out := new(Cluster) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Mount) DeepCopyInto(out *Mount) { *out = *in return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Mount. func (in *Mount) DeepCopy() *Mount { if in == nil { return nil } out := new(Mount) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Networking) DeepCopyInto(out *Networking) { *out = *in if in.DNSSearch != nil { in, out := &in.DNSSearch, &out.DNSSearch *out = new([]string) if **in != nil { in, out := *in, *out *out = make([]string, len(*in)) copy(*out, *in) } } return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Networking. func (in *Networking) DeepCopy() *Networking { if in == nil { return nil } out := new(Networking) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Node) DeepCopyInto(out *Node) { *out = *in if in.Labels != nil { in, out := &in.Labels, &out.Labels *out = make(map[string]string, len(*in)) for key, val := range *in { (*out)[key] = val } } if in.ExtraMounts != nil { in, out := &in.ExtraMounts, &out.ExtraMounts *out = make([]Mount, len(*in)) copy(*out, *in) } if in.ExtraPortMappings != nil { in, out := &in.ExtraPortMappings, &out.ExtraPortMappings *out = make([]PortMapping, len(*in)) copy(*out, *in) } if in.KubeadmConfigPatches != nil { in, out := &in.KubeadmConfigPatches, &out.KubeadmConfigPatches *out = make([]string, len(*in)) copy(*out, *in) } if in.KubeadmConfigPatchesJSON6902 != nil { in, out := &in.KubeadmConfigPatchesJSON6902, &out.KubeadmConfigPatchesJSON6902 *out = make([]PatchJSON6902, len(*in)) copy(*out, *in) } return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Node. func (in *Node) DeepCopy() *Node { if in == nil { return nil } out := new(Node) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PatchJSON6902) DeepCopyInto(out *PatchJSON6902) { *out = *in return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PatchJSON6902. func (in *PatchJSON6902) DeepCopy() *PatchJSON6902 { if in == nil { return nil } out := new(PatchJSON6902) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PortMapping) DeepCopyInto(out *PortMapping) { *out = *in return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PortMapping. func (in *PortMapping) DeepCopy() *PortMapping { if in == nil { return nil } out := new(PortMapping) in.DeepCopyInto(out) return out } kind-0.27.0/pkg/internal/assert/000077500000000000000000000000001475376161000164355ustar00rootroot00000000000000kind-0.27.0/pkg/internal/assert/assert.go000066400000000000000000000036471475376161000202770ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package assert import "reflect" // *testing.T methods used by assert type testingDotT interface { Errorf(format string, args ...interface{}) } // ExpectError will call t.Errorf if expectError != (err == nil) // t should be a *testing.T normally func ExpectError(t testingDotT, expectError bool, err error) { if err != nil && !expectError { t.Errorf("Did not expect error: %v", err) } if err == nil && expectError { t.Errorf("Expected error but got none") } } // BoolEqual will call t.Errorf if expected != result // t should be a *testing.T normally func BoolEqual(t testingDotT, expected, result bool) { if expected != result { t.Errorf("Result did not match!") t.Errorf("Expected: %v", expected) t.Errorf("But got: %v", result) } } // StringEqual will call t.Errorf if expected != result // t should be a *testing.T normally func StringEqual(t testingDotT, expected, result string) { if expected != result { t.Errorf("Strings did not match!") t.Errorf("Expected: %q", expected) t.Errorf("But got: %q", result) } } // DeepEqual will call t.Errorf if !reflect.DeepEqual(expected, result) // t should be a *testing.T normally func DeepEqual(t testingDotT, expected, result interface{}) { if !reflect.DeepEqual(expected, result) { t.Errorf("Result did not DeepEqual Expected!") t.Errorf("Expected: %+v", expected) t.Errorf("But got: %+v", result) } } kind-0.27.0/pkg/internal/assert/assert_test.go000066400000000000000000000055461475376161000213360ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package assert import ( "fmt" "testing" ) // fakeT is a fake testing.T that tracks calls to Errorf type fakeT int func (t *fakeT) Errorf(format string, args ...interface{}) { *t++ } func TestExpectError(t *testing.T) { t.Parallel() t.Run("expect error, nil error", func(t *testing.T) { t.Parallel() var f fakeT ExpectError(&f, true, nil) if int(f) == 0 { t.Fatalf("Expected t.Errorf to be called but it was not") } }) t.Run("expect error, have error", func(t *testing.T) { t.Parallel() var f fakeT ExpectError(&f, true, fmt.Errorf("heh")) if int(f) != 0 { t.Fatalf("Expected t.Errorf not to be called but it was") } }) t.Run("do not expect error, nil error", func(t *testing.T) { t.Parallel() var f fakeT ExpectError(&f, false, nil) if int(f) != 0 { t.Fatalf("Expected t.Errorf not to be called but it was") } }) t.Run("do not expect error, have error", func(t *testing.T) { t.Parallel() var f fakeT ExpectError(&f, false, fmt.Errorf("heh")) if int(f) == 0 { t.Fatalf("Expected t.Errorf to be called but it was not") } }) } func TestBoolEqual(t *testing.T) { t.Parallel() t.Run("not equal", func(t *testing.T) { t.Parallel() var f fakeT BoolEqual(&f, true, false) if int(f) == 0 { t.Fatalf("Expected t.Errorf to be called but it was not") } }) t.Run("equal", func(t *testing.T) { t.Parallel() var f fakeT BoolEqual(&f, true, true) if int(f) != 0 { t.Fatalf("Expected t.Errorf not to be called but it was") } }) } func TestStringEqual(t *testing.T) { t.Parallel() t.Run("not equal", func(t *testing.T) { t.Parallel() var f fakeT StringEqual(&f, "a", "b") if int(f) == 0 { t.Fatalf("Expected t.Errorf to be called but it was not") } }) t.Run("equal", func(t *testing.T) { t.Parallel() var f fakeT StringEqual(&f, "a", "a") if int(f) != 0 { t.Fatalf("Expected t.Errorf not to be called but it was") } }) } func TestDeepEqual(t *testing.T) { t.Parallel() t.Run("not equal", func(t *testing.T) { t.Parallel() var f fakeT DeepEqual(&f, "a", "b") if int(f) == 0 { t.Fatalf("Expected t.Errorf to be called but it was not") } }) t.Run("equal", func(t *testing.T) { t.Parallel() var f fakeT DeepEqual(&f, f, f) if int(f) != 0 { t.Fatalf("Expected t.Errorf not to be called but it was") } }) } kind-0.27.0/pkg/internal/cli/000077500000000000000000000000001475376161000157035ustar00rootroot00000000000000kind-0.27.0/pkg/internal/cli/logger.go000066400000000000000000000141021475376161000175070ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package cli import ( "bytes" "fmt" "io" "runtime" "strings" "sync" "sync/atomic" "sigs.k8s.io/kind/pkg/log" "sigs.k8s.io/kind/pkg/internal/env" ) // Logger is the kind cli's log.Logger implementation type Logger struct { writer io.Writer writerMu sync.Mutex verbosity log.Level bufferPool *bufferPool // kind special additions isSmartWriter bool } var _ log.Logger = &Logger{} // NewLogger returns a new Logger with the given verbosity func NewLogger(writer io.Writer, verbosity log.Level) *Logger { l := &Logger{ verbosity: verbosity, bufferPool: newBufferPool(), } l.SetWriter(writer) return l } // SetWriter sets the output writer func (l *Logger) SetWriter(w io.Writer) { l.writerMu.Lock() defer l.writerMu.Unlock() l.writer = w _, isSpinner := w.(*Spinner) l.isSmartWriter = isSpinner || env.IsSmartTerminal(w) } // ColorEnabled returns true if the caller is OK to write colored output func (l *Logger) ColorEnabled() bool { l.writerMu.Lock() defer l.writerMu.Unlock() return l.isSmartWriter } func (l *Logger) getVerbosity() log.Level { return log.Level(atomic.LoadInt32((*int32)(&l.verbosity))) } // SetVerbosity sets the loggers verbosity func (l *Logger) SetVerbosity(verbosity log.Level) { atomic.StoreInt32((*int32)(&l.verbosity), int32(verbosity)) } // synchronized write to the inner writer func (l *Logger) write(p []byte) (n int, err error) { l.writerMu.Lock() defer l.writerMu.Unlock() return l.writer.Write(p) } // writeBuffer writes buf with write, ensuring there is a trailing newline func (l *Logger) writeBuffer(buf *bytes.Buffer) { // ensure trailing newline if buf.Len() == 0 || buf.Bytes()[buf.Len()-1] != '\n' { buf.WriteByte('\n') } // TODO: should we handle this somehow?? // Who logs for the logger? 🤔 _, _ = l.write(buf.Bytes()) } // print writes a simple string to the log writer func (l *Logger) print(message string) { buf := bytes.NewBufferString(message) l.writeBuffer(buf) } // printf is roughly fmt.Fprintf against the log writer func (l *Logger) printf(format string, args ...interface{}) { buf := l.bufferPool.Get() fmt.Fprintf(buf, format, args...) l.writeBuffer(buf) l.bufferPool.Put(buf) } // addDebugHeader inserts the debug line header to buf func addDebugHeader(buf *bytes.Buffer) { _, file, line, ok := runtime.Caller(3) // lifted from klog if !ok { file = "???" line = 1 } else { if slash := strings.LastIndex(file, "/"); slash >= 0 { path := file file = path[slash+1:] if dirsep := strings.LastIndex(path[:slash], "/"); dirsep >= 0 { file = path[dirsep+1:] } } } buf.Grow(len(file) + 11) // we know at least this many bytes are needed buf.WriteString("DEBUG: ") buf.WriteString(file) buf.WriteByte(':') fmt.Fprintf(buf, "%d", line) buf.WriteByte(']') buf.WriteByte(' ') } // debug is like print but with a debug log header func (l *Logger) debug(message string) { buf := l.bufferPool.Get() addDebugHeader(buf) buf.WriteString(message) l.writeBuffer(buf) l.bufferPool.Put(buf) } // debugf is like printf but with a debug log header func (l *Logger) debugf(format string, args ...interface{}) { buf := l.bufferPool.Get() addDebugHeader(buf) fmt.Fprintf(buf, format, args...) l.writeBuffer(buf) l.bufferPool.Put(buf) } // Warn is part of the log.Logger interface func (l *Logger) Warn(message string) { l.print(message) } // Warnf is part of the log.Logger interface func (l *Logger) Warnf(format string, args ...interface{}) { l.printf(format, args...) } // Error is part of the log.Logger interface func (l *Logger) Error(message string) { l.print(message) } // Errorf is part of the log.Logger interface func (l *Logger) Errorf(format string, args ...interface{}) { l.printf(format, args...) } // V is part of the log.Logger interface func (l *Logger) V(level log.Level) log.InfoLogger { return infoLogger{ logger: l, level: level, enabled: level <= l.getVerbosity(), } } // infoLogger implements log.InfoLogger for Logger type infoLogger struct { logger *Logger level log.Level enabled bool } // Enabled is part of the log.InfoLogger interface func (i infoLogger) Enabled() bool { return i.enabled } // Info is part of the log.InfoLogger interface func (i infoLogger) Info(message string) { if !i.enabled { return } // for > 0, we are writing debug messages, include extra info if i.level > 0 { i.logger.debug(message) } else { i.logger.print(message) } } // Infof is part of the log.InfoLogger interface func (i infoLogger) Infof(format string, args ...interface{}) { if !i.enabled { return } // for > 0, we are writing debug messages, include extra info if i.level > 0 { i.logger.debugf(format, args...) } else { i.logger.printf(format, args...) } } // bufferPool is a type safe sync.Pool of *byte.Buffer, guaranteed to be Reset type bufferPool struct { sync.Pool } // newBufferPool returns a new bufferPool func newBufferPool() *bufferPool { return &bufferPool{ sync.Pool{ New: func() interface{} { // The Pool's New function should generally only return pointer // types, since a pointer can be put into the return interface // value without an allocation: return new(bytes.Buffer) }, }, } } // Get obtains a buffer from the pool func (b *bufferPool) Get() *bytes.Buffer { return b.Pool.Get().(*bytes.Buffer) } // Put returns a buffer to the pool, resetting it first func (b *bufferPool) Put(x *bytes.Buffer) { // only store small buffers to avoid pointless allocation // avoid keeping arbitrarily large buffers if x.Len() > 256 { return } x.Reset() b.Pool.Put(x) } kind-0.27.0/pkg/internal/cli/override.go000066400000000000000000000006221475376161000200510ustar00rootroot00000000000000package cli import ( "os" "github.com/spf13/pflag" ) // OverrideDefaultName conditionally allows overriding the default cluster name // by setting the KIND_CLUSTER_NAME environment variable // only if --name wasn't set explicitly func OverrideDefaultName(fs *pflag.FlagSet) { if !fs.Changed("name") { if name := os.Getenv("KIND_CLUSTER_NAME"); name != "" { _ = fs.Set("name", name) } } } kind-0.27.0/pkg/internal/cli/spinner.go000066400000000000000000000103131475376161000177060ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package cli import ( "fmt" "io" "runtime" "sync" "time" ) // custom CLI loading spinner for kind var spinnerFrames = []string{ "⠈⠁", "⠈⠑", "⠈⠱", "⠈⡱", "⢀⡱", "⢄⡱", "⢄⡱", "⢆⡱", "⢎⡱", "⢎⡰", "⢎⡠", "⢎⡀", "⢎⠁", "⠎⠁", "⠊⠁", } // Spinner is a simple and efficient CLI loading spinner used by kind // It is simplistic and assumes that the line length will not change. type Spinner struct { stop chan struct{} // signals writer goroutine to stop from Stop() stopped chan struct{} // signals Stop() that the writer goroutine stopped mu *sync.Mutex // protects the mutable bits // below are protected by mu running bool writer io.Writer ticker *time.Ticker // signals that it is time to write a frame prefix string suffix string // format string used to write a frame, depends on the host OS / terminal frameFormat string } // spinner implements writer var _ io.Writer = &Spinner{} // NewSpinner initializes and returns a new Spinner that will write to w // NOTE: w should be os.Stderr or similar, and it should be a Terminal func NewSpinner(w io.Writer) *Spinner { frameFormat := "\x1b[?7l\r%s%s%s\x1b[?7h" // toggling wrapping seems to behave poorly on windows // in general only the simplest escape codes behave well at the moment, // and only in newer shells if runtime.GOOS == "windows" { frameFormat = "\r%s%s%s" } return &Spinner{ stop: make(chan struct{}, 1), stopped: make(chan struct{}), mu: &sync.Mutex{}, writer: w, frameFormat: frameFormat, } } // SetPrefix sets the prefix to print before the spinner func (s *Spinner) SetPrefix(prefix string) { s.mu.Lock() defer s.mu.Unlock() s.prefix = prefix } // SetSuffix sets the suffix to print after the spinner func (s *Spinner) SetSuffix(suffix string) { s.mu.Lock() defer s.mu.Unlock() s.suffix = suffix } // Start starts the spinner running func (s *Spinner) Start() { s.mu.Lock() defer s.mu.Unlock() // don't start if we've already started if s.running { return } // flag that we've started s.running = true // start / create a frame ticker s.ticker = time.NewTicker(time.Millisecond * 100) // spin in the background go func() { // write frames forever (until signaled to stop) for { for _, frame := range spinnerFrames { select { // prefer stopping, select this signal first case <-s.stop: func() { s.mu.Lock() defer s.mu.Unlock() s.ticker.Stop() // free up the ticker s.running = false // mark as stopped (it's fine to start now) s.stopped <- struct{}{} // tell Stop() that we're done }() return // ... and stop // otherwise continue and write one frame case <-s.ticker.C: func() { s.mu.Lock() defer s.mu.Unlock() fmt.Fprintf(s.writer, s.frameFormat, s.prefix, frame, s.suffix) }() } } } }() } // Stop signals the spinner to stop func (s *Spinner) Stop() { s.mu.Lock() if !s.running { s.mu.Unlock() return } // try to stop, do nothing if channel is full (IE already busy stopping) s.stop <- struct{}{} s.mu.Unlock() // wait for stop to be finished <-s.stopped } // Write implements io.Writer, interrupting the spinner and writing to // the inner writer func (s *Spinner) Write(p []byte) (n int, err error) { // lock first, so nothing else can start writing until we are done s.mu.Lock() defer s.mu.Unlock() // it the spinner is not running, just write directly if !s.running { return s.writer.Write(p) } // otherwise: we will rewrite the line first if _, err := s.writer.Write([]byte("\r")); err != nil { return 0, err } return s.writer.Write(p) } kind-0.27.0/pkg/internal/cli/status.go000066400000000000000000000044541475376161000175640ustar00rootroot00000000000000/* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package cli import ( "fmt" "sigs.k8s.io/kind/pkg/log" ) // Status is used to track ongoing status in a CLI, with a nice loading spinner // when attached to a terminal type Status struct { spinner *Spinner status string logger log.Logger // for controlling coloring etc successFormat string failureFormat string } // StatusForLogger returns a new status object for the logger l, // if l is the kind cli logger and the writer is a Spinner, that spinner // will be used for the status func StatusForLogger(l log.Logger) *Status { s := &Status{ logger: l, successFormat: " ✓ %s\n", failureFormat: " ✗ %s\n", } // if we're using the CLI logger, check for if it has a spinner setup // and wire the status to that if v, ok := l.(*Logger); ok { if v2, ok := v.writer.(*Spinner); ok { s.spinner = v2 // use colored success / failure messages s.successFormat = " \x1b[32m✓\x1b[0m %s\n" s.failureFormat = " \x1b[31m✗\x1b[0m %s\n" } } return s } // Start starts a new phase of the status, if attached to a terminal // there will be a loading spinner with this status func (s *Status) Start(status string) { s.End(true) // set new status s.status = status if s.spinner != nil { s.spinner.SetSuffix(fmt.Sprintf(" %s ", s.status)) s.spinner.Start() } else { s.logger.V(0).Infof(" • %s ...\n", s.status) } } // End completes the current status, ending any previous spinning and // marking the status as success or failure func (s *Status) End(success bool) { if s.status == "" { return } if s.spinner != nil { s.spinner.Stop() fmt.Fprint(s.spinner.writer, "\r") } if success { s.logger.V(0).Infof(s.successFormat, s.status) } else { s.logger.V(0).Infof(s.failureFormat, s.status) } s.status = "" } kind-0.27.0/pkg/internal/env/000077500000000000000000000000001475376161000157245ustar00rootroot00000000000000kind-0.27.0/pkg/internal/env/term.go000066400000000000000000000055641475376161000172340ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package env import ( "io" "os" "runtime" isatty "github.com/mattn/go-isatty" ) // a fake TTY type for testing that can only be implemented within this package type isTestFakeTTY interface { isTestFakeTTY() } // IsTerminal returns true if the writer w is a terminal func IsTerminal(w io.Writer) bool { // check for internal fake type we can use for testing. if _, ok := (w).(isTestFakeTTY); ok { return true } // check for real terminals if v, ok := (w).(*os.File); ok { return isatty.IsTerminal(v.Fd()) } return false } // IsSmartTerminal returns true if the writer w is a terminal AND // we think that the terminal is smart enough to use VT escape codes etc. func IsSmartTerminal(w io.Writer) bool { return isSmartTerminal(w, runtime.GOOS, os.LookupEnv) } func isSmartTerminal(w io.Writer, GOOS string, lookupEnv func(string) (string, bool)) bool { // Not smart if it's not a tty if !IsTerminal(w) { return false } // getenv helper for when we only care about the value getenv := func(e string) string { v, _ := lookupEnv(e) return v } // Explicit request for no ANSI escape codes // https://no-color.org/ if _, set := lookupEnv("NO_COLOR"); set { return false } // Explicitly dumb terminals are not smart // https://en.wikipedia.org/wiki/Computer_terminal#Dumb_terminals term := getenv("TERM") if term == "dumb" { return false } // st has some bug 🤷‍♂️ // https://github.com/kubernetes-sigs/kind/issues/1892 if term == "st-256color" { return false } // On Windows WT_SESSION is set by the modern terminal component. // Older terminals have poor support for UTF-8, VT escape codes, etc. if GOOS == "windows" && getenv("WT_SESSION") == "" { return false } /* CI Systems with bad Fake TTYs */ // Travis CI // https://github.com/kubernetes-sigs/kind/issues/1478 // We can detect it with documented magical environment variables // https://docs.travis-ci.com/user/environment-variables/#default-environment-variables if getenv("HAS_JOSH_K_SEAL_OF_APPROVAL") == "true" && getenv("TRAVIS") == "true" { return false } // OK, we'll assume it's smart now, given no evidence otherwise. return true } // trivial fake TTY writer for testing type testFakeTTY struct{} func (t *testFakeTTY) Write(p []byte) (int, error) { return len(p), nil } func (t *testFakeTTY) isTestFakeTTY() {} kind-0.27.0/pkg/internal/env/term_test.go000066400000000000000000000062271475376161000202700ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package env import ( "bytes" "io" "os" "testing" "sigs.k8s.io/kind/pkg/internal/assert" ) func TestIsTerminal(t *testing.T) { // test trivial nil case if IsTerminal(nil) { t.Fatalf("IsTerminal should be false for nil Writer") } // test something that isn't even a file var buff bytes.Buffer if IsTerminal(&buff) { t.Fatalf("IsTerminal should be false for bytes.Buffer") } // test a file f, err := os.CreateTemp("", "kind-isterminal") if err != nil { t.Fatalf("Failed to create tempfile %v", err) } if IsTerminal(f) { t.Fatalf("IsTerminal should be false for nil Writer") } // TODO: testing an actual PTY would be somewhat tricky to do cleanly // but we should maybe do this in the future. // At least we know this doesn't trigger on things that are obviously not // terminals if !IsTerminal(&testFakeTTY{}) { t.Fatalf("IsTerminal should be true for testFakeTTY") } } func TestIsSmartTerminal(t *testing.T) { cases := []struct { Name string FakeEnv map[string]string GOOS string Writer io.Writer IsSmart bool }{ { Name: "tty, no env", FakeEnv: map[string]string{}, GOOS: "linux", IsSmart: true, Writer: &testFakeTTY{}, }, { Name: "nil writer, no env", FakeEnv: map[string]string{}, GOOS: "linux", IsSmart: false, }, { Name: "tty, windows, no env", FakeEnv: map[string]string{}, GOOS: "windows", IsSmart: false, Writer: &testFakeTTY{}, }, { Name: "tty, windows, modern terminal env", FakeEnv: map[string]string{ "WT_SESSION": "baz", }, GOOS: "windows", IsSmart: true, Writer: &testFakeTTY{}, }, { Name: "tty, TERM=dumb", FakeEnv: map[string]string{ "TERM": "dumb", }, GOOS: "linux", IsSmart: false, Writer: &testFakeTTY{}, }, { Name: "tty, NO_COLOR=", FakeEnv: map[string]string{ "NO_COLOR": "", }, GOOS: "linux", IsSmart: false, Writer: &testFakeTTY{}, }, { Name: "tty, Travis CI", FakeEnv: map[string]string{ "TRAVIS": "true", "HAS_JOSH_K_SEAL_OF_APPROVAL": "true", }, GOOS: "linux", IsSmart: false, Writer: &testFakeTTY{}, }, { Name: "tty, TERM=st-256color", FakeEnv: map[string]string{ "TERM": "st-256color", }, GOOS: "linux", IsSmart: false, Writer: &testFakeTTY{}, }, } for _, tc := range cases { tc := tc // capture tc t.Run(tc.Name, func(t *testing.T) { res := isSmartTerminal(tc.Writer, tc.GOOS, func(s string) (string, bool) { k, set := tc.FakeEnv[s] return k, set }) assert.BoolEqual(t, tc.IsSmart, res) }) } } kind-0.27.0/pkg/internal/integration/000077500000000000000000000000001475376161000174575ustar00rootroot00000000000000kind-0.27.0/pkg/internal/integration/integration.go000066400000000000000000000017361475376161000223400ustar00rootroot00000000000000/* Copyright 2020 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package integration import "testing" // *testing.T methods used by assert type testingDotT interface { Skip(args ...interface{}) } // MaybeSkip skips if integration tests should be skipped // currently this is when testing.Short() is true // This should be called at the beginning of an integration test func MaybeSkip(t testingDotT) { if testing.Short() { t.Skip("Skipping integration test due to -short") } } kind-0.27.0/pkg/internal/patch/000077500000000000000000000000001475376161000162335ustar00rootroot00000000000000kind-0.27.0/pkg/internal/patch/doc.go000066400000000000000000000012001475376161000173200ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package patch contains helpers for applying patches package patch kind-0.27.0/pkg/internal/patch/json6902patch.go000066400000000000000000000027661475376161000211070ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package patch import ( jsonpatch "github.com/evanphx/json-patch/v5" "sigs.k8s.io/yaml" "sigs.k8s.io/kind/pkg/errors" "sigs.k8s.io/kind/pkg/internal/apis/config" ) type json6902Patch struct { raw string // raw original contents patch jsonpatch.Patch // processed JSON 6902 patch matchInfo matchInfo // used to match resources } func convertJSON6902Patches(patchesJSON6902 []config.PatchJSON6902) ([]json6902Patch, error) { patches := []json6902Patch{} for _, configPatch := range patchesJSON6902 { patchJSON, err := yaml.YAMLToJSON([]byte(configPatch.Patch)) if err != nil { return nil, errors.WithStack(err) } patch, err := jsonpatch.DecodePatch(patchJSON) if err != nil { return nil, errors.WithStack(err) } patches = append(patches, json6902Patch{ raw: configPatch.Patch, patch: patch, matchInfo: matchInfoForConfigJSON6902Patch(configPatch), }) } return patches, nil } kind-0.27.0/pkg/internal/patch/kubeyaml.go000066400000000000000000000050141475376161000203730ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package patch import ( "strings" "sigs.k8s.io/kind/pkg/errors" "sigs.k8s.io/kind/pkg/internal/apis/config" ) // KubeYAML takes a Kubernetes object YAML document stream to patch, // merge patches, and JSON 6902 patches. // // It returns a patched a YAML document stream. // // Matching is performed on Kubernetes style v1 TypeMeta fields // (kind and apiVersion), between the YAML documents and the patches. // // Patches match if their kind and apiVersion match a document, with the exception // that if the patch does not set apiVersion it will be ignored. func KubeYAML(toPatch string, patches []string, patches6902 []config.PatchJSON6902) (string, error) { // pre-process, including splitting up documents etc. resources, err := parseResources(toPatch) if err != nil { return "", errors.Wrap(err, "failed to parse yaml to patch") } mergePatches, err := parseMergePatches(patches) if err != nil { return "", errors.Wrap(err, "failed to parse patches") } json6902patches, err := convertJSON6902Patches(patches6902) if err != nil { return "", errors.Wrap(err, "failed to parse JSON 6902 patches") } // apply patches and build result builder := &strings.Builder{} for i, r := range resources { // apply merge patches for _, p := range mergePatches { if _, err := r.applyMergePatch(p); err != nil { return "", errors.Wrap(err, "failed to apply patch") } } // apply RFC 6902 JSON patches for _, p := range json6902patches { if _, err := r.apply6902Patch(p); err != nil { return "", errors.Wrap(err, "failed to apply JSON 6902 patch") } } // write out result if err := r.encodeTo(builder); err != nil { return "", errors.Wrap(err, "failed to write patched resource") } // write document separator if i+1 < len(resources) { if _, err := builder.WriteString("---\n"); err != nil { return "", errors.Wrap(err, "failed to write document separator") } } } // verify that all patches were used return builder.String(), nil } kind-0.27.0/pkg/internal/patch/kubeyaml_test.go000066400000000000000000000231571475376161000214420ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package patch import ( "testing" "sigs.k8s.io/kind/pkg/internal/apis/config" "sigs.k8s.io/kind/pkg/internal/assert" ) func TestKubeYAML(t *testing.T) { t.Parallel() type testCase struct { Name string ToPatch string Patches []string PatchesJSON6902 []config.PatchJSON6902 ExpectError bool ExpectOutput string } cases := []testCase{ { Name: "kubeadm config no patches", ToPatch: normalKubeadmConfig, ExpectError: false, ExpectOutput: normalKubeadmConfigKustomized, }, { Name: "kubeadm config bogus patches", ToPatch: normalKubeadmConfig, Patches: []string{"b o g u s"}, ExpectError: true, }, { Name: "kubeadm config one merge-patch", ToPatch: normalKubeadmConfig, Patches: []string{trivialPatch}, ExpectError: false, ExpectOutput: normalKubeadmConfigTrivialPatched, }, { Name: "kubeadm config one merge-patch, one 6902 patch", ToPatch: normalKubeadmConfig, Patches: []string{trivialPatch}, PatchesJSON6902: []config.PatchJSON6902{trivialPatch6902}, ExpectError: false, ExpectOutput: normalKubeadmConfigTrivialPatchedAnd6902Patched, }, } for _, tc := range cases { tc := tc // capture test case t.Run(tc.Name, func(t *testing.T) { t.Parallel() out, err := KubeYAML(tc.ToPatch, tc.Patches, tc.PatchesJSON6902) assert.ExpectError(t, tc.ExpectError, err) if err == nil { assert.StringEqual(t, tc.ExpectOutput, out) } }) } } const normalKubeadmConfig = `# config generated by kind apiVersion: kubeadm.k8s.io/v1beta2 kind: ClusterConfiguration metadata: name: config kubernetesVersion: v1.15.3 clusterName: "kind" controlPlaneEndpoint: "192.168.9.3:6443" # on docker for mac we have to expose the api server via port forward, # so we need to ensure the cert is valid for localhost so we can talk # to the cluster after rewriting the kubeconfig to point to localhost apiServer: certSANs: [localhost, "127.0.0.1"] controllerManager: extraArgs: enable-hostpath-provisioner: "true" # configure ipv6 default addresses for IPv6 clusters scheduler: extraArgs: # configure ipv6 default addresses for IPv6 clusters networking: podSubnet: "10.244.0.0/16" serviceSubnet: "10.96.0.0/12" --- apiVersion: kubeadm.k8s.io/v1beta2 kind: InitConfiguration metadata: name: config # we use a well know token for TLS bootstrap bootstrapTokens: - token: "abcdef.0123456789abcdef" # we use a well know port for making the API server discoverable inside docker network. # from the host machine such port will be accessible via a random local port instead. localAPIEndpoint: advertiseAddress: "192.168.9.6" bindPort: 6443 nodeRegistration: criSocket: "/run/containerd/containerd.sock" kubeletExtraArgs: fail-swap-on: "false" node-ip: "192.168.9.6" --- # no-op entry that exists solely so it can be patched apiVersion: kubeadm.k8s.io/v1beta2 kind: JoinConfiguration metadata: name: config controlPlane: localAPIEndpoint: advertiseAddress: "192.168.9.6" bindPort: 6443 nodeRegistration: criSocket: "/run/containerd/containerd.sock" kubeletExtraArgs: fail-swap-on: "false" node-ip: "192.168.9.6" discovery: bootstrapToken: apiServerEndpoint: "192.168.9.3:6443" token: "abcdef.0123456789abcdef" unsafeSkipCAVerification: true --- apiVersion: kubelet.config.k8s.io/v1beta1 kind: KubeletConfiguration metadata: name: config # configure ipv6 addresses in IPv6 mode # disable disk resource management by default # kubelet will see the host disk that the inner container runtime # is ultimately backed by and attempt to recover disk space. we don't want that. imageGCHighThresholdPercent: 100 evictionHard: nodefs.available: "0%" nodefs.inodesFree: "0%" imagefs.available: "0%" --- # no-op entry that exists solely so it can be patched apiVersion: kubeproxy.config.k8s.io/v1alpha1 kind: KubeProxyConfiguration metadata: name: config ---` const normalKubeadmConfigKustomized = `apiServer: certSANs: - localhost - 127.0.0.1 apiVersion: kubeadm.k8s.io/v1beta2 clusterName: kind controlPlaneEndpoint: 192.168.9.3:6443 controllerManager: extraArgs: enable-hostpath-provisioner: "true" kind: ClusterConfiguration kubernetesVersion: v1.15.3 metadata: name: config networking: podSubnet: 10.244.0.0/16 serviceSubnet: 10.96.0.0/12 scheduler: extraArgs: null --- apiVersion: kubeadm.k8s.io/v1beta2 bootstrapTokens: - token: abcdef.0123456789abcdef kind: InitConfiguration localAPIEndpoint: advertiseAddress: 192.168.9.6 bindPort: 6443 metadata: name: config nodeRegistration: criSocket: /run/containerd/containerd.sock kubeletExtraArgs: fail-swap-on: "false" node-ip: 192.168.9.6 --- apiVersion: kubeadm.k8s.io/v1beta2 controlPlane: localAPIEndpoint: advertiseAddress: 192.168.9.6 bindPort: 6443 discovery: bootstrapToken: apiServerEndpoint: 192.168.9.3:6443 token: abcdef.0123456789abcdef unsafeSkipCAVerification: true kind: JoinConfiguration metadata: name: config nodeRegistration: criSocket: /run/containerd/containerd.sock kubeletExtraArgs: fail-swap-on: "false" node-ip: 192.168.9.6 --- apiVersion: kubelet.config.k8s.io/v1beta1 evictionHard: imagefs.available: 0% nodefs.available: 0% nodefs.inodesFree: 0% imageGCHighThresholdPercent: 100 kind: KubeletConfiguration metadata: name: config --- apiVersion: kubeproxy.config.k8s.io/v1alpha1 kind: KubeProxyConfiguration metadata: name: config ` const trivialPatch = ` kind: ClusterConfiguration apiVersion: kubeadm.k8s.io/v1beta2 scheduler: extraArgs: some-extra-arg: the-arg ---- kind: InitConfiguration nodeRegistration: kubeletExtraArgs: "v": "4" "logging-format": "json" ` const normalKubeadmConfigTrivialPatched = `apiServer: certSANs: - localhost - 127.0.0.1 apiVersion: kubeadm.k8s.io/v1beta2 clusterName: kind controlPlaneEndpoint: 192.168.9.3:6443 controllerManager: extraArgs: enable-hostpath-provisioner: "true" kind: ClusterConfiguration kubernetesVersion: v1.15.3 metadata: name: config networking: podSubnet: 10.244.0.0/16 serviceSubnet: 10.96.0.0/12 scheduler: extraArgs: some-extra-arg: the-arg --- apiVersion: kubeadm.k8s.io/v1beta2 bootstrapTokens: - token: abcdef.0123456789abcdef kind: InitConfiguration localAPIEndpoint: advertiseAddress: 192.168.9.6 bindPort: 6443 metadata: name: config nodeRegistration: criSocket: /run/containerd/containerd.sock kubeletExtraArgs: fail-swap-on: "false" logging-format: json node-ip: 192.168.9.6 v: "4" --- apiVersion: kubeadm.k8s.io/v1beta2 controlPlane: localAPIEndpoint: advertiseAddress: 192.168.9.6 bindPort: 6443 discovery: bootstrapToken: apiServerEndpoint: 192.168.9.3:6443 token: abcdef.0123456789abcdef unsafeSkipCAVerification: true kind: JoinConfiguration metadata: name: config nodeRegistration: criSocket: /run/containerd/containerd.sock kubeletExtraArgs: fail-swap-on: "false" node-ip: 192.168.9.6 --- apiVersion: kubelet.config.k8s.io/v1beta1 evictionHard: imagefs.available: 0% nodefs.available: 0% nodefs.inodesFree: 0% imageGCHighThresholdPercent: 100 kind: KubeletConfiguration metadata: name: config --- apiVersion: kubeproxy.config.k8s.io/v1alpha1 kind: KubeProxyConfiguration metadata: name: config ` var trivialPatch6902 = config.PatchJSON6902{ Group: "kubeadm.k8s.io", Version: "v1beta2", Kind: "ClusterConfiguration", Patch: ` - op: add path: /apiServer/certSANs/- value: my-hostname`, } const normalKubeadmConfigTrivialPatchedAnd6902Patched = `apiServer: certSANs: - localhost - 127.0.0.1 - my-hostname apiVersion: kubeadm.k8s.io/v1beta2 clusterName: kind controlPlaneEndpoint: 192.168.9.3:6443 controllerManager: extraArgs: enable-hostpath-provisioner: "true" kind: ClusterConfiguration kubernetesVersion: v1.15.3 metadata: name: config networking: podSubnet: 10.244.0.0/16 serviceSubnet: 10.96.0.0/12 scheduler: extraArgs: some-extra-arg: the-arg --- apiVersion: kubeadm.k8s.io/v1beta2 bootstrapTokens: - token: abcdef.0123456789abcdef kind: InitConfiguration localAPIEndpoint: advertiseAddress: 192.168.9.6 bindPort: 6443 metadata: name: config nodeRegistration: criSocket: /run/containerd/containerd.sock kubeletExtraArgs: fail-swap-on: "false" logging-format: json node-ip: 192.168.9.6 v: "4" --- apiVersion: kubeadm.k8s.io/v1beta2 controlPlane: localAPIEndpoint: advertiseAddress: 192.168.9.6 bindPort: 6443 discovery: bootstrapToken: apiServerEndpoint: 192.168.9.3:6443 token: abcdef.0123456789abcdef unsafeSkipCAVerification: true kind: JoinConfiguration metadata: name: config nodeRegistration: criSocket: /run/containerd/containerd.sock kubeletExtraArgs: fail-swap-on: "false" node-ip: 192.168.9.6 --- apiVersion: kubelet.config.k8s.io/v1beta1 evictionHard: imagefs.available: 0% nodefs.available: 0% nodefs.inodesFree: 0% imageGCHighThresholdPercent: 100 kind: KubeletConfiguration metadata: name: config --- apiVersion: kubeproxy.config.k8s.io/v1alpha1 kind: KubeProxyConfiguration metadata: name: config ` kind-0.27.0/pkg/internal/patch/matchinfo.go000066400000000000000000000026301475376161000205330ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package patch import ( "sigs.k8s.io/yaml" "sigs.k8s.io/kind/pkg/errors" "sigs.k8s.io/kind/pkg/internal/apis/config" ) // we match resources and patches on their v1 TypeMeta type matchInfo struct { Kind string `json:"kind,omitempty"` APIVersion string `json:"apiVersion,omitempty"` } func parseYAMLMatchInfo(raw string) (matchInfo, error) { m := matchInfo{} if err := yaml.Unmarshal([]byte(raw), &m); err != nil { return matchInfo{}, errors.Wrapf(err, "failed to parse type meta for %q", raw) } return m, nil } func matchInfoForConfigJSON6902Patch(patch config.PatchJSON6902) matchInfo { return matchInfo{ Kind: patch.Kind, APIVersion: groupVersionToAPIVersion(patch.Group, patch.Version), } } func groupVersionToAPIVersion(group, version string) string { if group == "" { return version } return group + "/" + version } kind-0.27.0/pkg/internal/patch/mergepatch.go000066400000000000000000000030701475376161000207010ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package patch import ( "sigs.k8s.io/yaml" "sigs.k8s.io/kind/pkg/errors" ) type mergePatch struct { raw string // the original raw data json []byte // the processed data (in JSON form) matchInfo matchInfo // for matching resources } func parseMergePatches(rawPatches []string) ([]mergePatch, error) { patches := []mergePatch{} // split document streams before trying to parse them splitRawPatches := make([]string, 0, len(rawPatches)) for _, raw := range rawPatches { splitRaw, err := splitYAMLDocuments(raw) if err != nil { return nil, err } splitRawPatches = append(splitRawPatches, splitRaw...) } for _, raw := range splitRawPatches { matchInfo, err := parseYAMLMatchInfo(raw) if err != nil { return nil, errors.WithStack(err) } json, err := yaml.YAMLToJSON([]byte(raw)) if err != nil { return nil, errors.WithStack(err) } patches = append(patches, mergePatch{ raw: raw, json: json, matchInfo: matchInfo, }) } return patches, nil } kind-0.27.0/pkg/internal/patch/resource.go000066400000000000000000000075451475376161000204240ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package patch import ( "bufio" "bytes" "io" "strings" jsonpatch "github.com/evanphx/json-patch/v5" "sigs.k8s.io/yaml" "sigs.k8s.io/kind/pkg/errors" ) type resource struct { raw string // the original raw data json []byte // the processed data (in JSON form), may be mutated matchInfo matchInfo // for matching patches } func (r *resource) apply6902Patch(patch json6902Patch) (matches bool, err error) { if !r.matches(patch.matchInfo) { return false, nil } patched, err := patch.patch.Apply(r.json) if err != nil { return true, errors.WithStack(err) } r.json = patched return true, nil } func (r *resource) applyMergePatch(patch mergePatch) (matches bool, err error) { if !r.matches(patch.matchInfo) { return false, nil } patched, err := jsonpatch.MergePatch(r.json, patch.json) if err != nil { return true, errors.WithStack(err) } r.json = patched return true, nil } func (r resource) matches(o matchInfo) bool { m := &r.matchInfo // we require kind to match, but if the patch does not specify // APIVersion we ignore it (eg to allow trivial patches across kubeadm versions) return m.Kind == o.Kind && (o.APIVersion == "" || m.APIVersion == o.APIVersion) } func (r *resource) encodeTo(w io.Writer) error { encoded, err := yaml.JSONToYAML(r.json) if err != nil { return errors.WithStack(err) } if _, err := w.Write(encoded); err != nil { return errors.WithStack(err) } return nil } func parseResources(yamlDocumentStream string) ([]resource, error) { resources := []resource{} documents, err := splitYAMLDocuments(yamlDocumentStream) if err != nil { return nil, err } for _, raw := range documents { matchInfo, err := parseYAMLMatchInfo(raw) if err != nil { return nil, errors.WithStack(err) } json, err := yaml.YAMLToJSON([]byte(raw)) if err != nil { return nil, errors.WithStack(err) } resources = append(resources, resource{ raw: raw, json: json, matchInfo: matchInfo, }) } return resources, nil } func splitYAMLDocuments(yamlDocumentStream string) ([]string, error) { documents := []string{} scanner := bufio.NewScanner(strings.NewReader(yamlDocumentStream)) scanner.Split(splitYAMLDocument) for scanner.Scan() { documents = append(documents, scanner.Text()) } if err := scanner.Err(); err != nil { return nil, errors.Wrap(err, "error splitting documents") } return documents, nil } const yamlSeparator = "\n---" // splitYAMLDocument is a bufio.SplitFunc for splitting YAML streams into individual documents. // this is borrowed from k8s.io/apimachinery/pkg/util/yaml/decoder.go func splitYAMLDocument(data []byte, atEOF bool) (advance int, token []byte, err error) { if atEOF && len(data) == 0 { return 0, nil, nil } sep := len([]byte(yamlSeparator)) if i := bytes.Index(data, []byte(yamlSeparator)); i >= 0 { // We have a potential document terminator i += sep after := data[i:] if len(after) == 0 { // we can't read any more characters if atEOF { return len(data), data[:len(data)-sep], nil } return 0, nil, nil } if j := bytes.IndexByte(after, '\n'); j >= 0 { return i + j + 1, data[0 : i-sep], nil } return 0, nil, nil } // If we're at EOF, we have a final, non-terminated line. Return it. if atEOF { return len(data), data, nil } // Request more data. return 0, nil, nil } kind-0.27.0/pkg/internal/patch/toml.go000066400000000000000000000060531475376161000175410ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package patch import ( "bytes" "encoding/json" burntoml "github.com/BurntSushi/toml" jsonpatch "github.com/evanphx/json-patch/v5" toml "github.com/pelletier/go-toml" yaml "gopkg.in/yaml.v3" "sigs.k8s.io/kind/pkg/errors" ) // TOML patches toPatch with the patches (should be TOML merge patches) and patches6902 (should be JSON 6902 patches) func TOML(toPatch string, patches []string, patches6902 []string) (string, error) { // convert to JSON for patching j, err := tomlToJSON([]byte(toPatch)) if err != nil { return "", err } // apply merge patches for _, patch := range patches { pj, err := tomlToJSON([]byte(patch)) if err != nil { return "", err } patched, err := jsonpatch.MergePatch(j, pj) if err != nil { return "", errors.WithStack(err) } j = patched } // apply JSON 6902 patches for _, patch6902 := range patches6902 { patch, err := jsonpatch.DecodePatch([]byte(patch6902)) if err != nil { return "", errors.WithStack(err) } patched, err := patch.Apply(j) if err != nil { return "", errors.WithStack(err) } j = patched } // convert result back to TOML return jsonToTOMLString(j) } // tomlToJSON converts arbitrary TOML to JSON func tomlToJSON(t []byte) ([]byte, error) { // we use github.com.pelletier/go-toml here to unmarshal arbitrary TOML to JSON tree, err := toml.LoadBytes(t) if err != nil { return nil, errors.WithStack(err) } b, err := json.Marshal(tree.ToMap()) if err != nil { return nil, errors.WithStack(err) } return b, nil } // jsonToTOMLString converts arbitrary JSON to TOML func jsonToTOMLString(j []byte) (string, error) { var unstruct interface{} // We are using yaml.Unmarshal here (instead of json.Unmarshal) because the // Go JSON library doesn't try to pick the right number type (int, float, // etc.) when unmarshalling to interface{}, it just picks float64 // universally. go-yaml does go through the effort of picking the right // number type, so we can preserve number type throughout this process. if err := yaml.Unmarshal(j, &unstruct); err != nil { return "", errors.WithStack(err) } // we use github.com/BurntSushi/toml here because github.com.pelletier/go-toml // can only marshal structs AND BurntSushi/toml is what contained uses // and has more canonically formatted output (we initially plan to use // this package for patching containerd config) var buff bytes.Buffer if err := burntoml.NewEncoder(&buff).Encode(unstruct); err != nil { return "", errors.WithStack(err) } return buff.String(), nil } kind-0.27.0/pkg/internal/patch/toml_test.go000066400000000000000000000125621475376161000206020ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package patch import ( "testing" "sigs.k8s.io/kind/pkg/internal/assert" ) func TestTOML(t *testing.T) { t.Parallel() type testCase struct { Name string ToPatch string Patches []string PatchesJSON6902 []string ExpectError bool ExpectOutput string } cases := []testCase{ { Name: "invalid TOML", ToPatch: `🗿`, ExpectError: true, ExpectOutput: "", }, { Name: "no patches", ToPatch: `disabled_plugins = ["restart"] [plugins.linux] shim_debug = true [plugins.cri.containerd.runtimes.runsc] runtime_type = "io.containerd.runsc.v1"`, ExpectError: false, ExpectOutput: `disabled_plugins = ["restart"] [plugins] [plugins.cri] [plugins.cri.containerd] [plugins.cri.containerd.runtimes] [plugins.cri.containerd.runtimes.runsc] runtime_type = "io.containerd.runsc.v1" [plugins.linux] shim_debug = true `, }, { Name: "invalid patch TOML", ToPatch: `disabled_plugins = ["restart"] [plugins.linux] shim_debug = true [plugins.cri.containerd.runtimes.runsc] runtime_type = "io.containerd.runsc.v1"`, Patches: []string{"🏰"}, ExpectError: true, }, { Name: "invalid 6902 patch JSON", ToPatch: `disabled_plugins = ["restart"] [plugins.linux] shim_debug = true [plugins.cri.containerd.runtimes.runsc] runtime_type = "io.containerd.runsc.v1"`, PatchesJSON6902: []string{"🏰"}, ExpectError: true, }, { Name: "trivial patch", ToPatch: `disabled_plugins = ["restart"] [plugins.linux] shim_debug = true [plugins.cri.containerd.runtimes.runsc] runtime_type = "io.containerd.runsc.v1"`, Patches: []string{`disabled_plugins=[]`}, ExpectError: false, ExpectOutput: `disabled_plugins = [] [plugins] [plugins.cri] [plugins.cri.containerd] [plugins.cri.containerd.runtimes] [plugins.cri.containerd.runtimes.runsc] runtime_type = "io.containerd.runsc.v1" [plugins.linux] shim_debug = true `, }, { Name: "trivial 6902 patch", ToPatch: `disabled_plugins = ["restart"] [plugins.linux] shim_debug = true [plugins.cri.containerd.runtimes.runsc] runtime_type = "io.containerd.runsc.v1"`, PatchesJSON6902: []string{`[{"op": "remove", "path": "/disabled_plugins"}]`}, ExpectError: false, ExpectOutput: `[plugins] [plugins.cri] [plugins.cri.containerd] [plugins.cri.containerd.runtimes] [plugins.cri.containerd.runtimes.runsc] runtime_type = "io.containerd.runsc.v1" [plugins.linux] shim_debug = true `, }, { Name: "trivial patch and trivial 6902 patch", ToPatch: `disabled_plugins = ["restart"] [plugins.linux] shim_debug = true [plugins.cri.containerd.runtimes.runsc] runtime_type = "io.containerd.runsc.v1"`, Patches: []string{`disabled_plugins=["foo"]`}, PatchesJSON6902: []string{`[{"op": "remove", "path": "/disabled_plugins"}]`}, ExpectError: false, ExpectOutput: `[plugins] [plugins.cri] [plugins.cri.containerd] [plugins.cri.containerd.runtimes] [plugins.cri.containerd.runtimes.runsc] runtime_type = "io.containerd.runsc.v1" [plugins.linux] shim_debug = true `, }, { Name: "invalid path 6902 patch", ToPatch: `disabled_plugins = ["restart"] [plugins.linux] shim_debug = true [plugins.cri.containerd.runtimes.runsc] runtime_type = "io.containerd.runsc.v1"`, PatchesJSON6902: []string{`[{"op": "remove", "path": "/fooooooo"}]`}, ExpectError: true, ExpectOutput: `[plugins] [plugins.cri] [plugins.cri.containerd] [plugins.cri.containerd.runtimes] [plugins.cri.containerd.runtimes.runsc] runtime_type = "io.containerd.runsc.v1" [plugins.linux] shim_debug = true `, }, { Name: "patch registry", ToPatch: `disabled_plugins = ["restart"] [plugins.linux] shim_debug = true [plugins.cri.containerd.runtimes.runsc] runtime_type = "io.containerd.runsc.v1"`, Patches: []string{`[plugins.cri.registry.mirrors] [plugins.cri.registry.mirrors."registry:5000"] endpoint = ["http://registry:5000"]`}, ExpectError: false, ExpectOutput: `disabled_plugins = ["restart"] [plugins] [plugins.cri] [plugins.cri.containerd] [plugins.cri.containerd.runtimes] [plugins.cri.containerd.runtimes.runsc] runtime_type = "io.containerd.runsc.v1" [plugins.cri.registry] [plugins.cri.registry.mirrors] [plugins.cri.registry.mirrors."registry:5000"] endpoint = ["http://registry:5000"] [plugins.linux] shim_debug = true `, }, } for _, tc := range cases { tc := tc // capture test case t.Run(tc.Name, func(t *testing.T) { t.Parallel() out, err := TOML(tc.ToPatch, tc.Patches, tc.PatchesJSON6902) assert.ExpectError(t, tc.ExpectError, err) if err == nil { assert.StringEqual(t, tc.ExpectOutput, out) } }) } } kind-0.27.0/pkg/internal/runtime/000077500000000000000000000000001475376161000166175ustar00rootroot00000000000000kind-0.27.0/pkg/internal/runtime/runtime.go000066400000000000000000000014361475376161000206350ustar00rootroot00000000000000package runtime import ( "os" "sigs.k8s.io/kind/pkg/cluster" "sigs.k8s.io/kind/pkg/log" ) // GetDefault selected the default runtime from the environment override func GetDefault(logger log.Logger) cluster.ProviderOption { switch p := os.Getenv("KIND_EXPERIMENTAL_PROVIDER"); p { case "": return nil case "podman": logger.Warn("using podman due to KIND_EXPERIMENTAL_PROVIDER") return cluster.ProviderWithPodman() case "docker": logger.Warn("using docker due to KIND_EXPERIMENTAL_PROVIDER") return cluster.ProviderWithDocker() case "nerdctl", "finch", "nerdctl.lima": logger.Warnf("using %s due to KIND_EXPERIMENTAL_PROVIDER", p) return cluster.ProviderWithNerdctl(p) default: logger.Warnf("ignoring unknown value %q for KIND_EXPERIMENTAL_PROVIDER", p) return nil } } kind-0.27.0/pkg/internal/sets/000077500000000000000000000000001475376161000161125ustar00rootroot00000000000000kind-0.27.0/pkg/internal/sets/doc.go000066400000000000000000000017461475376161000172160ustar00rootroot00000000000000/* Copyright 2021 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package sets implements set types. // // This is forked from k8s.io/apimachinery/pkg/util/sets (under the same project // and license), because k8s.io/apimachinery is a relatively heavy dependency // and we only need some trivial utilities. Avoiding importing k8s.io/apimachinery // makes kind easier to embed in other projects for testing etc. // // The set implementation is relatively small and very stable. package sets kind-0.27.0/pkg/internal/sets/empty.go000066400000000000000000000014741475376161000176050ustar00rootroot00000000000000/* Copyright The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Code generated by set-gen. DO NOT EDIT. package sets // Empty is public since it is used by some internal API objects for conversions between external // string arrays and internal sets, and conversion logic requires public types today. type Empty struct{} kind-0.27.0/pkg/internal/sets/string.go000066400000000000000000000113011475376161000177430ustar00rootroot00000000000000/* Copyright The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Code generated by set-gen. DO NOT EDIT. package sets import ( "reflect" "sort" ) // sets.String is a set of strings, implemented via map[string]struct{} for minimal memory consumption. type String map[string]Empty // NewString creates a String from a list of values. func NewString(items ...string) String { ss := String{} ss.Insert(items...) return ss } // StringKeySet creates a String from a keys of a map[string](? extends interface{}). // If the value passed in is not actually a map, this will panic. func StringKeySet(theMap interface{}) String { v := reflect.ValueOf(theMap) ret := String{} for _, keyValue := range v.MapKeys() { ret.Insert(keyValue.Interface().(string)) } return ret } // Insert adds items to the set. func (s String) Insert(items ...string) String { for _, item := range items { s[item] = Empty{} } return s } // Delete removes all items from the set. func (s String) Delete(items ...string) String { for _, item := range items { delete(s, item) } return s } // Has returns true if and only if item is contained in the set. func (s String) Has(item string) bool { _, contained := s[item] return contained } // HasAll returns true if and only if all items are contained in the set. func (s String) HasAll(items ...string) bool { for _, item := range items { if !s.Has(item) { return false } } return true } // HasAny returns true if any items are contained in the set. func (s String) HasAny(items ...string) bool { for _, item := range items { if s.Has(item) { return true } } return false } // Difference returns a set of objects that are not in s2 // For example: // s1 = {a1, a2, a3} // s2 = {a1, a2, a4, a5} // s1.Difference(s2) = {a3} // s2.Difference(s1) = {a4, a5} func (s String) Difference(s2 String) String { result := NewString() for key := range s { if !s2.Has(key) { result.Insert(key) } } return result } // Union returns a new set which includes items in either s1 or s2. // For example: // s1 = {a1, a2} // s2 = {a3, a4} // s1.Union(s2) = {a1, a2, a3, a4} // s2.Union(s1) = {a1, a2, a3, a4} func (s1 String) Union(s2 String) String { result := NewString() for key := range s1 { result.Insert(key) } for key := range s2 { result.Insert(key) } return result } // Intersection returns a new set which includes the item in BOTH s1 and s2 // For example: // s1 = {a1, a2} // s2 = {a2, a3} // s1.Intersection(s2) = {a2} func (s1 String) Intersection(s2 String) String { var walk, other String result := NewString() if s1.Len() < s2.Len() { walk = s1 other = s2 } else { walk = s2 other = s1 } for key := range walk { if other.Has(key) { result.Insert(key) } } return result } // IsSuperset returns true if and only if s1 is a superset of s2. func (s1 String) IsSuperset(s2 String) bool { for item := range s2 { if !s1.Has(item) { return false } } return true } // Equal returns true if and only if s1 is equal (as a set) to s2. // Two sets are equal if their membership is identical. // (In practice, this means same elements, order doesn't matter) func (s1 String) Equal(s2 String) bool { return len(s1) == len(s2) && s1.IsSuperset(s2) } type sortableSliceOfString []string func (s sortableSliceOfString) Len() int { return len(s) } func (s sortableSliceOfString) Less(i, j int) bool { return lessString(s[i], s[j]) } func (s sortableSliceOfString) Swap(i, j int) { s[i], s[j] = s[j], s[i] } // List returns the contents as a sorted string slice. func (s String) List() []string { res := make(sortableSliceOfString, 0, len(s)) for key := range s { res = append(res, key) } sort.Sort(res) return []string(res) } // UnsortedList returns the slice with contents in random order. func (s String) UnsortedList() []string { res := make([]string, 0, len(s)) for key := range s { res = append(res, key) } return res } // Returns a single element from the set. func (s String) PopAny() (string, bool) { for key := range s { s.Delete(key) return key, true } var zeroValue string return zeroValue, false } // Len returns the size of the set. func (s String) Len() int { return len(s) } func lessString(lhs, rhs string) bool { return lhs < rhs } kind-0.27.0/pkg/internal/version/000077500000000000000000000000001475376161000166215ustar00rootroot00000000000000kind-0.27.0/pkg/internal/version/doc.go000066400000000000000000000015601475376161000177170ustar00rootroot00000000000000/* Copyright 2021 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package version provides utilities for version number comparisons // // This is forked from k8s.io/apimachinery/pkg/util/version to make // kind easier to import (k8s.io/apimachinery/pkg/util/version is a stable, // mature package with no externaldependencies within a large, heavy module) package version kind-0.27.0/pkg/internal/version/version.go000066400000000000000000000224141475376161000206400ustar00rootroot00000000000000/* Copyright 2016 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package version import ( "bytes" "fmt" "regexp" "strconv" "strings" ) // Version is an opaque representation of a version number type Version struct { components []uint semver bool preRelease string buildMetadata string } var ( // versionMatchRE splits a version string into numeric and "extra" parts versionMatchRE = regexp.MustCompile(`^\s*v?([0-9]+(?:\.[0-9]+)*)(.*)*$`) // extraMatchRE splits the "extra" part of versionMatchRE into semver pre-release and build metadata; it does not validate the "no leading zeroes" constraint for pre-release extraMatchRE = regexp.MustCompile(`^(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?\s*$`) ) func parse(str string, semver bool) (*Version, error) { parts := versionMatchRE.FindStringSubmatch(str) if parts == nil { return nil, fmt.Errorf("could not parse %q as version", str) } numbers, extra := parts[1], parts[2] components := strings.Split(numbers, ".") if (semver && len(components) != 3) || (!semver && len(components) < 2) { return nil, fmt.Errorf("illegal version string %q", str) } v := &Version{ components: make([]uint, len(components)), semver: semver, } for i, comp := range components { if (i == 0 || semver) && strings.HasPrefix(comp, "0") && comp != "0" { return nil, fmt.Errorf("illegal zero-prefixed version component %q in %q", comp, str) } num, err := strconv.ParseUint(comp, 10, 0) if err != nil { return nil, fmt.Errorf("illegal non-numeric version component %q in %q: %v", comp, str, err) } v.components[i] = uint(num) } if semver && extra != "" { extraParts := extraMatchRE.FindStringSubmatch(extra) if extraParts == nil { return nil, fmt.Errorf("could not parse pre-release/metadata (%s) in version %q", extra, str) } v.preRelease, v.buildMetadata = extraParts[1], extraParts[2] for _, comp := range strings.Split(v.preRelease, ".") { if _, err := strconv.ParseUint(comp, 10, 0); err == nil { if strings.HasPrefix(comp, "0") && comp != "0" { return nil, fmt.Errorf("illegal zero-prefixed version component %q in %q", comp, str) } } } } return v, nil } // ParseGeneric parses a "generic" version string. The version string must consist of two // or more dot-separated numeric fields (the first of which can't have leading zeroes), // followed by arbitrary uninterpreted data (which need not be separated from the final // numeric field by punctuation). For convenience, leading and trailing whitespace is // ignored, and the version can be preceded by the letter "v". See also ParseSemantic. func ParseGeneric(str string) (*Version, error) { return parse(str, false) } // MustParseGeneric is like ParseGeneric except that it panics on error func MustParseGeneric(str string) *Version { v, err := ParseGeneric(str) if err != nil { panic(err) } return v } // ParseSemantic parses a version string that exactly obeys the syntax and semantics of // the "Semantic Versioning" specification (http://semver.org/) (although it ignores // leading and trailing whitespace, and allows the version to be preceded by "v"). For // version strings that are not guaranteed to obey the Semantic Versioning syntax, use // ParseGeneric. func ParseSemantic(str string) (*Version, error) { return parse(str, true) } // MustParseSemantic is like ParseSemantic except that it panics on error func MustParseSemantic(str string) *Version { v, err := ParseSemantic(str) if err != nil { panic(err) } return v } // Major returns the major release number func (v *Version) Major() uint { return v.components[0] } // Minor returns the minor release number func (v *Version) Minor() uint { return v.components[1] } // Patch returns the patch release number if v is a Semantic Version, or 0 func (v *Version) Patch() uint { if len(v.components) < 3 { return 0 } return v.components[2] } // BuildMetadata returns the build metadata, if v is a Semantic Version, or "" func (v *Version) BuildMetadata() string { return v.buildMetadata } // PreRelease returns the prerelease metadata, if v is a Semantic Version, or "" func (v *Version) PreRelease() string { return v.preRelease } // Components returns the version number components func (v *Version) Components() []uint { return v.components } // WithMajor returns copy of the version object with requested major number func (v *Version) WithMajor(major uint) *Version { result := *v result.components = []uint{major, v.Minor(), v.Patch()} return &result } // WithMinor returns copy of the version object with requested minor number func (v *Version) WithMinor(minor uint) *Version { result := *v result.components = []uint{v.Major(), minor, v.Patch()} return &result } // WithPatch returns copy of the version object with requested patch number func (v *Version) WithPatch(patch uint) *Version { result := *v result.components = []uint{v.Major(), v.Minor(), patch} return &result } // WithPreRelease returns copy of the version object with requested prerelease func (v *Version) WithPreRelease(preRelease string) *Version { result := *v result.components = []uint{v.Major(), v.Minor(), v.Patch()} result.preRelease = preRelease return &result } // WithBuildMetadata returns copy of the version object with requested buildMetadata func (v *Version) WithBuildMetadata(buildMetadata string) *Version { result := *v result.components = []uint{v.Major(), v.Minor(), v.Patch()} result.buildMetadata = buildMetadata return &result } // String converts a Version back to a string; note that for versions parsed with // ParseGeneric, this will not include the trailing uninterpreted portion of the version // number. func (v *Version) String() string { if v == nil { return "" } var buffer bytes.Buffer for i, comp := range v.components { if i > 0 { buffer.WriteString(".") } buffer.WriteString(fmt.Sprintf("%d", comp)) } if v.preRelease != "" { buffer.WriteString("-") buffer.WriteString(v.preRelease) } if v.buildMetadata != "" { buffer.WriteString("+") buffer.WriteString(v.buildMetadata) } return buffer.String() } // compareInternal returns -1 if v is less than other, 1 if it is greater than other, or 0 // if they are equal func (v *Version) compareInternal(other *Version) int { vLen := len(v.components) oLen := len(other.components) for i := 0; i < vLen && i < oLen; i++ { switch { case other.components[i] < v.components[i]: return 1 case other.components[i] > v.components[i]: return -1 } } // If components are common but one has more items and they are not zeros, it is bigger switch { case oLen < vLen && !onlyZeros(v.components[oLen:]): return 1 case oLen > vLen && !onlyZeros(other.components[vLen:]): return -1 } if !v.semver || !other.semver { return 0 } switch { case v.preRelease == "" && other.preRelease != "": return 1 case v.preRelease != "" && other.preRelease == "": return -1 case v.preRelease == other.preRelease: // includes case where both are "" return 0 } vPR := strings.Split(v.preRelease, ".") oPR := strings.Split(other.preRelease, ".") for i := 0; i < len(vPR) && i < len(oPR); i++ { vNum, err := strconv.ParseUint(vPR[i], 10, 0) if err == nil { oNum, err := strconv.ParseUint(oPR[i], 10, 0) if err == nil { switch { case oNum < vNum: return 1 case oNum > vNum: return -1 default: continue } } } if oPR[i] < vPR[i] { return 1 } else if oPR[i] > vPR[i] { return -1 } } switch { case len(oPR) < len(vPR): return 1 case len(oPR) > len(vPR): return -1 } return 0 } // returns false if array contain any non-zero element func onlyZeros(array []uint) bool { for _, num := range array { if num != 0 { return false } } return true } // AtLeast tests if a version is at least equal to a given minimum version. If both // Versions are Semantic Versions, this will use the Semantic Version comparison // algorithm. Otherwise, it will compare only the numeric components, with non-present // components being considered "0" (ie, "1.4" is equal to "1.4.0"). func (v *Version) AtLeast(min *Version) bool { return v.compareInternal(min) != -1 } // LessThan tests if a version is less than a given version. (It is exactly the opposite // of AtLeast, for situations where asking "is v too old?" makes more sense than asking // "is v new enough?".) func (v *Version) LessThan(other *Version) bool { return v.compareInternal(other) == -1 } // Compare compares v against a version string (which will be parsed as either Semantic // or non-Semantic depending on v). On success it returns -1 if v is less than other, 1 if // it is greater than other, or 0 if they are equal. func (v *Version) Compare(other string) (int, error) { ov, err := parse(other, v.semver) if err != nil { return 0, err } return v.compareInternal(ov), nil } kind-0.27.0/pkg/internal/version/version_test.go000066400000000000000000000277161475376161000217110ustar00rootroot00000000000000/* Copyright 2016 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package version import ( "fmt" "reflect" "testing" ) type testItem struct { version string unparsed string equalsPrev bool } func testOne(v *Version, item, prev testItem) error { str := v.String() if item.unparsed == "" { if str != item.version { return fmt.Errorf("bad round-trip: %q -> %q", item.version, str) } } else { if str != item.unparsed { return fmt.Errorf("bad unparse: %q -> %q, expected %q", item.version, str, item.unparsed) } } if prev.version != "" { cmp, err := v.Compare(prev.version) if err != nil { return fmt.Errorf("unexpected parse error: %v", err) } rv, err := parse(prev.version, v.semver) if err != nil { return fmt.Errorf("unexpected parse error: %v", err) } rcmp, err := rv.Compare(item.version) if err != nil { return fmt.Errorf("unexpected parse error: %v", err) } switch { case cmp == -1: return fmt.Errorf("unexpected ordering %q < %q", item.version, prev.version) case cmp == 0 && !item.equalsPrev: return fmt.Errorf("unexpected comparison %q == %q", item.version, prev.version) case cmp == 1 && item.equalsPrev: return fmt.Errorf("unexpected comparison %q != %q", item.version, prev.version) case cmp != -rcmp: return fmt.Errorf("unexpected reverse comparison %q <=> %q %v %v %v %v", item.version, prev.version, cmp, rcmp, v.Components(), rv.Components()) } } return nil } func TestSemanticVersions(t *testing.T) { tests := []testItem{ // This is every version string that appears in the 2.0 semver spec, // sorted in strictly increasing order except as noted. {version: "0.1.0"}, {version: "1.0.0-0.3.7"}, {version: "1.0.0-alpha"}, {version: "1.0.0-alpha+001", equalsPrev: true}, {version: "1.0.0-alpha.1"}, {version: "1.0.0-alpha.beta"}, {version: "1.0.0-beta"}, {version: "1.0.0-beta+exp.sha.5114f85", equalsPrev: true}, {version: "1.0.0-beta.2"}, {version: "1.0.0-beta.11"}, {version: "1.0.0-rc.1"}, {version: "1.0.0-x.7.z.92"}, {version: "1.0.0"}, {version: "1.0.0+20130313144700", equalsPrev: true}, {version: "1.8.0-alpha.3"}, {version: "1.8.0-alpha.3.673+73326ef01d2d7c"}, {version: "1.9.0"}, {version: "1.10.0"}, {version: "1.11.0"}, {version: "2.0.0"}, {version: "2.1.0"}, {version: "2.1.1"}, {version: "42.0.0"}, // We also allow whitespace and "v" prefix {version: " 42.0.0", unparsed: "42.0.0", equalsPrev: true}, {version: "\t42.0.0 ", unparsed: "42.0.0", equalsPrev: true}, {version: "43.0.0-1", unparsed: "43.0.0-1"}, {version: "43.0.0-1 ", unparsed: "43.0.0-1", equalsPrev: true}, {version: "v43.0.0-1", unparsed: "43.0.0-1", equalsPrev: true}, {version: " v43.0.0", unparsed: "43.0.0"}, {version: " 43.0.0 ", unparsed: "43.0.0", equalsPrev: true}, } var prev testItem for _, item := range tests { v, err := ParseSemantic(item.version) if err != nil { t.Errorf("unexpected parse error: %v", err) continue } err = testOne(v, item, prev) if err != nil { t.Errorf("%v", err) } prev = item } } func TestBadSemanticVersions(t *testing.T) { tests := []string{ // "MUST take the form X.Y.Z" "1", "1.2", "1.2.3.4", ".2.3", "1..3", "1.2.", "", "..", // "where X, Y, and Z are non-negative integers" "-1.2.3", "1.-2.3", "1.2.-3", "1a.2.3", "1.2a.3", "1.2.3a", "a1.2.3", "a.b.c", "1 .2.3", "1. 2.3", // "and MUST NOT contain leading zeroes." "01.2.3", "1.02.3", "1.2.03", // "[pre-release] identifiers MUST comprise only ASCII alphanumerics and hyphen" "1.2.3-/", // "[pre-release] identifiers MUST NOT be empty" "1.2.3-", "1.2.3-.", "1.2.3-foo.", "1.2.3-.foo", // "Numeric [pre-release] identifiers MUST NOT include leading zeroes" "1.2.3-01", // "[build metadata] identifiers MUST comprise only ASCII alphanumerics and hyphen" "1.2.3+/", // "[build metadata] identifiers MUST NOT be empty" "1.2.3+", "1.2.3+.", "1.2.3+foo.", "1.2.3+.foo", // whitespace/"v"-prefix checks "v 1.2.3", "vv1.2.3", } for i := range tests { _, err := ParseSemantic(tests[i]) if err == nil { t.Errorf("unexpected success parsing invalid semver %q", tests[i]) } } } func TestGenericVersions(t *testing.T) { tests := []testItem{ // This is all of the strings from TestSemanticVersions, plus some strings // from TestBadSemanticVersions that should parse as generic versions, // plus some additional strings. {version: "0.1.0", unparsed: "0.1.0"}, {version: "1.0.0-0.3.7", unparsed: "1.0.0"}, {version: "1.0.0-alpha", unparsed: "1.0.0", equalsPrev: true}, {version: "1.0.0-alpha+001", unparsed: "1.0.0", equalsPrev: true}, {version: "1.0.0-alpha.1", unparsed: "1.0.0", equalsPrev: true}, {version: "1.0.0-alpha.beta", unparsed: "1.0.0", equalsPrev: true}, {version: "1.0.0.beta", unparsed: "1.0.0", equalsPrev: true}, {version: "1.0.0-beta+exp.sha.5114f85", unparsed: "1.0.0", equalsPrev: true}, {version: "1.0.0.beta.2", unparsed: "1.0.0", equalsPrev: true}, {version: "1.0.0.beta.11", unparsed: "1.0.0", equalsPrev: true}, {version: "1.0.0.rc.1", unparsed: "1.0.0", equalsPrev: true}, {version: "1.0.0-x.7.z.92", unparsed: "1.0.0", equalsPrev: true}, {version: "1.0.0", unparsed: "1.0.0", equalsPrev: true}, {version: "1.0.0+20130313144700", unparsed: "1.0.0", equalsPrev: true}, {version: "1.2", unparsed: "1.2"}, {version: "1.2a.3", unparsed: "1.2", equalsPrev: true}, {version: "1.2.3", unparsed: "1.2.3"}, {version: "1.2.3.0", unparsed: "1.2.3.0", equalsPrev: true}, {version: "1.2.3a", unparsed: "1.2.3", equalsPrev: true}, {version: "1.2.3-foo.", unparsed: "1.2.3", equalsPrev: true}, {version: "1.2.3-.foo", unparsed: "1.2.3", equalsPrev: true}, {version: "1.2.3-01", unparsed: "1.2.3", equalsPrev: true}, {version: "1.2.3+", unparsed: "1.2.3", equalsPrev: true}, {version: "1.2.3+foo.", unparsed: "1.2.3", equalsPrev: true}, {version: "1.2.3+.foo", unparsed: "1.2.3", equalsPrev: true}, {version: "1.02.3", unparsed: "1.2.3", equalsPrev: true}, {version: "1.2.03", unparsed: "1.2.3", equalsPrev: true}, {version: "1.2.003", unparsed: "1.2.3", equalsPrev: true}, {version: "1.2.3.4", unparsed: "1.2.3.4"}, {version: "1.2.3.4b3", unparsed: "1.2.3.4", equalsPrev: true}, {version: "1.2.3.4.5", unparsed: "1.2.3.4.5"}, {version: "1.9.0", unparsed: "1.9.0"}, {version: "1.9.0.0.0.0.0.0", unparsed: "1.9.0.0.0.0.0.0", equalsPrev: true}, {version: "1.10.0", unparsed: "1.10.0"}, {version: "1.11.0", unparsed: "1.11.0"}, {version: "1.11.0.0.5", unparsed: "1.11.0.0.5"}, {version: "2.0.0", unparsed: "2.0.0"}, {version: "2.1.0", unparsed: "2.1.0"}, {version: "2.1.1", unparsed: "2.1.1"}, {version: "42.0.0", unparsed: "42.0.0"}, {version: " 42.0.0", unparsed: "42.0.0", equalsPrev: true}, {version: "\t42.0.0 ", unparsed: "42.0.0", equalsPrev: true}, {version: "42.0.0-1", unparsed: "42.0.0", equalsPrev: true}, {version: "42.0.0-1 ", unparsed: "42.0.0", equalsPrev: true}, {version: "v42.0.0-1", unparsed: "42.0.0", equalsPrev: true}, {version: " v43.0.0", unparsed: "43.0.0"}, {version: " 43.0.0 ", unparsed: "43.0.0", equalsPrev: true}, } var prev testItem for _, item := range tests { v, err := ParseGeneric(item.version) if err != nil { t.Errorf("unexpected parse error: %v", err) continue } err = testOne(v, item, prev) if err != nil { t.Errorf("%v", err) } prev = item } } func TestBadGenericVersions(t *testing.T) { tests := []string{ "1", "01.2.3", "-1.2.3", "1.-2.3", ".2.3", "1..3", "1a.2.3", "a1.2.3", "1 .2.3", "1. 2.3", "1.bob", "bob", "v 1.2.3", "vv1.2.3", "", ".", } for i := range tests { _, err := ParseGeneric(tests[i]) if err == nil { t.Errorf("unexpected success parsing invalid version %q", tests[i]) } } } func TestComponents(t *testing.T) { var tests = []struct { version string semver bool expectedComponents []uint expectedMajor uint expectedMinor uint expectedPatch uint expectedPreRelease string expectedBuildMetadata string }{ { version: "1.0.2", semver: true, expectedComponents: []uint{1, 0, 2}, expectedMajor: 1, expectedMinor: 0, expectedPatch: 2, }, { version: "1.0.2-alpha+001", semver: true, expectedComponents: []uint{1, 0, 2}, expectedMajor: 1, expectedMinor: 0, expectedPatch: 2, expectedPreRelease: "alpha", expectedBuildMetadata: "001", }, { version: "1.2", semver: false, expectedComponents: []uint{1, 2}, expectedMajor: 1, expectedMinor: 2, }, { version: "1.0.2-beta+exp.sha.5114f85", semver: true, expectedComponents: []uint{1, 0, 2}, expectedMajor: 1, expectedMinor: 0, expectedPatch: 2, expectedPreRelease: "beta", expectedBuildMetadata: "exp.sha.5114f85", }, } for _, test := range tests { version, _ := parse(test.version, test.semver) if !reflect.DeepEqual(test.expectedComponents, version.Components()) { t.Error("parse returned un'expected components") } if test.expectedMajor != version.Major() { t.Errorf("parse returned version.Major %d, expected %d", test.expectedMajor, version.Major()) } if test.expectedMinor != version.Minor() { t.Errorf("parse returned version.Minor %d, expected %d", test.expectedMinor, version.Minor()) } if test.expectedPatch != version.Patch() { t.Errorf("parse returned version.Patch %d, expected %d", test.expectedPatch, version.Patch()) } if test.expectedPreRelease != version.PreRelease() { t.Errorf("parse returned version.PreRelease %s, expected %s", test.expectedPreRelease, version.PreRelease()) } if test.expectedBuildMetadata != version.BuildMetadata() { t.Errorf("parse returned version.BuildMetadata %s, expected %s", test.expectedBuildMetadata, version.BuildMetadata()) } } } func TestVersion_LessThan_AtLeast(t *testing.T) { tests := []struct { name string version string lessThan string want bool }{ { name: "same version", version: "1.21.1", lessThan: "1.21.1", want: false, }, { name: "one patch less", version: "1.21.1", lessThan: "1.21.2", want: true, }, { name: "one patch greater", version: "1.21.3", lessThan: "1.21.2", want: false, }, { name: "one patch less but one minor more", version: "1.22.1", lessThan: "1.21.2", want: false, }, { name: "one patch greater but one minor less", version: "1.20.3", lessThan: "1.21.2", want: true, }, { name: "same version against prerelease", version: "v1.24.0", lessThan: "v1.24.0-beta.0.115+65178fec72df62", want: false, }, { name: "prerelease against same version", version: "v1.24.0-beta.0.115+65178fec72df62", lessThan: "v1.24.0", want: true, }, { name: "prerelease against same version considering prereleases", version: "v1.24.0-beta.0.115+65178fec72df62", lessThan: "v1.24.0-0", want: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { v := MustParseSemantic(tt.version) if got := v.LessThan(MustParseSemantic(tt.lessThan)); got != tt.want { t.Errorf("Version.LessThan() = %v, want %v", got, tt.want) } if got := v.AtLeast(MustParseSemantic(tt.lessThan)); got == tt.want { t.Errorf("Version.AtLeast() = %v, want %v", got, tt.want) } }) } } kind-0.27.0/pkg/log/000077500000000000000000000000001475376161000141015ustar00rootroot00000000000000kind-0.27.0/pkg/log/doc.go000066400000000000000000000013101475376161000151700ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // Package log defines a logging interface that kind uses // This is roughly a minimal subset of klog github.com/kubernetes/klog package log kind-0.27.0/pkg/log/noop.go000066400000000000000000000032471475376161000154110ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package log // NoopLogger implements the Logger interface and never logs anything type NoopLogger struct{} // Warn meets the Logger interface but does nothing func (n NoopLogger) Warn(message string) {} // Warnf meets the Logger interface but does nothing func (n NoopLogger) Warnf(format string, args ...interface{}) {} // Error meets the Logger interface but does nothing func (n NoopLogger) Error(message string) {} // Errorf meets the Logger interface but does nothing func (n NoopLogger) Errorf(format string, args ...interface{}) {} // V meets the Logger interface but does nothing func (n NoopLogger) V(level Level) InfoLogger { return NoopInfoLogger{} } // NoopInfoLogger implements the InfoLogger interface and never logs anything type NoopInfoLogger struct{} // Enabled meets the InfoLogger interface but always returns false func (n NoopInfoLogger) Enabled() bool { return false } // Info meets the InfoLogger interface but does nothing func (n NoopInfoLogger) Info(message string) {} // Infof meets the InfoLogger interface but does nothing func (n NoopInfoLogger) Infof(format string, args ...interface{}) {} kind-0.27.0/pkg/log/types.go000066400000000000000000000046431475376161000156030ustar00rootroot00000000000000/* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package log // Level is a verbosity logging level for Info logs // See also https://github.com/kubernetes/klog type Level int32 // Logger defines the logging interface kind uses // It is roughly a subset of github.com/kubernetes/klog type Logger interface { // Warn should be used to write user facing warnings Warn(message string) // Warnf should be used to write Printf style user facing warnings Warnf(format string, args ...interface{}) // Error may be used to write an error message when it occurs // Prefer returning an error instead in most cases Error(message string) // Errorf may be used to write a Printf style error message when it occurs // Prefer returning an error instead in most cases Errorf(format string, args ...interface{}) // V() returns an InfoLogger for a given verbosity Level // // Normal verbosity levels: // V(0): normal user facing messages go to V(0) // V(1): debug messages start when V(N > 0), these should be high level // V(2): more detailed log messages // V(3+): trace level logging, in increasing "noisiness" ... allowing // arbitrarily detailed logging at extremely low cost unless the // logger has actually been configured to display these (E.G. via the -v // command line flag) // // It is expected that the returned InfoLogger will be extremely cheap // to interact with for a Level greater than the enabled level V(Level) InfoLogger } // InfoLogger defines the info logging interface kind uses // It is roughly a subset of Verbose from github.com/kubernetes/klog type InfoLogger interface { // Info is used to write a user facing status message // // See: Logger.V Info(message string) // Infof is used to write a Printf style user facing status message Infof(format string, args ...interface{}) // Enabled should return true if this verbosity level is enabled // on the Logger // // See: Logger.V Enabled() bool } kind-0.27.0/site/000077500000000000000000000000001475376161000135035ustar00rootroot00000000000000kind-0.27.0/site/.gitignore000066400000000000000000000000451475376161000154720ustar00rootroot00000000000000public/* resources/* .hugo_build.lockkind-0.27.0/site/LICENSE000066400000000000000000000443271475376161000145220ustar00rootroot00000000000000Attribution 4.0 International ======================================================================= Creative Commons Corporation ("Creative Commons") is not a law firm and does not provide legal services or legal advice. Distribution of Creative Commons public licenses does not create a lawyer-client or other relationship. Creative Commons makes its licenses and related information available on an "as-is" basis. Creative Commons gives no warranties regarding its licenses, any material licensed under their terms and conditions, or any related information. Creative Commons disclaims all liability for damages resulting from their use to the fullest extent possible. Using Creative Commons Public Licenses Creative Commons public licenses provide a standard set of terms and conditions that creators and other rights holders may use to share original works of authorship and other material subject to copyright and certain other rights specified in the public license below. The following considerations are for informational purposes only, are not exhaustive, and do not form part of our licenses. Considerations for licensors: Our public licenses are intended for use by those authorized to give the public permission to use material in ways otherwise restricted by copyright and certain other rights. Our licenses are irrevocable. Licensors should read and understand the terms and conditions of the license they choose before applying it. Licensors should also secure all rights necessary before applying our licenses so that the public can reuse the material as expected. Licensors should clearly mark any material not subject to the license. This includes other CC- licensed material, or material used under an exception or limitation to copyright. More considerations for licensors: wiki.creativecommons.org/Considerations_for_licensors Considerations for the public: By using one of our public licenses, a licensor grants the public permission to use the licensed material under specified terms and conditions. If the licensor's permission is not necessary for any reason--for example, because of any applicable exception or limitation to copyright--then that use is not regulated by the license. Our licenses grant only permissions under copyright and certain other rights that a licensor has authority to grant. Use of the licensed material may still be restricted for other reasons, including because others have copyright or other rights in the material. A licensor may make special requests, such as asking that all changes be marked or described. Although not required by our licenses, you are encouraged to respect those requests where reasonable. More_considerations for the public: wiki.creativecommons.org/Considerations_for_licensees ======================================================================= Creative Commons Attribution 4.0 International Public License By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions. Section 1 -- Definitions. a. Adapted Material means material subject to Copyright and Similar Rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the Copyright and Similar Rights held by the Licensor. For purposes of this Public License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image. b. Adapter's License means the license You apply to Your Copyright and Similar Rights in Your contributions to Adapted Material in accordance with the terms and conditions of this Public License. c. Copyright and Similar Rights means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights. d. Effective Technological Measures means those measures that, in the absence of proper authority, may not be circumvented under laws fulfilling obligations under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, and/or similar international agreements. e. Exceptions and Limitations means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material. f. Licensed Material means the artistic or literary work, database, or other material to which the Licensor applied this Public License. g. Licensed Rights means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license. h. Licensor means the individual(s) or entity(ies) granting rights under this Public License. i. Share means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them. j. Sui Generis Database Rights means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world. k. You means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning. Section 2 -- Scope. a. License grant. 1. Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to: a. reproduce and Share the Licensed Material, in whole or in part; and b. produce, reproduce, and Share Adapted Material. 2. Exceptions and Limitations. For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions. 3. Term. The term of this Public License is specified in Section 6(a). 4. Media and formats; technical modifications allowed. The Licensor authorizes You to exercise the Licensed Rights in all media and formats whether now known or hereafter created, and to make technical modifications necessary to do so. The Licensor waives and/or agrees not to assert any right or authority to forbid You from making technical modifications necessary to exercise the Licensed Rights, including technical modifications necessary to circumvent Effective Technological Measures. For purposes of this Public License, simply making modifications authorized by this Section 2(a) (4) never produces Adapted Material. 5. Downstream recipients. a. Offer from the Licensor -- Licensed Material. Every recipient of the Licensed Material automatically receives an offer from the Licensor to exercise the Licensed Rights under the terms and conditions of this Public License. b. No downstream restrictions. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, the Licensed Material if doing so restricts exercise of the Licensed Rights by any recipient of the Licensed Material. 6. No endorsement. Nothing in this Public License constitutes or may be construed as permission to assert or imply that You are, or that Your use of the Licensed Material is, connected with, or sponsored, endorsed, or granted official status by, the Licensor or others designated to receive attribution as provided in Section 3(a)(1)(A)(i). b. Other rights. 1. Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, privacy, and/or other similar personality rights; however, to the extent possible, the Licensor waives and/or agrees not to assert any such rights held by the Licensor to the limited extent necessary to allow You to exercise the Licensed Rights, but not otherwise. 2. Patent and trademark rights are not licensed under this Public License. 3. To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties. Section 3 -- License Conditions. Your exercise of the Licensed Rights is expressly made subject to the following conditions. a. Attribution. 1. If You Share the Licensed Material (including in modified form), You must: a. retain the following if it is supplied by the Licensor with the Licensed Material: i. identification of the creator(s) of the Licensed Material and any others designated to receive attribution, in any reasonable manner requested by the Licensor (including by pseudonym if designated); ii. a copyright notice; iii. a notice that refers to this Public License; iv. a notice that refers to the disclaimer of warranties; v. a URI or hyperlink to the Licensed Material to the extent reasonably practicable; b. indicate if You modified the Licensed Material and retain an indication of any previous modifications; and c. indicate the Licensed Material is licensed under this Public License, and include the text of, or the URI or hyperlink to, this Public License. 2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner based on the medium, means, and context in which You Share the Licensed Material. For example, it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a resource that includes the required information. 3. If requested by the Licensor, You must remove any of the information required by Section 3(a)(1)(A) to the extent reasonably practicable. 4. If You Share Adapted Material You produce, the Adapter's License You apply must not prevent recipients of the Adapted Material from complying with this Public License. Section 4 -- Sui Generis Database Rights. Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material: a. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database; b. if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material; and c. You must comply with the conditions in Section 3(a) if You Share all or a substantial portion of the contents of the database. For the avoidance of doubt, this Section 4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights. Section 5 -- Disclaimer of Warranties and Limitation of Liability. a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. c. The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability. Section 6 -- Term and Termination. a. This Public License applies for the term of the Copyright and Similar Rights licensed here. However, if You fail to comply with this Public License, then Your rights under this Public License terminate automatically. b. Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates: 1. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or 2. upon express reinstatement by the Licensor. For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License. c. For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License. d. Sections 1, 5, 6, 7, and 8 survive termination of this Public License. Section 7 -- Other Terms and Conditions. a. The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed. b. Any arrangements, understandings, or agreements regarding the Licensed Material not stated herein are separate from and independent of the terms and conditions of this Public License. Section 8 -- Interpretation. a. For the avoidance of doubt, this Public License does not, and shall not be interpreted to, reduce, limit, restrict, or impose conditions on any use of the Licensed Material that could lawfully be made without permission under this Public License. b. To the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions. c. No term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor. d. Nothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority. ======================================================================= Creative Commons is not a party to its public licenses. Notwithstanding, Creative Commons may elect to apply one of its public licenses to material it publishes and in those instances will be considered the "Licensor." The text of the Creative Commons public licenses is dedicated to the public domain under the CC0 Public Domain Dedication. Except for the limited purpose of indicating that material is shared under a Creative Commons public license or as otherwise permitted by the Creative Commons policies published at creativecommons.org/policies, Creative Commons does not authorize the use of the trademark "Creative Commons" or any other trademark or logo of Creative Commons without its prior written consent including, without limitation, in connection with any unauthorized modifications to any of its public licenses or any other arrangements, understandings, or agreements concerning use of licensed material. For the avoidance of doubt, this paragraph does not form part of the public licenses. Creative Commons may be contacted at creativecommons.org. kind-0.27.0/site/Makefile000066400000000000000000000020531475376161000151430ustar00rootroot00000000000000REPO_ROOT:=${CURDIR}/.. # setup go for managing hugo PATH:=$(shell cd $(REPO_ROOT) && . hack/build/setup-go.sh && echo "$${PATH}") # go1.9+ can autodetect GOROOT, but if some other tool sets it ... GOROOT:= # enable modules GO111MODULE=on # disable CGO by default for static binaries CGO_ENABLED=0 export PATH GOROOT GO111MODULE CGO_ENABLED # work around broken PATH export SPACE:=$(subst ,, ) SHELL:=env PATH=$(subst $(SPACE),\$(SPACE),$(PATH)) $(SHELL) # from https://github.com/kubernetes/website/blob/master/Makefile DOCKER = docker HUGO_VERSION = 0.60.0 DOCKER_IMAGE = jojomi/hugo:$(HUGO_VERSION) DOCKER_RUN = $(DOCKER) run --rm --interactive --tty --volume $(realpath $(CURDIR)/..):/src -p 1313:1313 --workdir /src/site --entrypoint=hugo --platform linux/amd64 $(DOCKER_IMAGE) HUGO_BIN:=$(REPO_ROOT)/bin/hugo $(HUGO_BIN): go build -o $(HUGO_BIN) github.com/gohugoio/hugo hugo: $(HUGO_BIN) serve: hugo $(HUGO_BIN) server --bind="0.0.0.0" \ --ignoreCache \ --buildFuture \ --disableFastRender build: hugo $(HUGO_BIN) .PHONY: build serve hugo kind-0.27.0/site/README.md000066400000000000000000000003221475376161000147570ustar00rootroot00000000000000# kind's docs kind's docs are built with [hugo], and can be browsed live at https://kind.sigs.k8s.io/ To browse them locally, install hugo and run `make serve` from this directory. [hugo]: https://gohugo.io kind-0.27.0/site/assets/000077500000000000000000000000001475376161000150055ustar00rootroot00000000000000kind-0.27.0/site/assets/css/000077500000000000000000000000001475376161000155755ustar00rootroot00000000000000kind-0.27.0/site/assets/css/inline.css000066400000000000000000000156761475376161000176040ustar00rootroot00000000000000html { width: 100%; font-size: 18px; line-height: 1; -webkit-font-smoothing: antialiased !important; -moz-osx-font-smoothing: grayscale !important; text-rendering: optimizeLegibility !important; -webkit-tap-highlight-color: transparent; height: 100%; min-height: 100%; margin: 0; padding: 0; font-size: 100%; background-color: #326ce5; color: white; font-family: -apple-system, BlinkMacSystemFont, Roboto, Droid Sans, Helvetica Neue, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji; } code, pre { font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, Roboto Mono, Courier, monospace; } body { width: 100%; margin: 0; padding: 0; font-size: 18px; line-height: 1.5; min-height: 100%; /* sticky footer */ display: flex; flex-direction: column; color: black; background-color: white; } #wrapper { background-color: white; max-width: 100%; clear: both; overflow-x: hidden; flex: 1 0 auto; margin: 0; margin-left: 240px; } .sidebar-collapsed #wrapper { margin-left: 0; } #content, #footer-content { margin: 0 auto; padding: 0 1em; width: calc(100% - 2em); max-width: 40em; } #footer { background: #326ce5; color: white; padding: 1em; margin-top: 1em; border-top: .1em solid rgba(0, 0, 0, 0.1); } #footer a { color: white; } /* page footer*/ .footer { text-align: center; } .footer>* { font-size: .8em; line-height: 1.4; } #content { /* clear navbar */ padding-top: 1.5em; /* push down footer */ min-height: calc(100vh - 4em); } #navbar { position: fixed; top: 0; width: calc(100% - 240px); padding: 2px 0; margin-left: .1em; /*background-color: #326ce5;*/ background-color: #326ce5; border-bottom: .1em solid rgba(0, 0, 0, 0.1); display: flex; justify-content: space-between; z-index: 99999; } .sidebar-collapsed #navbar { width: 100%; margin-left: 0; } #navbar-title>a { font-weight: bold; font-size: 1.2em; padding: 0 4px; vertical-align: middle; } #navbar-title>a, #github>a { text-decoration: none; color: white; } #navbar>#github { filter: invert(1); } #sidebar-toggle { color: white; cursor: pointer; padding: .1em .5em; padding-bottom: 0; user-select: none; } #github { padding: 0 .5em; } #github img { height: 1.4em; width: 1.4em; vertical-align: middle; } #noticebar { padding: 1.5em; padding-top: 2em; padding-bottom: 1em; margin-bottom: -1.5em; border-bottom: .1em solid rgba(0, 0, 0, 0.1); } #noticebar>* { max-width: 46rem; margin-left: auto; margin-right: auto; margin-block-end: 0; margin-block-start: 0.25em; } #sidebar { height: 100%; position: fixed; display: flex; flex-direction: column; width: 240px; border-right: .1em solid rgba(0, 0, 0, 0.1); background-color: #fafafa; z-index: 99999; overflow-y: auto; } .sidebar-collapsed #sidebar { display: none; } #sidebar-logo { padding: 16px 16px; padding-bottom: 10px; } #sidebar-title { display: none; margin-top: -16px; padding-right: 37px; margin-bottom: 10px; text-align: center; text-decoration: none; font-size: 24px; font-weight: bold; } #sidebar li.active-entry :first-child, #sidebar li.active :first-child { font-weight: bold; } #sidebar li.active-entry :first-child { color: black; } /* consistent link color */ a { color: #326be5; } p { margin-block-start: .5em; } h1, h2, h3, h4, h5 { margin-block-start: 1rem; margin-block-end: .25rem; } /* ensure images don't go off the page */ #content img { max-width: 100%; } hr { background: #333; border: 1px solid #333; } /* code block styling */ pre { background-color: #f3f4f4 !important; overflow-x: auto; border-radius: 0; padding: 1em; font-size: 85%; } code { background-color: #f3f4f4; border-radius: 3px; padding: .1em .4em; font-size: 85%; } pre code { background-color: transparent; padding: 0; font-size: 100%; border-radius: 0; } /* inline copyable code snippet styling */ table.includecode { max-width: 100%; width: 100%; table-layout: fixed; border: 1px solid rgba(0, 0, 0, .125); border-spacing: 0; } table.includecode thead th { border-bottom: 1px solid rgba(0, 0, 0, .125); font-weight: normal; font-size: 85%; } table.includecode thead th { text-align: right; } table.includecode thead th a { font-family: monospace; } table.includecode thead th button { vertical-align: middle; background: transparent; border: 0; cursor: pointer; } table.includecode tbody { background: #f3f4f4; } table.includecode pre { margin: 0; } /* don't display this, these are textareas needed to copy to clipboard */ .hidden-copy-text { background: transparent; width: 2em; height: 2em; position: fixed; top: 0; left: 0; } /* heading anchors */ .hanchor { font-size: 100%; visibility: hidden; font-size: .5em; vertical-align: middle; text-decoration: none !important; } h1:hover a, h2:hover a, h3:hover a, h4:hover a { visibility: visible; } #content #TableOfContents ul { margin-block-start: 0; margin-block-end: 0; } #content ul { padding-left: 1.75em; margin-block-start: .5em; margin-block-end: .5em; } #content ul ul { padding-left: 1em; margin-block-start: 0; } #sidebar ul { margin-bottom: 1em; } #sidebar ul ul { padding-left: 1.25em; } .page-description { border-left-color: black; background: #3b4d56; color: white; font-size: 1.2em; line-height: 1.4em; margin-block-start: 0; margin-block-end: 0; } .page-description a { color: #0bd8ec; } .page-description code { color: black; } blockquote { border-left: .5rem solid #326ce4; background: #c9e6ff; color: black; padding: 1em 1.5em; margin-inline-start: 0; margin-inline-end: 0; } blockquote p { margin-block-start: 0.75em; margin-block-end: 0.75em; } /* style video embeds to be full-width in content */ .video-wrapper { position: relative; width: 100%; height: 0; padding-bottom: 56.25%; } .video-wrapper iframe { position: absolute; top: 0; left: 0; width: 100%; height: 100%; } /* mock github labels */ .gh-label { /* mimic github */ display: inline-block; padding: 0 7px; font-size: 12px; font-weight: 500; line-height: 18px; border: 1px solid transparent; border-radius: 2em; background: #bfd4f2; /* additional styling to allow using on a tags */ text-decoration: none; } /* mobile */ @media (max-width: 50em) { #content { font-size: 16px; } #wrapper { margin-left: 0; } #wrapper #navbar { margin-left: 241px; } .sidebar-collapsed #wrapper #navbar { margin-left: 0; } #content ul { padding-left: 1.5em; } } :target::before { content: ""; display: block; height: 2em; margin: -2em 0 0; } /* https://github.com/adityatelange/hugo-PaperMod/issues/828#issuecomment-1171994855 */ /* Fixes iOS font sizing anomaly */ code { text-size-adjust: 100%; -ms-text-size-adjust: 100%; -moz-text-size-adjust: 100%; -webkit-text-size-adjust: 100%; }kind-0.27.0/site/assets/js/000077500000000000000000000000001475376161000154215ustar00rootroot00000000000000kind-0.27.0/site/assets/js/inline.js000066400000000000000000000046121475376161000172400ustar00rootroot00000000000000/* used when sidebar is manually toggled */ function toggleSidebar() { if (document.body.classList.contains('sidebar-collapsed')) { window.localStorage.setItem('sidebar-collapsed', 'false'); showSideBar(); } else { window.localStorage.setItem('sidebar-collapsed', 'true'); hideSideBar(); } } function showSideBar() { document.body.classList.remove('sidebar-collapsed'); } function hideSideBar() { document.body.classList.add('sidebar-collapsed'); } /* get the page width */ function getWidth() { return Math.max( document.body.scrollWidth, document.documentElement.scrollWidth, document.body.offsetWidth, document.documentElement.offsetWidth, document.documentElement.clientWidth ); } var oldWidth; document.addEventListener("DOMContentLoaded", function () { // note: the default state of the page on load is collapsed var manualCollapsed = window.localStorage.getItem('sidebar-collapsed'); var width = getWidth(); // if we're now under 900px don't unhide it // otherwise only unhide if the user has not yet manually toggled it // or has toggled it back to visible if (width > 900 && (manualCollapsed == 'false' || manualCollapsed == null)) { showSideBar(); } // setup listener for width change oldWidth = width; window.addEventListener("resize", function () { // bail out early if it wasn't the width that changed var width = getWidth(); if (oldWidth == width) { oldWidth = width; return; } oldWidth = width; // if we're now under 900px, hide it if (width <= 900) { hideSideBar(); } else { // otherwise only unhide if the user has not yet manually toggled it // or has toggled it back to visible var manualCollapsed = window.localStorage.getItem('sidebar-collapsed'); if (manualCollapsed == 'false' || manualCollapsed == null) { showSideBar(); } } }); }); /* used to copy code snippets */ function copyText(elementID) { /* Get the text field */ var elem = document.getElementById(elementID); /* Select the text field */ elem.select(); elem.setSelectionRange(0, 99999); /*For mobile devices*/ /* Copy the text inside the text field */ document.execCommand("copy"); } kind-0.27.0/site/config.toml000066400000000000000000000026501475376161000156500ustar00rootroot00000000000000title = "kind" baseURL = "https://kind.sigs.k8s.io" languageCode = "en-us" # we use this to disable indexing for the non-production build enableRobotsTXT = true # this allows us to show the source commit in the footer enableGitInfo = true # we don't use these currently disableKinds = ["taxonomy", "taxonomyTerm"] # syntax highlighting options [markup] [markup.highlight] codeFences = true hl_Lines = "" lineNoStart = 1 lineNos = false lineNumbersInTable = true noClasses = true style = "vs" tabWidth = 4 # allow html in markdown [markup.goldmark.renderer] unsafe = true # enable hugo's menu system for the site, name the primary menu sectionPagesMenu = "main" # menu entries [menu] [[menu.main]] identifier = "ome" name = "Home" title = "Home" url = "/" weight = 1 [[menu.main]] identifier = "user" name = "User Guide" title = "User Guide" weight = 2 [[menu.main]] identifier = "design" name = "Design" title = "Design" url = "/docs/design/" weight = 5 [[menu.main]] identifier = "contributing" name = "Contributing" title = "contributing" url = "/docs/contributing/" weight = 6 # enable auto-generated _redirects file [mediaTypes."text/netlify"] delimiter = "" [outputFormats.REDIRECTS] mediaType = "text/netlify" baseName = "_redirects" [outputs] home = ["HTML", "REDIRECTS"] [params] stable = "v0.26.0" # privacy settings [privacy] [privacy.youtube] # enable the cookie-less youtube in built-in hugo shortcode privacyEnhanced = true kind-0.27.0/site/content/000077500000000000000000000000001475376161000151555ustar00rootroot00000000000000kind-0.27.0/site/content/_index.md000066400000000000000000000122751475376161000167540ustar00rootroot00000000000000--- title: kind ---

kind

[kind] is a tool for running local Kubernetes clusters using Docker container "nodes". kind was primarily designed for testing Kubernetes itself, but may be used for local development or CI. If you have [go] 1.16+ and [docker], [podman] or [nerdctl] installed `go install sigs.k8s.io/kind@{{< stableVersion >}} && kind create cluster` is all you need! kind consists of: - Go [packages][packages] implementing [cluster creation][cluster package], [image build][build package], etc. - A command line interface ([`kind`][kind cli]) built on these packages. - Docker [image(s)][images] written to run systemd, Kubernetes, etc. - [`kubetest`][kubetest] integration also built on these packages (WIP) kind bootstraps each "node" with [kubeadm][kubeadm]. For more details see [the design documentation][design doc]. **NOTE**: kind is still a work in progress, see the [1.0 roadmap]. ## Installation and usage For more detailed instructions see [the user guide][user guide]. You can install kind with `go install sigs.k8s.io/kind@{{< stableVersion>}}` (for [go] [1.17+][go-supported]). This will put `kind` in `$(go env GOPATH)/bin`. You may need to add that directory to your `$PATH` as shown [here](https://golang.org/doc/code.html#GOPATH) if you encounter the error `kind: command not found` after installation. To use kind, you will also need to [install docker]. Once you have docker running you can create a cluster with: {{< codeFromInline lang="bash" >}} kind create cluster {{< /codeFromInline >}} To delete your cluster use: {{< codeFromInline lang="bash" >}} kind delete cluster {{< /codeFromInline >}} To create a cluster from Kubernetes source: - ensure that Kubernetes is cloned in `$(go env GOPATH)/src/k8s.io/kubernetes` - build a node image and create a cluster with {{< codeFromInline lang="bash" >}} kind build node-image kind create cluster --image kindest/node:latest {{< /codeFromInline >}} Multi-node clusters and other advanced features may be configured with a config file, for more usage see [the user guide][user guide] or run `kind [command] --help` ## Community Please reach out for bugs, feature requests, and other issues! The maintainers of this project are reachable via: - [Kubernetes Slack] in the [#kind] channel - [filing an issue] against this repo - The Kubernetes [SIG-Testing Mailing List] Current maintainers are [@aojea] and [@BenTheElder] -- feel free to reach out directly if you have any questions! Pull Requests are very welcome! If you're planning a new feature, please file an issue to discuss first. Check the [issue tracker] for `help wanted` issues if you're unsure where to start, or feel free to reach out to discuss. 🙂 See also: our own [contributor guide] and the Kubernetes [community page]. ## Why kind? - kind supports multi-node (including HA) clusters - kind supports building Kubernetes release builds from source - support for make / bash or docker, in addition to pre-published builds - kind supports Linux, macOS and Windows - kind is a [CNCF certified conformant Kubernetes installer](https://landscape.cncf.io/?selected=kind) ### Code of conduct Participation in the Kubernetes community is governed by the [Kubernetes Code of Conduct]. [kind]: https://sigs.k8s.io/kind [go]: https://golang.org/ [go-supported]: https://golang.org/doc/devel/release.html#policy [docker]: https://www.docker.com/ [podman]: https://podman.io/ [nerdctl]: https://github.com/containerd/nerdctl [community page]: https://kubernetes.io/community/ [Kubernetes Code of Conduct]: https://github.com/kubernetes/community/blob/master/code-of-conduct.md [Go Report Card Badge]: https://goreportcard.com/badge/sigs.k8s.io/kind [Go Report Card]: https://goreportcard.com/report/sigs.k8s.io/kind [conformance tests]: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/conformance-tests.md [packages]: https://github.com/kubernetes-sigs/kind/tree/main/pkg [cluster package]: https://github.com/kubernetes-sigs/kind/tree/main/pkg/cluster [build package]: https://github.com/kubernetes-sigs/kind/tree/main/pkg/build [kind cli]: https://github.com/kubernetes-sigs/kind/tree/main/main.go [images]: https://github.com/kubernetes-sigs/kind/tree/main/images [kubetest]: https://github.com/kubernetes/test-infra/tree/master/kubetest [kubeadm]: https://kubernetes.io/docs/reference/setup-tools/kubeadm/kubeadm/ [design doc]: ./docs/design/initial [user guide]: ./docs/user/quick-start [SIG-Testing Mailing List]: https://groups.google.com/forum/#!forum/kubernetes-sig-testing [issue tracker]: https://github.com/kubernetes-sigs/kind/issues [filing an issue]: https://github.com/kubernetes-sigs/kind/issues/new [Kubernetes Slack]: https://slack.k8s.io/ [#kind]: https://kubernetes.slack.com/messages/CEKK1KTN2/ [1.0 roadmap]: /docs/contributing/1.0-roadmap [install docker]: https://docs.docker.com/install/ [@BenTheElder]: https://github.com/BenTheElder [@aojea]: https://github.com/aojea [contributor guide]: /docs/contributing/getting-started kind-0.27.0/site/content/docs/000077500000000000000000000000001475376161000161055ustar00rootroot00000000000000kind-0.27.0/site/content/docs/contributing/000077500000000000000000000000001475376161000206145ustar00rootroot00000000000000kind-0.27.0/site/content/docs/contributing/1.0-roadmap.md000066400000000000000000000116631475376161000230640ustar00rootroot00000000000000--- title: "1.0 Roadmap 🗺" menu: main: parent: "contributing" identifier: "1.0-roadmap" weight: 4 toc: true description: |- New year, new roadmap 🎉 This document outlines some goals, non-goals, and future aspirations for kind as a project. --- ## Beta Requirements To reach "beta" [grade][deprecation-policy] kind needs to at minimum: - [x] Improve documentation (Though this will eternally be "In Progress" !) - [X] create a documentation site - [#268] - [x] expand examples of using kind (We can always use more, but we have more of this now) - [x] cover known issues, debugging, work-arounds, etc. - [x] Reliably [pass][kind-conformance-dashboard] the Kubernetes [conformance tests] - [x] [Certify][certified] Conformance - [x] Support multi-node clusters - [#117] - [x] Support offline / air-gapped clusters - [x] pre-loaded / offline CNI - [#200] - [x] Support mounting host directories - [#62] - [x] Improve Windows support - [x] add Windows binaries to releases - [#155] - [x] improve instructions for KUBECONFIG in particular - [ ] Support usage as a properly versioned, supported, and documented library. This includes following semver without every release being a major / breaking change to the API (which must be extensible without breakage), ensuring the CLI only uses a suitable public library surface, documentation and examples for the library, versioned types for public APIs (E.G. config format), etc. - TODO: what exactly do we want here? Should this really be beta blocking? - [x] should be possible to troubleshoot kind without needing to modify kind ~or use external debugging tools~ (this should be possible now, if not perfect!) - [x] consistent logging (what is logged, when should it be logged, what levels are used) (this is consistent-ish now, if not perfect) - [x] errors have appropriate context (this is debatable and never perfect, but improved a lot, especially if you use `-v 1` or greater) for managing clusters in test harnesses - [x] move API types / labels from `*.k8s.io` into [`*.x-k8s.io`][api-review-voluntary] - [x] Support all currently [upstream-supported Kubernetes versions][version-skew-policy] - [ ] come up with a plan for stable node image <-> KIND compatibility ## GA Requirements To reach "GA" [grade][deprecation-policy] kind needs to at minimum: - [x] Support non-AMD64 architectures (namely ARM) - [#166] - [ ] Automated publishing of Kubernetes release based kind "node" images - [#197] - [x] Support for runtimes other than docker/default including podman, ignite etc. - [x] First class support for skewed node (Kubernetes) versions (I believe this is relatively first-class now, things should work fine if you specify different node images) - ... TBD, more will be added here ... ## Non-Goals - Supporting every possible Kubernetes configuration - In order to best support offline / hermetic clusters, we will likely not offer many options for CNI etc. out of the box. We may revisit this later. - Being "production workload ready" - kind is meant to be used: - for testing Kubernetes itself - for testing against Kubernetes (EG in CI on Travis, Circle, etc.) - for "local" clusters on developer machines - NOT to host workloads serving user traffic etc. - Replacing [Phippy] 🦒 -- kind isn't trying to replace all the things and Phippy is awesome ❤️ Longer Term goals include: - Enabling a suitable local storage provider for testing applications that need persistent storage ## Misc - [x] setup a regular Zoom meeting for the project [#244] - [x] achieve certified Kubernetes conformance [#245] Other goals / tasks not listed here can be found both in [the 1.0 project] and more generally triaged for rough-priority in the [GitHub issues]. [kind-conformance-dashboard]: https://testgrid.k8s.io/conformance-kind [version-skew-policy]: https://kubernetes.io/docs/setup/release/version-skew-policy/ [deprecation-policy]: https://kubernetes.io/docs/reference/using-api/deprecation-policy/ [certified]: https://github.com/cncf/k8s-conformance/pull/451#issuecomment-457663982 [conformance tests]: https://github.com/cncf/k8s-conformance [api-review-voluntary]: https://github.com/kubernetes/community/blob/master/sig-architecture/api-review-process.md#voluntary [1.0]: https://github.com/kubernetes-sigs/kind/projects/1 [the 1.0 project]: https://github.com/kubernetes-sigs/kind/projects/1 [GitHub issues]: https://github.com/kubernetes-sigs/kind/issues [#62]: https://github.com/kubernetes-sigs/kind/issues/62 [#117]: https://github.com/kubernetes-sigs/kind/issues/117 [#166]: https://github.com/kubernetes-sigs/kind/issues/166 [#155]: https://github.com/kubernetes-sigs/kind/issues/155 [#197]: https://github.com/kubernetes-sigs/kind/issues/197 [#200]: https://github.com/kubernetes-sigs/kind/issues/200 [#244]: https://github.com/kubernetes-sigs/kind/issues/244 [#245]: https://github.com/kubernetes-sigs/kind/issues/245 [#268]: https://github.com/kubernetes-sigs/kind/pull/268 [Phippy]: https://phippy.io/ kind-0.27.0/site/content/docs/contributing/development.md000066400000000000000000000172311475376161000234640ustar00rootroot00000000000000--- title: "Development" menu: main: parent: "contributing" identifier: "development" weight: 3 toc: true description: |- 🚧 This is a work-in-progress 🚧 This page is intended to provide contributors with an introduction to developing the kind project. --- ## Overview KIND provides various utilities for development wrapped in `make`. Most scripts require more or less only `make` + `bash` on the host, and generally stick to POSIX utilities. Some scripts use `docker` e.g. for image building or to use docker images containing special tools. This guide will introduce you to important make targets and their usage. ## Building ### Building `kind` Invoke `make build` and `bin/kind` will contain the freshly built `kind` binary upon a successful build. Like other targets, this target automatically manages the correct [`.go-version`][go-version] and doesn't require that you install any tooling (just `make` / `bash`). We accomplish this using a copy of [gimme] in `hack/third_party/gimme`. ### Building The Base Image > **NOTE**: Most development should not require changes to the base image, however if your changes do, here's how to build and test it. To build the "base image" for development use the `make quick` command in `images/base` directory: `make -C images/base quick` By default, the base image will be tagged as `kindest/base:$(date +v%Y%m%d)-$(git describe --always --dirty)` format. If you want to change this, you can set `TAG` environment variable. `TAG=v0.1.0 make -C images/base quick` For "production" base images one of the maintainers will run `make -C images/base push` which cross-compiles for all architectures and pushes to the registry. You generally don't need to cross build during development, and currently the cross build *must* be pushed instead of loaded locally, due to limitations in `docker buildx` (TODO: link to upstream issue). To test out your changes take the image you built with `make quick` and use it as the `--base-image` flag when running `kind build node-image` / building node images. You can then create a cluster with this node image (`kind create cluster --image=kindest/node:latest`) For "Production" base image updates one of the maintainers will bump `DefaultBaseImage` in `pkg/build/nodeimage/defaults.go` to point to the newly pushed image. ### Building Node Images See: ["building images"](/docs/user/quick-start/#building-images) in the user [quick start]. ## Updating Generated Code You can regenerate checked-in generated sources with `make generate`. KIND does not use much generated code, but it does use a little. Namely kind uses [`deepcopy-gen`] to generate `DeepCopy` methods for API types. There is also a `make update` target meant to cover all automated code generation + formatting (`gofmt`). ## Testing - Run `make test` to run all tests (unit + integration). - Run `make unit` to run only unit tests. - Run `make integration` to run only integration tests. Like other targets, these targets automatically manage the correct [`.go-version`][go-version] and doesn't require that you install any tooling (just `make` / `bash`). ### E2E Testing 🚧 More here coming soon ... 🚧 TLDR: `hack/ci/e2e.sh` will run e2e tests against your local Kubernetes checkout. Depending on your changes, you may want to e2e tests. In the future we plan to have e2e smoke tests that are cheaper / don't require building Kubernetes. ## Linting You can run all of our lints at once with `make verify`. Lints include: - checking that generated code is up to date - you can run just this one with `hack/make-rules/verify/generated.sh` - [golangci-lint] with a custom config (`hack/tools/.golangci.yml`) to lint Go sources - you can run just this one with `make lint` - This linter is essentially an optimized combination of _many_ Go linters - [shellcheck] to lint our shell scripts (invoked via docker so you don't need to install it) - you can run just this one with `make shellcheck` ## Documentation Our docs are built with [hugo] just like [kubernetes.io](https://kubernetes.io). We provide a Makefile for development that automatically manages a go toolchain and uses that to build and run hugo. You only need `make`, `bash`, and `curl` or `wget` installed. Markdown content is under `site/content/` with a structure mirroring this site. Static files are under `site/static` (e.g. images are under `site/static/images/`). For simple content changes you can also just edit the markdown sources and send a pull request. A build preview will be created by netlify which you can browse by clicking the "details" link next to the `deploy/netlify` status at the bottom of your pull request on GitHub. These are also predictable as `https://deploy-preview-$PR_NUMBER--k8s-kind.netlify.app/`, just replace `$PR_NUMBER` with the number of your Pull Request. For more involved site / documentation development, you can run `make -C site serve` from the kind repo to run a local instance of the documentation, browsable at [http://localhost:1313](http://localhost:1313). This site has a custom hugo theme under `site/layouts` & `site/assets`. It's mostly relatively simple but it has a few extra features: - The theme layout takes a `description` parameter in page [front matter] - This renders the blockquote you see just below the page title, on this page with the text `This page is intended to provide contributors with an introduction to developing the kind project.` - We have a few useful but simple custom shortcodes ### Shortcodes [Shortcodes](https://gohugo.io/content-management/shortcodes/) are a hugo feature, the kind docs use the following custom shortcodes: 1. `absURL` -- When you need an absolute URL e.g. for `kubectl apply -f $URL`, this shortcode converts an input URL to absolute. Usage: `{{}}` 1. `securitygoose` -- This is a special shortcode for fun security notices. It wraps inner markdown content. Usage: `{{}} Notice markdown content here {{}}` 1. `codeFromFile` -- Creates a nice codeblock with a copy button from a file. Usage: `{{}}` 1. `codeFromInline` -- Creates a nice codeblock with a copy button from an inline code snippet. Usage: `{{}} func main() {{}}` 1. `readFile` -- is used to inline file contents. Usage: `{{%/* readFile "static/examples/ingress/contour/patch.json" */%}}` ## CI The KIND project runs in / on Kubernetes' Custom CI, "[prow]" (prow.k8s.io). This is both true for CI in the KIND repo, and in the Kubernetes repo where kind is used to test Kubernetes. It's important to note that we run larger, slower Kubernetes e2e tests in the kind repo to mimic what we run in the Kubernetes repo. This is because Kubernetes tests with kind at HEAD to have the latest fixes for running bleeding edge Kubernetes. We ensure that the tests continue to work in the kind repo before merging any code changes. We also have some limited usage experiments with [GitHub Actions], currently only for testing `podman` or `nerdctl` support in kind. ### Configuration Our repo's prow configuration is at https://git.k8s.io/test-infra/config/jobs/kubernetes-sigs/kind GitHub actions are configured in `.github/workflows` in the kind repo. [gimme]: https://github.com/travis-ci/gimme [shellcheck]: https://shellcheck.net [golangci-lint]: https://github.com/golangci/golangci-lint [go-version]: https://sigs.k8s.io/kind/.go-version [quick start]: /docs/user/quick-start/ [hugo]: https://gohugo.io [prow]: https://git.k8s.io/test-infra/ [GitHub Actions]: https://github.com/features/actions [front matter]: https://gohugo.io/content-management/front-matter/ kind-0.27.0/site/content/docs/contributing/getting-started.md000066400000000000000000000154711475376161000242530ustar00rootroot00000000000000--- title: "Getting Started" menu: main: parent: "contributing" identifier: "getting started" weight: 1 toc: true description: |- Welcome! 👋 This guide covers how to start contributing to kind 😄 --- ## 1. Familiarize Yourself With Contributing to Kubernetes Projects ### Read the Kubernetes Community Guidelines Make sure to read you read the [Kubernetes community guidelines][community]. In specific, read through the [Kubernetes contributor guidelines][contributor]. Additionally, note that ### Setup GitHub Account Kubernetes and kind are developed on [GitHub][github] and will require an account to contribute. ### Sign CNCF CLA The Kubernetes project requires the [CNCF][CNCF] [CLA][CNCF-cla] be signed against your GitHub account for all contributions in all subprojects. You'll need to get the CLA signed to contribute. ### Check The Kubernetes Contributor Guides You may come back to this later, but we highly recommend reading these: - [Kubernetes Contributor Guide](https://git.k8s.io/community/contributors/guide) - Main contributor documentation, or you can just jump directly to the [contributing section](https://git.k8s.io/community/contributors/guide#contributing) - [Contributor Cheat Sheet](https://git.k8s.io/community/contributors/guide/contributor-cheatsheet) - Common resources for existing developers ## 2. Install Tools ### Install Git Our source code is managed with [`git`][git], to develop locally you will need to install `git`. You can check if `git` is already on your system and properly installed with the following command: ``` git --version ``` ### Install Docker Currently, to create clusters you will need to install [Docker][docker]. If you haven't already, [install Docker][install docker], following the [official instructions][install docker]. If you have an existing installation, check your version and make sure you have the latest Docker. To check if `docker` has been installed: ``` docker --version ``` This documentation is written using Docker version 18.09.2. ### Install Go (optional) KIND is written in [Go][golang], however our makefiles automatically ensure the correct version of go when building or testing. You may still wish to install go on your machine to make it easier to integrate into your editor etc. You can find the version of go we're currently using to develop kind in the [`.go-version`][go-version] file in the kind repo. Install or upgrade [Go using the instructions for your operating system][golang]. You can check if Go is in your system with the following command: ## 3. Read The Docs The [design principles], [1.0 roadmap], and [initial design] may be helpful to review before contributing. These docs cover some of the project philosophy and direction. ## 4. Reaching Out Issues are tracked on GitHub. Please check [the issue tracker][issues] to see if there is any existing discussion or work related to your interests. In particular, if you're just getting started, you may want to look for issues labeled good first issue or help wanted which are standard labels in the Kubernetes project. The help wanted label marks issues we're actively seeking help with while good first issue is additionally applied to a subset of issues we think will be particularly good for newcomers. If you're interested in working on any of these, leave a comment to let us know! If you do not see anything, please [file a new issue][file an issue]. > **NOTE**: _Please_ file an enhancement / [feature request issue][fr-issue] to discuss features before filing a PR (ideally even before writing any code), we have a lot to consider with respect to our > existing users and future support when accepting any new feature. > > To streamline the process, please reach out and discuss the concept and design > / approach ASAP so the maintainers and community can get involved early. Also -- Please reach out in general for bugs, feature requests, and other issues! The maintainers of this project are reachable via: - [Kubernetes Slack] in the [#kind] channel (most active, along with the community) - The issue tracker by [filing an issue][file an issue] - The Kubernetes [SIG-Testing][SIG-Testing] [Mailing List][SIG-Testing Mailing List] Current maintainers are [@aojea] and [@BenTheElder] -- feel free to reach out directly if you have any questions! See also: the Kubernetes [community page]. ## 5. Next Steps Okay, so you've gotten your development environment setup, you've read all the contributor guides, signed the CLA ... now what? If you're planning to contribute code changes, you'll want to read the [development guide] next. If you're looking to contribute documentation improvements, first: Thank you! 🎉🤗 You'll specifically want to see the [documentation section] of the development guide. [git]: https://git-scm.com/ [hugo]: https://gohugo.io [issues]: https://github.com/kubernetes-sigs/kind/issues [file an issue]: https://github.com/kubernetes-sigs/kind/issues/new/choose [design principles]: /docs/design/principles [1.0 roadmap]: /docs/contributing/1.0-roadmap [project scope]: /docs/contributing/project-scope [project structure]: /docs/contributing/project-structure [initial design]: /docs/design/initial [github]: https://github.com/ [golang]: https://golang.org/doc/install [docker]: https://www.docker.com/ [install docker]: https://docs.docker.com/install/#supported-platforms [community]: https://github.com/kubernetes/community [contributor]: https://github.com/kubernetes/community/blob/master/contributors/guide/README.md [Kubernetes Slack]: https://slack.k8s.io/ [#kind]: https://kubernetes.slack.com/messages/CEKK1KTN2/ [@BenTheElder]: https://github.com/BenTheElder [@aojea]: https://github.com/aojea [community page]: https://kubernetes.io/community/ [modules]: https://github.com/golang/go/wiki/Modules [SIG-Testing Mailing List]: https://groups.google.com/forum/#!forum/kubernetes-sig-testing [CNCF]: https://www.cncf.io/ [CNCF-cla]: https://git.k8s.io/community/CLA.md [fr-issue]: https://github.com/kubernetes-sigs/kind/issues/new?labels=kind%2Ffeature&template=enhancement.md [SIG-Testing]: https://github.com/kubernetes/community/blob/master/sig-testing/README.md [go-version]: https://sigs.k8s.io/kind/.go-version [development guide]: /docs/contributing/development [documentation section]: /docs/contributing/development#documentation kind-0.27.0/site/content/docs/contributing/project-scope.md000066400000000000000000000103661475376161000237210ustar00rootroot00000000000000--- title: "Project Scope" menu: main: parent: "contributing" identifier: "project-scope" weight: 2 toc: true description: |- This document outlines some scoping and major priorities for kind. See also: the [1.0 roadmap], and the [1.0 tracking milestone]. [1.0 roadmap]: /docs/contributing/1.0-roadmap [1.0 tracking milestone]: https://github.com/kubernetes-sigs/kind/milestone/2 --- ## Priorities (from greatest to least) ### P-1: Bootstrapping the kind Project Itself --- **Stakeholders**: - kind maintainers - kind contributors **Covered Work**: - Releases & tooling - Automated image publishing - Documentation bootstrapping (IE this site) - Enough Kubernetes testing to test kind itself (Kubernetes Conformance tests) - Setting up linters and other tools to verify quality - Setting up a recurring subproject meeting ### P0: Support Testing Kubernetes --- **Stakeholders**: - [SIG Testing][sigs] - [SIG Cluster-Lifecycle][sigs] - the [kubeadm] subproject - Possibly [SIG Release][sigs] (mainly to provide easy access to alpha and beta tags) **Covered Work**: - Limited workloads / [e2e testing][e2e] - Cluster bring-up (IE [kubeadm]) - Kubernetes build (and currently install, but that may be problematic for cross-platform [#166]) - Node skew, client skew (kubectl / e2e versions) - Image publishing - Kubernetes CI tooling and [jobs][kubeadm-kind-job] - Most everything in the [1.0 roadmap] - ... ### P1: Support Testing Kubernetes Applications --- **Stakeholders**: Various projects both inside & outside the Kubernetes Org. - [cert-manager] - [cluster-api-provider-aws] - [cluster-api-provider-azure] - ... **Covered Work**: Most of the necessary work should be covered under [P1: Support Testing Kubernetes Applications](#p1-support-testing-kubernetes-applications), however there is some additional work. - Improve "kind as a library" - better and more controllable logging - generally more control over output - example usage & documentation - better / tighter API contracts - Most of the rest should be covered by improving "kind the binary" outlined above - ... ### P2: Provide Cheap Bootstrap Clusters for the Cluster-API --- **Stakeholders**: - various [cluster-api][cluster-api] [provider implementation][cluster-api provider implementations] developers - various [cluster-api][cluster-api] users ### P3: Extended Testing Not Covered Above --- **Stakeholders**: - Indeterminate / many Possibly supporting various things that we cannot reasonably test today including: - "node" tests, e.g. reboot - Upgrades, downgrades - Anything depending on ingress - Anything depending on persistent storage / PVs - Testing the cluster-api proper with some sort of machine provisioning - Device plugin, e.g. GPU - ... Several of these make sense but are not possible with the current tooling and will require a reasonable amount of design and thought to do well. Some of them may not be solve-able in a good way, but are at least technologically feasible to explore. ## Out of Scope --- Some things we can likely never cover in a reasonable way: - Cloud provider / [CCM] - Some of the node testing (which portions exactly is currently unclear) - Being an alternative to "docker compose" etc. - Replacing [Phippy][phippy] ❤️ 🦒 ❤️ - ... [#166]: https://github.com/kubernetes-sigs/kind/issues/166 [1.0 roadmap]: /docs/contributing/1.0-roadmap [1.0 tracking milestone]: https://github.com/kubernetes-sigs/kind/milestone/2 [phippy]: https://phippy.io [kubeadm]: https://github.com/kubernetes/kubeadm [sigs]: https://github.com/kubernetes/community/blob/master/sig-list.md [e2e]: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-testing/e2e-tests.md [kubeadm-kind-job]: https://testgrid.k8s.io/sig-cluster-lifecycle-all#kubeadm-kind-master [cert-manager]: https://github.com/jetstack/cert-manager [cluster-api-provider-aws]: https://github.com/kubernetes-sigs/cluster-api-provider-aws [cluster-api-provider-azure]: https://github.com/kubernetes-sigs/cluster-api-provider-azure [cluster-api]: https://github.com/kubernetes-sigs/cluster-api [cluster-api provider implementations]: https://github.com/kubernetes-sigs/cluster-api#provider-implementations [CCM]: https://github.com/kubernetes/kubernetes/tree/master/cmd/cloud-controller-manager kind-0.27.0/site/content/docs/design/000077500000000000000000000000001475376161000173565ustar00rootroot00000000000000kind-0.27.0/site/content/docs/design/base-image.md000066400000000000000000000017541475376161000217010ustar00rootroot00000000000000--- title: "Base Image" menu: main: parent: "design" identifier: "base-image" --- This page used to host a doc about the initial design, this has been found confusing so we've updated it to clarify the current expectations. While the sources of the project are fully open, depending on the specifics of the node image internals is not supported. We only support that base images will create a working node image with `kind build node-image` at the kind release they were shipped with. The contents and implemlentation of the images are subject to change at any time to fix bugs, improve reliability, performance, or maintainability. DO NOT DEPEND ON THE INTERNALS OF THE BASE IMAGES. KIND provides [conformant][conformance] Kubernetes, anything else is an implementation detail. We will not accept bugs about "breaking changes" to base images and you depend on the implementation details at your own peril. [conformance]: https://www.cncf.io/training/certification/software-conformance/ kind-0.27.0/site/content/docs/design/initial.md000066400000000000000000000120511475376161000213300ustar00rootroot00000000000000--- title: "Initial design" menu: main: parent: "design" identifier: "design-initial" weight: 2 description: |- This document covers some of the initial design for `kind`. **NOTE**: Some of this is out of date relative to what is currently implemented. This mostly exists for historical purposes, the [the original proposal][original proposal] covers some more details. Going forward the [design principles] may be more relevant. [original proposal]: https://docs.google.com/document/d/1VL0shYfKl7goy5Zj4Rghpixbye4M8zs_N2gWoQTSKh0/ [design principles]: /docs/design/principles --- ## Overview `kind` or **k**ubernetes **in** **d**ocker is a suite of tooling for local Kubernetes "clusters" where each "node" is a Docker container. `kind` is targeted at testing Kubernetes. `kind` is divided into go packages implementing most of the functionality, a command line for users, and a "node" base image. The intent is that the `kind` suite of packages should eventually be importable and reusable by other tools (e.g. [kubetest][kubetest]) while the CLI provides a quick way to use and debug these packages. For [the original proposal][original proposal] by [Q-Lee][q-lee] see [the kubernetes-sig-testing post][sig-testing-post] (NOTE: this document is shared with [kubernetes-sig-testing][kubernetes-sig-testing]). In short `kind` targets local clusters for testing purposes. While not all testing can be performed without "real" clusters in "the cloud" with provider enabled CCMs, enough can that we want something that: - runs very cheap clusters that any developer can locally replicate - integrates with our tooling - is thoroughly documented and maintainable - is very stable, and has extensive error handling and sanity checking - passes all conformance tests In practice kind looks something like this: ## Clusters Clusters are managed by logic in [`pkg/cluster`][pkg/cluster], which the `kind` cli wraps. Each "cluster" is identified by an internal but well-known [docker object label](https://docs.docker.com/config/labels-custom-metadata/) key, with the cluster name / ID as the value on each "node" container. We initially offload this type of state into the containers and Docker. Similarly the container names are automatically managed by `kind`, though we will select over labels instead of names because these are less brittle and are properly namespaced. Doing this also avoids us needing to manage anything on the host filesystem, but should not degrade usage. The `KUBECONFIG` will be bind-mounted to a temp directory, with the tooling capable of detecting this from the containers and providing helpers to use it. ## Images To run Kubernetes in a container, we first need suitable container image(s). A single standard [base layer][base-image.md] is used, containing basic utilities like systemd, certificates, mount, etc. Installing Kubernetes etc. is performed on top of this image, and may be cached in pre-built images. We expect to provide images with releases already installed for use in integrating against Kubernetes. For more see [node-image.md][node-image.md]. ## Cluster Lifecycle ### Cluster Creation Each "node" runs as a docker container. Each container initially boots to a pseudo "paused" state, with [the entrypoint][entrypoint] waiting for `SIGUSR1`. This allows us to manipulate and inspect the container with `docker exec ...` and other tools prior to starting systemd and all of the components. This setup includes fixing mounts and pre-loading saved docker images. Once the nodes are sufficiently prepared, we signal the entrypoint to actually "boot" the node. We then wait for the Docker service to be ready on the node before running `kubeadm` to initialize the node. Once kubeadm has booted, we export the [KUBECONFIG][kubeconfig], then apply an [overlay network][overlay network]. At this point users can test Kubernetes by using the exported kubeconfig. ### Cluster Deletion All "node" containers in the cluster are tagged with docker labels identifying the cluster by the chosen cluster name (default is "kind"), to delete a cluster we can simply list and delete containers with this label. [kubetest]: https://github.com/kubernetes/test-infra/tree/master/kubetest [original proposal]: https://docs.google.com/document/d/1VL0shYfKl7goy5Zj4Rghpixbye4M8zs_N2gWoQTSKh0/ [q-lee]: https://github.com/q-lee [sig-testing-post]: https://groups.google.com/d/msg/kubernetes-sig-testing/uVkosorBnVc/8DDC3qvMAwAJ [kubernetes-sig-testing]: https://groups.google.com/forum/#!forum/kubernetes-sig-testing [pkg/cluster]: https://github.com/kubernetes-sigs/kind/tree/main/pkg/cluster [base-image.md]: /docs/design/base-image [node-image.md]: /docs/design/node-image [entrypoint]: https://github.com/kubernetes-sigs/kind/blob/main/images/base/files/usr/local/bin/entrypoint [kubeconfig]: https://kubernetes.io/docs/tasks/access-application-cluster/configure-access-multiple-clusters/ [overlay network]: https://kubernetes.io/docs/setup/independent/create-cluster-kubeadm/#pod-network [design principles]: /docs/design/principles kind-0.27.0/site/content/docs/design/node-image.md000066400000000000000000000020551475376161000217070ustar00rootroot00000000000000--- title: "Node Image" menu: main: parent: "design" identifier: "node-image" --- This page used to host a doc about the initial design, this has been found confusing so we've updated it to clarify the current expectations. While the sources of the project are fully open, depending on the specifics of the node image internals is not supported. We only support that node images will create a working Kubernetes node at the advertised version with the kind version they were released with (and best effort with other releases), see the release notes. The contents and implemlentation of the images are subject to change at any time to fix bugs, improve reliability, performance, or maintainability. DO NOT DEPEND ON THE INTERNALS OF THE NODE IMAGES. KIND provides [conformant][conformance] Kubernetes, anything else is an implementation detail. We will not accept bugs about "breaking changes" to node images and you depend on the implementation details at your own peril. [conformance]: https://www.cncf.io/training/certification/software-conformance/kind-0.27.0/site/content/docs/design/principles.md000066400000000000000000000120731475376161000220530ustar00rootroot00000000000000--- title: "Principles" menu: main: parent: "design" identifier: "design-principles" weight: 1 toc: true description: |- While developing kind the following principles should be considered. --- ## Degrade Gracefully As much as possible kind should not fail, because it is to be used for testing. Partially degraded states can still be useful and still be debugged. As a concrete example: We "pre-load" images that the cluster depends on by packing them into the "[node image][node image]". If these images fail to load or are not present in the node image, kind will fall back to letting the "node"s container runtime attempt to pull them. Similarly we must at least support all officially supported Kubernetes releases, which may mean gracefully degrading functionality for older releases. ## Target CRI Functionality Currently kind only supports [containerd], with experimental support for [podman]. It uses these container runtimes directly to create "node" containers. With the long term goal of [supporting multiple container runtimes] and avoid unnecessary coupling, we try to target functionality covered by the Kubernetes [CRI][CRI] (Container Runtime Interface). ## Leverage Existing Tooling Where possible we should _not_ reinvent the wheel. Examples include: - [kubeadm] is used to handle node configuration, certificates, etc. - [kustomize] is used to handle merging user provided config patches with our generated kubeadm configs - [k8s.io/apimachinery] is used to build our own configuration functionality - In general we re-use k8s.io [utility libraries][k8s.io/utils] and [generators][k8s.io/code-generator] Re-implementing some amount of functionality is expected, particularly between languages and for internal / insufficiently-generic components, but in general we should collaborate where possible. ## Avoid Breaking Users Going forward kind will avoid breaking changes to the command line interface and configuration. Next we will extend this to a documented set of re-usable packages (To be determined, but likely IE [pkg/cluster]). While we are alpha grade currently, we will move to beta and respect the [Kubernetes Deprecation Policy]. Externally facing features should consider long-term supportability and extensibility. ## Follow Kubernetes API Conventions As a general rule of thumb kind prefers to implement configuration using Kubernetes style configuration files. While doing this we should respect the Kubernetes [API Conventions]. Additionally we should minimize the number of flags used and avoid structured values in flags as these cannot be versioned. ## Minimize Assumptions Avoid making any unnecessary assumptions. Currently we assume: - [docker] is installed on the host and the current user has permission to talk to dockerd - Unless using experimental support for [podman] or [nerdctl]. - In the future we may instead only assume that a CRI is available. See [above](#target-cri-functionality). - "node" images follow our format - However whenever we make changes we do not assume the updated contents definitely exist - Metadata in the images is assumed to be correct - When building Kubernetes, we make the same assumptions & requirements as upstream ## Be Hermetic As an extension of minimizing assumptions, kind should be as hermetic as possible. In other words: - Strive for reproducibility of operations - Avoid depending on external services, vendor / pre-pull dependencies ## No External State State is offloaded into the "node" containers in the form of labels, files in the container filesystem, and processes in the container. The cluster itself stores all state. No external state stores are used and the only stateful process is the container runtime. kind does not itself store or manage state. This simplifies a lot of problems and eases portability, while forcing cluster interactions to be consistent. ## Consider Automation While kind strives to present a pleasant UX to users on their local machines, **automation for end to end testing is the original & primary use case**. Automated usage should be considered for all features. [containerd]: https://containerd.io/ [docker]: https://www.docker.com/ [podman]: https://www.podman.io/ [nerdctl]: https://github.com/containerd/nerdctl [node image]: /docs/design/node-image [supporting multiple container runtimes]: https://github.com/kubernetes-sigs/kind/issues/154 [CRI]: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-node/container-runtime-interface.md [kubeadm]: https://kubernetes.io/docs/reference/setup-tools/kubeadm/kubeadm/ [kustomize]: https://github.com/kubernetes-sigs/kustomize [k8s.io/apimachinery]: https://github.com/kubernetes/apimachinery [Kubernetes Deprecation Policy]: https://kubernetes.io/docs/reference/using-api/deprecation-policy/ [API Conventions]: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md [pkg/cluster]: https://github.com/kubernetes-sigs/kind/tree/main/pkg/cluster [k8s.io/utils]: https://github.com/kubernetes/utils [k8s.io/code-generator]: https://github.com/kubernetes/code-generator kind-0.27.0/site/content/docs/images/000077500000000000000000000000001475376161000173525ustar00rootroot00000000000000kind-0.27.0/site/content/docs/images/diagram.png000066400000000000000000011317521475376161000214760ustar00rootroot00000000000000PNG  IHDR sBIT|dtEXtSoftwaregnome-screenshot> IDATxwtTU+ !@ E"E`xz1>)4)TAHG H'($&1@-!B2,2̤gVrν>L2^dX,9)GDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD+{3F۶mZ9r4h}?"EL2rvn…3g믿^zerGHN ,Z޽:wۧ~[ }u)端իׯQFxMkk\Ү*Y|||2eʨvʓ'OG#bcc5k,?>;BJt5׹y6mpLŵbŊ {gHxkݻg:/88X (~N>mW2_oK"EԩS'M9r˗/+&&ư +Y,n4u֥k H˗/+88j[*UЕ+W4dM6-[׮]Ӕ)S4eZd 8KZ/W\&wGi˖-cϟUfRGu۷oW۶mڴi˗grGx7|]ZjYJڻwhѢEj޼yV;8gud#GZ>}dr7 ֭ӸqL 80tY,uM%[pQQQjѢgu; G:t,Y^xASɒ%ծ];:tHŋBBBԮ];15ĉ3#ѣ>}zVi&Ogu j߾ݫ۷o+""B|||-d5iDцcʖ-UVٙ`!Cʕ+Y݆dX޽ӧOT^e:ڿ&u o@ҹsg:uʰ?~_^ Įׯ_Vj*_ZhiӦ),,L#F0oXԱcGݻw/@JpT:{nݺҥK;־qΞ=xSN~˗gĮҟbѱcǔ )R$C'OUŋ2^tߺ{|||WddΞ=+.]ZsNnzΜ9#777/^\~bXsK%JP|>W\Qhhr-lƢE;f͚YJ˗OGVXs}:thuEPB*^Ӵׯ… ~-*ɓ'gϪTR*^\]]3~EϟWDDrk@NE7 ǴnZ}I|ܷo_>|ѣG^zIݾ}[M4:y߿$ݻ7o͛}%kذajݺ]Q.]ٳ5w\9s&ɺժUSz()^`޽6w9r7nls9sh…Vko:ud:sΊZ={ʕ+gEGGkĉtݾ};VtiѣJ(s玖.] tRW _~*P5'MuYmܸQ ѣxbݿ?}vկ__4amܸ:۷oO &޽[f8E>>>]OnWܺuK .cǎ͛I  /^zIa`&44T]vM||e{ر#uxh*U~oM:U[l_$zJjӦ^{5Y#G Mv'xB%KT*U4n8_tMG͐PiV8p?[Ϛ5+siuĉ$Y5jaÆٳRݿ$mٲE+Wի}v999rʪ_ڴiu^fյkW?c5k\ѣ2e~W$| *_ݻwW:ulΝ;[uE]tQBB̙?8shܸ֯_o X$EկKԩ˸q,߷Z,K\\ewww~%Y/nٰa͵ufFddeժU[otp+WX--[[?(&&һwoݯQ-vѣG^WppiW^ydM4DFF={ҳgOȕ+O>WGhhh^K.{0a~ze8u{=ؽNXX {VZgO__}Tb9v]v5?gyo{Ę;((oqrrr-uOS6mLw!|Ԕ)SԾ}{xʕ+Y`&Mu~rQFiΝB iÆ {trppp4҃Р=uO{MUoE'|sxՖ-[ܹs)wLLFisܯ*??TkR|7nΝ;'[ׯM]~]"##SFfyg^sNs-[va:/P@@@ď[f7ong̙3գGTΝ;jӦ<5}v jӦM*VO={Lvԯ>,T}-[f4w @ND->|Xڵ3ܽU֭S3;wz# k7n0 }iܸqugggZJe˖Į?\'O4ĨA:{l5l0 kݻgZ7p`9sư4ѣN8u,\PCMѮ\~]͛7WlllZBB7onJ*\wLglf8qaxڵڰaC&wqyZXXa0mŊ0`;ټy}]1ǎ9w_ťiӧOe˖Ek׮UŊ ;v.\>$)$$Dm۶M<}˗mB/^,L_ڿN>m8VllMENNNTj֬۷okƍzj< **Jmڴ1 |jԨQ&v<<<԰aCyzzj:z󏏏נAvZ1cƘ L2ӧm6c[o鯿J2ɓO>MY!!!ڲekUViVTLLJ(uꩧuqڵXG[n$y{{'ف088X+W:RJjӦMk^^^7zjժֹsgXjׯ_RPP9rҴiԥKɓG҃PѸq[>>|XWVL{JO w{u)c]txx~$l?~U^]~~~ Ѯ]L}7֭UfXԡCjժ *(<<\[lѵk׬={f̘@[n5kիgXĉMSNUXQ ?;wi&Oj֬}OȾX5iXȱc^Į۷FB %^KHHlz'Zll͛g8M6lP:uY,޽[-Z (k׮00`@&vq<<@SL:Zl'GZv.{>LN_ $ɮ_~IѣG?7k.;v, &#Ǐ7uvvV߾} ^zY=jbz ?/ԢE on>U.] Ν[ÇWΝk.kСv@@e}w+jڵʝ;w&vezʰڑ#G [3-=Zt4#ÇuAkVR͞pomXۻwoׯ_׶m jܸY =zTQQQv;|4#ggg%$$$]|9Y;=69vrrRʕwޭt}VN=a_U>}רQp_e˪w~nsʥ&L+W& I-RNxuɒ%X,V ʕKl{$҃ϮbŊޯZj/}޽{MRti[ήf_~|???گjwTRpPozg϶Αڳi&,X0َiKv1_Vt tIw62T͚5 e'NH:\ryҥKRkdfdzߣfǐרQCr7eʔz=**fy'O-Zj-8884ؿ=z5o<;v0 g뗪cWx$+cFuO;t_ck֬i^[bռysYf~.\X6mR…Z+_߿f2AjJ%K6ͽ{tmz3/<<9Q@6!!!Iڵkk9sFNNN6Ǝkƍ7R$ hΝvFfkdAh{͛7ƌcٳg5sLuU*X`Ĥ f3oaʕ+ZhhiFGJ{wOyI|C<њ4i=۪U+]>mެYLwq4/_6999<ߴ_ÙM0 ܼy3^}Fٯ^ :ؽFtt6nܨ7xC%KԄ >wjm) 3;MLvCHhvLov .Ze<ٞbQuݬn%]=/// =z>sۆc,:vd'($=`=ddzۦ IDAT3FٯQ .Դi_?PKVPPPz%yjbk׮yqJc~ *dXsgܹ_fܹˌ c`[n{hРA2eJVff>##̂h{1b$>ӏ?hC5fǗ:GC~˟?eF;^YzR۶m5tP-Y$EA˗/+ @W3ӌ3vX-^X.\HV;t242 -[6Y,e=߿oSݣ_fNݝ?^fϞmsl|~GLԩSնm[ժU+[IC\\Q+VTŊ C9sfoQV L0AڵSJ2Leygp\"E뛦>rfQUB4_H4Y><<ر#s8fQRkdov4lذ U k˖-Z`ݻgu۷5qD}cFhݺ4h۷gu+i'o$V|yϟ?oXN駟6k;5899iƌZxviuLΝu ,XpԶmt\ζ񜜜duE-R\Zxx ݥ?]6mڔ8&=Vk3gLrd9I&;wnڝ;wk28pqjduٳg9_G۷O7onl3wWLmVfRXXTb8v۶mY={\]]4 ӄ  5kTɒ%]Q{}}$Uf86((Μ9QFY:q]J2o|Izbцc.\ 6޿˖-nw_bbbrlMܹn߾]eC 1 XB3g̈r={_4߻wO;v4 +Wp/[nY=pnŊ k{,|*Wl8K.vZ 񎍍Ufi]v^zjҤ߿os<9=䓆.]^[UT9ɓ:|._lxd-6liΝ;ԩ՚Qxȑ#}_ ޽EСC zRÆ MgXt%G^|ب(9>HUV5H߿?5~ձ.]?`j $]|YSDDDtR0ڵKAAAڳg⬎ݹsN>ڤI8}z z1bkҥiώdڴiʛ7oVf кuTvmcxbc={ɓ1]oٲeΝkuy8`xCB{=:޽գͫpbbbt̙ϚC~zkdkܹs .]KWv%Kp)Od@G9O\4|ã*[;s3fTjד&M2 ʕKk׮USYR۷ׄ eM7o￯߰n=}Y*TP֑|YF*UJr'жmTb4ߣhѢڲe ,浲bŊiYFTXQGQƍS\9}7rvN_7o/l:'ԴiL?QJ-\0Mk<*o޼?aڵk;jVwmM)wwwm۶ !L\h\rehѢvɗ/mۖÿŋ/={8p.]Z޼ytRyxx~-[T=Rܧ 688ydu8777mٲE Jm{z'4qD-Z(C{QRFB4sL}嗦gz=ׯ?S^bŊ:paH"ڹs7o{xzzjͩfg3M}޽[/֩StI;wN҃c5lذ;3W^u!9rDpܹ#OOOyyy|j۶5k-vءرc|.^(WWW$~=z뭷}ǵnܸab޽O?p[oٳuYʕ+M6)^Ӛ_~E{klvܩm۶k׮^ztmܸQѣGu]xQ|}}վ}{tWjԩVkӧ|=ܳYr"wO?Ǐbǒ$WWWթSGkVVu떞y9s&=W_}mm8YҸeߢEԹs*%[ [pNW^M>LŋYF J3jJ_֭[:zhNNNY S ɮ+VL?SZR{իbɂnȞ8DDDDDDDDDDDDDDDDʭ[9۷md5}t):::,\Puխd\Ypq;:z$iܹswŋ駟䎀YlN8uM} (mRWu޽kK.%֭[AYu]&M䎐/^Ԝ9st1͛WUV[o%WW׬n-S:uJG$?;>*)NBbi b ,bC)+U (kAEUE)"LNLޛ ~:{9&3Y=$O>MrJxwDDDDDDDDDDDDDDDD O:_{y.\sΩA='5{`a0tCgwڶm[:gΜW_}Z{'aXjcƍذaj-))Ej?!Cz?K] رkxdɒ: QsӬfҬ?u l|زe Ʊc0j(od|7&$$`ĉrJ֠AЯ_ZX`,YZ;v,,L63({=<U`<Xnv͚5xꩧ0w팈ሁ]yN'Ǝ|qٌ%Knݻmݺv;>Ygy4irssUkC\\\Hۏ?>@v뭷ӽt6ƶ.[ ,wu$/Ç#&&&@#"""""""""""""""?0gΜƍUk5wŋx7'Lw^z={@$v#˳gu_:8p$ѱcZٶmTkM}.\|o߾of,[L6pzreXgGb'|fa(//V.44ݺuIDDDDDDDDW62jR/***<ƌF#Nz}ΝaX`ZjvG /??G Wuիn)͙3.\m<ǃm IDAT'x"@ }XdҥK1vX|ڙVZ)S8[!"""""j6駟b Q0DMرc.#nKBzk_jNrJ|x0{l]T}U/rv?"""""rssK/zDDDDDDDDDue'BI͛7kLOD̓VO?mG@oْ$)[ """""""" (c7pĆ p18q'N@hh(ѦM =zl:$aÆ 8}4233qYdgg#66HJJBrr29O^^~יfffbÆ csTY|v[ivܩY۲eO())AxxWĉ(//WCu~w%%%ظq#N8Ǐӈ@bb"RSSѫW/tF'N ++K֯_Ν; ȑ#@RRytgPs9}]sdggޟZ]QQ֯_'O8{,v!-- ]tx]vk<44=zۻw//_Ldgg#11m۶Ejj*n&$&&<]wڷoGz/[ k>"""""""""""""""@cG'O zԄbx'Z?^{5<ܸq#&NӺ/ŋ=rss hY`/X\s58qWYYYHJJro 1k\_m.wǼyd𓻘6lu!CF6mT{rJ\c~CBBB~K,Q_c=Z۶m?Z&&&'I`Xj{ԩ8pxjj*vXj^{5/4WZp`޼yxg+o`@|0aBwADDDDDDDDԼ`G޽1wZPVVsW^XvSP:͚5 ݻwZMx||}W=z4xiPMYYF;v,yڂ pw)(]x 4ӾUQQɓ'c_} իѷo_>H6 >(Ft&|ѯ_?Ր a1qDw}]PP;|_hWZ%[SII nwq=vm^x~moL{Q :9 Jm۶yv4-~\prKI/"6nܨY7Lu[$?Լ榛nœ9sTk?z)i}{Annn,˘}Y;vė_~ U;|0&O Y>;wcƌAeee ?SmN͆QFa??,9:k,Fǎjh9-؊+tK$L0۷oGV֭&" 8)))8<2224tUVVbƌoqqqAL9sFޔicLQVpРA>ѥKX,]j!=#}!V{A^NJpyyIܹs:XOEE>l߾ARw̟?_o3gBx_=BCCqQ̛7'OT~ΝXr%N{=}v̀`ZZ_oޣw}W9F# N:j?ٳgcĉ?H 4sh׮ǘl\9 sbذag!ػw/wÇ3220`QlMVnn.?^caذaٳ'}v(--Ō3TC[oaĉcǏȑ#ޓI "=o̙3U3f ^z%ͽԕZbફyQ 6xN:rTiu4hA?Oږ-[<z:>|3fЬL&<߿?vSNa˖-xqE{g՜.r?/`|7HNNּ_U͛[=6b<Ø8q"V\zߦM_=j]vEVV}oy,5\~ww}I&ޓ5k[ot9o<͟-܂~[suecCBB#==:u‘#Ge̞=:t .h4{Űap7 G3}v~5U۱]6W_Pf2GaԨQcԩ~@FF漯=Ƶ U1tP/^5ՊݻwgϞ6%Y5\{V ݻ^Z-خ];DGGcs{DT7g cpb2V%ֆ`[W Epo蚔VŁQ1B+}:F;f-99C Q=`0pHc-'NDIIj_~ 4Hٳgdo j⧟~ҬϚ5#.55~)Z?q8iܹ?w `ԩxU KD)( Łd|rKk \EHeZk8uݫZ<?~<>#`@QQy<3sM<'OO竎׵n}5|U;x^|>}W_}5`Z[nfw7Nm۶۰|rҥK ;+W?~<4('E< OZ/..ƌ30c !-- =z@^ЧO|r;wYYYFNNrrr43Uc_:W/ۥ FPd%$"""""""""""" .]fnV#[Fxxf0;;#xWcΜ9xulؾ};o^={O>d5"񺆸z%KxKC}G80ubb"RRR1^Ha=hi^ŚG¹sp¨< ֬YqPX+Wʕ+iiix't;5_ӦMÁtW`vv{j z& J4F3`0ЫA IFXy5V ]wn{}ݘ?>bbb|^P޽{c#VՎU;x JJJ<vgXpui!?at,SLW;Axb?ޯN'lقqaРAA͆bCC  ~m۶fMw]s׭d25N$ngC D j௄v[Bi )&"""""""""""""jaA`eejִ&wq6mڄ[nu`ڴi:uju!44Tu\3`mv ŢZۿfokL-(Imۦ۷nx(((HfZ5kdYFqqfhl~;w܉9suOXX}]|HHH{}aȑXvcرXti( NoyW~ a Bp8!"BD+8?k^B `2+MZ(k֬?ާ9dYŋ5zx,Z_5N4  bܹ>_Zj:#"zZۻwj0:::tW 1ZtYpiuNYn̙8| :6m»ヒ[ޒ3FV9~gzǎ1w\lܸ'N@~~>lٲFOC}UKll{j yyy5D@t} 2V}YNոOtW5'DDDDDDDDDDDDDthH/k077WV_ :CTTT`֭صkn݊UVv3gL})ǫ5(oܸk|as"::k//Fyyz~6(DGGɓ^5Ázk֬[TTƏ_}$pff&mۆ;wbڵ8x6 o&z-,c̙)S`̙7`Wa h֚랉Z2Y hDU!;JMYt;Y D? \|5uzjP Ui۶߁` 4O=-ZףGc͚5~X +))Q=zY 8k̙3Owozu[%##N7(X}5ٳVJJ F_ׯ;#0zYۑZKJJ¬Y.~_}C] ߯z: & ѕN$'`-\4%W?C.͇\Z\ nU+ʋ X^X,33֭S 5kxxb>}PORSNXlڵKm 1U>}>==]kxk]둙kdYg}Yi*Xl:ugϞ#Gk׮^BazzjFA0at~;v5yyy(**Bdd?СC!C4:t7?V=p`u:KwV"jNPQ8 A4&Odk lV sl@@ Cyy|` W_}5Zny}vϟ ,Y/fM$_Tz-^^5I0elڴ fz'?OհCпkEdd$Μ9͛7k~zyՊsVNHHتU+$%%ivܼysdѬoA߶m۰m۶.v?0j^zj^9Ǐnݺk_ѣաMwF} 55 /^I&k#F`u\6=;!"""""""""""""""?k1eX:u>Ʉ{ VBrrrׇT_tMX|O]:uꄉ'F} hwEmϧyDQZ_v'M%K %%ů{CBBꫯbҥ{ 88X&2L1>dlڴ {<裵 {5kz)FuS+VЬnO>/Ʉٳgc޼yވ̞=_}P n@6lٳѯ_?ݣ`"##_ ~ilܸ?O!য়~²eR7sL{k4@tts0`j/󜵅T1eذaغu+^}Up &&&#oߎSup i/fɓ_5ǭZkP<ؽ{7OSP֭Ô)St=}f3^y_}w|g 5]z~?O?EΝ5 C\\[yyy˃@rr29l6N8GĉBJJ nzNNN<'N ;;mڴAp֩{gm.^cǎرc())Arr2t֭IN\DGG#>>ڵ (6GwVZ5wצt:U70zt6D䣽C]/w 4pS D +ˀ.!j!),@e96cB{%<1Hy]^[tp:.0_&xY,deefCDDDDD6oތnkܹsHJJ c7p9ưa}QѷoF_')) III8p`3vXL>vc|ƌWT*Fޚe6ѩS: !%$$ !!7tSWS۶mѶmۀݐ߯KKKk?""""""""""""""""="={?`GDtP> @j"j*ٔVU\fDDDDDDDDDDDDDD5IhӦ"رW;v,  ^O/'Z.If|^{oQKy4>>cB0@6!@H` Rh#h\]r_h` 5)ÀDDDDDDDDDDDDDte`\zz:z!7|~avDD-Qqq1?~ܫo#"""""qA!(-(@*p@HJJPmb o 33?"((&M Έ%(//ǨQpaڬY0r슈 FU]\cJODBp8W`W A!:2 KQ 6)Gj/l6[vED-_ݻw{?S2eJvDt1 J`OQT-'}ag.{֪'"""""""""""""jl6cŸ[[of9;#ȑ#stV=xW+RU:8|1 E!; N99Kܔ%'A,,n" ح?$"""""""""""""jA ?3f?,3fLED8 2VBll,^y7.ۢ+TaE(q塁NtQnB+Ő% Bt N^nUlHDDDDDDDDDDDDD-p>}:n+6lXDD-ߏ$꫈ v D\,ĹYC&8e,zVȥrcdpDDDDDDDDDDDDDԒ1HF=lٲ@oZQFaԨQ$YnB-K>V7u2s:&ـ DDDDDDDDDDDDDB1HDDDԈ0 qEͺM6Q Q (!F;W$d# YWvɡt;xX؈֒P 5kȮx`,C[rnuP^EZmkA&ZH#iw=w+2啅!4YEѸ6J`1!6ϕ(B0CMQBk% J%' @P0"[q\R 4d ̡V% ; <rUpW| DBD ] $"""jrcx F{o\׻v|Ud5U.] 3Iw_[pWܮ7=筺V4*k2i~r} t(Ӛk@bp"7YE`p,)ѹW&e"l 1HDDD(:ɀnj+kkyYZ[v[Ck~@`սn=YQUڟ-|鹅֚_U(t= (Ȳ`)îTnJ vm#٭^VE:HCbxB*m"""""""""""""j  %+GBP:Me;lQ  KөtKz|)'9]6?9vYfWw=Wa^FZYm}t:"]?YY[dPm rYk!87Syյr[N—`ٶ1(FvہvA15ٝ4rY`0A¢AKr!8`4BvJ@EBa FA0ղ6N;P^80Ac "dSU+G# - ry!dBx,rdB(dBD<F3 9 M۔c+!]TI5L2\(kƠ#p*?#ۍ6N1HDDDPTmeJ/8n\Zdvuٓ,v+P^ V}]Zri>P|.ˀB :*Hk~YWE1PQ !DU(G@p8e@Y!){ ˗aʋ\ %{т2 "~;]gۺu= VZU $ 8Vk.":NDDDDDDDDDDDDD@"""fAVuT*ٸ#qW u/디0v^voTٕ^ۦ%[ZA:5wk<:IX4aˋ$XqUU߻K!Cauq8SCZDDDDDDDDDDDDDWuZITWC.+x@~n1?ͭVgL̋{ӰxGd(UE#` SƬJ RK%81!%(N! -c"ѕ@"""=&ps0Z*A>PKV5j*uA亮}Y :t~@oDDDDD\vINLAmf D%@J &aѐs3!e.J@T͸XD6'BqpKrJ1?ݻ#"j֬Y70ѕ#""""jNsjlbk GB t Dsk^} DABXP%K<&~:""""" @o,(**BlllBDDDDM@""""f|vM,"Kp ʀh vZBrZCEUA+$"""""""""""" 1HDDDDL i@ݬ ʲrԯ)TJNn^4@n Y4@.8*k]K*-8ܲpT6ۚ(pAݠAM""B`Ϟ@@""""fHWhN"`w`,B!<v',a$8,S^ J+p*/uDݓ dA9(0d/ě!":Y:4л """@cr8E\,įGBqԥ F (*>5 LA6;,6 m,vɈ֑H/D;]$"""j.dnJk밇US-ֺU=J]o~Y˛C6\a ~:x=rJ/= 4ZX볻 6CL$ry@\A cp"7g ba)g2s8G| ɑ<%dJ'@݌!0ז !lȮM]$""" 0%!(a.c`4+-Y@0(Fr׍dU}M>76 ūڗ)ȵ/ Y4zタdzkD}`4=w 2fʏ)H9W0~Wp$`)WN;N뎨2D@QEvf I8r1 .\KBouGs)! ׶0B>#tYdg ct;|7N{+&tn} Eh +S"""""""""""_0HDDDh+e(. 0\5t3A*5Y npf (EsK`x骺@06AʜF2G fA!h`2_ l0AJ͜ dpz\AQl2 ;!-@h{|W$̓ѳI\weEkG`v(F$VeIX{3bBJ1&-[ԘlEGwMWdm/#qb!uAۅdp);zlA| Q ҥ_x,LAمD&+ ɒ $:ɇ#y5 Fݥk( ( R:Imm4P}l—fpm s=kUC |5Q ^'\ǖ#8 g bkJ3(DZɲ3LNhXÈI1hBUvI\l̂X)$$ t[{nƋÿE횮$#±l_/>GQn.˲ŠP:%hS% k""""""""""DDDDLCA 3UuEen`2֔]PveI ,*k WSuDk]ﵵf{F 29"xTl;}׸``vu<_wAlʔ$t|D1_wŒ(T:> NIS2xnM6n 萲&1]i +B/+=\Š0+jvI$JLEJtZ+[VsW"""""""""" Q# jNX\*{ڍ -'b/,B-( 2Dal$uYz{%=` VУnH,Ľ6} jt;3S=}PPNID5;\KrJ"d{8 > ؟έϡ[U \@"""jNc68Szc9Qns[W!KD9JW4zB!W2l-A8+ ;N# BidQlp:Ra斅c8|1 !  ]Zg"bz9(J7HC=/ˀ!8 %U(8 JǸ9dǍ!L NXv>x%YDni8~:x=ZG{F!]DAIttD=w5%AuMh(P$""BQ2Ntsm_ua9 *.Y A(quk {kzzmkTWt!R8g5s zd[|+33Gsa1x%Fᚸ> t)RU?PAB"!;$YDVa 팎 Y 6S hgHX&dFҸ#+ Jjke9/ipJDDDDDDDDDDDZ$"""GsZc6-FOr{%䢋 oMꀐ (V%W߄k_m yP;aׯS0\aL㮩BI$lκ[r B,b pH䕇Tj6DABRNo#ꉈZ3Ԏʬ~!WЭ:'_Z-#טKW_fkҺM@Soȣ(1j>#`@,D'‚*`#""""""""""""j$""OdšpVt5š/~܈k{v@U#|(XAB Wr@DT(Eie‚*58"+J~7'!F!pG!9FjlGLJlkšۦ`b-^{R.6~^YBΟ'K˹sؚQjyVh:dP5wn[#I|֟S6B!B!B!BBB!ys8+˄T4?T4-T*8J<~C- 2)HP*scTG6[&*w:'@v|Lg|r<4]Gٹ ', 8 wޜv֥,IHƪ"zh挟N@&ApFN,pwdyC-{nsJ茟@ MYLQg_<\(9ܲ|!2Tt6dN;geqD<\ 宝`5N `+Pe2?g<#_Q٠u2d唏}h(4=#vڪ>Vҭ9|,B!B!B!čBB!9Q@C׀TGM"=rµNON; vt-T ?h%| UYTlpfNNٗpA Vog 1;!'5Ej*7FŇ %Ugo8#AP0K#ܩ8ף<'d Ȁbb^4\`ۨD;7yNL cv:3_|A`|g+snTvҝsMP!0CNͶ!@EzPh s2e4i4b{\05V Ԝ{C7 my-?L8#6T}OMgoi#R P!B!B!B"$(BdKNT<[+PX:Cm\:4+eNiE w+TL'{vBsLa^6uéf9ARJuM j*[Qˆkeefکfie{iEo/l@ ;k}. }B!B!B!˝B!r., Dk0 :Moiܖ錡?wlvq3 Ʒ 3y|}mMkO7.vJ\k xs:q|ufv܇܆0ǖsd[%׎YCs Xrg^r|l$%<^?u'gSy1T ҉^Z荕"Mke+ua5QOB!B!B!qHP!"9m}U6ˆFi~ C=;Fן'[!wrsNi56\4zٶ`tHF!бAc)zV͗>Ʀ.B!B,?p^(R× bF!B7wJB!#O{ޙ#trgr*q)e.WݯGnBjѪVUة<2ɌC UDB!B!ѣ{7_bFAx x_HdW$B!ܒB!Kn:o4T:J%f김lX)2eI{ 3k7r~h`[IQx n?͋>!B!Qxa8wY;[8wm?RgW֭<åpp}PW7NKS\8w}oNd".]~~SRB!˙B!b 24Cܳ ᡱ~*>VfJ?ۚYw߹V9TyMț}.}B!B,='NL\T[4 <w+̾z\w7|cO~v8?CX7pBƄC3'(KB!bNIP!B%2{m &㔂L5ff= FXdιdRym5|4+ʆRɊ]B!Bʕ|ya!߶nӞw6r*=|fɇ>)B!r&@!B! Mn%qVbèxiϠ mO]Z`h bDQPָ ⎕m|XW݋/:B!mۼ+|__!a۶e߰a"P]=*,O81n&[p)Uo,B!r#@!B|*W@]5E޾fJUJHMXäSL֐nVS<'jq31uܳTnä.*n՜^&GPh^>73aۨD=MSTblֵW(oB!رc|+_a۶m<#8p`$&g ۻa#mãNOnf_TjvGX7o@2B!b9s-B!0*yI]/0٭IkKL&Vk2Ų3n{".0Mu^@,<;F*pi.da(r;:dsm'[; qF!Byǹ,AXm}azxa^'8{v}mv*N o6/ÎǎS/|L&_6_}`,|̖-NBsvY M5~7 0B!X B!tpysTmU5P>0\ FvӲJ4@Ùts\7unhp9Pٹ48QCngΠ3Rwwа6a|(_s\}I)80=~|@SȎ?vMt4屜{f׼Dä&e6u䝫`@*#(o-\ԃ* ŎtCbig;| 6Tww/Pƭ[S (B SO~|^by?!nm09q_ kl_:(D"ϝ¿ [| oon~Odޖ`2 ;A@!B! !BN΅/sBa[6h 78vnyPV}Ah mq ʎ[Ь.ȶS--cSV7XNoP@J_8/49UñѲ4O`if9o]Ep?"+ᢓf8)4.ltƼv9iӅ/gVO{Jv e;Ӹµ.㒰m14 ƂU:X=m*qVb`ɼ31zO-;WL]P}E^X]SX{E)eM荖sg%5=@Л,Žkx\2;y3Z#U %僔Ƿ27)1^*g d mB\4y>7q~Έw~_h&.'8o菜B!B,'B!ͣJN. '$NUP%ZqpB] yC6-|}vJi(\ -T9v|vњ99DƎ @c4M7 VV0t-X ʜb.,g4SO VBbZkޠ#{=r K_Mww9K7XlJM !( J_=?9C? wf "](_qDN*Sl1۶`CtR5wSaSas]ա(>y\) [i(4l[#s9\_>0X-VMu]\h۰sg`ͅ\/eD,5ZyF^CKKܳ4n>em4aoeh s,[wP-' Kvwm2 e7I]h]5u)VI?JvsxWm <(z~Wp_gsme DN}(TZG O9Vu0Ε>OupdtK7 zTbx\&"ƔHdٍRx?8ٟk=Z㙼OLic9B!XN$(Bٸukr$sc:LU@؛E߼Be zToޛ„֥Bk0vڤ^Wy;o9֖ {G}НʋFKѱ'seJгbܶZ61 $𭻲v4zNemj{ehsl 9҉qϭ˰ٵn?ίUVJg: }0DZ6sǤLWj_S E4OESi@wklkh:*8蕶B4r_='n:Nu%։& & yeWp]h(*{vpPMmhy.ФցZ,[ϙضs} VXUP]jr_^Wf\xkk<eC`EмrbZ<{f7D0*foruo;=ϲsŵр|K.چI_Ǒ h_뜇36T=eH`$?~נ2-|dq6tPue4-X[#}: - 3C[5-S~-LWKvpZ#qޑ2b!oIxId vle]u/U=8{]xc޾BG xV3˿jvz˖77V a~7=utp/}_ix?_ǜux!?ga8ynuf|/5[ !BGB!XrGJqxӞ,Q ӆTP-[nbۋ-=@)2ֶ=tƿ~e;sO|C(D3ߍiZfPfGY5U+ϭl3R l3;6:̞{wڇ8ٱ2vsmכZVu|SiBjr+k>kg2d bd&FBk*ȶt5 SJw$LKͽ Ud$T^/RmP} ¾zV|u]I4k*TUĩ$3nbar&tB .Փ6gYH}#e{]jC C\4uRS&MnI:PCp#eXCHx.M4-wޑH.ʆR-Tb_JoE)en,L@ ﶭxe3(; sm֩ F\ۉϕjH=ôru=qM񹮕\kiE,;Z1n{`ݫ>wrȸ󡹷7.7qVt68TFPhZ:&}tF*JhݶF޾s]+鋅QjPm1mCմ M.hge n;63G,>x ѣG{9B^ n m=W<8tMMi2Y(76o.}.ۆ/DaRtSp}lcz}׮.]`*+wXYZg!B! !bYH.zep=xCzpx:?HT*lnC[T fN(ltjlOPmc2`_T: 2י;G*#ཌྷTFXQ>C;r0/\4YX.ݫ8vW.n'˜)=)lsoiLSmmlf}MDזnM60-KּnB NvxW۰ض`V>!oFY 6艖su=/Kv$eN'񴗃wgU ~ܹᵚ FR>9zm=/]sgv3.L^u\j6vƪ>jC6uqTbzT ,eNF^X.~#):֢YU>@]xfa)su| Nu%UH[6v&bi/s泸m L3ϰ~^|ELs6BqJ.N}(Qi_ ?~ŋpptim{m۩o~i=|#xv&pH}P>u=\xߚ5^M{'~|9B!B̘B,-7r^9xɩ\z/ߕ+?f)*۱f7nkSM7֎·{}^B!X^$(BeV)MW;gjRpefwɵ^Њ2!w>WmQ ͛lhmbѹ ә׹S#6CˡXX:kElOw>\p<~BpXz[\|JvW3J' ɨS 1kq=[Orߖ4v.jʹ FR^xv~vzet 3/z,#k4s֐ zakz$\W->wi{m7[6tW6]سh9SC n3`$'Ўϕ[֞TiE4?KGbb1 4ICѤ+͵j"2[е' rs5J.q.6I}}xx ] A{x{x{$0$xIdѤ;CH{V0OKӔ4=\#R`T(_i|&} ^WoiofΝ[n{'$jsa: tͩgp@Z=P˩`inO\4wDsX/tZ;'!enXA,tMӷEY ;ynjټx *`$㝫9ӹϾO>LPh\ ăDˈچT 6hk-maQPS l;{9ݹNYdkiecM7wo8gn{=[nJ{xe#WIygU [HluP)e qc-ݼqi WJ߷zGx<ݫ2wo8OcM/M2bixĭD4ò E XΕ:=p=Ԅ uMC8ᄏBLS,> u8pǬsZs0.y[r~:z _sLNo|粔9RiW+3BCtM6 aժYB!ӵ!B̅Il^ޙ v޼eŚ{)JՌlu?-3uh^?xhn?`dqAR f[B*"NPYft©I:ۖSr4X;v+i([bm!3H{8ٱg3F-EɌ:InAHO{W /]APi˅m/nHڼu}|zܹ7_)?Wjm 'tW."JR^*⍤|tzW6;)Ņ\;y6:"$3YѤh95 /]wvF+ae…x8ݹ9#e|b;u`TW:X߼1zGN1mHV)'z$>__K/aY?,4cNJ?_1q=gLoo oklt>>xǾzq#_|4;K=XkG NUJx6mu-@!B\HP!˛vڣN*R | O3lp 4w˓ ƎUU2 0EYSOe٦2NLvf+jg6|!vj]P";?n\V:#)hkŢSLj]Kvpx;ĩqHW F4sEԜT)+P<ٮU<n7/d:%F*Lò I?.mISfy s{%\Ι՜YA@Q:sJͪԌ4 ^3]g<|rZoɎۏ/nj~;_Yv-?^% !A),} }t4>|.l?v~Hz/ /39zKE1 7$*B!R$@!B,on/Z|~45VLם6B,)ِ@]n'̪eqZ=OXiTy-Dz3Nܘʛ6.kM2L4M|tǻ^ In罀2z~zjMs ,EEHϜKs_=iӽ*Ypm'߽X'M뼹J_yHgZ$3n>}Y}uYi8z'Oޑ9k{CfDZ͵}?r7/_ؾN rq;}#a gPӳ_cYSٷ8!o??c߾}<󘦹KBLk_sZ/zmMիСb1'X\|K[_7XU v !BDB!X\N*u 'T%ĒV-˅hfYLU7V N/9Rb+ἶÚ^R'޵{5=)tmqc5<Y2:X߽ 6o` 9hs޼Do}7㚿p2:?>;¶O/_P?=eIЖ) Uqm:pFg(;x`Ig쏅xN>y+5oe8. ֹ_;t?#)?<ʆn p_ݼ#Ŕ<?0?0?8=>0}q㨒dO=FfT<kNۅoږ1Q"_"== !B!fCB!XtMҭ-5-:Սfy@gKV+: =RfvZ&S d24lt]a6*XWǪ| ۸o*#[O)菅гO ;_IGZ7Jv.,鶿SIfL]8BhoD[;R߱ETWW}}s?}c=/a`^F-;'&7|38X8R7_IHQp ؊B!B,EB!IJi P0Eڙ p<+, 1ml &cc?]GltM-E#Ic^q3otMsgs~3a:WjxN~rjb ٳ9p}ViEwx5ݬ N_yy+d,^UJw|΃tsŶ5~Wsgwc:2%}2*ӷ]BfVɌwyc+ƥ&Ikػ Om')jjËQ-[}~۷$^7{nVPKƍ󻞥d>#>bHP!B;6B!)o_G,5_4@/F^B0|Ӻ7pS!0p Ĩ S)yutpשVQQK]jضFpuwi-u|p:"7D-_,->/&w6^2vOʾwGZm'Ҳy݆5itGyDy6,޿<:6eeдɌ+r/㶿Xʠ?z\&8ao< sOu^^~}ꫯ %ko^60P|_M|o^15B!ˈB,\᧧;_ 'ju&\MZo;q:8XueMKɳ`Tx]-},7:Wn[㼴24w$K )xT[i^6rV?ZjIdxT P*@,DH JM+eT4Vl25\BI?Wj8پ9⬫N2nSB YϯkjB-]46ps /ɛW6 9W,`mU(l=[N{z_hϱk\3mu&K䐙i ƃt3 z v;[7ۏ|rҭue(i^`5=2Rfw;Mcu/5fNJkTJዥ% ||3~s^"Y,]nwD"]\h>W\G? .b]B!q&B#R ;Ie&|ihUJQ_ո l0pZdk:k6:yp ut=LKg K[8ӵzN4n6,484bE.}i¼ղR:L*NYiNt3ۊ)MPg:WcEMzz9rtD*`\=ᄸTmDKjm:cw2XN6ܽ7m sz=7/ou&{o-MSx mܿٱ5=3/e.M\5KpתU<^.صF.Փ2]_g KMDjQ\o-`Ǖ.e}uxc V<)Pa+^d]Ui ҵj*//}Go>8T=J*Gӧ!0\ yc.ASS}\\XB!ˉB,lѻqm&թvL(ҨJ$Ă-'79*1J%pa_p_d[CBxA\N³p{՜Yn:GE`7k/QS 2muּӝkTi ´:TCƚ}MggiV#޿<탕\e$B 󺕶TVU uU}3 Xsgvor9h\Ᏻc5}p[\2KjZWlj46M3,[zӕ :"|{xy;=DI>8(p'E;`6ocߑx􆛆u]ܶ T^} M[g(ro=ԉh&*Rg,e0qkc%C2<0|($++6TPU+ff޽ݻo|<۷{LFo!ΝTk>ywzU]Ky^Bt~=\2ytO> f}{NUNB!F @!B, eCܽ/_ε= a).Z03xm1L*1UƫL Zkyy+ͽ D3/2,|4aoyh1n!O6,<%̩􏰶\ir.53RN\!=Sk$Xƚ.VWp3ܲ 8rV=Ntsg]i!0*eJ^ɧ=[UP)'tػbkpnוC[OMgy`)$ eisgX_C`[;8vm=IfJ Rj<YXǩ5\R_=ϞDE޹ab 5=Zγ{u f^Ŷ5R]xy]Xqe{ԅ)>8*{s|pYʦQpp[\-yqc ]î)x PtyXH2]tF*9xaGuŜJcuEֵؗW؎®ɒt_}*bwf4tJ^vGˇ4 ܆E]xPm tW B\3m 8tcםVc.qYؽp _KZpkgu]};Q8GwVâZ !BDB!X,['mت7U"2$(nlJe MX&*X?c ݦ6eYeZV޸Dp+MM([NoQ|a*m؄IWG'' G՜XP"8ֿuap_m>,8cfB¾š>rνR̃I{WOS]ga᤟]8ܲqVm ?}.=s^-NsgjQ=ݳf0 +(R$Ee9d9%9)VکWq8+Iljm]ؒKEII` 0}_{z;qz|?Utӿ>3@?-w?SK&_qĦ|1> SD?}ouڃ>)~rޣT%{}tMixy ]ZƥɆZ _ځOwx ۪'U?y] C9x.o_ۿ^Nuߢ?sؽ{鱞ɤ`ʧ#7ٷͥ/?cl7_2_7c>;euuu9~۷:B!G\!Bl Jy#NOoD]oGتi riT6 V6E)yh#D7c2Uɡv.M6,{EmKgGvqbjw/3BC)Tscͼym訾C*gx2l ZX4#Gpկ+T**nO 9q~7߶A7K__2wl{ֶVYz{_~'ZNN'? /xpR^,}>NJŋo3MS!BBB!w?],!@,^{s mѼ_6uuŶ Uȍ* 24Wťz&6 Mx(κa~kXq( dyn(㻏eWܨ x{Bl _"_~_*|#3kGz{olf>Q߃#Gn'`~ ^YWbߗsV6~[Z߇~.Jֿ|5;Eq._%IKB!\W!Bln׆%<5PIA>{5>=yz՗Wk<]BuM yz|n"c#,ґ/͓1 c(MGxnΌ6eyݪ.:ϑsԁwy|[{/Z/5ɆYLkb2 ^`!_Z+̃o]'8i8W]o@{_$kr4f459bx#|qfT.L:*7moqΚq*Vtd8[KI|y:&}G{ޡ*\v{xw-Ztz״kx=MS|fnT!Ě?;!2sVBY+W㮖;ד''A/yaw]5G~mo[/}~ o FYsߢ/}][s?_ڝ/^en;~~W?w'oG] !BV$(BMTcR׿d2]sOs+zG鼟scM|~fپQS|rQj,]`iĂi\! /fxS)F p+#Xd+ŝhFiw\S塶>VaTERnU: kpuqydx^Cm}4=vt]L S9v>ΡxAY GY VE4g] z kvr~ x2^;5Cw yz>=* ]__2dSm`6)g4yfzF7jIZ tȆ?c22|]*Cw1u5]FV~I>?ƿKwN6 G.xw{{եk!B!B9]҂V\&-A]R^nt,,XY/0X*,ob$u3m6Ȇ8=ƷO=H.UK\*#)~<}fM.E Ow*b*% 2ZRMƓ10V6lu{*i:L e CX8?:0Ց]J/sJZlOjʒ%g%yoGMG8=gFXW/ri HgOu~s[,T-83ҌUDXNӼ!_m^rj͛nDg/k0rqTn9;(tmu ݡ,ٞ<5x !X{<_/.}//ԩ ~?)_}~rx^{Ϟc8Vfm4K~/*B!fB!usMB-uw`Pٔ7׵o;u׎'^fGU MS}~PMY Ԕ%ֽwXeA_m£?z[\D2r%4 t,wCg|4g"ocn-Ұ1þDYtmk^CwhJ#k;V/\Ixg/m;ώU}Bl'PYyĉ5[ΆWY <}qf{޾neo^{ >4/PQkƀB!By-9n)u*_)FNua vn2 g ދ"b{v\&\b Tcz!6!!m`O-SW)8qf+5$}rgh.P.qiꨞ`_us->Ɠ1F+I-JdCnc"Qeלn-W-#§iz!ʹf3a@h_clgx|>c.Y3A54I\o`,UOjٞD5Ϣ|C"**p|Gu/WȗbG:.Y3Ny0jϵBkkF}ݏNÇa۶_Zo+0oz4T_ۗzB!dB!6%|;T!$VT!u ŪlIxX]G7F8Hs4"P.N|{ԗo`3tՎin%D2ƙfkI4 $x_h" S7lT6TT:``  <4n>71^iPe>}m9EU$fk;&S(_T]KY +@.z`I^:#BWK}WU-oO} `|9q{wÿW˻bKo=я;F8޽*04,zo~ſ˗o?˻͍~Pj_?'+{?ׅ cp򫮆a~q|;?w/ /C|70=r#+s۷xV!B!6 !bn_<>4@#O2I2Fؗ,ɧͳ=-r,v.N4og`e5 1o[in̮혺KK >C,fuUX+\&dYk:]|;FB^\[\`y0Cg8{i*j_|YqC}hJy0k;&edg{NiWi^/h6 9Z*+qث%a{T< !fzKo.߼_m>Q~i:\n~os#K_G*Vz/m[oyA[+B!jB!6M&AoBRv2 x|R.D1\uKR`PȥAЀ` 6 U;%=lt>P>æj)",_ ?͡bCm99F1m8c*:K7^dy0Cm}㇋6*,ߒyd&UF}93TI+J3tHG:.C{8-3kH)c0Lv?]SN~PS(T.l o^!c+u5p%t:#ޅBo7.7KZ6w__B!+AB!L!Q2bzN]@!W`(uKbQV6ʵ F%yMC]"I7]|k6-4>*"|i^]U8EEh1!o@s?c]L-DOƊa |Dkۨ\~Ӧ.:O4|E 4KȟЊ;wtM7lbͽ|CS|G;.{asWlzxr7Ji%uW$6qG2΍5♃ "t>-}P!+~W?+O@(>߅^}աB!ؼ$(BM!R1;Ob9> y() sԍ[xJ J)/تnlJ!|pj^Y0V4-*#)j z/ Lݽѭl%Gvz_鼿cMF`mdjb{(Jj([i>æ2b{ez/gKrF:`>a.<ٴoY{mO&]WCRK ލOw6s/B[g06v}իk,| oҗ~=B!B !bS0t@,ՑF$.oM["'@& [q}(mCzI\Q^vt 9Ut׎vz/4 ;fn M%⥭ -ᡶ>&Sm?GkGQquRQlx|y|N)mCEJ;+r\JBB^go{?>ɛ74W>kB!bȧB!4p\W^ f1i(;rlp8Y)dn!w'FAeRdo=qZVm[A<@W(=u#F뽜5E'೽Q+VjnְǑ0-`).U' ra$=|pE4!;{B!J|7osY(߷o߅Z!BdB!6 MS v \P{RB>C@*..h qªʹ!z'BQ,*D-̂ y5]eo5nyٕ9Z<=ôUNHf3Ax#L1<ʾSo]gX(gэEgqZ/y:@uByQ"{g* oZy|1 !bc!B!VBtTMdd|T\ &] Mk[| ixd[OԘgz/0==atnbg6:[&5lg9}8,*9 T;}ܷ0D]t~5Wx}a!PS1-YJ-6kv3i ]S>,.^1[o7趋lBk s|!^>羏?s֤3fbccpSg{|uD"E4ZFm}=miQq8Z69{( JC9HA6^jڵs9Z1á˔2M{V1pqM 46nHa9&k, iL4 iw̢o)2|ptJ#o‰oZ}y* ) 'ۓy}i\CPĶ=zF{YB!B!BiHP!N a MkTvu@hLzrO#j&@] Jp%~;w V>*ŧOXMɃu~.g]NʝD_{R*BfPyuSw訚`{(ՑZ~KJE99ѫ4g6lu5\c^\e)¾Tr"QIRg4Q[haWiE\%Wkp܍=rLT&̮6pΚ1:kޣ7{14Ss{pvU8cx:8lHMW7\ S/uuRh 1B!B!BܝBIi}R^H*@߾RެK-hS"Q!Lr #cؖiz?d2iVt Tu">Eq\Yv.j:9Tf5?M-֊ix} RJq5ۍ*26ctd6XԾ~ӡrY?|eӦbǻ.03 u35i㱎{NZ^ 0[wï|P=yCߡ,IuYߢ >S򣋻6mൾ̥/@˕ s"JmbUN3IW~CkB!B!XB!Ħd=#O05)ȗAl=׸\P: p!͒[(4o4r.oq PvCMaUP'K?wo]\2IœwߪH<w\bzF)MMT34[Ů!7ufz.O2.n䩆"`Z4gni"L/k-ԗ@S .Or~JT mpݥs] 1p `;S('ylysTF^V fh*8ߢ9>Ñsd-YF|m̦8=7Ob!gVtu)"Swx2۪'{TQ!B!B!֊B) e&`;l BQr _Z˶j֏u{/l.ǹ gdl=wUuPuc@P-Bjk,Z9ős'OY !HӼikx>Nv[&6Tqu*9>Υz2ŅTaDbXzSUZɣqOo f72^luQ\|MgaΒ>%Ƴ!/\D-S\wžߴ83B:_8`ՙMGGO3jLE(i A_DS](a:JXN!B!B!}#BLaS?ڵț|q[?8{3UEF:GObtt 9ޗyVkrXPe ƝV ݠ9>MG <} .apetygL/ql`\]x^(~׷=WiE7hf{3%c5(s04[E_̰aǶ]doAmȁE0>*2U|EQ O[$s/~G;.ױyyj'Yg؟rO;ʾO悼ݿscM,&(B!B!bB!6%Mh0KO0/=Nu4ɵ:傕\ZB[(kciPha먬c`YmYhFYYjealW1uϻd 9-6)ym)U뀻̱h|9|^l 4YkGy{1tW#4\vԍZY(d4|XD2Ft CsUÉE4E~3f`9X95\q?SÉv.70 {MELH,Ukjp]P;1 R ,3zZ)rl*9o!0-Z*x8Z*]SDTEL ϙ~1Lb9²tgG2]|&SB8,q}ǰhğ6 J3ksF:o+uLblsb#lS) d K^'`ԗ f1J`]L5|qai]ﰭzQԋۯ}M")>tMсNأL/H0Q!B!B!qZ !B@>ùIGeSϢ~0L^rV6mldlL91*tutຊXy9Jg0tP];ؿg8h:iwr $\4uYQ )\{\_Ӽs79ۮ~zN'^,!o04$;F(-ӄ|KV4r*TcFID5H vVS7oq 1^`` ګ&xn)΍51ϖP ԅ55iij"ϣkဏƺZjtyڜSL@5ns?4kLyk ? @MCY9TzMe -3OurjȦi[lm,D*4/%`KS|@x+DF3SPǼK`4hrc8ө(y\H[tr΅˒[jZ;i|yjsxm+ʅ~fx<~s] pbW.f..8g | G:{kw|ԉdޱFΎ62ue"j忿aN2t/O[$8Mcl?ՙjמ>d,e " vtrL[롦)A*<@s?e욾, brh K؟_"B!B!BU&@!Blj>æ"fw8zPWha/xMjV9xA>_liCcaցҊflm4y$a>mi,NL;\Lخ7NX-V h`P 뉉#|t ~Wt]ấ_A2D) uE$#^&F+JݹL&uAGAVVu Ep7SyE҂ c2X#>0S^] *&P#۪'x4Ov5S]"%'ŎcuF2T.HY ˓Q]LYv , sZ4( diS7Bmt]5,1 ƽw,;i!e !_~ںJ#̅xo?i63lqw gY a>O28yǤ:g8 gEBJy-ty`<W;ھ%h5|w/Y*")k:PMyB<ө2&SQ_h`2%FG}Mqk\tYIA߼m20S4|pDY|Acj!ʕZ~p~?Cmd;8?{u|Bt]z߅bx!B !bK0tEC4Q|&Lm Tf^ Gkl\st5\ eCQ^ 4K4Y]zoU$ԗor4ϰ16%{Z t03 q -3L 5dx |'oƆ8yO;ʡ>ō}~)c\]d4QZgd&`_^cΦ#Ʃ6P\;E,j!_͉4 vTMPW>O4e>zi'0Z9EG$;Fy$ qa^ɷN=Dt59Ǽ[4r|&̅FzF^zo8tTM= 6أ\h !0_?|1x`quzo/y=Jy{2*3JXp=\%lm`>*feTG~= !X}555! PQQڟB!$@!Bl3<};&k4kC>Jρa 8 ռu /g-#]p=u=wu А`Q[-~-3Рi|lꦱI]C|,U[p8Gvr|{D bW0oN6Z*0HOݵ {١:zǚxj83B"{1]w4]TGE7U%0 O0.+9а18;\&©6.Mֳ*U핓ԕ/d6ȥzSQc8[ycb<|wdw ~Ǯ,vwD)xBP?~Ϧ#?҂B*f':^5N}<ᅢy|K%*83VΎ6a9]_#5H傼޷j"U3vt{n?^D)&@(D_IL+X~Ӧ!6ǁ<~3#ͤJ*4Dw;Qz!E?G300SD2«};y®BHcJ# ri&v5 QJaB:tGRB/!B$(B-,Aȗf  [ NP-s/ؼ^k VB+.঒h Fym !6WQ軌]8iHM_WOaSȶ 8GzW>|蚢jSw lFL8NK; sTGoS^{( IDATnmslt>t\ żַP+8 iZ9Mw%Suo+b#{ߠ<)zkgٞS('ۘH%N UrhI1YMX~RR {xjL:uFqr= %߯ 8HgMkIr~d9UzGx,ó,%ڮdP;mWM]'( f_pJG"mjV֕5':0PԚ*LW3,K/ċ/ӧ?!+HL;&}VUfy70mfsCrH69YMh/$ts(4f;\hm&Dt7&1.NV( B?tUf<;iȧWdJӼK[ygJ coSk8׾Az/a2Hwу.- _e^6ؠq=L¤>~nZ ,:eol4˷;7ˋc7tEE_5sE#OL?<}]{uqz KX#癯J+ݫcD7g~+;>"K2~?>{`CBE ɹ w[ʟC~2?"f{jo]g999LDvc?Hރ;{nu+.1Vr#rt's(%˷}'xy{&/_qQ&hEX2~cgo}lx뷸2Xl>]ęGD/8ߜxWy_s: Cw/߉x\ŇGi)ZLȍRW;rkk %.ґ3W-puT ri\##ͫ;09XrLL>g݈ rZVAz;LTie9;=@ 9}sz2[-;o е"Jڸ|L=7®Sw݈]7o?yn^g+ TiwoMWsiÝ,5Wvn1,r4CX!'me)^?|;b3S^:9<|ڶPwy8i7#DkG9se{g1XAwLs3B rՒ🈈&QPDDD)XvtO:gX CЪcsL_vAlqkgnCc4:D\7[m;gf%h ЊW<6˚vH5s=-le> 5ִuC ߍxu ozv +|w>zXbyq+AFpwozw.Y0voyv֋cw/7q=7okt|T#]~l%r! h)a;rS˝u[0N^b643MFJLk%Q~F.޾ߢ+Wag4TCL3|@گ63lG1X۰&{mZgk;OSUe޾]IZUle55YG+nqbdQ@ja[ lm5[Ar[D`/0ѝc_C\Y~ W*sib3׀q lbK4/BVSxyo=4wynLoaB/q*͍z/5֬$iя" w19Gg:ssۤ\[o8;=yr×Wbs~CcI/^|_B hk!!M ]Ykh>4D ~kTtdžOnXgqM%tWwH{!\_,śb(6faTEDDDDDDDDD6"""L-, n.wmq-し8hdZ|Ϋہ1#lBB$a jaF8H1ҙfO_#(0ؕTԄ[a&֬] #E[~OPGD48/8GwžtfktRm15?ȧ2~joߐzng7<E$u8~ebrO`Z=kcvxC89^ V?y_o&<7\lc 84tFY|x {6z~j3Õn=͋cqݕsMBW?;,ty>6[`ski/xi"(("""(ߍ9:rËc6|6TiqZn-vSmfA`X<'fsO;~d=^[:Q $eY_?>.oڱ<7bgObF+Y!cg? ~@:ő[G8LSH7E9\[fs8n1])WX$owOaS4).Yj"gGao4%j<͞I6<,"""""""""" 351{oyL эj+c-4k2Ia<9`` qqIƻ΄YBl@'-~Q5p0m$@aS~W,ScS) xuGE9>z~#Ef8ܢ|'7,kۤ]2'#eou<-,oȧ~|ǒ_3K3d`Sk1 t,~o ޻2ش6tt"_![ v,l<'3[C1`RKܦ{#/߽c>4;{zI%j:Kޫ`%>~z~/Tbkn-]/lzuΗy=[ub:;-НlxmE@yf9n_|t}f4EؠA58`NI\+)16kFڤ.$@a\JYrRRYv2Lt-(=vC0Rxs XNrkj_fj:~u`ZMl%0bXiy8_?!=a@ -:Xl}6x#9NL xa"8@8ɯ'nꎳ wαg r7@1SgO ~|yGf6|G:O;vX?x[11i/|{X'! 588xZ+MqmL3iʼwuV25|޾3S,5sѣ )f|a)^y}w׆(""""""""""G@y熮2Zgre镦/k1-LameeFis]l;oǿp5ρtΣKӛOH'Ϗtr/Go>E.CG˕S-8Sޛ]+AM0 =.j`k YhV l;.xNDfycǼy]^qn#H)v$+ef,5a{m[ vo^ YĂ+%景h OB6Nƌt'92yc'D4fjSC" 27j1977Ǯm1B(\[(.cfJc*GF.!]*fhOgkDte_xy8)'"""""""""iߍ87c<&߼=y}xnLO~:l]/.{d=7W=3P\s-m,*|ŷɥZw,ru~n.=]mTݽg92|Oq%_0R㯏ʩ!&ΑX_^ _u %ŹO>[Lt{_;~1q}&0rA9K594t_1?:}|tw.~}o28qdY1Hyf((""" )7K{?zc999bV"iD.t(qL?¿ЀI]ǐ%\*Ot=YR֣OQΧ3tR}Y\` KjȻs}ߕ+͇bXl-%~U~J /3ZeOM3vj~15KvO{'y<973K#Se&z80p7|D)/E:癮O\$RL7wxy< t=HSSMNNqyk e~}y_\5 /+N09O6౿"밞xDΞ)r&d#DZSMͥN>a_wpW_uBk2ɚ&Tߢ-t3Ł۔SX5cxq<\٘rYz_דNύya(j+͙;FY,!LrYb@=Y PHwdȧ]r)r.MW֧-td<:24GؿR:}9в\FL7!>;k?/NB<6f 4$m?&Ɇ׉='W/SؒR-Rf{.sezVZ+E#7}4ϧP."gs96z4Cd`S`O ubjA2]J[')^RC4V)* v'Ӽ<~\w݈R;:rk e.Ҋ\ BwS=Ddey8cY`oާDdT " 91.L! vdLLsd]>8ߕq5v}:3 ][>%~`$#w9IܐBAWk|4~)Ɔc-ri?<\b>ݠ#S5)zl$EDD3e_uJF|x/vk-W}Γ8&:&iqH{.]4w28xw cwxYa9͗FfZ-ZL[hWn lW[VU(h|kT K~7KoaQo;+.Q482r##xcAN\A)ߋ0X:_wcc98p}7(媤m1;!_{&X?*?>sp{ov-}J3ޏaruΞ)>7qB)kQ'vk> L"&O6=ŷþwO3=EgF 7+媼gX0Cs<7%.?(nQifq3KxCwH3ZQxbkmekc `ډ;Dw1 .yϬF*ABvɬ Z[ɶ6=}lЪ'@27y$(߱ȿxm<'l.QT er\ť^(y7][,s~0vf xNxy \-.cY\|N28j}'룼siǯLD˭ W给^.L4j8˼,/#j=mҙәgWrrrlJV>;?LҖCW-,׎]M)W#n*tfk|u :vN򝣿11si7(擛 ˆɥ&{xF(cyM7 ֆ8IHH?,0PpO:ư4yh @DZ'N^;_ IDAT3s"K3=qm=|@??\SUx=b5aIyn1x8b0p_:4zuwcxLҢO5)>w'ω3ɿ?D.qK899] ?g_X>ՠ$o$cU7l wkޏ֞׾c,/gd<1k gYRB.SkZK ^p';5"8cbkqu16@gM+fCwJo=ޞ $ϴplb>9R#c Vȅ%f* k 㝌wfȥ fubb%gĀc-$>Eoph1M%ɘ৩im_V=w5 h'fWGЬbl}[?c$[dLcW+GG/_\sbs^ƶ:1Lc96zq팕fUs">7qfґsp*ۡbwMv>{cp_^ȖSPDDD;yc,1?1Xk6_at/_vI.-똉k-8q'8#eOֱ\\ԇX #![nmg!:T j4oyNXi}.\o9""""@.bGyo|k!>:g*LW[8Z`ۂ+ɰ0k-a^8n!y`]Гd܀Vr">#}oo~`8(†A׬bkЪCJF ɥZd{ >/^u("""""""""""""(("""rݽ|/q1_^ $8,ToY&R拻xerb(b˧VqLc\Ź pg<]i~wa gϗ#>\X\avB/=lo(+-w n~1S.:! _Nr*ԒBhi/re{eԶOc+ma4V$i/yFKf84x/19Gʋs\=dcU-rzz/i>qcC[W<'wr~wt`g:wMnmmX_kj8m;uWr$cu"wWO 1Ŝ_L6,Av}zWs{@p嶕qɫ#:[ q Ш@m֒1a 0J; ȧ|bc9Dͥȧ\$/^;R#~kIXuU~uy燺xy#% i:.bkrD fL@Ƿ^8Xq58ҮOOְ+䟯K1(}Vw[mﴦp:VXik[c~-`n!]|y߇̾J>" SM@O҅Ro9I>b__Pmi>%-VH-XjXjpf[@_rάO>Ǵ[c$Sw;/{s+8Ƿ^10|CsSpz1rL5 m2`ׅ3wϳYY=_lq/6lAЄV#ɈߕfetLlWv% ^a4GO~LKy(("""\Dz:{{oPR%.P&2B=`pfz7͞y~x)HW4:|Z2Vwv{3x1PJ+={hMMeXl4˜ضWhjy̚5eCAb5l A-i"nolqȥd=7ak?8 ^>vYDDDDDDDDDDDDDd;PPDDDdǒMO3ə^Gǀ&E秗Pg)"<Y;3t|)0c'46nYrÎË=.oOE 99rf m3ZmؠuHFFd8;ィ=77 ыtjmK@urЙS\12ɩCYZFSoEf WY;s vf)ͧ)3>Y ݽ  (.:҆nsͅ\\&md䮟gVXH{Q4(LF(_4om:7kkÄtiz K^ё O29O 7Q: <"׉/.]+pm̏$VzF""bBȒJ)i:2 td[dy1<qpkL{.Bl-Q 9agpޘoTb.-|-X>mi1Ml(F56N;"K\'fUo:#Y&i/{CDDDDDDDDDDDDDd;SPDDD19ƒB҄tҕPU\|7];vI3r#IYKYdW||#)R3r)C!RL>i|u O!ug_sp2 Ą18NRlf-1]Pk+>+c~0Iژ@ߚcln:%2SF t,Y~RH5pvʦ2G/Smϑ[XL?zK"ZDIHngc cpHrYߥf2Pʧ)3xc i;f5gi" ˭fLIjRrEl-e.zjAw}pe/di/s1[u,1P\3S`G4F/[? cq93g""""qb&_!!޾=L-w0G7FYnfAd DT1X\cp5n{[g&Dz6,}b1Xbk=w`4ˮޛtfNLoqγ:%|7"dc&EDDD6X tωԙYj䘭x.95AL ޵ۺM€q[ޱCu={eo"{m߮ޛWx~2[8%j0ZD>|Sy(("""-.[CUY,z[8k 5 ,ɕ*3+{p/wa1~ʡ &S^@.բvk}a)^q_/O/7(""""""""""""""m l!X&/^X Y 򝣿\.J3zd?%W>c,?S~l w_\sr)/TODDDDDDDDDDDDD!(("""ǒM]7V'_YlV5g864BwLG^[w—}H)Ww_oa /^ ܦnm""""""""""""""` x𜈮l ω3b l"EDDDc, rUJ.JDDDDDDDDDDDDDD SPDDDDDDDDDDDDDDDDDDdRPDDDDDDDDDDDDDDDDDDdRPDDDDDDDDDDDDDDDDDDdRPDDDDDDDDDDDDDDDDDDdRPDDDDDDDDDDDDDDDDDDdRPDDDDDDDDDDDDDDDDDDdRPDDDDDDDDDDDDDDDDDDdRPDDDDDDDDDDDDDDDDDDdRPDDDDDDDDDDDDDDDDDDdRPDDDDDDDDDDDDDDDDDDdRPDDDDDDDDDDDDDDDDDDdRPDDDDDDDDDDDDDDDDDDdRPDDDDDDDDDDDDDDDDDDdRPDDDDDDDDDDDDDDDDDDdRPDDDDDDDDDDDDDDDDDDdRPDDDDDDDDDDDDDDDDDDdRPDDDDDDDDDDDDDDDDDDdz"""~l*DDDDDD˭^G@mĉ䏈|vi6Su]rV/CDDDDDT*z """""""""[J@׾^S+_V/ADDDDDDDDdKy[tαcx뭷[-y7?ӭ^ȖRPDD)d^MstBpOF}4a^v gT                               =vڥcͭ4vZY[,C{nYD'۴cǎpO]ݻW^{mse^SRRR+ :`.ך֯_>+yj wmpĉ6=>@#ap-SsHզ=zt 0/ &ɱ{_8MƵm0\=Gɱ>]mrlhPjٲe曵f͚:YyY6g:R2{|mf*KNiٯV鼀wu Q¤>hT \r9yfLoѢE;)Eˆ9BCŒ ? # 4@1H@H!@-C"LGh9:kyf-GuLͅFU@!k&Huw> >9Hm(!@m Dh h[ DC}G@`0}'p"Q*C@b$о@ ?G pG?A? IDAT0! 뿾@L{eh{Ei׮] -Yf駟Vzz &(gu*KO<?D?A@(--Ջ/8=쳦::DQcƍnӦMMnC:B@$ hueeeMnC:PB"=W}nӪUnC&--Ms 4'|>(LCҴ`pOWjVIBr$jj"g9߯teff{J0@@ `@@@@=OVZZ|PJJJ  2DsM7uڵ?^z$}]v{*Dv n۶=O_|)u5XUU՞QhsN )Sk xIRZU$\8ZwC٩vK;_vM8=6)=ѦX*j<*vpg]?x$eM:d̋}ֹ=ҧ{o(3٪'$kŖJ; yW_˶]~:X.cd= k:-5;]5$[+Rs.kJOHRE[ۏԆqv&Qyl썲RԨxC1vioMH0]=^H#aa uA{Xâ١CuNolоR^SuI"@-j)V #mA*3٪fu*mj-'43#î_UjŖJ XYJґ3NFH޶knvMVBcกqj)(st+,9"^XAXO4}[\4ߺ3 ރ[#tE ->"}sdSmZL11I=f#fQU\&5Xj[Pz1O)͢[%9z 3 ; z5]?U ]tAn D|Z5_n(3Tm+s=\Czhx}qBJ @uP)%ު. .WTKl7"; Nc4OTLKYY&)'ˮi >6SCyyvޭxu]iii~3gرcr\ٳ[9N޽[NSݻwWvvvPDǏWyyV=҉'tQshs86jXUqLv+Ģ\+oboMwMJVW\yGoN;zU8͑ JֻqLv-dM. k3en=Qiyjĉ;u뭷J6lؠݦk׮zWB:g~~.\w}WS]]ٱ3FW\q͛2m4\~VG$}Zox KZtiHU_J=w+));CwyG?Zzmrrr>gQQ̙W^y'PeeVZUViZl.&zj`~~jkk裏ꩧRUUUʴi&ygcVVVהsjٲeۜڭRdTUZnj1%ZeZtԥfUlVgURE1v+*p>'EJI*-j Y&YlSӣJjؽ .GEm> R; Cy[0arssni{{tI]{A5c ;Oq[y`mX-ySt}8׹cu:Snl2XYVI[ *u5eADg}kcE4[xWnp|HYd ,ۏv'Y}"tn򁯜|N͊ƺtؤc:$Z4v@ 4Vxtԥ76ЩbDv;WU13>!YuN:6ixXA~]ҭӥ.p Ќ'%o%ȷ7W_A%=󯒳84n`F􎕣p{r˛ݾ<>Ƣ14*'V]ӽaц*k=;ԮN}*M%[+rkqG]W]a=}vBVw1@z'4Asjj."9R'N͛o>vܩiӦ?omk_ff&O޽{+//Ok׮ɓ'%I6luYF^{| /ѣri&}r:)--ԩSqF 80siܥKM0A}QMM?O?ԯmS7p=fΜi4hN*I:pVZZ?}: Y|El.m8E.M~q>bP͸0AkvW͍rI˒42'?iVLȜ~Y76N)C5uX0^ veWN] ZΣƐxg>(Qe*M.uw+ 69ͦ C5eh"uK[Miqɪqu xOSjҐx]}Q_^BE9Yv͹*M?PN2]liWGYLU0(^mj;pu6lPJp5M53٪&kpX-m2tACщgPBECzhH>.ӱG'dST_oҐx]?:Q^%*{g?:ժx@v?׃>x[rJ>:Y?wg?͛.]?}~ꩧM11BP֣-bY?!IOH2k:ю*=(`[ÈGBxδ[ odVɺati/ٵiAIΉɺqLa!Eatb^kKK2tD_ 1C64Gt_c]lt3?+]c%ͦo8'ZTriΜ9>v>CW zwޭ;vP<Ϻx-[L?_OO(777\ A:Q8qg5nRC{hte&71δkiA;5ESCHHת7Xi VMF.peo$7{и\CMUG #iѻ݊T]`HlJâ"!5_2,>@^{uZ͞&ϼpFj!z17:Zdz7_ILN1݆ H^}U߿gjɦYV=ӊ=ZǣKȑ#|]vezgl_~9 |/>ƌ 4OS 2guYlƍgfO<,YD>}QtMMiĉ&`^[WnJw8Ժ.i67-E1pجbZ)ح}+}Fg8 䒾70fb-dq@I:ZP'U~QtEjoUfB Pݢim'&+#vteI04oZ03٪]Tx،"}z_6v/rNN~_o޽u{뭷n3;wn:_{5y<v~UX,~UiiiP[5mڴ>}:'x?\S@0̥WHcbVelaKۺ6tVYMkTXnš1KͫIO:jgp1cM[ֹGkjgVYGkM>7Njv-5f}*.i7tKњ:ZPg^5ac[;]ҮN}Jըj\pk^Vl:pۃUkwndE`@iJ{k.-oqxWuϓe7mzĥ?_F91{Rah皋`u3kT;ZP?_sq"M0(^׏n^FW^џUvMSS\]_>ߚֵO#9]ҒRm?R>3٪]awpwduThU =+gl.:l-5ƠEpFUvΆOն͘1CVkԗ:v6l೮vt]O>n:]~-_c=zPQQm޼o] TT A[zum5$*1e2%>P7?o$ik^W%o>Njg-pUD3}C$DK}TjXnT} vv(~{$b$)s*ҙ22kS֖*՚ݾ?N-[W{eXk.iYy5{<҇۫ >uZn_~lŖ }Rϋ-UFzy_Unr=r~POW߭(777nY3fLH̷o>GqϞ=C:O(֭[.`nܻwodq[^^^ߺ^zŔ uܩGjua/Xvn,I/6 >ƁwTiw©=N 4hg:g6<hZd$SZ"fl%}㔑|6 wȥ/֧CVL[׶7L[@|ThJ6.pcdߊdK[Æj뤼N \v-`ЭT9_R4UQ0WoIwN O@v r+7G$ [ط#*G %yŒ%ҥ.Fl,ՅfHIoፂmzi|]Zr_joc[)tl8r .Kjj;Q -2C:k}~ p5>Ye wK]ni:u.k/2i0hxa]&̴p;] JEmZ\2`W}պD%_K6CU\a.l G4o5shk(2IVw0'˸BgmVlNשg'^t5d'+.nϙ mMrgΜۦ-ku]}l lh5I j[e9q IDAT= -+@hޡua:Vk*H5 [Jҙ2ie4=-jX0IRAqյ.imk.Y4I6 H*-|p\nZ;4ilobReװ170NWHЕ&o4Yi?; fAXK Ww@GPYiQhZߟWW j^5u ehKTZ(%U; NdOпU*nfa[UbLTXI&mJK+j$j;EKLkuS-A:+!~I5&ᰆJ߅6N;4.+wy>mh|E- LBBrZZF^p>)))~۔˥a B(y\?25u*؊C䕚g&ոJZ#%ZC|Ce=[TYӆ@QB,hԲ:et$b\|hm 1VIt :uYVnnn'Pѣ~zK^xᅨ%)((Peee#TdK2N:`M1Mm\RǴیTe&!ƪM*l}ֵe@Fr !=bTbu*U#LJYssq/5Tm-ge.ơi6>Sq{g0x>gJ]gE?_jLb,$K:tN%nUxorUŵ4Em#6v,\.޽U7]ĉg׵epuIW^flѣG5p0` UyΦJ8&1x-o+M[lc *U9f!*7*tIO;[tj-HG_jڰ?ʰHUir<⥫V1߶AOщjG֕k}yvMw@Y\֙[GbV@`ӧO[d-\5|po?[?b2`eggԩSg-]T.k ֽ[#Y,ML?Ub-tq&-@^bƳaȰ$i76+=/*5t08kf{d UɹwL[M[cIqY- jZ2X-}C|-V~QBoѤ$xإgUҎ Iȑ#uֳ?jΜ9 XEEEJOO7+]׋/{7\.m߾=|;c=vvyǎZx} I*,, R85}t\캿/z衇1߯"Hƥ##Uuq]ۚ(8$TV^Ve։B`S iK(pVZcZ|`ZñT2޾Q~wDQw"iPar  -tv*:TO?~]bmg>NS k֬QϞ=K/nswf!o~'О={ܹs PEEEꫯ4j(};Q]]d {rmm}Ѡ;ruWh_xb3uZ8]RA8Em8F+klfF3ooccL^#m~`ZݮCrȮ41ksX4~q֯3Lb|zS#p֣W_ y睚7on>ٚ={Ϻ}iڴi*++3=K/_A\9;w 7:uJ'NԾ}رc1c9ŋkҤI*((imᦛnҠA|?l_AAJmذ-W|Jz6<_tT& yqiiy-]&ɮy3%*6xNƁyw4C}:x*p/!Ƣ) Ϸ`p\&S:Vxm v6}՚b4:,^ ~4hnpm#/OzM)7ɓ#ϚqG:|8جYܹ{m1n.?{#G?%KnhΒwի>…ͥ^q|^ul%iʔW+==uy/~w뮓~8kE϶#s@ B#FX,z饗4tP֞+tRƎ̙3Zp{1g}fX,׿^}U9s캵kj_Ç+.ϡK=zu0Ç5sLIRVV/!˥ѻ^rr^y͜9ӧ5fYVGkq=ݮ_~YSLQQQO~'JHHА!CZm67lZbzѢk޼yZ?=szCa\\# #q-j{UKVSmz +S)cvx{[vK{t~N*tҭXmi۳bk~tyxtJיRSI[lW"HVE[#e---Yd2G=^̯oח |6i):SVi*rG'l&'UX9%. V*75Ɠ.)lH?ӧO?m4JOO7=k֬Y*--sڿ~111zsF͛7kƌ]YY7ߧO꫺KB:o[{'u!-555ڼysx z\ר\8|VJ㠌*δkҐx]7/tȠͫ$367vA]٩- Iުt-V o ȜV4qp"ȥ76Tɱ\O?߽'Z~g&[uIXMY3en-tחg'g4a5Phin̳anT&m;[*5GL*wgk-j=%婆CJ}~ rt"֭&MzvyyF{ Umgdqаwo㖵5KMV+.mkz#F޾WkNƚ{3U"# r-6lX .hr^z7ڵku:qJKKL_ַt*&&&[}VZŋk˖-SEE[gV.]$XHHHo~}{ӊ+'י3g,uMfҷuCN8qbٳgɓg~6Mgw߭_]/ۧGtjذa2dLoY:u$u]2d7.w7W>Jo~ _9[AU d2AZL1v ga_nzZ?.ӡSN8&Y<oMH3fwk=Ɇ퀃QYы˴XmPIOS{&^S'y+=AΔE^>:6Hϯ*\Ne$[8 ;ˮ=HT\];'hdNޙY6YY ۣyU[, V5'Gn}-2S=j %ᇽ-fss€@m%^w]p!͆c"8bĹ^  SSǯּн[tS%.B]92Q+pk m >tH~Xˇ'Q M6ѲªikvWkoS׏N )|xN-ַj+UCVUzno>QҪЂxN꒾abDXV[+N~E5!ɶ؍_(ԬK5}sKomP*0&QCWmQWjX]?:QYaiˡ\5-Bhf[9WE,gTio\|`^qT C|'{<9pxc99rf-osr+T-s~8pX{Y<'KF\|{Y,:b~4w.X}_l DZ]|ZjF> *8gҙ䛔tP I:qڃh8UPK^뮨˃}U{\3%:QSE{!_j୎VQH5T~;T82+R*[f?q?d(u]W|tYo1i>&}6-j+4G> St9/0jh$n39}P7\f|uUQ@ohVn~;\ trk1ffu0uwDHaUJ9fyJL:{k{xVНu;|Y!`{b1z=/:?Hww$QO2͛gޡC]Uc˖-qw.񽒴pBFi:Eop omC2kμJ+=Dqgg'SYekf͌?S3J/ҡѯ"rMr@[-ROYgK*9=X8֖ieӊVd~ߣ:5?7LR}} pu5ܸ?[#{+@{=Ƕ4i/0 y믿j7ԛo2.9GQӦM]7#c caX> Q׵7vwߓPY߾ҏ?}puѿ$^:\ʑnF]1pxVԭԮ9 vc]a [yeIL|= { mk}l_DzDF:l4yo$//s4h'Wkn{sa^eA ?P y2/0l5lۍc=b}lEGKi '&0.ŽtL13yd主*zKqFPPƌ@hڴKx[l]]&M2w %sQW@|kru9l6l+'չRT:3|۷ww .Z>\z 8PcǎU&M\P! j5`;m9Hz!0s7ӭO7 z |}Å `V掂&([Qv{:2ׁ2k}؞{=!:{xAڶtg9zİdٺWG﷪C{lw쌈ہʬ,sQm1=cdeIӧWzI@Dz%ntBqYYsr?f|C_wyR:9{'%rvG۞KLڵ+WO!@{;Ǹ4HHP%6vn2[Οow-{t+5l9wsm$^(+|::[OpԶiѾG}֮5OL4w>di(ty?Ӧk/\.>^f{(ENJ2lG6?"",;%&M5o~q=*/xf:d=e36ֺ`ym$^GA;GIO_c&M2lêf+I\Y8G4܉ΞDsa/xd]l8,,̸ .6\lcϿm^|,6G{phCv5 V^AAJX~AA-ODƊJmkޫ"a8G$&&".2#y*}H_~.5G+52hO2kH>}*}."5jsVXbbk p^_4sPJLoUVv>̟%\:|]8Oo׮5^۶9 U9Yc=3z1mk##-2֬1a(:d.(\CEꈍ5?3_oP]V~cT D@up[60pgOix˱'-7/uZzoʱq0PNޡe>*LVs[KsH^^֯CJHx?k:aHaPfI0j.4TO^ڶMV3VoO_IQh:a5/-^lBBu3/)*T'CǷ*X1`ۼ} g͒vpq1F۷[y뀟>Bw}~FG{M׌$,SGɷV틨(((|իW+77ץSN4l0ڰ3gΔ=ꪫt*dO?;wcǎܹs] @BVRRl8>k d2~ͦy㕔pnҍ7JH-՜7=TЋ4h{]Zd6owuʞ4HSNucE<G' Oz-KϟIùѿ];i6s7@#Y'4kl/r.]$ԩS..f8)܋ .mw)8PqQ\&Ғ%uҦM]wբK?.Y:uR ,ڶmp7U`C>Rmut_ ]]5"xܵEyZ0rҭ14֕-~zd*cK= oU TWz Vzzf̘Q%{ Ѯ]o(99Y;wֳ>+w]%9//i UK?^jF8Go:ի*{Ϟ=!IjҤ>cwpI |SHW_--efZY3ܮf+) b gϞ d˗k߾}ѣZjǏמ={teeekUVԢE u?oT׮]Uv|;vЦMm۪]v +SII֮]۷nݺj׮ڴi#/11QvRjj.2]s5ر|}(U.fk |PzeөutRR|va|z?^ɵ;MZ9R_|L&$[ׯ~am߾l}5m4a>H󎒓 hԨQjҤ_^S]R׮] 9sW^ٳgڷԎ;ԦMd8|uMN[RSS5rHyV?>|޽Ν;gw-[kՄ  )SkoV)Otjd2ioL%'.S~ȐBRt] w!-zA[⪫ŋZPJ+WJ<"}X+>SGKyy;<ɓ uꫯ4qD[8P~w߭۷E6׽{zWw߿.\,EFF*33ײe=ڷo_6lؠ &emխ[78t3gΨ[nڶm5kTl#r]?C"]qMwDт1*Ͳ__BCP||]۴^xA*)^d:Gw?ju} ƍi&ƍkׯZ6^\\qecVῠ ƪCƍ5eeew3ghС6Cuwֿ/M{ :.ZH [}JCpԩn&߿_VRvvڟ~Inr 뮻N;wVe_j_|a,))}g Rntȑ#Z~E4##Cڲe7RjЎ.av$-xN̑ZvCav &xԷt|=y~7ƍѣ+-&Lsx>|/{ޯ_?=裺TXXX6|rQFV{VQQثcZ]°ɓ JғO>'ϯlرU7~0x}Y\ˡEgϞ ڵ 8$[5klb1˗[| JJJC3f(KLLԜ9sSidDi?kS埱Us&HLc!%$'6r]'pi]Y'*7K dh1`s թS'5ѻwo녇[L&UP2Sxb7ްZ;|pP˭ZJǍH0.:]8wahEGG;t0L_@^ju dݻ[U|Lr;ժeMs\*##~U4icǎ-.5==]7nk߾n=֭H uz_ \"#{k{}@pd:_s;KM|,M6uh.jرcRoMդI#Gd29hժ.=T;] ' ihTBJHz0?(O޻0O &iL?x_[=oVm۶\?e!ek׮Vcs}܄ jǿn{e^1cABVח-xd*/3^ӊϨء=M&~2D驻ZhpQ ~1[ٳǡ}ovթSblʔ)ϷZk2駟jڵeݻwBCCեKݻw^۷;Tau:aϋ?Um=[E:uʅUoowS%5{}y¼r0wRrbl߾}ZtXFF-_j`HH}QWeciiizǴe{v^*ɓZfBVk~9~xq{Uz/={V#Fmݦ8ߟn@%uwp}&k2J$$HI?$]o|jp1hԢt=Rv}1v6jfǾsesp)ҳgO-Xb_ժUKyfp|њ9s?^6-ZꫯW\͛7ܹsUV]*ۚ5kS'N[ncuٳg4 :uO>H^[?I:rxpp{[$wQ-wn5^XX 6hƍV?I w KU#3b7lj8Գ.. K[J:fڷaNXva-|[P+K%qcrz7t.]@n9j|ӦM6_Ke˖ѠAM>]O?m T0*$$^^^zgu5Gd4lr^PZ6;)S$7YI4֮bco/).OS*=un=L&ܫ,uꙏGnqc-圪P-+22bQ_($uEgΜqh+oozKK.Ç4tP:}~ͧd2˰nݺi߾}Zh.](##C޽bbbԿկ_еkWv/27op]ƍ_Gծ]j,$$j?{!&MXo֬^^^6lkںuk秈uI?_|k@72O^po^6^zI=zʕ+kSNru -Q3 RPG[͛L҈oI|#Hk:x6bu7 $q0ȯNx5gK]HjI[I>ۣKHHڵuqq.j=S @8OCg_]T[+\\ v̡ !kC1ũ^egHzI|\(x#<@0re+ Zn2/.yFza?_J;t}! P70TD-is͗_J.eg*j=v.?WE\_ l"v֪JEhsڵRRJJF%-vt~I~.o_?$曥xVl]["h"ծp>3SŅU2[ 8ᲫiUj"_vqa(8X'Y|]TY't`Bɨ>i*s4 ^ iTJ g4l?iɇJݽp##͘!ծ+A&ұccy QY&ܧ³.6oז)Pǯv}L9Rۗff|S').qcY:x7jdfi/߹ n=yyٶMvtqqdX g!n}JoذJwHppcZwy@}_A~u CAZŅU@hX< @p[*[ٳʶwX^UO-r]N?r53?{V[\\E2Hn?y|}dsMxԴ{zyI-ZHK7\ EVУΚ%;Ν3QSVlPQ+菤V&〧M|=;F Q¼3/4#|Sׯ|JԾvkrQ@I9B`Gwpi0>zl?,MżSn lۥ)SAP gjxBm߾].ڵk:u'>}*1c_VPPA5A]QQQlpɒ%?~;-^X7tKB5Ajq5IPhw5CJι(;Փjղ9bSO-'Iz'TPPW+..߯ӧK֮]';_ f͚%IJMMUddnPSSB5oں1HI*)1ݹWXe5ׁ] tw̐!C4{ݺud}G ITݵsN7WOG<Х$R~["ù#\\L9>sd2IRIIK\⫯ҴiʞwQ .TڵWTѣGYCI:}7VOG<#!C\\T% Sw)! GzE7eQWҳ>[<88X-RݺuXU۷xIII5j+#`+HP>cTTh֭3$//7fGhX^)x3ͿSm}w5JYYYez-"ywT^Ɗ};wjʕڹs;Z;vT~Tn]?pk{LM4$i̙ZnRRR-[cǎ,:wV\\ݫT5lP-ZPTT׿Z+WjÆ JIIQnnn]{j׮ڷoŋo)99Y 6TxxnvuY>>>NWRRkΝڿ~wkQxx"""ԦMܿ6mڤ})))IjٲjJ;wS{L&}We}||t:]ۅrrrb *..V˖-աCKN[ٳgpjJ]t+I Ptt̙#|7z.j?\@5RB5ݾsesÇKP\,:%I;w,0T61cXկ__qqqzWURRb1G駟ք 4xrOJJ?22RM4ҥK裏ȑ#6ke͚5zm6cǪaÆz5l01L} r<}ƎUViʕ]v'.]t)x;(߿|Zp-[-Z\;m4=#ffƎuiٲeN;wl|ƍPÆ 6P yzN?=sVs[KsH{$ef:;y@ӥ&T%{=ѣ6m~V\iwz z衇 owy暘.9}%%% nܹS}QaaΜ9S 6l8GԩVZ);;[8l0tM޽k:d~V-{*o駟֔)Sʽ uС),,rKJJ7%&&~>f=3T;VzYmmFjDBBVU^iPi(Yի]!I7ZG5m4O^S~~F$`IIbccm73<~Aoaw͛ 5g_߾}GizWb1q6ZkVoQo-ZiӦL'h׮]V{?Ř&OӧOk˖-ڿcY~ᆵ7NYۃ>Ǐkںu5uTթSbT}ϗ`/4rH&88XÇ̙35w\=ZW^yŚh„ {?z!ϭ|ǗL&5JK.w̘1Vc'Os=0`:vܲYf˦LbTҥK-8pNEGG[}駟ZرC'N{~!!!Խkeϝh Ts$js:/||QԪ]_u^e ow|j\h*-1p:ا kݥ׹sg_~{լY|r/ܹ.]f͚Yf4|ӭ>c_qiܹJJJ*өS,:ݻW;wx-bAo*33S 4X{ᱰ^^^-/ц -ZdΙc%s83--bnݺW׮]-f޽σ:9bʔ)V^~e_+B<^z饲?Cj2L}{?x EEEY]}ՎB/⹣n w  B{}2U˿4R 4Y70TuBT~626R? ?P߾Lnu]wY%iN[l8կ_טL&Yb,88X_ {{{{@IZjEwa+շo_us4RvUt8.dK@ ׶m[G:TO233-ޕTn $X\JXIINS*00PrڴiΝ;wކ/ /hܹM4`oV֭u7+&&ƪ{]y-:Rv<3ǭ^z%unܸQڵknAڵ7raaaĥ \B\\FK*(#i\^)ם6w~ϯs?j^^2J]\|L~iZHC@ ME"캨(E[qU.,J "B("*$ JB 2t>3s? ̙ɤͤܿk==9,NS|駘={6>ϱwHJJ믿/oŬjv2R(\gYl\֞iqI/("˗cǎhӦM({נ@"""""zC YҸya"(Ч\+]Q}SO9 """m`XgWAd.++bǧƯ+y0;;9r7nl^v{Z{ѣGWhs޽{qlڴ HJJ‰',:G}mb…VlҤ >cZ 1118pN>~M6'6emerZF1*Jֱ}sͱvZDEE!&&Grr2=*<~8&MCU8lZ0He1HDDDDDTO  ~ݶb*!@lko.DDDDDD ə3g,ZjU󃗗Y69~X@O˾WVJk 22JiӦY.;;{cvҥK1gxyR)S`ʔ)yG͛׎=-[`ĉVkҤ{kl5 /f͚Uj=kؼy3֮]kvƶmQk}͛7zTTnl""""""XVq%ck.>}uA@XX͛7믿/"on1>rg8&>>jk֬ f+W֭[5###-: *J 0+Wė_~iqÇۼvYc>|ؿrϳQZNN1JĪU~z5ʻ9ׯ_7{\5~c@"""""z#/nH'@CٳСS_)J1!uM8;w4[`gᅬ?ll5Сw'Nرcիٱz}٘+ZhaQ3}JlYYY lqXlΝ[2ptС$("9bu'L0>|8ے̙3=kٳ8vXɘdƍqFX ,0{-ZҥK+eff",, ;vDHaٯ_?7BhͰFa, QRN QCQP䂳WqfDQpvIDu]ǭ[;vj̍1_~%J9ׯy7|={ڽ?bccѦM GJ<3gn1.mۆ޽{F/G||lo„ V/ٳgKܷoEuo-_3qD?:VÜt@6Ϸ?s n|}fz!Uh $"""""j` 2GDDDDDDD AA ~-s> |Xs|?/켏֭[~Iz)A|}}tR|ܖX̝;VST3g1`rkҥ oXl5+Ww}> V<|0VXaz|rjK.VEhhhkap~Ɗƍcذa%/\3gunYowߡe˖6U(x瑐nݺY=nʕػwoСCh۶mj`qʔ)^?nLDDDDDp`s#""""""hRzۛ8{Eǿkwu"FFלT)Q7e 4˖-CRR\]]Ѿ}{&L,+88-2 r}CѣGɸ|2Ю]; 2JETT,Y{_~NCjj*A1}tWVWWW,Y ,@\\9RRDnп5JC]YJ ,ٳw͛7!7ocǎڵ_0$$$`޽عs'!"<==`t :u/޽{Kx*N &`̘18|0q\rFm۶E^0zh^Æ CBBك;vĉ% 88;vDΝKFӧOmܬY3L8RkQ Qe+h DDDDDDDTMJjK|`~c & F &Sc0f< tTpGz/ |Ah߾="## Ł先DDDT~~~0a&LP-빻cڴi6mZWl>|x ?8Zjkפ~~~~~^SVcFzUͤwi_i9Mg$N)5z/DDDD DDDDDDDDDDDDDDT+{!#zi\J A+VhcB%ne (*pv6ݙ[0 9P#""@""""""""""""""ud5[plj*WZ~+w hęj& fcoc=(?F]-V)4ߛ&mL3:캢B0b~v$"" a3v ..h%݆΀Rqv9DDDDT&##(0DvSxvhïMDDDuDDDϟG; """""ZEwgܹr #]RxDCWn/ u? q/,r?2HDDD Q-K """"".'i]Q?̎[I zUO*wС@XXCTp(.j -K?BiBz1T8sFʚ b&FC """""sҝ]Q?[ŋ̩@k\@jx."rK+"Uc%zScpWt ]){=@w |kH7T=i͐WDDDTO1HDDDDDDDDDDDDDDFn0Qvh{5jpz-I~wsήhD Ԡ hDVMx@R'K5HIqvDDDDudG٢2R#-ۄwfcǯy{)0s»Jejl.m59!EǺi遱}" ϴ٭0= np5Q Q 5,ӧ; """"i9~HLc=\U)9N@+z[v+0ᄮMhR]2tAUP)ըtjVr_dނpH<YsQ nU/PPDDDDe0HDDDDDDDDDDDDD5f`Xŋrl=TLo&'Kcоpܲ?)Y"v`~7/4m#@{'u+ޞcԞ(g?[\ۨWYT)<1TX% F4V!@D^I]!ݚe3|=r=$"""0HDDDDDDDDDDDDT+fF`dCTHkpB!?芈hœ1^)  -$Mvpl=7|;Y&|4?ChrM\ ]!:QG")Eq$?'#'\ }:6&FV]Nj"K5"zOॱ=`4P*}vw.\7h{G#"fፀf=Hnj(]wA=6qFM7 #\$"""j.(;)7a4UMApޔl)W6@^]3W)L< ⤮d\V& oW,WIDDD`1HDDDDDDDDDDDDDDN˷+}~ބċ}>H HMJw+@3_%rMe%9uwO&""[l ;TZ-Hc1R >۪Vg PPdywe\PF^FvAf[X7:lw`T@DDDp0HDDDDDDDDDDDTݽ >vrs^yPThbZ{k5pܫam#@J `0HuO<LxxT~Ҳl7ڨТХ } E;w+qkS7:vO!mĶyXs ^n?owEvJaO wW]mWn!ŅJMDDD DDDDDDDDDDDDu^ $$+RK.Xe_𫯤̞]`j*0csIᅲBcT (`Yu:~\WOO灿 hڴuׁ^B~:tHz.AA3gju=+}OUV+ӷ/ J倎Ul˸v7BOFyaPI:R3Ш}.j*`0J i>hg|g0;@q;{$1HDDDQCTHk=#8*;y]>Wݻ/XPk?.u{5🜜`r a||,Iuo`[dY))_*uyf#3WU<_1ORwd# =Ws[ >c!uP&{..i =~f?Q7a=Xu? 5~[AF+*}.5 Uv_gBD"@lc~[6.:UĪUW͛ l)u^6E`! 7g!x2זUxe7= {[.O?aHOh?^+Vv '_ĭ #$\E õr] O_~y:Ȃ(8)7E"?.|y9i.y%oϚqKmwB,q)rʒ?6- 3شqÇ͚I`9[v,Zd9~焇aak틩~=Q;QCU*cpE~2s`0x@\W>7 ؗT(Wnq@FRu18u':9UZcJ)-nus1OP_A΀:TӦֺ}_*mi/@fYQQN'? J׭T"C yp6o)ε:>ҽ]+޲e<DFZa=8t([spLRsH}(" '?$OY FzL&@y) J"a,'dƝ,#d "A}PyM*$_ Q =H^LL /^ cժUuvYDT Qgnݑrͽ2188][^A3/+ODDDDfSq+켛wSzEWEDvf̰>ߵ+kdJ=-Zz?z`9XlFW ]R:Q:2\i}mgؽW9_sMJ$߶?}Z:QRv#nԹn| "6d%//bD pgf. Dx(0g'BZz<OYeEf!(~ ((5&iGFN^z qqquj5 w}{v赩~j?~|ht:Y QK{ z+|{x9@pӛhꕉ6~w$""""v [GfjgV-m m:~<,:ZkV eyՎ IT*.]wܴ ̔WROen^ru[\>W[o˖s;v0H+Jw\RWA||-wEШA)7rk6.6Bc "LES jDpK.d;M@Z F#x/wi9Mp3ȴipA\3f _n֡OҥKfc tNAvjvQ U;& *.PdT(*!uiz9id1WVYG|y__ 6@W]иqt t9c9w8`2org}+p~hҾuj(?,?׺x,R sֶa&{U\RԿEhkBGA]Ц .e>pӀ+w% pƒ ڨ9qNcyr~;@Ar E.j{jwѡuRj;ܼy7n@< Ttt4/^l6h"DFF: ;ȅ_U+ȁDZlM)Ћ .öP+uAxtN4\}\;a@OҖ;.g, IDAT8P>XT$'[vE9uv68Q{[v8x|@R=qՈ*Ƞś+}dS)+Ц(1 utA@s6i`F@ۦJ .j5P< 7p(G]\Fԑ22~IʾP]A3f̀V>>>rrUDTU  @!n&ȥ 4})=xjWFD.\=}8$(Ngqz `nn[[N>w{(JԦM+\w٢?rS`O4QO ¾(1a)č4#[0+Z@vJ%8ۀFW! B~XЮb<4H&^ H\?ݭ`x2X ڹwSpr+565\QOnW?`eF  s!fEbul8)|߈g,HTܹ<`w[\$fע\Nե6/mW!Kgױj%K_6S (@n w2hD~UAjtB|e&fW W`w8Wk_n |#2&J7`Ǘ[ ' }o}XM@v5TzȺe۶mjHLLDFF|}@oxx8O^Ybb"֯_D(Y744ӧOGhhhkDGGҥK%{聈NիK@mv+focŲ燅q/##u:\hh(ƍgw>V+[cY1cF[hY111%_/___DDD`v[,##۶m+Yzƍ+>+N+XyVks2$IuDDDD0J nAPۦ@I7@@=s`,)? "%""""2#"~ ??@06ͱQ86Jh>iTZg ) Ju[^ Dqcx|]E =Lj<YF\O3Ex-1<KBYj`k"^voVMhBjoBJx)㡀y8PXម\j3 1`V;?W9Jbb"ϟV+;j[ήNcuͨ(cժU6fa0DDD`zΌ3j*zˮiFkdѢE6{/Fhh(֭[WnRZ #fwz-BLL fΜ sbbbeW d*^֭+ tڢ,iXXm>oVUV>Qm 9p/'R!]Rs&!@(JaB9Q cQ~%Vy#fwpeDT,F rv%aÝ\VݺUQ]loHвeկ]RQ\zE ;Y&4 =?OBkQLz|42L(2Ana0f@k @^ #pn{@ۦJ}J꼛mB&lDPs|=~ uCɲ ؅:!11C*-&&Z)))6CYSbС+TVl̙yLtt4|||k=+~VYn011ǏGBB ̟?߮gcժU7o^k(yTx]"gbF4S`4HJ TI+ ҜKP@E&PT P*DDDDDv**c꩸zZwj !:7'-M&i+'&M*n^PZesr?xkҨjܸq厫ȖL/.{4#~=W\c9[&&,uGkC-wu1 $`Qwp0 g\ ԋl*9`$ӟ/KME [>,"GtvlWt͡C"!!mZV^y9lH0Z@@̶?3[:G }DEEYgo9vu,ZҜ$(6'""""'!I{"PQ1ӌEs!rJm'LDDDDDfeܼb߶0GTc6oq 7oO=Uuq+9b}S'1Aw?iꩫܖ--pT绕{\Vף1TZsp7Ҍq#̈́~1;O bL*,[`'?`Ld*$AAP .V]>'߄~,r D\mġBٞb’"N;BlI\xGغu+DQDzz:VZeAMZw޼ytfc>>>Xnӑ[" <0\ɭ"-Z[Nۛ\MBŒעEd/}L闵-ƶn NV N8:u:g XhkVqƕ|EQ֭[ѣG^hRRR "dk?~'h'QuaF 1Ĵk@~`2B 8/ b w/ &#A) @59wJ/Q[_q+O L\rpeDdƍ[KwoźY ^Hn*?xyύe}M[Ԛ([[E`n6U~mm\&A K>ulA[ "PQw9SS’ Yyğ?OtKE&^"o ?4QaPgDhXFyAQj; Hl(@Xt/" vY}ՠ@Zij%]|}}1o<Ȇ222m6qV3f@bbllxКu!22[`` f̘!ݯ\5AYf.<1r{P-#m[gzU mb{zK~_=^\s@|<5׵믥NBC?n>F͚H[\~>0nO:ȡc.#GJ4ooO/`}>5ܺu; *||`kϟ?t"=#-]=P.O}TjZ^sY@~+VGJ].E)txtݬ,`nIuG1gR8/+Wws5@R>^" <ܠ) keYlǛm_rѹRov-TƅUԡ8R=[r~D<* ^ ?ϻU^ogY5k3[hh(-9::a[;1|רM3!Q] Q=q*~>j.CaK4D´iw۶lB`IIsk4`͚hQ#أ6{h^wJaKk6@ U6SI/9+H]us sE=go-JsT !]Ƽhߑ_VN=z̟ /7AF}ж I ѡConߟD+obPW(jn8+¤ (He9Hϩz3 GU^. UիW#22R6WՖNDu&"""""""""~ݾqX x fdYhjc7-[Vk{xH#F?o߱ HV/?ÚRIQ}ka;TjR2:t-.*h>)-ApD7("İJwY&<}clgbJ798+=xGV`HQ}Wv޺&&&ͳ ,,U4#QMa@""""""""":d4@oBBe6#6<"?_X$&<(]ظQU*^5om}Kbk V$@@ffŮWڃ|c{_gT^x9q/Հ[%i4ΝARGHIڴܹT g,/~<6&m&૸\Eu{KkU_J.[:3W\n. 43s;Àep0j^Je~Q m[S;BFFfΜi6hDDDX_~+44ʴ>}z@5t QƢY$;T`ԋ!Cᇁٳ>`,믭{@F_G^xPl…RWbyfϖ֨QsR0O֭Zgu`-Xxq`߷uU[7_:L)'5jקگu. ٫T>b01n >~*Z LNm8~gfE"\hDNA6:wo|7 ƿ"FVV&" nW"<<\DdIڗbnNjcT^{%&&Zt7ol>"22a A \=s@v֝92"!Co;wU997nH>} `T`r >^rF)S_mkide<=iXڲv)[͖<s_^͚/M?Y޻Щt.vim9UEEÇV+m 35o.}=+zW)\WBBn39+YƏ: 4Qu`H^(ܼ(?bZQp1X3yYecswSWFDDDTTJWoboZnRRvQ1;QJ@J肞]H /7*"L oB9[wP+)<0u'<] W:A?{w'Eu 9Ṵo"\p7b.HC,z}+~r5xIb("" 03{wy8t0ڪ;lll:Oˌ3gu8lllIJe "&- Qaɬw9'X[A""""泷X$s|q +62_kM'!)# \: ˒x0Vo""1;ظ3 y=*s1rg{n{3.:9,8i訐6oQaժU=oԨQxDZ|N͹a~ɠ[[nvZ*-Qggyf-x.\׷;Vk IDATg ^֙{4Xd 6l؀JKKqWcÆ mn+Mta@""""eij.Kӛ nȒ*Bwu/?w)Zf^x^v;D{?&"""::i5nYK+DcN_ ).2Mhhcv}!]Sâ>>.SU=#+ʰt6l7o^[9g̘yu߲erzt@duu5/_5ϼy0cƌv1cVZ 6`ժU9ʰpdqٲeY\dI^ĺoƌmu/{jƌx7 o$""")%cꇚvS-VCL N  aoluw"""">gb9>u XQ5 Xp~~#8~>:6w0eo ǏpDÔю.%0}Z]Mp߆H(hv2zp^W͘1ZF[W|G~O|^u;It4a""""qf-doǛiioh9icZs,3ן8_2GDDD MX00X$`ݵ1_O(˕=f&n@t!ܼ9BBy?e1@"""<B-3DDDDDG;᯿`SA‚{.oi/WFDDDԷ]yc6!@3o.:BBDDDDDDD&|dH.oZeAxB-?ʴwvxXWcgZ۹" =|\reDDDD}_yQ=9~;OB_ Dy!a  DDDDDD%;!|eڪ6OkF 8 0\"_PZ 6 N{m=oX0a? `K)4a*LKqĠ +oz'SXyMq2vܴ?..t9Hל=-.zȘ1cuւt:1rȂ@DD}sDDDDۄ@n=MȖ:Hk? T(Q:َW_ģf @\5 tz!JҁBBu j }փC-SSsWbPI\F [-i^~<ހ.o{!$fY]v~]G.)iHE 1rwKt+@cccAwxzA'"@"""^goët]lɗ61>-uK2uc"u.-u'T*ӧ_HO]P[N|{kc[-`3=ZmT</~2 #pʼnb=:.ߪ'w:)%yXOs/.+$""":\t W72t-C?÷.Xc?(X>: 6 N3g΄uocPBpNxRB²CtH$z }JJn"|}k&e#}TPQZcj\$+#oaO{}}'|r "" Q$˂G3@ 'p,SuG:9 HizpBh:^7iObQK!wZ_ZR5[&p8Uu´uk K pAFr{`_b5fNOr,)5߽{6^:+c \xfݗiHM] i X5QGcJ?yA"os;\>\|btDDDD >Dt 4aiD`~ ) ό@ P@!XR hO P<`Z@2ģE_~Wv/Mu-+0 !Cjҁ0܀ia?d2V5U[UeCM%UV`Łp3d2Ub@4tP^ ) C-%Xm299\`ԅuq L+%"""b>Zy@J]Tǟ5Zou9=%UMAcfd"""c-tDDDD@"""j  @Uw?/}eBå}PcaUS&d$鯇py;5.% FX@IUW [xi[ Zbw_2j ./%SF ,3Mq?%%`Z:5;'pUb$]GTp@qF1y^TzDDDDG/ݏh9e}VjԴ~u%9_Q0\ÿlИLDDDDDDDDV[ZCLuP؃VbR]yX[Uw1ƘwRcfN#fhS|I`k!D2>vVnW_O G8‰^y8x,{. |6Oƥ"DDDDDDDDm )}BK yǻ4-u0mxz5w31$mH(a|t|P=F9ozӝT8.PwsNۏ%"""x@,9ㅻ!|6r \NWo+^""""""""0Q_dBxүr.}Ahu~;v[?1. #u@xTWu)(4Mmlbak@0B0ªmSi.#)Cj0QUo5ݗċoƫfz#""""""" Q3u4=ظw7xJ!\E]u-4 k"[@\?x/*|^;""";.ծӄY߼S\sE@"""">+uaw}%14[ I @ X`eovkC^x  عݾ^h:ί1Kzpy  ] %Qi.rBL,Cm5t D SץuGPÁ"^/"""[]/-j#"""" HJj>P] A8\]X`Y͇H@},WsFp°8е#ގ5c7W6#?λA ] 1-˗ """3 )SM twmOq{wٞ%_qKþrtm,O6BW 8\vW6{RB@8\8@hF\Fxv6T҄%9#wt,K)E&,C3 XQJ8%7"p1h}>wG#-/޷_mFM9ҪQ(tDDDDDDDDA֭u70'N01u=C▎excF`os %UiBƢeu Q 0x`ۡd4=J0a~LnGұNJ so=e"gǰIDÖ5pͧ=6_,{.?~y QPa.N@ՀDŽN;% fYrB]vTcY.ˮ ZP@jF dܛSiu`8D0Du}Uq.;~yyJXTmf ?Cg'@'mM!/9bhCK1~~lIa(rik i]c|ɨ FLRAMYQbW,ĚOㅻҁ=>?{$"""*$)io ԸTS)0íu&RVno)t'!<{ biNDtۮÅS ])VN5G|N;5&;u.o'>z蚄ԒVGI@ X1'? vàC/,=p_c;`ZU7ƶ)(qqqx+c%Z1z}>AKZJ§c8ml<S"*OVu2w,R\/)DDDDDDDDt`0(Tۺ d%c*'֮S8cݡ:YݒWpԼN~};Ko.Wuv'.lɫ2B]> uuNOk.GMCE voR)UlnQQ@4aG 4V`GP=! =45X2 "1b,DbPqScI$@ϯ?omC:ΓR!㰯cB+ P5~-/Ѝ:@"""ʢf k4 R!so<mhZB>s|w-%z<6坺&nXg4& ܏C ЭN"!Rku̺/aYm8"nu:iZǧ13GT@:i‚C7[ұiHL-a7܎( 0HDk j90zy:o FM;Gu&""""""""!GMPB@DD8c-gնcah:-DMZJos+U(_) ģFUCGi#jx4@i!Gؼv7Tmڟ&TA:5+JG"ֶɝ&./D% ,)p v1GiBB$:5$!5 Ե#tB}v%"@CM!Lu@%"a6[2ϸ S]a=6/Q&h9QaEԗ4|^7D$5[{>M{g} ޏUp^rH۱VGBSǢ! DBeYkFb6V=SO*rQֿGzM `فhnL/MX8pfK!jR@"B,r䝢MiYA7=PQێ Qw6`$3j\^@uuߗv =7B%늜G8ɘ#(Ep©P04nIDDD>lzn_bҜ1o|ȸ+Gy{,tDDǠ%vNBB׬`2V-EM_3'nYe z'AP# UWA8N/A@h[:Q'>#."GQ;a._7p L;L8NO*#""""""""jQ`^ ":y ]Qoiv*0 U2[bmkqŲזtu$-L[j=i-Խ놺!NTZ=ͪp#!F !4t t`ԉ}V;#+0W3t =zQӰ;>No8=p%8kQ5rZ+#""""""""Q$""":bãBa;4!C@5Ht]@nK&J@쵋*oi*דPX+; [>PXߠv!JOL S Q\*ۨY?hLtۓ H/z† LdU~ IDAT%Wδ`w_5 @,V)R[ v7LұPj)J[ A}UEp9bЄz ,n &U`yfS|tκSϺ'ʈ:@""":F١'py!tC@F+PS(6&ҷϑ4MP3Q0go隸s̏!` h:Rk^rOuHNf ҊgmW+B$CiK9WGjL MSC3xɹd2 lzg͟~HK@4hw˃~6+V F FMCE???olYpq;qt62z; ="W|~nLDDD?gӋ.S%U\epwV^@ Ыk;N̙3r JJJzum""$""cݩR; u.iB+-PiWikZ۞O7+}kҮMzmw"p9;3|"u^31bK-NSEr\/L=kRyRy\󷾇DShϽI ]2W٭0niGFvAg/&i`)oQۺ{>]X #ig]'q Q+K,O?]_}U\~j "@""":v%sPJ3D@< @"խN@ Bj|RT[Fj[+L{Q@X؞ ,3#V i!VszBwfڪ.iŁhR]^! 8ܪcbbkhҌCj}CS sd꾅}o)Skz!HLͯ҄EpZp@xJML,ZH+M]: R#@oBÌCNUj#&3ֳ!׀-50Tx3ˤkvHOz'G;(i {+8ǝ(0e;x?x+1c%(Qઈ[reK|F""$"""J![C[hpnV6B6 JXQuM>HM(*$H۝ CԫY Pc!5? 'p$j1 rXJp kǣFږh]6A3 uL #AtDm}8lW[ QE, 2R7å^x\1pdn9v?lTU5ߧ."|, l:b5A?Uo< a񔡦V"`5ފ K0a JcN|8io>Y7c%""cގ+a8ݸ{/n8 ]hnn.tÇ3HDD-;yDD0HDDDNov-9!3'Z]C4{RDzZsv{HN-m͌k[?hQ[rQUnH޶JX&Bh0ceıp;GDDDDDDDDDDDDԷ 3M P6hLK!"ꐡYQ~MY5PX éi,݄E0FjǒGC3Q c8h_ $"""""""""֭æMP^^Sbqv؁X,1cSOŠAzr`͚5ؼy30bL8'xbٺu+>C۷&M¬Y0lذnԛ>slܸըq0g\nF}vl߾[nEqq1ƏqaĈGS7lذ~!1e~|ݚ1zh̘1cǎ|RJؼy3f͚O<EEEݚR$"c‡+~5ӯK~U?puCc֜7 P8@ H GUWts^i=19E PM]F 0 7jmDDDDDDDDDDtd~a\wuY?k_Zc4aѕiӦ?9SO= ?O:)㦛nߟ56l0vmzsws=x7aY;̞=z+-ZԩK^{xǒ>{\?яiӦ̙wy'شi'd#`g3sL,[ gqFJ)b s=xs)GKڜ箻­Z=\K/ų>x衇Yn .w܁qƵ;W¶mp^@8yNEEJ?Ɛ!C:5og<3+2}ǰ, \sMkr?QUURJ,_w}7{=HEbĉ}n98x3NL>ymY5/O2[(pUDDph&a8t3sЌf` Z[Aud Θ2ןP ]+TDDDDDDDDDDUO<˖- ޽{q7⪫p˲o|_Wjժ7p饗/rp 袋rȞ={rJr)9~z}Xresq\qX`AGxh"̟?`aΝ:u*=gP?Ϙ4i0uTv$[wckid]\gHukc<\r&2u^wt|8e;iQcHiBb!@du_z;,2h{BeQ & ܏G@0 """""""""{o;+8t뭷?ARn9O?4yv?^^ ʵ%z衇=o„ εn:\vebg&o{w{饗`.jhh駟4o}+hu+_ N_U=g797xc^M6 .hw{?|smٲR6nLDD'{,X4$#0lʙӉCe9y5ݵ-y/5hgm+ٞB88*|-[yԡ_^i !  ~'I  -UQ 43 VDQ++ z}k_Uv&O5k`͚5Y'/^sk^x>hN: 'x"\.>^7̙33f]믣.ܺ:X-ʚ駟OY2M2JKK6mڄyelczz-̜93y -믿q]wݕ3x7gꪫcr%=CY?M7݄<ZZZ2 6 o;\_5 Qźc7vkK:   ;b P E2)̀p}sA4 uErKaP uˬ,0cb;yC2}[ -܂Nۺu+>SLc D:kmBxJ2Gb{K/!48nYTwPa9o ^žn 1 ?Dm-)d!D0*]}{ʱn^l>5.}QV0n~2)`Pm@oA-{Sy^ #I)`I h {p8&,K`s9Z}>aKL )mď+ < ĠF1hBB0*T;L J5M;_LD Fk'gߌy?ӈ,xQBTXIVy~SHPK QWKzc(sFQ i R wPK Wqxݳ3cΘYQEp9:^떈3#3ϧb롡3쉟`5&,`.`bY]L_~Ng 4(ŋ^vڬVXuks.(#={6^|EƑ{ښcڴizc:}_ljjի37sN?>|M6%%%s!T۝wމ*\qYW^yeONPZZ3<33Mk׮xZֵwsn 7xcF^`,ƍ3Ω… sΙke<ػ=̾AC`*85k뇴mVBeYA7MSAC)/pƘ<(*Lتd= u"v %K1A} =ՖٝۘS<땜;{褔1C~8X?KKƆCuNtN,5JqȰh @4iCw71!uuii!C]M!/ޯ׷LIU_e-7xyhB7f_ *|.mG;aP3 PZX%ACȇƠ7+˚7c5wz=Zz0#8CrK[6ճO07㚉6ZGp   05=c8Neqqqڵ/F yyy/}ԩS>l߾~$7H}믿jO{{#=8{,nA:n6}ʼ%''yf^ye---}|_+dٲe̛7UV0 %K7ϟ?<{~[3b Zs}XSSG4w!x"( \+;p;mo JWrtT%DJ3hQ9r{WE-B#uT5`gY t[[[qA/=n'*{^ޒr\|aKt|W==`'YV;{^y~:,<A~,QmJ󳼖|K 6נP_TV" "), I {ۣK@ [:Nr&F⚹q'\w %',K4tPڜ¹T:is)uYrr2ZzcM'51=Y ^$NMј&q"i$Q՞åح=Q\RqCcW, ˳KROLjtn+mN3Ʈ2i}nզaD+Nk $׳(Sy.!fTҧ,+Ѧh[9Z͞ss8Pˣ# Xzbr1V%#Kz)kIPU.했e)T%jlKn;kQg-֞HW3#nuvbdĘ=\ҘfYtXýZe["Ǩs`uX0%vS'U3xL=xyb̤FxZa=Fq1[T wd\58p)uW6s`9\ZbiFܙ4dT2#eY癑DΆF垲mNvKs8\CYK22PeJڔV \" Z=+^jn- a9|X:ATγ:t++`yV)BRiQǛšd +Y)ŮtXqzmCOis*ͩ%?6ûKjd])#iBv#5[63`aLc:FrѪ:Ѩ=8-->;)nH6 8ksv:uD, )   宻ɞJP*ټ ns<>@HMƶmx/R"a׮]ڵo}[oɼFJj1?A ep{$vÂS=A.jMqX$6|叴TqM䄐rDQ,Ngh_ۀ@O?Y*uc%JBZ_hTB[}RX.ǀ0rW+MȎ[ |򅐣Ê{.}ǖ]-JQ5>rVtu4j;Q%9sVCX$#"bZCoo4CJrܫ)Wv{lDu+0 NpI4l!4s!Ns!cSӞ@5 Mbdp6=^Nd8,>LpuRɨU=?U툣-:mƮFN7dӸЈ˭fun sw iJLc: ]~+1SOis*fwMᓪɦnn`癹XzuvUp-Aa"> ĥd{O9S.ql޼={xѝ0rjlٳG7) ""u:]vوƘ5kQxxϲ f̘1qU_TT<<o;w[ zwqlذaDs߳>QQQ^_GGvnܘ"̘ p6/nwڻEMTM̄T~Q%*&$NƳZM}>ɡνyU>L@by0:aj<$%rS@I$}8"@A =Z\A-/[xӁ찂݂lQϮa>`!;;•yOl ӭfʔgBLHZz"iDL|\n =nMӥFykI ,>a WTMqC&uqd}&Q8ڠ:nZIM{M]\;8iՄhqk,KC5h:OfPeJ3cTeeMiGn #0h$Date0; >ZxdmIHiL3XWVs%l?(aBcu^8nU*ZXYjR;^;hM#YyLl7f'3MiPޖęt[h3VcQhn>#fVmڕ 0qKdOCKDُ9CD'M }YnFh{ZFR\xr|Z$""NMM :Nmݥ*55].۷o %툇l_ ՔwwMgg'wf߾};z\xd5 * I@AA,zo %3gd׮]|+^y֭[_ph/tij }R^^_iՔx-qTzj';NȎ[Ha`2'<<+}v*++>1cbbX`<7mĦMx9v[lJN:տDٳgϲEy}j*~_{-{wkk.eV:!!Ljj.t9pvo[bal~UAAΫ?jlw] 0$B475MRV} 48v{λcO'뼃x ԠւF:# , c!*). )i:\TyҐ¢Pړ&{.%%vs9P9;J܁݆وOkr[ rO8,qzEm7,c=Ǵ#Mb6Si<p ?޶W-*RӞkǖ;cGܚ4N--I9NyQ^}+~3Yv7,{G|Bɲ̽/糼k|~9r{w^e)))̜~>zZ1=?O|_wu>֮]l淿v,SO=!8_A)a;HnW^\^&]?-\vC<+aRi1(Ta*H}ajZPZ* TXv$n7 vV褄$y 3373Oɬ:GwN!flSZo+͹͞#[OYp·xdIiC,DE[oI!Bgֲ:pjbZ*[ yd.g3&Mr$56#] I2zzCv"5tPܐss8ӔNYKUlN]HO 1qL ẹmunYM5Nk;N/Dd>3$Yq-Dl_yY'סntcƨ=pz:1&]4SAAA1{(((]wxb=k_*?o`=CT*op82IG7?!=*R nϞ=|v>ˢyfzy,Y@]]<ǎ?33v^c'~hZw^a1@AKJ';P>`@V!;ᮻK_|Dkq9mʹ]?sm¯&xV¤">'S΅RP[O=*"'{m`8PI z[.^bH2$c: 35󥕻 ?,auj9ӘƶSxr*ڒ&m>#KKM{<;*LIZp.-ql?vK%6c4kn#K8\4xO~tNf=jN7dbwjܶh?U퀝n5yKyZǡZseT=X+G.c_LLr',vs8'5Ӹ&yvSSis*)= AA47\ ypo}ح!_¦M[7>wqO?ײ{T/^LmmOѐe'?a֬Y>}]aaa= ,bO[[??nʔ {nnfꈏL~_:_)((rQ^^u.obccdp7|r<ؿfel2x>!䒒a K~;A-[.!۰vBnmO|Ʋ~,$QGc$!\ PJU@A*yJ菱: vv?̕JjȂ}Z'i'LOhazBa:;*i=V3'Ɖ4vEO|.6NgQGŠ'v]ǗWbUq 'uیT%񯢥sz>[NX?3rUɞҰb(TC&TXt:ar&.QB`[y7O.`eݶɭ9ZYEu{ W\q ,Zn:J>f?kُ~#}Ip8eeV[ѭO^^/"|;={6/( /p]wza}ꩧ jN>@PoEEE>NO>aaa~[FEEsN.2 NeSOyU(--;v~zzz;477lOp]'I/W~ȹ )!8'5 Yd$u!;F7JjR?WƻJ_nj-xwB`5]9{ ~OJ%-?&JѨܨA;IRZHj̀Vm 4Hj @<6FG׾Tv[3M(}‰*INFL9i5\Wx/Ž+w8M?ˌdeZ{"9^͛'\!mIXSmT"p9\ëǖ6`JSG]gEKxl!=vøT밆QT W[q{T.6jS55W^y𠶯+?<Au5pq.\wY(**nj<+Wrqgկ|Z-?8wy,[ݻw3wa3<7!̟??6; S.~h[\`4*ӟ_~ o *n:{`Pۯ~T >.j|q9{V?O>޽1?{?q`X^,SQu}/oZJIHȃ2 X'G )|ZȽB77/In78%*jʃz"6"0h.tj7˳KY5,˲P<.:GOs4\5el;7N,mG@%l.- ]1l=׎/.pih_'MJT[2XPBw@YK23tkp_Ɂ<(̑=\n5=QT%Rޚ[P/vJN|3[}IJH/XۣlI ^>r_dm*.n #>Ye%68 y)$!)Ȯɞ    +##5k[Ч6**{gs=׾5e ۷W_};vp JJJHOO/cٲe\twwsp\xzݺu|d…'?Geә3gz+wuaau(((`۶m:u[{nʨvEk{뇿({DK.68T'+Wz~{NpXK,{۷~***Zdee1|ZnVFcPcfffrA?kq)q+O>LN7f`q IT^4իT.^%ҠˊlêTcE|xQKj`]~1I$h03+2㑰fڱYZ*ےxjvCSw4.zR&sGk=p.%62W g%8=q08$IF\]n5M(^+G/FSW n/j7˲8(˧9k. o nʶ$kkG9Ȏo)U6ZЏc rAAAa"8n⦛n zFJbl޼y|az.ywp ),,?zyC0+<_{Qgܹ̝;zhTeZzU-5S4r!ROq@T`L%N 14uSByyd Cl4%mJ:Ɍ1|+9(Lw9dǵNzl᤾'QٖZBV\ Fz ,+5n{u9$,$9V ƮηPܐ3Vi&ŵ'Y_p̘6 ڑLe9VMqC&ݡ )$y A\5p鲍v_;Lv+o\;hF/zh" i; KpAv| k󋙓ZCl0GAAAӓ=ASLC  n!Zg O%7PY Cۿ؛8ؾnzklcJ`_VJ<++'cVSסgI>,*.XKɞ”|+ӮǷLo0aV$*5hTnr\;{ٜJkBNedLʍ^"\gG%y qQRۃjUHMf[=jZ{"yv._F) +4BlĄ 3z Sw9u(mIVcwi貅!KK]G'KldFb}ԕ,mN-mUBSI Z'z%0U3O֍! GVxh%8\,N{썆í#ywf.rmYk`T"t6O`m)rZGv\˨hMboL[CRi:Ԩv6h%-N=e)ɢ5.[=Э,tX‰1ZXsv%`kJjTYQIZi{9?hs\x+s#3D>     @"8L0P>jL`Af~7 u\{vN+#h *s.hXYɉ,>:_@qC&q!_<*:uQXWLjty FwrcjOf\K*Y1d8ӘeT:PNIc:_'w&GI tWPTeY a%Gurn[R_?Ǟ#Ker4vE's~6盓鴅K \ggì\?(+sz|2V9 ,VƬ:" 6»Q1?dj׉%vc5ۣNQ]yI $O?jg rE^ 1&Rۉ4TKj#KxmbzȊoeZ\[P^AAAaj$0,\'11q  L"8N ~k{\ ?P@өxwѣko4_2v.;TŒB=Ct锪Xok;F?x۹S-^~p5ۓ3ǯ&oM0IR:X5 Φ} |ZU=Jҍ찀_Uvc4BjTӭ-gr%NXjn zKϳq1dTiUIDdk>ŶFis m)+˧':dƢ 8UOv| 3Ɉ5&4 3+1Z1 9Wrd'b[mr9]Oqcզ,>mG+5.6/ςepj[(ˢelSʍVFqr\[SmDuxLf(HXtj1Y±9 RJNt .2NgRޚDYk2o^٦4mƐOvc"'iլ9JZG=#awi)iLhShQqźS\= Za:=UfepyLGȊk!h--)ͦj=G谆qr9 MF߆FUiDBF%y(L!/Nb~FQ+a:Ljm܀d   n:e˖d,A7*]<%n -rc UiP?=, n7_VwAER e>\%xנ=$} ðmD['JCx,7 G޾1,*xʴ=˔dOBJo/YKDAXYiC6wӮR!!U4X 3>US#fC\0$C|X7mdĶ(w,=.qf'7`6%o@y1*&뇘ݥ=\FYkʘR܄i,.yY1Ĉ.# Li\i\ą% mx͡=n{t؍fq1FfcPttۍT%Qe b`}mH"I&!/U3+8޲eͦ=gNrF$. xq2>ճۃpk툣58ܞ$IyA^rsRkfqznZ.»ZKSw :u(mnͨo : 3ypkq1+,+V}ǁhc Su0wZ>J ?pi1}VάYo^|PW*y衱33Cm};Ü9˯f̀əd<>oK~,ZC87}uG=>,A$'@w[ۣ e}N%q.v!-`6_Oܴj&54tt.VyHǹs^#Hso MFNb3v;\Ǘ5XUs~Qw}"XW- q3De(&>3;n<5zLGͩ|K2盓RF\0FqCTri+hVy0l^+rKOn 5pZs"ԨvwGUc{XQV|gB:*4ST'U3tU.-QцU 9${OnI'ZR1'͋sӂCC׆H1hEXsɇ8P٦F$N}%gAvMJpz|3ҫZU3O1;9)sAAA.zo  L0'Cw?T3Wz%t1VAVRΞUnRP?1[Mr܋MZrCl,45)Aǡebi.6lw] ֮UW1WΆyĉȃχj_-% w&gN~ xܾZO2S4ek:x<&%o$!-ȶp9.J6$]4asji鉢!Dl8IT@nuU(3$Z3+up<;ڎQkT]&ΦnS*;٥\?3Z鯶iV8jgRq"[8\frШti@eUQN֨<ǘX5YYAjTSI21as Z{")kI#iD,Dw0/5Y nٲi JEHa@`dZ+og2֭Jhi4U{`Xxm?XP~W Ws2Ϋ7_| _ ˖-dvn~w7J29kj4җ_`ǔ\Y܁J+)3LƏ/Q(# O&`S.Y}?3dxElk6Q+RzNA]6#UD6=dTLRdKUeIv$: i8 n6? <;3}t*Zib9n^pu4r?af#Je6s8&KĨCsȈ1qQӞs4tƌx}1nL+竗G~R'k\=)(SBd28\jrYY䕌l$ˣf`e.'j(oKfO,GJQDLkeiV_XMnYHCg,'Qe-%ɃN&''ݤDQ2hdŵʂJJ cytU [z"y"bzȎoYuE46aV'AAAAA&nYL ol@PBy۷.E/~Qi/)me*aY^ Iu)V+-bkjC$P} ].B-99pX9Q*??|Nƍ;+W* _|14Zcw QQ'g^7(l?ϑ9{$Lnp;{M(~ t9NspK/ےhjuRGa$&5qeϣ+hop- #i>Hrd' NqӂC~n+j-*'hE%yxQkΠSGCg,Gcur$Jp}\;8i5W f{-.eՌ؜ZtvҢI_?^x$S˱Go~p@Z\P*uZXn8nVg-^W^>t2Ç7o*2@y)h*_ބ'+IlɞwOLJ*M.S€j0)&6MǟšLcGCg,NCs:;gYǕk*Z3S\?&tjq)(" IDAT͈~|JhTn7sysyiĆQIs8`eFB#9LoF%{pkcwlw:Ϥ6^rd7;ڂb j&'I`9I"1M\6sϰby0-mܫ9ZJ_Ǘ+x2NM39"aXAv2/窙'YUN^baZR.XNgr~0<#UHypei[ѩC[0heY24a=hT#Kzd5V#6#qnyVyk,ȨdeY2cM\VAAAAa<8*l/h]K_Wp({IR*zkm4rH ?6T 2%I|8CILTaC @-m;~uN|>r oCK O?80? nSlf}+mY ᾀUЙ 8mgw-=&e1-MDuLǟ,Kt b%tGx MRd-:'6Z&J%e6/͒2ƹG8t4te V$fqC|f 2cqC pEn YY^qls1׎-5ime>87wNz}e" sc";hQ x>4wGq2'v_ow]ǫǖG]ykLF)l,0|@ %Co瞃3aݺCi4._?tqÆ^~TغUi*J> |>{ 7¡s Lʅcehⵟ߀|]?p͖ CH x.74% Oo?$!zڣ\0๑ٻ8{㟙Ez$994nBnCr!TrS &!^C  {*V˔HXv]i%fggGkw~Fx-jfQKfVd`2P~EOsc7~Žf7d͌XfO~H֬fNNYyXJ2;Dˏ%*i.j2a4$IR̄U+L"Nc7}X;_q)Km#,.%pys3ަ =*v$sOKሚX^,)%JZAz7+r)lLx}M^ٻ|"W&޷5p nؿh[Zj׊KΗ4pYssfVVPe̍h[ʕp=﮿. V}ob㉊v\>_|lmޜϣI[7 .WLUAo'O mQW^q;Ͻeg%LhUT!>a21~ҵ!kB~z 4ߓvz’LfbK yP^r[8w 3ƤPdYe RUzʳ۰R0ٓR k42~VTevAø2%OnIlH -LG vW@QMÚ֑rOpnbA'5Bv-[y֭'/EbmZ|8m/(vPǢ瞁{55-=z /okT =3ǎſc@s-w uuV/~qL,yp?Q{ sg%L4Q3OJ4F鄱 ;]7uM嵎)A7zmTDG6XL YXRGYv;IXa,y|vW&$i8!qmetL@,kdT4ut*x2PFu{`b@;g7sS*9m,.anaY?dۨ`K1u9kݙg)5zbeIn S*dbRHOssjQn\.^/ƄáVʹyy\&,ʏq8>¬&J2;u ˒3rJQ\R]AAAAa,(,t7WU=h[/Z@?i$c<`TfzlpU'/8֭]mN#.ocMXYg ^y/^3fD_\,•bzk{M>MXHϳ?}gxJJ4b4U 9qBЋwC8`Ee QƧNykn8 )!OjbsuJ^i z!V%KUh2 3YX\sV,d9},8JIf(nd3;io9TPHk!;sѢZLJ:BIVGmSIOYvrY_AAAAaQLH˖[o7w…' ݁cUc-p׬?ZjѶ6??x`Ws*Jujo6\}'^nL1.ykc;Ѣ /4\||wQ[-0~ƁYVqZBev'kFSMgg]?q?a{ $dY#aIi ER0,6sI!@D4lf\l9*Wete4t@F/fOf-l8u6n $eNf5+wn`U, L34d50&)cGt&,LRoJiVE[0G.vwd.Fץ+fT-}MgNk8i@㰄8{Ν;%Cʂ     #!ぽ{ac}*;MM'K_x5k1X%cF;ڡG $WV+V@i)CQQ-=DVcѢc/=q'/pD3ؾ wwIF@/v[]Q2Vk?8R}dk/fjvudj.ʟīSbZn+/:!p߹E |'$%B^Œ.2`#df1XLڈBD yl8:7.J% Bys|u{[rIAAA'1+|'Fh/Vw9;:+Ǟ`!dͅ_sݎg6Gw$HIFΚl`-S5 -?G_>ѿwj:!sԱ\yXlIH SK=x#DSЕ0C1kHi׷ Qn'ƅbhZOU^ 4=EwAp`ZqdԦ;d9|T72ʒfQVIzOHv8UH05DEN<@KGh9pٛ͢5*uK+#i@ 0m('x/k8JAA!y&xdb޲e -Zdʍ&..oL<;;kc}4t0bm8Bˣkeg,&ʄ渼\p$,e"u )$$.Mn2pm*wϳ;V Q5TPΗ|{*  ;rAAaGS޽Fc˖…75ѣ|'/e#xq*"'?|^4fܭzD78ztt[om~1d ki<++m2i O 05譨 4X~ {y8ػ~ogbqL`Z"$IfQ#"' x" HrOϾS.߻MGXe$L]C=a@IOCwIFnK$a5)fupjג )x{&DL9Œ&J:pX¸e8t9}~֐n"x{Mlrbwpk*=0,FqF'Viѓ0O 0h,  „&cࢋ._2zp7oע/ؕbz{eشi X}jj\9 m=h+ަX+L|jkn __b f]i0O'=\MMo~}<-G*V\t.ӗ|l뀱?Ց7;3&: \,N& AR0[{k}RmUH7P:N $ȘdˬFwX-AHONrV@W#;7}}{R io139% a52ҬA,&iw?ż㸬ARx6TpQՁ\(w#gvᴦ`  ?7}o/   L@"8F6l\<#t֫W^6to`{;}'"V 0Ff`^ .V^b7n4xJF7clFxתUOl[C/ _9sdUpˉ@$qb:ӲYu7avDf6i,(9N?Mel8:HRA =˷&Io_XSdaMl,I6vp̣]:0M]to6'(4OgaIWg#8pO9#Kߦ*X8V{V08_"~\<+.DqF7V2SFKU&ޜAA'/EPAA G3#Cw3Ljrn~rh>0e%7#1XG''GGuuyl]rI564 "ַӅ:xc8Tߓdl dUDOc<+a2pYCۂ8,dOEBWBFhWwkz哌iSL|jOδr~eVgebN9VZX̜L M*5S5ոz#_0 PBV 4.[%u,*#&`¡":l~s{dZ~02XQqt{`DtO >bwCH БP4 ]AAʾwnE fuz_x];\AadDplا:::`4_xIE YW\{$ؾ}?pv?"k z؏ip%S$Hobaq-b\ Lh] n-r%Lnv5VeDDLq"l,*弹;ۊv5]H[!w+dUlNkIA!bCAѢѸN>~O+y +/ Aabnᆵ>xn7>sekF0>2~9s3`x"v1X{h O?;l_Uon)<l:pyUX,oHw}F%OBYY-n^'犙\=2=ex2Max>%Pヶ  Beϊ7( СrOڸͼk`>MrN:<8A!1"8? ء>~;g6ohTp?vFu2,Y2p[n?ydLK5nMG]l2a=>m'ee7n2͞m@.ȀSO~x=ws!=f͂2^?{Njko~s?=pyg+3k|kuT|mf<Y?s uy{2hiDt5KE .EVfgشe.>]^UQ$ YqmaU4l|8JEK蚆ƺ*sX9]h?Iݗ2vWr+g3( E5&T]YȰ'I:a #KdY!־ Oȑ255]&9rf7rܴr=Ҳ*9gn.]3f#c7Oidh$=!BùsvqXP|lo% $[{Z~ACo=ў 0m?~k!r}kۂ\VxM;^A!>Ta| C/ oik3Zaa6 뿠 'E ߥA+b?Xv͝k jޣ}*"?ڵG"Ai)Q},YbeB#`8R6E_fMQ0B\]>kĮ8o} ޺+FquїUt)x'Hzo>U=A4Ca">l$#+a$M}4:if4ss9Ln=ƭغspMё0K`Od58,ηb{jd7>m{vrpuFߞ_$dY,TRL0SQ/M쨯dz^KVO4 M1Z!=IqZCLm!u1":䥹itg4] IrɝMMG^ʄ" wI]g.eY^>8obf~3ɥ !'9hGmblaLSw8E5QΒi[M Mʑt6Xy  Ho>Ǔ?ƁWdzsg}z7 Їl>Wl9<;#xv +h& L9"8iv }:ͫ=!.}**PU\Ψ$WX}Y;jO? ḥ؊^ { Dt c-VpZO-@y9vf@m=|9=7\u }X*9ܲ ՟4f3+Td ͞,ܾ@b%k:PɚpZp(ya2p Hj|atn>VLUkg+gL^4I^r[l&gwC Ff#s;/ř]#f!'GJDUoj AA["[aٲctbzg%M!9z**|y<ƹV٣ 8?_m-TV|l;GiLEl +|lf& AaDp 8`>j u#Tp晉_Wsۍ[o탓%`G!ÇX"mÍUn2:ķv%Cv>L]Ws38׏DV$wӟ;9yY^TUDvcu5|?\˜9PP0pK/A]g?KgеOnѕ\̟E9;ގ1Ie͌4ugALTL' @5@B:)sɚjDR~0$A(j<^m,\0Y+ɱ91IFɹwƉNOJDA]2֕NWK:X57\O-YN^He"$HÄ>3J9\ia1)T5Fw6+8^;X>]t飰tCϨ8ָK1*Y@ʵ x\s~a1),a,&5Ťgy6j;9Zȴ[FT& ImfyEȒ?l%)އM8yqr3inN ;$i?EOOJ< YVYRZ@Fd*SĹ={B 2 9r櫓χTL]44*q5Uu,-dS>`79{#|uO#εRD/-3xQNA4]J]_4ݲ=ehuXd.;sPa053iڌfn=%$4XX[ +*sZt6>r=A,]l86߾jf|E09\C&'dgS,5 [GTMPAa*Ȑ\,ڻ'`zlǷ>?AL&g>33uz5ʯPݬ$OFU3$~!0K7' /A95EFXo[waTы&yl~2)ePREEfmmcnjP]UjjiὪ*=gm۶֭FUB0*ƭ^ ѷzvXQ~l7kLH\SE˩GID  -ʲ:8kDD\J/-Kv|o:R.cm w}:4_x!0ўe 2# {Wt|!K:9.yi^%vKxF5fʳڱ[ƷmHdii+t k;%/>aSAˋ;HK"VrZ3#]}ipl] \e <ft7?6KdX$w7Tqyf6'jVS؈^ѭfaPb^fݛN!{vFl$I!/Cӏ!䷽4kS+FR&l+ ]>wA!p1DPGnag"u6UP$  srìvOkʷz^sj>r)|0'#pC7|; BzBnn.mmm8ٹs'o\~X,=PU9G~Æ Ye7|3<@RgD `A"zʨ87)$AWª:xg~z 8 .D'K:fYPGB=ߥ?g; B*T>lQ`Z^QK9X`>Uf1{l/^VVLg@a{Kzڷ0ZOOf yeV~#OMItLFKU~3Nk_);@K u0we0QkVɐt띾CGlTɲGȰʔ٘^^jtB$ᰚ)ϣ ?d;U^W֡Y6jM]3T>`ݭFOS?v?s[ffάKeĻkI8af4p]tW چDw?w-Gd.^ʜq^ξ2G  V)aP3ɠ×FHVeB̾R~Ր$M,8pkL(s %ĪG8ޕǶ Y%yzǩe532XTL$SוKCw&%ڃ~밷GAL,,,c3)l`Nat DT57ogG+07<ѭ/TAQPaS'O{YJ[^%k>O&MǸ_/‚׿Zػwt(#iruNUb z>E s!B0q!a #ovw߅ÇcVSGEAwށm瓊μ^H=<<`^g#kכt6LTb3G(Hs<6J=FpF=w4ZPY' (2l*i0 ,ii̚>"C'zgk{!-xz)OO/6:a 132aeDieմü~`RPG%|!;oZ@D5c9.J2:pX.j2]'ۏO#sp٤ef "ICmA8 4R$K`K;WR5.&w/>ƞ2ڼk{bOm=k%YC,q裪ڼupVB b!!fÊ_؆;XB''ӟD9,aVN;L}WG hrgXnh[!4&b!$qm5=Lm2=t.UD5SߝSWT,AVN;B͏4v?k!B }ۭF4v@Hy|c! 1)EPF> ؽ;vi̱ $pcj{ yK휿NU hζa^ {B{0/HhAo_җ/ K/p q!D"n8f@Abø*=IJlx5$l?!›og?;>sJUggm~fXx7sɗ!lେi )bRtSֺ$n~ Ŋd҆dI>Ð ZTڃ:G*畘Yoa`AQ4*U^9a{!@i-ۻA5k+x[fΟϜSdlTM&X鵧n>9SؘN$>!eYֱmP IDATT^s$k){@ͮ{ B >~x;^_bXua"QQɷƭ(p <$ﱄRB\ߑ YJ^k!K+fX1F0g1 }nSH:-{? I C9Gk*rlޞ)$ ,)fG]%9CVtу^zLY`2 | X VI@gviT:ƻ*hkk455vut| zvcH1vzOOM{Qntw *c1+d:;{.JeNrUܦ,YM,,3ࢾ+gXo.mtb5ʳ;GYD5Qߝil 5V 3ʒF=ZbD(K:.[,,N0r{ސ-%<}%uyetra5 چx$TM;w៻aLNt}q3pܝ亼qa 3=lgJ:s\'=[%dIg+ֶ[mylFuG[t?؝Ňә̅wPuXWv 0[߂|{4wmtۺvشix#OQL ga5ýlzߜvAQtv x Yӣة2gkU2E|gm4vx+ n_zomJNiO[aT<׿_dѢE|_eF 0,"(SC[wˍ+ X q_`ٲ:xcbY6q也_O޷1 μ޸Dɞbҹ { ,TfxvQ}IS Gw#ٜ)u]3*ʽ'vMW UfLi":x{{`w{C';DOASnO0'f Qęj቞M39NzNv]94t'ڎU%A;oXDHⴄ9k 3lF@׍q!BWsyjJΚ)ТbVA#W-ٌ;ࠡ; Ut"vz["L kxXK%TM&PߝJl6WWT3#SߕûGPEIf'eY-8d^QvF&nla{!}q -$r4[퀧`3Gl"Vڼ FT3]9?m pIfX-7 R3~A͵#I9&|[sE2Yl9;źV@ARjnnL:"(SCÓ'!659/~}t2&3W4p3iZk,>= $wMt\5 ݙdUm,ɩYtR 4| UY26M3TgVG ;8)Cm?U]NϾ3, . %jK I4s%jnhIwMD$0 =kU}l==~9NUWUwzZ{HǠ<-+b4ɉ %QPz%kAUaϙ,f1iE(>婼"AT0y  `|F!ł~<~RΠm]̪}q~YSmC[a(ͧ͠0aɍ )Sf[EbC+np8. A&+JF$?l)^@T6ſN:,uz+ST,p[)N6Ie}@rܾ1roъT=m=SQ05QJ (NdӉ/Q4m\?q3SϐWEfq||h|]I7~v<󌖺F҄0aϿ:tM6Q__O~~>gnw`dYf9r2gҤIQRRɓ'eС̘1[W||'8pD&Nѣ1%2۶mk F5 W/(((񐛛ԩS:KJJ8|0Ǐp0tP1>LZZ&L`̘1g;uVO}}=ӦMc֬Y.?c`TsK/>}y%uuZ$>/HF mV>=A=qng M62F&)n[ìtbkpC  BV7`@Qe]HAsDljM۩m6=BUE-n Am5jz3\w|IFSigl^|RFq<{2ZȔJ1k׉d45 @i&wcmu26Yрv-$z+G瑩k_6 ,ˠ3La\)#ɉ$1 n ǫRnDU286Z&ed~Fazvf7Òʙ?j 뎌kb M/DE rf =BN|9)Qڜ=^箢WP@i:J8XO:FZE1豰ңkPTF/P"SCQUΠ* ZG$&dݳQ'5 s%4B"Pf[P|H^R)ÓHaTJq#,RtM" ( |vbs<=_{Ucxe O=Cɠ ujp.nutttt~~ ꃭ6{$ }Eiq׿ҥދZHMm|};og<ػ F-ZIGb fS_ ]ֻt~Yk>W[I M5x\311[o￟._0s̀5k0o<˹y7R=t#GM,ӟO~ֿ%I"?<?눍?)wbg͜;> K|Dc0ka~ Ƀ]eM\D#F/5Q(cfS6 4y>+P4ɥN-bhBs..~=(͠e듓bUcUQ-Φ&dVqᐣJ9Krd=T;#Y6Mau"GSu&?s*woN( QXb]e J(wDZZǴNV'1#(#SɎ tR6a7djg,fM(jD.ӵ 4{&:STlƤlD[VͤEV6FQ\oz0)OMEFaވZzW T6Fxv%#*,tl;m ؚF& EMg{0(J8Qң>}&s:2G4cRE@9J{;\REzNUaf#8}JK4#!77^8~X3 O5PDGkF EW?|ChiJ6o_NfJop}m^6?\؅)tE<N~4ׯPGW6/cD ׄ,4x6Amq~̝;7H7~xVZ ;l޼TzyWYf ;w$33u^E$k7|M6uPTTTvZ.]J]]1Çwӧկ~$l_޽~uwy^}^_QU~ݎ;7ɻk֩ȑ#,XGWGe֮]ڵk~O?ݫ}>'߿? .GϽ1`6ZȺcǎ1{ln馳ܫ utt(.g~/ ^ [ R'-}]E]yAP' Ig"|0I~Z- {\O$m tQt>*5~QȮFTA0HbGDDAuܳ׃PwyҢkj̗\9z2y:=#@~"DKT6F0jrr8VC;c\5KJ1(?GRdGQ\^M\ST?5A&#nbbffCe L(&G2g T4Da|85Z@槜;jQ\&s:jglzCe4y͸|F>8u$=J,F_I7SCBz GQb|hwPɈ}L>A^RUH6ީ@G*$2G ^Yݰ %_c&FLv|WޅIg(:=~_gdV1!/uh8U@q]F [d̾,L<~^*g$ZtB"e""&y.Sᩧ %Fs,aeD[]7_+L:%"}>9٩/q;bD9VQ\D,.jvs,fO63vg;s>Mpz1l`oq ]Vtg,&jZ9^= 1-Oig!T;#\꼲,J;_Rf=B~ʙ&whXη/LeaԻ"O_ILQm71AU@Qe/׃qFx[7Dɇרhp+t_'6 Ιpkkvkwc[AjbX>.~vpat4zNUIauuM}m3@T6DPDAEVDR U̒'Xz&<~t |vl(  (iqW" (a |@EVZ_ZxXt)`O=]veX,m.N;v`J8.eڴi 2Ʉe֭;['X%K[OPVVPvW;_휇VX3fLkj7MV^RU{;TU?qP3<ÝwPc.Ҁh M \bEV7wL>?|>V\kaʕn|^{-mذ? no~W\t8UǙ5kVP`l},AuͿt3Vt~L.'c$[FMig-xZY'_yEH ի"Qi_A!+q,E-FQMuf&1-Q]ԚbԆ*@fl3am,)Yzo##8 ߛQ)g7x[U"H̒Rj2E֞Xvr؝\{ ۯo x^E§3\>z7ߜÓIs/egWi,0*Ȫ?Яȁ$x1 Iv_|II罪 La {:J՞]6RE㷑hw ڵ&KvnK+.< kdB /þϟ^}u)- _`lݪ>g.(|]CRr.haz8~ 6-ޏP \"ï~ CEwdޮYRT+4 .b߼wof <lfcCVS|MG*(9pJ{f`6xLqu&w/(ff|=AѴׯ:LUUzꩀ2Ixo<|g~oCY~=G ʐ)Sp=9r!,;  ku]_$#67x#( ꫯoր|_*{'6 -gs.9Kc=+1Zc&V>=NB.=Fn|TX|m Z^[B"5g? B{V4:P\^/_FP@P!)hO 2ΌL3?x6j·[jT¿DmB@uւ׭OHS,[gl争[vhkc%t Sͬa~V.rɃ(to;**€EYR̒I[1Q 2ɑvG&bڱc7PDZ\L*`\=f9qY(*15zgl`Zq#z𤩇{[lgHѩEL 4N+b~LBv\ckPTLJƵF9XT"Sp]\?q S^ܣ7fGzL ٱU$G=WGGGGGGGg0:+/,l ): wpݰf TWkm˖AgaGt)|A)|enbؙ޼y6m ܑ <ǂgfjPԄwȇv२2M(l"m,M|6*vtVmo=`ym7<#<#e$j*N: WTUee[l r[hQh\FP駟k2gΜ-"/ ?*xa;lذ TO{Aej+* a8]wtf͚ņ ^/XVl6[Ⱥ|;Ae;v~(¶x!ۧnxXGGGG$!s4 ](kYˏN5W A&Yq2}Y8fEt\&T,`JF6]4 in?!N&ƪj[d"~UUke~_AU`6I$&1:-IQLώatjZTfRYp̡&?tcmo*]`Uzՠ-Pfr34+Gqqj`G'Kn|%׌݁ T;T%S8೉  2C˹t~L°2,N%g*&|^?˸fNѩgl[AfHBL QP[ %l!4 #n㪘;b?2ORQT}hz-'s*3^ }+TM d55݉yoN;[.~m#2R]TAXVhu& M{}pj .|[oi}'NtvȐu== 5Ǎ ;''|3=:lsp{E"Oa l;tpUjrL3%ѭ_Disߞ/"I1Orya_9}be\z饭o޼9hƠx<O<٣~x!]Pptz%T_nʮ]Boll * ˗G<_~yw!## &0iҤ>A1~xAsaa "\G HB^R)S 8Vʞ,j-#v*xP}( M($$""Tb?@}ȣ"&SY?,kb?@@@iՕ ӂMfV&F0sHQdX[X6MzČT8 sVAz#YiCmv69P|^=Ïɥ\=Khw6DԨ:%Qu< vuz(X^R"z&e2=)QuL9NZT-f?E*&Smi"-&Y7tA@R%#dG2XRQPI705ߛ SCw ~((%S <7sUI(FUl&/q;kaw/N# Q)OKa %?\4Ml99c)8<}' ?3!GTƐ !.u8>ڐ"u/\BYpC\x!>ۻmLE ?'QQ_ww !t g3g[꼍Bi'uIgǾ" _Q9DI߆N' ^ʞMU[#E2 ,ƼƜ䶛 @VBۉ'Vpq汿\1g:`ʕ[aZq 6Ep(j =-B'2 DձX>+S(sDZMOR34uhuΠ&ԃ3a}9?I/TR3㸸ޭ3V'OhqÇ˗k10"_Xz`0q`ÆFYY 3;;Ϛ:stv|{be^<)*xZ5&wlF쇍F>YEɣ w*1g˗3{lF}ֶ[B :d""݁|'? ؾ};o6IN'˖-c޽>uF;Tי:@S/멌N/ɛep3H1dƥDeJ`|(U1Ц%U;h_D(⿖X` D[$"LM$G`cTJ؍&Id=O~H:hAu_mmyݣ9Z݀PM{hxln[EVl3賬Q!!y#tb$JPwXM^'v: zN#5#Y:i 6D{=k8VJi}9S("LFWČ4uY ( ~*y!2(n2* ] !7r@kjP]ɼ}g{&|~N;CnBFc{d(uf =3?!? VOZUv=ۛ(ޮzyOo0a,] 799=˟ |N'L/ aPQRxP y]lJа:ZE%n#;sمBEr.@2ejLbg .a5 㦰OeLS'M*qg}w}?vh"݋s(7<g7֠Am^xn`"+|ힸ]U'+.>tIc m"111ԩS'.0¥x]6z߹ (5NE8TAZ/jKnȊ_Q+ hnQ$D %(b3iѾ&QVXDrq&"$GZ0za/?ID#ō ϮPU6GDv ~ɡzOmV}6~{Ȍfxb9 A0M[YbmN2cQTBmS'*9Q'HqM`TJ]{bk; *D[}8[#|rx FP%HIJ)fjvQudԜp$Y(4N$R\ӹ"R99Q)E42%9BZtmbUU@VŞH12P9qb| I`tj#Sv{<4C`7wփ>%fɇK(CNl8 ~SVԬL)&o?Pzx>.jcNjuttttttt;_l$#f\n: ׍߻uLpݻ/~'…0mL=6~2C9sz L2Wu0;muGC}Hx|Y,3az((ST%sJ&}ߌbɬv \=e>v@fdxҌ̛hAhw6IWMq4=龜$7|ŋh"FAm;cUӛٺukPYnٳڼ_`of * ycٳoEQxO8s ;dO>Պ ̘13fO?>8/" :dEpy: z**~V39q"L =1ւB(&3ILD[Z%lF"-"-6f KW=nmdAJ\~JݬSs\Lju" 1&fq,qɈ&5s7XgR'%&/r4Xl(01TI7YOjt-?L:=kqhɊνIr6@iM^u<>#:pǮg4D=DY\L(dݤ2,aI1J~,MDY]X^>SHY=&HƊJrfhEM>n[Gq՘Xז#a|)l&UHaיvP3o>)#>X)%dUa7ZD(h CDwcN"$u!΍g !#Ed壋B z`gٳ5QVgy4sAUQQՑ]W YY0kz+t=II`|LpGs?eK 5fg-pu۟'@C$2241]o+n3x9\Zik!=ׯRP.u"XM">^ \V >20e/"=8=*9|FmIڳ&pD+h >`ն&\пb &xb@szYpa@\uU,Z(:BmƵ^T+osOYzu@hdeӦM####-pռ|[ 5k_Uy>rJi;eo^}NE~!o&/bK~ȝw.^8H|w=0k,B.*wy'_~ysu͚5,[,7W_}uPۨ(~_pYv- {G. P-[;?cm2wGjyٵ uttttrL[S\ U]~Vu% "/zDQ_{`"%D;I'+(! A0K"1fFFY0A (hN|QzVȈ8s2D|^VQVQTt@G&0PQ] X>"vATH)_p]$D]w>dwcm'21$sɡq*py:>0.R(# *&'Vr,.08%AO)kg8V~ b> 2'l#]CZt6'ipG&GȔ*9Zʾ,V(* *i15L`3 c%twd _o2!ÇkoC?}^{M{}쳡ۅr ,,ăַghIQ.|n\C$O ?]s7˃ vo36:hEt3@kSog * *۾}{ĉjaÆ ZVOPd&##[oɄ෿-ӟ3:::::_I.ad|30K~"wDn* qc G[K#DOMg6J "( 4(X$Qq- ? xQkbfƴZ#@"4G>~n~]"j'h?aQHJt9Brd.;0 VыR%bJ Vh#6M>s'arc7k!QPwF/cOWIrT=f9.і&,H^Y"?ȩ 9U/"^ـj1{c2[j&3,Y32Hы0Aɠ4>%kk$+*g _p[j^VMBN|%SOaUOv\CH!ڻ[KFL 3R WBgUw?"M6y394& mZ\"&e0%ukѠUɋEb4)Mxe _mXz QPh@njjda`w05ɑD[0yŁg'`ި}X>|隄X[#Os٨\{Lp~Cuttttttڴ|JU?/qX79|XKMN]u Q rK6օMIM '?nhx!?=`AGv켿9׿6^lwZs8ƌ_:|eeoZW8.$eXy<|vkO[~?&͇m%|Qڂ.T_mc-6pD Ml;VpٔG/[.)E 3}t9uTkdڴiL<ݎb۶m}3<Lff&'NpkϏ~v ֭ (y4inrG?8Ǐ>9˖-7'xo߾ Gy'tNpBp̜9|v;wa!k?7n muuu|dggSRRz+9x`/++'|ɓ1K&xժUر#?(r 7t}/c˖-\.Νۭ7nw|}}&Mw:mJ˶4eUEmQA_~ʒ("?E>  "e](mZ۴'>IM^Xr :3E"VoM2Evj{gכoپ, +Piif̘7|S~$K~`s>x:!(n7K\zB5N-rR}KH9 iq)/#IRscHmGmK ن%8ȴww1)ͥTmڊXv=C|Lo'7RIMMjѨ:e^3ШzȈ0;xޫ E\y S)ޠ6*qߴ6[)lcN=+~@_=YMʮ˰qeZJ ju:{lComW6OڏūPĭO[Ҟ^#F5E'lм\~{+3u:%gNߠwwN?A{3tgkFko}vر)7=6gR.(|IY)JMj9,ei)3QgOSIMmNՋN ΆNߠų_լ[rgqE4:VҒ @fiG߾iCUcnm_A VY};tT.6%iQ1UmM5ڼ/Wm$-mѤV+I4x+'74#5 [ɞN^imJQuMnqE45w>7%5-MI|t5F&;1`{$M-k^Dd#$9|FVp՞=NG1cs@\)|z(;NSt]I< } wʋ1%݋/v]j2+,nX?Asbh4UmܧH/.-!U/5h)rn[٧ !t: z˔.):C=%SUU뮻Nwygr0t-#Zt*4u|>577kƍM;/\g}v?>l[.jyCCCNgm&Np(2 C?<̨3gN9s."=]ֽ;]mڴ)q ?Q݉ZqkXpbӟTNTW7?͍7ި)~q9 >---w.]:曵hѢ.H:gΜ)S'<OgyZZ666jڵ_ރ@eν>EBAm|=vTc)#Q)卹zqtI?Ö޾OڸA-4.#Ib_GtڶsWa-$9<$)}kH{lEl5l=>,n̯Zol}qBͲ[d7J `CcFiqitڔ4keih8" Q(jTm-sUMCB huJǦ',MA -mKǍ۪9T<2hC*?8VU[ zu}k5&Vi>$WX>OPɞ=귷=4,UtJLiS׌1۵_l7h_u51{N.ؠ9Ԭ$eZ4pB&-sbR)q~zmѴub:jÔ$wH>2aHGѷfk[vyN鵚MRzhp5ۋ?W _;pFfXC^۴ I]nAzu7)}{ p-.뮓N;sfglaHW^^. v[ G瞓y&~7$'WV&;'K{'o:ڵH+W:,czMze*g?.tROIӓ 2Gk{߯ש3|wih~,pe@)r2;v&l?i2 c49%нW?ݏBݿzM#9ԽRNƑ7"]uU{?'> uVtK.D[nu]-iZj>+**tei۶mquww{YF˗/sw֕W^n)(ӡ&''GJ_n&'v߯M6i͚5=n/(Iwx }~w0n8r-1;I{O[os$߯{'fpKҢx 6zgws9GJ5={z)y.xigW}G1INNc=/}K9& `X3M>ᆪw^xHm.0$£V0V0{*Z6jͦ=lk 괩5>ݣd3S˲dR Ð١+`rҽfe–"l'dEwS럭_dȎp 5niPs{_\A^59gS SneJ6!؅4p:kM٭(u1 ) FE,S9u2 [xɍ9fғuw3giHr7wFMSq0lm̸|ku}a5jbV}oP>~e jb־u.3Mګf||:N*ؠV5:;"MSѴwtTݯc0-IhT*?TW?)߹!]msnrե:x~Zz]i?)H[8Ǐw:vwYqz8x 8#vxjj-$9| [o9a<'?<׿:㛻SV挵7.zӔN9y :aA MOѺM#wTzJK6u$^- l[=ХකYդ }r"Ks=?[p/LE5?.K ,Z6cƌ8[Kwql٢kwϟ/oc|{ӂ t^S0ѣ5k,]|*))駟5wر]=z.uYz뭷tdO:$}[J8[nE]w***OƍРp ?JM孤.5uQ ĉ+fʔ)]ɉ}Q)));wϟ}k = /׿V\uEueKNNּy4o<]vekԩZjy=zamٲEpXǏ 'E.rB_o{O|MرC^W^xn&X6}~}p3x1}*--Z6f̘-8:,v>~NSTW9iynnc$ɋ}k۫ܙ۷_4ˊ_e,}P0Iz9']uՊXm+IStެI:=?S>x j54̓[C47|~E-;w#F7xR\mWzkg'ޘ?t^3MC 5h{G֢y?eRNβl2G IDATeY,Ka(t\2HL'x5gbKXR%yThm}ۃ~C^lΖ"!F~ 69]ۺ/یhrn5-Nֱc?Q; ϡ#vF65I tS]ڼ4w;λm.'9< Mطs zW^s~zߏ9&# f^]:T'M.6n9vJ~Vow _ԉӒڗO+QR(,y=Lr9 նm&:eod^چ?$wXc2*ΙN&mTѹjH2ƖvbV=6^\)˔g m+bY G" ò,Km?'a(cjf[L.0PVĶP}!g"H:otx1$Ð-gĂnu`Nف]9]-}!'עu̘‰ѕgC>U:}MݥfF .c}O=[zC;kI瞛x޽N׾ /tBy I+VH3fهJӧK?TW,Kz^}nI]Իq˒~T\tN4w@$n]:Xi׮ޝWNK˿)w߫D)bE_N<ƣ9St$NjhwTOm^2RLR}Cg o iNc8˥ޞvS}K\.Ce%׮&m[= A͝cҕsm:}C|m~;Ƕm[4rFlbowrAmTcD:m=q`-KDdCRXtpa \]җ̙QGuZ\\ڱljnYϝW"qF6o^xnys/ѣ{_kKMMG#8JJ1H))پ]r>g/,=tHsϹI׬gᇥ;WY5 ^ ܣtuǟjS.P]{z@'OORAGMLl6m=цa9gOK˔\ťhK74|dƘmHnT'9W/ntFGMOj[=TSY`ZNkJN2| "Xi6"n$hK2 㰍 N.MXeːb\'~YljrB͍Rsjv_c؞m̀Eg; _2|M `_G;v 1Mig\n$"C cų~}tAG:Us~۶j۽+J99:z{O">~C!9|s_yHRr4}m%W; /HG{"z Eim[ثwDM:^=a]waf 0k= >xpʁ.FƁ.WIeעc)-6[kr6[/Ik7&5]ǏIY*G>K%ֹ#`GHDgLˊ(1)6wiJre(5)Wv]!U¶dي!mZukhE6nipf):8NOr)U0j&jmMc2jqÕi0_[tj(MIL\.LCux׶n #j;B4 #vNcx\njtK e'Y,mV(b;t0ei;ږm%W}1VDvEj [1dcud<ɍJMjѴ;t~:*wfx>!0c:cu1T[;UOoo٫ݵ݇0M7zd|ވ iBGgI֯vy 2{w2krRtOM!>=V5 , 6%[ 4}@?حYt8)[c3tzmJpD.m]mۖu 4M)۶efcI2 6u\GL "zfkXZj;?)8wkcϼ- ;7tSSPk/,C;Iu֤-Ns&n::w|ܦE`mb6tyt3ΐ~dS]Q}Sk?l#/u>eK=\w6'ӥ/:?_ߐ !S*H3%(ۢӧWI;Ҧk%Qs-[RKR!搥Ơ6մ476CsҔQZ[>Kf-r}1.;/$tlv1’l[Fkpв,ź'eHY[ML[.wv k-Յ,l[lk 9Yt(1/|==ж#NҶP`nI 9Sm<~&e&7*;^zOs&nҴ;4&#z%5L0r  Y=*-ƽggt3 }>ǻ;k6t׳w2dRu9T{@#s.Wikj.MޭW2+qi{m-eʲZBk z_lާsҴiRM'ktFR.y\f`[uC^[p0V83LTm*?Ulh.)ŴU]'նj[,۰eHpvǶmImI ˎZF RQm`LSmV'$IKjFY&nˌ 02vپaH?)hBS/j] }FN*2#4mx:k[=Z9WoI1en8jeozg&dj ;6CGj|f&}J*lKVVIJ9Bhۂ5`K3L1ƭhްެ i[eeHnOj:`S8(Ya٭P37t2r1O5sM]ꄉkRn0?6 ?YGi&겻u 6t9V)%#w(ðԬdOPiI-lSiI-^;&m۶u YK!9Шw>ڧLV~ve1>JMVFW^Cl:W]so/'h0$!%JuR(["p zKuAK\0]gaK(pgYc{?;t~`k׿H#|0ÄiIMK2Z'' :*w$]k 2@FB'  o tȐdWeю}Gz󼣓 2ߐ ]0{K$LK9iu2QK+Ij&)ДWe*b9᷺4:b˛QZWcғ4&ç1>IOTR=<.CnӐmc2MC.h=ee׽lIL帥ѩ Sd4֫{"zuoD[êjH-Km &mx[gږHX 5;"a)"E|CΟm@jh  2[ӢN.ؠswjbvR͚[c3q$<$7F+wQ}s-Kr2MVJҒu蝚6zNږzB5NR\2$LC2][VEuڴ<.S^'')UnKR}&kBV|.yMܦdH.Ðai I!ݷ¶%˶)~SSMk6׆F@z;`6,%EB!go8$EB2"aّ~di[;EG'cľa|\c[ QFR24!F%2d;֖k@&g(t܂0)?@G 3u7l F$wXI kbVFIƤtT.m+?N$շT۔*ۖ""j"jkކ.SnSRn=NMRfW>4[>Ru2*-+i0$!z4lRcRCRزe$[-7!Kܵ-RC`(:ߖlmԯ,hwE].[IҒIFԜQ^WX+QEakm_dS3߷M^^Ky'&kEa>)c:sOc\ a8!] A&fմOQƽyjy \j zMٶe+`X2$E$0dL9dK>>J(e*+5IY>]LÐǔ2\J$R]0K\4)(˶U9p]HvS`PmE7mm J>OPIܦs$PQTGs&n93ސar?^ }W=:֣c=]+WBUUUn_^XX(߯ŋt 0hGi)ۢ wK֖9]uz{DյZ~xzl Eho}65Z2 tp zD/ArZr{z٭m21eٶl[y@Lʪ]5eZM?6jFv$y\cVTThҥ bRUU%Im IiJKjVZR$)֨:hNQ]OrQmNm FzgcBcEҳm' h9-b6KHΈqpA[hQR q.~Oۮ9;k<8a-=ܗmNW7؇Ė FSVV˗]QQBZtiB<GXvjS%Im%I{Tל,[B=qmېeט@cʐ-ð*VYu *;^.Êt:Jk+I3lW~xAఙJՇkRK.1.:;/kᒟ`첬j]{ oO@lpwMlg-CnjݪܾeoIU'o'vɲ+ayB2cޭYhWs^w04kiR䵧W/^H̒jyu'I˖-SYYYy+VtRF;<iQl/ӭ 3M[PԲK?3%'cnm9њ}-7N8uJNx:&ۢ$w8DXBEEE%\jժ.u60||w5o҇=L5ߧ81.s|KۋV;,n~`  QVEE~)W]]ufff Y !>wH;eܜ5ıU}>یȟ(U0l !-߯~9vaaaC`1 [.#Ɲ1$e4(+^G1bܱHUUUiժUnZWXXB-X@ Z+WZdɒc\Rժl?WAA,Yq\7onz]ηzj-_@ `Yg+WqٳgҥK{+//#ҥK{ZpaaA#0 q˗/kQYYYr}N~%%%:_"X;-//ʕ+YdIz(XRReneeeBccy,[pa׳g֊+b֪m233UQQ^{[׿ίKhX*++cobŊ\KKKnݺn@ Kv:bBu;UTT:&??_*,,oUWW F}]YYe.ADzeee}&RUoiii1~jժ@@1+F&EEE*..VQQQT9:|XξOHE#F^UNlZRV_G.^Xqυ ;qyp^UUU-Q~~~@daaJJJtwG/ !0Ėh'' k*]GR7`FTVVjʕV 2p())!Dv\R咜~}.dXPP^!=ҵ^#˗kŊ}@ Kv# г*-\0f,???*z#X:[bE|gnX]]͛7'q?Koٲe*--20#Y:Ե^,33S*..Z^VV˗ŋTVVeffv9:$iŊZbE暄 T`c}p(B ]3Offf׽4ʺԝx#s-^8mc&F)//ٳ{UOeees6П+`! R;%ޔ.CHIIntzbvWPPʄk݆-X f~_UUUZlY _9;%%%뮻 l&,F F Ci t bT^^\AATRRҾm{܂-[,j[XX*UTT2*UXXn[RRݯЗ[['հHσ>%X]]yʢqR:vd,,,l9 zTRR묧 ]g*++;Q\\>s>\PPPRݦ98~D# RbuTҗt)K$Z{ -;IEGCXR%@`"D! C@ -o=7}@0L{\m_$s?xR.g+ 8 lF{ZSqV+:pݢ `Mk>X;•mYǯ},nOl~5E#X]-߿}FÊ `[^}zڵ^{kTi/<|"۸<>v M]1 N.ζ,U>[$itl{}{lֆڦv&M}C iw0 wl?q.O_s}C45 C^KG+l~hO)o~`$ *++2H?ߧeEKDV7#{չ+ޔ.2r&P?mx ra0;/_>eЯ́.f zEz <>~2]?O }ʛ|B嵻7c-iʗ6*`IEVѫOChwMK~Z'|s^ڵs=#/ )))]]jjj0I5?)_9~_G):Ui{+lJ0\yR5ʝt\۾^)y@e 'tP7w!ѬHX=sYPչY mx ڴ6jyMz[uuBM.ӹ`$ݳY|jm2L>߫`]~riWz衁.bĉ;z܎ `ĪkGo@\٫eQ^۽:(o>pІW?{B$ۊ_,Յ?|:+BW\q@$CT$Oܩ67e.jYC M)ﲼmpS0;pP=}euf=u =#ʞ-o՚u{~ >gǿ]Mޤ>va=$u5[M&6n?~#XпFH85Wj΢KvL˺vvmz7nX=[qۍ?T?aoϖ3wʊcn&c/]:7]']W6HXyˊ/(t 2n@0lEAzso{qkw/>y5ߡn[O<6+՟e {?K@z^~^E׿G]1 ;hdžns_˞]7ߡkm?K@ ?ߤGn/=v  C^$Ԣt\߹!vc>Y_יr_2 $~16N@/M~y?.mӿTM[5msko6 ~g_.0`u7]+uXt: -70m[=z[e[۸<>q-:n=aZ_WQ3^y0`3o{;HwӦI^u!e_鸮k >q0ru]$58pY/=UqJN׿s/W9`uw6۾>j3 ͿT _7MFN3ᘄZ?IYpԺ⦆5n)`oٳc1]_ǂҟthǰm)v}ToHI]WWK'r0F0믫v .νgw 4V>M;|M3@}TOQ]ց.%ihٵqEIN}M.ν&iEG9:ez뻬{wiŒ0c CrӦ9on]O: `nպjXt,w{5zwE`0zP{m2bJQ]pX5WͶwe6nOc20@σle1$nU{sRQM:hKJyK6۫}LϾsھEm|yRiYc||Ė%z4e @5WHO>g͚T憀Eڵi@ҭqoҳt)?{Uq3=HXP`.T(bA)] bϊXPbDa hZ k{'2%<`9/$B2mGmtOP@pӥMrOnѽxjֻHTxY۾di{j%kk5P+:uΩ32:ESF ӧp]FșZ3q{:ῺMk+͑Vly4ݷU[m^ׇ$S<]PnG?IԚ<]h>'MO79y%8ԉ#.ZK;Ss&^0׺>BcgפzʾMZrN[7OWPx 4ڲ]sCe2Jw䯕膭uiq&E+e&ye&z2R=]ҏt pA8%T[WRf>~xS {G7l.Þ3ۼ#li=֮6md)+3V s);Y[W~`Ze~#B)߹ydUoRXVXPyjTz AKt Tؖ(2RuP1.p3Oii -ׄDĨUI;Ѫm:s~Z`7*s'@Y/]}5R^҄ R;u )-[V%T+^uZ1>e\?:w/룢l^\1@u\'vV|?>Z.RlZEo ׉{-ĶޣQpxm}JIj&3YZR"'GZ@[OAB<B^n}VVNw*w;_DLEEe/ ^/0ȣ5AN*t>!ÿ __~>h /8Gk~qV~rLm6]!kD=Kylךُ^qK-UI^4jԣn&6ֽ5T5wp:-{\et{R]j%ZJO:AN#@TIE&]|?P:2Cz7;涯:in ,2R>\jR.@[WUKJI4I7J_|aʫm[k7!9"7җ_JǏn1chᅲV6K#X׾ԠqϽ{>fϖ)T77hP=Νx4l(8!=*csB٤3>n-֩c읐 ͛'͚e|܍ crIھjuթ_49^ ?-\Yۜ8W߾]\SI;3]uXYqM Ё]kcvs˧ߧ֪Q*f矗Fԯo<ڷ[o!70[շ4yt%skVG2E8Qtncf JomΊ3\#]Կ_{\i:^x3|T|Lh8vx4iLix}9sN&ﳋ5ݺI/$+G  TOv֬'r U[^Ԑ_W_9_D|ջ+KڶBNftY5xB29Վme7o3M&?+QQF .~ >\Zq8tf=wIƟgI|`0"(HzEibaĿYZ8b453/}e9s?VN\?tceۏfc?]z5U\Up&t.%7v¢=JDuT-z6Aju*ɭ_+"BKBuS?F,|}w5:mXnj)9TlntG *]||IG~y}1>߽z9|G5pؠAٮ( ;>{J;:~afη^*-~\cMtbԻJ˹=Y/飶=GiivskfOPF(Q||<9Y>];i~:ulÆI&֠tFL[דiV6Be={||}-[Jyy%^ʙro#ڵsFKI~Yڹ\o/EG[]zyix5/(5lh=_P ͛'_/%&J^*!uVW p)guaZ_*dϵfΰ\H.h|+U;Z]=BWͰ[9_Rը9ӍtxFTlLOOV@Qu7J))؁c2#H}Z}ֺ{)?&Lf̐&M`{Y[UG1Bo H7$=4wntVHtaao_m[{GWOzukss~~ޒͯ}sQQw[1iР I_܉ %Nggh'kW6=6l"N֑?~Qځ⯮e7릧?0C9~cghĕ_~>PuTO-[Z;[oj֔~͛0]=t8. o3FQ6bz;>)֭'m`m6Ǎƌ-(0 &] ~|L\P>[k愎Az5"9xGȘF|T~A7n;'@U ޣgoyY g 砽ɚ5FG5=zǵy4u㏎CsCX=k75ӹѹN6j?I!=fuשּׂ[%#iMrk>:wkI'O:^P*LZ}PegX߬nZ~D_^˧gyKzְgp/*)vCk\O cL7WT:qέ|wD0UYۿƍ_o>+oowZsW|%s{;׺c^ejk]3KIoI={ڴ1__P`tZ,ɓF7Fw![k㝴{k/W;ǯԁwMO@>jsY)uv9oac۴xaAMa/<9hu޽ڵxwqaRZ/mk>WP %&Z3^|-g\Ykͮ]WH۶Yz믝?87W7zw׫g|ljsn@Ep$fk ǹoI=|ZN?w^G 6:8IlTy jj l>>;zLƮd}7m{`li1srt,#sV@IzUi-a< עEFǭk: VX(X %__+5Hw^J(ퟁfV?^=u~-@y IDATMKurPqZ]=yU3gu)5Q4QQL9PEdgyi7cUW3]__EE撶/>p빲ĦN5uX{lmH+=w _ڵ .(5))%w2B^C:]GJ-1-yn-zk_ftECYl>8 }f5?VXT]5x<{7}@UΫ״: ztǯ^ npwSVQԭ;UT[?[ W|{\Iݥcoq>W҆뒓炃(iʉί(/Rٻif>I \kF?2[TT+kth:uQ4эO|N7<%_?j@5׺.{ܺ'yG{ݯƋ #QZ$G5˶gD$>mtkJ6|cn{e feU|a빺uKqivm験 @E`SrNiG秅oI=|$xV6_]j?=Cg‚|-2B>;zLFٙJOwFfrT7 T34*,/6A?QWxa=ݾ1n|})~w{v.x\vsnݤsqQ46$D4t5VT *MW?x *R4}p-yN]u"w^{:=2='$DƨϘ쟦LڶB,~U9NKIs?/z]IV \hf͌ctKBC+~/X!qc)>^z Gv|aae|X%~RTs,_^֭k9[PLK͜Q6/\kFUXn i;TWG֬L7>LWy\~n*zM۫s?~;5J/c?QT轢RV5T"xp 6۵)%$؏lRÆF{^}Uk#7$=tukz{Jq3foڶ5~eQXX*Mfgqnɓ ,^8}Z~ϙ6i@E!(&;#E߼9\ME׿?8W_G7Ϭ?~&I&=צMRvtԹsVݬYsk<^iX3;*Z}IҠAC~~K> ܈o=Î۬/_o>KU.y[ꪣ{\w_Afi k7SGȋ[KٷIxBeӻjWUX#:H۷KἡC=cxǍ>DڱC>s昏=j=gI Jv3;vvRiZF=jϏo#J9\K+X޳HMj[Nj3+=ҁ~I顇kՒV<]Tw7Y~3UشmunRAan!;#E˧ߧm+mU=RGK߻G)[,~kI)0$RuKNؠ ^RAyE7l.Þ3i7y;QGKZu?}ꄖw_ԩZޒFճ^cIظYyUi4[KwK/,Xa /6BV] 4{!ԧkQWRtg0z4dc„uoYl&k4Nfͤ0'^^?&ӅK~j}/Ko|&%I]&o/|oF<(aعXF>i]sna׺ϴv:a& 8\WZv-5yRaa6-y[?IyjǶR;_@u]N7LOԠYg5h~*sNupj%nn{5զG;@zQ?/~C***>YT;Wy˴7̔n]Z5IbO_ml^x n]#WZ/ 8gF㏍|DG;knG &LJP˝sĒ?LFA C <8-jXAҏhk7kcb[t-W_z}1}2vMO-#.ж(NyEE?w(+=EBo*vgr*?S=Oe՗/ϋ^LD.ҥҰaR~~kK#pgOk_yE<=oqgureutߧ(,fͪ&Nyƹw-m2D}&}Aٮ(-A)6[-}or\r]?ǯtxo@pu |hn?B _?}fOڱtj?aD\SM;fע)#&!Ob7v`V_^sZ{6:\_}(O?5]=|b4]`DғOA䮃ѝ/~;.uQ=L[#33˶GNyv6'IǎGF/_^{͜i}L5+JIj9wWoao+kt&7r]uwn}LO8^7=L7ssPKٷIxU9F1Mӹ?Vƒ Eڸտ)dZ#4*uǢE&=oe#7I+߯nᇥ I#GJwoNvS.:Hk8{ѣK/-}/Z4[v6|ZP Ԩ4{vyԫ!PGv쐺w7gJh_t䏟oY.j?Du_Z{X3Kk?}a/ 8B>W +]A *,Ivl+?͢RaۼտQ^i:{a7|}ku^%ܥ;W;uZ-WEn#:ZQ )6Vj56aK'NHƍҪUF.;|5$'KO?-=ԵԪԬԼ1I ƽ.-럕 {5i"vԣ][ }ǺuҜ9Qκ6)4~5טϝ::w4aRϞRiS#Hwey/?kYEEҫAΝ:u2ˆ[__~i>P{r\sdW^N ?uw-_@s!~/ĶwLJRZ8n/@?_V]ok}ǼְgV- {AGl}p2?*8yF};u*=DWDKjqoiHFt,={2駟*vHK3mުP;.FE[h<\#CR8.#e6Lؾcz_@p|C[gaKٞ1mt u"c쏷=EoPA~f?(n.=w~__y/ z b5m7P [;ㇴ1:{uխC-}uYd!#?0D>{v`~L+YDuT-z6A.jz@}7n%mnYQ;N}|wr .L:OWOWw!n,M/EFijjӫv+**U3I:sHuY5jhDA~6.|E~ -ķQQT&n.Roj;wm_5C uVvj_f~-~_]ZwWo*$2W)[{~d_@.3Nݫ,cUu:XzjfflTʢi:kvnnZ54@etDNؠ O^HR` x@Fjr2c/ Xy/ڟմ@լwѹ"m[9]xZy9 T硓ռbgNkÂor]բ7o{***WX}.hTqPʤ˰ttJ;x~^ysnzjC~7vf?Y3J\UW|:/=7t-?T_:HIb:^%zvPGi@oދiqWߑhī7,AxS񉥼HK׿?Ն^Ҧ:Wey6t_>@Qz5U޺n.jТZuʜ׺.i벾uh7V.w+P $no[〠0mez{ĵ#THdL $EK߽($aࣺh|h ׵u*p`*52T+ӍZAgĶ=]dtv{߮p[KmQڼlܚT%n9JNؠC?7c?T&,;asodL+J@%A<`L|]ԼRZC=~Zy9Z铚\_:/ӰgV>c 㶮@j(Yǵ7<] [4׎mKz뒽KeT+]w=udoG')%vgrxdy*||t׺C"ռP }fd~p9t7;qtr2xV硓mr2SKܻ:u;A?0ƫm{kKovL]?fSPQA~qkU!ѪYŠ(4dBecUTTuy9u74hvH>>U*JO';tvF̴Zh+Y@P? u8GTo̺I%)0$B=nC_1r295xcΜM֖K^D2kjiꀒEDǹlogGߒ׎u.6mI \ Q"j7tjm~^r|Vt}/PɅEUQӴv?#6/{Wm{*։{M9zYJ}0,BfTvLZw׭U dgmYe/ XWnR[^Pؖ.?0T-꒽KyZ%{G4R]7xECoG\\=Ʋ Y ׺.g~,'Mn5:nn̪_]}KJMrթ_^nϝmOwn1Mj!W:vh隈q~R)ҍDu*rQ?]k~hc_+(,%W9,Ӟ͟ VNP^ס+9aC‚|-2B8.6Rʇrt۴nVm{]_AW-Lw0G+>_E^V%k<˦΅׊%ZU{UzΜvseC@E Q1e?噴m~Y8vc1_ @p#%nvnĻعZJWͰ\.Þא_+vC7VT!(%XRǾ!()ʄ pzM۫àL~>Ĭ`dL W!nd @!ʏWwG* Ґ mЙ_Xzn <: pݯƋ #brHI $nkJρ5ڱfPuE ]-C`7??ʏWl=jBeghԑ**,@eev@Ev/^ Y:m=P8WשG,oIÞ]A?TI-HJ㫾c>ta*HDKNؠ聪.#5t#@xrrƣ/@5!qr͜pv0rPo ~d>]PU ?p]k+Mwuaϋ^WҶ-2Rٍ||^7!nbO.h|+S'1L\WY'2yZ_s< @U ?Ҷ(5DQ6JY/ 2RjiBunZUTTRTb!mybQ!W{7-30N7LRk/r68JON?5oT# @ٝ .~vۼnls i=9*3mZ4en`obw{>V|loPU| {^mzUR؍ķ-ENq=A8:o+f}7Ux$Lpk7iyMocgn)6iydU県T##4@%6UÿH@۲rQTXԤv1{`s IDAT%9P|q 6ltuZM*8s͕#D7l.Þ7ۼlnNz1:@bTO :uH8rƫzǴ䝻:f.EW2z*$OI!@?݅oԪp5bF-3S:JRd PEE.@TXXP=̎:.-*a|6η\nxMK?$Pbp+{$H7>MEսnLn5B&A;OHI4'[$nTZ9'͛õ;^-ʫ L޳!@`ͥخ5@Ue&GĻxj,':ķqɽ< _j愎ڷy嚀u |h¢.pcu@WYB} f*u7MvX+r,#eXxXzxj)*,TځvU)o轢RtuF׿ujyo;lм|. P4m7@Lϛj3;p@pcwVUf>D׿t* f+զ)6,XRA΄x3O۩wH/g?3ƻx'b&JRL%nbeghk7k{u:;r]\F׿T$o u7Lf5ԧi -\{+*YFj@;jiTS1v0Gk?}a/0$RNVN72z9⹾Hg79!fx_?]wl-2B[͟ zhu/g~AT?њY +?vse*'3nG@b:Vʈ XJf=PIeŵ#THd*ךu ~>֢n {Nk:oU5х ꜓h7߳Pf9=;8+/;% B\(m6z*]jit@hdȮh\=1~"%||gC/^T_l˫9Rx\{TtMpxm zK/|)RGyUPi>U-vc1j\sts?XGkicXw\!ճ [>?PvL]bЙuϖ` eϞ&4UQG7VV6)"c_CkxUPD4o, .d\|î!17^ۍpBUQr._5@i%n]9ֱ;l8 Pu[\uw{t P)&czLӲ*i uQ]=H7U,Άpҏ챛߽3IR;ޒf+q?Pl6eឰ+!!VПFr˃o鞄x~U?~5}@M.M_aaN;`7N@~l2{I]] J I @UE\$N`Z=n&q7V< iC+?vt.":^C/$!̗_=w ?;K||wb.QFjҹ``fj2RM~Hy:vheRk7:4nGĻVT>̎Z {MC'+qrdB~vi_UUJ ?TQBTv~Ujof7WTX%}Ư)sҥ(3m2}>@;'PŤX̞ I TFqmF7;˔$Do7ͭտJE.u6˃L, hc(8x`DtCm\$m{ITY6EԎUDXyg<{΁~|ȭr0G\?VJbAAn\ 0&j5h._W6=մ %lxt75pB{eT/6M$\;eCAaQzZΝvs>~Xͻp0PE .|}6Hbۨf)f}׬/5d7{Ĺn:$*+­ΑC\ $"Z_%٨ԤJIܢ̴r]XpF)u6I3%/:)0&j7lUP`vkJں8GU_@p?2_{~^<]n|=]P%PPӨ\A~޹3SzvP&~`OTk~DQuxT¢ۢb[t=7{!0iR(%i2R^J;]iKkgK2֎m)?sׅDD-/hc\ÊmQހ xXPh Ŷ]΍PJ?[šw#\_}@B!mY;;yS`J&mщ}RQQew(a${7-vu_@$C"ԠUjsc3R $mщ{ ,*,Ptޣ-(]>Ӧ%oء]7B԰eWuk5t9@C*5hI u:7sR &nQ? Oa)I)I['swӷ9A|rP'֕o0@:ޤ]<] pNO 秅9R k]wӥ%PEUrP-J?\/7:A%I)6U^q492ӧ+?/ӥvv\_ =^?eu.SFj:KSST<7u LIܢ# f[‚3{t ^G^-ir IVxVҶx-PMޅT΅*l(09AEEvPY2@t C9Y<] I:Z9'{D K]JqqRÆƯQQRj"%&J6I7K'OzڪeK):ڹRrrNS-&:ڨLQfMuRhK~ڵM5Ո)#C:~\:vLںUZP*p/PӧR*%qZZ Y UEI T&+wxU{ozB@MTTP,TDE^Ď], ņ"M4f|<ƴْMrk/͞3'Kvfsz0$ҡpQAZey/+VTN'SJP[0n ۱`\ E跏 zΙjvcFz>.x|^- |TH4RA9tl"" ށJϘZQ ճktDb=c@.jjz(DDDw =s6""""":1$|pg6ÆOgFdJt {9OW^~%zIy32o ^xh4vࢋ1kp]QMJ]\`agp'"UeSjx$DDD5T 9L=]UU4|8nвexEUtJ6Ǖ+%)$=<U4f y3ШQXEDDDDDDDDDDDDDDDDtZb`-7I׸1d РAxPᇁ~ _^ yI_j*wZR>h~ŋ-\>[s-|}ΜY3uĉY3`޼KDDDDDDDDDDDDDDDDt:NW='%b8<Zڵ @?=͛O?1XҟN =hhxߎmg,-[Ӧ?=O>iܾe KR ;[\6ƎFLZVX zFo/.^+_vx+%kQ6#G=*r3fGo )4{&g :@F9 bxbWX(*Ul0a~Efsh#"""""""""""""""":]1` __ʝ8Q2⫯yn;7u.l7f3p ?z5OR5P@׮m;vfZkz4/@@}-Zؓ$A`z矁]wUmwf `Un[0dp7߄?w(~>Pk1iS#"""""""""""""""":]1  (v97c~`~R._8k4m˖۪*#H`?KhM ?2ٳ%h@E Ǝ.+v?pظQ4O~RxC]Yl/ QK#n@""""""""""""""""`p >\m< ͛[6nؾ]J $n0`:)u[xq?80.2R}UR>o_ 03&1WW->4(|jxWb6Nv+:Z>CZy>V"""""""""""""""" kXϞm˖ʾZ8}KթqxQ)1l Sԫ6rL90!}C ,\~uJ&F-&MߧۍۿFJ[ѿڵ2?92>zK^)8Æ7ZlKoHu6mjYگծ]=2"""a  87Ϙ1p18|?>};w6nx*?#AZR OO /ׯ_|s6WrݞYL~p";2ɒǀ1` v@͍߸qR7ܴ2RՃJ̞Y#匃5q"֯fKQrz^-%*8P2^uUm߃5(-MMUܚǻ-\\~d|k> >}VVS .F~`wDЪ'ioٳǸ]/,ۧOWoN$R[Nmf30w.…5(ܱc53o1C~q̛\vs5n \rIhc$X$_?yL^~{o~q[H0Z߾׫M7j.gJ Ay+Wjկķ*@p&p$({͚HN} AFͨ4Mͧ1Jgf ;੧$pRylV]|<пv.pUkթQP`ܞz_{~;о}}WJߤI~ #uDDDä"*)=sѰF~٨۱XOmH dͨh|^6V@۴H/{DdLQXNQ^z>vŀXrrs֯ȑ߂}0B+d ,rEQYx$`NoJ 0s&bd$""2 2/B1ňN)F|"AA-&Zۣ%""""""%‹H;+-.;60J]b4.xNQ%O%TQ IDATW-[]Hr^231Y5(kZM&'iim99;c"sNj>)=~7귵nB/j?ҹ9݁%/X{\j3Lt'| 7-- ™٪YǃyhK&Q1}Ӷf!$,٦*dVӠs,'\֭…qR l&}M&+knDDDDFU=Fo &s^RR֮_e C?zdloKJ iHk}_Xy'GO=̛ddT8Z-G#3:ZJ>m 쬑>D+F\"$4+glU3uZ(рDDDDDDTL- Q|bPLjPVInD$16D؟'S޽Rl(M예mXۢmu~`)PFc7*kW궪%@~tuU8z֯~w %~ з/!&,^6r!I0}!eVEJ\Z"g Y;?le bRa!*ŅHT0Т"Á<3T.r i# e*9S8zTb~M пϤqrΝkWCh׮bc+Miir]>/]+/P=:մ<6#G;/$\>}5oWO};-;Q0Xf >̬''mVk({aUU_߀@ s㍒Bn6z49X@8n\?xQm2}HhUVpzaAq^5-BaD[;'""""""Lt# MQٲx+m ݁=v1=5x00ie$@ r뮓==ӧ<?gЪU`TK*o3y?^Mذ!<V+pK&/l,2~[PX{GЎCDDD QUmfޤIo &VYu3*[UQQz 8;wO?>sfedxӁQST*ZǑ7I +w( [͊h|a&In(f%"""""8긑vV6Frj ENHZ\SǗ_H؅|"td7.};uϗpTj XL;߯ 7@@l)}hZ=7lz8eխ 30eW}"%""" 'zԠ]$wlv{NiJlR8qB`2m{x '+2D}Ŋ':+\\. e4)Wc#""NX/R\{F>" E! 89,3E\CDDDDDDY"Hf.X>(5|9$SE=há_Qp)id`̘ѱ՜s$6gt K)A$+`(,࣏~;SǎmT˺zW#n-HF@IV5zkHDDD a?('Z>C% +0Pdd'L*(}_nE.̜\R {#Gj}X̙X[*tFLz1"&KeUmWf.d#{mx3YUb$,DLC OXq|YP\NzEET ňkZ[T.@@j3F3[۫V>lk&.d!{0t4e 4?O>T[`^WO4nW? lۦ޾=0mvҼ>py "bcY $ɤys믍YR3$@ӟ|)ܪ\T9W/~g]Njܹ@Qv[uޗv~rQq`vm##%џRZ23%P:,[fb( @B6VF_@t/./""Cjlq :Y'?,\vine"pN>7Je>7/BL$+#kLlK>}Gf-yG6"(UyD \p^wm@ 碋?^ 22 %3ҥ2;U7$rt`zy=DjBn vh se~T"1O,sCHfȳϖ4 kX~>ӧ b yhZw+?b>7ܠVYNtƎ YV> qi[3:|Xrq@-\h׵Wix8P2ii2|>X:q7ݤ?V6l9gzo'AwVV=rs%r8=@F/*x7&PU`&`(y}:@꫁$1= ̛/Qu``-xQn_:o߯͡CW~~}z^{M;HKQ'3z3O?鷿ծ[WJ0)'խ+}WR_}>YY;lZG~>jһ*ѭd#j(ɬEbXZdF3lr'#|2?+ix) V;~\{Osw[Ϗ .J@bFFŋ6KPdg3f[~С,^ |~ {~!QuNG;wӦ駎Zeё#_I׉dm\Z&N *?t)r%Ы~c͚Ir&/Tx6w>nϘ!c?|X%卯Z'I:[8.|úu6m' p%ͼQd }V ,+|S.dDaCIc/G&"":YX=HŰFye|Y^D9Mذ @uR1pu#U! t4""""""i&Ps>,ϒ(@f>Fp_G+ A,dnwhp!.ƍ2wRelb={;Qޏ?97W~n}7ŋ;d<޽-ZH-EEvI$9VDI~oSݻQ"""pO-yQkW)ի'@K?87K5MB~WZJڍMz DD\$m+__xy? mP\ p5vGSSX KDDtP*R{"UE8C;{jdlUA~TؤqgN9ԩFJTe2E8׌CDCViD!*<:E ֮23x}۶DŽ RoL7H@L@>۶?ڴ >𫯂dTT$I=Fo'u^rvLO@-|50\U/Ȇ +Tx^]}; Ɨ_*-gx`ɄS%]wo\%K26Ֆ( 7W-?jvjۜ9GQyQňkQj>nL|͐z ώ\^2ZjY^Vx& [(̰Ō*XB}`^ qˆbbddl&)`IJҿG\T<@`c(*ϷneW,TUBM(Y4nk_:Q#~ lKDDDTUͫE6ɪŋObhpU@qm_}U.4MKʒ+B?^;WD=P7q̔2+WVdd/yU}da>]woSbϙBmBrpf3>^Qw%L5‹Ȕbo%:nMz7kvX \4bDhڴwK&os-8UdT)LOM$bɔp!kΝg4weKpc0>.Fy0 -JUղ]3{ ;6`|`guY]vM.T} ncǀɓeW WfΔw@̘r о}y]I-_7hƌ1NNDDtP|n *RUfRѴ}>mo6sca2j1T8 nhJHtiVON Kǽ*OÇ#䲳eD5=zW%Ѥi1#-R-ZY bQv`1o-v8|xETB4*9+բͫGRj4l~118qk^2cDUlNI,>emݪV13WMH2YӋ@VLR&p\. yIs gY4o<%$eu΂/^~YN@oIY咓}s\p_cT_/AUd5Ok a~7)[9~( ɬa`8e4_2W^?mй3p`ݬYhc0$Νg uN0 K}ne0S'c: #""ȟE l)*Z%sѣR>82Z ^s)]/' 7uP,>,JφR23vK3N/?^le-I5Ll~~Վy0nTXKӦm?F ,2<{L"""pcIjy$Gy/Q6n,ALDDoYߠlV+cб ذǭ"6BN D4+WKF0W$tt? 1B FR(Ί}#a_ kآDb^""""EDOwPsc!RL*MI܎D1yd" >[YW(lўbTYJ|*\nIdR)ځf)V>߬*DfZzl n=[ٱy֑ aNPjy< p!@.@ne(t8n@pvb^$ըi5p~۩DDDDU+"""""fDiLFI8&tnn1hӰDH &G]M3U=8tqwvp>_}1&\#\95& Q"c͢ -FpyTo d"+VHY7G߾=:""epر Ubd>]~.VzݚhƭJ=G=Xb|?`֢<,\]mp_k֍5bpL+v:( |F[h/+bWY$(𬳂Ÿ}W֚5R Z]_?RU__ۆ@X ?sxÆg#""" z`q~syT]WL4Mb҈X4O)hG3t? TՅ2qCGqssc𔣸cx
~Qupv8Ӌ?UT$Dp= hX׌.e=#`mQ7Is9x\,\]ܢH%Wx<ƺr ,fcѥ if\׉ŠYExl54hQ)I`(3f[1eZ\sg^`nN+ qnGo+ѩ0f Ч{#Z>~E? `zqsT7sluc֭ xc`#""" &""""Hpd>\QG=/2ՆǮ7 w^F*2}m saǡ}{c8 T}ċŬ0:ѲfaWםkvo[;?3^}\K e,ۚ6WbvǍE*XEAFV1N"&dy1<|YysG&30\6mriZ2V IDATH bOVg^mʕe`>7=ϗqj} 0r<4gT`ҟmۀ[瞓l;*gjO|{V}{?>8|}Ǝ.,{$HFDDDDDAP{fk`S ^rs1&_bR<=&|Omxl8*_oQ!ך7WN`vVfֱ ۟ԹxxyɊdUSƂ\|^M&^fspF/+/[B䧘WHReO? o d;xP=*_aCw24>]+/{wի'fɒϩ*O#`bl?֜(y mGQT:6DDDDDDtRry`q=ȧho;4ꮙ3:lVഛtřп1=4'A=Z ʁtr/T_;w,N֭{aeY~?~  `J ^|Q? \~|$%I{?)xm&OS$syDDN,Y*cMDDDT,=""SQ̙.pؽƆ .}SGm[Y1R 7m[8`@yt0f Փϊ L }/,7 C;^I/( >,mpn] 2=|LVK`8)1v3;V62D^h2EqhٻWP v 6}\`w|X^&Br ]lec*s'0gZ9wA.@vLl,_,X`ߧ"\rMr߭%K$Ss'PFPϸq|XGQgoӧ T\#I5-fFy/Dz.+Jw 1Dfցs;pMQH!|,x 5Q%&ƛndҿG!ァ'JS~0'Lѩd:xbx l "x 7WUWw!Zb&0|x4&/~zƍ3[hg|as4y-P.d :+ q7hP=5U]GI\cm]IYѣK%h];[$K`H~:~\>=]IӁ'_e᝖] qzk-[ʣăJ iӂlNgGת~d _F3K2sԔմ8tHKU2j6".7ܠѨf}|!V3ఖtڔrSS=;([|0#:8gPapPUnʒkTO2ul%n{b b~ 2Z| H@tڱ$0?]|'F`B̙#& dgOFZgNY`to(xCDDoW>0o^͍)u݁Y& +a6D׳φ8]qE70Ў圵nB6j$VC + Xax! 7WE *OX\x}cwh|SkW@sQL@D]w7%[Ч`;)[һVlX҃,Bd)s3&UgGU [ͅKgEݝ ,LTڶ@4VVbdy]ա^=z9?-]+jQ%(G/`Dd{~QQ /I6t(miM?On6}pvV_UȂ!y{T32t>]];]}ǩn]yvK^ݮD\~`|ڣۻ>%'˂^ $>^[&L_pRYЯ@R\iZM0 " w.݊/A1u9XS;&leH3emC ̯kt%22W֭Xe <0xY^wP['ȑ-$gB@JܟKۺݲ`m[JDDDTS(@f(+f$˿O| 7״8r#+ˍ@nlF˗Mz?*XtTttp);jߜ:vLVa?S ڒ̇={DUe-]*Y*4xM6VI `)&Tib`QpK'%ez0iNic)m )ΌDSS,¶n\5!E4s;:4RWUUYWڝnKs;P]#+׳Xv @,p[^4i\r8 K}Џ⡏?̨W_-ʭZ͚i[޿tѡC@_|Qf\X-{,l,[5w&ȮرGE/-$%3"t%X})^=*|%ӭ,E;ϝ[>%YffCDDDT2Yg2'#G|…r#wo`0CRW/3H=/^ə֭e͙|ʒ5}'zI"lӦɊpx$`Oρ/v.!io_L6iewH١Փ%?ޒeܸ.} ?QF#&&JLӦtҧx\kPPbehPd(hІ&`A7:4 <łWoM˭eZ_^7[x_E8LGЮ+7 WP^(w_tKכ ?=.MٕۻtL"DDeLr-Ѿ6˖$QFr}tM|m%vfϮZZkªGȑtJC-c=da\(ߏ>ҿ2p9m~yⶪJ)9mZp+)L5KDhC6^\3Gŗ§J&/Პ\ךu,o${ܴD3?? bb6$sڝn\>.9֭,]ZvJ"""`1(x~Ͳ0iB8Tv#7/׿mJʕNaJ4+}%mMwn:|Xc@LI)?q$YX,{C&_gug`˖mn7pT3d\I&sϕ '&KUw3-%[MdbһֻGccH^"o2)hTϪ`i>_^]x(Q]ntin,kdŌ5҇V7W++Pno?%qcÇB &""":pȏ~d媞Ç%*'TU&wzJqqR??rV r;wJy >WmGJ޽ Gl0gp7*ɺ'=*X";[~ud¨"I9C?QHBqO__Vfň ,ZBq bkܬ|$.LD+ .7};.FJ~Qaa ύ<KPaEhp0( h\o%3o~~s:I ~&. >]%V>֝wJ81pX|ӴA{owg-يdewҥ2ftCDNg86nᅲw@@п>" cVn>8H{\9/{.޼AumU^ {kYd)? _T-ɒ?w~bD}WQliON/r}d1ԗ_VZrs%;o%ULL}ԑg%99(gJbI0+Jg( **j^\}8ibHG=Hig%5L2{3rߤH^zRC֢u?*8P_!HUgE"ZP]ˇg8[ZUQ;ʵmhլ~ў=[9 TrեSϚ5ֹU r 4l|RX(7qUիVWֆ ^qϞGo8`oձZ7{ 7ՒS>"+V\?~\& $s` AkFkip/;t~ 6h jMFؾ]&Niَ X a%Mb5Ojy^ jwŊ%++?Tqd/]和H˖I^ .H),(5޽rPFi{5r}Uzn}v۶¡M˻4k&ǫ kVX(xZ̑3n/L_ \vY;G_z華4}.<]͝'/ں]rrK>oK> >ШGU~NQ^ɓ%_E˗˿-[3px\%?OѣX|p LEHN(*lr U.>;|HUUqH7C[̂ |[!f,Ev/n dhĘWgu 8 \|q`:5]UUf1H |~O&ٍnRfѣ;Đ|a϶mg)7pLFY̙XR=(I@SeRR+Yig4 2nlsR̴&Z^ɐ K3@\׵* IDATbk4al|H堺5]te\n&|Q4) rLґ,/2ƒɤ )6@ b|P*To H4krXތ ɨ pgl*W^Wx!`Lg(?f{v7 BBG@" *ŊzӞk,N:xP$틦jjce:`NNlsx۷O^yE}|֧vlVjjWl2E>Sh_Z?$Zڪ_t7V4bD/XW`ƌٵK?'B?x,-MT+?_{7Ps&kAfXu/@a)kׯ5F}]E"Дt'܀fU)8T)#5E˚$ 9f-='G kaH!N8ivlꟌoaQ8Q?vliìVQoǎh 7j'[hhZZ'qbH,ZD+DU#/7j]6'LnwڭquB/#t KI0<~X-Q9?, _Ub˾$vY wJr2J3!PXq0G9͈$,0Ug Ȱ3GH֣><@8D1_;Wk"'{Ro+Xk4ԈpDCzVQwch gB}CD/D;еx1PP?a"vE+Vo-*D;b ҩe_iD Kckh]{ij=ߧN5PGE_h3XC3g_-gk^DYqz͆ 3ܽ[?lhTjfx:4W1ciUX5gNif\Oxjwʶ>.$`NW] P (78.,qTϙ ::0Y>Q{ Qڲ*-N NӍS{vrhv]GUUت~1ScWl.Y%+_@R3Nlǰ6 fm4y_[BOHjg+@IF{Vl~ݏCUugJ$ FN g=]@ VQoEY56E۵Kp^ò2`6QUիkWBm/.s)Ւ+ :wk?衽N'=jm 'ǖj]L&/Ep Q/$jѪ&?}hAc9vD^ʳώ 'Bj*ŷf^ i07 I\u4k e}lqf(,֭T@+P<nG^q~wd䗄P\F0X8d!yz7EX۴%;vlڹ_HުTVj ҫd/`q=wռpmkvF5|<|?i2v s/#OeLu';zeqpޝ EcFwTxҫp""Y3ȟSN:\CM}А! x.3ukoXI|P0 ?_Ehr>xIOU$zcS>_.S{uVqbjETSw4oyi#K Gkcƴ cp6lE,3v4#E%W߀n8lfЭp+$ mAl  %2ioY 8ð1#v }ёG8DDDqA|1?eJKϤ4 2 5u@OEkn hF O}zSb=a'NbxlqҪ{wqBP_8gKaqF(e g1=-0#ьGY[ n_a0j:˞:Fj|(LCU JAN2w\bێa4zeDT_׮%jm^uXJ{>}Du /lC(YW_N9 駋|⫧~4sh狢fO;-; !.]2MUwT%S%%;e5HK`ݬxxJ:ޚWe2;Í Or;YBt4mx5rUq$nT|B<(nDDDDDԺ'ڦ.oCږhui9=Z-^UD鵟Jv_(/vtQ;W?<$暪oJ ̶rdg"NL&n;[ Ķ$V8ptW[=;Y`O2 gv"֔`RS=M{,uaz%ϳm]kv7ݶkKHF*uGspw+j]S?_?- dv$Fմ6Q}e%|V Ki­E~4$9M%cREa!Y8ﰙp N,|Dn<'2̇( dM`6Ip:L4K|tɱ 5E Xͱ]|00Q DDDTWka;A^xСȟS_/*%bϞoJnS֭0cۏ,*Qk|n3f/B=|@guhO/GfpjRS$SLi1Yi:( Q #+ΰ`fÀQ #mb|睘tU00<>ea SBL !_lYV/ S.uF/0Fܜ)>֮UH^U* D <=wPH5 6 8w:wdžO=9֖>YD ܹ窯+@;ʂI0B=(+t i!K3v( T{( Fyuŕ200z=;Y"NjW뼨*jB4Ҝ&dMHwഫ_7+޸$jq\D*<$ %DDDDDD-@""(-ظQ$Lv>q?ԷX*dcj$IpKe S`76Y"""""Vx0""#L *JԶ{ qշw&ڥ=ֿ\VP% k+=|囒Vgu7/3R 㡇^~>v Ǟ=%j}Ilʀ~0|Cg;[7F^q?/*q;eCx2,; I@˄]edbn a& ^*E@(0 sVxp른CxrLvP^-Fƞ^IqFtj^ZV뺖uXNƶ1n4lXlg}ؾ]^L}[zo-aôrs#nK)wc|кV+0jxIS:p3vc;;6JVx5YLTWs⢸dYTV`EolK.=?,aR/~Ks>$OHFy@N_ !Yr #m_=WDKڃ2X9gVb3ǬJ^k֙Z"""""j?$"" EE]sh&;xeT*C1 7O-R<x@zwhDȫ7^ˀ?) -^7ɓk<w%N*]x!pg\}^e^,~K6)+}fsR_{*+33+0k[p,u(9'`@73)v G:#Zyaoq V#^HsG|9y {ԽpUnBXV`ŶޞW'>}w1s6W~`޽oK.48p@;нvEZj?n_~~_8)Q{J^X(*Oa ^˴bѯ4ްڷ{)Q}F`,Y[y`E&:.] o7ިޖ-uVz:бc}koMEQ*}6zG]ȑ'@1Wϫq5/{; 'CkCzYѿkdU-RLG \A8\N`ˮt9!.8+X3Es$F%Jy;D@N╶DDDDDD-@""LK9*[AMDV8~Yw_ /h{7fM{\ǜ (7"xޗL&䓓7TVKV~U+xFرo'{~K`߾y?[>.ZzNi}Oqq g ,^ L}٦/w 7v\+Iu75ve_]i&LDv7*VK|Ƌ=E.;N6$׆^9q\t>Ty/Z>茾Q Q\h޼9$wC-%%bكEElrQ5`V뮻0ժV^.Z2Q`JbXZ4JqQ˒K|怜,zgck_+EnEϧ={DR3svՃJĿqu-S狾\^dmjWZ- QD;QRS2Bؘ$!=sO6 pXWD?9`Vs eYA:g0h[*N'@u'QA3@ =Q-nc A7Nsh~ ; u_xhHk} 5>ٮ;V|vQ{J D>\w8~Ppt>~ѱNJ Ϙ1U_*.&Rkm~iUg7>Esi]`v0׺uR '9^N{;o6cd_$7m QW/u%q~=[{`f֯?'. Ss₴^?O/nP0/lڤ_qh1uAN{y_.KhnU6xS`yQ&Q4gυ}1 0oqbsq2t2Fǵql.j۸;[S_eơ- QaO gg/(/|R|'3S` [x}88ӵ1 .]/+KTT|ckj;6n&7W<ڂ"ݙɪfAt"ZkyI05g892vo}N>8t͜L1*1t{ C% @?a`e$Itq7 JeIm8*'$㚝~KdqJF G'a3aD?;vHb ֕P,Tr7+%_wUYO8AT Q%wF4H=6rxCt[j[D琞=E{ڐɾ}ܹꕎ-w%}1f͊|?*O]i5'’L&+?_FjUUd_;ҫ8V5^$>[yhyxG.zk%%?(^ {J~I'5~/:|8FիF|@"R|FFs1\nxݵ :TTԫ ~Լ37oYq`j۬VG|==W\dԈ=VT޽d>]G3o &^O=U aB%Vk lCQh >_\EPH5z^`DBO3ZT:l{i\xabYXsEoɓOL1ֶ㏣m[b4T%ttӐł{~* V]Uye<7щ_z/>ViXzXPR.aN\z+bwּx(XF8CDĉn'X,"Xg$m^ln}KbmzˍzEG^oq##ӫp$O?-RvE^u眣^չ[~ZRTSY)>_ϝ]O/j;E_sűVDuXeg7I-pZI74u%ϋ o<)7^+ubp.\waze[ vU/ƌE5f7GXPۜP<%""""8'@DmFaKOڐCD%wQUпJy}SSnr杗'N&YgV@˴i"h7jWOŗBY2uhheD_}=$Z 0fu6l5k{u+O^_^i?ЪP[)鰧H9 yt0A,$IBˌ/KGyuSEb_}bcG~߯℈$I8kZ0odPUcQe ſ8QK%EkxmY'ī{V%h}W?0ƉGm|WMٮZ;qO).RiXDcSOϭFi Q㍯ J>ݢ}yh=ɴh8.fYEPN+$P 8]PʕZd(}X剋>\"\wX+4"^a3{w]'*'?T"^o*3d_%Vl `*p! pM8}#"( >_5Wu/ϩnp87f C X kLʧLE~[oV{U.B %SPƝ؆H׫x/l<]kB}'W7O?N>xm`pm*!zAxf̈~ >L$$:2L**ksUFYzH\heh0(>?8Vq͈͛Ejlq ͡Cy1g8zwm~։*_~zvᇣ:6E_j ^С}믋2ڳGX|b8`j7{~+."dYxBN[ԻXMט Ep807߈ݺu?YNt "?Z~ثp˹ntδ@)reaz 4 cRE)-f V (݅!,fsZ_@5Uj|֘g^:Bsڠv,/TI9Wg_w|q3bEsmǪB~IhhVH^%-6ۓO&F*'߿nVqJX]* ^%a\z $ 5$02I0g'3wnѮX ?큄$( PSlD֭AB0'kDpD"ӧ/6]6^f^QUPb~]{գG~ywD+B#aZEEb[oAaQ De_T D="WT7X($^{N{oUV DDUNn_Y=WwQ߫^n٢K\)B}^ L"bؼY㫎kp"+cADVqJMݞ)Z?/Sz{۱CTr;W<_~%qqcCuhaaW^1dD ԆOxC\PuUr½^;>DoU<7G~ ׮ϕ>8xPڶ-ַzE 'brQQ+G6笞ʦ}\}f/K- N?{QZ%38 VK!ՌGpLFeM!p%tJ7[G3jA R8`_q/bF*[@e:FDDDDD]hW$"""*5U"YV,TP[^}='EߖX^ .h+~fKn bQzoo+2 OG尧'vX5=|Ww5:>Y$fEn1hnWϭ`pB>ֹPg0Q֮f ∈ڛ{n6cF\PޅAt)nA!?v,Ƭa1G($ 3`1@l6 ʛ33 ׺=I"J>MQ*Q6QqkfDDP˶ej޻s𠸵v@+ yaa_+Byyͳ?Gb[ok4!Y6XFqUpJel$0:y%!lޗw9P=J퉈}kWԎ*4<-DDfт/n.`vۯ)VVt8 #?^f9M @QB2йf$!I?uNYFDDDDDDmJyn RBVm'I䰂c0bMR]dX6Baw2aD?;l VI%K") -T~V'""""]LGsDԦ}@&nJs5Ϝ@PVg`!qCEIÒJϯƒ~t`F. aE.VtpK0ʫLƎboa3px7uۄX.7 Nlt!iB""""""jQJHB22ږ!ұ.( hOΨ@Y7Ff iQ9dLp$$ RPQpTƶAz j ,7;Ӎl8+‚g TUI$2d0vl,ɧ[lCj7zNˤ̸L7ƋOz+[$X~{P +,+EPJ*+q;t0)ؑr<ׁ= ֘qpE*2Rqo3\ӊjozP^@`](cw ׬wb r2̘|onj3/Nuё?Q_2@:W8C"nBY+O P#O)x&F80-) PSdAJ7A#_#guqC2$aG~{BJRMKG_$ c90e+ kk}"""""@""""j3d2ؽPޅM)˱g'80 8$`BEAˌqG0X-{KY%=Z0 ԨJ0e )6Š{(B~-I[EHV`HTtpvҝ)8 %)) QPV\vg-_aFj7&Hb4m甑"K(Dn$'ژs4GDDDD )! kRQSI*7ہ6\$"""""mG&@UP7QyD||m!""j?NDDDDpPBTTZ}%p(\B6gKOZpЄ NTTY0jsKO٤fr(I$""""J_?j' 0PӉe-=""""""jed y 0ʋzM&V|=3iY{-= """JKKOʳTuz$S:[(@e@hDDDDDDD yMe= !5a4J-=fos(vCDm\{ٰX-="""JD ;( 5-=*3 ׺8$QleXT:BrPBf'*vہu]ϾADDDDDDDDDDI%9T} ,(7`-7""""""j3*v;%t] Gfˆ堄+RQ%ဩEBDDDDD1HDDDDd,a4x}H%EZ $+8ՉI,"ׁʽv lMs h+눈Z93h 8@jE! Xc"""""""KgG>V'"""""j$""""j* D=2(v A A A A A A A A A A AQӑT;`\3σ66GԞDDDDDDDDDDD0HDDDDDD혢qfEzE`+t={@q1PTTVss5- JKCgGDDDDDDDDDDDDDDDD0ʤL de5^ XXx-`g{cرƖx(W}JͿ̙c|[V_ t颽ڵ LTW6g""""D؝-="]WfKO0[;STOӓ L n+=%ǐ!jvovO<8P}lLcې$+rr3|81c%ka: YZn)Q`+0jW0X_W^I(>3?/'o'{_|mqYat+~5ʕɝQC.sU3x&2km薞a I?lĶcjsrF|8Lֹ8y~eo?ysQO?ݻ'DIbk;LDDDD]`ԅ48=0{vn]WeX`Fт7Yzhuf&xqrfmH` b駍-[Z Y|Ɩ1`FM#'G H6'y UT[Իw 0cyR{U@7xܮ5nf30u2{ֱK۶L&wo쳁E}]I^{MTg~NIz;O `Ĉ8#V୷ ߖt஻xMf'H5))oIy5LZځFH& """"֋p@gGxӽ IDATv*Wӧ'oӀѣe˒7o  '3D@՗2x9/9?E)}6rid >Y9s Cw@(|mL|[""jLfkKOP[ۍȦ-=""""""MdUS[3q`V!e.7j:l)QS """"""""""""jXj =~着cl[Ӧw߭>~9  -\%Ky6nS=@oN>(*f'>VܳE`F`rQN/0Lxo׮zwmE_cޛէxZ'qlj,L1Su-=""JNɖEawZ-;@DDDDDfv%cR3j`߾ө(4Dh뵃ۄ "WX j{qW_ݻ_V)W^S5 xu 5 \zOxB:vU coر^~qs \~x|e\ _5|'oDDDDDDD@DDDDDDDDD12E_gO-[ҥ;O?%5h0gvQ^P|IZ{HRb1b Q-k?zxJ"\i6'oDDDDDDDDDDDDDDDDDf-Na`f76mE{cNo%9-OPb߮u؈,;?aœNJ\H`Uj9\r >V&NTo iiW7?n<Gmx,ЯxѢ"ࡇ-)yD{|8M@ ?HĻ7 \sMV3u*pVVp:~zf0\!Co'99c}$Z(ݪOOO|[}Ǔܸ$ xZ G _ @w9M; """"""""""""""""jlFz@@kmJvXawanTQwub6l4 ; C Z )B  jpU8,4ZI+m~KXg̑4vnNlbv'O2zLgo)~`"Q]&n8Q:l5[J.M M,=Dn}t'_o1"߾ݾ-''~JJ*+'pfmo픗Ko~ɽ`tn7|n\=di*i.~r}RE䰉rΚ%xO|ٷUkRiРm~ضUۚ\Cx {A쳥=lٖon8Xib}'K8c?&ߔ)?I꫆[ܼ9~/xmgfmuRAA`Pӿ͓O>,=1キ Ʋpa@IzCΤbzm]ܶ} &wTjWﴩ[o OŃvlS 9&v̙RQQrUþVZKiZ.[R"+}E  mߞgI;g_?颋۟yToLWj{_Ź3vlr}m"mj+hOgh43q [}'ns RAm ZC= lʒwåprrβo Hc_q/ի㯟_N<1vۑGJc4v;jm65ғOJ^2k5WLT`vyM**IE!bV/N{ߝ$Ԓ6ut4`Tsu3M}Z>- Iǭv1mZO^NKƶ:ժNٳ^\&x5`2{{~Laܾ=UMH>jL" bƛ:-=Tz]i_Z-sm{?jFڹ6wW0> $b˺od$݅Z+zgvPu.hÙCf$V9Cfvo6no9SzmSmЖc=p>Zz:ƏL+ 5&UX{j;Vכon`,,BzMSoSoI4}zC6իԾٯb=m룢vO?.,Ғy{.t+mܦ݇d3TRfǰa٢8v;;Ynw1pزE?߄첋 I&,Rq6N>9455ҥO?'Nw&`W_->b$;vI&&iԯxZ3M[A[*A S[?Ync[?J" Ok+4St4j4uԫW۸Onqf!gr ?xHF2,ҧZAQxyi7Ycͪ$]ufo=Voe*ś>_%.]V:ˌߎ%x`}p+2˦MҜ9Ғ%VUk1TUT*~ov\.ihsK{Igg#w{3N3~wn3l6|QFo~-Lwu5Z}1s_?)Щp}scIێ aܾ6C]tT[+2? S5Ko% Ҷm$͝+E"m'QwmMT4*xb}W:H3q{FSҮ_"Vt%H=%H@&t`. /~n!p]Xz\%={ȨꝘtVx- h# ݺz 3߬ 3 Cv:{4m]*H@w@,+t5H@gUBt $3!@`+ :k љCtf+kVt ?]Ak!)H@wEH$+,߼*!@3B]wHCtE$#I,۴}me+TV"zM!ܛ@A@Ƞ}ζ 5L:ivA?=@ȰoN3.O@?Q*{&wj 7]w;:Ul_QaTMwv=fR@EIk]*@3S"f%s@@ڑ]%GOQ@7mre?{̞P@eY6-s -j;`;1fy?I<>A*@+&r=Om*;[ѨM:݋!nhcНfi90e!@-V B Z3H?- S”HY oק1Cj:ztySEvǗC )*od6._CЍ|=hۅO忟PmUGneϣ a*(Ie}9m^HG@4p4CjCEϩj{G=/@'s."@3e3+kv@I= @lrXS<:^4Ωlڨb}Npt@ "@D. ]@ tA ru@e9\QEB)# D()_| Y*ur[UR!iE;8(wVDե.u@YN)wpP#*';,a Alwb[[ b=A@2 hި|u*]QUry nssVxUͥ(,UX!QHNOTVS0$_AHCkTƣ ezc @7G*wPPyCj;F@2Y1ھܧ^Um(YQ9=QzT2R-B1W$w ‘A }ڴ8K5\҅ HUРz7?$?7`9@QH V6}W&O $͝VN*wp,GbQP5ھܫmK}XQ8Hy f?J.`9$o^X޼*SmvAj2% ''T@ﺘS勨ptr)/ IDATjG۾jrBUζw@EسdGշVE;V)_mZC*i mƧU]鍨hJ ʛTZYΊ(ohPkT4ޥE@250"b7FyC*Õڴ8=XaAj2*V{&@{VL';nvz]u3BlOEMxrC*ڱJyCj JϴVU+|"\ ,GTr֨pt//3TJ[bW.)?[! KGWk??nޒk;V%Wj;9@we9[QAe7/$m}:<*苕!ER YK(SfW{k[AʛRM'?>KY>Keې#4mXV钀*׹U[dJ@R/S_'?*j{.hKyX7=QwiS`KS'QHmW?RK4Ƿez Qv:QސyCi ۡ.=a]lS#*Sz/l)H7/޻T`du5gYҴ>e94n[}YΨ<9aPڸ8Kܥh gqDݷVE㫔]R'wVX#},r94[}LKA>*Se>m,PeS tN_Dj?z&~@7V0ZgnṠ$&23 k3<ݽ\&q]f7@'Dk\U8p)ۉ*wPrd ]?r"7BHm.1i{ݶkYRv:ݣ\HyLwb}ys'] bmlv b}m[K?>v֭;XvA'vҥ;ƛW޵s8ͥR|0S!3Հ!R Ti$ɒRէNkT殿^:m&=L']Czm/ tP曥3b͜)JsOc~;X.L7/v׶'s"f>JY^KVҁy ۡw6$Һa)DqΈ!gf7@'D#͙#]pA'n}ǓNhTwv Э͝+qF矗n'J7{KSOM@rg~:hT{,e,Mܒқh*v 8= *[XQyr2W{/giq͘Ӹ^yܒe!4uO6ʈ-Ջ}.qT !MݶdI%,qHAo˖ž~ !{2 tkhԧ].[ڰ-Cn]H߮ &3)yCQEL9,H`_SrȲjZ]RͯRQne2(ujx_;K߭?_{_h{eTV> rjU6,)g`*yw @7Dn(.j{FYޒ4ĩçHs 8rZ17أqh4ڨGbCH/}\> .3[ ?{z!Qiϱ^]rdK^[-员~͚ל) (jyux?~3/_-տߩը6v _S;~p 3VitI%n(6N?8KG߯ϔ]=,GGN (u$5˲Z xPq>vpH7<]oct1yǥo&򞜰܁*"]';,o^qWSgd}Ra=F{tlM2-t66կKWĞii^ -qi5Kn]I3֬~9:@:!Y& 62bYLZ=GLѨt s:qf||\eUMY^KFyt -q+:*[MBd5+ӯХߝ\gT둷*rGjIa{g5q+y#rg @wկFgAկ[,#_7y˒ӯUn{[eXo^-B7ĭKS|35!x*#eG<)vj$tSc,Kg,QF߭k-TohOb]qt;ɫ';в Ar9-W@HTzR -'KGPAk6:}>UBɔ&Ңj}&A.Ҙne~CGL hdyR|Po.qL*K$_Rي5Bd}6Ju9:p_yY UkXKQn|D2>}Tj#촤Vokj=Z; t97أ, :,K; vY:|OvS(Նm!J=^ZF ~u*[O9; @7n=hY/ThKyDsa{w[z*}6A7zo֐N}[5[RQ8"r+6[U{,)phXKMkP)4aG8ʩg᷵mG'ҨnE՗+t߫zkII*L8[5Oς,Mt$"Ql8V#ZUR\ղ°%9}Lp-9媐ξsէNҘnxPo_ S,K*#zjJݒ`޵=@_A*֝ϖŏu ǭٓ5ѯz8lzqQz\_m{^pxͿkZ!zWSU-".iQy\RM]TU^ԖNNl/PTk|y!=KѨ:.-ӕhK{eU=zZ[c<.hJz㠾[)u Àu^^T;[$ysrªp*,gT޼ V4R=n^z绚VueZeO:Oktzjk.=d;u[ I}xn{\I''*WVrU%MMƆm=VM[XVc$ےQJKׅjE-ea=*$9<y 8z~@7G _!nDu}W>GtejCj6}T> &( ! z';#FH'K҇J,դҤ! ,)%E"gI|"o;pH%%ҠA6tTU%^mnVI6Jct`aäモ>7]ۈ,-^l^NyG ޼|ct4H4<>.f܆ ҥݯƎϼ˖I=z_99C?$rd?Yn~L-U]QS~I Kשra%\J*cFRjo k5͕ Ri_Tt"wo);lg&in'|>3bmw:/Il'/O¨֭3L{c55f62Ss^&u16fIתMPѨo:@NhT1~t^ [+"&"g`H?Im]҂3SOK>{7҉'n;Oju-I7$͙q&K?|8<YJz+#)_vZw’nt?ޜ E;0C: tfݻeKt˝NCΒ?_V8{ 7HgC!.?%s>tcyϗsHO<).gJ3f^&4!;|0NW*ͽ4u /.~ QpZ_ƸAng>[;@Q.Ҧ;clo5diY_^w{ZQ|!YΈa&@ HK3gڷti悓!jaKxf̐~m+׉3v3BwKvܹ&4VRb^SOmz}\GR4mԫ?oqDcJO?-x g9TsIDk`TNzMg{!y:ΫZ&9C|rbǟt;xs^Ol~?6ۦ cs~9΂\Ծ^*)h8ȭRyUD;6 w5aG^_*JӒQ/tn~W@T F͚: RaSei`& h`U-KGUW@_HK\c^Zj.W,:%QR3??v%v-X`B!'=0;r#FǛJ\qNsMx<iM(Sɦ>_n@s;hsQ N]$;w}E|>NZױǶ 4 ؼmתUf?,rH֪LfɓܹWdY&Luy_z9jl.V =1#4 T;~Ln{-|>;ׄ~;se} bkc:b]mvٜCN;̈́:4k c Y9`y|GajZe: Ƹ䎙;h֫˄ v)&ܷs6}|f\S]gz@]cVT^aY)chھaey?^ 84l ϔkզ6x!/g,;QBg@yuMC'ֵoYq5NWT∪6$.?s׿PSg>ҁ&&ܳ&DޚLXܸ6{ YuTt:HR[70윜߫$YGM2Ym/n `>Kb:"BvL%M1zmq|Z$G ҤQ ,U7Z*sHjYZ( ۡSgeI~gyRO/ŇjqYסvOM! "7]5Hԩn^]m˗ېh}=0/'NLN0͝xarz,\LSU.'`jknM8-U&+:ȼ^KBBZCM%_2KnkTLx)SL02V0Ij[ŒT6wzU _oeW^;\Nz;wWwghsԧ<5qIѧy ?TvS%+G@K"L+%"XA9^]w]N{rHީM|n~ FtcezjU4$K'uǙ3ůl:%S(燪t]rdjZhTräQsS^,ޚ0~3!,H~"Z[70ǹTz61">1im͏XpJ7d~(0wTlc1Ax2_T*8H-qN:|LV߾+}%+i}Xqisa8Ua]r6=vBQE5a_/QyB]??OhӲt;-I/*X߮?B6~%MC<HiwҀ :\ D"̇︒-mBTͧ=l2UT=LEL&MՖXzs4]my~$O8Є :5ΉMG 3gڇȼ^¹/Zw啩)ÇoNs2*Kp*sa~{S sΜގe)$wcgv$Nƌ1 K*<Z?PO[cc#*Qj\C&wa^$;-coUx^BΝ_tܼӒ=,Gg鈩Fݺ:D|Z/Wp&*U:ҡڷ/Zd*%R+WsOJɩ6?I58{D IDATol:=ycx-zkkU þKь뮋_28 ~|ȐMw}QaLC45n\jX<3vRپ~{^_:~u N3֯_;o97 jY/cL;7/4ii;¯kUYVսTo/+X!D^\P}{S4 _V,G;$RC锞|2~J*[GD?6G0+ usDaqK}&mj{e_ctNdРփKJ+ml*_p}nϛ^w_n*5v4@6b5sw{ğO2Ӂƻ;/~L}9sގ֋Dg"iZsv HDevWjĉf[;'|rŋ J6qΛgu:M:Mg5Ȱa&bWI2^kyǚ`,٧O ygsq{y+Vl8fE&@N\jX'+j7; 4e*-k+xZ5iW6,nY1^pTM* Ն/W"^9NYENuhKy$ª\ۆ67UVljEBTͪUʕ = _w9sF-K=9?p>Ą/bwK/%RqAǽ_JZ?o+xcfj +sG5W|YYizu %NOVs޸瞱?s~ތ;4ܷr}N+L:n yå۶`~jkjw{'N5%ɏL/4 S~ٝ7/.4wK.mo2!ޮҚ-a]>7O٩Sה닕u 7Z5:nz×aSP-MDO,GT0!@@G8Ր^z)';]w۶5ti.~SObUyYGޟma7egd>_W4͚WYh9 Yyί6:^ԯ 6&dˆ &ػdNeI-y ;Ff5*^w^Y7ͅvXE{-+Llrs\[c?;8ؑﱼT3>ZZb˒YXڨ~~Pg2[S5[V,i妐6m W9RqCK->+@ZUm"lL-qmti-֬1Ǘ||~RsL=rH>H}^Ԝ1VF朰RIIٳ1-Sok~l,TfQNJW]4W`„mر&L3ό̹LռX}P77 5f?c~x~ v=6s{~Z|bqDrJ+Y\} 5$B-] !Ok/ו&RC᨞]XW4}G&Ѩ_4*-Z>ͷxY‘Ղ|:~[ @onDN_X:.?9O?5["m}TSccZ>կR8&WUk"F%˜ zjg?g$BBB(&@Ut !P,(O4hRDA -Bz\r{u~|9M^ݙ=O^xIOҽ/5h+\\Y^``Iצ0,T:ڡ9;. cy֭&0iȠ]p{ 7Ad67vad֬1Sbۋ/&_~C%'c7m~s{Uӝ}w^>}έaJs̗䌭Ь?q_G[7X}m+{v{LTN;͹mrC^KsM/+3oܪ$f2mK?2ՙn-Ms'RO} 9YTsw +,=۹ [K&0쨹ٴs & j;_+TL~=T8 o`I2nՔVp^6thN8ju׹} 78Km،f>`HT@BneEvf[\DG+I=Z^՚Ҽ2|PrwUf[ ْ,6=<݅/J gr҈˯/׽Сm`Rz-|>w'Mj_l5Jnv9bsWz qw>>p;g*YwC~{e"a%;/w >;_3sf󞎂A72$V{UR>\WӘ\>_Fu^OzwQXՍmYҊ1]־cZZ,iVljɆVn\66ߣ ^àtvY\_`HM[dA$_RZބN:)Cmbq M̘zrl2G9xKWLkѱE[2}|RM̭޹XG6I<;ȩKO7\ɫfZtɗqD~3U[|I`yj뷿M?t]_g,M:s[jp=;߶q{Musktbws pO`“m9%L9N{xgJucRcz &>hX |޶ᯆZg~8)"}}[5Ix'ȑ ݨQuuLvWw߄~F2ðaUqaB0w6/NJWJZq q1C}}WG~WIA甋eYJ$گ+2n}Q׊Q-\-Qצ#R,>X]8J+i{90S8(l:njZǐ!{6ϗ.u=Yw_[2/ yY:-v(bYy3v{mvr/8м}UYphT| 9DN2o_2K{ Rc-% *hܰ@Ix,r+l674kpY^O7V~yLU}^D ,[fZܪuL4*6yYbp{~چUQἬBi\^%%ݟh MWH_l; ˒9G"Ӫ1Y*/w^eK">vy@$&< ϣ'tAsOz5̛ ~c*ʷ ~@ )h؀Ώ,ipW˼:dRC mMhˎoj__8jws㷕W5lW[ ] nkPUeڕ**N<\VUvZ6 tH`P8]{dKXQ>)i󰆦D% 㳕 _/=L[ynͶǂPpkӦ!U[l#[nɬrCIg,&_VtzSn.%5;(tIG#y>(ٶlNȲԮpѡulm/zsן^В +M!^yqVsĜCf/ WRO(ub @.c OK`&v6n-;t.m7v-P~}ꜫ  6n))jjRcpe`Ƶ LũĀ\kř?O?#UѨ4sL۲5toژ1&^N7Tj po!ʤ m6>։e+`\x cPQARyW{5n1`khJ>ꆄ^P}(C' vA֑/V420ܫ/C|)[J|&-Նm1ՇL%ilB+4ysPBؠ`ؖ#X*T"*+OT ϣ){i5Gl=~Pڨ`s iOEۤsI~_|^lyYj^ e뜷m,=).?_:?.߫V9wg |ZL?k$wkNyۖIjZ%9Ú&"euE*7\%mI&yOvz[z6a~W4ڧBKui[RemBhf[=]%bϳt;OcT^Qc`2y (?`E pɗx;_+1cmn)?fgLٶzL6'W4zɌ:Bi;vd>ʕER?>n#w\!.yٽJ?Q %N>9&CLlXyYY1Q\yeє+/ho۶`mD?nڭ1MwO/U:9IsBK~_$XXQ(Wg¸Tݺy"=ī}?'k"zjfP5qON:0_cdYr|rX:I5{.R:hirߕ?^*+|VS:_n9.KFIG'_ lmS)u34r4qbeܹ\ccҟdgc8hVLG3 IDATd=| }젃EH۷gg[9?ߧ3тASu;݃?H(7+IR–IB -Ӫ-1ϏX Iye^|-Zu g@#ZZlԐ24Ƶp]ǖ/'=@Խ G5Aw5|%N69?t ۭ]穧fg<=m_LNn)O~~q^lj'3RK_r^NmW>+W:OFpOqd';؞93{?rNebN*.ZCiМa=5Q޳C߼ZϼҜf|-K*ʳ>w2'O-x֦>]VCɐ$൬b>WUmkvH$%bzF]t6/7>io71uՒ5 I&;wk^lqx<djԭl/yYJBk6x=S!z+SL2!K.q{k&+_8O8yيIoޚ_Krg9+>})8­Bd$_Jܓ-S?GӫhWӘMtmTeG\, 9b>/ퟯF%ŀ"KPq~cloAYDkxp/?dYҡtxfoߠE룝B}OoTcSlA>{D=8ʫiݶeG\7?YoWG^mP<~pG}!_$_#]pLK[ c,)bk°~:]x>Z$q S;d+\zt>ɗmdd˥.J,/Oz{aA΄ ~3:?:ʤ_Nt2E5\0}zZyWpsɂc=缮ow>.8?/׾&}'|:x9ljyy?]'n*K&Lw]o/SMMc\qynsɜy#BJݚBvzx-9OH-nI^}}Q}:5o۶EaْlqִcY$}"y?_'k#Ztk*X0 @{c;ϝ[xI!\Ml~1Z>'=vMlK.17O|~Lt㶿}קM3ɦb/0]&_o>M7/O>ܴs1O~zGJT:OvURY[׿> 7y\?TۿH6APͪh[&t}:nŖ8X{^^ۤʚbvBYU~~,"@}c&&C}Lgb}"S#ߞoNU1xηbw8?fP3Qr'v4YoBZnB=]5.vߛ 2K/u;o?!?_zY6Ŝ&_/vXϏTqrM߫fs{]Γн_rpL9s2kK5ɿgX̽z=䎎=Tnr mV%M'KGvB&Q}yLH?g#FH?yg$H}fsu W>ac(?փ!>]|\?`6أc&̐beQN[:hB@{x:Ty<ڱ8EIXdպu)S̳̾:?TZœ _ꠃʕmK h9y)"믗6ov~l &}aUѣv?.vo{z]xgifMu,fwr鷿5!`\s^[ZNτW2a[w[o|cdɜ7>Κ5fʤz{GKAst9gۆ 滧7q^m έ^st+lK+U@V4I.p!SK4#ۖ )?/[6+&K{+/;XÍO-ӱS۝Yo/~b׶HJR" i3m콷H-XP` fBL~YN<фjkM31s`ǝ'6%0XT9UUf2m;:%3Iy(7ϼFO?\ф:i6\yyUft*~1˖bt"{_o^knŹd ̚e&SNq~̼yTzmAP3dL3Yy8:^x=:][K.1--W:t1Te,+s_[淨Hzms&r]{yt2f^C[uz];vX~9q!}̶}a6f'rKLݤpԬcY*-4-KymBjWY9?*dY|_gw>T%uN#ۖba*+ROv=fq pؽqm En'+J3Ϙ '쿿9i3__8LEBpm!CLbC悦޲`uW狶#5mbNkHmkReC tY%>ȣ(!폥ϿҶo۝LӴM/^ow.dCDxApOh7i734= k8OM[w dZMf2SSiv`U$"}S_imO9%u[=Tw**r~u3գܴ<7fTMR2Kqz򭠖n~:NX^Tm9Wt [ùN֭YkM8ܹ梏`khפK/z$9821kY"0z{yy*}Ljn6킝.=\ݱ#d~Ჹ⋳nh;\ з,bSx^[c?;h=:ij ]qj<m֧k#z}soiX>Tw]>Pn]bc5^϶Z6PXod˜'CZWVWp͐W]\i*%yM󤦦mPTsk->k v׃*N^yTzHs*ᾯ'MU5-<{}ۖ.TɆM6d…=b Yux~ޭ$X_l5~mxkOZoݠ;ZSS ;TJ詷w.$I:Eh x,t`uR;L<zkA^Ra}hWbś ŋ'L1|й gqo9.2mSGһK/I`!]Q[+gN%H\s]^3&̅4'`;n ~]cO SO~ߛ5\X3k}qS_oKmǴf 64ub|߼單_;f/%%zYf'x}Tw?_uퟠ1C1uSJۛϫ KҘa~6u=团ﵪLRI0Sf~Tж. @?B ll*9?޴衇-mTz˖7U2jNxMѮVILwo7m\2߸On6]]T"Fpթ/7];Zv.[b1*k>L;O wF+zf0r']s0(]x96e58l =Xfۛ>lc-=%TӖJw?rKLWܷC|,uҁ*)hf_K7El=vP͑q#|b$}2U oj{,v nEsE"&L{QCo%u |t{[ km*sf+M3˜gp*_1O s*m* T'KOP,fZ ry[o;*}s昐GҕWƍ}-s`~}cjo3W^qڵ&;e9ng6}7khz=7*zy~]wuZ6츾XB:-_k`[*/4fW&t:x4{YXwPkPזfed:]̃A|WK/9S)]fNckਆN ށukmBNלS}vdYmԛ**+؊mٶ$RR\ѺZ]z|'+fRDԩf2$4!n&VgK>ym;^3g%\#=tfv=beX;׌\#ԩ`&. ~3RUe&~l͚e&O?:L n۷ĹsMLm-[f\tt9fYK@o>D_o2{RUeeZE89Θ˗Wa͖-' {nYܢ+m3ͯ}AvWKWZnM 'ttXcRim[7V?bNӦwJz,^O?6|p-Y};80zWn9_m:p]#7}1&Nt,o.o`L{RK=:z<]|&;ׇ#Y %bZ%JD2w$=Q?gǔmMz{%Y/p% Ҩ#/Ƞ$O]xҬ>P(R|Yۖ1[*/(?`zZw?hRs_,UpkR)UIֳyA>$57oCtw>ٳuꩧvz͛5_v1 WLds[Kepgi+5 u?>6ceatm}2;?IjP $lfe%q88(УiU1=1Q[ksOcp"1)5q3?19ʧo[ol@GXG[*D_. 7 .@^O~<;pO)_7v+,R,nky7c'k0-y=ǒd@"!19bkѺB | *TgVHW%LIDAT,S]m~%q ާ[:pπM ȶ;eҜY*ʷXx$R–b [[f߄-}:"egC &^ T@v?`Ew4aӥ_Zʷ㏗gPŶ{wB ; $o,2ٶh}DGo4lGF5vO<XXR6ۖjk"umW"]qE-Kz{ߓ,1?۶IÇKcJGGZGܯ%*@\z/\{zgaX>ؓ*jzgQXv@A4.w~?Pv딃 mZhG[+5Х3J,m~X)XF!@G2mzcAS+ Kh Ə𫍊gpmK f oj~ɔH"]vI\:itYfΉ^kgc:By<t]ıZ_Ke'M[/ieYcO[@.SLĥK T2_ Vh_k`TW_ Ijh5oe%Qx}TᨹbIyRl[jiRś9 #ү~u҉'JUUY_Z ={c&iP۲#ݮdY+Mt}Hf{m[jؔyŲcNjh¢ٲ%ٶk#Xt8 iު*bq)ˠpG>)Vv_v]_oH -]q "~m~T`ARiGeB.);ET$ilۖǒe URy'v9Ū]H15 ]z_ zm͠G ˒mkȫ ﵪnH?X@ dgg8\IO>)oYYbқoJ=$bϏ1WկWՀMm7z½)lkfyyGLiv%qzP![[K6D~lsW*S?mo 7PQU]>Eͱ9[k+zjVbvqvNjꫥkN;M,K$:sjimpYڶP N}["E룊l /5ȶͺk*7{R>N屬zmK n8U舿pXzyH=n Pހx@–ͦZ`H1KU U&_R$'B[qv;^ۼï9Ŋ}=%.` yՁjɭkiۂ"1dNH TXR' Vz([E5bv"QQ'ͯ-J x诶|Pq@;!mHr#@_!.n]ZxW {vrb!RҦwT>O׃̶H?!.̎[ھHkm іY% Pl<:XJ_uyTS·gvq-JtYڶHu$*Pe@si?!POKkbt7(74Zp>W:_U 'ԯ׼>^b[_Fa)"5W3u@ $ILhP,ON,U/RE+Z ˛gKsXGz/ >׼ï/TDnR"h#`gg',*6KXjW6_9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9@,zZO2QVKV/#Cl;G:vVi# x;5\B,tbY<5PP}= vZeCH@@+()! !8: 0T4`X_z|˴n~\Cp4aڙ0̾wO+`_4P{xJ _(2VpE_ gxz sAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAܘeYnX5GCHS_Bmt[Ū9X57@Σ09W9#^ ~9#89כ v,.RM4@`ii$;^m 9 9 9 9 9 9 9+ݺu  9@כqP>*2$pz)Fzsl=:Іe۶ݕ477k@'EEE6mZ_~%+@лoHݎ@w(C 0$C 0$C 0$C 0$C 0$C 0$C 0$C 0$C 0~`68MIENDB`kind-0.27.0/site/content/docs/user/000077500000000000000000000000001475376161000170635ustar00rootroot00000000000000kind-0.27.0/site/content/docs/user/auditing.md000066400000000000000000000101041475376161000212050ustar00rootroot00000000000000--- title: "Auditing" menu: main: parent: "user" identifier: "user-auditing" weight: 4 description: |- This guide covers how to enable Kubernetes API [auditing] on a kind cluster. [auditing]: https://kubernetes.io/docs/tasks/debug-application-cluster/audit/ --- ## Overview Kubernetes auditing provides a security-relevant, chronological set of records documenting the sequence of actions in a cluster. Auditing requires a file to define the [audit policy] and a backend configuration to store the logged events. Auditing supports two types of backends: log (file) & webhook. The following exercise uses the log backend. Steps: - Create the local audit-policy file - Mount the local audit-policy file into the kind control plane - Expose the control plane mounts to the API server - Enable the auditing API flags - Create a cluster ## Setup ### Create an `audit-policy.yaml` file The [audit policy] defines the level of granularity outputted by the Kubernetes API server. The example below logs all requests at the "Metadata" level. See the [audit policy] docs for more examples. {{< codeFromInline lang="bash" >}} cat < audit-policy.yaml apiVersion: audit.k8s.io/v1 kind: Policy rules: - level: Metadata EOF {{< /codeFromInline >}} ### Create a `kind-config.yaml` file. To enable audit logging, use kind's [configuration file] to pass additional setup instructions. Kind uses `kubeadm` to provision the cluster and the configuration file has the ability to pass `kubeadmConfigPatches` for further customization. {{< codeFromInline lang="bash" >}} cat < kind-config.yaml kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 nodes: - role: control-plane kubeadmConfigPatches: - | kind: ClusterConfiguration apiServer: # enable auditing flags on the API server extraArgs: audit-log-path: /var/log/kubernetes/kube-apiserver-audit.log audit-policy-file: /etc/kubernetes/policies/audit-policy.yaml # mount new files / directories on the control plane extraVolumes: - name: audit-policies hostPath: /etc/kubernetes/policies mountPath: /etc/kubernetes/policies readOnly: true pathType: "DirectoryOrCreate" - name: "audit-logs" hostPath: "/var/log/kubernetes" mountPath: "/var/log/kubernetes" readOnly: false pathType: DirectoryOrCreate # mount the local file on the control plane extraMounts: - hostPath: ./audit-policy.yaml containerPath: /etc/kubernetes/policies/audit-policy.yaml readOnly: true EOF {{< /codeFromInline >}} ## Launch a new cluster {{< codeFromInline lang="bash" >}} kind create cluster --config kind-config.yaml {{< /codeFromInline >}} ## View audit logs Once the cluster is running, view the log files on the control plane in `/var/log/kubernetes/kube-apiserver-audit.log`. {{< codeFromInline lang="bash" >}} docker exec kind-control-plane cat /var/log/kubernetes/kube-apiserver-audit.log {{< /codeFromInline >}} ## Troubleshooting If logs are not present, let's ensure a few things are in place. ### Is the local audit-policy file mounted in the control-plane? {{< codeFromInline lang="bash" >}} docker exec kind-control-plane ls /etc/kubernetes/policies {{< /codeFromInline >}} Expected output: ```bash audit-policy.yaml ``` ### Does the API server contain the mounts and arguments? {{< codeFromInline lang="bash" >}} docker exec kind-control-plane cat /etc/kubernetes/manifests/kube-apiserver.yaml | grep audit {{< /codeFromInline >}} Expected output: ```bash - --audit-log-path=/var/log/kubernetes/kube-apiserver-audit.log - --audit-policy-file=/etc/kubernetes/policies/audit-policy.yaml name: audit-logs name: audit-policies name: audit-logs name: audit-policies ``` If the control plane requires further debugging use `docker exec -it kind-control-plane bash` to start an interactive terminal session with the container. [audit policy]: https://kubernetes.io/docs/tasks/debug-application-cluster/audit/#audit-policy [configuration file]: /docs/user/configuration kind-0.27.0/site/content/docs/user/configuration.md000066400000000000000000000373061475376161000222650ustar00rootroot00000000000000--- title: "Configuration" menu: main: parent: "user" identifier: "user-configuration" weight: 3 toc: true description: |- This guide covers how to configure KIND cluster creation. We know this is currently a bit lacking and will expand it over time - PRs welcome! --- ## Getting Started To configure kind cluster creation, you will need to create a [YAML] config file. This file follows Kubernetes conventions for versioning etc. A minimal valid config is: ```yaml kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 ``` This config merely specifies that we are configuring a KIND cluster (`kind: Cluster`) and that the version of KIND's config we are using is `v1alpha4` (`apiVersion: kind.x-k8s.io/v1alpha4`). Any given version of kind may support different versions which will have different options and behavior. This is why we must always specify the version. This mechanism is inspired by Kubernetes resources and component config. To use this config, place the contents in a file `config.yaml` and then run `kind create cluster --config=config.yaml` from the same directory. You can also include a full file path like `kind create cluster --config=/foo/bar/config.yaml`. The structure of the `Cluster` type is defined by a Go struct, which is described [here](https://pkg.go.dev/sigs.k8s.io/kind/pkg/apis/config/v1alpha4#Cluster). ### A Note On CLI Parameters and Configuration Files Unless otherwise noted, parameters passed to the CLI take precedence over their equivalents in a config file. For example, if you invoke: {{< codeFromInline lang="bash" >}} kind create cluster --name my-cluster {{< /codeFromInline >}} The name `my-cluster` will be used regardless of the presence of that value in your config file. ## Cluster-Wide Options The following high level options are available. NOTE: not all options are documented yet! We will fix this with time, PRs welcome! ### Name Your Cluster You can give your cluster a name by specifying it in your config: {{< codeFromInline lang="yaml" >}} kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 name: app-1-cluster {{< /codeFromInline >}} ### Feature Gates Kubernetes [feature gates] can be enabled cluster-wide across all Kubernetes components with the following config: {{< codeFromInline lang="yaml" >}} kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 featureGates: # any feature gate can be enabled here with "Name": true # or disabled here with "Name": false # not all feature gates are tested, however "CSIMigration": true {{< /codeFromInline >}} ### Runtime Config Kubernetes API server runtime-config can be toggled using the `runtimeConfig` key, which maps to the `--runtime-config` [kube-apiserver flag](https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/). This may be used to e.g. disable beta / alpha APIs. {{< codeFromInline lang="yaml" >}} kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 runtimeConfig: "api/alpha": "false" {{< /codeFromInline >}} ### Networking Multiple details of the cluster's networking can be customized under the `networking` field. #### IP Family KIND has support for IPv4, IPv6 and dual-stack clusters, you can switch from the default of IPv4 by setting: ##### IPv6 clusters You can run IPv6 single-stack clusters using `kind`, if the host that runs the docker containers support IPv6. Most operating systems / distros have IPv6 enabled by default, but you can check on Linux with the following command: ```sh sudo sysctl net.ipv6.conf.all.disable_ipv6 ``` You should see: ```sh net.ipv6.conf.all.disable_ipv6 = 0 ``` If you are using Docker on Windows or Mac, you will need to use an IPv4 port forward for the API Server from the host because IPv6 port forwards don't work on these platforms, you can do this with the following config: {{< codeFromInline lang="yaml" >}} kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 networking: ipFamily: ipv6 apiServerAddress: 127.0.0.1 {{< /codeFromInline >}} On Linux all you need is: {{< codeFromInline lang="yaml" >}} kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 networking: ipFamily: ipv6 {{< /codeFromInline >}} ##### Dual Stack clusters You can run dual stack clusters using `kind` 0.11+, on kubernetes versions 1.20+. {{< codeFromInline lang="yaml" >}} kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 networking: ipFamily: dual {{< /codeFromInline >}} #### API Server The API Server listen address and port can be customized with: {{< codeFromInline lang="yaml" >}} kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 networking: # WARNING: It is _strongly_ recommended that you keep this the default # (127.0.0.1) for security reasons. However it is possible to change this. apiServerAddress: "127.0.0.1" # By default the API server listens on a random open port. # You may choose a specific port but probably don't need to in most cases. # Using a random port makes it easier to spin up multiple clusters. apiServerPort: 6443 {{< /codeFromInline >}} {{< securitygoose >}}**NOTE**: You should really think thrice before exposing your kind cluster publicly! kind does not ship with state of the art security or any update strategy (other than disposing your cluster and creating a new one)! We strongly discourage exposing kind to anything other than loopback.{{}} #### Pod Subnet You can configure the subnet used for pod IPs by setting {{< codeFromInline lang="yaml" >}} kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 networking: podSubnet: "10.244.0.0/16" {{< /codeFromInline >}} By default, kind uses ```10.244.0.0/16``` pod subnet for IPv4 and ```fd00:10:244::/56``` pod subnet for IPv6. #### Service Subnet You can configure the Kubernetes service subnet used for service IPs by setting {{< codeFromInline lang="yaml" >}} kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 networking: serviceSubnet: "10.96.0.0/12" {{< /codeFromInline >}} By default, kind uses ```10.96.0.0/16``` service subnet for IPv4 and ```fd00:10:96::/112``` service subnet for IPv6. #### Disable Default CNI KIND ships with a simple networking implementation ("kindnetd") based around standard CNI plugins (`ptp`, `host-local`, ...) and simple netlink routes. This CNI also handles IP masquerade. You may disable the default to install a different CNI. This is a power user feature with limited support, but many common CNI manifests are known to work, e.g. Calico. {{< codeFromInline lang="yaml" >}} kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 networking: # the default CNI will not be installed disableDefaultCNI: true {{< /codeFromInline >}} #### kube-proxy mode You can configure the kube-proxy mode that will be used, between iptables, nftables (Kubernetes v1.31+), and ipvs. By default iptables is used {{< codeFromInline lang="yaml" >}} kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 networking: kubeProxyMode: "nftables" {{< /codeFromInline >}} To disable kube-proxy, set the mode to `"none"`. ### Nodes The `kind: Cluster` object has a `nodes` field containing a list of `node` objects. If unset this defaults to: ```yaml nodes: # one node hosting a control plane - role: control-plane ``` You can create a multi node cluster with the following config: {{< codeFromInline lang="yaml" >}} kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 # One control plane node and three "workers". # # While these will not add more real compute capacity and # have limited isolation, this can be useful for testing # rolling updates etc. # # The API-server and other control plane components will be # on the control-plane node. # # You probably don't need this unless you are testing Kubernetes itself. nodes: - role: control-plane - role: worker - role: worker - role: worker {{< /codeFromInline >}} Multiple `control-plane` nodes may be specified in order to test a "high availability" control plane. ## Per-Node Options The following options are available for setting on each entry in `nodes`. NOTE: not all options are documented yet! We will fix this with time, PRs welcome! ### Kubernetes Version You can set a specific Kubernetes version by setting the `node`'s container image. You can find available image tags on the [releases page](https://github.com/kubernetes-sigs/kind/releases). Please include the `@sha256:` [image digest](https://docs.docker.com/engine/reference/commandline/pull/#pull-an-image-by-digest-immutable-identifier) from the image in the release notes, as seen in this example: {{< codeFromInline lang="yaml" >}} kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 nodes: - role: control-plane image: kindest/node:v1.16.4@sha256:b91a2c2317a000f3a783489dfb755064177dbc3a0b2f4147d50f04825d016f55 - role: worker image: kindest/node:v1.16.4@sha256:b91a2c2317a000f3a783489dfb755064177dbc3a0b2f4147d50f04825d016f55 {{< /codeFromInline >}} [Reference](https://kind.sigs.k8s.io/docs/user/quick-start/#creating-a-cluster) **Note**: Kubernetes versions are expressed as x.y.z, where x is the major version, y is the minor version, and z is the patch version, following [Semantic Versioning](https://semver.org/) terminology. For more information, see [Kubernetes Release Versioning.](https://github.com/kubernetes/sig-release/blob/master/release-engineering/versioning.md#kubernetes-release-versioning) ### Extra Mounts Extra mounts can be used to pass through storage on the host to a kind node for persisting data, mounting through code etc. {{< codeFromFile file="static/examples/config-with-mounts.yaml" lang="yaml" >}} **NOTE**: If you are using Docker for Mac or Windows check that the hostPath is included in the Preferences -> Resources -> File Sharing. For more information see the [Docker file sharing guide.](https://docs.docker.com/docker-for-mac/#file-sharing) ### Extra Port Mappings Extra port mappings can be used to port forward to the kind nodes. This is a cross-platform option to get traffic into your kind cluster. If you are running Docker without the Docker Desktop Application on Linux, you can simply send traffic to the node IPs from the host without extra port mappings. With the installation of the Docker Desktop Application, whether it is on macOs, Windows or Linux, you'll want to use these. You may also want to see the [Ingress Guide]. > **NOTE**: If you're running Kind on a remote host and need to send traffic to Kind node > IPs from a different host than where kind is running, you need to configure port-mapping. {{< codeFromFile file="static/examples/config-with-port-mapping.yaml" lang="yaml" >}} An example http pod mapping host ports to a container port. {{< codeFromInline lang="yaml">}} kind: Pod apiVersion: v1 metadata: name: foo spec: containers: - name: foo image: hashicorp/http-echo:0.2.3 args: - "-text=foo" ports: - containerPort: 5678 hostPort: 80 {{< /codeFromInline >}} #### NodePort with Port Mappings To use port mappings with `NodePort`, the kind node `containerPort` and the service `nodePort` needs to be equal. {{< codeFromInline lang="yaml" >}} kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 nodes: - role: control-plane extraPortMappings: - containerPort: 30950 hostPort: 80 {{< /codeFromInline >}} And then set `nodePort` to be 30950. {{< codeFromInline lang="yaml">}} kind: Pod apiVersion: v1 metadata: name: foo labels: app: foo spec: containers: - name: foo image: hashicorp/http-echo:0.2.3 args: - "-text=foo" ports: - containerPort: 5678 --- apiVersion: v1 kind: Service metadata: name: foo spec: type: NodePort ports: - name: http nodePort: 30950 port: 5678 selector: app: foo {{< /codeFromInline >}} [Ingress Guide]: /docs/user/ingress ### Extra Labels Extra labels might be useful for working with [nodeSelectors](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/). An example label for specifying a `tier` label: {{< codeFromInline lang="yaml">}} kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 nodes: - role: control-plane - role: worker extraPortMappings: - containerPort: 30950 hostPort: 80 labels: tier: frontend - role: worker labels: tier: backend {{< /codeFromInline >}} ### Kubeadm Config Patches KIND uses [`kubeadm`](/docs/design/principles/#leverage-existing-tooling) to configure cluster nodes. Formally KIND runs `kubeadm init` on the first control-plane node, we can customize the flags by using the kubeadm [InitConfiguration](https://kubernetes.io/docs/reference/setup-tools/kubeadm/kubeadm-init/#config-file) ([spec](https://godoc.org/k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta3#InitConfiguration)) {{< codeFromInline lang="yaml" >}} kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 nodes: - role: control-plane kubeadmConfigPatches: - | kind: InitConfiguration nodeRegistration: kubeletExtraArgs: node-labels: "my-label=true" {{< /codeFromInline >}} If you want to do more customization, there are four configuration types available during `kubeadm init`: `InitConfiguration`, `ClusterConfiguration`, `KubeProxyConfiguration`, `KubeletConfiguration`. For example, we could override the apiserver flags by using the kubeadm [ClusterConfiguration](https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/control-plane-flags/) ([spec](https://pkg.go.dev/k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta3#ClusterConfiguration)): {{< codeFromInline lang="yaml" >}} kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 nodes: - role: control-plane kubeadmConfigPatches: - | kind: ClusterConfiguration apiServer: extraArgs: enable-admission-plugins: NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook {{< /codeFromInline >}} On every additional node configured in the KIND cluster, worker or control-plane (in HA mode), KIND runs `kubeadm join` which can be configured using the [JoinConfiguration](https://kubernetes.io/docs/reference/setup-tools/kubeadm/kubeadm-join/#config-file) ([spec](https://godoc.org/k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta3#JoinConfiguration)) {{< codeFromInline lang="yaml" >}} kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 nodes: - role: control-plane - role: worker - role: worker kubeadmConfigPatches: - | kind: JoinConfiguration nodeRegistration: kubeletExtraArgs: node-labels: "my-label2=true" - role: control-plane kubeadmConfigPatches: - | kind: JoinConfiguration nodeRegistration: kubeletExtraArgs: node-labels: "my-label3=true" {{< /codeFromInline >}} If you need more control over patching, strategic merge and JSON6092 patches can be used as well. These are specified using files in a directory, for example `./patches/kube-controller-manager.yaml` could be the following. {{< codeFromInline lang="yaml" >}} apiVersion: v1 kind: Pod metadata: name: kube-controller-manager namespace: kube-system spec: containers: - name: kube-controller-manager env: - name: KUBE_CACHE_MUTATION_DETECTOR value: "true" {{< /codeFromInline >}} Then in your kind YAML configuration use the following. {{< codeFromInline lang="yaml" >}} nodes: - role: control-plane extraMounts: - hostPath: ./patches containerPath: /patches kubeadmConfigPatches: - | kind: InitConfiguration patches: directory: /patches {{< /codeFromInline >}} Note the `extraMounts` stanza. The node is a container created by `kind`. `kubeadm` is run inside this node container, and the local directory that contains the patches has to be accessible to `kubeadm`. `extraMounts` plumbs a local directory through to this node container. This example was for changing the manager in the control plane. To use a patch for a worker node, use a `JoinConfiguration` patch and an `extraMounts` stanza for the `worker` role. [YAML]: https://yaml.org/ [feature gates]: https://kubernetes.io/docs/reference/command-line-tools-reference/feature-gates/ kind-0.27.0/site/content/docs/user/images/000077500000000000000000000000001475376161000203305ustar00rootroot00000000000000kind-0.27.0/site/content/docs/user/images/docker-pref-1-win.png000066400000000000000000000135741475376161000242020ustar00rootroot00000000000000PNG  IHDR?sRGBgAMA a pHYsodIDATx^흻-E gK=Ę'poGp@a!1HXX` 9 G:ZՕ:OQqɪWZZ7/_?Ag[o¯o_~p @ .P? @ .pwsswrw.7QίzJ>ILRww~tEV}sǙ~z2k';OoqqȾ, ~yA$,T2kd8(KW.S\"1Sn|Ja]{\$?of0YyF>w*Ke*tRO6RuqoNRZH lG2AFDn2 YE]g<,[;NAq]G_! Nի8Il~Z^~:_oSZmXv 4"bT8fe_,t}:JZh\0܂§[Dv4[cKj J-KtW15)oMPoDPʦjNakL%h?N<^q?fUqDvXKJ33eUe放4u!i|0mN7p_ 8ځ_;Pz~qA\~qA\~e{{)~a>Ff9Ç'Y}XyڂI;Gǧʼn_ѹ=OW|Vst{?:wg3[&7'Qdy̑ޣs\(@X蠬Rd(jvz\ߊwqb3[vj*Z3>gogya>.㚗x=j;ХX:FP9t7]'m\'=<ɫύg-hk㝮p2pU8qS;G,ڄ*Oe6%۹ȣ㽳?ȭ~s96%'^īI%,/gK3)̜7 0s\:Cٖe8ۚ"Qڲh ΤM^6@g?A\~qA\~e# YoOllol"lP?ԏ-cAnɻK7:}7/cv6I"?~z4oذ]f~5v=6I7?G^2~w1>Zqis[miˊ,rtWO);&#ҧV>|q"i4a涉M,V 5%wNc臭Q-6[ MZg63TGΡeW~6&(Oy\G%g'd~`mֵ5w͏8}X3czWlzM)ll{S>`;=k?6l7[ w8[<<qA\~q~\ 7&Ҡ8[}`3/ʷ_=۫s ߼M'vx&l%'}<7D{YKEˮYFq]Lfx{֛ ^ P!QeRÝX,@a'쬫>]f]I)su`gF"(|۾$fX*Sw+@e+_!3ʕ[GYh,AFsA*꜖胋-Ë3Qح?&l~Q_͡X$'BS45.͹ ʸVɪ+ٶ<3OR2y}G:&ġ&KU6s!tܧޒ9?GJ< 6` VӁP@ qA\~qA\~q^vC#awmxA\}oXLQ.uxiե?U_{=^yZau!o7?Y}]E$O{_*/lseOPG8@㲧=)$Ab- TQj "wjfID/_Dh egscs4okpaKߖ.,d=$9Ff,@S(ͅ<zuiA:,`]g )p#88iCC'4πW}~^!";㇆A ~UU6BNwU8qS;m\Y2EJ-GYx`%Yof8Hcm3G6;`wAxv >]\%֑L?Zᴇ`VfK!]L^E1Wdv])юgfo[ha)8մ),F1EJ- VOx'_=ss6g[ThFV ( 0~tXyP^qA\~qA\~qA\~y#7FmaG^~`` ~=P?؎Io44J þ8%g4Sؖ^sK/Pg1hFÚ늘Ot뷗HvuڋMf+6WL2U FXuCR @h?0lj;B3fӎ왓=^ea%Ol~@ .P? @ .P? ⲑ`g,qqA\~qA\~$gŋσ׿wyσꫯW^y%I fKO[ݒTwq\|k~-vK*r?o>`d ٵqᇯ>|7?vKE}o /~CJ1zxç~ A R?]ЇkW!@By~pRK!o>9@ .P? @ .P? @ .۫~I:qߓο]!ɬW /F(zC}W}{Di ci*0dg! 9pSig=V*c &';$,X~bgNR'*Uj)Ws(1J3!T=$ey "+ v*B-U& 'd2/J3˩S,uX1#M*}bf¸`׈왼w6,Nx_SK2ts/RL{lX&I#aa5j:$wXL`:eWxi$@ .P?n@ .P? @ .Kv~qA\~qA\W}OWeD|i_'zKaTI4 S}WsD'=P\Am?B TcLQ^gC\ w{An8ךf'ӏBT_4oJzQȀӴkaMXE.KM~hf`*Е2gBv^ MH^A\~qA\~qA\~q~Cgs tH3qݛo\7 YG_nSyR)eW;x,(\,P%sP0oqc\֝N{ Pɐ͌08}ׄH<0•9\=. ~+x$[ ,WIcx'þcV!IR"Qo Xv?mvf?vq5%mtkY*yuINo8uPx\hїgHS*Np6WBNjvjiDĨqʾ EK4PRB՟_#ւt䜢 g[U9UaLwrG[9/i 3A/kZoȃk-^hqvflSJzp19EӘE޽@B_0@PSA\~q{D"|xp )zAIENDB`kind-0.27.0/site/content/docs/user/images/docker-pref-1.png000066400000000000000000001040231475376161000233750ustar00rootroot00000000000000PNG  IHDR'B iCCPiccHWXS[zHZR! $]TpEQȪkDlˢCe]ņʛ$~{{99wfc8bq6@(O$=T H@xqRLL2 U4y|)$4pWXͦ!&B@[ Bl.J.iJeA Ñd&f@?j v"[ 8ӆ}r8XBTRq6gY-9ٲfQXyeM c*DiQkA|]SS,4aWʂ5 P*Ħ쨈AO0 1=/c+Ǣ<ɴA 4(ns$XrbYV>{gs >I/LX ҬAVԐD+ 9%JT9(BɗN) AX8/ vоZ3hCzSۤqCc{dSq^L QrmA`@24 m==K 8@2jF$)zD tx\˰V|ň,L1J4-<Oѹk6ltL!1H %mp}#6'͞NxLA ܙ*, "A<]Ы {C7=>F}all8oEv d? l\+}-҆1wwl?v;ZFĎaM%ύ'1-V' 3S^5CC>ǟ'_,ia wk>-trp|Wn-o=a\[ wouCJϕre|?JFpﲆ9W@ $)8O%`: "PV5`NgEp\/A/x!!4!ƈb8!D H2d "DF"%Hي #yE@\}e K1WB->Mff2]5k,xZict3:Υ/WOӻVlL=mڽ:Z:cuufT`` K(ecd|a8u#x;RO[[{CS/H/Ko^}\Vt{FjY>[|:|M|9Ux~g 8ɚ:i%mzl\2+x(!4w&ɳg>r /^lr.lsmkvǕqG^=q-ڙoDhpI:nn?}'/~êõȣG=|Dsעό{?98`LQnfiG?O>k ͖-#Lu,'ˡi>a 4McŊ,\pV)B<gϞ==z@ ڵkYz5>)g?劢pq}YEUU!qWre 1E h<{/SWQ$o6o6###(I `\qXEQf(uPEQLy'S LL&g=ߵԔ!!ôfIA*=nYNC*}'[裥+V`z1jz2 &… C 8}4ǎi'\UUyrBjkkԧ>E8բ !矧cǎ0d(¦Mf$!###<3|ʹw^ǹꪫXr%oǦgE46o̲e˨EQ <,Z[oi)gD7LGgw7ѠL᧥cgt-_FЯ11:Z: #@QI-UAJa|}^D7`E'6g$ |*&'Sԛ T s ֬^=# illdڵ$IN8Amm-+V>R&!s1xo@00 b>(555u]_X_W^yBE]i8p'|n۷pi0 RRJ~Ɖ'ٷotwwk|r6oC=D,^!o&>,7p^z)}}}߿bH4ロ1~i~LIEwx?юro7|qj~$|&7Wz5HiK8FQ\$1M/-[(<?~."pDU`iB$(0I-v- aACD'Oq7_lcC?gll\.opIo7| .rO xwG?f UUg߾}},[}q 7PSS mii_*===ܹW^ybH]]>L&C"n~ݻ7Z17QBcǮ֮&[ȓI&jzcüeI8pe# à|#AwfժUhv~D>nG"+i YPHz$7mXܢ>C&SBc^?=h_I^<{M74uߤtyp8L2$ϣ(4b. E @Q0RJ~iz)yss3_׹ꪫro뮻O~B@Ap={pexby3<<< Y,lj'B}]{NX,6bJ)Yt)Byo_φ a ?OҗĦM0Ms$Hw)&g'/<Ɏ#:bʓtD#y>qJ'hielTmݺ~,^ 6.T) ق[ S݀] b3Y[Χt ur7lOb Ӳ[WM.N}vW@D4LDuilldppz!ڸiiJ4leUlHi +V /={~r\tEu];vh4:riJom-?dͲ.r]>Uy_P45\>O%%Vz~@ctl=/<=%KTtDy f9<ƳO?uk˴#S%UU|aCKiR(ʞA.28>(.y<_?ŜIٻw/ ,`ǎtMDQjkkf455:Q0AX$ɸs~D".KYp@xbLСClܸh4J__?Ϲ{Xm˂3*G3\`|"΂aw>L[KU̥xɧXa MZPc(}Z1im p~6Hロ.kcqBYHd"0T:IQ;FCYNkᢆ;Hl_+F2 ǎ 611Ś6j~Z#$ xb;V.J~uհht]خ.XG?䢋.bڵw}vmLLLpN+oڴ;RJFFF/~ekjj꫉b>|b13\r%\a"W\qD"Y!'.OӟG}b>yaP=FOk+/%;OQ ԳjI)Q7~zjjjʄEMn9JFJtݒ .[NB4Mv|MjjjXreY;OŤc7n,H$RʚnY8>jQSΌN ?իikkСC[tJnĝiOjT?XxF;:O,1IaqFZZ[[ٸq##(.|S;TN `ƍr»ԧ>5t4=3%)%V#G000_˖-=SKKB!nyսqF6l0; ;LKK˜ڬlW&sgD01՛YTf'UߨےUE) $ RJH3 ;I^լC϶-8;@z^4>OZxԛvΓi %ɱD3fMZk-Sz.9BZyyqSM_θ+!fs'lqLDZ*M[HE_)1i H^-ar~>[Is[ ș S$EQ'R>~@(",EAm&,_*aelAVHSbBE(Y#3~f:4Osm9BUEQTEEQ!B\ApH(q&o7,q'K*PILS 6NCͷ%Qۇ?U *o[E@^u3)uI0pS3:ͦ"agg *@PhWQ } Ӟ>kd "PaQa}wuYT%C",@xnoJb i^R* p,{C.5dX^KRbcG|4M%cAw7Ni.&RRXZԣT) hM S,irR8TSY"J]md)FFASo߫Y=tU*fU񨍫G5:{ [QЎvyǢ)V<@8V<*L'1S*(i ?]ӟ?UvoșY }j v]DȤS;z`V&LjEh4B.adtbQwA CgddP8J2<274P_G2#Q(hlj&jQ4B,^>"Meddl.O8 0!+iniQ`t2O{k TX 455ǔ&L+ՏVP&49'366n h8DPW_G>%LS_WC,C7 r<њIcۭ_M(aDkjinj4tWP0Ğ<m^;j(~TTMw U(~%~oAAț2𦴑WQPSSKSc=TDlSA0tN8A&IP~&IC;uXU8@߇'O48q8lFF B8?X6gqc#FI$b$i(a4dP(d!bM:#sd*Cl|! dar,## L.sD*@?1B0cb8 Xp((}(fZph )eᱡYR+BZ<E*HEĦr3Ύ(C)O!:xHM]]]d|||Ag%DBAQTT|A4+V`s3Y"565HR4469۴Cx@8' j#!L]P,FM$B6cldEX|95OigPN Hg2J[G'-M }*F0 ͖/=0(4R[-Sz>]FȦYI>N"D|!T!0@|ttv Hd2Rm6 =nB( Xr 2A$oezZ.evǣJ0 I&1I$A%DXUHTE6 JHCI&a@(Fv@ ! L&_WUUUimk'ZSCl,)Gԝf(:uDQD#$FG}DNgl̎DJ\T+'O[U}$INI[[Gv(a:rF9}A(%EIqx|YGDEf, P\nYK$X;6Vq'0M[JH`**jI\m ?]d̓CH5Jg w$4&(3$1KфzҩF SNC0G7M">8,2|dgtlӰxpZ1댄?'61P}+76J'  D>N# S"xp$X_?C 5uRgdhP8U&FHe 46ԓN')ŲUH2::Ę%C t c틢248a9t 9lQ@lfQglcVR,!Z(_UQ, tvkG5ÑVuD tw5%F0uhůi58ZK9Yg KHT}~zzFATE1Ljkk-;F$ h FRLp$JC}=4C<ՏYY+UҶrZ,I kTwsYj5g;ҋ;4)c,PIцSD9GY`b$`lXnDN\%P*B)KusF!GeYסepTUjE@z$QAKOl~.r 2 ˟tg\XR2X*7\yStu[u饌 I:u}ps'7Ge47$<_ ,%vd%rV.[6[euM OU9՚&d2?Y?tL(ȇs-<̹ !e9 +m(!=4FVs,cse&:WA>lOb}M~Kp5 e'dir=;lq9!%f9.ѱrFQ"pM/|pΟ*!r%i=shf$ILnp3Z% &Kyύjqo|{ᠰO9緗1, hyI)|t ćcrߓ~-SQ%Bا!EAiFsJz W |f*׋?Î;Ad =w{G)(CqBJpb,/%P:z&͕ ):_™NHRp?i<*}2*tRC v{z[68hN% v[8A{d|,YJQgx۸<њ:>à &F8ۇn(JGWso&9[K R;ruB(%@ϧycoƚ[N/A(+hL9Oӹ"=&BHRQzxEnR[&מTiz^'P7^'XѾW޾)B|_<L_wŭ ޢ8My}62vs5)~z<3ڵ?G•zPgx}kSYm)~O(L)NT[TCfsLs_U4 cr|l+i\f˭Uzbz@y< A.XϮnB[wm/56A}K'7x=Amw&>Ž&x5:v -;xQC |Pcu]gᲕ|@1G~˪+[=KG6dyB-W%hG.>u^ũCm.kcqg|&b Dn_ pG?\Dll3[W{y혊W^ڕKN\&pazK;xWcyW#xa+w/믃l7E>tKV嚫.ǯJViޠe× J23Z\T0;Q\UITqeI ' -C{'8=֭[ˁxٽ5VV]'?9c8;zXE;>::e6PS@E| _} р>{AaӦ x+yA[{jGch,}>&&o~K4AH˗*7  -\wՕ у^]3/1˳z~Gض)z$s 39ĜT|~jQR"CC'tcq\?)uvnBllq.zn.`aޡ0$xٕxu7Cմ2vHYƦF֭]K8鹪jh>N ɯ|V/%hfx=SӶ;>14 6_:g<͏~#n3z^zW$rd]BllqꚚOjQnT$k(;vX=B/F&ʱÇYnL('?]{+wz>;QڗVpɺDE̚nZONWs '9xp?\i~x.a*9^T*M:%g.`p1{ B:Z69^Ar|w6]`U%ƃeQiMúTǴ.tgWx"a?8J.-^̲/ra[;[^GEx;v+/#/E?ylbmo /g>J|h/<K/k 'G,o>=wV>~7^A"#`8|r%:-5rHY=;N>o|'R4Ce麋XdnjjS'~~Kyo;l^%yGI^̋Ek ;oy [xm>n(>y-)Iqd2dzp@{N:{Ei6Mt@/joZG200ȂeXt!a 4POsc-G{>~ ,\`(tw©Lj Ye˖`A Nx2AnJbsE\QnB ,!>:,][n6ڛ9t`?tne ;1LXfV,cp4+^m}}_v9 %K/˒Jƙ wúKā%ٴ֬Zξ}H nc,nX4X|֮fldv/XP?c9~E+qۭ7PHQ;;=E[-7"nj%L`US tNđoGױQ#N6angl Cf%!##8r>?ϊl #%r2vďr܈X:/R22y;MuMt:RYLY2 ~:Noj߽GH'GLSin?҉%U*4߽u ASB>u=VMyC~@ISĦ.A H<Ȥ8ʉŐ4a3nS e#R:t\Pͫ7h_cTI>lsuBJ;J2ݹ@ ՒvE;rAD|bD<2ݠ>,IL8' WL(_+U"JyTQ!sH y0{oZ-4Ǜ$) 2P9Ks.r:)Rx"pC뻃x~%oM?W`?[f#LE-ë곅tǤ@ KpleŰ@wE0# (s2˔_ןM= :>OUӹ^.tHT ]} ȖA:[Gv?!m4qVQVC9踴D`}Jg=3@PrQ;i/?4^BMDpF\FcUE֍[&ysëPgWkR"鴧?,_Yձk)6-UY"IMkM ׮ѤM* !s^hfKU!8G݇6{΢UNbb(zm~If {+ω.sR`MBMH׭P1y@quk4BIgTBpF]u00npTՂHPX_SiVkUxp׏e5 kn̮"'bKWj\DA7~uܸN%žc^9$Yҥ* ,AIѬp'dr jM>n ꂒZLtssɁ'/48:lK7XTk~[ QU4Fus ٢18Zd)/dEݒ*7,Tf/X\c|Ȏ&7]gSl7,TikߎrP`q1ӄBђKLx$O Jbl!c䳴rҝ Nr<~s3I6uFTxML4-Kh C}J/ @Zqɲ6A.!Xb] ӢoN<?AMXTP5KHW ʖ."Ͽgb*Evt4t@H'PTAW`(.Yٮ1$gr^/i$-aٌ;aGgĨ҅Qg \w==X*>+C"Gv9-0VMpH%'GLRE,*i#LF&GqI6+10<닺w$^ ŭ QpQ4mĎmYU ɩQѬ`0{jRXեPPUOݖ05ŢIda)B'PEeC Q5ٕtJJq>FIBk*Lz|T% mLJ %ܾ؟&¢&A[ࢅUZNU ZӢ )GMF-Z+d1ԎMCu~T|jT` )X"hjpf˛%IkfU>"M~ַZh OsMP0e$GNyrN<'MDbaM/w?<-Q*qN'`* #xVԦi`TT y䋒c#P0iEƓ &>sxg2#99'G$>JwиGL drprL٤R>В'$)XwT zUTi{䨤Q!$s\gOd"'nR {4DIRLAG$y/h' `"-iUH589BSYԤðKUAΨ>= 2,mHj)h[ l?4S9r#H_6v`9 0 HXD/tf>Ĕ]IܔQ:yMG+JSΙ SuE:JIy uV/<9Ö:a5VԇVE'inj@4>͇PTEr#"HYWu%sGl{'LJV5WC'a yRtiPgIg2F8v;wTt&keyҫܰH$(ul%r{wSl`D1e ]㟥y-kqj\,嘯V_G_).lj%Ea{owsyi _t9NzǟxŽm4&Fÿe+ (6;'SxХIGr_C/\y\y;[bY)~o'x}Bk/o'-H1_ׁ^CǑ.,l}.{hgiG=o% 4sϽ",OqU"EλeEW/ENZL}!? FW b.[X7=. SwF8|1֯\Za;x>|xϱ{rr-|O~Rd1D,HN=©a$ ]FGʳϿH(!sxFJϏ͐H1 qB=vqUJ$*Lщ85a12<\xhDL{7/&[-o§*XF,Ӻ%JB,]oox5m Wέ7^˾]xmzx")!NJgRH)xd:7 ^VcY^Mi#'? m^/ptO[&(=k2M+tJ~͌: }ٵ\iҽd/ /V/}+l^ܝUc54EH$St;dqW;HΎNj4N,d͚UH@ R5]®\Em$G(_E8ExA7/@[k5CCC+W&)0x kW,& MkKCBUkPL\;- ORj\i=F>ƒe˩ a֬\+l묻"6_Ύv.ZH47\O6g rɅttS["+i\|Vz#wvL8R dz̈=t8@оRﳷ芫*xV {L7 xKFn2 ы:S(Ȧ\qݼnr(I$)ݠT1v1JiS¾PET[fVNmeϡOGm^>3M{pG΃suߡsUicUT"%˰<2:'m|hի 2*o\( %5\5G(`$tC[p/)M^dy-(?$MS-VZL*?MT )Yi K`wJFʺݼLc2N*1lig~Bk"TtJlOE##wHR y̡ xx"C Ё~+Ou܏dQŕG\gE d-ӅI4@]|#T PVTyή?3g_f'5YTY`-ƌ)פsLUđ,Ȋ5sx)vf١" %gdQUe_8ECP__syc~[ꭔd2 }w272㌰)ͤ*Wfpir{C<ēd @Jjkiobsr_ 2`R݂<+]R2y#lx'B, j L? ,@d'{TUU\u ]vc[;˨(/ΒWʍ7ɧ !R_1:[phNv3ҔV*>wUgrOX[[宦yHel!=@Gwmo&F;zg+O4'QA6bt|@(LSc9FFG >{ t*)UT%(|&A? MLihj&Ev1( t )%x`8JC}-Br ch|g 4E]ZjR216J2p0@.ftl`8JSCegdtm,"b||ar"-}5Q2ټ5.¤q&b jjil'M36!gd EjkjG󂃹I8\b\ 4ڣҹC=tNlVu3hM^HWS=ׯ}4/&h('?#k r,\a)g# J=G?N4e|<­³?w[z>ݯKih3_?<j—@OG3_ýC`,\uf87ߧHʢ?cB(cf v6,]`Q5d/s/i &/S ɡEn]\n O87B$^]oGS$I,[>{^߿~n_2E.t,iJ[oZnh(Bbw+AzOf"0XS q7b&Gx@/!5oϷ=/敗^f ɏP|4M^}]jy̗ W^_|]rW /SwݎB}m,^w~f~ɦu+(rR\qu,n⅗_c K/X׾Ye`@f$.=Q.o͵H M [$Q~l|kdd{8:+_=KMMPo+ڸ@1{7K6=MsɲȥWi䭗!ұo|sdd_ލ;n_=#-܂L&x,N vTbU/)i 24)xS%:{m= &tm!..M=Ҿ`9AgɒN Xt1m-dzzÃDXl1ªKಫg_aM,#`/O5Ӵm4 FOs;Y$+`&>ĢR dXj=m4يYg金cђe,h`2L8NP4 31:S?J>6L)ZŠ]S’ekX h1n`0xl:El"uon~%߹{,R2:5δb U69$Yp95s+_&AVY#o-Fn),@cIIQ/R(N8C;v*YXȶ9=01z{@jz52 ^t?]PuR_Bm}=6_U1kwt?w>Sg]6iʒ/u' 1>DD|l`bFOSrWsấ8vvkŖHFKd)L.&bIT=A{'&⬤em?@ ;z,{+ ]JOCG~k:{D4XΎbcC:NQLMd)'3a'=Lf@ Zc;^y*:R[Mp߇L\ylZFituǷ?D4t.g}=L$$U,ƪ6} `*,~ wo^϶O-uDt)J ó|!nv+g$t 岠|n%ZrE][;B *<*^!iԷpG6ٿ ʫy? cڋڶD \MUQ5!G? Ŧ p7x9xɏٸg^=R6>?|x\='^qd_ h/K)p\Ead5ř 2r@gf"sqn{,6I\QRyC^;(jQa{B{(S:ra.VN⼫=pf@2ru{@e:Q[Ws,.YQeG D W_ɬänz+NVיIF YznݘXIQ<$S-Rx}_BM$JazYu,^r  g4{0uW,&j[9X=U:# 4Ma$9Դ4HPJGq Y[.Sb,9r[9.J:  fD90l3r?ӈڳaŦ@1: tgyQFEER :zV@rHG#gҽsݽQqYήL"_ˎi-͢+'Q9(;z՚J~.Hu mj)!4Mnhr`X5Y5)f֑Z1.Q:DUއJXy7=LܘșG9{5T<{3GqN]3SJNSnA* >*t%h^7rJ$a(a &{tr2NП3Ę\jh4MF4U]m{=3MfiUȽ|N-MSJRd&lӐE(%iE8D+k\xwanr˲)LÝvJfٷ#llX9";CŠgYW&ijCSSYh4֙њ<J7ht@[dڕDAR8}}}d:Hl|c&K.?QN 1$d!4`_Ae5Ψ!TO`&}:&ds9}O:*_gymNeeޣG"^ZV\?`; ]MrMxi⯾ jxfJ8s}sAuɧYM똄n< @!+`&BUiDDg.WzɣP=%*S:SPRzֳc]X*90FjXp7 [}~wMWK-xd"AQRLl&o|{lZC*'~K-g8=_7^}/?#$in.o8H/c |\v5| _`ӚEs'7nbe\{MlZ1ҩ= 7^p%kxI3$b)nMsw`|dҙ8Yu.+?u[ܥ;Swήdsy$l|!G&t M0MA!W$˓d0M6z]m,9T8.;[2b)m>NkFYb G!U=zk4P` QmNvkUu%|~rz;&66HyBBkUU@BkDXv/m?B&'ZF._(9S՚⢊D3GxI2t6nơ* PÐNX4hB|~?D>܇˨T(U`M-T{idd3iG=t VP_ǑCGIgsy'W~#ׄLY(MI!)h5|K_bêeʢ\ô4~/@O#r9:?v#\'\GUHˌu̴{w6tte:{:e`B>K CSS Rµ"zM省+EzfOBfR5T*&L?v'W-5E_ѐ!"\rU,[zSC#QU/XZ'CI.rvcBH t!* m$F'aɒeo+ д` B瞦6L8"rÍOp.ƹP ;@; 8a@gKQʝ+aB5ѠujP(E%_,24G& k3S1$9 45bRc ݅OS)%otwPW!rI⣮&B EC'[4hnj$`@SS#IKs8N ZZ0zAOKΎVN"-؀*-MiNSЌYk 34ptuNڂzv@!p6үXyl)&}yS}~님|>+LlSS hEQf P( :spɝjgà1}:׶ͳ`xhv"K211A{{]!¡ a)T Ueč⒔ўe)g&kNIrHbr[yӐUkˆ!ey(;+MOӛtU"R4Q=:=3u4MK,P,  @iHi280bYҥVN$w߽v:xS}FfAN`zΣ1(1N]^JKS[!mOOm>&|(|.nD@~~ Ca aGVsjЭ|JJYD{o_-0L{+_\鐢v,YgNsJ̯K-b R 8i*BHTLS%}HR(訪bE5)3J;~L}!Lff1Mi 8EMnAY!F ; Ǐ'RLwg(lݎ![ )$gE)FGPakUUgc&ފ "_y* 7dke^-XT,+?)@$!p`qrl/ q! `ٲ40\.D,U(>r`+keMFOsIZV,_N61ƮݻZK] j  qgVݵ~3([6ftx#G"Xh89~(CwvxA'zhGy19qH=-59x t=X[IfqgIfQ!G+o`ɒ%9c H(@4bKPU UUfU\D,N4X, elXrAʜLXefg0-;ĉìZ#IX^VM |_㩇~ɞlظO>#,^s;bGô/^ǥ384_mM:W4Z[馳Sl߹A~o%Վ|˟c7YMMɶW^wx} }[[xg׿/ or:>NKc-ވ1]M3>:[xg~|oIwC+ȤZ[[Tn#%1t]7P\֤A),V;uml.oiL&MNV,lξ#\v4xo}|{%PH :]u#9u]27U[6m M$ 4yW\|+]D{ش~%[ׯ(#3 B{gxoR}ͯd+|}|;jyٗӟaQs^͗s7WIPdoe=mT^3sy9Mنٱ1y{N G)-066AP@SU~fR j޲94RvP]T9ѬG=#wqŗ^3[_e_Kzxx&jUjo$L⣵M$cq҉񏘦5k?$޹_H @*v97Ž'pJsm1htw* \|)&vhXHsS\L:M$GNocA[+[ }1|"Y+'i^[nEyXjr* c Babъz"E84I$b(=('qY g>rPu? ` qe 0 OsqNzDksSQaR)8d0ЋD('ϑIgԩA[ PC <ݹZMinr k[H\nCGޡ&ʂekuQXj7wGdӖ}`l|>>f+[c'ε7j3F< иOsUm;|oΖkod%(<$N3\ڶN.b3?a>';o RSWhI<6UToϾfjH6;U)% |s}ֶV%)kt`…EEQ4B\.GX H&> ?%%n&i:b|>O6 ;f:lH:/ Bݴ4u1$466i TgYB-mX 4_q2>GilCL}niJ&({H0"Xa,&&P5?v;B8thm5Lj~24ECRHgs eHԡv\hwE ðz3T( چp|| U'LQXtةLUѲe2sD43cUTe#P5aLw|ǻK$>۾R.Lt !tSbJõ>?mmn_jjꨩù@4#lz-iae}Z:jRꋪ8 bH*BQljPW[g' 362N*FQ,{TZ,A:j¸a.cd==I-8X&ߔiMU+[޺ܬn8IB!ﲢnXϊ빦\d>X E!|H6= 124@Q u΅ !\uDA6$l*yh*䛲?)7 d3՗p4+իK})YYf%!4E*mmV]k\@G8Gd:M(KD.&}h)H$+سGYt|LMrROSW6*N;jd~~RRD:K ~졔hnE( ᠆Q4lou2<(iGլ#R,$,rMtK"NoaqLMMegy<;s}f<%gIn0ME1 $*uuSSij"QBE=G$B|{g}hEU3o9M_.\W/;5E{Uީ!fş4tv( d#G aU  j~,B@0qPp6;h7YhYW&q4MI6@VeN=hUHWz1a*kQ1f 9elV'=_ |HKk7|_0TTOh]3X,x lwwY -F /K"uTfܜ"M6͚Id%nNg!hjl`ʅLIf45}r).[GEb+q܇AJýij\ks,>]FQr$STUU*TMlXP9/g([գ)QiJQyj\@hig;϶mAVPlGQ(::p\>G.[`ld ]):\fZ[Œ7y`?>+h"֝nV2-/]}Lͧi/:BMG t0Tv)4bIYXNEͬN6# * |lFGnF!TO%Q.;kC/wij,UŘQV'Iuv9fy!' ? 岌&H&|~0ZzELD"%2ˁV:ùȩc?`<#= c#M<ᄑYUUAQlNXȭ( '(F?DZD3+B^xSHηd QVWkBN a E=}6g04mRtRFM]MlXŋhljaEAi9(KH"h\$-]H;P'e-wPI8F|櫸ҁi;|hlig}t謦+\aoyK;YNg+4*&(ĔNyVT9_P(H% u44FYUO2&N2OHet}eOKW9 <6[A!75_l*No6]Hww䭷a&bI.tyc:t%vHM](fHlڴARuϚ\rG:W6m ፷ަgx%+pj۵|q.*ښ9vnSW4G>+ϫus:D$G"4vuM5t/KZPT#phYnX"nH[,( $q/_F0*O7f4(b_1 z$Nj~BtL.G$RC0G/)&0%]/&`L:MP$!4 `2,i>|F>%J U3\3ulsٚ˩(Aھ`Ѐ(45DH!bI'0#ch^_BK*8 ݒegaO*ֳ\8)(P+B4bJFQQMBH-cw3Ћzjr8 uJMmNJ?HSSf٦cO g86ҟbh4ϯR($'&g, ^xԹS:^J/DQf7` Ӌ]UFd#PPyObIe8oEFѨåmϤrViJ,uhJ$P y|n;sy"![d&ڬMS\Vv 3d'7L%LbZK?tl# q%Jk(PLBpEhyzh$)LYъ(,Zj^JPA"A UJN[H2C 7l ~2tvm,B RJYaJƩk@Q}$ Z‘j PzHzOBF-Jb"pWri{wWE5ʳrЫnj*'% TԠ\qtD .Ta uETPo4BE,@VYluU}zf9gNwum}>#+ffW`yf~n7?B!JZM̖;w-cg4i]~U:ulM,FvX&>k]c ``hа tk~1k;43ǜ E|dw[=`ao:a`?eͥk>9~fyMg|Uf4| O\r9++Vv5@ĩ1:Jғb^qZwyG_6i0y;7+ˍY322b.SS|e^y3Nʌ%3ogsęy1o:&s\]lSgϋk5[lm& ?GDMPݹ*u֙/~Rc3{~2G~lV{]瘿]>aFyKg<f5ìְ8'|l:3љ п}ct5Ƽ's2Y1WegWWǚ0gG46@uyOb~zzDoZR3o;Fs~oy1<Øg'?2f}]] ̈:`lv],oRG!~?XbԿ:]cݺV }'7>5?w+5>sW }L sPٞSFhژ80Af2ߓzJFr\~cNeש`ڷz4183c=~ȸy/7ОL\PS^'=1WTqN{KsI{Ѭ\y;43@!sG7} G1OK|9̏ 9̑s0@ (#cMHlG:#5 PKbHkm|7U7?dξZs5.suƼkY.˳?a̘^ 91Rb[, Scj,n*98 QgtU 3@կ̿c7_ɼ31?|%5@'kܮ~q֒[F .;w~u }t7Gp6Ap/D SlCg7{渓gusxorl׷o"} *@XxH\yfį}[į_bt̑:,n~ùliN#刼Ϝ P=c\},w `8Xn``hЀC  VpŦ`j h ~'4BKtłb4<BKtłb4<BKtłb4<BKtłb4<BKtłb4<BKXJrucwtsu7in6s}rn݃Zok51ʸz~sbeH\rs&:s-ϱ^dqLOyy(BKTM2<7fn>xKrc~knłz-R}sh\\ep7[| \fjMgKEl3}䜓β6+~/&][&hson} B=`В+nJVs@ɲAܔ"o\hӐ,N-=+9X3SN}wuWǐ9YwoK'ŖE1@{%WhF8f''糌4-8lV8ݻ.)̌,ŘzsTӷSY( ZryF_ʞELPvۂuNRc|ⲙ&[RM -<#7ٟuׅfGc 0ݗ.u ݽ/\}1g,h Hn9Iz6"Jd7#? t3?}+~:?\X>SQBM\11S-bc 0@ I3:2j&FΡEQ` bA1@{6##f$Hh3XPGdNZ3> 0@Ktłb4<04=q3;Zr Ơ+yfrtnlQ./k)kF*H/˔itwqubP̟'?Y~~g~sxJ/ogh4kcksI a*e~%׍FͶh+267poo|+N0Gyk+^ w\} -w`of3ea2y MUZ)eex!_/5@[̿ۿ'>c6h#67c_//{ήZ,Jز1rJJ݉7@ey>`\Vl&q{;+ 6z[nɮZY@e@ PU{(}58ds=>}GW{6kh)s'{q̐]weV[me6tSZ=׫}chYkdx.r~v&7&h!@cY,mqϲ0@0 ..af+V0o38:|.y֖| N l.s dڍT4v@>B6Nmvlna  PՇv]Ʀ̏73inxem iyu]͞{iw_M}yA2K2ChSOO_bζnk} _qԠ B 4h;cw 00C~YY8#q]??[K(ڼ5fgժUfbb#ep*7fyT g=fr%$fzz.$0@5`Μ1Jl18+93fj,3W68f 7lkʅ}מaǘ^q9,\Sm6zf7f$1elh6l[sԷMe;<`M<`}NǍߴS6=$$ãe^%ns1>-isFzGK[( $#evZ'6eyWbAbvj, l1[d$͚}762}ls\,fmhv]M@aV6>bN%7p3sYy]e{f\nc6A{w,Vrk]fqi͚*uǏ1Ռݎ4  eY.BQvFGд Yɥ̕U'_N+?, >ɶ蹠Zt4+Yqi_ilb=+4oU0='PNVjc۞.31ڌɕݓ/&os’( Vf,i\0sa*>mdVՏ;ԍӎxy顜k\cΛfugJkUfjpmjv>{r//2Ȑm̻-#.q9m2Cx3챘 %s#ιRꯒaC=һ^̓)#5333@%puՠY>?c8 †fjϟ+JB-2%2k|iKXkfB$ $zsqy,^ǧs#R !_kΪmu'uʄٱN1צɽe;>  H ecdL6;\￿"[h77 v8+K%.''ٝZ6l.n4!Inw)v@B[+(Ӥv[n>`_:H[acj`flyIvЧs͡Y;rKv lqԎN_!nOO::;u3W㶪OKʸffBo=X;^dW:j^d~rg>^RHs@zHr!ݘa!@-u8/H$SʽNƁxrOx _^j{j: {zsPU8q p(LIsM$WeLdv4]}'2,yW(#cl1nھZTI&H!-e: kԞm6@m}B ຉL?(rDr͖̭2Y˿ruN\9*@s?oYgaiI3B2H1J&H/Ai 7H͔^K/m!@}y\E*qYUt1@ ?y' Ew`[oڦLof;uw.5&s=OCѧc];@snfO~.e 9s;zGDQf%7,@&fvLѫ^*^N*mi7M~}gm B o:Fc5_dnJB.7̳EF \ Pa:f74W,[LTߤ3Es3N66T_RRnM^4lE{X? @[!Qk9S̉'h;+Y{dt[la=2zG٠?姡RG)۴^{yIjCL'-{vzg~D6 B ?7%wuC;fS16Wޙi1f͒6m\g CqGa/sPswfFsg#M[twZpGbҗ&ls̷̞?JX xyb\slc̷=p/ͥa^mv0lzڥ\\61Ćj^ c2?wy9묳lя~=7#KOcFNVLơmOIMx뭷q B /:/l|cȂ)yZsѻm7۸ؕmmS6ڴLa" P 煢x7^|9`y6h̶d.Uo17AYe4omO: N2o{V;6zfݎ6]vkQ]椷lk72.3vlYqѳ< k_3KJ/G?m+WDBHhɛPzFhwK˴L̆2,Z~ NϴL͊ )3ep23kR;I(#3,o7,@/ J-FGY= WIAGe%mdlk leYf#L^2- U]&HczK_@R_R_ԮƬ)կ~5!\Cmlp̌}e@1)PvFsAyY28-mSE - v;udPdVTVCF:v.Sy_QB"))R~59DNY'<3 U-ikk)b>!ַ] B PC=d`ʈhZDLKՔ}yfHTLKd2; i;iIu)k'>]'$áߵ͵9o[s]wI92DSDI2D2AJiYɳUݤr2vzNH&O㔱ɳTc}կ~e{:ꨣ[̚AP--ҳ@v;mgr}qQH|Cw9R6D3ԧ>e˿͸hC=ir!%S][L6>RFIKhٝ̋L6;P㣗.0@5 7wy]N_F"ϖﵙx=gl ͏ ?yG>b9>Ϋ\n|K_R[M-qQfHd~; /~CNfIF:etr#$zGsC*zHTK1?Po?O;仴iYLAd7@s82 ʐeB"3t'>np𶷽ne:dPSu\Vfȭ'WUwìXepG=g{d^t\"U .0{m2BZ%po-zG;/˭ҜhzM74PTB`xN^dzvF)Ўk2AZZ2"ZK\ek2I*z蹠9*W♟\hiC&Lyg͔Ant2r29"i鞖>Q=K'> Ʃ̏]%ҹA B Inвg>s"-1eN,LפLq NSE^qm dzdzZ&wUWgt, '`w2\ڬAcЮn_~\tE6ˤ4)ۥgW\1h5k#fdtҬl#f:r-_pݔ ;~d6Ӛ)̋̆2E13?0Aٜi>ؚLD2&212?Z Lv8d~_[&#slVKckl7x] io=[,b꛶o4qei͸7ȸNp,̄QeoZ6n/x &zF;D=d[7ɼخr~5YZ&$æ>~;߱/{׻Yeo2?*#Ef Ne j~YOA`&NOǧS#4>GC#Nr0e}dO mX.}NnzONWO\#}D''?ٚ-:MikeqtL]ƣieB3N{o_6G(t17m2@O9zf;<г@ZlvE3-iiQp,DZz. v 2:2i2m仼)ˣ2@2I\k9i;#}̠뮻ZR,ɵs:_V^.$1T!\[ά5L/%YN;&pd~Lc^.C0(WC- ^"]d:\3gyA/ۦtgf: Y~|ȿ>^ԆgҾwF-6bl}:Voʴ(dz9^F vG,R&Gs] Mi3>i׷w>$cХ^:4GIi`0A6@U&E.3O -CQ: aevʌ%ߋ<K_fդL.fD=M?ݘ?v'tf"/eL9s 2%_|^r%?.FzF/:u6Dy镴tخ$3W|) Vie5hgjB(=7(5V%pҲ0N%c͵<5'cSIxKp ۟ߡ'_:{5 ,d JeN"??ʨh78mۖvd= S;1W,=+dn Pw%h@lpldRKǼI{uȗ~ٹ7(hIZ,h0@g P/MTt,/Z)˔Ϛ eR(]il e甌\,3F_Wv'lva_eydҞYepmmsK/6 ?WA`NN7Y 3>A#5'bJ*U^MZ*  CmDjVzN `njʖh)Q3@zgD91c76vMI;ѝp E( ۭ~sUW٥}4@ Im?eezc9n6YL:>dX18-8S>K߰A?440y}R{v)־)  L5Sp/bß:ӕgfu#֟wҨ){хz%/W5yo<;1|On|o9nvnp_š24`}Fq =_?yHS+âbf>OZSs9/ɠ~vwcߛ`NJ$XBnVn'=ɾTCڞgeqZbszk$mPcܻnۛ۞>z汏}}GM٫7Lf0@5P? V@TLSgH=1}~Pui36{Minq:ohNz` ;6 R_Ox>VMį/k[cVS]YcKOv'DrI_\9uw>W݇p϶plu,d&G%oz N;ig5UHY eidozq۞@2@^ɤ=11zֳ̞{iۇ6 }w!M'# "&"v}xTɉ-zO)nrz :UрRs/z+!ZH#U~M,dyuGŸclد9\?;~}Jád2cM[R].4j^Ɍ{FV.z޻oexM6젝sglb,>ʔ|K_[]+"c7n1ߴ6yed@\C"i;#<2jf#-S}3G vb-Q̐~W&K}F2A֭F:\bׇJe"Ĉ-zu TQE9W 9coRJ{(Cwh 2Am}Σ(9,2tk_ZԔ|5x;z!».s'Q:n&gcݖ[nir>eǛ-kF:\lψ&*!R`% (l bc2zbD&mHjpGx/NѾUo B7X>a ;>3&vjӆvX47ICy3i͚L6n {}5A8Fyd~RE$H]+i|ƂhB/isO|!REDʴooK^0 $}3KԴ*}{^;[Kee]lJߔyg@ oH};d)B^$ 4•€M~ fc26xmEӹ>҇g:=#9XwZ[{[^3 8ڵk_o?o{g**H2?zH-Ǔ-ϓ!k^"TUKa/~.> o~v m !cRg9lVHYGu\rn9CSkoolG#-{ӳA i7O}SCMhɖ4 eNshmp2ty*TWSsk[<̏>Z6ev  Ja~`8В|_'崲,z5A.[(n>sDZ 0erYÍ7h7Oxsc6oбa 6!R}{6â,6׽uvg8e`dFTF/Lզ2/sҵZ}gyԎ 6ʮ /U(#&#]zaPTe\|Z{>6w)##ɜh5=squũ)7EOIY#oz.~}2NWm( PQGg|~ڝsX7?ȱH7$!R(.oxmG߳[vvcI1pzmv^h kzS}栃m`nK*/SI(#m=66f򕯔2=n|rRtѠ! @u]v|ӟt׿v76M 24ȘH({ev&= OI?踖霖X"SO`  B"ԗh<򑏴ΔёyӞfwq7k/[729{,ΩTN3?h^u>#- 'x?Ze_r#B(\|Ao}n9-s"wɠȔ|57p9s6ڙMefddldrtL>&Gchiz]n٢xVZԧZzj&5h*aEn6~l̍27uQv(?]&gpdx=ɑO-c >K*vjs;^̏̐ɘ=?5Xj a D;?F)vw},X4q **dHd4zHwюozO ~* $-T=o~ӾGHvQYeFGGٹR{2{뭷lvd~;όA5@? a`r-o}]9elQGtk_]Nwi k%m燴LNhvˑQo|9ͅ^h_ꖁ2:P0@0Lϓ$rJM+_Jk􌏌ɰZ]v &HKtwmL ffXtaLvӋQ *쎞ѹG>krNnesz'̒ zl:S:Xc<!eKS?|16Ox ^`w{{kN?tsM7ah߽1@UKvqSܡv{n2K[w0@U2' aڃ5@BJ !T! @{hv!T! @{hv!T! @{H|F??rn9-@u^;bFF0EŅBG p'<* Z>yϧ})S-a PjϩfgݿIdDZ& ]et]!|lZWN٘es^#(Z# @{Xj H@5=/L&$7cI۹&ö똭1qC dfR92XaS Pɸ(Z*X:V"+Tu>bN*rhiDUhr2+D,O!}ȟU*;ݑ8Ph6@2:楹qp -9} N,PT0@, @{ gore 3KaV)r.fprau3H3:e۽6)ʯML&iA:=:0.aü gt3\}ljrL589zeu(4j0@a~ H B0@Pa. B]h 0@u=`" @{!E B0@Pa. B]h 0@u=`" @{!E  P\VZBB`j h vB`j h vB312bF  B w`r&2}VUDBOM!c.('ŭ€d f*di6   \fddLΡ`/G+?S֌ٜ~ڙ /& ``Z;9jN_k&G 0@FD2T!GKid,Ų\mR67eȂ Ȩ\6I}ӱsK I39Z<Klf7;qTdRu̎5\H[k'ͨg\2E]a*7Zy|hlC(h=0@gk\Q6/^y)3E'7&5Qrye:=]Sx(-84vh[  BioʏeS27H=hxg7ʗTgcS&*;=r5NV&k4B@;Jܤ(XrVRܘt7#k϶c Dž^zkYÒp2 Ƙ$M'y*cAB{ .`Y3Q63 rfk$נY%*=oibBêABk0 {8?0i,a{u0@-C % |aX}Wynvr B]`=!M0@54[3k1Kpeg.c*hَɥcvP7@sBkxݛ0@u4'f~A 3? 7c&hNHkވB91#  3? 7BEB!r BEB!r BEB!r n`~ÁB!\5+̏4B91# ;7n̏4 3? B9kfKGW P7@s.~+:'̜}B`j h> vB`j h vB`j h vB,KvrԌ9CmR2ktҬ0@h`a3:6+es5%nX^q-zB`Q3@Fjdٚmc\4NOcSّ3fBƦL]6G1S=-fjlLd]f&:"s;;eƜ>ڒ&KzhT_WqcjRljBC㖱p Bs `13Vgn(59?d&#Sٹr Mf@r3~v|}lhXjfB.; l(Muu(3^1e 'yjw9HdHv8 Kjv|us\7w|}"(=F0TB Pdu5@ M9!<8Xd.A| )|n5/ANMSs9As  ,A=G^`g`bf#+"bt/zv, r#ל\~ݧ`~-U26@ /N Hci*3ȿKSOA*1blEګb~)!6yyj* B4|@ڍ Pym ߢr_Efɖ,:djA17 \4?_W.O?5j9oLy+]>޻!@@4ѢJGneHd&|TqMY,I $,*Lp~}!3!@8D"B`1, %^`j %sC<KvB`j h /x GaOI/w;;;;y"Pi eS(q ʦP6).q ʦ,l?M$G ʦ,FٶKP6)MwٶKP6ee1@ j(Ҵl[%(Bʦl[%(Xe B /F`1 Ѐ zW [0@5P@k?|R7vuY&!43fc~r  \ XJ0@5P@ 2@ `̏W m"f~$P `gj* B 'f~$>XЀ_lBIK  0訣B\!dpXBmS,XBK! \ Bh`j~.p&bG B.X81#Bh/@ `)!@m"f~$>!43fd~t  B~AEׯ3;|E3; 45###eOG#/0@FeI;LɵqwOLP9 ޲(螵f噡\ )-6ɱWw],zG'צ-/CH2@a>O{#}~hL SƧ0+1Ou Oz0HE2h(5~Q`xd\Jf#1M ު>C+] @oY d MiI"SV7,ykb:r _`/1K޺d0@2FA3@U}cPw#<Ҋm k@B3<#䘗ly\hp;d~(. B  O!=3BΒy )3A1@!4LvB`j h vB`j h hPsi%33aFƦLcpǜhb&;cL%e*4kdf̄et B 4XSx{&bJ?'̄7ԀU <76M ؘM@{gX0@5PQн @&dJf")i.'!^Hib꼃5JiKslb"0I>/0`jY?J؀>݈gglIQ~QXVi[tڮ6Hy-S=l0@5@1_ff NքjJgk蘯>g[ZsSgg`p!@C|njPdADXwMC?؏*Rg`Rc#$]ƖŠ^J3b B 4xAxEЯ^~fANc칠:ލ)2*٘gUzxIu;u8_6@_Rr  B 4Ȥx˼52iX 䛐RYc'[VcPeWտb ,- hP(@/4H35@i+m+d9R{ VBOtu?9cnPY]%s.:Ȩ2%aa9yƢ-HI}M0`jTa$!gcJL`"\0t5u$[oWᘯJ۱ B 4M`jamP # h# h@!@0@5 B P@;!@^ \P - oa0[`Ơ$#0- o0w [!@0na0 [6 vB`j X\~}f|5>rN\t,  7kf;'B`y\0|Mq3;Ѐ71#1ovrԌNftd|N ˛`62?^ZQ36r `y3? =2@k<̏B@ 7[&4E,itr]n:e|ӔgZ?u޶9>\[2juz;%}.́s~> `y3?  6@SAp M,QaR⚢Ԙorⴗ=>^w<gK$1@V"  k.|P*"67*S2Y)LMF%Wc~ QtQG!Bh 4H,8.9+NF(b㩹IV(u32\ԤuD)XV0/bA;bB!{  6@2( {W('۟xCԥ*ؤ}-oXЎz'>zb9͈F&jJn\Ri5;fUk` 71#3T,r̄5ɳ!JueJyyuGgurrc`y3? =4@Rf&ccRbF(ShZeorUuK8g&.bra~`y3? 6@ `y3?  7kfKG`!@ufǓ蘟3swfg`Xjnv@vB`j h vB`j h vB`j h vB`j h ZnN 3}fLsw=M%#33<9L= B 4cUn0@5 S3@zhd5mP uDbc B}vʌ9Yr3-˃f*)YYg uRZ_r9>ccmu$ǙΣ5(#uf,>jLЗ%0@5 Pk:q/39qLE؏ar̄>,wr_rs[TSi;aNmNƢy e&3\UE m!@@Awe&0@1##5 ^SE>2]aLHcF|Txگp2 hv99_>3&c:*qdUrZ54@U$UTEt5@D=u66 h5@A _UwΥHb.kdL"1 60@5 Pej Ы&x&¹MPX| PޱTbXqV_7vEt5@71q B!@@4dD)wW Jq A>^4l`ag۶7YncImT]Ŗq, B 4 (kfNE@ydJ ^ GG9 /Js{8yXB$Jc g} B B B A,{00@5,=3A.>  s9 ځ5@;S47ӑ-w@;wI96=&ȶ7>=P/{Ȩ\[ {@;Jiy[&q7=p˸Ir3"Q_? !B=5@rH4C' *|p4C' *k>hЇJOc TtЪhЇJOc e]@bժUJ'!R @o Z5 B 7`K"5@F>P~ RhAB(U? `_XtG>P~ RH  O0@4X~Ѡ!`zh/hЇJO0@Ĉ>U0;eFlyfŒ%7猏 6;?>9ntz33?g܌l|ӽ h͗?7jFfY Ej7!R?3fb$ =M$G}UEaRwnQѸ۝ Ɯhl̵il+5ScA3u7S7`@Ŕ< '&x4lx ׿?bz@dMMol>92SWsm'v1@à~a ~D>P~cr 83 4@*܀=P5.PO[2 i5rY`6Ӡ06>;.h*F53@iT%%3i~VsiѲâ~a 8Z /JpGc5@uj4XG_3oȒf*3v-e. Pj8b`*` XHb(k3~]Zcc]f:4wYb9sx2bY^aRH о{G>P~ lW.!VN|>L^sj8cq^1@^ `gs]Ce0^c6~a?>G̏%^ߋvNLzs\v55@X;!ԇϿg{JQ ʠaԷ#DwƔţعS 5%N{^ݑCwmAJLc=_8IfC[5s9I2ߊp̝vg5E~+">v7vYϽecC^Ѡ!x,h+5 Ε$X_ <Ӡ8 2@vf9:`:%fjx]' w FÙp~q~[ȏWAi?ދ~1׋S7u~n^.舘ǩxvj¿AyKx\y4zQ`6v, \ 0Ah4X2@ Py qt񃝹v6rMGZ^m3Oӎ5L{?ldS:~2N4'.qOE_1esc +9t P^x:lYf*eV1+w/NuuJ"3@{F>P~R6@^0w ΅iAǪC/,}kҟl< :hxb, u=?9!&r/ucE-,ދ=6Fz*CDu>EyJu.߳Ja98DHB`; *3 [D +ʫKYp];4_EjXP_z뽲Pl>)bgv\:5wj=*s݆mYMPi +{IIoB2V#+곂1Lvrne(Ie3 kPɼ2@1bp]}9YLE4t5@9 m9T:x8=benҏ}/!rbߍ^ދ΃*n'4sS{^{k?W[~gJA_$pM M tA(=ɜ PU^j.?$/\sb׉b>?ϾIҹw)z_ǃ~w].}NYvJ?iŌRݱV4+KRS2cpY^`F"5@{ B:6&b bAnNչN`[ ÀfAg$0-3!ni PZ_̠U ϼ (/pإ9,!rb?8V[H|ὨckN~w_'ݛ2!n@guB tȶܳN5h?;Alp-[k0aH Sj Pou8'i/֟hX"0M+6z_~ON~o)~{s2@3+2QCbLMaJtFʽV U,qs3=yn=!R *Ů4uAir lzd a]/O8o4g'/n2R]s]^Ŝǻ#ZGxg{/ ؾt̶s>k/#=FFZxH`<˂X ڏP\[=VWy}viA*,Z{|>}(x$c̩76=CsMj;GyujB PR6(;nױl.{^0BQZ\ kvihЇJO ,xudP6ЋA # clr==Xss]ا0ꭞ'UQ(ҧz/G|y{Ž5cua8n½DZO5voL%=s$4PW`>)aP 4/g; '\gx~9(;_Z5si 5NSI gu{:gE?󏛃>E`pg;wcfs{$c~SE*(9r4@͖I9>SGfce٣RI"]BNu'@sZY`i8uoB4 ;LJ,͂ǎm\qy?k=3u 8^5Ahk\N0՝uWvn$R,}aPh/y㓒1~. Oq ǖu P1o$oG6J'Esď K=ԷܾU]6ud7?;KYfpG&a&_,isHg^e\gY_X}TTo #A=cߍK:>xăWRb `HFU`*/ r ݱY`=/%47ҸTs=P!@(, <,3 zݨ| P쿀ϒ/giPYP ,[N`Y0@~B Tg0@ %Fi - ' ހ, B O0@4XV!M0@~A' ހ, B O0@4X `zh/0@5P?kBF vB`j h vB`j h vB`j h vB`j h h1;5fFF&LY356bƦfi^7̏312{~w& P R1RV$-g߷ʠzf|HVט)~'2Ϯu u'cfX;x`jAfcSIn9nL$ߩw-FaVyٲyw>&wo$A=g)#dx! hk47.DeW;(N/ĿwIM10@5 Pgf/i`T,p9@bM@֝uRZ_Z![Ͷ)ogJ09?֝ C9୚_ڃ9mȮssh+,S_9*3?g=>4mWE>LSt %𾏱 C3y6^aP eD(uMhy ?pED7,%_O9 zp[D^<)_LV͵,nV> M7bX|lnxF]Ivٯ;䚉ҘCSi޿;1 B 4~Uh (Y 9!{MXd.|itˢs֥~z~]ߕ,tuo/zTa&K͍ڹ]!ݫsay]= # h~u@jLIa`?6{mdz A0W< 0_0@5 P k-랏;ڥ>\8+]ұt$V_~Ale}~-ؽ6#^oJxn>}Hȿ&ޯr9h;4@a;>V]KVG͜~0@5 PR@ PAX}0ǂX;N# Ӡ[]7 61JAdޗ^ps\kU^C\;Ӈח`My+ ߗ'm?c]kߋ6$޶p-r?dvMx/`!@@N!7*t?JΩLZ`xmYWYsP[W/O2=b3IΩ@h9 X:~eG4GR9 eudlM s*s5I=#F'h٦6#f:znAzfd|:z-\cLƦ aLL$tL}6p+(ƶ 0#]:޷`vjlQ_T4 J,X_4ڬYaV1+0@)s6@ZڦNɥ2*kbuUKhL-A G$(hYYzh60u)3昆6@A_XXdKbN0@)s6@zGKb'&5U6@L\T. G<_X`` Gg՝f +MM*kbuU+nY;iF;΂er\j\ߝkTo^OLևq{||JKjw=iXWh¾%NY؊:\s>]̏Tkr|246TRLe{*A\l~wQy=2Y99W6;)s1hbl/y\=Juj~qM?S8>uʈkdX"ק&bcO{MR_1wθr@?--;ZOoSx߭R߽~ӜV`:ݾ9~"޻|D{Qs>|W1?`}Q17)eyF.sJaetqM .ꍘERNo1S']Z Pj~lLk&#{*QdTOX.mDm ӮpR9wiϨR# Ȩc /9 vN{*eb/},3n}@Z@{{]@Tܠ#zE[w'(ntM N?ƻY>_Mnw+}mV!}OG^'۞RNB|pyJUڟtLET{bĂPL=V6%r27qORVh/ ]Ӓ똣ȵ6OLk~Tx'c|kp}9i!fϡKݼ!TI lTaP ܪ,a 1 <֓ >>Lde/ ߫X ȞϔH*W9s|5N`$sX{ŖK:bt֎*6l沘]wXy {sdML򷢔r Ojgc3=1dZ:i?~1g4/BEK.&u0&# KZ\ !A,XlfY<.Ye=y(7B (bJbH[QvGKgʨl4R6(| E0و^6Df0ˁX7(vuB]ż mr$c:Gg.- J * 6@*=+UK@t]I,X5q/m& E5@51,!MdFO~B`x Xz~B`x Xz~B`x vB`j h vB`j h vB`j h v` > BXjժhЇJ B  B P@;!@:Hҳ@:B!JN;Eo%I3W*P[uhR.H,<)7\$B7ԛOCL,B3Sӕ8Raǂ8 2Ñd*,fB?K!v"! 8\l|q ΏMPrõ8ccp; X 0 tRLɊ|ƃB'D| 2U>@b@1#<8D[%B\56O:ڐB &C { fOxFh#F al7l8oEq]JGvn^J^b `%;Nbf0X#v;*kbm ESɆ~? ƔWMT?8囅'!f 3"H_y\Q鈾7|"cElp;LRe3r0Ā&: :i`A)Xր l;4fpUp܇k==C =x"~H!)H2 Y"eHA~G#' Hryt!o(P-BGh &Lt*Z.Djt7ZD/7vڋLL1ca1X*I9X VUcuX|ױvqz q.>/+x=~?{:`O& ibB9a; ;D"QhM{/EI\J@KɞK!qHbznq5R'#YlBv!S"r||GQXR)1ee9erIjPju>uzNEELKePe:}*U|ih,DvvN[|2z *CQSZZzMER-PmZZ+ju+u:G}za g\4.h$iZih4jn< X8"jYkJhjhkjNҞ]}T]ӱa,ٯsK糮n._wn5z#z%z{n}ggoh3fA>##JFq533iaQh)nc,njL&~&B&M^2:ifitiiYY^TsO -=&Q,j-YR,=-k-Y~JZd`ZϚm]h]kno3զ-6vU;N`WiwwooI5R4zmCCCcGH"ף,FZ9ܨNnN9Nۜ;k:u.rnr~butJw ufh荣1ܢ}qp׹wyXxyTy\yދ5׫듷w~||}vc=?fۘ_3_v?_fvS^灶Y_9I}`yfNca%!!!!BB3CkC{f'G 6bs5잱cg=Axi)lBFzm-n1U1ccG;rܳ8YqSwſOJXp?&Qؒ41&CrprYrQgb"LiL%&nO2ä́Ήn'ޚd=i &L>:Em gʁ4BZrڮ~N ӛNJᲸkxռ./<7,Eo.\-d +o³6e}Ȏޑ=7{X)3Λ&ۧzO]3G!.EZ#Fq_AeiILט.~y݌%36ɝ2tYg2>e܅s;9:?{ENEeE/H^дhἅR[Z,)gѦb%K/Z+XTZ^ڿοu`YƲ7 2²UQW3W{͔5GoZK]+[۾.r]z+W*nVU2ZRaoõ6m*yp-a[ꫭ˷l}-i۹<nt;veky-Z+=q={[{{Go9yCC%HAC{cJc᱇[|q<ٴǨ8^xD̓-SZq[D96s玟=|ዞ._vnjuoqզ1mǮ_;y=nFlx퉷sͽ{}= <(yko{/?riggM׼pyu儗įug|OɛK[zc{}ǝ Screenshot n.@IDATx py}Hl$@Hi)eص]ǎ뤉v%Ifߴ4I3i'v긎|q]/dIENw$@؈}>'H89yΒ(mF ~WSorL݄YY.RRvFOH#ݼ9H{ѣeP?moe$3~O|:R>]P0O>j>$" " " " " " " " " " " " " " @J >}G|Fk#4?%1zߊW1s4 ." " " " " " " " " " " " " " D 57u2Y{睁 W@䩩o:ڞ#}ǎ@\Ni87ѢϾN{Sܑ>c7[ߒDd%@ש)cGO;}[cjgo6׋#0LKO}'7O>;8*dFÿԔWo"_D@D@D@D@D@D@D@D@D@D@D@D@D@D@"SbTD@D@D@D@D@D@D@D@D@D@D@D@D@D@D`R7OmſQ))FTK٪8Ĺ@}0Xz e\v >D^wo߳7$SzKB 5sx9T\J >@ +ة"8g'oPbl ;8s' ϝ붠@[HԒ];" " " " "0cc==]pKH1犊.]ι󧒒ˆ'ps.54tv:uhknذjs990/QD@D@D@D@D@@F|0ΝQNsI;ι3F 3?~-" " " "<FFrsghȹaCnݮ]<\UUQQf̕yllxx`[';{R)gQS!\ccCC[@+@̔}*$FD@D@D@D@D@@jr×-D{N E>FOTӌʻ2a#1r9YT3r\3,'z^1̇,LQ" " " " hKs55MMQQ ܯ_onN:iieXD NlhR'wT$w76O =U{ԌӞ(e9X.3o,r՗4{L,7vdY;ZNG |Y匦/#~V})8i;ㅑ?ڙt*0q M?w[D@D@D@D 9 tuAq%/̛7sώ܌.pF.X|ӧ}On" " " " " " I`S Tg*f;MO;ӥ{pg9Y@,r,dDNt3YN[@ t 9lH.7nܼ9LVmPȾޡCΝ;pGGmswX\iivJ>1x.xŎkN|(.^ι߹srZ۷É7n@!LYb۶snڲuN˹sP.P`x&%,_;v8nݒ%P&κ:^{ΞVںGGkjNvVpL-)/oj^(?|Q皛J\i]e:CC==XqovS/%%= -[v :ڊo(ѧ"7>KFtnL%m9kgVx34.pD0(\"h|?l;rMz&r(\"h|?l;rMz&r(\"h|." " " "hjD~ͳq"NK+*?>Aixƭ[SQ|3&c=s7훺GF?xpÇ>͌.t$h;qT(77%8GP.\`8Q]9tL]{],\8s-|Odllx\&^|;L]q[[QrŹv Co8g#/CHدoߑ#SW3=B9vlW^q;wilt 28gO-Iṋ" " " " E҆A[%Kp䴝|4:Z\ E4zչn TSރ˖ّ>65okoN캺xJ%K֭ۺ;w;wd\{cueRSúBz)Nbwۧ&ď[ Gtc;5q|'Ow3VqqUvt?ʕm;e̙G;sE^hE~wΌ6mذ}UVQ66֞?/bajf(;;QϪyaދN<ٸ`˖m6lByCtt`^}=l[K͊Gyc;8E;m۴iJLDx)쌿q#v'sG9\Y /㋅&B%\'X=m۱'6̟ Ncc#*Ν<ܥK w[11e3걲z0Əc9Yö,W4Ədr\?Z." " " "놆ס ֋%|d8nՋNm ԯ؈=th9 +W]bgq'///X P~{BVNmKq%[_BomBTU-YRVuŮ //_Ypb(7o~1^P`sҥCfMqqvO;pں~闖Bqf9{E>\|UU%Bxz{т&.,B .ym۠69maߺuJܽ,66GY޽;w_ܼy٨7n"Gh?c8TUsq_כ{{+V'NfػYR|QO^jݻѾ/B"0 SO`ttpZӜ%BܰP(ŋڛPGwg{_xW5q9+V,Zd4Ge[\䃝UUw55a@ܡ7&qo|,Ǝrߴ ߲͛eFܵ%w+q.(^2Yhyo2).@rNkkt4ꍍ7o;|ʍo^E^ee+V`M?'\~*-h~qMmz4vƍPGe\P!,P76_@zbaTNCx ;?YΘ{'hTKxS  (>rG=l;rEM;Z>ƛjhDY;aY+Zn?1TG%rx<nƝEV7+ t(q75セ׬も{3=* Ba s8|4\^XX^n;\aav~u5SQ"~(;;av 큁qA83ܹu1OGOUntbw\+EE;'?%%= (*+-@~/?h-h__vp|z ; `KwwGNn,=$iz?guVC.D/PHbnna~ r03`oz߂MB$AXWWSɋ޺u|s?~-" " " "0FFz{ ]8)zdTJ^[[S%%%Uzo<$NXnmP-dgM=: '#P5c'عsߴm{}4_,@Bq҃M"" " " " " "07̸"́@j+KTp=jgLc=/'p3ewo߀7Yx׮M3͛|rN]=46ޡC;VS"J7pϛ99Ptr67yyxӲt ,W..F~X4:~vČqNK,\XP8ζ6(jՖ-)α<@MMnݹ(̻;" n,@A|PVWd2::0EgϾ.pZEEk:|yy9%%_}=OH@;}hz 1FFy.HMNqR@a"[Я/[`z{>'d^kCrQx7u}&kӤ;& <&rܬGQwڧj2=&Ydd1Y.f=区>U1<4r\,'x4 A!Q\#ˋ8'E(#;*7o^ 7P nŋDgs{2bwwcKP64J{4(-[& 3g;y2vAfݍ>9CԼݍ'7P+W::=٥xi۷ϝ;v ):;&Ν&qil ==>vIɒ%OW;vsw Lֆe_S\LtE7N==\}',`cN'6$" " " " " "0 $;q;q0 ^P¦{Լp̗`Xh3mgXN h957e9X.3LY.f='ZNG |Y匦/EcgoT/6`T ȞVPPZ# GF_K) ڵ8:=*mm55PNaG16(د];{ۿ1M^쥥UUX@0TW/[dǃ Dstĉ_\GG[h」Gp3gjkמYT~+WbvXԩނb+WnD}F,8zt^ ;- `XQU5q!x>ӟ"˗nmg#t X~aW9|ȑ}^}Wz[('(twMю$" " " " " " @x+0?N Kf> ̗`8" " " " 6n(8,a'y8trX ݻ7mZfbp kls= EMl(Ɨ/_9yseiwnիwzY6mZ2>q3ڵaCtg<ꕒJMB=Hh#(Aq[ yy ;yCBw*m^x9{lٲhY~۶=?܆ O3--''.<>!_V(Iff~>ʷ}޽o։͸~'K_" " " " " " 3K VNuT0?xGZsgzZ ܱΝi#Ӽ8}f/w?w[D@D@D@D#==s8s87s#xE)UJ^Z e\v  Ĺ@b=jG ˗kj::۷wZZ뛘gDоbŒ%ݻsgis+VTUHnWD@D@D`6}6~k*Й,{N%͙"L]ܩ@aS U ĤG "Ĺ@vҜqn783sx8^ oR On5^\ X>v'*{8]'d) MwwOvʶUT87<<2R]\9WsY'd\Bf.9wN(}۹[Q<<مS#=;eeέ_ryZ25 %" "sȥKc!͛i<=ϭiiW[Nt&E@D@D`Йnt(w< HN`'jԟaև;ȸl vߣI; obӯ=a{[hSE@D`rP@0w2v|.\x ~GG8s&4A<2SS>åﺟҋ))>fc|*̙63 3}J3==- )ϟ_X܂ϫcc9wի=f;Wt8CA=dž˨"~QN8{Go;[[ۡ0{Q8O%/0z$gkh JK,pgo|,?h[<mp} dяׇ?5ӧ‚۸Q TAU" " "0pGYޯb 4">:|.QwOf0|4=i2E?xL? G~y8ǝQe.B1HN‰)d|;AtID(*+~eJqNyjcPCq>'^>vwasnʵk<I/JGD@D@f1*`߯ A*8aB4cB0N t٣?j2D1<>]\;NS"׆@;?"lO vm8{˨8~)-גLSrt G:]wIw_h>_T|˃{0&t%pte9 8 BZ i<\h;ǏCq#wXo߉!Z?Yu=сs64@~wo ][,)IO\%zz P1y>:btƛ\f^R7E@^vn\ spe8ླ j\۷ڵk}&j4!I pB;V1oѝQw=Df4M|g|i&rdx G2\4ٓ@p%ėqs ȇL}\aܡvRO?'Ƨ@pD0?6@lE.&Vo 9_sgx@"=7hwvbWkkv&(Z[2RZ7l(-]ڹx],(}"ޙ3H…-]Wс;cz{q  ##7w*gf;WTd%%έY`˖AQPGΝ;RwXQVCC/ ~i]ī,)p3Us SdhKgĞg,ÇQۑՂ) cbT!nG#p<Ćw7_G-JYr G^xŸK'ƗiKb;O(ٳ^3|A;2ݴ4Ow'vL%taΰww!/oߑ#9䓛6a^gϞ1^iw'SD@D`qϡP8B!fI~D@* Xz7mx€RU|;V…f"Wlל۸q_ESH)"I^Gb:L OwOhFEQGҝp4x4s) d&~OwN!H@b0s).sxl I4Y.^TП(|TU@;-)bN84xgA}1b”8(\X/sАMwνʵkPdC޸a o /_[}#˗c"]c{9k55o yK f́vm01CCPtBx'O65]&驩PLl\^nsO=;bq$1GyEZȵk08&:>f_ZDÙL~љrAHáΝAA^m9x+`q.33wje91}r[7aglFFZ{" O9^Y+@kk[hoC|ٳ8 l4ӧ?Mx^C_5ܱ_=q'|Kn%aig|b  |1,ܶm|;wbf"!@`d6.`I2+1rF=GF P䭃ݨd"  pBΝÄ=wN: #H'HntigBϯƊ77p޽~'d @?連^4^";ݣ&?h2Z?}̆zXO57+g~>_pB;{iyzw_5<bBWW_cMqY@CCSSOsoq0|8q6J rG-/&6>//7|(huܭ쏴ӟBYg 3gH8t ÇOyuEE=SOAp!Fp," " F8zȹYy'D2Lx_GvKwxp4' v3Qf2|4ݨKOtoU^BbL,6ohD6.jg@p(fxDd8 iC?3+őz,'a2o~}Wh {*{^Dܨ;vAqplW̟E111c3>('_zvĊ/~qÆ皛{{7n9⏖2/'4=;wbg{uOKLEnnNNw7f ʧ4 \c 75ŸO*aC k2pF!ŗGp~N11^X4hp1t鋅^],a\tz>$3,@G1=bz2dnM Y= f]]by=Pz۷/Z}EK(c"_НvF+5+W5{5[]$3hW^w{-Hn|ƒd'g:<*;r-QsJq$l8<x8D?MK?NDK,~_lW<*};@ .0if7e?a;?b>+l;.ެGw @1;Rv3<ˍ MɞvӭP2݁~r3}ssmm{[܎TGxZ+m|2˃`^UY rr7/#w#秧cE==v'zo/ ;80qap;?ҳ؁{w?\Nv(d^=quuN9m_v4gggIr`?xyA]dGVlaރޭTb`ggHl:̏&i/2}X~}B0a. rx67_.ˇJ;Bfeeg]Ɲ C{!o~oW\X[ 9EtOdL('T=Q?`hzӌ;zY A;QS;ヒq _~vH~2E@D@_##`p…sv?^ q|cgws/'mmvUn.c֭{ =*!@etGG?ώs>0xݪ?t)L]+/^i1xwYc{c_Yj3>ʃ}cE/8KKq7z#tL hփ;áŋG{4ԤԼs? nq @ E³7(D ?WSG!T] O;ͨ{>QƓ\H]}}&ݣb{)p~Dߞ_(2? C:ϗqnU bˁ8WWoBX~g9;իqa}=jvv77.c̪*V.(C>DZ,("0rzO=u%驭5:v$ZKJms:7wѢ Z𧶶G--cG#?#Gm(_)9 }[k@22Mz1A~?=nL!G?]f),?6X< OJ0=| <%Odєd`;ru(z/\*زigQwtm8j'sx6c?DjCȈ>xc,1X{4~"dp@IDAT5̛aKre`X" " "0U08> 47C]~о}]r=7cfZZf&GG[[ٵkv[zFW܍D a4e؁^4Q%*%~pa;G߰>bٗ,LͿ19+SqYw޽=/[? <|76Q DAwɓ_<{4<̟ @A ^\ aa &P23SRp_J b*ܭ?ێC 1l\p^ 9ͼ- h{dIf\bg UHpLXx&DZ O^ч#~xЌ1= f_tP /|SOa2@\hQ{ۗ}ch❮0.b"@LwϠ`øtߗyQX.\|p5x HȀ !ء?i!}#O-wX-%ħ=\Z{#'Ϟ* pH@ڙol5cs_b[q1&h6[bN oν XP_؈]]66/4/o w7 v?y9(./F=8~3L o.]p #GOB d$۝;|gMaOi!+C=j;]]~!Oa{S'WS., pfA11<|dB(WZb R?77ob«^v0HƎOƗBvi}~֞ܬ>`T3*\>ot\sTN~U?s7of`#EY qߢE6|{^sO v 8m58g3䱮&,*)xe8I iS)Fh:glnx<6gxµtaֳU={&;k}-[d~wP2EWa!Ywwo/?}똗`8}w{cc[|d`{,f8~|>(ĻКH3{;OIK+,F֌ <[@L/Ҟp@z"a9yɲ2,|?P4f؈룣oPoퟵb}g5d1`Z73L G;lhx1 3L_h!DNw+jd&{/My}X;ag;b'`CqӻvHX-.hQ;Ӝj~Q^NwIwC*X.7Ue>\XX_NwƋ|f3&*3c7Bt#p~8\`6AbBe0\VVv6Xp #dQ>iRAmoBޝtɧg?%%'U8X/o2؝G޴/Jf cˏّBggc-v`3)\U[/%[PwWԗfUUe% p b}B^Gjkh0)" " pyvǎ8]]8''h#ʰPє\RZjGs#^ps(s…O|JKs'Ofe*0k@iQ^zsrQ8 Cpd=|9GKv@efIIn.?( _^gt)Wns?0?t1D2a3^ԝv3x؁=LXA~wwӈ$}և7NM7|4*xT{u έ MMIO;b[#־J5_?HfX;uW@b}w ;t:/\0u /L{'Нmq̏;igH*iW3u?zHx1s+^r`]8$eB=|̅t!’Vs0?3LLxC{558꺨;*Ϸ"B"8 7ت1bسJax~]1qVmBq~xʡC<pv t`"N{= ڭܾgfL56~;!38M(ު7n?Ȱ# Ӊ8#iaL#1 ^c gUҌMW /ƩSbVZ|;˗q՝;+_{1޲e/WxoWysv/^p!?/j~lg<W?E3ܓAip5(,?ߟ0<8+[Zq fxmQjjeڵux+Wn܈9aRvȈma8!_lۉ$`ش o?~nY q;hs4cmԷuz+0g%\TPy*R7[Wގ#Z#G_X'&h/ּW|v*:ŋ a7@#W..ƃ?j}VPK{D:_Mx]{)¾,,@`:;~B%oig;gxNMsv  l!_:~2򊆋-9Ӈ 0WN(*+p GFH#?X>]3Q;T^;&tV[Z0ed Đoh%~=.^\,H1@;@0AAph~Q;LN5 vD xcc⬺zR4" [gK}Ta8kj՞|t_7{Ocݷm+*a͚'oo;?ݹ Z[1SSׇ8/_Os?^Vr/ٳMMPuv @qxqNv&\4&oS@SFxvUߪ9 ^n OQv jFxAx.tOcm.4gYV D:ng%K|Iy$7*agF+N77/;0e[^avǚv099g>8T?$nx@A9` a{ ~ntؿQW8 ~w(ٻW]Q(=q}y=Dӕ]D@D$#E_g } 7 2x8ZVbvbdϞŹ@ qϤw##1bXfc,_sHҥ?I;|c\0{ժME<' b"mٲ};ҫz=l)[jllHM۲(;! 3Av6]ޯ+ۋ{0&sr}Z츯*-ŋt8|? c /I={ uig̨;gFφnLQwy3"%+@?GHzW:ܯ4]VvD;Q!v2A.T;;m%~n-]@aC's*ק̐ҁϕ0z!>ܽF񝱣|vz^-s Z MR,p^Q+WRG\LwTXs<TqG@p6_SsxѤ{8U|اSh_Az>Y K|/]\]p/̇9ZHcA:N!$"pZ[ڰ;>NaXqϰPO;9{ֹ7؎xڊcxDoc\ĉ'|˟O2Յhm,aOO? NOlj{`Dt:렅 fp+zj8wkbr",뫯t)\ˉ~w[R,+v+|:<!A"ms ;|D@z΁.ϧ߃?с!waЗlݺ GCy=R*B}ʮNjs@~@;??'֌/+ӥz JX=hޏ|i倐ޗeD3p s__MdA2g;߿'v{-K@󡡖T.a{ /T#=C0>e.Mz! ̟tj~W?)thh8y'aQ>q|5Nw "ܩS--a}{Eņ J$o߭;܁m5W vihgnr1Rb JϬ'Mezb1UU: z )ЇO+'kjpωSvvшu7//C/3E&jݧdɢEPh֗^я0A꫇W)qg;4#'Ǟyԧ>aەub:~W2tux^… 55x*++-ż}$" "(WWw:ƥ7qrmquPV6dgcYv'aWፓ(Q{q?Z܁~TMMaYaaS`5|T̥#G0mˡG^˘ @o^,th?:YSnHlBbB8'.| .c~N;ZZ0 X8p7]Êt(׮]#֡TkŇ؞C_[  u|!G8A;cҝNs2Gݽ'3>_ϡmhM;ͩ~ȠBnm >ScG=qt\Tc-E;97g!wn׮΍Nkk_zs O׮m-tZ* ĎE8v~>=* X41= Gbز@7퇥 )m֬Y 1Q7` V\;yyvbis:vQ"!,L8}ϟz\vڲe)Es3ʃVRQ&P~՝Sz[jvɫd[e/-0;9B`&`&p20  f5E^M-[eIuKߟw^~jjIݭs~z֭[zZ44M5ZB%%@ӞJ:VRG>f]Ut • 'Uﯩt0ϋU$sQj|~55L`} B C;V. *,АA;Ւtk V|<\&} CS;"l;L,X]#8_a|n߽ĕJ$Al|ПhwIgWfea8*egOA2+<Ș:Lii2DhMjzO._Ι7|WuvZz~^_]2\kǷv1Q:y2#T^, PӀV\/-/9ToǗX`>ܼopޥKTUq.}Q Wf0CiTNJ/#}j肠8tV[ΛW_тvsGҫ<۷75aNv5%`- pc ެ= 6NzIFōy8pnէxi9wPqc}0q#pŧ^ /]nnQ,@ջv }dxpa/%h#Gl%F:8 4„'8 |՗^)qw}, ~s09.w _aYssk+´i\ 3gӞz50'64451/ܭzn݃r?_r+W^vׯι-L.v8<%^Ԟ&D8zj]f iʢˇc*>35kɠ5|.U/t 8,;sF7ߟfre\)N_~-XPVF{{,8}"Nv?x;++?l)U~֬4,:͞=t."]8:!KT$Vf4Bp hxNF#E"5\A4X|$6W9jp;qfa,<5{H@zVH%MAU肠^ee.s 0  z##D ڀggBWWOiCG23i rgB*##/f@?ɦ`Aݘ ]{( ;2(J^\J^3~¥)SBwVO:C\Nt&FY`o;_c].]E tw;JH:3` _w7nr%vxCz/|GQ$^AW XX褢_ŧwUK:,^~9\9k%ZRzK@B_`zac(&D D;ߟUVFBvzVV.ZnB l~%SNƗ ҹT?2UhᨫG `AAiWEEeezzΰر~O;shJPoq3.㝗J %{54oБ9 WHL^Q1mr--e8'?{03w7.wX%鹪qQ*ù_RR\^(\G0~pi'pF ֭vQO::'q̜93f@Q'`7ֆt FJ&h7h{zgI,Фu!TYb/v4- ظwpP3D9ZNI/愲l0[Kl_뫯?pqF}}]XsTU9- غum#ftTlڥK@_v&?(F!JES|~7G;sƍTgٻGS :v+WVU!#{:vȹP Tv-+7}tV<]6oJoF q"ݵkX]*;C(!՛2@N*+?ى`f:wi#B {BJ r ##v8VzTg,nqEpu~W=B<ԂojLS)[lbz„xO, g㸋8~NpOڔ#ӭ/B32 i(-5|Ï=Aގa\F:wu睷lC@]HkjKwr;u|w7[}Fχ.dUKB&.(VytԻB:8jG+CDŽ/SP=yպ:vIT6geى!W\1s&wxkaL\9s&;~Ga)aZv74]&Ǹ/4NKަWvm"P,|Y_UuM ~9_"Zӟ7}6>ɻ樂7oMq&Ϻk_ayg'KSck.yhC1KU/U%;Ǽ%qw8Z_lW&c|% tvrYkc֖nW-]zѐ;{h5^e"Yo~5m q ;7n@.ZV7.&/q#'fͣGJ7bP=Lk@D;"g*pS%@8zWBD;_)zGU QzQ~I:p([&._n*I imdmق? 1eeAwR n5 t'XD~=4$ysR:dU7`0pn` VDOG1*}EG)(\3xPb?^% 9]_o:OO;w?n@UYsfN / 9e<݅K>Sr99,޷kcٿa~iSB{T{wVaV6aV'3\_S3<':~=;l3ùK) pFz>4a SXRY:;,0i? <9eIxТEg1YY7tj\y]|xL8 p9 OߧCdOv7lq,*᭭O]o –+v7t) O8vyXXwЭa/n8OHw6]y!2ű;U[wd1ث:H_OfN+c=eֵ;o2dk7ABe6O"nGWtb WOffr4 ߘt-ɉr8|Ʊz>Z*!:6Pd]C]*TMTkECsb`;\j>Tk3ېJpTVjZz,TaL}4P x>Ks7FΥS#Ok55~S~.HݸP8VQm6= 0~N &pw5:@QhQ V߂xDR= U&#%5O;PGffzL)g\Kw &$n#<}:wϗ5XQADHi`ca^;E_|k΍/uu\MQz:AIJr\f3zg' 0gZ;& ^::J>|K@z6piiX_–?U4`MM>z0X}~ ;nTbzF:2ܓxUkHF87~aŽ3f\}Λqs~>Gǝhi6 `B~N&pZ歜I\qqF`"qq 邛ǥA;oTl䇶6;zVwt{ߝwr/~#$Kub'Rډb5667`"B&쓕%S覒^q~xKKKK`bJ@}NVR{i3j?矶G'MϜynvT~|1{rj33++ zz%܍;)9- ހ@*:XzAŘJ(X00ue]VtRB /…TظA$@ƟˏX'ÍM|@77!GU-Sz "fɑpLyEe0+OT 'OIϝKOU&5ztWuB+w7`aGa=G}&@ Js6G/RdzBC;A9}?ղӥ5/Cwt}S\*x`}e&PMNjuOCvXA&%?3'R6ZχOJN#SF!SP}+tWkU;I H?\2ˏ~<\tD=*]Ƿ9i>10sG I$ї\La;;$0+cM6T)`^hѣ73˛;33sr./xz_Gyۙ@ݥGK^8q',ƹ_\22`֬K.gE]]mmsankoߺ0(Pb$]}/Iͥ8@tꉛS]~XPm~rS8wxk-ٳOg/]/_j3|FiDKJLdỴ&FΝ5nW?;Uvڷ^xWٙe\D}KWN0>B.• O._#GrZ *:P18"V^ϓԩ3gΚ\>uuIX,xhݶhtsw4R\yz'ūQX "EttjTQ &=GUY*ËMy=1, |7TYn]W+`;;;:0Cʥr6*@3iFxb:YYf)+ʢ_UeG:EyGNRIm-?!{zapB[W)0]:KR+>.XW`G}tNpO^yG44K2J?w z,Tb˕?jGJZ:z`eA;lgnfL 1DnK3#°RĜ\VT 'm{${YNh azoYzcWxT/_|kH|L ] +]|Q:ܗ!zzdRrOlBͩUr<9+ `sWFHv0Y{mq9}}uu=. X;֭&.CKJ+'T<_Ǘ !5< ԩ#ڭ4?݋'OY O,nXoPCe1,\%J#Gyn_xa# c@c>ַ.[ӅOk O~;1t<޻7T͝y߸p!ߕ;n@WHN_ }}x/G9~jzKKKK`KV7yWB MW2l̟b.,]_&:}};mm9u*ToqfGm:LpӹۑsҎٹv ]Њ: 7XxzKΒ( Q(v&Z[9ā'+18Ug cj Krf%9},  4pOg"at`)\:${)!`(ϩq>aѱT5K/J,|/ BY}#`@WKyy3:|ezU D`_FFv6J[ 2k]y#o9WvtѮG[rB[G˼a(\O]Qtc#; %pə4r{]! ?LӒJj8Ξ09o K@?ig(R{mo^=lى!%Qc)z$EZ ';j0& E'\ -N 2ڢvG\.oZz}/I@Pvػy4 XEa0nyJ&bg>Yvﮭ%}gG<󂵵68p?~ַ>oLF꯾'_ٳg`Ke-?{vEpUՌllZ\dXg:wOy{8}S,dx{g{KE;vYk0hl>=ጞ!7|dM 3. +X1%5ϖܜW񋷬̔-n=~sT{lih^kh@y硣W11M*au>Ǔ)Ʌ'bd6D<Z$ яK=2v|W9㟃[HtSq/s:Q^nG\'HY&fޟP}^Ri1K}]踉K`HezQ?8+WW88'T.67X .4iH`3ډ c#;Mcg@CCs3|IIQI.Ik39lb?:ʜJ)SAF14<ugg~v7.jwT9[~j՟n3vԵz,'qϖ$ϟ<0I?s3X0?tA0#t+.=`+cbz4uc"79>= ܉==+<|9="6!əVP{(Bx(ջ]L|ևvpww)),6 H|8 iI3 O؂tzcX}1 G_k3F{ \sB{P^d<\|ƍ[`wӦmU;|XnW]qOlߥkjjknlܹ,xQnW_vYEE6w.h~_bv4fŊe˸}K.!ɶ}޼J|ia_'o yOĔ.~kd@W+4|ii` kf9 w4$7]=_:NĹ4/$9y{z^ Ȅ >?R"#΁| 'ŃK?U8=;~9n<'2E^'hJESW`WWz1R8tN|97 K/uo{-p /]'08j­^{meK~W_5}%KXApmWϙwq/ K=B壏n{-[(8>£Gcǻ`QN G|]񾗀!Ci'HaA9Z |OZl'Ĝ-õr`< ϗ<::vg~9jh-4yX\w|']wLlJp&g*gqKMQ")[(J/N*tD+?q_Bϸs#|`a+^ x4g-`+x¶pM OE1YWJ/.Xҍ!>8(?~%k2;̱#nb= |{dx͚Nf@߸Wh?֭{!Ko<+_k֬Z׿~j>o|WXL)סCƭҀg-(ؿI4#WGff$ { x x L| Օ \Sb3-wӮi^c;U;mܶA=e\=- @ooniQC&ω鲳1kJ/x:XR*ot22uJQxGp\(o,O|;D]8OV|u@/nmr˰dwc|V B< v@G&%`HS'ڈ>a[Lt(|>S҄V" pO*pS*7./9£a; v1xxv-T'TϪ gͲ2ژnchۣGٱ'}R9RI[smwE4~Q.<5 ֋A;X}QyrV֬a^3Ԛ:|֑r`06 TEWGͅ[zoUN:9![1|؎)r4D}_|rZƇqsCnn\}ԩ7'JE{O)4م o:n>h]Տ8L}ˏ,ߕ,_:+\r W8Lw_z tWIWJ'?NZ:N4Ay}[<󌥦>|M;;yVZ*^, MMDQB{8=t(j`e+q'yȏ{J@B0\aI z8׊gg ҇CؾӎEXo 㿿)W |BZ*\ޟ{rve!n(8)'ܼS*^*aaAXKM[7Ƃxaw}5韀!_cғZJuw8]nnqܹAPUf?r[n`l,@G_"ů LG>-drv͙7Au7~;tokÀW?Z~IS|9|,<<%LZ y\frgja Z])^|qv =3,QYbSi.~Sp͞]XH~7x5,0P]xKKKK`bJ@53g^>rܭ6"q+GG}!`ÛhRRZ[ׯAʶoиvZ`v6'̘q%]NpGVU嗏eŪ>@6p!tJiQ$:gՁX[<;Yzi~h"'4@9F+?!&^=deC-oYCJr?3 ÝHEz^J_5]I,TZ`plVO 6 ,єU`:&yM| 'U?~%TKt/N< U&HKcwd/^|E_M(&zzjdbG}adMUCk9+tJ>Q>ؕNY :I8g({Nw7.Vn  \ꁦzI=}Q&ljLh:7r۷QpK޶mFqqnna*әpq`[[]]<}>[?ΑǎuvrCf7}z^ MwykqK<^}ڳ)I՟7gڻUoDha_Tl"]ٳ _}ȠooA~jyC~Т>+ū vkG*N.MW.菮F~?q0mt}y7ipY-__x!GM _W>A0~eeuv~]G1܌,nJpq#;˕ {^Ue ]G+ܳi39*Yԩ\ޔ^^^^SW) ]ܙ8ݮQZ {֬Y%ɉ"&&'0 ]|,2bcAl=3DƿTN]~yE;Ӳh/3Ux)):7F)Rb~ 7,G$WtoQ>P=1w1cL&7p^_W 6\DXs8lG#X_Va+]VTsoӧ}*^ gG/ঢpBd] O| xr\[w_|>S@KZ[{z8d׮&lq,jm2CW_]ZznJTINnݴ2kn.1<ݙEvxmz=PV_|'ͣmjr8}=xm*==Z.ejYpUIZ*x@* C(޺|9 &әځp 6ߠسfǭڃnZ2ٝcy[t/75RGjj\>zkIf8?to?OQ>O$Tx:'M$k#fw{тf7}////)}5ln;c&M讟j=EGwu1p}K3]Z*d@c#G[KԿW?z(օ Erǻ\`Z\VrtK$u:f ȀL\yVǛ"i@8uKj/|uuUK g,+?8st R `;sё XwKR 8}4]z,X#tN.U/T_+u<\jxO)\p…OձPǷjtP"t+~$?U:(\:t$vs u~7 U. 3M>]TG߇/ 7k xժի"C&TЍ>?ؒZ_p"tIŽc".z!+WrL1?: w8\PoL^ysWXޮZUYQps%6z|m ɻwӇ/rŊѝc2 >/۶54`P}#ԇ?L}n__g'&srlG4(ԕKS%a<8, ^" }믿:iǎ56> l_}:cstQ857!_vCz;ޯ]z5Grzڵ\rf`߽pJEYP@ 7olv{lFx?o\T^C]V)> nXu]{u׹ >kzOy x x x \XjU8!W8%/v9n3ڼ1_q{aE\ 2--;vmyLH j_ S2ϊ\.B8 O>YS@k:fmmL乡*,g뀩kXa\|"xu_ Kq 7.z]Dl,sO.*÷8eyQQ^O~+Pk'f$):;pqpѣP9)V%8 k]:E_\B̏;'WKOgVS`bxMN]99 2egBI|)^;DC>/PWzM !N`oߡC|?xea]QQA'>% C~{پ} 6,+>zv/9ЉȾʫrc*yn !:J{d=$1tAp4t$p~5zݕ?tйt.ft|k@wpr߂G0F;Cw3?5|-myiuNŒ N~ԩI{W_]TlYTTdeqC=={`{;;Xwnl$nS=l=$G{Ox( IKOd_XurEE==۶aX>qE5m'=K:2_x,\G ff'Mf6Qw-/L0B,3pyQMrCu+Dr0~FF~Y,ggi@2 HرCv}jnshTp2}X -Fg7;.T+s֬r,]:o%sΚE9JJYwsWsT; hT_U^W`QNp{b;`b_D[$gƢ(-=th~kmLIFm >̈́w33: ޖ!G|k< ZZ2LEʠrGW \yMAlʕ5xx:N/;,CtP"ȹfҩ#&E H+[*^F9Rxzxa._?DCYġ# 'sGw/ZJ5?_@s&"u>x:Oc kOחJ%Uzt?p?_ қThB$t#|WxIJ s;Vuq!kjvHgeicv$@͂aohA&v33:f6vˠGOTOO!ZڅBn8seXDCp5c%0 PBuq>d(w$tL8 7{ ~K]ĸt0toSO=u;.G>RUn4\ .$ ;;𯯯b۾}?Y*6gΝ΁WZZኆza%Zb[g+ҩp`kkyê(EE,nlJn%Υ)N/ ;#̥Q3 3DװLo,NZEM+JA\\(^8~ |ė3rT~,VpEI/%^"tA7s;ww }il0=H;] {Ï,W]c^LApE~˖,??7WWbwuu^}2*ϝ;s&^Vq4ߣ~ՠ< RS8͟?u*ozӭX?8wl<., F%KoJmwNl R𾾮.ڛ#G}y?OdKؕiw7G!bҗ>;qJl*Qooa!…&7%Wwoa<&-[P&YI{N!5 T1n 5iR)OC9BjZQz #x[Jij?Ƨ|UGW%en.]j&bYsJt, KΞ]^ag^-=D;kQX;WzŘ?RzmlOctX+>NW^ V.X(\+=< _zϮ2)tK*H#37+!cUbu.dl ;z-,?QRRQDeם:wta-t݉+O Đ q7t=mGK)=H$Gl55q& ?pA|z\oZ <ː-}V_)_|wxbh71XX޼:WYT)_o HoyΎ57%ZlJs̟VE.]N#G}4ydg 5k!|_Tܻ>%zsQ}wuGx>~w A^z) (ϟ>47|\<0ʯӄx9%ҎRT i2(İe`x*ѼDIt(8\.\$+pϷD)IGx-{ ]uM7q~U_΄8ut鿅Mݚ5+WΙuu ̋~_EM?$ǫ6@v3|OPr9Çẘ}s[6nk8:{[({9?~+F啄[|yy^뮛o7/:.Q񾗀OWȽUW^M<&t!HA紟ݭ^[Ay__5;,jZ+CjWkj8~yK 'hz1 jk#9'Q5Khݶ`={Z[0Ww="(V u +,Ie5V \zU S>r˟EGhåI _tѷA uNEQVfGOp~KLv/ FOcONljkJKE8\iR2AIR/ J7x Q̾<)/_uё/}֎?e7@D$ ;MƗ|T.Uş*X'=fg6{Ӯ Xb֬.rJz/U/t?ѣ`8s+_>vl.3ZTn‡=K ?^Bt8Ƶ[<ϭxO/N! 0a'K yR _9<Mm/l!17͢8|.c)kOb,wE+6 xȥ6<^ b*o{ ?ZuI;*~'ISf 6oR̰}s"WgΕp}ڑ._kG䓜ʹ;w (etəkйi$w򾗀Pvˮ`Ӧ#G.\m 33we5yk蕛!}pѣ@;tUox? :I?=`0߸;;wٕ*p2>? 'N]˗_?&~;;vb;UK8(;;=_VVQѕ ʬ WG *OFlH_]>ڬz 6qX/Q:pʞ VaT|tCW|C|Ot' ލ DH~}Q| ^kǬ 2h*^vuu| O|t.1;DW>+Wpؐ"F:2RwkF:UcT;!;kt#\ҋ^zknu RYȰS|aw?Q|s'BȠޟg2z!'(!CP{90ƀ,:vþVKzpnhþQXTt(-m'jqt8n;~-Ň|ogt=/z&ed 綶1}/# 9NBY⋩&YgEé/t,=s>ƍx8|}Y|,^)f WC |p KQ x HOjiX9yr^^vv]tP~ qB"%y = ^GHp7󙦷禂+/M)\ *\xãˏph>]W鋪zR|Sy;t ɏ Np/o*|ş?8txW?$ й#_]L (+޳&:YA>7jaH@~k>N³F_G~jA usW*oU$@ Qh8&Dq\Ʉs|̇LD Qq ?۷76bN:Utdwvu<3کro1`Y]%h:jTxtl9ϷԂ'9z>C:`#{5={w֕k(D)Gjbz#|IhItr"J ߥvTxXFrQ"KKKKK QZ09't\wwɼǿ6vcR=Wlm]vus|ZZ?=nvɇկel\_r嗳..2kl'/^x!|Ns7\ZnB󶶲2`˻7)*CDv]EOkc8w/wtuutPr PZRBnU`.Fٰ+K❁hVL(B߅X5q|)ʁߢpӧSQLʂϏ}l ٿA]JΞ00a@Cy G~YN'Np/,|M (W >4!a}z av~K/tq`Oc.ū'CO'2hйܕNt-x^ \x\YRlYwÏ=55ʙ3Y _XT+ɠ/8 х֗Qo~}&&;:pTx[U}# $1 y筷\wJH@B>{oyK<#}̜Y_ςn;K)wZT-{wvA0iiGSq赴<}AWt\Kh-ˀgv`aG{_OZU>.0=Z]}%r kUWq7t'K?NBF˦tz谱q$vwvC,I)5RI|6 )HC6i tG踊s":ʓ#6SRRVVv:1qzuy9/)+§L!^gS8GN| Vẅ́kFFv]|?aN 'bTh )tnZ>N_EOp<8,x:DG W|*_锟/|0=+CI@K(t 1(=фU  йB A:RW |ݗZ*|%/<1L.]NO@:"·vp.<8O': g,ǩTTde1پTb'b)99 tu1!iS]ݶmAm0`={^}a o۰!񞟟x|av)ҨOO?^߾=~={}׮vi>}w 1߅G 9C46vwkajH\;SՃdi^oX/`$30}*ٳqMWJJ> :Aĉb4@IDATx#G?qKmީO'X'``^O;wp[A XF0O?Uk0U!sz窃ʢBLRR@&˳xg^JxeeY;^nW%p$CYQXHCTq`L<'Xi϶/^ij`cW`W.ɟ_N/&XSY9w>=y.>vH@;W9й|J%Xc',pQxN.<|(WH,z,?:^ ؎vV;ŏLzڱ3tAb.y8o?d -Yc(~-׷b&Aq{17t/}̀aıGkܽx†Ѡu>hHnځ,я}&.M-IOqpTWq5؜9ee/<933/2ig:;sn&vww 0Χq~p0'/~{iG/zJ@j7={ (+aGgwC[Np2>D/ qx1P`1c Rv;*g4OƧhԉI̐`+/_:6e ;[uWQK9:O=a3C6_Օc\MpX Qk ~<\-9KP0DO ..[YI.Zh z}/////є+_hc֭6< y{zlp0'q99={Ko{RzDFz6RttjB:tkn9.!jƝ vK]ϛWVƄGeeA&$R溄BFR-E&c'8΢4XR9 ))ʦMc ]o~E¤yhDra/!MXpgz]XX\ 5@:A"^\-8,|ſpa}aV,IOEL,o g9\|s%}թɏ@x>񂕿^G<\|ᧂt$WΕ KC&?Z EBV:gZOd wFʰ;Uϕ.ND/2@+C Z@#~r/3K>q:&S{Xzҟ\)K99LLQxa cpEEv6;K/eua%,cbΑ#_b|7I;Տ;1sm3{5,0ǎFv8΄c:;1|9K'Uc]5.B-W_ڝhAlVf44?΄K{{[+ lFS0\ WPq C?Al4C;:LOQ&QA.d`mKPg̘:Z|-K.]wDR{t8P,;BG\ScΝ\-ɀvm+K߱b!]1H#jY-ww =ޮ-ZĂѴ FM}EyE'Mi"{_xС xZmm}}(s M*p^+GB1pk<;;'cCѢSI7/^L2Z! PI ƸdXRgqge͐ۼ Y32 5ScO 9ߓCn .7+;#VTж-t9NA?`AйǕhv(/ %+]"3ex.)~ 誇qZ 0o9CCΩTx8y h%?^"UW= ꯽fwo]:;&2ԧT_)g“cmΜ X`Ѣ X / 76rƍܙ <5y,_`AuWe7r"pDŽHNc[{ha|ׂCҍwkͻhvkP@*{lj%pP}{YnÆ ؼe8s ⅯpɏMvP`pp~gX_`8ʟ8] S[qͧb*2ͷfd?^_a}nN0lkޘǎIhk,F__ffv6Az1wwOL?aN\)+c\+fF@_O?]W|o@?7*^8xݣ԰]ww?>28bXWb2[8+.4Yc`gu~vڔ3,Aظf 8ԄO l~/D"@ q=mSh+5#:^_m큝J DT!@z =@ys+f_=yކ% phG>H/i>zzC;}^5AeoD)z@NDgt6x>Am?yg[o/͖+kx @B/D"07j~U[?qQ|:T"@ DhG;<@oV'Z=Ȫzxf8f^;N3BOUWg{B?U,5'RNϼ"@D@JzUJWuӳ, D"}Sֈ"@ )EIfD ЁQ D"@ D"Yu` D"@ D"@ D"@ D:g !"@ D"@ D"@ D"@B@Y"@ D"@ D"@ D"@5tB2 D"@ D"@ D"@ D -D"@ D"@ D"@ D"0k@5d@ D"@ D"@ D"@  :[d D"@ D"@ D"@ D`Ё>kɀ, ^W3W" ^ye.h/<(G ݲfD"Dz>"@ D"@HbNp> 722115y䚓x-[yEEMg{> jnq>}6mz{=oڒ쭅vy32.Zx&멩/ŋ7@ Q[U^G2ˣGn]yCCKILHڨpLPXf-;;U۷kWm-?,S0nmqbg ƍ;wB[W^qLD|u]2qM^D<T;"֬Cl? $jD月= hgeA D@igh5.M&I$}dS8.]|&&d1׶mv>&W3ûYp >~ xzRa6˳gܙ;+/C۶ZySS}}\Gi6@ד_xLŋR8ó7n\vb^{LMpWTnQtQ|[[{{}7o^\LOÁ^V.;~B;Z:,hڸ qrs9"MtmKvJ_W^Ă3g~*-7>Ov8_/~˗HnP'Z`b~llr!B6:Sa7mܸm[Cխ[WY)_yw'j;ddԎ"~Я8smG8fFF<˗ON;w( 2[Zn޼xS-HYXowI^}# yOK;"0HQ ; ΜuXiӞ=sov|cF}_~/[,JJ?;!׮agٺ GbG9vj O1zիڮ\ë"+U]:ypﹹyy~998С?~!;w݋ ۷cGy}+Wbg K>EGV1ӟ<#c ƬXQ[A~su i>#&;v:Cⓟv뤭r>}bKzR}x\kS.] 8/q]m.YzwB݀P]zʝL@GG05:xa! >䂫嘞VNhm_* ~Wcɒ,h'L@G Z`_d`iW' 8|Opŋ_}ʑaWm^] 9v?nRbѢ z]•+ܹn]tc)x:\71"]"8gXe(Sn/󆚟}ٷz\G=/_t}E D/^O%[蹕]575 L=`U28"@@#6.\F0`!>|9ӧy;0o;䆧SYtȧ?&ɩlh蘔3CM͞=ZY87iBLڵiŋ--p*xo |m?ŋA8.\>t&%KJK2eL9F3 i{pdmm˖UVb@|n[`;})(=tGWXQXob0::n߾zGwt`:^;ǎuc[8$S_~zj&/ptίhz8,ol4waՙpz?h P fm[̯4Hহ'!۽7GGHIW;JdʡN LMeUU6ʾ}H#KIɲeHNΒ% }}8ʕױ*^{^x|L;Llmڴb5l~87^=Eˑ]uuHw#…?/ǢkNP3*ģܿ8"=oC?"w_Jwu:QVzu]9h&dGCMͶm .ϟ׻okͰ0>P2"{ܤ5 $w?bS2!pE{6`E-v{ʎnkSv4~r߾g:% p\s-[,^zG瓨L}k!d[JȻ.Q}{?bc.ƟXack֠=]z{ޓ'obU6ݻ1n kM]~@3l^3N|ۥK?ia9ԴqcǍ~`CA}Qy=x9=_5>岯zK 8.(A=VT>|&mL-/{^0T3i9/8rd]z t()Yѣa2R]N<}K?}iqWw׮={v킝"2y"$bcT{aÆ:,Yتfcy&lp|.ѹGv ,ՊpZ%5gWVןq83'&>qeC"4b%hqp5&09{pxڤPо}[85(֛710r`/q}LJa8Nx; `gQQ)L޺uhj„Zmo^{zC}4eeзhh]G vK> ^.ǹj?Q!G`cc8R9qǮ.8שc=/O;p9t&Ai, GvݻԻa}֣Gw@/޾ՅQ~XO(q1.xqի}0ׯ< ǹ.+~…{L=ӧݎs}%Lж߻YW }}_q@pܤ\c' >Igϰi\ ͛0~-- )^޺r%8_tσ]c"W}mx\֯#wmߧ[^B8qe?z> L9^>miB{{ ?A[_꟎__ٳ(1>c>pU/&AzM5>ݻwqųg?uI,$Tts-".]t3A`C$p~)?,'6?]I<`,(/&cDi>"@:_l=Oe?*}MwRz l&`8 { $G%D]fi8ش0C:`Ot9x+ W.);@1܅ N2L޹s>݇mQW(=xLĔ-Y#Gc`z:V޽Ӄ|==p?{7XaN`FAY;vܺz@'1ywaBɓfVUUǏcA+V| 7b&ֆ fG`|2s02p9s͛je(&|:;7Q%xͧ{uzr[E8@Չ`͛j!ƭ[pTXJr|<zDir+MMGBZ+X x|{ M(7mX`\#i{W1}ȵ'Olݺ~=0hNF至gϝ|=w̺3?ݡ?r7w;^\ȥwk{"e'm,p }UV¡!##\Ίɰ(9zjdmmJtn:<ׯ:Y~'lٲf)?5crb; };q~pnMO`"{lf;7uʞk.;bLY %L,=y\WXXYGۇp!t !أ׮}=&껺$ *+Qb *ɓ+qt7'׭~R'3 v:k`h;$._;8ǩ>g&&P|]fZثZ+ "&{0q1[]~Ԏ}, sG{s[߿9z4 @ d{wC.˗O^OM&Zs3g6l@[QT@NԧanP tpC=pr a~ӻkװ`>5X`y/.Dҏ {Ϟ5zS6FFpa}ᅬkaZ ~k0; ݐƪ*[8)oǸJ;{۶Y8{o'|tk]sٯ&DvR'j۾OfYoW{m mj!3y8&+WGGө2ݧz',lC?^;ᇰk U:D+\<=th~33=+W~`Aݻ쩮6ힹn~;aq{zka9s<8q؃z3XL̇~ >9|QE8pOϜ۝:sM+ D`>"ǁc>w1ba\ZTwDvL-dW' ʎKt|6Ȯ0T1-nNLO aFD8(ۊ0wް4x?ok jE恫r> F8z+&.3^ I;Jo0ՅouǑ{nۆD=߀CwwtNȅT_ #-\(wp瑷GcsͿ ߾ڱ;B=\W0u"Bv$ nlġG>z҂ƥKʁ}wH–dCC\6AǹY*bE\Dۻ]DqDAǹt:^O`qNM a}k]޽p؎sի7o: CwJфḴ[*,C;w9r/9r ,Q|SLX<{t; y͚ )/_Z-Gwwӧa'@n ?Pg8;xttC?:`]6xFWףGXw'O:: @R1|HܡogaxB>Me\"8kkwR:E]n8*1^[qqe% ;wndB65/ژލK\SZ 7;v *իq!Kvp~ }ǹ柗WX1Ϧ|t8B=:+6l:};`A0&h?V'{ܻiGA{f> cX;j<X Os]nkjGE kȕpJu5ZLO,iiQ t-cWR2e_x-CdN:LK{Â2|q0GkcB0|zgCCwcc3o^z)u?&lB,<}l}ftfJ~!\c~2j+s>Q^W?uM=LxofQ2]N].^ ~d:D_|c+]׭++37,iz^ ۟>8w&`>xBo3lnX q|.hމNkװDV'Hp%c%X=&•vVctmx`B 24M3=&t-C~:MٳMNN~>#o&4+t]|=UUk֘;cʹ\u4ri/Xm{p nWaoD)Iˉ6' I{՘P~h0~ KΜu͚[1RX]jCUVNuθ0W(U~i5kJp.)!4'}}pglݺJ4mص%@U9H0J99yyhGz}_?}`P¡VU{p, ,Qi&Zv|kyD3:_dw޽!sXXwB-ә}p5[ 'TW/_yzGGk+d }w z5= ;7lhh1Źw6nTG_,4మQ;廻ع:`-^)'5ڵUUkQ*tJuPKMvQkCB_֪?,,PWxlUa vhn?+sѭYP^^Uyaٖ_It]NݑAOpo[8 a&t܄s O ޠg6mBǏI5؉E|%}ew䂓'@giiNCg25~uߧ˾ϔDv~XDpd?BB G-իLNǤ?6qٹs.a8{ʹn4 ~ vjCU)=r>O*wq{3i<2QNJ'`kLL1͛qMi)dhRS.HBW"@B>ņO$WBjsE=θk5{ods+jBq0al KL`bqhhtTONa fTLڇhgMW@./Ghj^ȞӧUǧ؁/ [|ݜLu3?-PGM1oLu`?b*xU;J˖aG_MM}661= pjO77ۭp| vbNꉟX:d`/隤3 ,v?zbR-l49kj0YG6 Cs:\WO￿u+vKh&Vk\M^ X_We AuUew\ǗV:5dNO4? &R}-,An\ LZؑ *;Rҥ+i(/o&:]+-]|=};0Eşપ ?]G㋛8%цRw_>y8*Q곯yOV\L++`5?e{Xb?ʡ޽ك(j?^>nRr`gR-Yt&iڃ^;w~ 0n'/.7?]Lin9%jϾόD-oA]0&'80/nKl VkݾwVOVO`\?]mDzmҀ~^Rϣʛ*D+K<|gT9W7z { ?p=y+7lB-[Qrx"@|A-ߊ'a.lxF#ǎmbgwLm?qdj`0<:t07!_QQ+:KJ֮_|}_(Ο??s*Grpx+V\Y~/^QI~.8XRM,Ga?:{bSg^#3p= KL|/[r8BQ/M9oab=vH&n a`[VVQ ݻTUG2ro6C@hOd; CE,_AqۿX0\lYIQ<;|'0eر/8!o_Mɕɓ۷ n6lG[~amyg iQWߎnЯZUWޓ4U8)cÆ@}\yrG/]{ؑT^k|3,8yYdŋ 0P,..('\/~qG&a#vB&|+Ɂ>ڝTc"hfSx=JR3>RqtmyF|9ZΝ^/++-fx`x~'`7+tI/[T)>;saߧK^2I'ٗr>s@cѣ NabN|b v nmݿ f-}7 gqֶQN0&? N^‘8#\,E[2]q'NU$#R' D `~Zo믯^ 6BG[%*wGa7_Ɓ,(XL;ьk{ӧkl\FH33tt@IDAT!Ҟˋ=;v45HLos#,ZCG1A~kn܈oX66VU;_BNGs|S;mS; n/߹_Sw-Qڎ>ő8*lܸeKu(OCIʕ؉p-9='ze;J }aaIWS|G xzn)PizwK>QjP8>qbƭ[1+8H耽 7._B]70vc qLVmތO;ܹf ogNΔ[Ξ愅>b $**cfZudVTO>Ďb}D;`rrR']v IRuϟ̄t;"]n&,xE0p,gc#4>~ޚ"x} _> <>0QzuXhXV71a/2xO qpj}`ty@&\x'|ѱcA#߸s5T|I;J^=͕~Ȗe9_(i:}`ssq&-zҥ˗y1ݭ'у%>A3mmWMi&P++KKLo06K4 43C8U}30lx4'W^3OEYʙs;0x޻8wڎw;;;`ioWG7T~/_ݮ+* 2.b; 8TSa֭;w '{y#5o'w0M0mm٥KA} իqc;я vvkNpZ;{ꂹ7QxvLE&LG:Ԅ#…3gC/[Ork˖;1q/hMoCصG74l pHq0+=Nt>~|6&V޾NOOL`6msQQe7TL޼yhw=kTT !B<ǪU!ikk+RH6z Q,[Z&'aG{p;ֆ_D;zn905RӧqhKuTKK{ >mmŧ š`zXMx$ϑ;1(x8,7Vh`Y[yߏy:3~ 7 U:[|R;3%/=2RYKFb'amG4KΝ65k^zWc]]B){ud=89^3I+wܾ {UVTe>O 33͠=󵽽j^KsckѓXWSs'>SU<VqxMc"@|@(^lQFy]~Ku޻X>sut d0!}{c#4&$^=uꫯ8Ątۃ_o¡NHh޼L5U^^q1&mѣ701ݍ 1 D&؆0rԙ3p߻`&਩&\z'{}U2;{ NL}"DQKիΕ+nݽ>X.ɖR?{4wddr &t^{toϞߥlmGeeXQk^| w{;&H'&!vyO&ORi:reyy<LJMh6DM**LxO&hЏtu@tw o_M oO?G>j>mL;4/\8u/=8Ru޽ؑg;(l:Xoo[ypy͛--h1551ݿߙ;Cߺ5{o8aݻi8| (>M\Sܳ犉JGcGk \%/*Z;]3A4;l̙'/^đbU;yrtNqd5az+a-]vP);؝[{ǘd_޺f'[B~~Yu悀>sv5@WX<}t>!=P_{_pП:ukؑC};;tܹxv7o"{ozA?aɍ^n9^ 7׮XLlku ;._ni1̻T!Cno> /!XxusffSv{:D44܉Yee+Voݺri/>y f)MoH/2c߾τ-̞&Knq(+ Q pN 'ambvgg 0.`^k^xo?}>qiWb&._Y,dR-:r&?{n ^PF9| hnyz{ٳ8 ts 158ݍ,[ޗ-[퇁" ?s3ܽ٩7;4\~OMWrh?}KPZ~= {'g&߇P--7obW+nb"3pm{bTOϙ3ѓeׯc WW0xwƎ3ֿ $IJJ7/koTpJLl۶z5& 遃ˡ!8Ξoݒ%W㛆jU%ؿs=CaabZM$C8@;O+\S*tR*%( rr @߿NL|9s㆖pkWk"|kjpM$kGkkZKo~fy7nlj:p_,V5} ƍWM%0 ''nS:lݾ{r%N]Gpw2y[`O,-UGW߻+J; ypiI4kvmc#t;߽ 6vZ16R,$5sĴ:,װG:;0ʕ55q-χczÆJ8QG cA8Raƍp?|I/Ϝ+,H4=_qrb*7HS]~"H?X~ok;0AvKX] 6RyN6/eÇ'6 Bw{pp뫊 ہ-;ūWoݺz5&v<==4+O}$~X.K+y;Ǹɓ\.0ݙ:ɤn}б9b'?AЀ#˗l\ K͍[GG9 '=Ϸ0H?)654('~3'8>%TE^VLđav8] 2͛, MwjGdO| +uJ~mkqte2|͛a} 74=zyUWgQ][Wꛜ~~=@?zupu^\Qy_\lƫ >,@ke _d2{`WQ˛ou+[. 4L_%pq>?㐃 =IUTG?zݺp?vpC-'a@޸?6fGþ/,_G6oV @4Ϸ\gkYlM۰aN3؁]@iӎh?ǎkyq.]7&wI_kjڹڏ'D1/Oe_ۻ0yODw{L8uIhm8c܀]B^:@lg̢ؿ]:dyڙ}M6\ރt>8{(IrpOr`: ݻu+%Wڟ؀`<&/iѿMk\ G /L*ۧ.1lM L3i۳aͰrš.'?G:{7@Xg(ʕ6lyǏ} 5޼"@0; ]y3`A\g'awk5\_ us]\?6NLa䤽xf/T!{zz;5ul*Tn/fVE 4gp2S]x4v Q߹*]R>'@#|(4AuX qۑ\:ѯqu74Laaq1SSǷ;@SWwG1ܩfs>o`Kܔ2{r~<*I^[qvt2%CE옂d&G9Gކ~eoV_h;XᦑSv0p9991PJBY꽀BېW>Z>@_uWZ]tدC>ui>x.'S^RH8+!}Hgt#ߨ?z\$|C'Zly&tszZ[Gþ Swr"@f|.Н_KŜ D`!|>ȣQ}8MGVt1k8^[ L>'ZUX8['BÜ,T{,1H:nj D"@ D"@mám0X{"@x28}˖)~r[{/صfv$}}Ϟ㘨[v8"qn"{"@ D"@ D"@ ُwg;b  D =|Ν~掎^y]c56nقo;HI@Ͼw)܁.EtD"@ D"@PЁNI D"hC}h&)Mŋ_Ʒ_7JJJgyyq$t/JЁxV"@ D"@H4t! D,d--(𼲲 \ D"@ D"@ D@¯"kH D"@ D"@ D"@ DnHA D"@ D"@ D"@ t/U$D"@ D"@ D"@ D7t1" D"@ D"@ D"@ DЁdV"@ D"@ D"@ D"@ЁƈD"@ D"@ D"@ D" @;YE"@ D"@ D"@ D"@p#@#R"@ D"@ D"@ D"@;͛w" D"@ D"@ D"@ DHr=2 D"@ D"@ D"@ D"@  ;߼ mH&RRDK&+c! UJX'+c! UJX|R\t?1J˝/UJGC@.)R\t?1J˝/UJGC@.)R\t?1J˝/UJGC@.)R\t?1J˝/UJGC@.)R\t*Nte 2 Y/Q:?IjQRRRRRI]*/_cw.ߔ9УzcIgX(n C-6͇-7aƹPbQ& 87_,j?܄CE疛0b|(QrZl/쑿b+ ;>_ܘ?⟘$QT[J 5 ??ԿH'A$&5A RP$&I???Կi ?A8iN'MbG:Ѓ쟒7Hg780d b (d>???d&DRO>uIaOb 2Rp7cԈ"@ D"@ D"@ D"@H9՞߹ң~'wwKswGƻҥDѹңJ]]|\]Q|.ti>Qt.(xW4(:Wz_i+]O+=4ߕ.'ߕWJEJ+wwKswGƻҥDѹңJ]]|\]Q|.ti>Qt.(xW4(:Wz_i+]O+=4ߕ.'ߕWJEJ+wwKswGƻҥDѹңJ]]|\]Q|.ti>Qt.(xW4(:Wz_i+]O+=4ߕ.'ߕWJEJ+wwKswGƻҥDѹңJ]]|\]Q|.ti>Qt.(xW4(:Wz_i+]O+=4ߕ.'t{AHqB)l?ԿJ??iXI'Oڟ<Wڟ?iI3MwP+IgT7H_f ??+DҫmgOocShS2)u~^?`D&ڔLJ6;?6/RE?MdOMˤyQ(@ocShG>=W,^8dM䈿Zidb{ʟ/tS.wgbgtSSPP-"?&???fH=0"}Oc"LCCctS/4@5dPE&g6"a_&'6v?,!?N6?_Ф%D&Wl2ltVa> 'YSPPح|+N&[,kW?2LdqOCCc Y֮d8n泬]Q(BLh]|%30?o?zG(ٻp???~ >BޅOJ./ߦPw|6E'| KCC)p=#]8_l??:Ѓ'V0E;1S}G)l5?.EMCCl3\%'f=Q4J=5O{qi z.j`D(A\???6ǥQ蹨Q,;o'"@ D"@ D"@ D"@  wGU-ѕ4Q|x)_),+sgKJlg)_)+?;]WJgw=KJ\RR:YWJN]RR:W~vzҹӥ|t6׳Ε.+|tt)_),+sgKJlg)_)+?;]WJgw=KJ\RR:YWJN]RR:W~vzҹӥ|t6׳Ε.+|tt)_),+sgKJlg)_)+?;]WJgw=KJ\RR:YWJN]RR:W~vzҹӥ|t6׳Ε.+|tt)_),+sgKJlg)_)+?;]WJgw=KJ\RR:YWJN]RR:W~vzҹӥ|t6׳.e;uFન4#u!/GCKQC~E?c/__rb/_3́)vM:??7j(??L5EAKKKKK c?29a?~;?r#p+@fgߔS(@/sm̉OA3"s?`\3sbShPL=2̜?S*2wO̵93'?ϔS(@/sm)_V";C;7_ƹX&WqP|,;|~n~CcQuMjqn>X(n C-6͇-7aƹPbQ& 87_,j?܄CE疛0b|(QrZl/5ʟ[nPsŢFsMjqn>X|܁Ev%+XB$ʟjKD_?hĤ&h}43{fS$(_ڿiXO/HLjhE/_mE:Ѓ|OwO?_rQ*/ O*I|B$ڇ////oR?a3EFd́k篈 D"@ D"@ D"@ DdD+~J4ߕ.'ߕWJEJ+wwKswGƻҥDѹңJ]]|\]Q|.ti>Qt.(xW4(:Wz_i+]O+=4ߕ.'ߕWJEJ+wwKswGƻҥDѹңJ]]|\]Q|.ti>Qt.(xW4(:Wz_i+]O+=4ߕ.'ߕWJEJ+wwKswGƻҥDѹңJ]]|\]Q|.ti>Qt.(xW4(:Wz_i+]O+=4ߕ.'ߕWJEJ+wwKswGƻҥDѹңJ]]|\]Q|.ti>Qt.(xW4(:;uB8x!0 v QP*=A>b'Oڟ?iT\iI'`OK7oAj['Oڟ۟?:S }~1;IQ??MdOMˤyQ(@ocShS2)u~^?`D&ڔLJ6;?6/RE?MdOK_}zK|7#jIz)&?ʟMH=۟-۟"}Oc"LCCctSSPP-"?&???fH=0"}Oc"O]ּUCGۈ=Q#ldRd8R(Aʖ\pecc[,kW?2LdqOCCc Y֮d8n泬]Qp2=*gYd"{UϲvEp q3u5C$v"??ePIdRPPm'\O%{ΗoS?zG(ٻp???~ >BޅOJ./ߦPw|Q@XXZN2S<|\R4jC5O{qi z.j`D(A\???6ǥQ蹨m&KsQSPPL'F 碦Fm : D"@ D"@ D"@ D,<~܁UDWD|t6׳Ε.+|tt)_),+sgKJlg)_)+?;]WJgw=KJ\RR:YWJN]RR:W~vzҹӥ|t6׳Ε.+|tt)_),+sgKJlg)_)+?;]WJgw=KJ\RR:YWJN]RR:W~vzҹӥ|t6׳Ε.+|tt)_),+sgKJlg)_)+?;]WJgw=KJ\RR:YWJN]RR:W~vzҹӥ|t6׳Ε.+|tt)_),+sgKJlg)_)+?;]WJgw=KJ\RR:YWJN]R.@%zԂt<'vԅT\t?/o"G i:?aGk/_e~Uʉ/_d2zTGԺjQ4gVܨϣQ2i/////+d?\?BY]P暝WhS*2wO̵93'?ϔS(@/sm̉OA3"s?`\3sbShPL=2̜?S*2wO̵93n9X!hۿ||~n~CcQS;㷛_ Crƻs!aq UlE-7aƹPbQ& 87_,j?܄CE疛0b|(QrZl/5ʟ[nPsŢFsMjqn>X(n C-6͇-7aƹPbQ/rGCqدln` OL(-%__?i$֓a h `Oj6iKob= _@ 1D/_he@?%!>op`vH?P(D>???d$?@' ThԿԿԿԿԿNHIa%IO1zr"D"@ D"@ D"@ D"rFd+=wxW4(:Wz_i+]O+=4ߕ.'ߕWJEJ+wwKswGƻҥDѹңJ]]|\]Q|.ti>Qt.(xW4(:Wz_i+]O+=4ߕ.'ߕWJEJ+wwKswGƻҥDѹңJ]]|\]Q|.ti>Qt.(xW4(:Wz_i+]O+=4ߕ.'ߕWJEJ+wwKswGƻҥDѹңJ]]|\]Q|.ti>Qt.(xW4(:Wz_i+]O+=4ߕ.'ߕWJEJ+wwKswGƻҥDѹңJ]]|l?@ GL<@'ğGC@za_5ڟ?iSqI'Oڟ?=-oߴiBmQڟ?i&n@OueB$ F&P6;?6/RE?MdOMˤyQ(@ocShS2)u~^?`D&ڔLJ6;?vz/#.IDF&&齧R(K7"}l&lllfH=0"}Oc"LCCctSSPP-"?&???fH=B?!tYJV Qd‹{o#LerbcGȲJdKM*[Bdr'Fn泬]Qp2=*gYd"{UϲvE#D???v0eG,Va>)ā.^̄ W=ËSA'} KCC)p=#]8_MQ dRPPm'\O%{ΗoS?zG(ٻp???~ >BޅF=zb #\cic |8Mqwğ[JҨA ]???6ǥQ蹨m&KsQSPPL'F 碦 xO?.DEMCCl3\%'Ϣ!6(|.ԻŋgUٗ+oj+ TuԲH}iȁ"@BD`meO.ZND"@/''1}}?\D"@ D;УJ(>vzҹӥ|э{ޙ3y;|<_s?Im{i_y>|옪Þ}:rHq._JaiRo޼|y_3׿._~$,N?(e<b2\8%=h|t?1J˝_n!쉿 Ӟ8r?SeOz[K5'=ɓxG寯u3A\3V~=cWot)_),WJWJsIJdTRR:NWJ'էuөw"*ίNWJ'էewRR:Y>,sǽG|AظWO+;)^|t|5N^|t|5N^|t|5N^|t|5N^|t|5N^|t|5N^|t|5N^|t|5N^|t|5N^|t|5N^|t|5N^|t|5N^|t|5ιHztHJjl_|_޺uW}-]^?>yo~|g.yv'6~y ΋?>D'r_Ů]aC~>S]/)tѢaϿ+;uJϞ~˦.}GI\J0ﻢ[[ '?'z{o<7oOVy;q=KΥ?soO2O*̂KlQto{`SlfOcIJCܹ?=_ZjJ+ kx^]X('ǏchCCUUy먻wɡ!K/_ r-{W1u Bnq?(-кuy_erdy1Tх Ƿ{o/_*lj9^R"#ğ'I^ t끔%*1lPm5r9x*:g;΄uӅi` R͛=bġvv ׿\20 55?)vG%Qר|w_x?>ZMGŋRX[ʯ(-*ݯET>E(H٦1pptf+u)S痕ų'֬=aŋlsQiyܼ`rF}h&,`if X{P['5ڿ|]9oo_^ ٟ6'ӿ׿i²@IDATk6GY.e 8|QF"ulLo[fB<޼B#89sUo__&6\5:36x^y9>Ts1 |ؚďǶ2}wPQQŶ??HEvdcJ 賟XH~/*jwaj6:>ZVUy޿v^52oUVbѹ!GY[WϱQGVV׿硎Q;:;O3E/1;x^fWN?Ϧ`׹TwQ #Ns 'ZR\s >!/b In>?HN?{oUq퍟H -\QDAB{ j`ŊHjZDD(RA+ J{ V[ DDAA03y>k%gfߞٳgBwMSO͎7?JّFR/T?聡ڟ*qyέV!5hЪUIҽB|7C>OZ:A}3/t{>3mUU*Uz:>~}PxN֡ //=&_v:ՃcS˵i$_ȇ}u$OD7>xj Z nnِeNm䢴Tb!M┟aÕ.R|Q#ҧ[#|f-ᚨNxjرIrMÇn˻ᆑ#䥗V~$qbyQ9;׬m 2sZ>٧G88ɷt&TZ}ϭ:+[QKJ邡C3ٹCrB1?~v`z&w竫* n\4G֚n|ZK8bivZ:KkM쏷i<_ZkbNqZWXSl(@„pQt?:TQvسO.ֵl٪H $qޝ;oO}Zl۹3I^{Wz+I>`Ϟ]>Z>NKvZrMݻ_{m$ٶmn3&M;S۷zs>Q(~$ٽ{v;M`}慅TGgώ+I÷lٶCPY}%IÆ/pQoV-Z`%CJ?xPν{Q}l<ƒ$\۷o䣏W++ 6oFG%0m^y7?/naycڡNtC]I믽Ig<ַp$pǎ 6n]/'?T.?PQAA(h=olڄrكO,@K(o.~IҬG~D@yQ{~wɝ:QIJ;P=ƍ菛6mۆO8SX]?ۦI'%I'wזCs;vly}),Ic4nܲ 'ŖSOm'A^HzˇaXvO٬)rdN>c7?-+^#N-rA߾}F?ϕ$V}SċΝO<+WUUE_>`^~{<ᄢ"tߨkp(Ν7bG#EǩAd&룣nݸ7a/ٺ.ڵ _>p?B>ao~NZV Z1wX+5 r0jhs"G@_`\|}|Cq)~|A{ڵu+ݼy&#(hIc;t0[c>G:_\I8_-/M^߸u 00Kt\.BꙜw&ƍOh{i^Jt'N*K'=a~VW㏯|(l|y[SuYomۦeaϷ1yrOLc! G#fhl,ǯ_]>ߵ~x'_Oʤ7Hi~*/Rǎx@k}#򡁰C/ q}/##o?e!5<=laLMݚPVYM:[o=$W\1s&R޵k{wݕ$: >o$9a׳'v{JKÅ.*_}5L<Gu%EH{Q8gάYx@عs+ţ tؾ}7"^QѰa $ɢE_"xT nI|_^=]֧çXjѢK.<޴Gh3e7݄reHWB~u^ /,̽f%ɵNxqLcdY~$رE z̭S@fFʏ/y.F' ؏# -t}\9b{ ]KKa]qoWx4z}wOxERm6lx$/'Ourֵ+ڷ즛3loH;vb̞=~b^>8³t֬nvE‘Xkذ{}$Nj;TQpqƌYh$ׯHy>wqkȐ/>{=zu4&zcl2TL%iǎ_:rKB6ltѶK/_k 5~[=9 ԋMuG몽q$7n\_{(?(* 5y1h/mh-^B)x#Ix7x|]e4e %޴~*ywetb1RJ`u|k .bQ[{ȗ^z#;[ڻ7sϘϝpQG z:pUNZVdm`^}-s`r\cΫ׮6 M\qm[0 |*~Gv .~QOGFUh[+W07t*ȑwމyŀgW@TN'eϷߎ/%gZ|e>ܯߍ7Ÿb q̙}y> vZ)pl';w۟Otn׿'0,Zŋ㯺 [̨9sΝ}gUċB˗qfnxq1 \_uoWTTZhxQc JnM[MN9RNt ߵ>Lñ?|DO[tnNԸqJ 4d΅]bJ,W] ^[>a{ԕ|(Nu\4B ~6/C" rZTtΉ t_v<<կT_,Byҥx0ڧp:nCu`)&5.1th|˅m hɓpNp>}~[B(ݭI&nFiiDW~Rx/l/jkx䓛6eq* .fᜤ];wn\qpNXԫڲ)zOMif D ;1xk9kSC5\PD\zN'O_\PIKɜ'tW8zT2sptc"sf3Oˏ)N'\gnj<5kHr, ŋɼ wYzJg/gbX ́|A߰!`1ci7N1BϙC lXvx9׿^}p#SQoګzϜKz'ҥƩz VEU(]F 4v=}y@|ժ%KSژsr^v;{Tyl꫱#2,?-0x5I?fן]ڥp˭ϧnNrl:fS̙{e-g?Î/\RB(e.'7K<sB r.{a_9+Va菫WX;D157nܭ5ӵGKj,ô' #vycyIĉ@udE$ ! [/8iG֯weSGžya!z VnL>ؼgWQѐ! x3gb{a%qgqngMELK/3y2ZM_^ HR{-Z=.!8uM͙ϥeh$IѺu;v5&={,Ï|3=S<93-w1s9/Js2s^9 Շh^{=cdc,Sw,$S]6ՎQ3cL3={ZO+W(q~{ha{0UN%DD./N89|\sts\R>Nw*qprs=W脓ʥ|>G'\>GU.9:z9zr) ';sK8|N89|\sts\R>Nw*qprs=W脓r%h% G׸v_GOBH']jrt3/!9=z}}8No #8(|[FBLQѠAx05a7amXh3qgnT|B 񨨠BttCJ& \8 GU_G|~T} QoܥEiذQ#^Q͉,Tc-K. |n 9IڴGOulٹ.u! h+V=MCx=`58 B}ڶ`v,Erν>켡EtJ'>}VЌM!<}.XpoYcAy>o} lt&,^ݭ[۶Gިw15w…fsΟ#7A':Ⱦ: Du ZPШiߧ/#GK/{>8|X_6w_rCdI+ ;N;cȁ:—kC{?avEv9mzF DWc)Eݑn>|l|G3f,]`9 m os qc |,6eeJH!CfB}R~cVrYI2f z~˥oܨEQw~%O}Q׳o_<8?P6+,lvQGakcŊ_m!|h$m^^;tG()c^[+Mںާ*(@  J'> vsN('A/-Zk׭[5uĉ*zo!XY r\??Gҹ7T/n%%ŧ/jZ(?X*;LWvUUy9^`ؾZvQ{S/3=]cI<ګ}{^IT**B{pôiXpˁڵ+zzj g^]K'Zȯ_}uԫѲ2X_wW`kPAHGHt:{U?vy̒pGG7ftԼDͷ?f]4ϙCcJW5r9Xp:WN?V?ayk> YNիg|s*څPө~S귾uSE*پ_&U>W1|G=.!~Qq]bǧKlډ)W8qҥeeG;il~FzP ̞tŋyf& *ёJz199ƿK.6gnW xqN |/$\rIm^xz>|}sEBs<$<$3 I ym.m[4mժeK\E!ԣIa"8;=[7ȿV}!~}֬իvUT_}5c`Vj0/$w e ~<覫>:iR<̳է?.fM(׊(^)>=+.6 68lqAOMn}ZO2tUU_߉|^B0 {~'f47uZK}WEF9:&>tN5mK#S9lK# S9lK# S9WD]ᆷ6P߯P|4Tz˔ڭ[ /E\;WH4On3i!=~k I'DQ};}]>zsy'6s:4q")k| sVg`]7hԅIܾrX~l<իm[zd_Xؾ4 [‚{/s疗x[%%ݺa.JZߎ`O2 J3gIo++|_~id -ضI4/kzW^ٺLUG7cE^㎃=#/Mc#8 շ+9w^vX_G9[]4jGq0PݣM8A`:+EEX~8QCB޹kϬSN?kbP؟i>j^۴E^59* $& >vW`+eK"vc-Ny]s9SK9hɯnQr43ŦUlM0O~Խ[X4&tA{I ^9[bÆ-[_[SkwaZH>JaaffI/oތs`?(M݋/Kx !=>#ܺ>uqG/'BS`4kq#d\}q wׯW 7.cQvS׿UχI9G%Ĩyaz> C# 4]sHsˇ_ /KKY~6SJwk8?\3`Хٸ''O~ ߰z̚I11~ֱ\RTU5k2;w:jIn4]_M b쯚ÔYO[3C7ɦtP&U߉f1oE E~X7kڟsr!?!EbO)#/FW{?5ƦAblEOndzdlxl@? v owg? Pcj}g inm §8Dǝx컮a>RUr%[\ܽ;tnb4m$۫唵kyyʏ/I7lX O<Ѷ-T?Ĵ7h.]_m?-r UoG6<4Ȟ|>t *?_߸,q<( 1#G|ݻㅈ|ӱoi[}Bz}pm"DG:O;9",CnhŋgD9qKWfrΛI:J P@w-lM˖:8vJ2!<2)xg'kWaA>^~YP6˳Р^=.]TFN= ';M+.MMキmk޽{ +*۵H5$xp޽x‹.ء_T~遼ӳg?!uIUT e֭'xf~U={ Q|W6m"O12Pfaz5ŧ!DOn/kΝxQaWfEG>}M ԖOz^39agґ6C|QXEJkMq|u[⨯ӭW)_ݻ3f?&ucg^>1f 4,wͅmɫS1'|qECqS8⨣"~ު#sdУ8{3=HX6C|4[7|-_rܸ.<5]ÒLY ӦoەpG|h^kJK(Hb+|O#m^§xS9W_8cGŕ+IN<{wdͅ<Ҝ]sw9@߿/ cV՗;({c~c3C[҅>ngRN{<q;ғk$Wҽ|Z|㲬L]gѻ7vr^VWzP]\*oqNqGo>}۶iݮcޑʉp|qA|-?LB$Ǯ'(_C*CqIO#fN|8XWPyHҼ<ԴGԐ![Oqux }հKڵwo{^ {vGTnM?I2v3fX)gܝC_[r*p;;|s:|sԼ_jVB.bݼ9I~C}jl?W^-Zf:$õfMu)*:pT6mz9رjpE75$WXM7s0>qѺ?9}A%ISTi8jܭGɧZTu Q\N.ũ8ڽƣ׫1N/+k{3~ 󔟯?9;q˝.͡ݱ'W۶';ԗiP]O=z^ң;Co UI*}ZͤPuWɄN3`v_'+/oF1̇횛3)_h^0 WZuo't׳O~O\rOe|{kdkTsK'o׿A-O: /l)<:J' ws˫%#D)IEHO>?^x\ʌ~񷿩߉:߉$?.4SEjѿ>Bw?bg֫?c5P1f}nj {༉i3,z߳/8: LPsKK էP:dh?9ܔNH]|(sܚB׹}<ݟ |zJr+IҷoN8 M~!}IMڻ9H>!;#ysױBv؅կ[K*[>sߵ m׮Y3*G^^aa۶I2x:G?zpzXE4'y/ryIr>f̂ءW}aP}Nr}v¦Mi qÕOһ|oI.8T|8M-*jl.oosc~4zqWpg=]6 =b绫tu47+l}򑞋wa7~{̟zIұ*'ŃżН[n3s^y{!揧[J':/^b:;=м1!|OȡsQ_l_PB;8ݹ3XR :)7J':BwC|/};h_o#S8&MW{m.B;r'zK̏+؟Nr8;WOݗrqI~"cϗTp4O1W ,3_akWH7S&$mIHn<)IוA$G'⣻ʁ;̸JU9Cc3>ZR|u5 )>9@SȕqqI {}mkRpX>]~WJep9TTF!/d̘&GztNɏFmvo]˯px y믯_$Kum$ӦX뮻msGGPٰNo bg.sfo߱́J}F$=կ_1⧴zb G-NJ4HtIC^GU|晹P$_}B-ϛrǎ7ƋLoSmiFю'ع3j;k~l$o~^[٣ACHw)X_fq(Է1eeFO~rx0]޾}R,T?B }O>stp-]*$׿6Yޮ]Fƪ^RrX`^FH޽f|hi/+:u %ަƄuq.V2$/*GrIȗNS^($K"<n//'$/] =:Q.~W my{,SN9X !7}߾A 'GL&Ս?r^ny]:ũ)$|8\9?ݭء?ƧVlP.֟1?m*ǁ7N'O&ϤgmK>DQs;^8ӛ5|40Pi[Z۰1ػ˓EFJ| 8X;1N/_pC5&A} Gl闓w^]CR]N'-k((=8GDε $=e~ŵ$yٗ_~]|ӼGL \{M8 ҫ*$R)]N1)n89ҵ4*^1 'bvwW\nֿN DhD_{Q'=*n+G(?!Al?|A|\|Gups&3Ꟗò -]ikHlcJb#8vVQ^)\WԡU+l k+! ߖTToVTTV>J'<7Wo8jkWq?t܂z qa98+7n:u /Z?$ +/e =mϨLN}u^z]XE {,ۯ ҿk7o٢mʕoaa.=qIW+O{aq)_xᮻz*IpW϶_H ;_ߒVOtRKa,>/ɯ${T>CBwu_uرcn8u„E,==* ,m]⅂^8'$6z\sMtvW|b瞳b|x6Km[ykFm&>ۮs7wC>Q\sCˬjՌȏpP{HA.ZϔJ\_CeN㶷Bs[nK)c|$8 X_ZIͧƙ7*F/1\&5SXG~f*6!deMF\޵ȑX.*(^…qΝU_Ÿ.-jO>q W_+^Ef͚=8i^zo KW\|~iSX&?/#l؀F7*?8߁FiQ*͇g}rC!4_7A{,>^w<,?0[>*۟mUXx ]Q.ħb^|¥_S>l'Х#%B5'9f9TZv/TWؘq*+C}㣴V#Nqm!Nq9 egQqs7"΢i]hvZϡ?;fҺN+E!CN?X,n,_ō|O骪 ~̥r䓡|@V[CP=Uv _ 16l֭vbn)=~OΟ]*+;Ƿf8GH6s,_( '_=:?9_\b}fk$tE]k?G;[o=ϴi?4m7YgW\qXח]*Sj7S GpܝrJϞ矯(pw݅j 7'-GY>ܹf 8  ۞/r$"c}8?xpv,HΟ?m})`C(+?I5·s/+\Ir|BHキnvz+x ;'h\䓋4]w]{-̾=M)ocXrƌիi!{4ȵǐKvv{){lN7KiMXh@IDATqi 3?vD}->^T6KDx~~۶X6@ v_sԩy,|u耯3F^PYm֭~hǍ[ҕz)LB M?8^q@CL ˭c,|޼kʼn 9-_~gN\f f_CݻYd1ﺻtd#qUS5tN\x_5-\G&MR/8h2?!BtT*Gi"y=j=`16s2>^/٧Oǎ|"IϠG_ɓg4_~?2TenG[u+^ t[A?h02b\\}/پFM3Z>ضXXo|CMQ벲6A]^#s<^x|7Zw[ߥ^!mqcBvBwq3VS'k 酅I7h=V/^{Сx7>?rUVET̶JKaWᴴtHi:&uqH.с寧${/_L_@z^߉x%;q78aCT?x&. ;FwǂYgɔ+ JOaxN9 S9><ݖؼ #~ocgdGM@<!ߠvmva(-$ɕW^pv ]6o !˖}QX!+Wu/*N?Gj] ǃX@ƞ=o K ~^ۧJKow?,<8RwŊF/^a# CiE} x"C;_KUUy93lOEauf?~=pԩxNZN2UHQg_=p0aüy8һg7NY.;7_Z?W 1jԴizy۶C{a}̘bw[t=\ :\,3}:>5н'h$~GX]n %}v֦Nwvrc4A?wMefl%P!pۧ ;K2Zկ@vt E{U~t4m~?^ѻwx@E.:N՗J"ŋoxwtv?x8Ix>׿ t{xݿ ?Q#Ӿ}[`'U-G!3KKLQ׿ز2 j}qAzS^8{ĈK/'6W %Xr0 |3k)~ _;Px!7VMנBݻo'Vxv=~*rc\,ӡmi7/O__N%KWzļ#GIW^i$msM{/t;F]tƩ/۶%Dܩia0>Mc=u/&עEVߺVzχ.4 swy-u]p9 [y=&1zq5d>M'<[Q5kvI[.7Z§/N<[7أ윴95Yȧv@w/)k/;F!m4o{[}\I˖YStHk>҈CȭM)Nňjr/5;!|bΜA0t_4Z"+n<Kⱸ|"_/=_I'O_i>?Խɧ CAȔ w=ܹ3f aK:x}⤷]#:di褐|uZbN|9)UT|x@٩S޽zk2aFyڵX>^jբE.MG#8,$Ѳ2] .4 hWiV[;2^P>sg݊طӯvj.V.5ʮ45Kn tOzlyt qT%ӧcQSR_k(:0 /;vT+_iӠ9[F>s&vD|͛Ǐ0aCñG~9C`8;Rsy)ߦS>{Ϟ: ˶?,D B^ c+(.ui~;𵗶Rru>*G9Ӽš޲eee&:&&{x%vR}:r1xݏ?>vb5X^x}WQȑj5`[ƿv.&+2ÆW/Y)ϛ7sq ƼyGcЩSh7Gv-2ogcUU^xa1hʇwzE>|zjգ^o/|ΝkW;]/h>j>b>xg.٧P]p߶ YsBҐSsP֪]"_ٵߜԜؿ¤IC[ÿKkX?<9}_o Sͧ ;-4\:Ҿo7ydf9MpqGlZjбv |<:(gM_4~}Sc!P4-Gc~>tpג^UՠvuDzaaF>}f^͛w%>.q7mzA%%wߍ~P|U3%_PS-G>!Il$MwjvzB>8}:H&ӱshذ g0x0`5s!Sfz: D7CA'_v;/$OU}?tDzέV  ӒbIWR2r$v._f wԤBڱ~"QV6fLIIXKڵ_?<_G4ү_.nwSf^zd $~iaU1c|CA?n QXzMb#Y-yMf._ء6mz=^`GRϞn*(Ki ދw܁#tpxg"p>}H䣋Eq^\kΜ?=thn[pk|^#G_:2eV‚v?xq$N{|-[ KA YYv<G8l3N~n'/KS ._$zQŔ]?4iuooa KkժŽXf텑, z<.,?;M3 1}[n3 &!ՔJ1Bf ߺyy|DqkwX̶_]_2 kNآEvW>?X:r5VW\ܷ/TO?Hg6 z-kDzT |z6l v-,=}G _|W{e?`-ak *y0|.-,T!;>+ċW8E!xk.'YܫȞ,]ss8$ѼLB浡m$M'Ύ)5iyvKn߅Yo} >ޔ3=8YlD~ Cq_qeȱc1z㸚ӂ/ɷkj ߽$?wgonww"1ǸA%*(8dR^G_8$Di"QC/-xI F\8=I[FlLUU-Tm>0OU泥\p<@>}5ȧOvWȹփGv% 8M6lo\^xܬYjA_֏-E8w{:] ֫Qo<l^}5Վ#q ,^8Q˾/K-Z`׎JukvMܹcTWB?oQle1n>hvԳay’`SwL嫨ػ;`S^AmfT{uݪp>aæMȀa&zG@1W\>}/կ7Gj9{5~cLJY}G`4]q#|n9g~_**>cAAa|َ!!!"fA-[< _^o.V K +ooscԯ?XF~qsQR䊓k>_Qg/\Y yšCXn޼E IQM5H8ct bjܸd:^8|jWxԼY3s^¡sr57QE,VGظ/\_vIXTg ǿi#_/}D՟sGD\k>[zȇw\\犓k>K\qrGr+NH.s5ɥ{8#t'|$䚏=W\\犓k>K\qrGr+NH.s5ɥ{8#t'|$䚏=W/TľdɖM#Y}b|"?s;eJCLA2{T?J2h>$㏌?2k?x駧MÉV7l>vE8O"A5JoƗ/?sdd{4K>$ӚWCdk1I=Ib(ݟK?G_~Oَ?7v8GpC̟|9߸qVy\۷'믯_I2mڰasݻvN4q/=ɵ7Mu\R+Ḍ?2=ifşK_\t ځRBрh@4  Dрh@4  5O =矟$3f][5;v'4 D Dрh@4  D@pzH(&GƦs=VN!t q=sX9!>pc9|+'snl:c8|M9zC>G9z76rB|>GƦs=VN!t q=sX9!>pc9|+'snl:c8|M9zC>G9z76rB|>GƦs=Vɇql)S ÷̋L.ܻAc&CZ쳹/s\99:9|sts>G9:9|sts>G9:9|sts>G9:9|sts>G9:9|sts>G9:9|sts>G9:9|st_@'B:x4'0.7DʆbR.|+{t2B2e^G2?n+UС$ؽdϞCa|oҤE$''BJ/O??_G!#O.ϗ 5mn:D҈\-^\/jeB#Jj&' H> YҎ_&+=؟4 X&dJ;bhbbЀc)eڣAjeBS;O Nozyk#UZ7mLnXԯ؟ ]^X[=L #cjX?fS1{Dm#G#j;,?,u\PS|W#?N\݉Gĸӫ_\S*BJ_\J'f<_ӓY 3ׯ,q{Wdja?Œ+?qz25?^a'߼0S\74$K/'O ?~?5kȏ+GݧP!??tZC}JOh #G)?~?5kȏ+GݧP!??_Gq\z,n,cqc8y.=7ⱸ|<sXX>NKōsx,n,'ϥ\<7cqc\|.s鱸|.>ōXX>q\z,n,cqc8y.=7ⱸ|<sXX>NKōsx,n,'ϥ\<7cqc\|.s鱸|.>ōXX>q\z,n,cqc8y.=7ⱸ|<sXX>NKōsx,n,'ϥ\<7cqc\|.s鱸|.>ōXX>q\z,n,cqc8y.=7ⱸ|<sXX>NKōsx,n,'ϥ\<7cqc\|.tQ \Ec"HvG]'''O_5č+tdGd'vԬ9>eW_eX$eW_eCrcxj3]cX5n%IS=+W_Pw!dGdQGKGdG=*]HǟteۙD҆ߴ Aϙ)mVQwa??h@_9S? ?*.,' H>gJ!gZE݅uLI6L؟4 )"TD:7>FqDO|ixxi-&ۍOk4G/5?n|ZK8bivZ:KkM쏷i<_ZkbNqZ܁N~`FarD>4^DY؟KiM?#?Ie!h ;g4WkN?jT2e+Fe̿d%/}_t۝~,eA: kBbbMjI'/$jB>2+W_>&4 ㏌?2#㏌?jQn?3zn.Dрh@4  Dрh@4  Dрh@4  Dрh@4P78"[1&C(GM9zC>G9z76rB|>GƦs=VN!t q=sX9!>pc9|+'snl:c8|M9zC>G9z76rB|>GƦs=VN!t q=sX9!>pc9|+'snl:c8|M9zC>G9z76rB|>GƦs=VN!t q=sX9!>pc9|+'snl:c8|M9zC>G9z76rB|>GƦs=VN!t q=sX9!>pc9|+'#&p=x@C#NDbU~v#㏌2{Ě?e)Ovf U2?e)O{̿e-o+\T2g/kCjhynH+Ԯv?4 D])mեiYbbЀ?'2$Oi[.N>Q!JbuiuZ؟4 O ISKӲOeHv_GgvMqr!M͉՛FNj7,gWO쏆t.j/,ԭ?Qa?c15,GGvXa?=L #cjxWn(}D^E_\g'#\ U/NOv)k!qv%/NOvI 3ׯ,q{Wdja?Œ+?qz25?^a8=EX?n0qJғgo^PZM]X%{5#GS~Bk(אW?O \C~\?>%'r q5#GS~Bk(אWrnaM;͋\:UB!ѿ؟? ]QmrLΣcL1?Gq#3b8bk#Gg2p-Gd<=[ŕy6uDрh@4  Dрh@4  Dрh@4  DрhO_@U-7iB8nz,n,cqc8y.=7ⱸ|<sXX>NKōsx,n,'ϥ\<7cqc\|.s鱸|.>ōXX>q\z,n,cqc8y.=7ⱸ|<sXX>NKōsx,n,'ϥ\<7cqc\|.s鱸|.>ōXX>q\z,n,cqc8y.=7ⱸ|<sXX>NKōsx,n,'ϥ\<7cqc\|.s鱸|.>ōXX>q\z,n,cqc8y.=7ⱸ|<sXX>NKōsx,n,'ϥ\<7cqc\|.s鱸|.>jl: t $?.b'GlƕX?2#㏌?2;j2+㯌2[sj,2+㯌2[c 衁NuձC<.1$bbiQ+W_zT?2#㏌?2ݨ%#㏌?2#.$㏌?O2 AuLI"_iCoZE݅uLI6L؟4 )I҆؟iu]3%IS3bbЀs$JbU]XOWw}ΔG *YlO#d 8"ߧ?^o\4G֚YrD@Z/:EOBxѼB<ߧ?^o\4G֚_n|ZK8bivZ:KkM쏷i<_ZkbNqZƧt#֚o7>xxi-&ۍOk4G/5?n|ZK8bii @' }Hrh"{/,IO&ր?eWٍ24س5j'S\NvZs5*_W2ov#2BY?l K_2̿?p sD5?&D5a}I?5?HMx+WlHSGdGƟ{(HƟl=f\рh@4  Dрh@4  Dрh@4  Dрh@4  z!Ŧs=VN!t q=sX9!>pc9|+'snl:c8|M9zC>G9z76rB|>GƦs=VN!t q=sX9!>pc9|+'snl:c8|M9zC>G9z76rB|>GƦs=VN!t q=sX9!>pc9|+'snl@VU IP` S$@hpHHj-2b@o*C18JF_@Ԧ\͏ƊJ \ʽyXw=y~9}ך<9sε߻#?'!~xY;G~6NCvďl?e8#?q"G#~gD8ď/kGƉp#^֎?G#?'!~xY;G~6NCvďlw}=;ض()zĉW>kWX?uO].ʳc?uO][ߺw+,ZTן]7]o5%)~rW̥7&V9Ruj+Vu%Og JMrV2J,PXHש[e֕X?)+5ʑS[ʬ+?S@Wjb#_ro#ǿ?2g_+'O՚,wU)^ƪZ[՟OWWIJ????uE,{S+SWIJ????uE,{S+SWIJ?4ns5 QTkcҟ?"+t*K*!\^8?՟WE=JөV???*9WW?N6VQQUQϹRtzՕS|Us:7eҢ2ߒqco%R?>Y:jRiQWGGԔOOڼ???}(4uUQQ)5E6O)v( Mycu F;XoU(Wr5} ZGG_3f?Ch~̛W53o:J_=VQQ̼(}ZGG_3f?Ch{ͿyOuͤR@ H) R@ H) R@ H) R@ l<v}=:xeqY^y?!_b˸80oy֭uІyPu&M.Ţ+ZΛ7<ҿ֭uІyP5_7-Ն6̣yRmh<ʿj?7-Ն6̣yRmh<ʿj?7-Ն6̣yRmh<ʿj?7-Ն6̣yRmh<ʿj?7-Ն6̣V/zіEk|;lQMM܋IʿƩ+G믮?ƭ)0.kWhߩWg\ש3N5u_]W׿VZMqY돾t}@Ϧ/km -}]wo[_GvءtQGq)a܅z;6˞M) b/?P@WWWWwO^֟ikZW_Ze%vߕ@v-[oN+׼&޼=SNI~;/|S:|g?ץtb7O9(j=t1>:O>g?;OOOc9dxSm?O >(?%{/{5׼])=sqvaf~ï=?۔n%nK Ox?.~;F(]v4Śg>%<9_֭9cs OxKS⊋.z3S:hR@ H) R@ H) R@ H) /ںʼncyy5פt9z1f̛vؾndwojֿʪgv?Hk_m2޺0~?gs. {oJM~uJ]y#]z}cǎy{ďcyy/կ~ ROE/:d{伏oz>ovØ=Q>r ( ?~zWt(=Lo ׸V9OSO߮nﯟi3w*#?g^?Co9+G֟=su=~sxI xx<駿yOJoz_~oxi)o<ޚ{~Jv6Kko_q/= _nlvO?~%|gtCDm9o=.޹wϼsI~yul,(pvoG9?-׽o̹5>3|R>vҿSD̨?SV${_|ʿv2B3O;C]^h?_;CV\wSHG!mk.xi4_G՟ꯝ!m+o849@IDAT>o@xyumKSK/-!7SN9\qeO}jy9:c~wmo+2j?_le}=`JZmtY_CJ{ nRo5>gi:-[W7S)lWe߲<63G{RB ??W{NZ{ciXK}yc_k+MZe?꯮ejXXVXGGejXXVXGg#Murw|zcozzcrKJoxyu7տ:\Mtݴc8nh>Y~%]n~vCO<1?p?67ogt}=ꨔ~W<{W^yoǿifΓX=r6Y n7uՏ>r}0"U?cqnu|%#ؔ~N:)C7c>+L-o|D5^?n0Tk_A m9~i-ml\;5JNHZY^jPUI1XRpRUU֟ZQhۭ,t.K_FG Ըl,7 Tq=3֭w3<N8ܳJ餓C\D|^g~)}lnݔO鑏|G~&zz--nT|>u_fy{?֍s{~J{u>٫vn)}7t5mzۼ~{Ŀo}+͛7γ^7x}#=~nj/ml%~Aؑ}-oI曍>G?=3jşw_+~oŬ"/s_"۔*Wʿz=ÙUT_3VT7SABkfLuzӯy3Q!O:N~fcnOofo}|Cmhoyyy6}-[?߮ԣ"si/|aJ_l)so{}Oַ;ݏ}1<Ǐ箻Rs/8{o1Y?{ܞ=/E-(fn8.jA,0s#qQ eq,ZP,/+܈eq\ԂbyY\aF,/ 37byYX^W⸨̍X^E-(fn8.jA,0s#qQ eq,ZP,/+܈eq\ԂbyY\aF,/ 37byYX^W⸨̍X^E-(fn8.jA,0s#qQ eq,ZP,/+܈eq\ԂbyY\aF,/ 37byYX^W⸨̍X^E-(fn8.jA,0s#qQ E8x8,[#}C^?w5ĵoXF?~m]g>[~gǝ}vJo}^v)m.yj+jdkll}, ?qO|SڝiwC8n+^no}~Yg'?} ?Sv#x}r|wӦCMwngP/Bv xz; /䒔{jg:~8[2G2|87J=,VVW'OEWX>_,b8vz2mDNscǃ>@ϻAY?b̍ŝ~)}7|)w=؜_8rW85Wao:iOK+=k>h^po}3WR_:>/2w Ycnj-ޚһַ~c5=3 /| >Ώdk90y"q+)0TS jHOV0+oQUU֕0+oQUU֕0+oQUU֕0+oQ;/zj8۲ "7ٟo(|ֵn^O|h0U}o<-;xvMxj1{LJGuq~kw7#]a;uwcO;e/K[>g<#O[o37=|s7_K3{]Jdh?qvcq[)tW^y)koOx=}֯~_G?u}qM_bJ_OdJ}aOt}ûTtGߍn?ʫﴘonʿOg L#irUן5=VwL{t+SH7M7_V@SbZ閳O?՟)0P锘GiC|̄nj[q%a=#>ym[9͛kSW9n|sGw?Y~6ayigtɇ^;çx-^owu?טndvoyy;ۿGgTw>%񳽽fmoWCN>nge7o~ K/]7ΗN餓{κ!^o|׻FgحWo' o?f=q'>5 •)\xk_/B_G3?c7SƬynJ{{ԧxG m##ۯ'ۯon@n 6߶'~"~ N=>nmM:C-/ϬDd[/}19ΒU:h;<Н}ӿt40˴+)LsK_#TeV|n՟O7FUTˬܪ?՟o~,ӫSYaU?YwYiFdB<#;GGY?i]Ozt%W^i7h7tMn_P^].~]vu)گkێ6\nWz|;vdkj2p>)}CyϷw7w/o4[kT-?ᄳ~ /x}mOyʋ_l>KR?o .=YϺ׿n؏o}ǎK>aE1ά=|ߞ0pݐeUY→۷dM Hq#?'!~xY;G~6NCvďl?e8#?q"G#~gD8ď/kGƉp#^֎?G#?'!~xY;G~6NCvďl?e8#?q"G#~gD8ď/kGƉp#^֎?G#?'!~xY;G~6NCvďl?e8#?q"G#~gD8ď/kGƉp#^֎?G#?'!~xY;G~6NCvďlwˎۖэ|goO+_i7?.fgϾxgq]UW}TJul^ߒ}G?zΏMԏ XU&ϏO=ۣ? 'q}Sԯ ~WS:P{]ϳ^dnᆔݿG#jE ק7u։'{?)oן%c?t _a-GSHHɟ>_ץ+)PcGU~Gw'}+oȟY^gZΐgVW)3oEʿ~ [Q~!f{i9CV_ȟY^gZΐgVW)3oEʿ~ [Q~!f{i9CV_ȟY^7-{$/{mE޽sb։m[Jwoڣo;lG?jf݈=G?:~cF=ܴ;֫߶{]~{tݬWM#8ؔ=cQf=ٲ{߻^c>pflߜ7L_w*|C翈jϡS񵳈#TʿEdxPETT-"s(o|,_o4C3TkgG:&ϱ}rg}Ң\_ے3OSQUC_Zj_W1W_Zbw#^No]`GKfkCN?N'__՟o.\]p:Ws:IϸV +꿜N6VQQUQϹRtzՕOSz;LZZT[2n-OW*C'BSGm^RS?>Q:jRiQWGGԔOOڼ???}(4uUQQ)5E6z?nߞa[hkmT*~E =1(I/Bo"OOSMDg Et]i?P@?ZhYGFZ]GI) R@ H) R@ H) R@ H) X ?q#?'!~xY;G~6NCvďl?e8#?q"G#~gD8ď/kGƉp#^֎?G#?'!~xY;G~6NCvďl?e8#?q"G#~gD8ď/kGƉp#^֎?G#?'!~xY;G~6NCvďl?e8#?q"G#~gD8ď/kGƉp#^֎?G#?'!~xY;G~6NCvďl?e8#?q"G#~gD8ď/kGƉp7гc {m}GHOG?Zu_5]Sןr<;V]Sן?+uo]¢EuO]u}YVsPw,{\KS@Wjb#_oYWb)*GNm*R)L_UTʿUf]SR:̺K3T&V9Ruj/'F?}[.yrҿQrʿZ_//u^,oUTuE,{S+SWIJ????uE,{S+SWIJ????uE,{S+O:W^SE6V|Hk.<)#B?N'O׿ɫS|Us8jem㫢suT+kc_+NZY\]p:XGGWE=Jөq^&X--*-7V^_+E6O)v( MyJMDQhͫSjJ'BSGm^RS?>Q:jRiQWGg=]7o0aS{5O6XNKI/Wޫ53o:J_=VQQ̼(}ZGG_3f?Ch~̛W53o:J_=VQYggTL H) R@ H) R@ H) R@ H) S`7УSIY^ќeq(,9Pj(W|S %?Ql~!OSQU!~?ZhGj.W_Zj]ܺ2iW_\v=Z躶vY]mYSVyYS(˪?ZhUDG?Zʪ?y!nw `KʮҿΊՍ?S@#:5uVnS\IשbucPHN _+ou5WGRuj(XX3T:}"%/ۉ+ FG[aZG mGUY>nZG mR,ʿE)y##[aZG mGUSyRmh<ʿj?7-Ն6̣yRmh<ʿj?7-Ն6̣yRmh<ʿj?7-Ն6̣yRmh<ʿj?7-Ն6̣yRmh<ʿj%ogam [f˷ϽHqjij??ZcJ]㲦Q xuU:TW׿u;n%5+/]K_Z_ ~;_lƠS$ʿiD>?՟O7E(3D}t;,B?Zhgm-iYv%R@ H) R@ H) R@ H) R`5 ;6L$CtGDZvďl?e8#?q"G#~gD8ď/kGƉp#^֎?G#?'!~xY;G~6NCvďl?e8#?q"G#~gD8ď/kGƉp#^֎?G#?'!~xY;G~6NCvďl?e8#?q"G#~gD8ď/kGƉp#^֎?G#?'!~xY;G~6NCvďl?e8#?q"G#~gD8ď/kGƉp#^֎?G#?'y]@ώ#&#-o q"?߮,Zh#tO]Sן˹XuO]Sן쯴u ?u9s Ed[@G)U/s/M_UTʿUf]SR:̺K3T&V9Ruj+Vu%Og JMrV2J,PXHשo}OzIF&+j}ʿyVSOO] OO] OF?\yMmCGX"k q*8?_gW?N'OUQϹRtzՕS|Us8jem㫢suT+kc_+N vz` dX[zE_?Rlk9&[EkI~(X)/-߮Vy]uu UwU>sƮ*~Wo?՟/wFH-r{ߟ_/U_zyYggyWs5/gGD_o ~{_z7f'FG?m)mm-J볔Gl^\lʨ~ ϿÙG5CR<mͤ?RB+Mff?Q}"tϤh[3VW|lU)V9^ҿoi2Cz>׻_F_Zr1{fmo9&l>D˨q4T/~-QϿ\EC5xX#ECmͤ8Eǡ[mEF9qhFOwHW=nsV?NqZxOloelou;|]wyW`O]@N_,,nbyY?B!NX\e,/cf2/gyY7X^y=ظYV>.׷g,nx| GzY^70ц^ #̷^őŵVőŵVőŵVőڞe l|70ц^ #̷,n~eq-,/meyY0|K#|veyYG,ώg,/eq8b>;,G#"^?S5~X?Y^y?'ͻglwʿYSNiy߃ ?R@ H) R@ H) ;ɑ!h?:+)<(1 #y?sQbG ~(ď13AEQcf΃?̜#?%F!~䏙9G~.JB3sď\?f<(1 #y?sQbG ~(ď13A?Cf[Jl[ }qkGy??OCȟy?OCȟy?OCȟy?OCȟy?OCȟy?OCȟy?OCȟy?OCȟy?OCȟy?OCȟy?OCȟy?OCȟy?OCȟy?OCȟy?OCȟy?OCȟy?OG'۵gh}SDw[k;^SQW)ykm{f[Jߚm)8R:|U/ͶܹuTJ9dxFxm_׿uۿbUiӭZjWog+S?v@o.º_w,7Og Y][nE?)*dSζm)lKfrϸfDlnydJ̶ѿwZXH3TKi+9ʑOg VYu%BWrb#FοяpzwL=BkIZ厕?_byc_O+bcZmͶ8ϚB?vו_g-o_k7_k__+bcZa"=VVQQ+bcZE t.#:ӳ{EZsNtUq:yuʿ%.TN^]՟O竢suf8λJٖQwȯ+3>UNj=$__+SU??*9WW?N6VQQUQϹRtzU?my퀚aƍWJedQhͫSjJ'BSGm^mKmSk`yen۳-|O,?zVͦoWKe'BSGm^RS?>Q:jRĮ(B n-g_PS "W53o:J_=VQYO燳-Ͷm^:;C;Ƕ6|Ū?>͏V s,ZЊ U?_fPG髇???7SABkfLuz}J3) R@ H) ޡٖwf[Qٶj7<1-f۞{^zR@ H) R@ H) X$C,/h8Y^ќeq(,9Pg[JyDw1wlߢӯ b__a]bk+iI JֲU7WS?#T3yUHy~ߞ]Ͷm)o_>|Y߳n}ku>+kVPJ6LQr.L۫S%ϼ>!3_v->̣E2º-n6#-9)R8۔YzFc#[aZG mGUSyRmh<ʿj˿|zEc|p mFcKA[먡 Hj?#?THiڤ)0ԥXE2ySFGú07-Ն6̣yRmh<ʿj'O"F٩N8E6"OSE}&Gd]Ggb?e>#{7֮Α}^KS ʳXOSE}&3^GGG'3='c??cO/f>̇7uj|u;"˸+.Q>eoW9/|Tu"}gw[oSQDGg,cM-F~j{k_+|U u=>.j5e[mi<)+4+FErd7RT*kxƴDǵ.o]9q]g?EZbUt5Ἦ'ߢOKFXWWWp^mCl(_=M)yz6,~_Bױm)=fig%ndo1K#CMQ왷KZ#{I* #{+o"MG?VTlH[KG;<ʿ}Fu#{+oِ?"I*@y#{Ki_KΆtITʣ[J+Zt6cHҿ7PERZR!#doEZGζ~|mg7 t8,Ѽo'?O̶~&}rϿ5SNy=5[m_ʿ~]gO]*f?*VvdOgg[Jζ{gg[J?3R:`u N)7~WPSSS__VPo@opkKΧE oo*W*Bm ǎT-JedQhͫSjjU']ޗWwJYeM?TOڼ???}(4uUQQ)5E6z?n-aڧ6ĚşpU_KI/Wޫ53o:J_=VQglzϟM=?cH-)P+6C?_fPG髇???7SAB[oi-6?.ơZf-6?Nq֭8tKMStuk&):Rokݚm?=&{mKYʿq5)?NqhP˨qh~1+QK#T*jW˨qhR@7.ơnSo]eqc_,n\t o UY]YG,ώg,/eq8b>;,G#X^g3}x8`yY\X^#,/p>eq}vom)lK'g[Jm)0ۆg^-ߘm)lKٶ;k}SNwow(S?#p?OGgw(;o;ᇣODo}ɤ{38+0UǩߢGo :z\?S}8~tT' Lqq{N}`۹})mݹw_J3nuroV~lKGg[J?2ۆיk_Ty?gq9nOz\SyTy?gq9nOz\SyTy?gq9nOz\SyTy?gq9nOz\SyTy?gq9nOz\SyTy?gq9nOz\SyTy?gq9nOz\SyTy?gq9nOz\SyTy?gq7wfҒ1|bAM?\w@w[?O'h}7#M?"j]C-mm>ۆ <cz8gٖҁ7oxvMo o#)sfT,2(COәat:hsfG^GkezAN6kW?uO].ʳc?uO][ߺw+,ZTן]7]o5%)~rW̥7&V9Ruj+Vu%Og JMrV2J,PXHש[e֕X?)+5ʑS[ʬ+?S@Wjb#_ro#ǿ?2g_+'O՚,wU)^ƪZ[՟OWWIJ????uE,{S+SWIJ????uE,{S+SWIJ?4ns5 QTkcҟ?"+t*K*!\^8?՟WE=JөV???*9WW?N6VQQUQϹRtzՕS|Us:7eҢ2ߒqco%R?>Y:jRiQWGGԔOOڼ???}(4uUQQ)5E6O)v( Mycu F;XoU(Wr5} ZGG_3f?Ch~̛W53o:J_=VQQ̼(}ZGG_3f?Ch{ͿyOuͤR@ H) R@ H) R@ H) R@ l<v}=:xeqY^y?!_b˸80oy֭uІyPu&M.Ţ+ZΛ7<ҿ֭uІyP5_7-Ն6̣yRmh<ʿj?7-Ն6̣yRmh<ʿj?7-Ն6̣yRmh<ʿj?7-Ն6̣yRmh<ʿj?7-Ն6̣V/zіEk|;lQMM܋IʿƩ+G믮?ƭ)0.kWhߩWg\ש3N5u_]W׿VZMqY돾t}@Ϧ/km ?uAP@vASSTS QY????AdGcAZ"G?ZѢi֟?+>mQR@ H) R@ H) R@ H) R@ H) VcO2D!tkGƉp#^֎?G#?'!~xY;G~6NCvďl?e8#?q"G#~gD8ď/kGƉp#^֎?G#?'!~xY;G~6NCvďl?e8#?q"G#~gD8ď/kGƉp#^֎?G#?'!~xY;G~6NCvďl?e8#?q"G#~gD8ď/kGƉp#^֎?G#?'!~xY;G~6NCvďl?e8#?q" >biTkܱWKzjmU?_] OO] OO] oӸΕ64|DQ-+ҚK.Ov?,ɫS/|pytT?_+NZY\]p:XGGWE=JөV???*9WW?N6VQQUQϹRtj@o VKʰ~KƍWJedQhͫSjJ'BSGm^RS?>Q:jRiQWGGԔOOڼ???}(4uUQYg 3L~kb*VůӣG_Հ?5Dh~̛W53o:J_=VQQ̼(}ZGG_3f?Ch~̛Wc6m?5R@ H) R@ H) R@ H) R@ H) ~&vy~4gyY,/h8Y^ќeq(,9PG `oeחS̞v؏~\@?[b7돛Q6O u -E&qL?]Q6UC&ZMAUCqSئ !VlqEzT+8j?=nbt_Q5ğ71Պm:⯨Oj6WT MLb+&ZMAU |O%R/;J^HBTN5??ȿ0L&{ԫcyuʩ&/q_L/)P.jpW_pm%Z qnB 7C Q3?HO?4#14C?0`GFZ3meQP @(P @(P @(P @(ڣ@߲4$C8͞:ڮkv+OO)\k٭<)? _pf4|͞µkʓ5{ ڮkv+OO)\k٭<)? _pf4|͞µkʓ5{ ڮkv+OO)\k٭<)? _pf4|͞µkʓ5{ ڮkv+OO)\k٭<)? _pf4|͞µkʓ5{ ڮkv+OO)\k٭<)? _pf4|͞µkʓ5{ ڮkv+OO)\k٭<)? _pf4|͞µkʓ5{ ڮkv+OO)\k٭<)? _pf4|͞µkʓ5{ ڮkv+OO)\k٭<)? _pf$7P}x~x@'@k_W~b '?qٚ;OOg~7q|Vh(?q[Л= ^{nW X0&ڹF3G `1=?6⯝Q?R/vay팺CaLsϫkg. c{^_O']T=Y堿(IkY}?ҳqѺ}}䟬???}䟬???}䟬???}䟬???}䟬;Z,ۆW4TDQV^T$V8!'B6]6?_JF-0l:Iu100m ǦSVYGAA"[+NYeiGl6l:e}9*u۸BUm Y@ VKRn1?/ x U݋" 1A{q\0y"(Tu/Okk[|P`{Pg&6nܼ_?nTbP @(P @(P @(@Jo>I‘V\V\'V\V\'V\V\'V\V\'V\/ >Oݨ|wǟ|rj֭}I9Y6n\Ο:GgM=Fzڵ:nhYbʶ͚'Q:U P?Hy֬YAV˖97iGܨ裝9z(ңZ݊k+{V\)`Wo)`Wo)`Wo)`Wo)`Wo)`Wo)`Wo)`Wyߴ7йe?P)m۹n=쳝~؟U/ܘ1^;g-znzQ?8\OW_uӹ+;∓O^3<ë[rfCvs\s_sKҲm>rĉ3g:wS|7f=m]wsz~Hü|ޝcƜus^z9s;`ZYyy˖xsW_}y('e7ロ:kʔɓ;c; RnW\qƏwng"}vډo˲{ލJe:9GrcN8c)>dH=lo 'B>)`cG@Cocٴȿȿȿȿȿȿe>5Z~?0`ϑl֜ N/R>lo'?-,Zt9G;7{~I :˖}[&鯻S+Θ9o}P#hکcqV_.o?=X/|ߖc#/衔o|cs=zbnaW_=csg=o\t3ƹnB>x]fzi[w7=}I͝[ }5/;waCtt}e͚eˮ; [g'z ̖-[(/^2eほ}S1kٳ5kM֏}lذq}'8,;!q2,˽_޷XgSlk1+<\j|Ԩɓ SN7#qn=${IZ?MuyMH s)87lYқ=]jZhM-Ox&ĉ0-o ~i/]Jo7tC#OϞ}1w צk„ӝ;c?Ao@Z5k'/~qM]wr}b'@=遤 og =Ӝ7od:>9u舟 'СrƗ_뮅 ob_Yr޼s_en>%qz%O1:)Cjq4E!HjqWM7?V+Q-0G@5tC^jqWM7>??R8B+㕨G;].觎%q/u폟\'ra ݩBs?WhQ`b=EֹKq{(?ù{?)PAӛ*isݺ5sO?\\w7^x'w߽bŲecGyq_7[z5qj+VwsW[G꿦?ݽv-]C{tޞoժ5k^~{J -~_~H'R˗ӧW^G+VYݽn]πf M$Q gnoW^ǥs?/۹^︍{?1,y:pƍ=yKw9wIg9u*{ ګu~_9=fgĉW^ΙxBћr7iᖴnEoӳf'1OY-|k \8gA7ܹΟO r[j޼%Khhn!R_ׯqu벙&#F({zw=7u[Ku>ziK_ wֹrΗ[%I&/fm)?^$㿞jr㈿zJ"HbHJ O=Ր8B)(S²=]OȊk+ǛgDGe| My-/#F>]0t=]q-[L! ~㫯vӿuT5O0s&}Z3'L7>MM7mzԹN8$D6o3f̙s5ķd rz#G:G':oiݯ|~x\/^O0kֵ:0~:ǿ̼|~ْ%K%̛3~K9s}/kт&QhO`_y>E1cy]w]~ٲG?8gŋﺫww?}z)Sˢ',6ϾeL ͫW?~Eԯw5v )Ϟ}q 6H+g^mexŶϸYZկ,ftҸqō{0ɓZӛ|ҥ@?{.a^oEmٲy3E4nvSowp+y,ٯ㎣ߞ3K.:RQGg޸^kzU?aL'O q(ۆf2>_L!??_b+ߢ&#)-ٶQ1coW~`ώ7%VUs6'/wn"7fkvz[aVo[wEK5]]xޗޅ\쪫hA?SEgQgvQd{z^]+O.iIjƌ3cFs_N?>zC;,G( n={Ԩ 猲jҥFg?;z4-wڵ7=̷\I*[8g.E::sEUrw?D5ϖuΩ}ș3?a iN=8 /߂2exZ`}X4БշX{V7tqzCIo :hP%V_7(0~s6|xbXFC:44~>Cgs?̙.{?}≇Zd'x^ПWG_z0ٛˎ~s(=}m˖ikRя 4Rk&og?)n0ڽQ000xh_G] {uUzM:]TmOXQ~q?)O9s̘V}VG{'N8sl{T?ByvEκꪅ i͹;{74W]uYWUw_Bg,SVhsst-u=-s(7ztr~9zo>|nՄ@MKn%k֬Xqq>L|o~wI_}_~y8@ӿ-y` {F;=p5 #n7K\vs_ X[!@IDATS׌V*OҤqŞ=XPGy)z0I|y:ryַn>zڵ6$@rgAyo\WdDG^t畮<<+<#Gҧ>wmȐoӻۭ-O%0<ӃG1lX (*K~>hν=B[^8b&&FO3/yU;k~SGVws;~]3k8~?aX~UK _W4|8UK _W4|8UK _W4|8UK _W4|8UK _W4|8UK _W4|8UK _W4|8UK _W4|8UK _W4|8UK _W4|8UK _W4|8UK _W4|8UK _W4|8UK _W4|8UK _W4|8UK _W4|8UK _W4|8UK _W4|8UK _W4|8UK _W4|8UK _W4|8UK _W4|8UK _W4|8UKu]A~!"|žfi砃>QZo?iK"v==O>I W^I ANZ:-ׯ. ߗ.3Љ'{^_`-7K Vz7yGg#s5wwz>˓'}Υ#k]vqo-[wbt_&d;TbUW-YB #FJߟh!>-eki71knŹiӎ?e{NY˗pyNBeއkdz{߻ISCz7a؃}۹c? GoX5&{wO'F-= 5t(7{}9У 5zތ;;Q\|ObN;o3zr"s<W_{>QϞRtOI?_IKU?xx>'*lY~*cKZG8:/}K۝y(ǥgKWZYf4V-_lϗ_>i)Vm|ivF%4|_QeC##$ߪŗfgTY"HɷjU?_>8BZ|ivF%4|_QeC##$ߪŗfgTY"HɷjUkm]@Ow< ; 忣azx ;^c%n= 1Ioۙg)S9N;:MT"`>_Yw]]S҂N a̟?:hYt}s錂j~ٛ >{cPZ?xϮܹ=w|pC=S߹>RM uڲeĈϧ7;lȐ,N~?-\ne?I6Ti']GG#aCKnK->=Xt;Yf܉ǍU ;P~Kts764e~^qv;OzO>IloK[g{umdv~ZG)=8CPˍcGuǽ "@S vg/;в';w_gPI}al'M:Hzp?]9r#CY엷/=YCx_jGzZ"F%@RXHzu_O!oK?]#eHZ4>?V?HZb @ O<[-1}0,ZaaGZS%V&1;+' m_!ha#O:wy鷰N^xraxb a4}. -YpGߝ޼~Y.1?oKnsʕ?/~Aoھ=i-[6mʦ`ߧN;¿]~~W-dw}ޔٻ'-'f/̟#:qˑ#:|lw~8-"8=8A&}R'˦UIUNf…>N._>P]cqZ?aɗ^u? ts{9h}9x~zɒ.XVѷ/w̹o~0dHy*5 !Lm#Z_y%6;Sb  /쳔h rq=͛4j80s7Av6nQVqލdٴ3=84g΂Kxܹe *7pf$[|-Wڟ7Lq< ?/if+_VM#fuEEEEEſ3cvh~?0` :4_훟ua57/̴ wuKu/|6>Y?ӹٳ_9u*ٻ ^Mc5|~ُ>VWz|L$ CgC7gYEG`f?VO7y !ݗ]][y:K:O^g鷯 Ӈ[OZg'Sxy"ںuyE\Bgh|̙^KQ=a¡O˿:~MeˮFyG}\O~wAfM_j!^٣_?o/l8P{փ㦈z?׿,32UE}5>]q{;=ܻ~8WOeUhAz޼KSGG>/QNGyNƟ1?>f?'>^lGA2 YF/򯏒I/o3J??XҲ)柼Sf,'.`]|ؖmMJ1\|1-_w݅$O_ӧ'M:PZp~[orʉ'rHC_^I F&rd䓴>a} ?Nmci᯸xibu}=gm, \@AZ8g_kB:Lvy4^}NmӒO ӃL|5νfkݯ]fy.qQE x׻?Fwߌw&yd c!Cc$%rGOh>\??J- ~F_@CQ''c\[!0rsm;/_̿1bǶruO É+vk7<1lێO|{ >bKfϙhq|{C:^t#G~+05l\zΦ6rĉ_"}xå^O_' qO7OB~~{Ҥo_~>j/y'w+ǮiL*莎a߳΢>S[ͯ~g?n7? ~4~4# __"_ `d)Ƌs>\L/졗@C=)׾Dh{Roc׭t>{޾'!K޿裔&L8l_OGw%<y5oZ[GPK,.➾X|>.9+g?gП,#gk +ey+_jDH B{)o2ygTAM)7_""o_rÃJeΎ_P[kЧ?ɓ;;:.]zMK/gmΝv?ٷ~$ @Oٛ{u~?ٶ1OϦM rΛ6/1N:O2)*)R:\t) fe~EhoY߹ѳ\qn?Oћa[ꗿ{!6l]]ӧ{,-膥` {Mg%`j'5_X׻/y/Dww/\7ii'}|8bܸɓOVoC{q/'~+msơGu:U'_h'Nz?vٳ_瞵ks[Gt\wܹa[)w{r#${&Hٚ>ɔן^{lٰS 6Ҧ^ rEe /h9ov)Zλl--.֡9Ey-п弥:/h9޲STX-][@rjп弋z_NrRbSwQo)Z[]Uߺ^ȅΔ_v+o[-P[/rȸq'?wnA&;!C㦣lmϹNOH%KE% 7{GR0}zQhmYITc6hLjܹP/-4k^[ׯBIK G?JLn uk~>}_<G?C7WX)N.hl.\x5}wvNL?uq!sχV9[U΢.8ho_~ 78{K?1p`8Y !-խVgcŵV]~cqqmij+{.V\)`WߪSկ;ߪSկ;ߪSկ;ߪSկ;ߪSկ;ߪ'S'`};jo߶0>afKgW^)߳G5k}}b]o@23 ~zu)SfrK/x%O=j̙}ƽ_y':|sE{ 7;62سmO*\R*Ovgu. Zhyf!v3׷gϞ`A=Z<z9XۋϔُzO>/nOo`zC_~P9}s^{_;h쳯2t)۾Ku}{=j(vA :o?y>} F>}O{zz}ֿy4.ͣX/3 ;ιw˟5?>7|uСH1u h< .n4z+/|yP?'ӿSO7F_>Mn޸?\O?s8,_Nq=woƌN8„#=ԹwuW׮]ڹ\%̝}σt1¹sϝ<ިa/|ϊ~Ҟ79.hbsClB_`G4)-әW.k7m7G4/\]ciӟ5}oyұ6`_LoLNtمޕ+,7LɶC:ƍcp⫇Yl%%~oE0N{%oI睗N?~sӧ2m3Li̟E<=3?fGeC;ߞ|G>B )__C>K{xج%G ŧxUW9ߌw:0-u։'f#k]楗5 vؤI^̙VїfOo_qřg~/\~M7_PQ:#fZHqh!=NGQt(+r 㝛3gbz?ݞ7͝{e_:תӧ_y%Dqo1b=vҚ(j o????PfuFEEEgVW7d^o_} }Ӏ?/4Wȷ}lW-|unT9u*}H-]2j~f}!Cvޙ}egB-̟_M,izkiar0n 쎎vOwuo'O瞡m1Oof%g&/\H ' |Yo]4asQCmtÆ;?H_pm2}7iVnt>4|w{Ĉ)}uY΍~w56#]hSt :;gμꪅ Yh!7TIZ13p)_LpEN gA|֍@?@?@oЏ_ `N~2O tC>ѴܯNIN9˖ї&M:4ܲK<:V^5n< xވ?H:_x)b5`a٣%Sچg_\[+Ɵ-RR^)e,8I`-;Bm۲.t" ɐS8t^nZ^~yfzSyz)7-j"γ<{Uˣ7Ʊ~^ 6m~͛I}Pz4n'k_8p֯Z'ha{*1_swqEr^ Ǖir87n_|~7|:v4۫]]w};iug &:^+ƍ':Qh}i|Rp8aÊg 5+8?nӦ{?v+PqsF{J{ER8?)PU7WU˳W'p[ W8z\㿪UyX<;⿪UyX<;⿪UyX<;⿪UyX<;⿪UyX<;⿪U_[ۊOT,KY6_TނO T`|=W_ܙgΛS$2ôo Ptyռnh+z":W[e/*ָ.~ !!!@o Qr/a MYX?T6O { YycG@mC^{Gq{gqxG?ߖ^(pNӜ[{N9%y&*\m!X_čC!AGPc%?AG#"""""63J?0`ّ柶-n @+xo< &O `9s< P @(P @(P @(P`Uoٮd4{8k٭<)? _pf4|͞µkʓ5{ ڮkǹή#ݜ5jz|Μnpkvk?S~fOZ5|nIi=km5'k]VSv _[yR~fOZ5|nIi=km5'k]VSv _[yR~fOZ5|nIi=km5'k]VSv _[yR~fOZ5|nIi=km5'k]VSv _[yR~fOZ5|nIi=km5'k]VSv _[yR~fOZ5|nIi=km5'k]VSv _[yR~fOZ5|nIi=km5'k]o} O,Ї7hk'b??_Z?0c'?qyzT'?3-qoBEqOܺs"MRPП@1=?6⯝Q?R/vay팺CaLsϫkg. c{^m_;.p!_j&J½ϟ8?R*FYMZC񔞍ceˎV#dFAAɎV#dFAAɎV#dFAAɎV#dFAAɎV#dOd6'>":ʼn'?%$E!T2BlqgI'GEnW?6>ٺm\!t*K???rTdqc),# QM#GEnW?^z5+V;?0`:k6/_̿1bۼyՊ/_̿1smzjiO+O sҊs?O;ok/////oڷ?0o L0`Oڷώ<}ԾaeWg}?)׾1ej QѾ}kߘ2ay5٨h>G `oe0lTo#07L^ _6*ڷCsYm{'Dっ8;ئ@j&vT E]B /h tO-vTMǁEՐVlqEzT+8j?=nbt_Q5ğ71Պm:⯨Oj6WT MLb+&ZMAUCqSئ !VlqEB%@gaq ca厒h'_.~,S //̿(7@}d^:^rIEq_-7`K p/m_|:oV}Bh_uBPvCԌCf(S">CEEEE-i?P?0`iѢj?m[@vqP @(P @(P @(P @(з, ?ɐ:Nkʓ5{ ڮkv+OO)\k٭<)? _pf4|͞µkʓ5{ ڮkv+OO)\k٭<)? _pf4|͞µkʓ5{ ڮkv+OO)\k٭<)? _pf4|͞µkʓ5{ ڮkv+OO)\k٭<)? _pf4|͞µkʓ5{ ڮkv+OO)\k٭<)? _pf4|͞µkʓ5{ ڮkv+OO)\k٭<)? _pf4|͞µkʓ5{ ڮkv+OO)\k٭<)? _pf4|͞µkʓ5{ ڮkv+OO)\k٭<)? t6?1A?) Ga! <КcX'?qOӣ'?qiqo;}/OfȀמ'im:/vay팺CaLsϫkg. c{^m_;.p!_j#u #0˜hƟW5W/}I8UV94j}_V_⏧l\n/-_vDz'0OvDz'0OvDz'0OvDz'0OvDz'" U5 ?Q/ա-Nv?,MWğM'.񗿥b+?NR]???9*u۸BUm ǦSVYGAA"[+NYeiGl6l:e}9*u۸B:EEeՆaL/ #''BU???aL!DP^')x U݋" 1A{q\0y"(Tu/Oua/CAE-!m^1`?Yy~1b/ͫV$̿1boSO~`|Z~VVC!0y[(ìо=?0`}N`?¬о=?vǰ ,?G!H,ƟW񗍊#_\ ϫFE?R}c.˄@e}?)׾1ej QѾ}kߘ2m/=!§=PoOL?]Q6Uo7mП(ZAM𭷧@~nm:/MLb+&ZMAUCqSئ !VlqEzT+8j?=nbt_Q5ğ71Պm:⯨Oj6WT MLb+/: KK6^+wD?)@Ͻ@rcjy1`G EM#4* թWSM*_o/_R\#p/k۾J.yB܄o?_fD_ 4CiF////o?Hci0`OcU0f[r?J|;*͵͹mb3-g>ϳkU4C}KY~>n3:Cg>.mv/<\ڑy2?qY QKj'#?2 Fdy~x<|^yzY~ʓ3)?qY_åv|vg>.ŵݼ|'zKRⷊh6?1?Fϼ\J\k,?qiIY^v,K~R;Q;3Zn^c~Vc>Xq˥o?3l~c~IQyzY~ʓ3)?qY_åv|vg>.ŵݼ|2/Uq㘗K*~gϣ̓*/'ϣ*?r)qǥ'gg>.SxeSeY\g>.8UW8/{\wTiO2h|_l]e/i1Vd{k-$[e?5|^T,"yK쌓*SkvƑfg+skZy+ʿ|.K7yG쌣5|82fg.5>RS5{Uf3NRfiq4s-m~DKLc~R mv߯Om5?xR|~2em?ϋ[#Ka`U:K>ϼ~|]]}׷;'E]gU+Gmc[jOU~'֍G_>[ϼ)T;_~+Ovv5;Ȓy7ˍRVK> {y&ᗨz9x^6~>]r#?_'~u)^o1~lϛunMYv\63lOz/O_^WwV{K^rҿ?y/?>nv>1~3lOz/_*Z?\7߷l架s|o.?^6'=YnI^PKO_^WK?Үh[Sk=?8cֵ5O2SWf?5gZ5Xme8ѽ+[ y|7_۬R?YRGWog>Hb?O;&eޚJTϼ\ʷo><#ɏߌ#U:_Oοn{2Ͻ^پki2U>9J~#|'d!2R7?g>.W'$3e/u?nK/Ue ,R]WgΙ~/ytV_BG9jk?rYYo/q/_nӳys/6umsmν1g}y3B\kfߌ!eOU?۫LI5lx|lo\ge8G#+{F/?ʎ?>N/=ԗ[jo|l~'-OWAs6Oj/Oe9yיdG Ӫ'u][OS/lOX`:6.SЁ7n1X O _۲JV3il~+rh%oƟ?*m!1+p?g^Sx#Dbi ճcaՋݶc?mjs66]{`msykvWk|_l]e%^ٺI _Ku _fxef/'5|.5|^Ok]╭k,5+[5{Y>kvWk|_l]e%^ٺI _Ku _fxef/'5|.5|^OKo G|IP~R ز_~ /_R>ؾ?zj[x㜯!j͹ֺc0b4.JG"57W? }ataZ` 1=__/n]@o~J`L@V,5iXԝ[?'<"[by;fC!H?-1ވfݺ܆ܡ-fPmsnA7ԇ m*^5*1?)XUCUƏA!HR?J4~ ⯕W/h~(T=Y堿)IkY}?ҳqѺ}}䟬?wOmuֶc~[~uν^ثLU kiGguB#qG*G*#{UÀ@T}נ-Nz?DMWğM'.񗿥b+?NR]???9*u۸BU]ۜ۩97^nuWV\@G5#ןcL+ϱPD_Wc+ȿ6???rTdqu,ËJ %)^FO+QrU[SW.y#4"wP#'e/H-Zq~ٶgŵ1+?Uۜ{9xb?P @(P @mU^@^]ۜ;mgMmsn]msm<@P @(P @(PF~:zWww}=vݹj[x)u]_s#_+z48(׵ҊkVySHKWpW:q)vYeøOُKiR/U5ƕ~~\J]xY0uRe=zU q.)?n<^֫k8+dvYOq,^_a\'ǥzʏe)J?Yg?.]S~.KyWpW:q)vYeøOُKiR/U5ƕ~~\J]xY0uRe=zU qٯ쿳d=RvYRlR:q)vYeøOُKiR/U5ƕ~~\J]xY0uRe=zU q.)?n<^֫k8+dvYOq,^_a\'ǥzʏe)J?Yg?.]S~.KyWpW:q)vYeøOُKiR/U5ƕ~~\J]xY0uRe=zU q.)?n<^֫k8+dvYOq,^_a\'eO_s͹gjzhi<|Tٿ Xq~oie1fط~~\^PY>z*)c<.]S~'XPJd=x=iu#xtRY~Oee;Pq"IlJ9Nd=x=iuǿUJK]_[1.)?#"?<:v???DփߓvYgَǣ#rz{./100xtRY~O^׬zq,z8oa=l[ q[<}7O'@K$G=H|)?^A?'W)?嗐?T^L! Q٢X61}RyE"#u꺲˔r[*Ypo?Zb/o,>I%2?;YXMoz86_]x:ҏ'J8-dN;W/t[p,~1|,>,q=ܓ&IJ, ū%XrԨ%Ţ.Pl\E6X&@ $o9sy癵`{ϳg{̫S1q/՟_\~0yI.TU} lItM &̖SyV{O.?R0q/_TTrĕ'RPUW[b}Gؗ k8o?2^]"qΦ9KJ)!_8~/_Cu d֛q^GGG_/]ZoҾ{՟͹8Rc|UT6WO.o}?[f뎯ޜ{"K6bM[~¼O' aqYyy:9my]&cme7GSjfݰc4jjxsnmrqSҳ~JvK#=d𑾄>ҳ~JvK#=d𑾄>ҳ~JvK#=d𑾄>ҳ~JvK#=d𑾄>ҳ~JvK#=d𑾄>ҳ~JvK#=d𑾄>ҳ~JvK#='~7n/]/җcY?%;%\VSCH_eY?%;%\VSCH_eY?%;%\VSCH_eY?%;%\VSCH_eY?%;%\VSCH_eY?%;%\VSCH_eY?%;%\VSCH_eY?%;%\VSK/`^zR%@W"~>d_#GJ] =jW)#.E5+HRBzzKQ|!GM?_=|Tnʣl{YSxA'+4+_gzt):u?_>R7xumnWQ M_@V=OگǓτI^1?7fCʿ=OgD۪?1ê?f 7o<ۏoWxjxf0>޵_+?hл#v,5zq?g789>޻/_0txs? ͹o΍{.9t4iUTTWo_*<*\M8(b'?oo<5n 㯯k/ o==9͹UÛ]R_59ÛsㆷХ_gJǗ,.kWS]OIv%?%9ڕ,.kWS]OIv%?%9ڕ,.kWS]OIv%?%9ڕ,.kWS]OIv%?%9ڕ,.kWS]OIv%?%9ڕ,.kWS]OIv%?%9ڕ,.kWS]OIv%?%9ڕ,.kWS]OI".Wr7Ow#ſOׅAUTTTTAUGWWWWWWOW_5j ~4j卿=[@ުҪ"LO-+3QşOJYG? zbNGgеXX zSQSQt1???zuDŹ|GşPUq0+*ʟOg (8㊿AG^O3U ʿqߠ#Sʿ*_ŸoБWS)_Ɲ# yj򁃏ˣR#)gǼj0o&qXk0ɚM ()pXk0ɚM ()pXk0ɚM ()pXk0ɚM ()pXk0ɚM ()pXk07W0wY&J^HRUԎ5__?5h7j1.j곏OHRQQQmW54_Ϳ5n7j[^Nnx 6vB,{Od>?O?՟ ۋo1?`@?4hi@F/Fׯw` 1 Āb@ 1 Āb@ 1 Āb@ 1 Ā#qH_:#|g>җpY9GzO#} #|g>җpY9GzO#} #|g>җpY9GzO#} #|g>җpY9GzO#} #|g>җpY9GzO#} #|g>җpY9GzO#} #|g>җpY9GzO#} #|g>җpY9GzO#} #|g>җpY9GzO#} #|g>җpY9GzO#} #|g>җpY9GzO#} #|g>җpY9GzO#} #|g>җpY9GzO#} #|g>җpY9GzO#} #|g>җpY9GzO#} #|gRo{E7r8?oU?4jo5?5SO?3P5SO?5>jߚ׫B碚jg^'d K[_1_l)*K3!'RUl+uOg (BN V 2/şPdKWd_?ş1 91Ȗb?o}.q=BcQIۊ_şq\U)qF3gD۪?1ê???qF3gD۪?1ê???qF3՟:^ݖ'bz-)#$N~"+eU񔲫SէTipqxJU)iV}.T8bffEJ)fڪ???iV}.T8bffEJ)fڪ???iV}.T*2 2bjQƷ][s|ş/dONmqUTTBND`VWGG'ONmqUTTBND`VWGG'ONmqUTTBND`VWGgc?뷧V0ݴ5Iy 8</%? x*jCdSϙN=TQ!k՟ztRgYsSOU:{ZGG3z?C֪???SAVQψ΋PWO 1 Āb@ 1 Āb@ 1 Āb@ 1 7KIN*gqYY\K,.k>!eR|gqY;/ճ],.kzKQe퐿Tv)>곸Y\.G}CR=ڥv_gqYY\K,.k>!eR|gqY;/ճ],.kzKQe퐿Tv)>곸Y\.G}CR=ڥv_gqYY\K,.k>!eR|gqY;/ճ],.kzKQe퐿Tv)>곸Y\.G}CR=ڥv_gqYY\K,.k>!eR|gqY;/ճ],.kzKQe퐿Tv)>곸Y\.G} tЅz7>uS)?m>5VG?4haGiW_{7H5jW ߞ-W]\mXLYqNS rZfaT\K?4hܨ0k2M{E$_KXSPQQWv#>@N}_N;RF5W_5HW1.j43_i=*.6ܷ!?_w^DO՟^0DzQ}4SUUUUmz?4hau7i3nb@ 1 Āb@ 1 Āb@ 1 Āb@ 10Fud(X9GzO#} #|g>җpY9GzO#} #|g>җpY9GzO#} #|g>җpY9GzO#} #|g>җpY9GzO#} #|g>җpY9GzO#} #|g>җpY9GzO#} #|g>җpY9GzO#} #|g>җpY9GzO#} #|g>җpY9GzO#} #|g>җpY9GzO#} #|g>җpY9GzO#} #|g>җpY9GzO#} #|g>җpY9GzO#} #|g>җpY9GzO#} #|g>җpY9GzO._W4?1ac SVu?GXSO?5?3 UO?5SO?#ߚkw*z.柚_@uBL%#-)/ſ1 91Ȗb[7Ȩ ?c@rb-_ŶoQ|)ƀ/ [ʿm .R)_ȉAۊAF]Sʿl)*?O?#?fNWOŜU)Eʿ[OgD۪?1ê???qF3gD۪?1ê???qF3gD۪?1Û[,smj~(2.NR'R8^O)?_}JFW?]OfEJ)fڪ???iV}.T8bffEJ)fڪ???iV}.T8bffEJ)/#,a|Kڵ?ǗW)Bfd`VWGG'ONmqUTTBND`VWGG'ONmqUTTBND`VWGG'ONmqUT6~~{o#M[ R#[_ـ?9D֪???SAVQQL(uO=g:TPEUTT9ө*J=dSϙN=TQ!k՟xjxx[uĀb@ 1 Āb@ 1 Āb@ 1 Āb@ l~ tim)rKQe퐿Tv)>곸Y\.G}CR=ڥv_gqYY\K,.k>!eR|gqY;/ճ],.kzKQe퐿Tv)>곸Y\.G}CR=ڥv_gqYY\K,.k>!eR|gqY;/ճ],.kzKQe퐿Tv)>곸Y\.G}CR=ڥv_gqYY\K,.k>!eR|gqY;/ճ],.kzKQe퐿Tv)>곸Y\.G}CR=ڥv_gqYY\K,.k>!eR|gq]@~j](c9Sl|!;şOSCh\a4hGv읝_5jw*W_5rziz%~߆ʔ? 5aFF4hGƟ:?4hF4hٜb v'qT Sʿ\IWbpmşP .bOʿ _k+ƀop9{RUl(\[3˹ؓbCGڊ?ş1\Ş6iOSO;󁃏 ZGsarG5eG7Y,72o 4y _"pN-#sarG5eG7YSqc)8&k?79֚2kqc)8&k?79֚2kqc)8&k?79֚2kqc)8&k?79֚2kqc)8&k!o{by s&aJ^HRUԎ5__?5h7j1.j곏4HWUc-eT_5W_ۍ$5F:i_mzoxa}w; ^0Su7!E)ʿn Q????AG?____߶  hG?hQwƟ?[@(1 Āb@ 1 Āb@ 1 Āb@ 1 Āa`d[7IqH_:#|g>җpY9GzO#} #|g>җpY9GzO#} #|g>җpY9GzO#} #|g>җpY9GzO#} #|g>җpY9GzO#} #|g>җpY9GzO#} #|g>җpY9GzO#} #|g>җpY9GzO#} #|g>җpY9GzO#} #|g>җpY9GzO#} #|g>җpY9GzO#} #|g>җpY9GzO#} #|g>җpY9GzO#} #|g>җpY9GzO#} #|g>җpY9GzO#} #|gRo{E7r8?OZ׮]ڹU֬9ءm*SK/Ϳ4J_̲h_iW}SO?5WL;柚jл+QLs+Vvhsg^x{QC[8W5ykqm?>ȻooW{'e~?|Z=>sk8g #F]kcn;6۔rYo O=|;f0bĸqvY;m&u9_>fʇ?~3pnfƌSN;#_ٖ|7Dc@O?.^zmSʿ^gxRqk+şP:8<_œ⏋^[)ƀי)*6ۀwI/] U=?]r;x_aU=_Ք);OwG>ܻ5eʄ ^f]lu6tzWl^c-\x};cp8l;_uե:Lܛ7*}o {͗]qǝ}WBm1O2s';wwiGngo s-\q-Y=n3f8w)ky}αW_7νŋ?~p啶0x㏟?ǧOapCϝwdۂ%\syΝxbf2S,;ȩSsJ_{;l>sMt=3Mٳɓ[{9slak#L98箿=ֹʕ_AY^7ϣ:i?˿#8w݋;]z…̶Eqŋ?{OݿA^s2%ga{=d3g&ƀz'3Gxk 5jWoGWW_ *=h[6}V=svsا}S-Շᔯ{Y?y ӦٛE٧s9ūM_-W͋/>{C:sW-o˖Yz{Y^mzgx-@X.`B>u ՛߶mו.`ϙ3u9w ӧc8[WoϛwƼe}3&<߸{3_x#?in{L+/Xo ^9TiĈkoK<ӹ3H 0m]-\t?n«_Me]nѮgxWYuo.uk_Eg4{?şE_fpͿzd?4jTK<C?4܈柚S.ͼ5޳#m[~¹-'빅{XVKدmڶfzj=fn1u':Y~٪xֿoku}6cx[o5e˗X;TcdWxoŊkc[,O6No^f: +lN >y~{}+_bX8\]Ǖ?3,{ɓ:?8Dox{#of{3;,n?/ӫ_v;,/W_mo~spKvҤݹ~+?9oK.X`<-gsϭ\Gv?u,ClK+iQ:>YR@ވu 2_qCۺmgoh;kPvֈ}vZPvֈ}?_jgC (ET;kľ]DV!"5b_O.YV'(M)5=t/-z9̜/qg}&oyO=?Bܹf7{t = _{ŊKc7g6{s@kzʹn}͹κ2nv2eOtK_:KduCzʕK/3o^О~7i|MK'?ѳΊ;X)Sߪ/'k6hs=v>-u{^gq O.>e}=>mmlً/~ g뿮#=t}Ri?usu,6>عS>Vx;\Kң {K<&'3fϹsϝ9K_7{{=NV|A{?S؏F؛3ujc*8{ʘt:{`Mo1h='w?ec61>c61>c61>c61>c61>c61>c61>c61>c61>c61>c61>c61>c61>c61>c61>c61>c61>c61>c61>c61>c61>c61>c61>c61>c6ֿ^r[`zG9]Nvq^wwnڴ.Yr饟s\s]Mб5aĉ>ix;ۛ~j]vw_l0n'?yiMl7ݖ,Y2{<g9雹|c ~u,vKxE9o5w޼6~V(lw_=*` |yD~+pC:عsm8ùc=pM{6]>J3n;@s xD%ןz/sB7A{>= cܖϿ\?ԋOm쌟x{. o}oyKV.7<{㠃>y[[6B5kd Fmfi oj :kխM5?ۭܿ1>9.c%qG4~y\oFN)o ظbĿWs-'eS)Lؼb?_>rR6X;O˴+NSs-'eS)Lؼb^ oJK.?a}jzƌt ?#pcN;m쮻췡/jMŬo |};.Om!p͚<ڵ>˿ T/7G;wn=w}`ۛ޴#Lo{rfP_wUW''OÎ7ݻE٥?4{nۂ7U<_h?+[շO{cV#FU;l.Ln;cr[>Ϳti=:,C wgԚ=_wyN;5eor;7nA}1_wmxe>P?.ֵmY7qm*}oxWw''ԏkKUGx"՗> Bm.g:|7@}gQ{[ۛQWc-+L/]QK{?? |U}zAô VFWtǣ;B$*@wq7g@b8RuǛ?hLmT*n#_wxT)Ho?πODwq7}o7/=lo'ןi%w?fC:_}wl sot_Moe˜;j/ #sOsqOS6~;ۂ 'د |TN[p<{n9¹)ֈۂG{=pg/YbzݶP~]cGq4q5TUWiwߕfW{6lk_{ϯ:~KZĭ?9g&M{?K矿wn+/,z)O߬ !Q]I?v i&Mz81}[ޏ۴ڴ;Kt@Mt-MLSojq#ZSqM8nr5eGdM&ZSqM8nr5eGdM&ZSqM8nr5eGdMftNAI_KoF gSomh )Y=<=k#lɒ;pG?/>;89^h 'pѣm rk󗿴Ot-}:?"͘q&am-Ksk{|睫'>{?;o>oVǡ{'Of?== /s?r-$\s/>vakFY><3f|m_ 6{ooC߸7{[o=f:mwxvۀ<~,v?w:lMS[!۽UԯxTK/hq7?8yIu]Rj4,O/~a̟qw/쬳\z]UW-Z87e ?>/%}I:WOI.>s|k+(<,S+U)ZHG*I)>Jr___c@OnIe:GPkG5hM\G5j[^^7β|F)TybQƍn;&Ow_[Y>qMama?7Ν7߂s'B-qٯ/Xpe"o[˗vڻm +V?_so:uL{][ߪ~L?i'?}B*f9s;X_4;<xe^T-? k ;P_ }}ƌ%g[y9ddÑĭkގx v 0F mν5CkU-mUof`o׾v}8h3qGc6)KmA?RV[%Jo-;  m毳s|s}[}C]pM73K.ફlA=o <1/@gĦ3ΥGS)TTqk&'<?Sk_ş?h;vZɉ?4ha֮YkgdH!9Uõ_}?- /tnRK{h}^ԩӧǟH7-?O[h\_y|n&?ejn?e{Н[_Ŀ_k2Tno;}n>My|ʔ7cg̘99~Q-DΚe VI{+mqɪa&ao۴i}}-@YzM?N(cǾ νv7mb{PgW۝ u?n__}rݎ K }G¶\tU {i ' msoa}18`8η^~y͚X略 ۆv(Qw3ُ18wQ%T_d^W.b9|To ϶H_+fM?_-Ok5W)˧H?m^S.")6kjmSʕ˗'<`}qn+SUƎ8T?|MRBn)gly=K>s=K◾Gm;^x⡇y-<%ŋ ~~OޟwCsܹ '~{rk?VEf֮}{<|Wm=cAl7mc GĈU{ lW ~UQE[NwpC{o~gmߥ/~q5Sع>}y'G3fm_dMz#zy\K>#=)o>K>#=)o>K>#=)o>K>#=)o>K>#=)o>K>#=)o>K>#=)o>K>#=)o>K>#=)o>K>#=)o>K>#=)o>K>#=)o>KS "@J`EB6no׭n;ٳ; m}sX_oV-ۏW}~=]ws7}>gλmoigcmdo~z[^{MhfB['msڴOwof֬ӝ{*ğՏ?/;9{gyUyŋל㎧owڳG]v٥ZfSweoRVzֱN.} #FI.o^K9R/BR_HQӽOW4!u)/^G{tS#GH] =jW)#.E5+HRBzzKQ|!GM2M7]no.̟s=߷^ư7e,j;pS̙Fn[n(4 v{;Gxe{s+;s4/,-g^v٭/p~/ϟo7~#|{߯]koLz뷾u}+g8w5fcz'ƶձkTs5V[N{M{׻r?ʹ믯0)]_~5Y9ľP-}huWƎ4>D1\[;C?Y[HW&6ժ'O7CN>2;Y>٧*sO*u˭?=Љp+~ؾq'\@_xe zԒĐK? ^rK9~Ҝe^R*LKөĎ)LK}sOLNU6? W?_>R&ƀ+??J21Ul7~l!(-'lg0L1Crڶ|#~o|ù}?췼O>~=pn뭷pݺΛo7W\a m,{MoOm+ĉko=Of:eJsi3>b=cB]-;73[H.mxڂ`yq__vy}Sfի_|1[h\ӟpTS9=+.]> >!}-yx≟yzgtv|[_w.ߍu믿v8`7s6D毿/٘/9ϯGBu=ruf_>qG{`o#FI'>O}d/|Cg)k_xᡇ sv|Tn9{;߹N[n_.sGx-Ϝyiv'ULΝ{7~vf͘ν5nk<^3}~Ѣ ;K/=hΙcy[s",|Q)W\Q ֭D?ѣ̜{Gޘv_5s8gCCuiWG_̇/ø:'fU?4h(v݋?z7DK]>i^W0N$Hq m}߰O/ZƠ {x .8 cnsW_=w-ScǾo~sGUbuǹa^1k&- {} y =9x$9,^/-Ybz)~*QZiP֭[ܹ>gSMvq}' uUg?x瞫IٖpU5!:0=C֪γnj /y^` ~}9/]z!vl~-/?ܣ?7om7u>eqQv~bk_.=P{/{k袳β,1+~3^ko׸q^п3߿xq<ƿү|%Cc'^p}iuzgNqƌxΝ̙gY} w:^||>OW7/ވycsI? ~/3N9˔)<*QƎ3fĈwՂx[n [?<9ڃf}o '<@loOlr|_ܻew o!4=ȑoo9pٷ~ v'l|ۣ'|gm?q]O3v- /?Uo}ߘ1xgȔ)՗+;-.׾OCݶ?ş/ONmqUTTBND`VWGG'ONmqUTTBND`VWGgc?#Bx0xD˧D=|xpcsڵ+W>_'7#ν< 'BC;8"v4b+/fKli2-+W.]j>c/͖{/>Q{FCsV^<^wtnmƌ ǕZ/3\U>kq_,[fu>z9ֹ0a$soW58 {xh~+~{"5k6;h.;d+?+ X\ǿ]R?3[ySjWaTGGG䪿VQ_|bF?400ΖZ5jC/nD"O/'n~@c7^76FU “&g罸&v ؀lU~<~BUݩ=6CowWg:;n_αc'LFӫ7ÕNm[~2ԩվVmyig^1 ߎv1kQI5X^.Y;$1Z#"5b_]DV!"5b_O.Y+v7|½~sc]=k{>,.k'`yeޫxϤy|%isG}n.KY\֮)aqYeʞ{)KY\֮)aqYeʞ{)KY\֮)aqYeʞ{)KY\֮)aqYeʞ{)KY\֮)aqYeʞ{)KY\֮)aqYeʞ{)KY\֮)aqYeʞ{)KY\֮)aqYeʞ{)KY\֮)aqYeʞ{)KY\֮)aqYeʞ{)KY\֮)aqYeʞ{)KY\֮)aqYeʞ{)KY\֮)aqYeʞ{)KY\֮)aqYEvp]GKaqY;7R}Wnڗ_n^o~jj7o^{,i^W mNo P啵_.ټb?_/bS)(낞+NSB-6X;O.ټb?_/bS)(낞+nKͿm˧݈"sh_-+o~*^&N{3>}ҤmcȗƩoK*tu-7oU))'?S/zmGOO?4haƋ^l^ @VzM.'O⟋^[)ƀי)*\J3,OWV?ş1ufqxʿ'/R)_3SU<)x鵕Og (zYƝ#  >.J1Y;)8ɚqc)8&k?79֚2kqc)8&k?79֚2kqc)8&k?79֚2kqc)8&k?79֚2kqc)8&k?79֚2kqc)8&kO@7>2_owT&ƀ="Eʥv) jGT?vQS}?UTEo΀_kwToͿ5Rtr{OH7ܷ!b  E)ʿnAD ^TTUUUU.i4hGO2z1~[@ME 1 Āb@ 1 Āb@ 1 Āb@ 1 @֯䗎Cq#=d𑾄>ҳ~JvK#=d𑾄>ҳ~JvK#=d𑾄>ҳ~JvK#=d𑾄>ҳ~JvK#=d𑾄>ҳ~JvK#=d𑾄>ҳ~JvK#=d𑾄>ҳ~JvK#=d𑾄>ҳ~JvK#=d𑾄>ҳ~JvK#=d𑾄>ҳ~JvK#=d𑾄>ҳ~JvK#=d𑾄>ҳ~JvK#=d𑾄>ҳ~JvK#=d𑾄>ҳ~JvK#=d𑾄>ҳ~JvK#=d+ȿ1ĉW)TT:ПkWOi柚jٟg柚jVoͿ5^z=SO??/:!`&_ڒPdKWd_?ş1 91Ȗb[7Ȩ ?c@rb-_ŶoQ|)ƀ/ [ʿm .R)_ȉA֟po'ti3''bNV*~Hm_̭O3m՟a՟8#VVQQ3m՟a՟8#VVQQ3m՟ͭdй 5?QkmO?')wY)C?]ş>J#+SʮOK"sy3km՟4+>W?O1VQQI"sy3km՟4+>W?O1VQQI"sySY@χS0%K+!3Tu20m+rJ'_'Cݶ???!Tu"0m+rJ'_'Cݶ???!Tu"0m+rJ'_'Cݶ??cY^=ෂIҀ[x-Sl{TQ"k՟ztRgYsSOU:{ZGG3z?C֪???SAVQQL(uXF<5u^zb@ 1 Āb@ 1 Āb@ 1 Āb@ 1 6?ֿ^OҔpR9ڥv_gqYY\K,.k>!eR|gqY;/ճ],.kzKQe퐿Tv)>곸Y\.G}CR=ڥv_gqYY\K,.k>!eR|gqY;/ճ],.kzKQe퐿Tv)>곸Y\.G}CR=ڥv_gqYY\K,.k>!eR|gqY;/ճ],.kzKQe퐿Tv)>곸Y\.G}CR=ڥv_gqYY\K,.k>!eR|gqY;/ճ],.kzKQe퐿Tv)>곸Ȯgo{Gm?.˿1o 6OSQUm!4z?4hG;jN_5jۻqEW_9l4UeJM?oÊeʊs?şOGgӚ0PUUUUU Z4hGFI?4h Z4l^ AKؓWl8*V)r.PQ1Og (s'_ņ/?c@7=)*6qT Sʿ\IWbpmşP .bOw'D)ƧN-#90o2#UowΛQM7/pZ8nm90o2#ɱ֔a_5kMQ5YSɱ֔a_5kMQ5YSɱ֔a_5kMQ5YSɱ֔a_5kMQ5YSɱ֔a_7=<9XvGho s/]$)\jǚ΀I5h5GC t+*vڱ2濚jFvQGͿ4K/Ϳ6Wq^7 A/P)"T?_7՟ ҋo 4hGƟ {HOƟ-wwub@ 1 Āb@ 1 Āb@ 1 Āb@ 00$C8/>ҳ~JvK#=d𑾄>ҳ~JvK#=d𑾄>ҳ~JvK#=d𑾄>ҳ~JvK#=d𑾄>ҳ~JvK#=d𑾄>ҳ~JvK#=d𑾄>ҳ~JvK#=d𑾄>ҳ~JvK#=d𑾄>ҳ~JvK#=d𑾄>ҳ~JvK#=d𑾄>ҳ~JvK#=d𑾄>ҳ~JvK#=d𑾄>ҳ~JvK#=d𑾄>ҳ~JvK#=d𑾄>ҳ~JvK#=d𑾄>ҳ~Jv)7н mOşOG?57Ě柚jyVj柚jYi5[oͿUsQ?5sz2 f-oNY}/_ȉAۊAF]Sʿl)*K3!'RUl+uOg (BN V 2/şPdKWݟk ~I8!1sz(椿m_̯O8.V*ʿ8#VVQQ3m՟a՟8#VVQQ3m՟a՟8#VOfKnPE1֖\_sqrK?2xJU)S4BRq<*ʿ4+>W?O1VQQI"sy3km՟4+>W?O1VQQI"sy3km՟4+>W?O|xa1( [Ү-9ĿO2C'_'Cݶ???!Tu"0m+rJ'_'Cݶ???!Tu"0m+rJ'_'Cݶ???!Tu"0m+1֟ ~+nښ$ U?WA!VQQL(uO=g:TPEUTT9ө*J=dSϙN=TQ!k՟ztռ.1@IDATRgYlgS[z'Āb@ 1 Āb@ 1 Āb@ 1 Āb`c`Kk$M '],.kzKQe퐿Tv)>곸Y\.G}CR=ڥv_gqYY\K,.k>!eR|gqY;/ճ],.kzKQe퐿Tv)>곸Y\.G}CR=ڥv_gqYY\K,.k>!eR|gqY;/ճ],.kzKQe퐿Tv)>곸Y\.G}CR=ڥv_gqYY\K,.k>!eR|gqY;/ճ],.kzKQe퐿Tv)>곸Y\.G}CR=ڥv_gqYY\K,.k>zwS BY߆TkK7SԊ놷S o_7KU/t?/??>}q^GB{_oۿoGq^ҋ!ۼSmH8vk>WgyeĿg{Wyy۽moTja'_5V^VRgޠ-,ڿnc qZVʿ:VӯCyϳ_=>>TT,w*|J3SY䣣M~/3*W[l7v/]:q^+II;5jxsnִe&Oe (7|%SiLWsg1?6Vvy~^6m^ni⯛(ş--6>'hb/#27X#̤ (?D74|=EٲH{BI>q1zQMM§s62o tbLcM &k79֚23OU &m$Gļj0oCN6v@<>uK3V0o2#5!g66}s\oS QM2<(2zա\ϗ 鮅qÿOFn/sՔa͓~dK6W_愭4taO[O/ȿ1Pf0-; u=-ş1䩔k7VoO¬ Gd,Q?d7_/]Uwߣx/c6PG㕎!޴yܮ?\W7T=՟+%Y)|޵=N+LNױ--8Ү? ]1iMopyWW q9⯿y{_f?7`BR7RVfմkbsV_(jxsnOozU;%?nj7re?ʿ\DUG+4/Sx6|Nj^,9~q,9~Ŀ1c&/SiW|i(^GR|+T_cT7+y%fB[/c L^֯cC篶)nRI77糹9\_ye7Xzo'͹g ޜlx v;oVvww9vߴgR$ot_Yyg\4~oIxv M#U?g\Y"m˿__|Po)ʿzN |隼 h%\VSCH_e)9O,.kSCH_eY?%;%\VSCH_eY?~Bp2O7==> c=w?}>kGH_j>kGH_j>kGH_j>Kiz)^>GR{)^>GR{)^>GR{)^>G?~_CH_eY?%;%\VSCH_eY?%;%\Vy~tGH߭_Gz#}~q=N{!|8>𑾭#}׶𑾭>Q}?6Wɿ16_Qg%fmş1Ϻu݇~晧v͹iÛsr7ޜzhfva͹#GylJWOkWQg%fm4i>hk ԣs.p[TF**((($xH 5X'*gQ+K*%PC+0KGxWJK%&&'ox/r?g3ggy}|`fw|g}?# ?8Yd%?v>_L1JM@?ǟ,"oVۿVۢVR>Vn7֊s;Պs; &Qھ9۞wA$ $"ey;FƟDDs?7~'?0h52<0R$Nh~ksҾ< jŹjĹp-Jmt~[.Vɿ0-yF{gGc# 300# 300# 300# 3t@Fi(/Qw/~HNm~rG JL\+ɷ̷w|]mo)7㟍Wп҆b+Ɵ]FEn+OyfFEn+OyfFEn+OyfFEn+Sd=^ ,OKRmc|/=ȷe|P8׵Vl=X+Ή_}<+jmc'/;p/du??,2y2cѽ8.OS?<1^')xjt/?Leqj˺h keO/G?}ܒZqnZɾMY4bw%K/vN+rG[xo^eڈRR100c^m{41Se"{6O1f՘RdONNJ[a d>oԊsjŹ\Z߷=r<׊s+={:ݺid 2@ d 2@ d 2@0x U}![qz!VZ4{܊k ת V\^խV=^(ZB|nŵiBתku+U/eoed}jeO~Zqnhsrܭ=nV_[qzPnŵZ݊kr+U/V\f/[qz!VZ4{܊k ת V\^խV=^(ZB|nŵiBתku+UOʭV_[qzPnŵZ݊kr+U/V\f/[qz!VZ4{܊k ת V\^խV=^(ZB|nŵiBתku+UOʭV_[qzPnŵZ݊kr+U/V\f/[qz!VZ4{܊k ת V\^խV=^(ZB|nŵiBתku+UOʭV_[q56{] ^uNr՗l-ҟ_oJ6qsdoKlhgk92I6ѿMgg񟎔0?a[ec_____*Kri8r/_m3VAT_ t+u+JzOU_? -M@w \"_8Cd!%zOݸr300#m[+_߲W} //o9^-̿̿̿0޺2OsK,e{^6꼭jŹj%;/9Om*?ϟ7v?6Z?_3?+S1=/6Ɵ&W*-*l?_ꌿj|GT12g1P}Î@C;FC1&ubm:/[WM!e6<׊s2OĎ|j{od%@esJdu6%SBimJ5|MN`@XJ߈0~dR_Gz'/_gSzJ*</tT^*/p3^jU (wv:J!Bzi~Ȇu_d O_MΩvSj3 3F7a]yG()oø a|߱?v}? _gd| :/EFGd^̿̿̿Yh{̿̿L{NL76__xl[;R+URK; .KkܻjŹkŹ-keI&_Q+uzw^2@ d 2@ d 2@ 닁$W M59_M:X E_z-埴:7kŹWkŹ֊s+j9hFO& ܶ\ZqnZɖp}m}Im5|Mµkj'k]V;)= _p&Ii~=2^d G˨E5mbԫ1hȞSz5-Ӵ1Se"{6O1f՘RdOfaa)LQi??bi7Ћa d 2@ d 2@ d 2@ d w@ON'iR8aתku+UOʭV_[qzPnŵZ݊kr+U/V\f/[qz!VZ4{܊k ת V\^խV=^(ZB|nŵiBתku+UOʭV_[qzPnŵZ݊kr+U/V\f/[qz!VZ4{܊k ת V\^խV=^(ZB|nŵiBתku+UOʭV_[qzPnŵZ݊kr+U/V\f/[qz!VZ4{܊k ת V\^խV=^(ZB|nŵiBתku+UOʭV_[qzPnŵZ݊kr+U/V\f/[qz!VZ4{܊k ת V\^խ^..UN`9_m iz?RCڸbs?fq/_n\"q/_l=5;ӞacaimMvG ?7d8pǟlTh?y1L{Լ[}{E?0k^-1<W4oG\ϳA{E?0k^-1<W4oG\ϳA{E?0k^-mCvw_PoOǡ:o^6Yo׏Xre^_EM[oO!1tbm:/MrC+F&ZMǡY~cܦʬtVnqeX+82k?obt_571m:Ok6WfMrC+_ t!K)Mê h _͓>+2/?5Żh_@N:ϳSQ/$9j^"x +9^L筯5> 6h?_c7Dm}?㯑?Hڂ?E////o98pO-jl4>O&E d 2@ d 2@ d 2@ d9 lVՌ<ɐS&Iis X'?yOϝG'?yO8߼w1+(?yLu@fx͹{i?,&lu- 0hϳMke?0b{?6^٢/f1//'Cz/oc# 300# 300# 300# 300# w@WiDQ^~HNm~rG JKoBS. "_󏍧͋%Ɵg`׼[by6yh>͋%Ɵg`׼[by6yh>͋ ;6"r㎣8O-֫ܦ2kqUn#`KB˸tt8?Ɵ[WM!e֘uVnqeX+82k?obt_571m:Ok6WfMrC+F&ZMǡY~cܦʬtVnqe2K.b81XtX^!ceeee4ǟ`?8piݣEFgizc d 2@ d 2@ d 2@ d 4ͪ'R4y]V;)= _p&Ii-qB?_?8k'?y'?yOGZ߼.f'?yY  97p=#훀Cֳ:'`D3m_3.E_cyYL4s٦52[?`e1=ƟgLl`D3KW']4ڃ̑QݧdHE32|D>Oa|D>Oa|D>Oa|D>Oa|D>O᎖"j4 (Ӌ}ɿOBY!C6^6Bv-U!6bx e1aT붸bg???aT붸bg???aT붸bg???aT붸bg???aT붸bGo?DO*ʬ~$k^?R+vy+zU[گvc9݋S'1m+a1?h|mZɎgKqThŧ)rx~gT022rIeYkܬz倪n0o\.俚GU[_jiEB1ZM[XNo5F!j%}(!d 2@ d`1VVJ-ET5mjV&jUM[c_*ƦoIbkkV\^#ZhV^zլ;gŵ~5ZYj#jVM d 2@`zoիzV\WcʫUuaժG=s@IDAT1`ժW:ʫUWcʫUuWƀW^5mI'jŵ>W'?Ɵk^m*ʇ:P 2@ d gzoTI]?O([Uc,b+KE[W;KMTkIb5mڏ3L}ZSi wԋjM%OUnj\R 2@6=ʦw d 2@ 5W-8Zkb`ck{s? w6cLzt_f X|yF}_zt_f;72@ d 2ж ]M7:o^6Y860~N8?ƞ[WM!e֘uVnqe6 O.vaSpjNph XOŮZO(cՋT0cg) 2@ d 2~~نGkY SLTrxd1yRoXc hc?%S'PnVRC` dR"*g-{14+OOa[^sZߗC"2@@{3w/iw&1_<ƲK-ދ=Or=mxBl n| 耠1kZmZ-o}Fp~kmoZ[ן&e d TA'\w@?Y?imO_njUCnSɫ 5|MUkP_!^պɫ k.Z_-8 V8׫GzhiKWXk"Zɫ[,5y-]{v~V>7n;뮝;nH~4|Mµkj'k]V;)=Oۿ]y[ϖf]9}-͸ƴA ~?XS55|Mkr _k\&55|Mkr _k\&55|Mkr _k\&55|Mkr _k\&55|Mkr _k\&55|Mkr _k<g=d@R7E/vﯼs;| -\V+u[Wzvn=@9#g>}v1&}?~ j~,[\sOӈ "_r )]4 ):6gs3lQF<}'>'}kџ/{E\VGz 슭5q(KْûX%ni 2Q|WC~ɋYן4g||cOOg w_4yq{??w&ӎ Y#_ֹq^ ss̘1u*z0jԄ MկqsuQ볩cm|"8׵6W.=n*8Ͽs-3/md[?s\?Km7W^Ad]/[t g+}c}qֳ-\h?՝=Ț5a^YϞݺmu{5+V8dK/8.}ի3;[o=z8קOݻ;GLQ x_uY&;S\maһ7L&k=…/+ν<^Wp]k~u$pds/H|Smn;wevÃ"nchiy5ygkow+~3pÏ~4cs/?>cN8C{71!g dc1 w7rVM?g^cێouxy{H?G??qn(xm?EJh-,ړpaQGab`-Z[mmKG;> fΜ2Ź1cfμ^L4|܁ڹ0?~mW_gve97~h14jsvs`bEF_p{Nӧcbyѳ]k_}չ; /tn.5+uv\r' ^ۯ /+{ιn9\}=ԹISg 8ʃϟ9_{'qvg5~sf鞄~cǞz*<N8t-҂ɓ뿾Uydu.Eu4|UW9|kqCFL0ϛwm?si]qE}}H 9qNs --[׿Ξ ˤ{رe[ȑ>o~z9od/-VsL8wq @~YsO<ؖ- 6ȿ1Wg~wicؖ^g{{K[3,21%rhc/|Rĵq^|~>۹o|SsJ56o}a=3=KLuZG?MƒvrŪ'vel~ծSț7%9xƘ~moו5z;ya(7Hgo%k[o2LН~W&v"NdɣbЇƌ4(y䦛o{:O',qd 8=}gxJy3رI?}{c/pO͆f &q=N9xm|"T):\)~=p箼6q-x7x{L+שҥ>|`^&N<8lcgcbZBL[;1~|~^즛. bo?Š ~:ں_tNO&^ =}IW\'1ܧ5 q͚?Iv=zw+CeV{;dE l7c-{q}pq]8rk7_zrK ɀ!mƵ_sQI= 0י|ԏψ~̿3%Scm=G&Nn޸;l_>+He}k:uaþEmѣ&|}_}3xoԨH׬YSO _}J?Ν׿szʕX'zҧ˗W/,cfY߾=zٗ[ЫW֟导У#.vu睁өӪU<ӰKG6;4qefV_$Xa|_G^:wzkl]Gz֬ii|eѢKn={Kڵ8®]wKn]?zԷ/wWݮXsK9;oj,qǥ'18eʍ7{,N]{͗^_ɒ~l*{cxL NG& rns#F ҷ/_?><|~r֬?icX T/~78XBp:u0+7wX!/|q1ez%X>?<LHzذ"OG@A*=yW.T{ذ `ypɘa|UsGag%yw>I,~-FM{tLP|Pr^|ΛSJƃCԉ'}]1~IyiӰD8l/5Xbk,A1c1?{~]vu=gÆpk`Lfo {/<<ƥ##+^|A=G뗺)Tiݴi&B,z +NZw<# xdcyg1t ?άYx0'\>~ԨĄxVz'epރ7ooMސC?9΀{d 210PO30n-hȒGGGYy;le#Ɵg7̿̿̿ǟdI \"v?TVl/xo?zf->")ymĻw8&x1%|,N|s}̘: j|O<8?8l\,oC&3`z1V^g=;6q~x3|lG8?<󟗏lr˔)x0⠃.?nږKW_}晘P1 urℑ -->1! b'5`]twyƌ|_v3oo{-(܎wfs6A8onQu &N8ah@N_̹o|oMp-xڟv޽Ǐ'G8;'[ )SN: wK?|7~m8̙ޯ?)S;~{-Mrxy'8?jԙgAXЮ-VX̹/"? OL~5xpW)--x7p߬F'6#F|\k̟X:e L WxY,. >dYga±\}h<&'M*N!}đ]}oo4 +ymDSS[nYnJ?zbRX1cmgiD|1v,ޠk?ѣ NJ e׷n뭳ȼ|H׃|3D ??z]oE2ko Rh=[9#W*cfyygԨb(SzkĻFvbZo%诸ñbAXN(h}SuO meU=B+U1`ժWͺ鬑0TUʫU1`ժW:WƀW^5+VgͰu&_NԪz}˼}C;cT9E,o (? b2;* q~UWaw_|8,=z9s٭>#D,`} ; Kf&6neacܸK/?t_J:{8۝6-Ï9K;c&,җ~:Cxd0q7dO@sLޯ(}S'Lyuc x` V^߾C`)ҫtM:Lak?>lfv5>0u7oK뮱~Oa=[b\_i7_Ą~+ԧ>`o|K0͝oGLH̞TCҐ=H8/Apa~UsyXpOf ?aaۺwA)3g^{-&N8ap8W />8|Vq>}㩧N7%CDYLsa __yԩsN ¯g Ç}t~꫽__w{ClmuSf=c2=o?pq#Hን .[ճ uL5dN;"Z|sL'Nqn2sSO-Z=Խ;̞M3տ_"%cg){r\g~Æ/įɹwOLT?ovyYGy#;CeD<|1F~. k/G X'd 1+yUY@vߕIڭժG?gmyŊ:.7|6u>x0Z#ǏKv8|Rx vEX꫋o '` O)[n~njb|szƌ3ĄĀ~P6loЌ[8xgyxiĈ_[lѹ;O蟺lI}8O?0Z?]Z'm(#F|[M F~cb^z …˗ t?ɞ>7nĹhoިzstc/҂ enޯ}l<'x1"G8syDUoDŽeŭi'=d l5-b? PHxB[źGU¼y=V4W/p}S{s'teũr|ȑgsN<[ro SN9,L>Im6r$: O<Y~Xqw8Χnw| 'Ɩ:?o;/Ge+V S9t1RscŌpbb K_u]wa߿&CQWj|R[/yE'믷ިgμ|S~}mk94x9:z=zGDgժիA^{'qWۯѩƯ~5m/P׮]uĉa\GѢᑰN 2#b7.MY&.Nu?K3qHESv_a=0.?~=zLc㲟Od\&UZZ0m--˖efÆ woW.~,2׵kݸU-{̞{+q=z ܇ϟJȔu׹b򮻊}qM8oΝ=w ?xK\qxGS~b({OxRtSg+ožQKΙ3m1W_ ^G<#ݿ;^{w9;7?W=. 8Z.KC쾻[f^Sh/ } $/Ǜlyn=~5)_7xS0v.}]>ӾZ>_7ZۮG>}Ա Kݺ~R\hxEimk׮XQd`W>>VƘ6зu}^1Μ~zlOt&8O; l | -,o[<8b-c-\w݄ XIuEtA1Y޴ӟ)soA<^81ΘV&h4_qǏLz?1{o NQsgD\ؚHg6kVh ox?@jOCS՘8׆~>jOyKK QzJ6w+'ucFqm7 d/-E^?us7l/I{=<~8p ? ;b/q|^>QL\r ~33KW<oO87?ǎ&U.ШE\A[? 0ޞQq5oQ;Х <ru-5Ɵmы}s̎SpŋGi_0]k :4/Y~"q^ :N}-C^lW~i{,}I]i_I'Ɲ:M'JGݺ'v7'@zݟ…<|5j(j]$>Ez5+ p|̘FW:z4.~5kZZ_~ =L;g=bDɽe^|KĿJP}x?OY瞘n-N8fs^}C7`3>O|x V8q~+žDr'Q>eʍ7vsXI[1,[pc FƄ{q~ʔ1c`'=r"ݺC~`u>@O-{W/o~XF%K̹Lpcf?!bѶ.Oq)!d FW٦)bK':y*8ZA:nbm d9qcܦʬtVnqe7S7 A'm\c=^}u<|㠃; )xc{/maX)"mf?lI'_u>peos筶x6tرh={] O:)u׍͘[k_sLO<5ʯb'(?Ib?b-xM?}7L:Z۹&tc0ӧ-)7s&35ݽ{ț'zxjË߲zこφs,׿X {?th>+gu^?1oGY8kƌ>d=߉{J܋x_}5{ֳ⾣2+KGZ8oD+~K yʔquͧ$B6y2De 2@c@e4~7:GgFo*z__:>ߏ1SqeH=X"Oo㎫9dDRwH?$u–[Z,2&*9x~իW Q{?yB<2zQ7wsnG7aW"ܹ+~?0J~#(ygJ8~b,Y?>ۣazu9ڸ{z_4ú[ 'Υh߷c|<唁1dL@+ݻNxxP->N%c lﳏsw@gGFҗ7{\i }ENqsgg?cB+_YڟA`["}__ioիm3J$+ Oq\ɘg~>з1 y[yc7Ə:4["Sy6 q~/^u>)~sg|Ȗ[~'[ d`}0q">iGռ?h?vȿU5̉b/j^?nƕU2'?69[{5he?$ ;9z<|9#-i;xcBg]7n|;/vD+>o~s- tU.]vm=v{&oEOu8+箸k1D}qذCm}t튉#8iINc-l#=Ђm)93:4uY7l!8|ǔɚ5<@&& S?{GU Wέ;\;IHBrEQQGAQΨggQ8GtFGGeD"$\BHBJ'N:$ŮڵV}[ϓkZU]7n׾y&?gc pn_e˖=>t}߭B g w]cy1OŜhfc53㊾?_1'kTU@.5@I5}rqz֓C`jpժDŽ%s^pG>cRyd/CXo,^w?C&iݽbO k '19u&d9xp7g₧on ?[-wFܷd7pgYWWy<$]-NHbb+}.'fL|kL{+Qe^bk7/#!vٽ/~: ĂWuŻvmqc{p{u]ysypD9El]?3Ol;1c"6"s‹ h׮\D-3}oݝ7(Dž^L/]z߂Í}Rm/3Q7k; |Z ;?쳩.c{[kߒe?߾--xg?+=~ۍaeɒ !T[s 2@̀Vnja4}UC} _Wk>ī5}UC} _Wk>ī5}UC} _Wk>ī5}UC} _Wk>ī5}UC} _Wk>ī5}UC} _WڇM ?x]kz[\{,~طdҷo+LP^'Wmٛo+n,(>_Ssg6+<'\ޫcxrkE߂ɅgxdT׼y}ߟe'X$W}ؾpyn2L^~9-{=Kc} z]N?Mo'yG?oΞI4owh#GΚ'sW^*'s jp/|7<\rW#O-WL/~#%v":듟?Ă82'˞[~p|/b?3wՁg"c,@?.boOqJrŸv?1t_D7qv瞋ӧ}#:#F'-;Dɖ-֯nqvm? k>w37&$kE4~58nLbR:ܰr]]oC_p} .gn,|'gǧ^sgO: Hlْsq[XGY4c ~[ی ǐQ[~Ϳ?dHOg` 7Xy~=^eی۷?"vHxYǞuVuހ^{ ۧ%n ^֭翳o%xuoy8Wnּ_b[5+k%[Q{C\2@oW70>3NvIW jؒ_4T/M/ac#M"(K j2H)J?_1$B0Z,T?YzdjX_9&ok0Xr晘/O-Zt!)䢇ѣńqcW^|ɟO<_zkc׾@ _1b֬÷߷Ƽůe6y&~SxpO-ݩB #q8eg߯xc8}\pU㎛0Hxa )vm]w啮[ֱDq~8_< '-Ũ!O5`ªq7ooK˔)x"K]o7#f .8jiٳt'k_{+'4޴Qugٍ7 '|}vvso;_ϗ'G ]{r?엿{~իOy8JS6w_2:;'L?ylqdѢsyg~o'^@|Ag{^(?+(sO>yp/ppF#9lѡ)^|Nyočpǣ2=88m'#~pq{Nu[޲h>P^ AW^;y00_'WuR "_;ށ(³`׍ fW7tGz+[(;w.]!7/覛߳/ƕ+x1 t.d 2HpO_clrX#"M-wGH\ηPcq42>6ג>)'? ,\#F_,)-[%mL*:oyᇵSN9h|k2#`jܳDOzxb榛 wa}2r|Kw1> 3fqƅf/~ oT7#XcK qn+~ .mm92]VO+'xᅟ<x_.D1!|E7ߌWse۱̝`b?>pl 7 7 /~įCW\_z>?5?דrLO8~|{+Exe'?_qGt 5f?ď;rE^eg,XJ/Æ!/W«RL,җnU87G?zh_1O|7i"O--#F퀷Bn ,[y'&o%~6ag>obbu;X8ԩ b\׿C81FJmx-pƏpS~N~Ï0-<Í P<__{v_zߘ89sE8#w10d7obIyKԟË9㌿ ֗E<> sǵ<ؽVÃ+1eWoy"˾M]wܫm÷?u˲s'MBlٳy3T+ݭʔ O}]*>n _+΅{."?m>oƧLbO3{k}ԩo>O?7m|W_]0v9s~ٙgN\z+Ƣ;w 穓NWQƄ7E+V@-];wl۷Qrg c]]ܼGpW~Bqw7(\{m[o3UG[ˮ/ _y矏뜝;o+_q:Ĺ/[%pw}f[zzVB<_jo{K럫7n\}`|=uucʔx۸q:qcvӧwto[۸qցV<: d?ͭ 9vN\W$i&2aa)V|?C/KzY/:wɕFOݽs'cǞ=RkmmiS0ݽ}„I0>eDޱEݱc^ik6 ~/œŖumguߏim6 OT9~  ?k^Y칳w۶5[ӷmm0Oώxs{)?Æ9?x5;v a} ݔ܄ ˟|4UwNc =9xY;d܀C.nOy/+V~ YvXPoc]Dط_'~;u'7,^7ŞPeяHRd\wg.uwމ %K‰˼Uqs-喫ƓUsιbWZ4>q.Fyd->W:%K[ߺrLw}w~?w-% 7{v^6޶ ",iHki5*#F-bŖ 2@opAccHk1_@̿xN5YcLbqu'%=)8$0F.=mlȿcU[?RlrEWeֿտXǣ'hU4r oغuݺUl vr7`gg'8f«,ˇ@|`5kfѣq {8NS[f2ؓr֭^ 61|;wfپ}آ,9slpm_OL_b+^|P_w<fΜ31^jo޼{7n[ۇ7̝;g>yqU&q.O϶mxckm߾m6n Q'0G/'O睇'U. /㎏}̽bןeEz ŀ<%N_qap``a^M+k"}ZDUMլӼWcn5FUyMMIӧc אl2u707y2q9qӧc\[ u#Gƕs>|5kw9ѿȑ'vX姞6hzou?'Eמy&MM[ji<@x1`yտ_VkۑjUͺ6ВjV&jUZcQլ/½HIUgŵU"+Վ1`jWͻBVG`@cʫծXqv_+VjtXr|!VjVֽ#-VbSZFj!VWtYZm5j֡ux,JO?(˛+PGޫXڲuY[ӆ 2@tV;cŠk+=+NXZzϊk=-V"޳ZtE +ծV;cŠk+=+NXZzϊk=-V"޳ZtE +ծV;cŠk+=+NXZzϊk=-V"޳ZtE +fN[$7~!` tWV;O| 5ּڽTGwkeIW8oedկ'^Z㼕WVj{kVF^Z٪qկ5[{keIWfՕe>Ă%^.z[lؗ˸ei3%޿_{)o dÀ:jR'u,+YJb+Ke[Yn+W]TIXC_ 5&٩cq?M<:jeT5K%OU.,,6gކl47GgC_k_OhuKЏ 2BfOpa_qmrg8;,5gCģ#M̿8̿wOW}1fz j_+:5Hm=6?/ 4j3F- d 2@ o %Og7_+G`L_?GFO &":.hq?Ƥ[lTY2k鼉*?`I5?1nK^u2@ d 2@̀\K˿@(ĥ:o1Lǡ2k[ke+&ZY0ʬ1V82k?=nbe:NkI]kq8n3]H?HO,x#?` km#فɴ?0t}4׎?Y_s`ѥf1 k\]҆٪_j?.GS~*)XXXoކTaeG*n}I~=a x-~|NȐY_Xu3TZONx]b Pkq\S?:w-B?MO9Q0]$ ><*c_r.`壢y?`5/ _>*c_r.`壢y?`5/ _>*c_r.酝Cqlb~zrZ}ck!/y_^V;/5(2/^_za_%'Ћ|O D㯾 >?W$?OD< <?<3[;ӘO&;E d 2@ d 2@ d 2@ d9 FdHqV~RvOZIi>kk'ekUkzSV~RvOZIi>kk'ekUkzSV~RvOZIi>kk'ekUkzSV~RvOZIi>kk'ekUkzSV~RvOZIi>kk'ekUkzSV~RvOZIi>kk'ekUkzSV~RvOZIi>kk'ekUkzSV~RvOZIi>kk'e?(ʯG4b8!?믫xWן'?sPyO^ן,iyo^XZןg&;!=^s.p#-!O?`hϱkfy_?`hϱkfy_?`hϱkfy_?`hϱkfy_?`hϱݘ ꇾrKuzG94su__ONh:/-gDY 3?yYXXu֟<ì???h:Oa֟|F4z'D&mUo*(O/?d$'8 c c/S. "߷돍Xu3TZONx]sS~xhm! >nϋOAoYZPfaa)LVQi֬???Ŝc*J=͚3z?ZE)YsVG(E4k֟bhȞfB?C/'o d 2@ d 2@ d 2@ d =ڵwҤpBjk}+NꭸV_[qvPoŵڅZߊkz+.V\/[qv!ַZ4ފk j V\]V;_ZB|oŵiBjk}+NꭸV_[qvPoŵڅZߊkz+.V\/[qv!ַZ4ފk j V\]V;_ZB|oŵiBjk}+NꭸV_[qvPoŵڅZߊkz+.V\/[qv!ַZ4ފk j V\]V;_ZB|oŵiBjk}+NꭸV_[qvPoŵڅZߊkz+.V\/[qv!ַZ4ފk j V\]ݠ=.jAQ@jƗfc1XXYjH;X<?<9xv.q ctXQ!BER5֘EXXXyΤ5ūhc dVةZ(y_^׿$< E ? _^_ b9x}=!`h0c `0S>ceeee4??<?< ֢F<4Ӵ ;G2@ d 2@ d 2@ d 2@hC;R4}jUkzSV~RvOZIi>kk'ekUkzSV~RvOZIi>kk'ekUkzSV~RvOZIi>kk'ekUkzSV~RvOZIi>kk'ekUkzSV~RvOZIi>kk'ekUkzSV~RvOZIi>kk'ekUkzSV~RvOZIi>kk'ekUkzSV~RvOZIم=.+&,}+N?@cyk'?y\y:T^ן'?gZ^߼.V'?yY NHל /\Hc pȺ>'sl3u>'sl3u>'sl3u>'sl3u>'sl7&*½/nRg;4vSz>.sc1u֟<ì???h:Oa֟|F4z'0O>#g'^g3b? t[z[ʯ(Ӌuɿ-NB+Bl2l<2K0Blqc1¬myc),YXX¬myc),YXX¬myc),YXX¬myc),YXX¬myxL 0H/c`IPkq\S?:w-s'^'WUڋT[}ʯȫU?*^U_Wūj/~Vp/o|)8EYۚjg꭬V;l[Zl^jmkV\ͫZj?38?#op* U3gp_7TIl\7"yE46N|Z'y؆CX܉x(eXߘ%9/I{(}^y.Λ:oQeCˬU0P1_=nb2Ycqc,qe7'П3 .FaJ&xFr!om'#en#?`V`{̔-yOweMCOX8~8x^</'@IDAT8iqb'뵗'mx2^p5qjNk/Ozdj8b'Ɖ^ xpNI;_kk'ekUkzSV~RvOZIi>kk'ekUkzSV~RvOZIi>kE.?Է RF?$qҗ6K_Z =Jh!{Kʥ/?&mgJ.vҊ/2 0p=i%^/m(b=,'Uo|K5|Mo5}.5SRqTTvOqK _QR _=-5|MGK5|Mo5}.5SRqTTvOqK _QR _=-5|MGK5|Mo5}.5SRqTTvOqK _Q P!p{M/vaK` dTW<8/_dZ*J5~5-W4T/M/ac#M"(K j2H)J/VSZK~[izoG!W _Lj\4#!W _Lj\4#!W _Lj\4#!W _Lj\4#yGi:m=y_޲F`ȉ>2O&Rj5lbIZ|izA [i!E_^PÖ+FDHQsϨ zdj?,2x#4k1)jO/riE/8"SNbI_bS}Nj\Z ȥ/<؇E.q؋|߾]lɲu6mڵ+65˦O?CͲ#]GZ ȥ/<؇E.q؋}zzg^L|gwƇ}ˈY6l|Evpȥ}8NP}8^Ҋ^pD.}ѧ䡝>/riE>bK_)yh'}Nj\Zч/z婾؇E.G}JI_"V8^Cy/k_x3y$` Cqi2.c1@8b1kMLX#4iqً;@`H c0<ƠZ[qyg q¾{kvqҗ)yhK_NJe>ϟ3g$TX ^d{EQq";чekvqҗ3I+wj㭁x&ەMm^?q'aa"U0w}xqQ m}? A"deӀ#i=Ǹ!?;?Odq\xC)f'y/vʥ/z6%P.ݻo+;;QhL$Z'w>譭2l?/SrK~P.p|8N6/b_>;xvA qE]?,nb\Ҋ]w-kF CzgG̙3eJi,۱cƕ+졇|cg'|<} 3gf^^kqb#%}чrZ=Й3g ~KFU[>4 c֕+y$#Lik-Z]^lWLJ㤟j+1a{-iV~!CǍs纑2NpRmh1}kv+vʥ=d[7n>[_t+}чr^ME/mh'>K?/prKk ?S;8niG K?9$@?u[Y<6lٵ N-2^Xd|^u n~%e⯖_.y[gC4K_yb'y\#-bǎqE~?|ӳlؑ#=Sbb#Nٿ?/?e7gǏ8 K=,շ'peG=g-[/_:v,f?_C˰/C"+nz>^'rnM?_nk Ǥ?>X1f2VQi֌?3z?ZE)YsOW|^Հ1kjUZ?լɿZDU'B/2~d/"I(OaZ#B;谄T?!كWU\Y|USΘW(o؀'ݫVm{<^ ۇ'tݿM gGhioc+wڳSÆnƌ;v̘,=zS<`b|OϞ=/?9j*]v7{{ v' >bx#E[[kOSx={\ĥs<׷ -9uO;wn-㉼vgp?d]4/q# W A7nqێ~v;>ӷ`\9 1jȑev#qeG/vgd(ZwkYT2cG\܆WjqR_jyլլL5FY9K5FY癎T/"D̮̊kj XyUnC!*){+V܊kKIɭV܊kKIɭV܊kKIɭV܊kKIɭV܊kKIɭV܊kKIɭV܊kKIɭV܊[N쥭OɂO _SG){;i~ǎ-[6ooO?oС۷co:7ži`;a&}gY{lr,?~,; 1fpqBoYqm/+NTx ]]x5> ,'pIpܵ 7 ?nhkmmxfzEqF^Y mۉWsOq;lԣʲO7r0L_]}KO7lX0,DK+8˗?&e<ԋ]ݍ ['k?0~ذ1c0A~ %~<dz}\Af'֎Lp2~GY4˶ms2*e'8eL0tuӦ-X!&Nw#oϿЧR?jt;L>즾ǁ`b$&!5kG9{6rr(yeU}OHooX7f n<7^ŋ8thow߰8^MX/~£VƓ؝S --xĂ-h '֯_Ot\?pqgg+^h{$ypCM+V<_CfMxċ]ĻW?3<4&Ӯ?u)AF8ᱵu$ly<=φ=><&$#B>L,qoO+6q?!N)p\ naacīUb8ì0R (J-nNEF@pdU5W=_U?_>3sG<_>$}ieK/v"# ['r b';;6oƄ,'M8;wCgē"يSտ1w/{&'LpEj19s,;#ʲ+֯>^4lg8ꨗ_MvvHd…vxz/*[vuy߰#\O[x>ӛ6R]qbյ}{O2y2^%-*y?w9Fc9SOͲ4)g>}5x>&/_S4({cMKZWH'5q.82 [gy)LXBwtbTىa^͟kmu<#?x:&݋ekvu^'wĵƴ2&pe֬ٳ1Oc 2v;zt{;tBN:oɲ)SOǫ2nذ2-zhbT'G\u!ؾifgSsΘOL..ud)xOx|O{Ӄ#'-3-ʲw7z5oA+*u{ ~__>e;8Y)'җ֣C2/TH۫:We,oO]9Wњ+FD*cy{/י9#-޵@'k:y*t_f3U0(3gW筌8_oQetenX+t?C]q2N"y(va?'P~cߏ ؍_Ǔxw{{65«1Qv [jL8M4}:ΏCH?/f͚;oGq“e\k롇ӟxO_K۴ B?Ox[l̙ H~ńیz˲ }7"3<,m\Dʥ/c7H ^7hȶ[p#ų}h9c"|Hﰀϝ;-xGϞ de?x1u9GeO>u+Жe֝;31a+ >Piz|c|[?ztGw޼mA%v>@n+xLFifβaYގ|>xp| ϡٮv~ap\aȐ,o+x}a?ouчrK7fYhqv=iWuz]ǡ{:oQeCˬJMlTYF@-ayc,qezX+t_5ƟMk)>%-Z¡0IF?0SeIo>x~?Ru*;9SH?<";ы\NgOg'&`m'Xc$&M7eX1 tyBs =u*&Ga_Y~OxU;&%E1z!s`q:y X[?'N-D5~/b/} ߿>@{=hŔ}o6xgV K?>'K_pC!Ǻ؉42^8b/./}ѧڔlGJ#2/a8YT05dL#_R!%f~/{iriŵyFD.HX'` dҏ"/Cfx&㣘̿ZϤklumP.PՔ~zci6<{ouW[Cvvb66i+=pÄY;6tn~?D&‡wE\OƄr=g{ 1cëSۛHxA9& CߘӘ+L _"uuUȵ#Ǐ7&ώիݫglo ꮮNlMgϽ/hro_gYciWOX/^|IxU6l>񦃓O7cttWnʸ8ŋqƈhLbu֎+5N)vulcp׭sOv ނ'xxS#4~G>Dvݺ*7o_;vlݺe @;m_v\dN8ᨣOٞ:_{{^4{kd ogҗVdd(b'OمrK+/==x"U^ NW&^׮vr ޮXi&MsO??c pr';Cb'K+^7W@^Zx[7lpŽʽ˿[_)i:œww%vB ;vl[vڼyIr89Zܠ1}|owb~Ȑ;0A{˗ Ĵ8gox b'-?eb)mGnȘ7Mv?teK?f3`&M5 )"Unkk;L~'ƍ} OoGZcݶ |z…A&lþ//OiqV߈!Xbe Q} _%$\}|l+\*?"Gu@G&kܚW^ 8Ys `9\ǼeFsd?9kLU@O!MӋ]+4|M/8ׯ5j^2NSokz8 _ N+4|M/8ׯ5j^2NSokz8 _ N+4|M/8ׯ5j^2NSo^-B?RbNB{X{ںο.mm?~ԨT[Pc"ggxɱGZ.}|y߾]ynMt[DZdxK vΗkR ۜSCsEx{qzlS?dMWc ?|ul/&a/0~ =x!󭭘4\Da-^|8wxm'O\?{]A CEƷ&b1>\-j3E\.cĉ3fmm]]@F#^>y{}h+w#Ioo|Ξ`s[{+K_F 1y2 yU=={wvnق#V!e2df|3~3d)? [PZ}v׮<Ƶ[} Un&d 0@H )VPbkCn[i7j jK 6m?'8pBA#cBS$ IoV]s9]{UZkW>UuYq/._~pY|Rq8tʵ_WIhN\+~~Guʵzk_[rm^4jU=.S rq6/GzkEHߪ_^qZm>ҷW!|WV[q#}~uGziEHߪ_^qZm>ҷW!|WV[q#}~uGziEHߪ_^qZm>ҷW!|WV[qi&l~ޖ#ٖˊR/</_fZ*KHe3ʑRBzE-W4+mri(^Qm_q9 ?[=Om/aOj]˓}LYֿ[߿2+:h4Y('U-[JOll_dҥ>+ b!i;h֭kOH[dI_lE|_'˓/ٲ`?\*V_-ϏSjƍkJk_M1CׅW-B+WXab2/3R+:VοaqnMboq~UyS#LVq~Uک\[ ڦ2Bm|s eNB,E"}̃?/GFJY ն?_94BR_He1ʑRBzE-W4׶tzW>5 1vҘe\F㼔?_OʫnܟKYɂQj/]p#F2E^=f_GY~׺z[W/Xh~[&M =_]sIYxvР#l=8*_??~|G/ϞǏO~˽yƌuW!EqͻzB)" r%شțu_uqIm8I bLť1˸,e1cGu?_K^L_^Gűu'X.|<,w|Yn]E{ӊ9~gح^vGh\ū9h\e'E~ɿ0Pd"Ì?g)2w9A0 VW`m?pS+qg?1??@}^(KYY#f`%A8ӾF`JnS~Rr~4W~'B,ر%2FqR~br& ޢE>4O>b,@޲V{2Y4JmzE~fy:Tz/ {_\;1|ܸq#:U^ݭ_Lj1G S'crz 8.WqzTe}ENˇ oۄo?_Mae{C~X[ Or<@:iɲx?jS O8x|Cܳ:BOڪ/2/%WGVǕyjPEA֭xb2y&LoK^.?X^sbɦ ,^Mg}gϞ6-8FAq>|'iѦ|L8}}aÊ4(ر{~`S֬YLG !ۨQrsf]v ?49sDA}|ba(˖,떟y񊬸Qj? $.!B^1N3o',v/rB~(~]Wxˑ8k[;Gvq׌ LmxVu}= nU"“S:b>bF 1c̲S3XBb̨}W⧗C أ2lQ}qEd3_Nr,ccaf6fe_)j8O8j-˒ҥ5jdY92<{>P#F춛n/_s~9?|VoZqQG̙Sȫ6hШQ2n9s9F'Lo}yi+2f?q/}|#fΔWe-BO^>{v})K^yWHt.ٷYkYf:Up!W dX&zgٰȣV՗)}ҷZI;tP1ɵM4)A{%߶]v;V̙3{﮻d\*V#ɟ=8 C>]psQҗuٓ'Z.z}.//Pe`y|.WZhCRg |eP|mW[QI8jVv̎.W)+J8tmiV⸌?/)֟xju/Ooyyh1(x_ YzO-#ϓ+˘?(_Eae ꋖrXw+/WJdO<2c}E+uSG~n+j:;>%W;+c}.??=U8dAZ._Q'h??Y8W.c#$] O& gOT|R~˖6UO&~C VeѾdKW;!>f/V}IW;Ջ} sP-"R GV[;kǭj7d,ԛ=7?pw  s;.}8&9}իm_ZNjq)\էqb"^@IDAT(, &G\nSǯVrd)[SNs\Wb9]8W;+[u|OOX:x|xvکb_EU{z"X)X4m[x8W}jک>Qy}nޓimsw$ox25ͬ?b,Y#!l,7V 0e G3B8.G絫xqvުZ/׮^{j^zkWﭪz%^\]֋뵫zxqvުZ/׮^{j^zkWﭪz%^\]֋뵫zxqvުZ/׮^{j^zkWﭪz%^\d ;OUa#/ h[/O?[/mѺ T}mG㬽Unrko[;ګ^嶯x*׾:N}Y҈]^Ɖ*틍l*:N%b «Unrko[;n,<2}eY5킥Oe&[y^>/E?"Yi߶vWm_T}mumkY{իOVi߶vWm_T}mumkY{իOVi߶vWm_T}mumkY{իOVi߶vWm_T}mumkY{իOVi߶vپųz}_. )vr'caa\@oeR)U a`Z)kxtTU /0V88;[8/_˷~oj/pX ]-BRe|Xǩsǿzz)SfΔW2O(OX3bD1xKXN]8"Н9stys~s >vȑi^sa/m_Mϯ׭+/6kp }{oߖe{=qرyxr7;"K~~^U26?I6ϫ-|^QKy j,[Iq?j\g7^Y;bв-$U։[vFJWߙB?Ɵ0C=/L|!TOؾOP \e,ۿ?hXp4?ܬ^پ׾NVlklkI_tپ׾?8!fl?6FdN"go[J>a![ڿQ8O?pN{!_` ?a`̿_HaUe`ȿ0P/ب *k71֪2qc*8*k?7P#OrTR};.vmT&UjpRvhOSqrx}k;1/(RvhZe7kV]8~A7ب *ky^tMd_ccMlTUq5?kUaUYcḉVa_7j_T}kzmUOSUƩ qxIE~hX;EΎ׾).vvE8Nq8㵯-±vƩ}mS\4Nxkpqjgk_[cS;;^"khX;EΎ7ltVi_[}ۦo#?@o̿:֪|xd1")7̿:֘TyOW[ 8n] / yG#FG1o,2d(ɭ%?֟x%㣺Ut}x/䟗:qrVl*W?}۪ Yqo[՗WI^{ڷ_ہՏh߶WT~GUvr8ڷկѾmU~\(mz@Gqo[ի_m*W?}۪^j;PQVWہՏh߶WZγ:U/GUq%<ảEbc3 P|r'׳+i#N_7I) d 2@ ,WȮ߲lXe#[w=CrGvW\^嶏ToǧjzWǧjzW#;)ک}dz)ک^UnNv|Jvl٩^qu|JvW{>S?GvW\^嶏ToǧjzWǧjzW#;)ک}dz)ک^UnNv|JvWg/:O+[2@ d 2@/jjU:#"|oկCH8[>+N-GV8jU:#"|oկCH8[>+N-GV8jU:#"|oկCH8[>+N-WBa߶W{.h_EzV~,E*\Vǥj'-XnhQk_긔^eyφosի}1Z8K!}+>c>ZGHߊEV>ҷ8#}},AH_je#}+>c>ZGHߊEV>ҷ8#}},AH_je#}+>c>ZGHߊEV>ҷ8#}},Э"/(;׷g2?DXD]c1Xbe8e8vfU^oY nْe"VWV fVT]OJN2(d:˟0ܿe5Ptft6O`2r_8K3`F!C*׮8/׎ ސ^Ѽvj-^-Gzکh{qζkڢqHQ;"|W{qvȟ{qv^;꽸^;^\g^\G}/z/⣾kYkgQߋCދ뵳!VY|z?z,>{qvȟ{qv^;꽸^;^\g^\G}/z/⣾kYkgQߋCދ뵳!VY|z?z,>{qvȟ{qv^;꽸^;^\g^\G}/z/⣾kYkgQߋCދ뵳!VY|z?z,>{qvȟ{qv^;꽸^;^\g^\G}/z/⣾kYkgQߋ:jt^= YF_Ȏc6yWz?8pwl_ο9r}/_ο9r۶DzHM'/ aJ1?֟nwн=?8p{N?8pgq#?2u/튞?AQѽ}O`u/犞 _1*c ̿\/+FE?a׽+zb6Ũ>' 0sEO̿g1_riοbxq9Bơب *kȿ0P%0r.=7m!10oQU!UXqX0M 01pX0M 01pX0M 01pX0M 01pX0Z'ЕXyGt9(F€7$_ȥf1 rf3)?x! 4GhcSgvf/y_L0,jxQf_x}%|'ouB"`k톨c1Z O;`aaaivT64v0?8 EF:3tmQd 2@ d 2@ d 2@ d 2@@OS7K8O>{>ҧpr^?);)\#O#} +GHCH>{>ҧpr^?);)\#O#} +GHCH>{>ҧpr^?);)\#O#} +GHCH>{>ҧpr^?);)\#O#} +GHCH>{>ҧpr^?);)\#O#} +GHCH>{>ҧpr^?);)\#O#} +GHCH>{>ҧpr^?);)\#O#} +GHCH>{>ҧpr^?);)\#Oo{]WLȋGd_3?t8p+xO '?yOgZ߼.Wvߋl~m u g?0lY <' l3u/O`9=_`ͨ}1/ωn1یnF]yNtsfu3r_?Ɵ0s{̿vg+ܛ_/]:̑K"'ge1tJ/EEn?_1#:Sdg)2S̈Nfaa)fDY 3?EwY@We"OXrȲ xex2[*!bx2?ž/X|<}ž/X|<}ž/X|<}ž/X|<}ž/XOxx aEjQ/I}E̿<3Xu2gս8.OS?:3^')֟xju/ON eaas'^'rZ݋99CqYXm|y/#U[؀[R/G΋O?9D֬???圩RfYs*J=dSΙ(e5O9gz?C֬???圩RfYlgв~2@ d 2@ d 2@ d 2@zԚ&c^\G}/z/⣾kYkgQߋCދ뵳!VY|z?z,>{qvȟ{qv^;꽸^;^\g^\G}/z/⣾kYkgQߋCދ뵳!VY|z?z,>{qvȟ{qv^;꽸^;^\g^\G}/z/⣾kYkgQߋCދ뵳!VY|z?z,>{qvȟ{qv^;꽸^;^\g^\G}/z/⣾kYkgQߋCދ뵳!VY|z?z,>{qvȟ{qvȮmOZ@'ӿ0e٫.?/oW yū?8xgq/_ο7z8r/nοm[@OMtNtRN2dcamM~G ?78p|V?;X@ SPҮg1_r`{?Ɵ0^=1bTto˹'_`W3{9W l0Qѽ}O`u/犞_!;ߏ#ay0,ϛب U^r /"qaUa_eM 01pX0M 01pX0M 01pX0M 01pX0M 0|]W)LÚh/ ^Hb\j/οh6@)}>,;u}_`kQ/$9 ͢ex/_Wr\k}BoAV'$n}?֟iG////o?Hsi8p~Zl3O[E d 2@ d 2@ d 2@ d; 4udHC8#O#} +GHCH>{>ҧpr^?);)\#O#} +GHCH>{>ҧpr^?);)\#O#} +GHCH>{>ҧpr^?);)\#O#} +GHCH>{>ҧpr^?);)\#O#} +GHCH>{>ҧpr^?);)\#O#} +GHCH>{>ҧpr^?);)\#O#} +GHCH>{>ҧpr^?);)\#O#} +GHCH>{,'UQ}ńxDξx+N?@g_׾b'?yٙ;πOgy7yrUh(?y۝9^wnrv#-Oɿ0s{̿6㯛Qb1_c݌' 0/f?aD7m_7.c ̿<' lw&½/ҥ9N#4*r}__Ob\tnWc3?EYXX}֟"ì???Ō>Oa֟bFtz0O1#:SdxG?t_zZ(*+oɿ/N,w?,ːWƟ'.W+柏'.YQSYgaaYQSYgaaYQSYgaaYQSYgaaYQ'Y@Va4ۧ_3'^'sZ݋99CqYXXb׉V⸬???yNDP{q\֟{qvȟ{qv^;꽸^;^\g^\G}/z/⣾kYkgQߋCދ뵳!VY|z?z,>{qvȟ{qv^;꽸^;^\g^\G}/z/⣾kYkgQߋCދ뵳!VY|z?z,>{qvȟ{qv^;꽸^;^\g^\G}/z/⣾kYkgQߋCދ뵳!VY|z?z,>{qvȟ{qv^;꽸^;^\g^\G}/z/⣾kYkgQߋ:jt^= YF_Ȏc6yWz?8pwl_ο9r}/_ο9r;dM杦ۅ聏V SVct&o`eeeeegq?ݛurO8pO>+toyp% 7` ƁzƝ`ck=zd$@c1??֟@g 3aaH $~XX??;v_ԧH<vcLbb2Cɴ*[LLqQ[lTUq5?kUaUYcḉVa_5kUaUYcḉVa_5kUaUYcḉVa_5kUaUYcḉVa_5kUaUYcḉVa_<=ҿUXE(F@x?R3e?_i6@)}_ (A:/ͤ0,j?xft# 扫˭7jZoZ8QL[_!-rRc+I]Ԉ.>[;A!LBG1?;?JH|/>711)/oƢ(bee-I>/o g?v|=?2@ d 2@ d 2@ d 2@H>:uKOz~RvS^9GzGW'e>#|I!|Oz~RvS^9GzGW'e>#|I!|Oz~RvS^9GzGW'e>#|I!|Oz~RvS^9GzGW'e>#|I!|Oz~RvS^9GzGW'e>#|I!|Oz~RvS^9GzGW'e>#|I!|Oz~RvS^9GzGW'e>#|I!|Oz~RvS^9GzGW'eg=)R_}Xb5ͩ&?/6ο4hjGyWKˬ4_=_ο9r[i8rQ'yǶNNn:[O`2r_8K3/!3Y  鬜eu6R?Ɵ0KeHg̿/㯳qBg1_*C:+l5~{-Ҥ9N#)r}__Ob\tnWc3?EYXX}֟"ì???Ō>Oa֟bFtz0O1#:SdxG?t_zZ)+oɿ/N,w?"ːWƟ'.W+柏'.YQSYgaaYQSYgaaYQSYgaaYQSYgaaYQ'Y@Va4ۧ_3'^'sZ݋99CqYXXb׉V⸬???yNDP{q\֟{qvȟ{qv^;꽸^;^\g^\G}/z/⣾kYkgQߋCދ뵳!VY|z?z,>{qvȟ{qv^;꽸^;^\g^\G}/z/⣾kYkgQߋCދ뵳!VY|z?z,>{qvȟ{qv^;꽸^;^\g^\G}/z/⣾kYkgQߋCދ뵳!VY|z?z,>{qvȟ{qv^;꽸^;^\g^\G}/z/⣾ٵ tuU Dz7{Յ7caem!4x8p>;ο9roU/_ο9r>Rʺu;'i+?_Oƙcu9WjT_[+_jT_[+_jT_[ͫN_ `<'!y-9QS+tk3e{_f=YXkeIk/U[_GK̒c+c1cyI?_)fcaWn8k8pA#׿Xp 2@ d 2@ d 2@ d 2@@ ԩ_ } +GHCH>{>ҧpr^?);)\#O#} +GHCH>{>ҧpr^?);)\#O#} +GHCH>{>ҧpr^?);)\#O#} +GHCH>{>ҧpr^?);)\#O#} +GHCH>{>ҧpr^?);)\#O#} +GHCH>{>ҧpr^?);)\#O#} +GHCH>{>ҧpr^?);)\#O#} +GHCH>{>ҧpr^?); tUT_MrTN@?iN5g1XX9Wq+ DS[?3-\Zf9r/L0P t?T?-7 x}w px?pHS 0/!3Y  鬜eu6R?Ɵ0KeHg̿/㯳qBg1_*C:+g~:' 0RYyg+ܛl9p&qZAEO32:s̿"?g)2S̈Nfaa)fDY 3?EYXX}֟";Z=˦O>l؞cs01g uNP\+ O8 ?z^?RyҤ[֟Yu.OXgq?I?M/0vLkYu,k7Yv]qGmۻukCӽAz3p#F~xFY6ztO,{_`sCղn޼aCmذy!Y6oSw:8._;vI?vEZcͿic1'j׬/_;n{{HtAӖA٩~g`ƍ>e]lٿKqǺu |\yxU?WvVyN_[,ɲkW韲Ҿӟβo_,]mYÁEt95?Է}Sy|U{oyeƐ^nȲSN93lfvY}wI9Wtogi?OHc5;K>Ue*Țc)L]U2{Ț3u=TQ!k֟rXPE)Yg3Squ&lun-~ի?5a?miSoMY\e'8fI'e^6r_T=^yb[/sox[ѿeml֬WͲ׽59,;}7.??^gӦgͲn?_β4&#'LWn볳˿K]'ӿ'=vM?a̅͟v܏{˲~[_,;!C6o.ȿ:+b tgɿO{„| FzߞkWC^}:CʲI> ڰAI }vbY8_sͲW+ o}JO$ߠvz@m{qvMߋSgUgNϿݸ^on%;~u?俳3 cuh|񡵢-?bee?48py!z(MemzuoUYv]qG~al8?~ԨWB4hX9Z%߄ߴ)D- (ŏyŋ.ͲkeY'O#~|?|y[|&~> Y3gq #(ˣ8kp}gyaE$sKy~„ifʲٳɯ_L;u{ƌ}xG_ wѣC[rM橧VODlf -^pߖe>;o˲ %Rybk<#5d1o?e'0Z1ZMo"?VXX5Le3P$:.KPq^,ذ~}3g#5n11 c2D_H"sYv^{ᅲ~꩗\;,{NEbU<Y kW}>ٞVf:OZ~%gzY }-N>3~oѣu1L} ?ܹxݶhQtĉ؅kl|1H^^~9]񺉿[/Whqı~9y}o|/2>Yy?Au5wn&qs48GeCzqTwew{&se>H|˲/>s-%5&ȏ?>'?$8IdaVZy믗o>_s:ƾ3<,5 ٳu?qE|/\=6p[,ZtYU矯ld>_͎8xb<߿⊿{!ʞ{tkϿjYο!C6n9cO=U>FH!]qxQ#?zK~]gq͝w>1̚%}Wݫ{?c{ܹ&?L,Su~ß,v?WsΕW士{fqe?["? ;7,]vٴIkxͲ6s< rSA[lTUq}C##@<ب *k71֪2qc*8*k?71֪2qc*8*k? FRԘ<衍,-Cǝn/럺 MoĈMoʲOZ[.,n}"Y ,7O̜|\.@*O&϶?~ t? iS^*q`ߖe5ț-vy<>(q}oeoyˌל<˾?u rr] &O_r;iȟ,d{< ?p_ ͓LֿO}{>ayu*S_f%qXG*:^Z_+QBiNQb#G y"%Zuw;~%Kbxվ*yʑ4;'3x˓\rze"oOoy=}(v_Ue9.ir8~. 9Kh >>c%.caWx(__"O?|X_^}; .8Cʳ]6q̙~ȟ^eo0W(ы V~V3+囯«,klϤ7<92|#x=&L=>fر)fĵ| ]\\<][2cv)yJ^^2^-,2qby׾[x[]bx[-ȫG}S]vٙg7'L((ǯ}'fS]2f9I_hM?g5͚3̿SS$4kgc1ڗOM̿N_QM]Koj𑾊L𑾙>wȑ{˼y >L.4詧'&}S۞vJRۼyȐ3d磌b X-XNu>wyo},\;D6_Su~s5 Es?p8Dtek= ooZ{|Fvm !WϢjDbG&[M7)}ر2| }[ξMޤ7u~IyS{['5b?ҏ~T>aL8g~[SvVNٷ[E8#O#} +GHCH>{>ҧpr^?);)\#O#} +GHCH>{>ҧpr^?);)\#O#} +GHCH>{>ҧpr^?);)\#O#} +GHCH>{>ҧpr^?);)\#O#} +GH}KWaSE\zE-җގ̘ +^\r% Ң/m/׿|Tkv6_YSkiʾ-'ȑp?q_ׅ'iƌ:T_#~Z6gΔГN/7߾~+LN[X8fY;},|?|כn:Y@8@^+LX~x{U~?`o/__~ݺطm -]4SDX-1@+Wʫ6fLoǿe.췟|K;=!<͗O~_ₚFJ]uU-̺MOm'86Lm^l--'5^c'mڔUQC1?Uo6OL%E]qӦ^ϓűY&Om7rx=U1vro+ {+_7e^6,[=YwsΜ+-oJϘ1n\u1K) 35=r*7sɧ&OO0bjcmٲ,1çN w?N>y޼oβ_׿կWO,#M7]s,jg< 1c o{/-]$F/_V^-^5>?I~h8_J*eiz>_1^O-o)K,Y4GKwڐa@)[N헽cg?a2_ο9r[:3piY@#&ÇtPkpCm*Ϥ5a==ɫbS_?u>]G?ʲky޲F>dG_入P^͓o9&q,==V=tT. mٲys,3',ǬsM=||>ay2#їg0zg) qvge{eKxr|)^O>/0.=fߎFG\86ɲlyW{O%zgU5e$#I3 {?d٪U˗˂cw߭fٿۧ>u{ϟEgYz{DC>?/קz]nI%R6,6__ZYZbb%?lwe[~tWڳ{{WX(Ԋ1}رDۈɵWe=.Ne!oxWK?&_UkWy W!vo~jt٪WuAS6?u)}4??l@n}Ə6ܿYv]W~Hv=1V]?ڒR׌'(4e!Ƙ?aGkFee YR'6v8pο!gK??\[*[CD$R˫2/4%\83gΙwWy/sy39o_{?t#mEbӞ=ڬ󿚚2/ز /}I&kjGݻﶛL7{*oƾK+W67{,EcK73v@ݩS~X&:wMZ5o~`̵lߌ_i[$ߞol<Íc]e1cnIy.mѢS9묛n77S!^򓧞 4iRsQlDM,N-'Q+/%}CڷMy0b¥K׭Kv7HRޓeys Wخ+>#8$cf|≧6fDqѢ y啷߮γ\½uE]Y`ɒ7ߌ<:NlEeߖ$/^zIn[ͳ|y<ǥG+btIcpMR_~y&3@0?O27и/ R%m8)h.fh[1 y|Р\^[ɬS;$Ne[,=>>?_<3Tjɏ?O Z20011C?fyw.}zΗwmVWZkŭۻaz8HcjkJY9f2%{^<]+o%ѹ ^u\/{yW0S}ΜK ?k?)嶶?G.s qF svr>cf^2ʮ_oKOկ^s1vsf̬Y_d1--hڞ,n i~~+ƌ~|Rޘw0M,?j{TlJW2*1 w >{\ryD~^[v/Op챲€]s '˗$'L]=Wϝ{@JC&M[ s[K/1{3{ELD+ɭ#7“2_9o+)տ[x}C=GZ<ɇ+1٬Yv<oȸ/O̜9i1tgCvO"k߷\zMMˆ Q ߸.:snٓfBQ;>l2N ? /!VO{r2߳g؄=^2gAnI2,^|睒빸G_b(}t8r=0|'T9XcMpx֭X!Ȝt '\<^lei_ja#J3?_:fj?(C???ɫ1fi300c&2J=mw*vzՊvng:YzN>٘ZV.dLe%dPyNϟ/p#㬫sSYM!1<裳fDQ (#̙?~M2qԵks˫L4țY#GN4}Lϟ|?2p\e)S>{PYRS'Y\T;vҤnaċ.{r=vyc\(dj-Ztu_L8.[v%sG]X3ַwkl6Mi-ߝ]onWU?o̒%w-ޟęg&vglWG&`c^'N:g΅s1/r73iҨQ |[pݺ׿69g=hx {4J^V-s~M>46s#G' ;@&wu ;,K[׿J͝+j'r'ʩqDGwݻ3`@͘qiI6mcN9e0k G=[⮩}C{%K9~)w6˧3My邏.1n?YYb_ǎ{o5k䁧jnNg<~BGCvm;e-'}^WN{-oT?(Guɉ+zH&N=6}-G~}KkqzȞ+j\|Tj=W!{\sQ]C\W⣺Wr-VGu-VsZ\Z\ʵZ=յZ=dϕkqz.>kqzȞ+j\|Tj=WEzohk^?= SN[&ꈷʭkN7kkK;MdߞgvMرѾ{B<?>3xgGw\qń 2zgR? /6M~YgDy_5kY߇EYsյZ=Blg۵j?e,Ԣ+ E[]WZ=񗍳P6z?/mvm\i;fUV$ΘbiGӫbe˖ۘ ׯ/|7a嫺J&>n>X^y|PGQ׿a[o1kn(4={FC--mmRW&|rΥݸQڌe]w ϼaZomm2!&F~So]Q t}/G].7mڰ^[Fپ[7޽kk%i}W--յ}Xիo_yc>WB5vDjs&DВ@k7_"QZ_Z7A^z&{ơG>: |"bOoMzKKzGο-ߵk>&}[=_i?Dߴ魷׬3dº^z|g]L]!ΝoEQuW''䁷s_|Qp4K]ʧNp>/Bߟ3Ee???̿8TfGǟJ?F[H'ȑuu!Cz6L:nm%wW޴Im,7o wu$XyNdBu.]d} j*yy_lW&ӽϾ{]*٢[HT1^e]=^cKr뫌**l۷_dSWIy&yR^[.S0Q6jߺSfLnorŭ=b˖*d$-鯸[6%MSF׏[kk{~ύ~J#n:n}TK? lbō7pķM V6ZC|b=u8?yruZ6fG\#4O(-JFJsx+͏*' Gx_9]BTS+aӾ>=voļY=1Y\%7n^ 7Q6CEo6換m;:YFˈԱʶaecIֹs߾Q|ܖ5Qn$ɣG˃׿;y1/W_Q&y{|$_y{C} b|Ge0ϲZ X˶a_5k6C˲F~c-ۆqYo|e0/lZgO&j3,SGe+T?™GϿmmV}:bĔ)1 W^)oΛwYMvz1,]iβ5[Z/?K}-˖]u 2'+PA??7xbwrbq9r/b# _@1vA7;NEؾ]{4zCj؞c~gl̟~})˛6`@\?/##^{mRc;oŘSoY=d>sN`S{X*30l;b3RuNܪ`ee{6I2!d 2@ d```˖M6l0fMv155=7cZ7g޽۷Wr>Xc˫$d 2@ d 2@('iB!y8m;GrG#NH#yWێ\k'k>pɵvBzCvZ;!=!\m;GrG#NH#yWێ\k'k>pɵvBzCvZ;!=!\m;GrG#NH#yWێ\k'k>pɵvBzCvZ;!=!\mV]CckGi_-q@#㿻_?:~F?x#=+5ӬG$9rozWH{EiwxǻaI᳤&@bGl;/WY? /!mgE*g!tO`"팿__e,N PTK?aʶW6 /^bӎc4)SdGO$9lGCZ/*Wf%e1Ɉt'0O2"*]fI2J 300$#e$???Ɉt'<*5 eI+ewɿO\E.C:^:\v-!:bxre1nT$뺸bdV???nT$뺸bdV???nT$뺸bdV???nT$뺸bdV???nT$뺸bxLKKR0%ʴqd0dP%?.OS?<3Tjɏ?O Z20011C???qL1DP%?.OS?<3Tjɏ-w'ݓu~I~J~bKр?(9D???ɫ1fi300c&2J=(i6O:fj?(C???ɫ1fi30lw۴F d 2@ d 2@ d 2@ d`c7CVIۮ!{\sQ]C\W⣺Wr-VGu-VsZ\Z\ʵZ=յZ=dϕkqz.>kqzȞ+j\|Tj=W!{\sQ]C\W⣺Wr-VGu-VsZ\Z\ʵZ=յZ=dϕkqz.>kqzȞ+j\|Tj=W!{\sQ]C\W⣺Wr-VGu-VsZ\Z\ʵZ=յZ=dϕkqz.>kqzȞ+j\|Tj=W!{\sQ]C\W⣺Wr-VGu-VsZ\Z\ʵZ=յZ=dϕkqz.>kq^@..T+}a_l !=[d!4h8pY>=9roU-_9r^ tr(kQJף}V+cU>o?_68pzE[q022f7oaeeUbc ~,!y51K?_qIKQc>/GWc\mƟH:W_,'O->MƟmϋyJHbFci$[oa~>t1)^IDAT8˖1oV3qȿA̛l!Y֌ʶaecm8,k?7>ֲmeZ X˶a_5k6C˲F~c-ۆqYo|e0/lơeYam8,k?7>ֲme;o^R~U;@l'8ZMwoՋE3/Muڏ O/?̿8Iya׸w(?xIxXg߼7ᅨ'?A[,}PJ~w7$V '.HG7INegH?{kPǟICcG100{__&E?N,gegG%Q̿̿̿223[mq_fݶ7n]+gΎKKw/!9.ApTJcU*0.qR) W¸?I4?_ 2?'=L`{YD z>#|$GH#9Gr>#|$GH#9Gr>#|$GH#9Gr>#|$GH#9Gr>#|$GH#9Gr>#|$GH#9Gr>#|$GH#9Gr>#|$GH#9Gr>#|$GH#9Gr>#|$GH#9Gr>#|$GH#9Gr>#|$GH#9Gr>#|$GH#9Gr>#|$GH#9Gr>#|$GH#9Gr>#|$GHI~>l,*ok7{ГHŏZNIHJɿ0dĖӭ_$YI"BrYI"BrYI"BrYI"BrYI"BrYI"BrYI"BrYI"BrYI"BrYI"BrYI"BrnΛW[c~ l,~K) !/Cɿ'0c[?[ޓWcce?Ɵ?o9֡%Lo=!3Hj|*aib1oO` ?[5>04\1o55yriХKj^{ᅁ_UU>;0{@}:LÊ[?I_(a$e_EOnҙ1%YN[aaaa'=2sIbp?!9f?8Gr9I?Nfl^-V}紮6}}oƍ+O>@~G4DS o/jAy/l`A-̿C200 6Y9OI>??'?q/'b(W8A&+g8G'?N0D #~'(d;v8K7l&f-8D[|[~^U~T99]~=zw. SN_.97'?/͈Y^9royV _9rW3bW/_9rہ^[~TۃlYZ=`9#\IENDB`kind-0.27.0/site/content/docs/user/ingress.md000066400000000000000000000107371475376161000210670ustar00rootroot00000000000000--- title: "Ingress" menu: main: parent: "user" identifier: "user-ingress" weight: 3 description: |- This guide covers setting up [ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) on a kind cluster. --- ## Setting Up An Ingress Controller Ingress exposes HTTP and HTTPS routes from outside the cluster to services within the cluster. 1. [Create a cluster](#create-cluster): There are two primary methods to direct external traffic to Services inside the cluster: 1. using a [LoadBalancer]. 2. leverage KIND's `extraPortMapping` config option when creating a cluster to forward ports from the host. 2. Deploy an Ingress controller, we document [Ingress NGINX](#ingress-nginx) here but other ingresses may work including [Contour](https://projectcontour.io/docs/main/guides/kind/) and Kong, you should follow their docs if you choose to use them. > **NOTE**: You may also want to consider using [Gateway API](https://gateway-api.sigs.k8s.io/) instead of Ingress. > Gateway API has an [Ingress migration guide](https://gateway-api.sigs.k8s.io/guides/migrating-from-ingress/). > > You can use blixt to test Gateway API with kind https://github.com/kubernetes-sigs/blixt#usage ### Create Cluster #### Option 1: LoadBalancer Create a kind cluster and run [Cloud Provider KIND] to enable the loadbalancer controller which ingress-nginx will use through the loadbalancer API. {{< codeFromInline lang="bash" >}} kind create cluster {{< /codeFromInline >}} #### Option 2: extraPortMapping Create a single node kind cluster with `extraPortMappings` to allow the local host to make requests to the Ingress controller over ports 80/443. {{< codeFromInline lang="bash" >}} cat <}} If you want to run with multiple nodes you must ensure that your ingress-controller is deployed on the same node where you have configured the PortMapping, in this example you can use a [nodeSelector](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/) to specify the control-plane node name. {{< codeFromInline lang="yaml" >}} nodeSelector: kubernetes.io/hostname: "kind-control-plane" {{< /codeFromInline >}} ### Ingress NGINX {{< codeFromInline lang="bash" >}} kubectl apply -f {{< absURL "examples/ingress/deploy-ingress-nginx.yaml" >}} {{< /codeFromInline >}} Now the Ingress is all setup. Wait until is ready to process requests running: {{< codeFromInline lang="bash" >}} kubectl wait --namespace ingress-nginx \ --for=condition=ready pod \ --selector=app.kubernetes.io/component=controller \ --timeout=90s {{< /codeFromInline >}} Refer [Using Ingress](#using-ingress) for a basic example usage. ## Using Ingress The following example creates simple http-echo services and an Ingress object to route to these services. ```yaml {{% readFile "static/examples/ingress/usage.yaml" %}} ``` Apply the contents {{< codeFromInline lang="bash" >}} kubectl apply -f {{< absURL "examples/ingress/usage.yaml" >}} {{< /codeFromInline >}} Now verify that the ingress works #### Option 1: LoadBalancer Check the External IP assigned to the Ingress controller by the LoadBalancer {{< codeFromInline lang="bash" >}} kubectl -n ingress-nginx get services NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE ingress-nginx-controller LoadBalancer 10.96.33.233 192.168.8.5 80:31753/TCP,443:30288/TCP 27d ingress-nginx-controller-admission ClusterIP 10.96.80.178 443/TCP 27d {{< /codeFromInline >}} {{< codeFromInline lang="bash" >}} # get the loadalancer IP LOADBALANCER_IP=$(kubectl get services \ --namespace ingress-nginx \ ingress-nginx-controller \ --output jsonpath='{.status.loadBalancer.ingress[0].ip}') # should output "foo-app" curl ${LOADBALANCER_IP}/foo # should output "bar-app" curl ${LOADBALANCER_IP}/bar {{< /codeFromInline >}} #### Option 2: extraPortMapping The Ingress controller ports will be exposed in your `localhost` address {{< codeFromInline lang="bash" >}} # should output "foo-app" curl localhost/foo # should output "bar-app" curl localhost/bar {{< /codeFromInline >}} [LoadBalancer]: /docs/user/loadbalancer/ [Cloud Provider KIND]: /docs/user/loadbalancer/ kind-0.27.0/site/content/docs/user/kind-example-config.yaml000066400000000000000000000013671475376161000235770ustar00rootroot00000000000000# this config file contains all config fields with comments # NOTE: this is not a particularly useful config file kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 # patch the generated kubeadm config with some extra settings kubeadmConfigPatches: - | apiVersion: kubelet.config.k8s.io/v1beta1 kind: KubeletConfiguration evictionHard: nodefs.available: "0%" # patch it further using a JSON 6902 patch kubeadmConfigPatchesJSON6902: - group: kubeadm.k8s.io version: v1beta3 kind: ClusterConfiguration patch: | - op: add path: /apiServer/certSANs/- value: my-hostname # 1 control plane node and 3 workers nodes: # the control plane node config - role: control-plane # the three workers - role: worker - role: worker - role: worker kind-0.27.0/site/content/docs/user/known-issues.md000066400000000000000000000464541475376161000220670ustar00rootroot00000000000000--- title: "Known Issues" menu: main: parent: "user" identifier: "known-issues" weight: 2 description: |- Having problems with kind? This guide covers some known problems and solutions / workarounds. It may additionally be helpful to: - check our [issue tracker] - [file an issue][file an issue] (if there isn't one already) - reach out and ask for help in [#kind] on the [kubernetes slack] [issue tracker]: https://github.com/kubernetes-sigs/kind/issues [file an issue]: https://github.com/kubernetes-sigs/kind/issues/new [#kind]: https://kubernetes.slack.com/messages/CEKK1KTN2/ [kubernetes slack]: https://slack.k8s.io/ --- ## Contents * [Troubleshooting Kind](#troubleshooting-kind) * [Kubectl Version Skew](#kubectl-version-skew) (Kubernetes limits supported version skew) * [Docker Installed With Snap](#docker-installed-with-snap) (snap filesystem restrictions problematic) * [Failure to Build Node Image](#failure-to-build-node-image) (usually need to increase resources) * [Failing to Properly Start Cluster](#failing-to-properly-start-cluster) (various causes) * [Pod Errors Due to "too many open files"](#pod-errors-due-to-too-many-open-files) (likely [inotify] limits which are not namespaced) * [Docker Permission Denied](#docker-permission-denied) (ensure you have permission to use docker) * [Windows Containers](#windows-containers) (unsupported / infeasible) * [Unsupported Architectures](#unsupported-architectures) (images not pre-built yet) * [Unable to Pull Images](#unable-to-pull-images) (various) * [Chrome OS](#chrome-os) (needs KubeletInUserNamespace) * [AppArmor](#apparmor) (may break things, consider disabling) * [IPv6 Port Forwarding](#ipv6-port-forwarding) (docker doesn't seem to implement this correctly) * [Couldn't find an alternative telinit implementation to spawn](#docker-init-daemon-config) * [Fedora](#fedora) (various) * [Failed to get rootfs info](#failed-to-get-rootfs-info--stat-failed-on-dev) * [Docker Desktop for macOS and Windows](#docker-desktop-for-macos-and-windows) * [Older Linux Distributions](#older-linux-distributions) * [Failure to Create Cluster on WSL2](#failure-to-create-cluster-on-wsl2) ## Troubleshooting Kind If the cluster fails to create, try again with the `--retain` option (preserving the failed container), then run `kind export logs` to export the logs from the container to a temporary directory on the host. ## Kubectl Version Skew You may have problems interacting with your kind cluster if your client(s) are skewed too far from the kind node version. Kubernetes [only supports limited skew][version skew] between clients and the API server. This is a issue that frequently occurs when running `kind` alongside Docker For Mac. This problem is related to a bug in [docker on macOS][for-mac#3663] If you see something like the following error message: ```bash $ kubectl edit deploy -n kube-system kubernetes-dashboard error: SchemaError(io.k8s.api.autoscaling.v2beta1.ExternalMetricStatus): invalid object doesn't have additional properties ``` You can check your client and server versions by running: {{< codeFromInline lang="bash" >}} kubectl version {{< /codeFromInline >}} If there is a mismatch between the server and client versions, you should install a newer client version. If you are using Mac, you can install kubectl via homebrew by running: {{< codeFromInline lang="bash" >}} brew install kubernetes-cli {{< /codeFromInline >}} And overwrite the symlinks created by Docker For Mac by running: {{< codeFromInline lang="bash" >}} brew link --overwrite kubernetes-cli {{< /codeFromInline >}} [for-mac#3663]: https://github.com/docker/for-mac/issues/3663 ## Docker Installed with Snap If you installed Docker with [snap], it is likely that `docker` commands do not have access to `$TMPDIR`. This may break some kind commands which depend on using temp directories (`kind build ...`). Currently a workaround for this is setting the `TMPDIR` environment variable to a directory snap does have access to when working with kind. This can for example be some directory under `$HOME`. ## Failure to build node image Building kind's node image may fail due to running out of memory on Docker for Mac or Docker for Windows. See [kind#229][kind#229]. If you see something like this: ```txt cmd/kube-scheduler cmd/kube-proxy /usr/local/go/pkg/tool/linux_amd64/link: signal: killed !!! [0116 08:30:53] Call tree: !!! [0116 08:30:53] 1: /go/src/k8s.io/kubernetes/hack/lib/golang.sh:614 kube::golang::build_some_binaries(...) !!! [0116 08:30:53] 2: /go/src/k8s.io/kubernetes/hack/lib/golang.sh:758 kube::golang::build_binaries_for_platform(...) !!! [0116 08:30:53] 3: hack/make-rules/build.sh:27 kube::golang::build_binaries(...) !!! [0116 08:30:53] Call tree: !!! [0116 08:30:53] 1: hack/make-rules/build.sh:27 kube::golang::build_binaries(...) !!! [0116 08:30:53] Call tree: !!! [0116 08:30:53] 1: hack/make-rules/build.sh:27 kube::golang::build_binaries(...) make: *** [all] Error 1 Makefile:92: recipe for target 'all' failed !!! [0116 08:30:54] Call tree: !!! [0116 08:30:54] 1: build/../build/common.sh:518 kube::build::run_build_command_ex(...) !!! [0116 08:30:54] 2: build/release-images.sh:38 kube::build::run_build_command(...) make: *** [quick-release-images] Error 1 ERRO[08:30:54] Failed to build Kubernetes: failed to build images: exit status 2 Error: error building node image: failed to build kubernetes: failed to build images: exit status 2 Usage: kind build node-image [flags] Flags: --base-image string name:tag of the base image to use for the build (default "kindest/base:v20181203-d055041") -h, --help help for node-image --image string name:tag of the resulting image to be built (default "kindest/node:latest") --type string build type, default is docker (default "docker") Global Flags: -q, --quiet silence all stderr output -v, --verbosity int32 info log verbosity, higher value produces more output error building node image: failed to build kubernetes: failed to build images: exit status 2 ``` Then you may try increasing the resource limits for the Docker engine on Mac or Windows. It is recommended that you allocate at least 8GB of RAM to build Kubernetes. Open the **Preferences** (macOS) or **Settings** (Windows) menu. On macOS: Docker Preferences on macOS On Windows: Docker Preferences on Windows Go to the **Advanced** settings page, and change the settings there, see [changing Docker's resource limits][Docker resource lims]. On macOS: Setting 8Gb of memory in Docker for Mac On Windows: Setting 8Gb of memory in Docker for Windows ## Failing to properly start cluster This issue is similar to a [failure while building the node image](#failure-to-build-node-image). If the cluster creation process was successful but you are unable to see any Kubernetes resources running, for example: ```txt $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES c0261f7512fd kindest/node:v1.12.2 "/usr/local/bin/entr…" About a minute ago Up About a minute 0.0.0.0:64907->64907/tcp kind-1-control-plane $ docker exec -it c0261f7512fd /bin/sh # docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES # ``` or `kubectl` being unable to connect to the cluster, ```txt $ kind export kubeconfig $ kubectl cluster-info To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'. Unable to connect to the server: EOF ``` Then as in [kind#156][kind#156], you may solve this issue by claiming back some space on your machine by removing unused data or images left by the Docker engine by running: {{< codeFromInline lang="bash" >}} docker system prune {{< /codeFromInline >}} And / or: {{< codeFromInline lang="bash" >}} docker image prune {{< /codeFromInline >}} You can verify the issue by exporting the logs (`kind export logs`) and looking at the kubelet logs, which may have something like the following: ```txt Dec 07 00:37:53 kind-1-control-plane kubelet[688]: I1207 00:37:53.229561 688 eviction_manager.go:340] eviction manager: must evict pod(s) to reclaim ephemeral-storage Dec 07 00:37:53 kind-1-control-plane kubelet[688]: E1207 00:37:53.229638 688 eviction_manager.go:351] eviction manager: eviction thresholds have been met, but no pods are active to evict ``` ## Pod errors due to "too many open files" This may be caused by running out of [inotify](https://linux.die.net/man/7/inotify) resources. Resource limits are defined by `fs.inotify.max_user_watches` and `fs.inotify.max_user_instances` system variables. For example, in Ubuntu these default to 8192 and 128 respectively, which is not enough to create a cluster with many nodes. To increase these limits temporarily run the following commands on the host: {{< codeFromInline lang="bash" >}} sudo sysctl fs.inotify.max_user_watches=524288 sudo sysctl fs.inotify.max_user_instances=512 {{< /codeFromInline >}} To make the changes persistent, edit the file `/etc/sysctl.conf` and add these lines: {{< codeFromInline lang="bash" >}} fs.inotify.max_user_watches = 524288 fs.inotify.max_user_instances = 512 {{< /codeFromInline >}} ## Docker permission denied When using `kind`, we assume that the user you are executing kind as has permission to use docker. If you initially ran Docker CLI commands using `sudo`, you may see the following error, which indicates that your `~/.docker/` directory was created with incorrect permissions due to the `sudo` commands. ```txt WARNING: Error loading config file: /home/user/.docker/config.json open /home/user/.docker/config.json: permission denied ``` To fix this problem, either follow the docker's docs [manage docker as a non root user][manage docker as a non root user], or try to use `sudo` before your commands (if you get `command not found` please check [this comment about sudo with kind][sudo with kind]). ## Docker init daemon config Please make sure that when you use `kind`, you can't have `"init": true` in your `/etc/docker/daemon.json` because that will cause `/sbin/init` to show the following cryptic message *Couldn't find an alternative telinit implementation to spawn*. This has to to with `/sbin/init` not running as process id 1. ## Windows Containers [Docker Desktop for Windows][docker desktop for windows] supports running both Linux (the default) and Windows Docker containers. `kind` for Windows requires Linux containers. To switch between Linux and Windows containers see [this page][switch between windows and linux containers]. Windows containers are not like Linux containers and do not support running docker in docker and therefore cannot support kind. ## Unsupported Architectures KIND currently ships pre-built images for AMD64 and ARM64 architectures. In the future we may support others, but currently demand has been low and the cost to build has been high. To use kind on other architectures, you need to first build a base image and then build a node image. Run `images/base/build.sh` and then taking note of the built image name use `kind build node-image --base-image=kindest/base:tag-i-built`. There are more details about how to do this in the [Quick Start] guide. ## Unable to pull images When using named KIND instances you may sometimes see your images failing to pull correctly on pods. This will usually manifest itself with the following output when doing a `kubectl describe pod my-pod` ```txt Failed to pull image "docker.io/my-custom-image:tag": rpc error: code = Unknown desc = failed to resolve image "docker.io/library/my-custom-image:tag": no available registry endpoint: pull access denied, repository does not exist or may require authorization: server message: insufficient_scope: authorization failed ``` If this image has been loaded onto your kind cluster using the command `kind load docker-image my-custom-image` then you have likely not provided the name parameter. Re-run the command this time adding the `--name my-cluster-name` param: `kind load docker-image my-custom-image --name my-cluster-name` ## Chrome OS To run Kubernetes inside Chrome OS the LXC container must allow nesting. In Crosh session (ctrl+alt+t): ```txt crosh> vmc launch termina (termina) chronos@localhost ~ $ lxc config set penguin security.nesting true (termina) chronos@localhost ~ $ lxc restart penguin ``` Then KIND cluster must use KubeletInUserNamespace feature gate (available since Kubernetes 1.22): ```yaml kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 featureGates: KubeletInUserNamespace: true ``` ## AppArmor If your host has [AppArmor] enabled you may run into [moby/moby/issues/7512](https://github.com/moby/moby/issues/7512#issuecomment-51845976). You will likely need to disable apparmor on your host or at least any profile(s) related to applications you are trying to run in KIND. See Previous Discussion: [kind#1179] ## IPv6 Port Forwarding Docker assumes that all the IPv6 addresses should be reachable, hence doesn't implement port mapping using NAT [moby#17666]. You will likely need to use Kubernetes services like NodePort or LoadBalancer to access your workloads inside the cluster via the nodes IPv6 addresses. See Previous Discussion: [kind#1326] ## Failed to get rootfs info / "stat failed on /dev/..." On some systems, creating a cluster times out with these errors in kubelet.log (device varies): ```txt stat failed on /dev/nvme0n1p3 with error: no such file or directory "Failed to start ContainerManager" err="failed to get rootfs info: failed to get device for dir \"/var/lib/kubelet\": could not find device with major: 0, minor: 40 in cached partitions map" ``` Kubernetes needs access to storage device nodes in order to do some stuff, e.g. tracking free disk space. Therefore, Kind needs to mount the necessary device nodes from the host into the control-plane container — however, it cannot always determine which device Kubernetes requires, since this varies with the host OS and filesystem. For example, the error above occurred with a BTRFS filesystem on Fedora Desktop 35. This can be worked around by including the necessary device as an extra mount in the cluster configuration file. ```yaml kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 nodes: - role: control-plane extraMounts: - hostPath: /dev/nvme0n1p3 containerPath: /dev/nvme0n1p3 propagation: HostToContainer ``` To identify the device that must be listed, two variations have been observed. * The device reported in the error message is a symlink (e.g. `/dev/mapper/luks-903aad3d-...`) — in this case, the config file should refer to the target of that symlink (e.g. `/dev/dm-0`). * The device reported in the error message is a regular block device (e.g. `/dev/nvme0n1p3`) — in this case, use the device reported. See Previous Discussion: [kind#2411] ## Fedora ### Firewalld On Fedora 32 [firewalld] moved to nftables backend by default. This seems to be incompatible with Docker, leading to KIND cluster nodes not being able to reach each other. You can work around this by changing the `FirewallBackend` in the `/etc/firewalld/firewalld.conf` file from `nftables` to `iptables` and restarting firewalld. ```console sed -i /etc/firewalld/firewalld.conf 's/FirewallBackend=.*/FirewallBackend=iptables/' systemctl restart firewalld ``` See [#1547 (comment)](https://github.com/kubernetes-sigs/kind/issues/1547#issuecomment-623756313) and [Docker and Fedora 32 article](https://fedoramagazine.org/docker-and-fedora-32/) ### SELinux On Fedora 33 an update to the SELinux policy causes `kind create cluster` to fail with an error like ```sh docker: Error response from daemon: open /dev/dma_heap: permission denied. ``` Although the policy has been fixed in Fedora 34, the fix has not been backported to Fedora 33 as of June 28, 2021. Putting SELinux in permissive mode (`setenforce 0`) is one known workaround. This disables SELinux until the next boot. For more details, see [kind#2296]. ## Docker Desktop for macOS and Windows Docker containers cannot be executed natively on macOS and Windows, therefore Docker Desktop runs them in a Linux VM. As a consequence, the container networks are not exposed to the host and you cannot reach the kind nodes via IP. You may be able to work around this limitation by configuring [extra port mappings](https://kind.sigs.k8s.io/docs/user/configuration/#extra-port-mappings), leveraging [cloud-provider-kind](https://github.com/kubernetes-sigs/cloud-provider-kind), using a network proxy, or other solution specific to your environment. ## Older Linux Distributions KIND uses a cgroup setting of `cgroupns=private`. The cgroup namespace functionality was added in 2016, so some of the older Linux distributions, using older kernels, do not have the required functionality for KIND to work. Notably, distros like Red Hat Enterprise Linux 7 and its clones. Attempting to create a KIND cluster on a system with an older kernel will result in a failure, with an error message similar to: ```txt Command Output: WARNING: Your kernel does not support cgroup namespaces. Cgroup namespace setting discarded. ``` Using KIND in these environments will require upgrading your OS to a more recent version that supports cgroup namespaces. Another option is to run a virtual machine using a newer kernel. ## Failure to Create Cluster on WSL2 Some Linux kernel options for WSL2 do not have cgroup configured in a way that KIND and other Linux-focused tools may expect. This may result in a failure message when attempting to create a cluster, similar to: ```txt unable to start container process: error adding pid 655569 to cgroups ``` The KIND development team is not able to provide support with Windows and WSL, so the project relies on community support and feedback. It has been noted that the steps detailed in [https://github.com/spurin/wsl-cgroupsv2](https://github.com/spurin/wsl-cgroupsv2) have been necessary to resolve this issue. [kind#156]: https://github.com/kubernetes-sigs/kind/issues/156 [kind#229]: https://github.com/kubernetes-sigs/kind/issues/229 [kind#1179]: https://github.com/kubernetes-sigs/kind/issues/1179 [kind#1326]: https://github.com/kubernetes-sigs/kind/issues/1326 [kind#2296]: https://github.com/kubernetes-sigs/kind/issues/2296 [kind#2411]: https://github.com/kubernetes-sigs/kind/issues/2411 [moby#17666]: https://github.com/moby/moby/issues/17666 [Docker resource lims]: https://docs.docker.com/docker-for-mac/#advanced [snap]: https://snapcraft.io/ [manage docker as a non root user]: https://docs.docker.com/install/linux/linux-postinstall/#manage-docker-as-a-non-root-user [sudo with kind]: https://github.com/kubernetes-sigs/kind/issues/713#issuecomment-512665315 [docker desktop for windows]: https://hub.docker.com/editions/community/docker-ce-desktop-windows [switch between windows and linux containers]: https://docs.docker.com/docker-for-windows/#switch-between-windows-and-linux-containers [version skew]: https://kubernetes.io/docs/setup/release/version-skew-policy/#supported-version-skew [Quick Start]: /docs/user/quick-start [AppArmor]: https://en.wikipedia.org/wiki/AppArmor [firewalld]: https://firewalld.org/ [inotify]: https://en.wikipedia.org/wiki/Inotify kind-0.27.0/site/content/docs/user/loadbalancer.md000066400000000000000000000035651475376161000220250ustar00rootroot00000000000000--- title: "LoadBalancer" menu: main: parent: "user" identifier: "user-loadbalancer" weight: 3 description: |- This guide covers how to get service of type LoadBalancer working in a kind cluster using [Cloud Provider KIND]. This guide complements Cloud Provider KIND [installation docs]. [Cloud Provider KIND]: https://github.com/kubernetes-sigs/cloud-provider-kind [installation docs]: https://github.com/kubernetes-sigs/cloud-provider-kind?tab=readme-ov-file#install [Ingress Guide]: /docs/user/ingress [Configuration Guide]: /docs/user/configuration#extra-port-mappings --- ## Installing Cloud Provider KIND Cloud Provider KIND can be installed using golang {{< codeFromInline lang="bash" >}} go install sigs.k8s.io/cloud-provider-kind@latest {{< /codeFromInline >}} or downloading one of the [released binaries](https://github.com/kubernetes-sigs/cloud-provider-kind/releases). Cloud Provider KIND runs as a standalone binary in your host and connects to your KIND cluster and provisions new Load Balancer containers for your Services. It requires privileges to open ports on the system and to connect to the container runtime. ## Using LoadBalancer The following example creates a loadbalancer service that routes to two http-echo pods, one that outputs foo and the other outputs bar. ```yaml {{% readFile "static/examples/loadbalancer/usage.yaml" %}} ``` Apply the contents {{< codeFromInline lang="yaml" >}} kubectl apply -f https://kind.sigs.k8s.io/examples/loadbalancer/usage.yaml {{< /codeFromInline>}} Now verify that the loadbalancer works by sending traffic to it's external IP and port. {{< codeFromInline lang="bash" >}} LB_IP=$(kubectl get svc/foo-service -o=jsonpath='{.status.loadBalancer.ingress[0].ip}') {{< /codeFromInline >}} ```bash # should output foo and bar on separate lines for _ in {1..10}; do curl ${LB_IP}:5678 done ``` kind-0.27.0/site/content/docs/user/local-registry.md000066400000000000000000000023361475376161000223510ustar00rootroot00000000000000--- title: "Local Registry" menu: main: parent: "user" identifier: "user-local-registry" weight: 3 description: |- This guide covers how to configure KIND with a local container image registry. In the future this will be replaced by [a built-in feature](https://github.com/kubernetes-sigs/kind/issues/1213), and this guide will cover usage instead. --- ## Create A Cluster And Registry The following shell script will create a local docker registry and a kind cluster with it enabled. {{< codeFromFile file="static/examples/kind-with-registry.sh" >}} ## Using The Registry The registry can be used like this. 1. First we'll pull an image `docker pull gcr.io/google-samples/hello-app:1.0` 2. Then we'll tag the image to use the local registry `docker tag gcr.io/google-samples/hello-app:1.0 localhost:5001/hello-app:1.0` 3. Then we'll push it to the registry `docker push localhost:5001/hello-app:1.0` 4. And now we can use the image `kubectl create deployment hello-server --image=localhost:5001/hello-app:1.0` If you build your own image and tag it like `localhost:5001/image:foo` and then use it in kubernetes as `localhost:5001/image:foo`. And use it from inside of your cluster application as `kind-registry:5000`. kind-0.27.0/site/content/docs/user/private-registries.md000066400000000000000000000101431475376161000232340ustar00rootroot00000000000000--- title: "Private Registries" menu: main: parent: "user" identifier: "user-private-registries" weight: 3 toc: true description: |- This guide discusses how to use kind with image registries that require authentication. There are multiple ways to do this, which we try to cover here. --- ## Use ImagePullSecrets Kubernetes supports configuring pods to use `imagePullSecrets` for pulling images. If possible, this is the preferable and most portable route. See [the upstream kubernetes docs for this][imagePullSecrets], kind does not require any special handling to use this. If you already have the config file locally but would still like to use secrets, read through kubernetes' docs for [creating a secret from a file][imagePullFileSecrets]. ## Pull to the Host and Side-Load kind can [load an image][loading an image] from the host with the `kind load ...` commands. If you configure your host with credentials to pull the desired image(s) and then load them to the nodes you can avoid needing to authenticate on the nodes. ## Add Credentials to the Nodes Generally the upstream docs for [using a private registry] apply, with kind there are two options for this. ### Mount a Config File to Each Node If you pre-create a docker config.json containing credential(s) on the host you can mount it to each kind node. Assuming your file is at `/path/to/my/secret.json`, the kind config would be: {{< codeFromInline lang="yaml" >}} kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 nodes: - role: control-plane extraMounts: - containerPath: /var/lib/kubelet/config.json hostPath: /path/to/my/secret.json {{< /codeFromInline >}} #### Use an Access Token A credential can be programmatically added to the nodes at runtime. If you do this then kubelet must be restarted on each node to pick up the new credentials. An example shell snippet for generating a [gcr.io][GCR] cred file on your host machine using Access Tokens: {{< codeFromFile file="static/examples/kind-gcr.sh" >}} #### Use a Service Account Access tokens are short lived, so you may prefer to use a Service Account and keyfile instead. First, either download the key from the console or generate one with gcloud: ``` gcloud iam service-accounts keys create --iam-account ``` Then, replace the `gcloud auth print-access-token | ...` line from the [access token snippet](#use-an-access-token) with: ``` cat | docker login -u _json_key --password-stdin https://gcr.io ``` See Google's [upstream docs][keyFileAuthentication] on key file authentication for more details. [keyFileAuthentication]: https://cloud.google.com/container-registry/docs/advanced-authentication#json_key_file [imagePullSecrets]: https://kubernetes.io/docs/concepts/containers/images/#specifying-imagepullsecrets-on-a-pod [imagePullFileSecrets]: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/#registry-secret-existing-credentials [loading an image]: /docs/user/quick-start/#loading-an-image-into-your-cluster [using a private registry]: https://kubernetes.io/docs/concepts/containers/images/#using-a-private-registry [GCR]: https://cloud.google.com/container-registry/ #### Use a Certificate If you have a registry authenticated with certificates, and both certificates and keys reside on your host folder, it is possible to mount and use them into the `containerd` plugin patching the default configuration, like in the example: {{< codeFromInline lang="yaml" >}} kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 nodes: - role: control-plane # This option mounts the host docker registry folder into # the control-plane node, allowing containerd to access them. extraMounts: - containerPath: /etc/docker/certs.d/registry.dev.example.com hostPath: /etc/docker/certs.d/registry.dev.example.com containerdConfigPatches: - |- [plugins."io.containerd.grpc.v1.cri".registry.configs."registry.dev.example.com".tls] cert_file = "/etc/docker/certs.d/registry.dev.example.com/ba_client.cert" key_file = "/etc/docker/certs.d/registry.dev.example.com/ba_client.key" {{< /codeFromInline >}}kind-0.27.0/site/content/docs/user/quick-start.md000066400000000000000000000466611475376161000216710ustar00rootroot00000000000000--- title: "Quick Start" menu: main: parent: "user" identifier: "user-quick-start" weight: 1 toc: true description: |- This guide covers getting started with the `kind` command. **If you are having problems please see the [known issues] guide.** [known issues]: /docs/user/known-issues --- ## Installation > **NOTE**: `kind` does not require [`kubectl`](https://kubernetes.io/docs/reference/kubectl/overview/), > but you will not be able to perform some of the examples in our docs without it. > To install `kubectl` see the upstream [kubectl installation docs](https://kubernetes.io/docs/tasks/tools/install-kubectl/). If you are a go developer you may find the [go install option](#installing-with-go-install) convenient. Otherwise we supply downloadable [release binaries](#installing-from-release-binaries), community-managed [packages](#installing-with-a-package-manager), and a [source installation guide](#installing-from-source). Stable tagged releases (currently {{< stableVersion >}}) are generally strongly recommended for CI usage in particular. You may need to install the latest code from source at HEAD if you are developing Kubernetes itself at HEAD / the latest sources. ### Installing From Release Binaries Pre-built binaries are available on our [releases page](https://github.com/kubernetes-sigs/kind/releases). To install, download the binary for your platform from "Assets", then rename it to `kind` (or perhaps `kind.exe` on Windows) and place this into your `$PATH` at your preferred binary installation directory. On Linux: {{< codeFromInline lang="bash" >}} # For AMD64 / x86_64 [ $(uname -m) = x86_64 ] && curl -Lo ./kind https://kind.sigs.k8s.io/dl/{{< stableVersion >}}/kind-linux-amd64 # For ARM64 [ $(uname -m) = aarch64 ] && curl -Lo ./kind https://kind.sigs.k8s.io/dl/{{< stableVersion >}}/kind-linux-arm64 chmod +x ./kind sudo mv ./kind /usr/local/bin/kind {{< /codeFromInline >}} On macOS: {{< codeFromInline lang="bash" >}} # For Intel Macs [ $(uname -m) = x86_64 ] && curl -Lo ./kind https://kind.sigs.k8s.io/dl/{{< stableVersion >}}/kind-darwin-amd64 # For M1 / ARM Macs [ $(uname -m) = arm64 ] && curl -Lo ./kind https://kind.sigs.k8s.io/dl/{{< stableVersion >}}/kind-darwin-arm64 chmod +x ./kind mv ./kind /some-dir-in-your-PATH/kind {{< /codeFromInline >}} On Windows in [PowerShell](https://en.wikipedia.org/wiki/PowerShell): {{< codeFromInline lang="powershell" >}} curl.exe -Lo kind-windows-amd64.exe https://kind.sigs.k8s.io/dl/{{< stableVersion >}}/kind-windows-amd64 Move-Item .\kind-windows-amd64.exe c:\some-dir-in-your-PATH\kind.exe {{< /codeFromInline >}} ### Installing From Source In addition to the pre-built binary + package manager installation options listed above you can install kind from source with `go install sigs.k8s.io/kind@{{< stableVersion >}}` or clone this repo and run `make build` from the repository. #### Installing With `make` Using `make build` does not require installing Go and will build kind reproducibly, the binary will be in `bin/kind` inside your clone of the repo. You should only need `make` and standard userspace utilities to run this build, it will automatically obtain the correct go version with our vendored copy of [`gimme`](https://github.com/travis-ci/gimme). You can then call `./bin/kind` to use it, or copy `bin/kind` into some directory in your system `PATH` to use it as `kind` from the command line. `make install` will attempt to mimic `go install` and has the same path requirements as `go install` below. #### Installing with `go install` When installing with [Go](https://golang.org/) please use the latest stable Go release. At least go1.16 or greater is required. To install use: `go install sigs.k8s.io/kind@{{< stableVersion >}}`. If you are building from a local source clone, use `go install .` from the top-level directory of the clone. `go install` will typically put the `kind` binary inside the `bin` directory under `go env GOPATH`, see Go's ["Compile and install packages and dependencies"](https://golang.org/cmd/go/#hdr-Compile_and_install_packages_and_dependencies) for more on this. You may need to add that directory to your `$PATH` if you encounter the error `kind: command not found` after installation, you can find a guide for adding a directory to your `PATH` at https://gist.github.com/nex3/c395b2f8fd4b02068be37c961301caa7#file-path-md. ### Installing With A Package Manager The kind community has enabled installation via the following package managers. > **NOTE**: The following are community supported efforts. The `kind` maintainers are not involved in the creation > of these packages, and the upstream community makes no claims on the validity, safety, or content of them. On macOS via Homebrew: {{< codeFromInline lang="bash" >}} brew install kind {{< /codeFromInline >}} On macOS via MacPorts: {{< codeFromInline lang="bash" >}} sudo port selfupdate && sudo port install kind {{< /codeFromInline >}} On Windows via Chocolatey (https://chocolatey.org/packages/kind) {{< codeFromInline lang="powershell" >}} choco install kind {{< /codeFromInline >}} On Windows via Scoop (https://scoop.sh/#/apps?q=kind&id=faec311bb7c6b4a174169c8c02358c74a78a10c2) {{< codeFromInline lang="powershell" >}} scoop bucket add main scoop install main/kind {{< /codeFromInline >}} On Windows via Winget (https://github.com/microsoft/winget-pkgs/tree/master/manifests/k/Kubernetes/kind) {{< codeFromInline lang="powershell" >}} winget install Kubernetes.kind {{< /codeFromInline >}} ## Creating a Cluster Creating a Kubernetes cluster is as simple as `kind create cluster`. This will bootstrap a Kubernetes cluster using a pre-built [node image][node image]. Prebuilt images are hosted at[`kindest/node`][kindest/node], but to find images suitable for a given release currently you should check the [release notes] for your given kind version (check with `kind version`) where you'll find a complete listing of images created for a kind release. To specify another image use the `--image` flag -- `kind create cluster --image=...`. Using a different image allows you to change the Kubernetes version of the created cluster. If you desire to build the node image yourself with a custom version see the [building images](#building-images) section. By default, the cluster will be given the name `kind`. Use the `--name` flag to assign the cluster a different context name. If you want the `create cluster` command to block until the control plane reaches a ready status, you can use the `--wait` flag and specify a timeout. To use `--wait` you must specify the units of the time to wait. For example, to wait for 30 seconds, do `--wait 30s`, for 5 minutes do `--wait 5m`, etc. More usage can be discovered with `kind create cluster --help`. The kind can auto-detect the [docker], [podman], or [nerdctl] installed and choose the available one. If you want to turn off the auto-detect, use the environment variable `KIND_EXPERIMENTAL_PROVIDER=docker`, `KIND_EXPERIMENTAL_PROVIDER=podman` or `KIND_EXPERIMENTAL_PROVIDER=nerdctl` to select the runtime. ## Interacting With Your Cluster After [creating a cluster](#creating-a-cluster), you can use [kubectl][kubectl] to interact with it by using the [configuration file generated by kind][access multiple clusters]. By default, the cluster access configuration is stored in ${HOME}/.kube/config if $KUBECONFIG environment variable is not set. If $KUBECONFIG environment variable is set, then it is used as a list of paths (normal path delimiting rules for your system). These paths are merged. When a value is modified, it is modified in the file that defines the stanza. When a value is created, it is created in the first file that exists. If no files in the chain exist, then it creates the last file in the list. You can use the `--kubeconfig` flag when creating the cluster, then only that file is loaded. The flag may only be set once and no merging takes place. To see all the clusters you have created, you can use the `get clusters` command. For example, let's say you create two clusters: ``` kind create cluster # Default cluster context name is `kind`. ... kind create cluster --name kind-2 ``` When you list your kind clusters, you will see something like the following: ``` kind get clusters kind kind-2 ``` In order to interact with a specific cluster, you only need to specify the cluster name as a context in kubectl: ``` kubectl cluster-info --context kind-kind kubectl cluster-info --context kind-kind-2 ``` ## Deleting a Cluster If you created a cluster with `kind create cluster` then deleting is equally simple: ``` kind delete cluster ``` If the flag `--name` is not specified, kind will use the default cluster context name `kind` and delete that cluster. > **Note**: By design, requesting to delete a cluster that does not exist > will not return an error. This is intentional and is a means to have an > idempotent way of cleaning up resources. ## Loading an Image Into Your Cluster Docker images can be loaded into your cluster nodes with: `kind load docker-image my-custom-image-0 my-custom-image-1` > **Note**: If using a named cluster you will need to specify the name of the > cluster you wish to load the images into: > `kind load docker-image my-custom-image-0 my-custom-image-1 --name kind-2` Additionally, image archives can be loaded with: `kind load image-archive /my-image-archive.tar` This allows a workflow like: ``` docker build -t my-custom-image:unique-tag ./my-image-dir kind load docker-image my-custom-image:unique-tag kubectl apply -f my-manifest-using-my-image.yaml ``` > **NOTE**: You can get a list of images present on a cluster node by using `docker exec`: > ``` > docker exec -it my-node-name crictl images > ``` > Where `my-node-name` is the name of the Docker container (e.g. `kind-control-plane`). > **NOTE**: The Kubernetes default pull policy is `IfNotPresent` unless the image tag is `:latest` or omitted (and implicitly `:latest`) in which case the default policy is `Always`. `IfNotPresent` causes the Kubelet to skip pulling an image if it already exists. > If you want those images loaded into node to work as expected, please: > > - don't use a `:latest` tag > > and / or: > > - specify `imagePullPolicy: IfNotPresent` or `imagePullPolicy: Never` on your container(s). > > See [Kubernetes imagePullPolicy][Kubernetes imagePullPolicy] for more information. See also: [Using kind with Private Registries][Private Registries]. ## Building Images > **NOTE**: If you're using Docker Desktop, be sure to read [Settings for Docker Desktop](#settings-for-docker-desktop) first. kind runs a local Kubernetes cluster by using Docker containers as "nodes". kind uses the [`node-image`][node image] to run Kubernetes artifacts, such as `kubeadm` or `kubelet`. The `node-image` in turn is built off the [`base-image`][base image], which installs all the dependencies needed for Docker and Kubernetes to run in a container. Currently, kind supports one default way to build a `node-image` if you have the [Kubernetes][kubernetes] source in your host machine (`$GOPATH/src/k8s.io/kubernetes`), by using `source`. You can also specify a different path to kubernetes source using ``` kind build node-image /path/to/kubernetes/source ``` > **NOTE**: Building Kubernetes node-images requires everything building upstream > Kubernetes requires, we wrap the upstream build. This includes Docker with buildx. > See: https://git.k8s.io/community/contributors/devel/development.md#building-kubernetes-with-docker One shortcut to use a kubernetes release is to specify the version directly to pick up the official tar-gzipped files: ``` kind build node-image v1.30.0 ``` If you prefer to use existing tar-gzipped files like the ones from the kubernetes release, you can specify those as well from a URL or local directory, for example: ``` kind build node-image https://dl.k8s.io/v1.30.0/kubernetes-server-linux-arm64.tar.gz kind build node-image $HOME/Downloads/kubernetes-server-linux-amd64.tar.gz ``` To clear any confusion, you can specify the type of build explicitly using `--type` parameter, please see the following examples: ``` kind build node-image --type url https://dl.k8s.io/v1.30.0/kubernetes-server-linux-arm64.tar.gz kind build node-image --type file $HOME/Downloads/kubernetes-server-linux-amd64.tar.gz kind build node-image --type release v1.30.0 kind build node-image --type source $HOME/go/src/k8s.io/kubernetes/ ``` > **NOTE**: modes other than source directory namely `url`, `file` and `release` are only > available in kind v0.24 and above. ### Settings for Docker Desktop If you are building Kubernetes (for example - `kind build node-image`) on MacOS or Windows then you need a minimum of 6GB of RAM dedicated to the virtual machine (VM) running the Docker engine. 8GB is recommended. To change the resource limits for the Docker on Mac, you'll need to open the **Preferences** menu. Now, go to the **Advanced** settings page, and change the settings there, see [changing Docker's resource limits][Docker resource lims]. Setting 8Gb of memory in Docker for Mac To change the resource limits for the Docker on Windows, you'll need to right-click the Moby icon on the taskbar, and choose "Settings". If you see "Switch to Linux Containers", then you'll need to do that first before opening "Settings" Now, go to the **Advanced** settings page, and change the settings there, see [changing Docker's resource limits][Docker resource lims]. Setting 8Gb of memory in Docker for Windows You may also try removing any unused data left by the Docker engine - e.g., `docker system prune`. ## Advanced ### Configuring Your kind Cluster For a sample kind configuration file see [kind-example-config][kind-example-config]. To specify a configuration file when creating a cluster, use the `--config` flag: ``` kind create cluster --config kind-example-config.yaml ``` #### Multi-node clusters In particular, many users may be interested in multi-node clusters. A simple configuration for this can be achieved with the following config file contents: ```yaml # three node (two workers) cluster config kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 nodes: - role: control-plane - role: worker - role: worker ``` #### Control-plane HA You can also have a cluster with multiple control-plane nodes: ```yaml # a cluster with 3 control-plane nodes and 3 workers kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 nodes: - role: control-plane - role: control-plane - role: control-plane - role: worker - role: worker - role: worker ``` #### Mapping ports to the host machine You can map extra ports from the nodes to the host machine with `extraPortMappings`: ```yaml kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 nodes: - role: control-plane extraPortMappings: - containerPort: 80 hostPort: 80 listenAddress: "0.0.0.0" # Optional, defaults to "0.0.0.0" protocol: udp # Optional, defaults to tcp ``` This can be useful if using `NodePort` services or daemonsets exposing host ports. Note: binding the `listenAddress` to `127.0.0.1` may affect your ability to access the service. You may want to see the [Ingress Guide] and [LoadBalancer Guide]. [Ingress Guide]: /docs/user/ingress [LoadBalancer Guide]: /docs/user/loadbalancer #### Setting Kubernetes version You can also set a specific Kubernetes version by setting the `node`'s container image. You can find available image tags on the [releases page](https://github.com/kubernetes-sigs/kind/releases). Please use the `sha256` shasum for your desired kubernetes version, as seen in this example: ```yaml kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 nodes: - role: control-plane image: kindest/node:v1.16.4@sha256:b91a2c2317a000f3a783489dfb755064177dbc3a0b2f4147d50f04825d016f55 - role: worker image: kindest/node:v1.16.4@sha256:b91a2c2317a000f3a783489dfb755064177dbc3a0b2f4147d50f04825d016f55 ``` ### Enable Feature Gates in Your Cluster Feature gates are a set of key=value pairs that describe alpha or experimental features. In order to enable a gate you have to [customize your kubeadm configuration][customize control plane with kubeadm], and it will depend on what gate and component you want to enable. An example kind config can be: {{< codeFromInline lang="yaml" >}} kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 featureGates: FeatureGateName: true {{< /codeFromInline >}} ### Configure kind to use a proxy If you are running kind in an environment that requires a proxy, you may need to configure kind to use it. You can configure kind to use a proxy using one or more of the following [environment variables][proxy environment variables] (uppercase takes precedence): * `HTTP_PROXY` or `http_proxy` * `HTTPS_PROXY` or `https_proxy` * `NO_PROXY` or `no_proxy` > **NOTE**: If you set a proxy it would be passed along to everything in the kind nodes. `kind` will automatically append certain addresses into `NO_PROXY` before passing it to the nodes so that Kubernetes components connect to each other directly, but you may need to configure > additional addresses depending on your usage. ### Exporting Cluster Logs kind has the ability to export all kind related logs for you to explore. To export all logs from the default cluster (context name `kind`): ``` kind export logs Exported logs to: /tmp/396758314 ``` Like all other commands, if you want to perform the action on a cluster with a different context name use the `--name` flag. As you can see, kind placed all the logs for the cluster `kind` in a temporary directory. If you want to specify a location then simply add the path to the directory after the command: ``` kind export logs ./somedir Exported logs to: ./somedir ``` The structure of the logs will look more or less like this: ``` . ├── docker-info.txt └── kind-control-plane/ ├── containers ├── docker.log ├── inspect.json ├── journal.log ├── kubelet.log ├── kubernetes-version.txt └── pods/ ``` The logs contain information about the Docker host, the containers running kind, the Kubernetes cluster itself, etc. [modules]: https://github.com/golang/go/wiki/Modules [go-supported]: https://golang.org/doc/devel/release.html#policy [docker]: https://www.docker.com/ [podman]: https://podman.io/ [nerdctl]: https://github.com/containerd/nerdctl [known issues]: /docs/user/known-issues [releases]: https://github.com/kubernetes-sigs/kind/releases [node image]: /docs/design/node-image [base image]: /docs/design/base-image [kind-example-config]: https://raw.githubusercontent.com/kubernetes-sigs/kind/main/site/content/docs/user/kind-example-config.yaml [kubernetes]: https://github.com/kubernetes/kubernetes [kindest/node]: https://hub.docker.com/r/kindest/node/ [kubectl]: https://kubernetes.io/docs/reference/kubectl/overview/ [Docker resource lims]: https://docs.docker.com/docker-for-mac/#advanced [install docker]: https://docs.docker.com/install/ [proxy environment variables]: https://docs.docker.com/network/proxy/#use-environment-variables [CGO]: https://golang.org/cmd/cgo/ [Kubernetes imagePullPolicy]: https://kubernetes.io/docs/concepts/containers/images/#updating-images [Private Registries]: /docs/user/private-registries [customize control plane with kubeadm]: https://kubernetes.io/docs/setup/independent/control-plane-flags/ [access multiple clusters]: https://kubernetes.io/docs/tasks/access-application-cluster/configure-access-multiple-clusters/ [release notes]: https://github.com/kubernetes-sigs/kind/releases kind-0.27.0/site/content/docs/user/resources.md000066400000000000000000000105161475376161000214220ustar00rootroot00000000000000--- title: "Resources" menu: main: parent: "user" identifier: "resources" weight: 4 toc: true description: |- This page contains references to additional external resources for learning about KIND and how to use it. --- ## Using KIND in CI The [kind-ci/examples] project is a work-in-progress project to give working examples of using KIND in various continuous integration environments / platforms. ## Tutorials and Guides Here are a useful external guides / tutorials covering things not yet covered in our docs: ### How to use KIND with MetallLB > **NOTE**: We now have a kind integrated loadbalancer solution, see [the loadbalancer page](/docs/user/loadbalancer/). https://mauilion.dev/posts/kind-metallb/ ### How to Test a Kubernetes PR with KIND https://mauilion.dev/posts/kind-k8s-testing/ ### Using Contour Ingress with KIND https://projectcontour.io/kindly-running-contour/ ### Local Ingress Domains for your Kind Cluster https://mjpitz.com/blog/2020/10/21/local-ingress-domains-kind/ ### Connect directly to Docker-for-Mac containers via IP address https://golangexample.com/connect-directly-to-docker-for-mac-containers-via-ip-address/ ### Developing for Kubernetes with KinD https://docs.gitlab.com/charts/development/kind/ ### Using CRI-O with KIND > **NOTE**: Depending on implementation details of the node image is not supported, only that the node images contain what kind needs to run Kubernetes at a given version. > > Installing CRI-O is possible but not supported. https://github.com/cri-o/cri-o/blob/release-1.31/tutorials/crio-in-kind.md#cri-o-in-kind ## KubeCon Talks The authors have given the following talks relating to KIND: ### Keep Calm and Load Balance on KIND - Benjamin Elder & Antonio Ojea At KubeCon EU 2024 we spoke about [Cloud Provider KIND and how to use Load Balancers Services with KIND](https://sched.co/1YhhY) {{< youtube id="U6_-y24rJnI" class="video-wrapper" >}} ### Deep Dive: KIND - Benjamin Elder & Antonio Ojea At KubeCon US 2019 we spoke about [KIND internals and the challenges ahead on the road to 1.0][kind-deep-dive]. {{< youtube id="tT-GiZAr6eQ" class="video-wrapper" >}} ### A Kind Workflow for Contributing to Kubernetes - Benjamin Elder & Duffie Cooley & James Munnelly & Patrick Lang At KubeCon US 2019 we provided a hands on tutorial [for contributing and testing your Kubernetes code with KIND][kind-workflow-for-contributing-to-kubernetes]. {{< youtube id="BPVO2mcfjJk" class="video-wrapper" >}} ### Testing your K8s apps with KIND - Benjamin Elder & James Munnelly At KubeCon EU 2019 we spoke about [KIND and testing your Kubernetes Applications][testing-k8s-apps-with-kind]. {{< youtube id="8KtmevMFfxA" class="video-wrapper" >}} ### Deep Dive: Testing SIG - Benjamin Elder & James Munnelly At KubeCon EU 2019 we spoke about KIND and how we use it to test Kubernetes for the [SIG Testing Deep Dive][sig-testing-deep-dive-kind]. {{< youtube id="6m9frvTxK0o" class="video-wrapper" >}} ### Behind Your PR: How Kubernetes Uses Kubernetes to Run Kubernetes CI - Sen Lu & Benjamin Elder At KubeCon NA 2018 we spoke with [Sen Lu][@krzyzacy] about The Kubernetes Project's testing tools and infrastructure, including a brief discussion of KIND and running it on Kubernetes's Kubernetes-based CI infrastructure. {{< youtube id="pz0lpl6h-Gc" class="video-wrapper" >}} [@krzyzacy]: https://github.com/krzyzacy [kind-ci/examples]: https://github.com/kind-ci/examples [testing-k8s-apps-with-kind]: https://kccnceu19.sched.com/event/MPYy/testing-your-k8s-apps-with-kind-benjamin-elder-google-james-munnelly-jetstackio [sig-testing-deep-dive-kind]: https://kccnceu19.sched.com/event/MPkC/deep-dive-testing-sig-benjamin-elder-google-james-munnelly-jetstack [kind-deep-dive]: https://kccncna19.sched.com/event/Uah7/deep-dive-kind-benjamin-elder-google-antonio-ojea-garcia-suse [kind-workflow-for-contributing-to-kubernetes]: https://kccncna19.sched.com/event/Uaek/tutorial-a-kind-workflow-for-contributing-to-kubernetes-benjamin-elder-google-duffie-cooley-vmware-james-munnelly-jetstack-patrick-lang-microsoft-limited-available-seating-first-come-first-served-basis kind-0.27.0/site/content/docs/user/rootless.md000066400000000000000000000074541475376161000212710ustar00rootroot00000000000000--- title: "Rootless" menu: main: parent: "user" identifier: "rootless" weight: 3 --- Starting with kind 0.11.0, [Rootless Docker](https://docs.docker.com/go/rootless/), [Rootless Podman](https://github.com/containers/podman/blob/master/docs/tutorials/rootless_tutorial.md) and [Rootless nerdctl](https://github.com/containerd/nerdctl/blob/main/docs/rootless.md) can be used as the node provider of kind. ## Provider requirements - Docker: 20.10 or later - Podman: 3.0 or later - nerdctl: 1.7 or later ## Host requirements The host needs to be running with cgroup v2. Make sure that the result of the `docker info` command contains `Cgroup Version: 2`. If it prints `Cgroup Version: 1`, try adding `GRUB_CMDLINE_LINUX="systemd.unified_cgroup_hierarchy=1"` to `/etc/default/grub` and running `sudo update-grub` to enable cgroup v2. Also, depending on the host configuration, the following steps might be needed: - Create `/etc/systemd/system/user@.service.d/delegate.conf` with the following content, and then run `sudo systemctl daemon-reload`: ```ini [Service] Delegate=yes ``` (This is not enabled by default because ["the runtime impact of [delegating the "cpu" controller] is still too high"](https://lists.fedoraproject.org/archives/list/devel@lists.fedoraproject.org/thread/ZMKLS7SHMRJLJ57NZCYPBAQ3UOYULV65/). Beware that changing this configuration may affect system performance.) Please note that: - `/etc/systemd/system/user@.service.d/` directory needs to be created if not already present on your host - If using Docker and it was already running when this step was done, a restart is needed for the changes to take effect {{< codeFromInline lang="bash" >}} systemctl --user restart docker {{< /codeFromInline >}} - Create `/etc/modules-load.d/iptables.conf` with the following content: ``` ip6_tables ip6table_nat ip_tables iptable_nat ``` - If using podman, be aware that by default there is a [limit](https://docs.podman.io/en/v4.3/markdown/options/pids-limit.html#pids-limit-limit) to the number of pids that can be created. This can cause problems like nginx workers inside a container not spawning correctly. - If you want to disable this limit, edit your `containers.conf` file (generally located in `/etc/containers/containers.conf`). Note that this could cause things like pid exhaustion to happen on the host machine. Alternatively, change `0` to your desired new limit: ```ini [containers] pids_limit = 0 ``` ## Restrictions The restrictions of Rootless Docker apply to kind clusters as well. e.g. - OverlayFS cannot be used unless the host is using kernel >= 5.11, or Ubuntu/Debian kernel - Cannot mount block storage - Cannot mount NFS ## Creating a kind cluster with Rootless Docker To create a kind cluster with Rootless Docker, just run: ```console $ export DOCKER_HOST=unix://${XDG_RUNTIME_DIR}/docker.sock $ kind create cluster ``` ## Creating a kind cluster with Rootless Podman To create a kind cluster with Rootless Podman, just run: ```console $ KIND_EXPERIMENTAL_PROVIDER=podman kind create cluster ``` On some distributions, you might need to use systemd-run to start kind into its own cgroup scope: ```console $ systemd-run --scope --user kind create cluster ``` or ```console $ systemd-run --scope --user -p "Delegate=yes" kind create cluster ``` If you still get the error `running kind with rootless provider requires setting systemd property "Delegate=yes"` even with [host requirements](#host-requirements) configured. ## Creating a kind cluster with Rootless nerdctl **Note: containerd v1.7+ is required** To create a kind cluster with nerdctl, just run: ```console $ KIND_EXPERIMENTAL_PROVIDER=nerdctl kind create cluster ``` ## Tips - To enable OOM watching, allow `dmesg` by running `sysctl -w kernel.dmesg_restrict=0`. kind-0.27.0/site/content/docs/user/using-wsl2.md000066400000000000000000000157231475376161000214270ustar00rootroot00000000000000--- title: "Using WSL2" menu: main: parent: "user" identifier: "using-wsl2" weight: 3 description: |- Kind can run using Windows Subsystem for Linux 2 (WSL2) on Windows 10 May 2020 Update (build 19041). All the tools needed to build or run kind work in WSL2, but some extra steps are needed to switch to WSL2. This page covers these steps in brief but also links to the official documentation if you would like more details. --- ## Getting Windows 10 or 11 Download the latest ISO at https://www.microsoft.com/en-us/software-download/. Choose the latest Windows 10 or Windows 11 release. ### Installing on a virtual machine Required Settings - Supported processor and operating system, see [Enable Nested Virtualization](https://learn.microsoft.com/en-us/virtualization/hyper-v-on-windows/user-guide/nested-virtualization) guide for Windows - Intel processors require Windows 10/Windows Server 2016 or greater and the processor must support VT-x and extended page tables (also known as [second level address translation](https://en.wikipedia.org/wiki/Second_Level_Address_Translation)) - AMD processors require Windows 11/Windows Server 2022 or greater and the processor generation must be AMD EPYC or Ryzen or newer - At least 8GB of memory - It's best to use a static memory allocation, not dynamic. The VM will automatically use paging inside so you don't want it to page on the VM host. - Enable nested virtualization support. On Hyper-V, you need to run this from an admin PowerShell prompt - `Set-VMProcessor -VMName ... -ExposeVirtualizationExtensions $true` - Attach the ISO to a virtual DVD drive - Create a virtual disk with at least 80GB of space Now, start up the VM. Watch carefully for the "Press any key to continue installation..." screen so you don't miss it. Windows Setup will start automatically. ### Installing on a physical machine If you're using a physical machine, you can mount the ISO, copy the files to a FAT32 formatted USB disk, and boot from that instead. Be sure the machine is configured to boot using UEFI (not legacy BIOS), and has Intel VT or AMD-V enabled for the hypervisor. ### Tips during setup - You can skip the product key page - On the "Sign in with Microsoft" screen, look for the "offline account" button. ## Setting up WSL2 If you want the full details, see the [Installation Instructions for WSL2](https://docs.microsoft.com/en-us/windows/wsl/wsl2-install). This is the TL;DR version. Once your Windows machine is ready, you need to do a few more steps to set up WSL2 1. Open a PowerShell window as an admin, then run {{< codeFromInline lang="powershell" >}} Enable-WindowsOptionalFeature -Online -FeatureName VirtualMachinePlatform, Microsoft-Windows-Subsystem-Linux {{< /codeFromInline >}} 1. Reboot when prompted. 1. After the reboot, set WSL to default to WSL2. Open an admin PowerShell window and run {{< codeFromInline lang="powershell" >}} wsl --set-default-version 2 {{< /codeFromInline >}} 1. Now, you can install your Linux distro of choice by searching the Windows Store. If you don't want to use the Windows Store, then follow the steps in the WSL docs for [manual install](https://docs.microsoft.com/en-us/windows/wsl/install-manual). 1. Start up your distro with the shortcut added to the start menu ## Setting up Docker in WSL2 with Docker Desktop Install Docker with WSL2 backend here: https://docs.docker.com/docker-for-windows/wsl/ ## Setting up Docker in WSL2 without Docker Desktop Alternatively, docker can be installed in WSL2 without using Docker Desktop. See for example: https://dev.to/bowmanjd/install-docker-on-windows-wsl-without-docker-desktop-34m9 Now, move on to the [Quick Start](/docs/user/quick-start) to set up your cluster with kind. ## Accessing a Kubernetes Service running in WSL2 1. prepare cluster config with exported node port {{< codeFromInline lang="yaml" >}} # cluster-config.yml kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 nodes: - role: control-plane extraPortMappings: - containerPort: 30000 hostPort: 30000 protocol: TCP {{< /codeFromInline >}} 1. create cluster `kind create cluster --config=cluster-config.yml` 1. create deployment `kubectl create deployment nginx --image=nginx --port=80` 1. create service `kubectl create service nodeport nginx --tcp=80:80 --node-port=30000` 1. access service `curl localhost:30000` Alternatively, see [Helpful Tips for WSL2](#helpful-tips-for-wsl2) ## Kubernetes Service with Session Affinity If you want to create a Kubernetes Service with `sessionAffinity: ClientIP` it will not be accessible (and neither will any Service created afterwards). WSL2 kernel is missing `xt_recent` kernel module, which is used by Kube Proxy to implement session affinity. You need to compile a custom kernel to enable this feature. 1. Build a kernel with `xt_recent` kernel module enabled {{< codeFromInline lang="bash" >}} docker run --name wsl-kernel-builder --rm -it ubuntu:latest bash WSL_COMMIT_REF=linux-msft-wsl-5.15.146.1 # change this line to the version you want to build # Install dependencies apt update apt install -y git build-essential flex bison libssl-dev libelf-dev bc dwarves python3 # Checkout WSL2 Kernel repo mkdir src cd src git init git remote add origin https://github.com/microsoft/WSL2-Linux-Kernel.git git config --local gc.auto 0 git -c protocol.version=2 fetch --no-tags --prune --progress --no-recurse-submodules --depth=1 origin +${WSL_COMMIT_REF}:refs/remotes/origin/build/linux-msft-wsl-5.15.y git checkout --progress --force -B build/linux-msft-wsl-5.15.y refs/remotes/origin/build/linux-msft-wsl-5.15.y # Enable xt_recent kernel module sed -i 's/# CONFIG_NETFILTER_XT_MATCH_RECENT is not set/CONFIG_NETFILTER_XT_MATCH_RECENT=y/' Microsoft/config-wsl # Compile the kernel make -j2 KCONFIG_CONFIG=Microsoft/config-wsl # From the host terminal copy the newly built kernel docker cp wsl-kernel-builder:/src/arch/x86/boot/bzImage . {{< /codeFromInline >}} 1. Configure WSL to use newly built kernel: https://docs.microsoft.com/en-us/windows/wsl/wsl-config#configure-global-options-with-wslconfig Create a `.wslconfig` file in `C:\Users\\`: {{< codeFromInline lang="toml" >}} [wsl2] kernel=c:\\path\\to\\your\\kernel\\bzImage {{< /codeFromInline >}} ## Helpful Tips for WSL2 - If you want to terminate the WSL2 instance to save memory or "reboot", open an admin PowerShell prompt and run `wsl --terminate `. Closing a WSL2 window doesn't shut it down automatically. - You can check the status of all installed distros with `wsl --list --verbose`. - If you had a distro installed with WSL1, you can convert it to WSL2 with `wsl --set-version 2` - Alternative of [Accessing a Kubernetes Service running in WSL2](#accessing-a-kubernetes-service-running-in-wsl2) or [Setting Up An Ingress Controller](/docs/user/ingress/#setting-up-an-ingress-controller) for accessing workloads is using `kubectl port-forward --address=0.0.0.0`. - See the [Known Issues](/docs/user/known-issues/) page for additional Windows-related issues and concerns. kind-0.27.0/site/content/docs/user/working-offline.md000066400000000000000000000142061475376161000225100ustar00rootroot00000000000000--- title: "Working Offline" menu: main: parent: "user" identifier: "working-offline" weight: 3 description: |- This guide covers how to work with KIND in an offline / airgapped environment. You should first [install kind][installation documentation] before continuing. [installation documentation]: https://kind.sigs.k8s.io/docs/user/quick-start#installation --- ## Using a pre-built [node image][node image] KIND provides some pre-built images, these images contain everything necessary to create a cluster and can be used in an offline environment. You can find available image tags on the [releases page][releases page]. Please include the `@sha256:` [image digest][image digest] from the image in the release notes. You can pull it when you have network access, or pull it on another machine and then transfer it to the target machine. ``` ➜ ~ docker pull kindest/node:v1.17.0@sha256:9512edae126da271b66b990b6fff768fbb7cd786c7d39e86bdf55906352fdf62 sha256:9512edae126da271b66b990b6fff768fbb7cd786c7d39e86bdf55906352fdf62: Pulling from kindest/node cc5a81c29aab: Pull complete 81c62728355f: Pull complete ed9cffdd962a: Pull complete 6a46f000fce2: Pull complete 6bd890da28be: Pull complete 0d88bd219ffe: Pull complete af5240f230f0: Pull complete Digest: sha256:9512edae126da271b66b990b6fff768fbb7cd786c7d39e86bdf55906352fdf62 Status: Downloaded newer image for kindest/node@sha256:9512edae126da271b66b990b6fff768fbb7cd786c7d39e86bdf55906352fdf62 docker.io/kindest/node:v1.17.0@sha256:9512edae126da271b66b990b6fff768fbb7cd786c7d39e86bdf55906352fdf62 ``` You can [save node image][docker save] to a tarball. ``` ➜ ~ docker save -o kind.v1.17.0.tar kindest/node:v1.17.0@sha256:9512edae126da271b66b990b6fff768fbb7cd786c7d39e86bdf55906352fdf62 # or ➜ ~ docker save kindest/node:v1.17.0@sha256:9512edae126da271b66b990b6fff768fbb7cd786c7d39e86bdf55906352fdf62 | gzip > kind.v1.17.0.tar.gz ``` When you transport image tarball to the machine, you can load the node image by [`docker load`][docker load] command. ``` ➜ ~ docker load -i kind.v1.17.0.tar Loaded image ID: sha256:ec6ab22d89efc045f4da4fc862f6a13c64c0670fa7656fbecdec5307380f9cb0 # or ➜ ~ docker load -i kind.v1.17.0.tar.gz Loaded image ID: sha256:ec6ab22d89efc045f4da4fc862f6a13c64c0670fa7656fbecdec5307380f9cb0 ``` And [create a tag][docker tag] for it. ``` ➜ ~ docker image tag kindest/node:v1.17.0@sha256:9512edae126da271b66b990b6fff768fbb7cd786c7d39e86bdf55906352fdf62 kindest/node:v1.17.0 ➜ ~ docker image ls kindest/node REPOSITORY TAG IMAGE ID CREATED SIZE kindest/node v1.17.0 ec6ab22d89ef 3 weeks ago 1.23GB ``` Finally, you can create a cluster by specifying the `--image` flag. ``` ➜ ~ kind create cluster --image kindest/node:v1.17.0 Creating cluster "kind" ... ✓ Ensuring node image (kindest/node:v1.17.0) 🖼 ✓ Preparing nodes 📦 ✓ Writing configuration 📜 ✓ Starting control-plane 🕹️ ✓ Installing CNI 🔌 ✓ Installing StorageClass 💾 Set kubectl context to "kind-kind" You can now use your cluster with: kubectl cluster-info --context kind-kind Have a question, bug, or feature request? Let us know! https://kind.sigs.k8s.io/#community 🙂 ``` ## Building the [node image][node image] In addition to using pre-built node image, KIND also provides the ability to build [node image][node image] from Kubernetes source code. Please note that during the image building process, you need to download many dependencies. It is recommended that you build at least once online to ensure that these dependencies are downloaded to your local. See [building the node image][building the node image] for more detail. The node-image in turn is built off the [base image][base image]. ### Prepare Kubernetes source code You can clone Kubernetes source code. ```sh ➜ ~ mkdir -p $GOPATH/src/k8s.io ➜ ~ cd $GOPATH/src/k8s.io ➜ ~ git clone https://github.com/kubernetes/kubernetes ``` ### Building image ```sh ➜ ~ kind build node-image --image kindest/node:main $GOPATH/src/k8s.io/kubernetes Starting to build Kubernetes ... Image build completed. ``` When the image build is complete, you can create a cluster by passing the `--image` flag. ```sh ➜ ~ kind create cluster --image kindest/node:main Creating cluster "kind" ... ✓ Ensuring node image (kindest/node:main) 🖼 ✓ Preparing nodes 📦 ✓ Writing configuration 📜 ✓ Starting control-plane 🕹️ ✓ Installing CNI 🔌 ✓ Installing StorageClass 💾 Set kubectl context to "kind-kind" You can now use your cluster with: kubectl cluster-info --context kind-kind Have a question, bug, or feature request? Let us know! https://kind.sigs.k8s.io/#community 🙂 ``` ## HA cluster If you want to create a control-plane HA cluster then you need to create a config file and use this file to start the cluster. ```sh ➜ ~ cat << EOF | kind create cluster --config=- kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 # 3 control plane node and 1 workers nodes: - role: control-plane - role: control-plane - role: control-plane - role: worker EOF ``` Note that in an offline environment, in addition to preparing the node image, you also need to prepare HAProxy image in advance. You can find the specific tag currently in use at [loadbalancer source code][loadbalancer source code]. [installation documentation]: https://kind.sigs.k8s.io/docs/user/quick-start#installation [node image]: https://kind.sigs.k8s.io/docs/design/node-image [releases page]: https://github.com/kubernetes-sigs/kind/releases [image digest]: https://docs.docker.com/engine/reference/commandline/pull/#pull-an-image-by-digest-immutable-identifier [docker save]: https://docs.docker.com/engine/reference/commandline/save/ [docker load]: https://docs.docker.com/engine/reference/commandline/load/ [docker tag]: https://docs.docker.com/engine/reference/commandline/tag/ [base image]: https://kind.sigs.k8s.io/docs/design/base-image/ [building the node image]: https://kind.sigs.k8s.io/docs/user/quick-start/#building-images [loadbalancer source code]: https://github.com/kubernetes-sigs/kind/blob/main/pkg/cluster/internal/loadbalancer/const.go#L20 kind-0.27.0/site/data/000077500000000000000000000000001475376161000144145ustar00rootroot00000000000000kind-0.27.0/site/data/apiVersions.yaml000066400000000000000000000000131475376161000175740ustar00rootroot00000000000000- v1alpha4 kind-0.27.0/site/go.mod000066400000000000000000000140301475376161000146070ustar00rootroot00000000000000module sigs.k8s.io/kind/site go 1.18 require github.com/gohugoio/hugo v0.111.3 require ( cloud.google.com/go v0.101.0 // indirect cloud.google.com/go/compute v1.6.1 // indirect cloud.google.com/go/iam v0.3.0 // indirect cloud.google.com/go/storage v1.22.0 // indirect github.com/Azure/azure-pipeline-go v0.2.3 // indirect github.com/Azure/azure-storage-blob-go v0.14.0 // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest/autorest v0.11.20 // indirect github.com/Azure/go-autorest/autorest/adal v0.9.15 // indirect github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect github.com/BurntSushi/locker v0.0.0-20171006230638-a6e239ea1c69 // indirect github.com/PuerkitoBio/purell v1.1.1 // indirect github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/alecthomas/chroma/v2 v2.5.0 // indirect github.com/armon/go-radix v1.0.0 // indirect github.com/aws/aws-sdk-go v1.43.5 // indirect github.com/aws/aws-sdk-go-v2 v1.9.0 // indirect github.com/aws/aws-sdk-go-v2/config v1.7.0 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.4.0 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.5.0 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.2.2 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.3.0 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.4.0 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.7.0 // indirect github.com/aws/smithy-go v1.8.0 // indirect github.com/bep/clock v0.3.0 // indirect github.com/bep/debounce v1.2.0 // indirect github.com/bep/gitmap v1.1.2 // indirect github.com/bep/goat v0.5.0 // indirect github.com/bep/godartsass v0.16.0 // indirect github.com/bep/golibsass v1.1.0 // indirect github.com/bep/gowebp v0.2.0 // indirect github.com/bep/lazycache v0.2.0 // indirect github.com/bep/overlayfs v0.6.0 // indirect github.com/bep/tmc v0.5.1 // indirect github.com/clbanning/mxj/v2 v2.5.7 // indirect github.com/cli/safeexec v1.0.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/disintegration/gift v1.2.1 // indirect github.com/dlclark/regexp2 v1.7.0 // indirect github.com/dustin/go-humanize v1.0.0 // indirect github.com/evanw/esbuild v0.17.0 // indirect github.com/frankban/quicktest v1.14.4 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/getkin/kin-openapi v0.110.0 // indirect github.com/ghodss/yaml v1.0.0 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/swag v0.19.5 // indirect github.com/gobuffalo/flect v0.3.0 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/gohugoio/go-i18n/v2 v2.1.3-0.20210430103248-4c28c89f8013 // indirect github.com/gohugoio/locales v0.14.0 // indirect github.com/gohugoio/localescompressed v1.0.1 // indirect github.com/golang-jwt/jwt/v4 v4.0.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/google/uuid v1.3.0 // indirect github.com/google/wire v0.5.0 // indirect github.com/googleapis/gax-go/v2 v2.3.0 // indirect github.com/googleapis/go-type-adapters v1.0.0 // indirect github.com/gorilla/websocket v1.5.0 // indirect github.com/hairyhenderson/go-codeowners v0.2.3-0.20201026200250-cdc7c0759690 // indirect github.com/hashicorp/golang-lru/v2 v2.0.1 // indirect github.com/inconshreveable/mousetrap v1.0.1 // indirect github.com/invopop/yaml v0.1.0 // indirect github.com/jdkato/prose v1.2.1 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect github.com/kyokomi/emoji/v2 v2.2.11 // indirect github.com/magefile/mage v1.14.0 // indirect github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e // indirect github.com/marekm4/color-extractor v1.2.0 // indirect github.com/mattn/go-ieproxy v0.0.1 // indirect github.com/mattn/go-isatty v0.0.17 // indirect github.com/mattn/go-runewidth v0.0.9 // indirect github.com/mitchellh/hashstructure v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/muesli/smartcrop v0.3.0 // indirect github.com/niklasfasching/go-org v1.6.6 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/pelletier/go-toml/v2 v2.0.6 // indirect github.com/rogpeppe/go-internal v1.9.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd // indirect github.com/sanity-io/litter v1.5.5 // indirect github.com/spf13/afero v1.9.3 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/spf13/cobra v1.6.1 // indirect github.com/spf13/fsync v0.9.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/tdewolff/minify/v2 v2.12.4 // indirect github.com/tdewolff/parse/v2 v2.6.5 // indirect github.com/yuin/goldmark v1.5.4 // indirect go.opencensus.io v0.24.0 // indirect go.uber.org/atomic v1.10.0 // indirect gocloud.dev v0.24.0 // indirect golang.org/x/crypto v0.3.0 // indirect golang.org/x/exp v0.0.0-20221031165847-c99f073a8326 // indirect golang.org/x/image v0.5.0 // indirect golang.org/x/net v0.7.0 // indirect golang.org/x/oauth2 v0.2.0 // indirect golang.org/x/sync v0.1.0 // indirect golang.org/x/sys v0.5.0 // indirect golang.org/x/text v0.7.0 // indirect golang.org/x/tools v0.4.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect google.golang.org/api v0.76.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20220426171045-31bebdecfb46 // indirect google.golang.org/grpc v1.46.0 // indirect google.golang.org/protobuf v1.28.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) kind-0.27.0/site/go.sum000066400000000000000000003143561475376161000146520ustar00rootroot00000000000000bazil.org/fuse v0.0.0-20180421153158-65cc252bf669/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= cloud.google.com/go v0.82.0/go.mod h1:vlKccHJGuFBFufnAnuB08dfEH9Y3H7dzDzRECFdC2TA= cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= cloud.google.com/go v0.88.0/go.mod h1:dnKwfYbP9hQhefiUvpbcAyoGSHUrOxR20JVElLiUvEY= cloud.google.com/go v0.89.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= cloud.google.com/go v0.92.2/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= cloud.google.com/go v0.92.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= cloud.google.com/go v0.94.0/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= cloud.google.com/go v0.101.0 h1:g+LL+JvpvdyGtcaD2xw2mSByE/6F9s471eJSoaysM84= cloud.google.com/go v0.101.0/go.mod h1:hEiddgDb77jDQ+I80tURYNJEnuwPzFU8awCFFRLKjW0= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= cloud.google.com/go/compute v1.6.1 h1:2sMmt8prCn7DPaG4Pmh0N3Inmc8cT8ae5k1M6VJ9Wqc= cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.5.0/go.mod h1:c4nNYR1qdq7eaZ+jSc5fonrQN2k3M7sWATcYTiakjEo= cloud.google.com/go/iam v0.3.0 h1:exkAomrVUuzx9kWFI1wm3KI0uoDeUFPB4kKGzx6x+Gc= cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= cloud.google.com/go/kms v0.1.0/go.mod h1:8Qp8PCAypHg4FdmlyW1QRAv09BGQ9Uzh7JnmIZxPk+c= cloud.google.com/go/monitoring v0.1.0/go.mod h1:Hpm3XfzJv+UTiXzCG5Ffp0wijzHTC7Cv4eR7o3x/fEE= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/pubsub v1.16.0/go.mod h1:6A8EfoWZ/lUvCWStKGwAWauJZSiuV0Mkmu6WilK/TxQ= cloud.google.com/go/secretmanager v0.1.0/go.mod h1:3nGKHvnzDUVit7U0S9KAKJ4aOsO1xtwRG+7ey5LK1bM= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= cloud.google.com/go/storage v1.16.1/go.mod h1:LaNorbty3ehnU3rEjXSNV/NRgQA0O8Y+uh6bPe5UOk4= cloud.google.com/go/storage v1.22.0 h1:NUV0NNp9nkBuW66BFRLuMgldN60C57ET3dhbwLIYio8= cloud.google.com/go/storage v1.22.0/go.mod h1:GbaLEoMqbVm6sx3Z0R++gSiBlgMv6yUi2q1DeGFKQgE= cloud.google.com/go/trace v0.1.0/go.mod h1:wxEwsoeRVPbeSkt7ZC9nWCgmoKQRAoySN7XHW2AmI7g= contrib.go.opencensus.io/exporter/aws v0.0.0-20200617204711-c478e41e60e9/go.mod h1:uu1P0UCM/6RbsMrgPa98ll8ZcHM858i/AD06a9aLRCA= contrib.go.opencensus.io/exporter/stackdriver v0.13.8/go.mod h1:huNtlWx75MwO7qMs0KrMxPZXzNNWebav1Sq/pm02JdQ= contrib.go.opencensus.io/integrations/ocsql v0.1.7/go.mod h1:8DsSdjz3F+APR+0z0WkU1aRorQCFfRxvqjUUPMbF3fE= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Azure/azure-amqp-common-go/v3 v3.1.0/go.mod h1:PBIGdzcO1teYoufTKMcGibdKaYZv4avS+O6LNIp8bq0= github.com/Azure/azure-amqp-common-go/v3 v3.1.1/go.mod h1:YsDaPfaO9Ub2XeSKdIy2DfwuiQlHQCauHJwSqtrkECI= github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U= github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= github.com/Azure/azure-sdk-for-go v51.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go v57.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-service-bus-go v0.10.16/go.mod h1:MlkLwGGf1ewcx5jZadn0gUEty+tTg0RaElr6bPf+QhI= github.com/Azure/azure-storage-blob-go v0.14.0 h1:1BCg74AmVdYwO3dlKwtFU1V0wU2PZdREkXvAmZJRUlM= github.com/Azure/azure-storage-blob-go v0.14.0/go.mod h1:SMqIBi+SuiQH32bvyjngEewEeXoPfKMgWlBDaYf6fck= github.com/Azure/go-amqp v0.13.0/go.mod h1:qj+o8xPCz9tMSbQ83Vp8boHahuRDl5mkNHyt1xlxUTs= github.com/Azure/go-amqp v0.13.11/go.mod h1:D5ZrjQqB1dyp1A+G73xeL/kNn7D5qHJIIsNNps7YNmk= github.com/Azure/go-amqp v0.13.12/go.mod h1:D5ZrjQqB1dyp1A+G73xeL/kNn7D5qHJIIsNNps7YNmk= github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.11.3/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw= github.com/Azure/go-autorest/autorest v0.11.17/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw= github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= github.com/Azure/go-autorest/autorest v0.11.20 h1:s8H1PbCZSqg/DH7JMlOz6YMig6htWLNPsjDdlLqCx3M= github.com/Azure/go-autorest/autorest v0.11.20/go.mod h1:o3tqFY+QR40VOlk+pV4d77mORO64jOXSgEnPQgLK6JY= github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg= github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= github.com/Azure/go-autorest/autorest/adal v0.9.11/go.mod h1:nBKAnTomx8gDtl+3ZCJv2v0KACFHWTB2drffI1B68Pk= github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= github.com/Azure/go-autorest/autorest/adal v0.9.14/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= github.com/Azure/go-autorest/autorest/adal v0.9.15 h1:X+p2GF0GWyOiSmqohIaEeuNFNDY4I4EOlVuUQvFdWMk= github.com/Azure/go-autorest/autorest/adal v0.9.15/go.mod h1:tGMin8I49Yij6AQ+rvV+Xa/zwxYQB5hmsd6DkfAx2+A= github.com/Azure/go-autorest/autorest/azure/auth v0.5.8 h1:TzPg6B6fTZ0G1zBf3T54aI7p3cAT6u//TOXGPmFMOXg= github.com/Azure/go-autorest/autorest/azure/auth v0.5.8/go.mod h1:kxyKZTSfKh8OVFWPAgOgQ/frrJgeYQJPyR5fLFmXko4= github.com/Azure/go-autorest/autorest/azure/cli v0.4.2/go.mod h1:7qkJkT+j6b+hIpzMOwPChJhTqS8VbsqqgULzMNRugoM= github.com/Azure/go-autorest/autorest/azure/cli v0.4.3 h1:DOhB+nXkF7LN0JfBGB5YtCF6QLK8mLe4psaHF7ZQEKM= github.com/Azure/go-autorest/autorest/azure/cli v0.4.3/go.mod h1:yAQ2b6eP/CmLPnmLvxtT1ALIY3OR1oFcCqVBi8vHiTc= github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= github.com/Azure/go-autorest/autorest/mocks v0.4.1 h1:K0laFcLE6VLTOwNgSxaGbUcLPuGXlNkbVvq4cW4nIHk= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE= github.com/Azure/go-autorest/autorest/validation v0.3.1/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E= github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/locker v0.0.0-20171006230638-a6e239ea1c69 h1:+tu3HOoMXB7RXEINRVIpxJCT+KdYiI7LAEAUrOw3dIU= github.com/BurntSushi/locker v0.0.0-20171006230638-a6e239ea1c69/go.mod h1:L1AbZdiDllfyYH5l5OkAaZtk7VkWe89bPJFmnDBNHxg= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/GoogleCloudPlatform/cloudsql-proxy v1.24.0/go.mod h1:3tx938GhY4FC+E1KT/jNjDw7Z5qxAEtIiERJ2sXjnII= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/alecthomas/assert/v2 v2.2.1 h1:XivOgYcduV98QCahG8T5XTezV5bylXe+lBxLG2K2ink= github.com/alecthomas/chroma/v2 v2.5.0 h1:CQCdj1BiBV17sD4Bd32b/Bzuiq/EqoNTrnIhyQAZ+Rk= github.com/alecthomas/chroma/v2 v2.5.0/go.mod h1:yrkMI9807G1ROx13fhe1v6PN2DDeaR73L3d+1nmYQtw= github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/aws/aws-sdk-go v1.15.27/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/aws/aws-sdk-go v1.40.34/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= github.com/aws/aws-sdk-go v1.43.5 h1:N7arnx54E4QyW69c45UW5o8j2DCSjzpoxzJW3yU6OSo= github.com/aws/aws-sdk-go v1.43.5/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/aws/aws-sdk-go-v2 v1.9.0 h1:+S+dSqQCN3MSU5vJRu1HqHrq00cJn6heIMU7X9hcsoo= github.com/aws/aws-sdk-go-v2 v1.9.0/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4= github.com/aws/aws-sdk-go-v2/config v1.7.0 h1:J2cZ7qe+3IpqBEXnHUrFrOjoB9BlsXg7j53vxcl5IVg= github.com/aws/aws-sdk-go-v2/config v1.7.0/go.mod h1:w9+nMZ7soXCe5nT46Ri354SNhXDQ6v+V5wqDjnZE+GY= github.com/aws/aws-sdk-go-v2/credentials v1.4.0 h1:kmvesfjY861FzlCU9mvAfe01D9aeXcG2ZuC+k9F2YLM= github.com/aws/aws-sdk-go-v2/credentials v1.4.0/go.mod h1:dgGR+Qq7Wjcd4AOAW5Rf5Tnv3+x7ed6kETXyS9WCuAY= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.5.0 h1:OxTAgH8Y4BXHD6PGCJ8DHx2kaZPCQfSTqmDsdRZFezE= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.5.0/go.mod h1:CpNzHK9VEFUCknu50kkB8z58AH2B5DvPP7ea1LHve/Y= github.com/aws/aws-sdk-go-v2/internal/ini v1.2.2 h1:d95cddM3yTm4qffj3P6EnP+TzX1SSkWaQypXSgT/hpA= github.com/aws/aws-sdk-go-v2/internal/ini v1.2.2/go.mod h1:BQV0agm+JEhqR+2RT5e1XTFIDcAAV0eW6z2trp+iduw= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.3.0 h1:VNJ5NLBteVXEwE2F1zEXVmyIH58mZ6kIQGJoC7C+vkg= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.3.0/go.mod h1:R1KK+vY8AfalhG1AOu5e35pOD2SdoPKQCFLTvnxiohk= github.com/aws/aws-sdk-go-v2/service/kms v1.5.0/go.mod h1:w7JuP9Oq1IKMFQPkNe3V6s9rOssXzOVEMNEqK1L1bao= github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.6.0/go.mod h1:B+7C5UKdVq1ylkI/A6O8wcurFtaux0R1njePNPtKwoA= github.com/aws/aws-sdk-go-v2/service/ssm v1.10.0/go.mod h1:4dXS5YNqI3SNbetQ7X7vfsMlX6ZnboJA2dulBwJx7+g= github.com/aws/aws-sdk-go-v2/service/sso v1.4.0 h1:sHXMIKYS6YiLPzmKSvDpPmOpJDHxmAUgbiF49YNVztg= github.com/aws/aws-sdk-go-v2/service/sso v1.4.0/go.mod h1:+1fpWnL96DL23aXPpMGbsmKe8jLTEfbjuQoA4WS1VaA= github.com/aws/aws-sdk-go-v2/service/sts v1.7.0 h1:1at4e5P+lvHNl2nUktdM2/v+rpICg/QSEr9TO/uW9vU= github.com/aws/aws-sdk-go-v2/service/sts v1.7.0/go.mod h1:0qcSMCyASQPN2sk/1KQLQ2Fh6yq8wm0HSDAimPhzCoM= github.com/aws/smithy-go v1.8.0 h1:AEwwwXQZtUwP5Mz506FeXXrKBe0jA8gVM+1gEcSRooc= github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/bep/clock v0.3.0 h1:vfOA6+wVb6pPQEiXow9f/too92vNTLe9MuwO13PfI0M= github.com/bep/clock v0.3.0/go.mod h1:6Gz2lapnJ9vxpvPxQ2u6FcXFRoj4kkiqQ6pm0ERZlwk= github.com/bep/debounce v1.2.0 h1:wXds8Kq8qRfwAOpAxHrJDbCXgC5aHSzgQb/0gKsHQqo= github.com/bep/debounce v1.2.0/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0= github.com/bep/gitmap v1.1.2 h1:zk04w1qc1COTZPPYWDQHvns3y1afOsdRfraFQ3qI840= github.com/bep/gitmap v1.1.2/go.mod h1:g9VRETxFUXNWzMiuxOwcudo6DfZkW9jOsOW0Ft4kYaY= github.com/bep/goat v0.5.0 h1:S8jLXHCVy/EHIoCY+btKkmcxcXFd34a0Q63/0D4TKeA= github.com/bep/goat v0.5.0/go.mod h1:Md9x7gRxiWKs85yHlVTvHQw9rg86Bm+Y4SuYE8CTH7c= github.com/bep/godartsass v0.16.0 h1:nTpenrZBQjVSjLkCw3AgnYmBB2czauTJa4BLLv448qg= github.com/bep/godartsass v0.16.0/go.mod h1:6LvK9RftsXMxGfsA0LDV12AGc4Jylnu6NgHL+Q5/pE8= github.com/bep/golibsass v1.1.0 h1:pjtXr00IJZZaOdfryNa9wARTB3Q0BmxC3/V1KNcgyTw= github.com/bep/golibsass v1.1.0/go.mod h1:DL87K8Un/+pWUS75ggYv41bliGiolxzDKWJAq3eJ1MA= github.com/bep/gowebp v0.2.0 h1:ZVfK8i9PpZqKHEmthQSt3qCnnHycbLzBPEsVtk2ch2Q= github.com/bep/gowebp v0.2.0/go.mod h1:ZhFodwdiFp8ehGJpF4LdPl6unxZm9lLFjxD3z2h2AgI= github.com/bep/lazycache v0.2.0 h1:HKrlZTrDxHIrNKqmnurH42ryxkngCMYLfBpyu40VcwY= github.com/bep/lazycache v0.2.0/go.mod h1:xUIsoRD824Vx0Q/n57+ZO7kmbEhMBOnTjM/iPixNGbg= github.com/bep/overlayfs v0.6.0 h1:sgLcq/qtIzbaQNl2TldGXOkHvqeZB025sPvHOQL+DYo= github.com/bep/overlayfs v0.6.0/go.mod h1:NFjSmn3kCqG7KX2Lmz8qT8VhPPCwZap3UNogXawoQHM= github.com/bep/tmc v0.5.1 h1:CsQnSC6MsomH64gw0cT5f+EwQDcvZz4AazKunFwTpuI= github.com/bep/tmc v0.5.1/go.mod h1:tGYHN8fS85aJPhDLgXETVKp+PR382OvFi2+q2GkGsq0= github.com/bep/workers v1.0.0 h1:U+H8YmEaBCEaFZBst7GcRVEoqeRC9dzH2dWOwGmOchg= github.com/bep/workers v1.0.0/go.mod h1:7kIESOB86HfR2379pwoMWNy8B50D7r99fRLUyPSNyCs= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/clbanning/mxj/v2 v2.5.7 h1:7q5lvUpaPF/WOkqgIDiwjBJaznaLCCBd78pi8ZyAnE0= github.com/clbanning/mxj/v2 v2.5.7/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s= github.com/cli/safeexec v1.0.0 h1:0VngyaIyqACHdcMNWfo6+KdUYnqEr2Sg+bSP1pdF+dI= github.com/cli/safeexec v1.0.0/go.mod h1:Z/D4tTN8Vs5gXYHDCbaM1S/anmEDnJb1iW0+EJ5zx3Q= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/devigned/tab v0.1.1/go.mod h1:XG9mPq0dFghrYvoBF3xdRrJzSTX1b7IQrvaL9mzjeJY= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U= github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= github.com/disintegration/gift v1.2.1 h1:Y005a1X4Z7Uc+0gLpSAsKhWi4qLtsdEcMIbbdvdZ6pc= github.com/disintegration/gift v1.2.1/go.mod h1:Jh2i7f7Q2BM7Ezno3PhfezbR1xpUg9dUg3/RlKGr4HI= github.com/djherbis/atime v1.1.0/go.mod h1:28OF6Y8s3NQWwacXc5eZTsEsiMzp7LF8MbXE+XJPdBE= github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo= github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanw/esbuild v0.17.0 h1:gGx9TCZDO9k9x1PJdizx6syIpUq29RwrtHWlgDIdQH8= github.com/evanw/esbuild v0.17.0/go.mod h1:iINY06rn799hi48UqEnaQvVfZWe6W9bET78LbvN8VWk= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/frankban/quicktest v1.4.1/go.mod h1:36zfPVQyHxymz4cH7wlDmVwDrJuljRB60qkgn7rorfQ= github.com/frankban/quicktest v1.7.2/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o= github.com/frankban/quicktest v1.13.0/go.mod h1:qLE0fzW0VuyUAJgPU19zByoIr0HtCHN/r/VLSOOIySU= github.com/frankban/quicktest v1.14.2/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/getkin/kin-openapi v0.110.0 h1:1GnJALxsltcSzCMqgtqKlLhYQeULv3/jesmV2sC5qE0= github.com/getkin/kin-openapi v0.110.0/go.mod h1:QtwUNt0PAAgIIBEvFWYfB7dfngxtAaqCX1zYHMZDeK8= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/gobuffalo/flect v0.3.0 h1:erfPWM+K1rFNIQeRPdeEXxo8yFr/PO17lhRnS8FUrtk= github.com/gobuffalo/flect v0.3.0/go.mod h1:5pf3aGnsvqvCj50AVni7mJJF8ICxGZ8HomberC3pXLE= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gohugoio/go-i18n/v2 v2.1.3-0.20210430103248-4c28c89f8013 h1:Nj29Qbkt0bZ/bJl8eccfxQp3NlU/0IW1v9eyYtQ53XQ= github.com/gohugoio/go-i18n/v2 v2.1.3-0.20210430103248-4c28c89f8013/go.mod h1:3Ltoo9Banwq0gOtcOwxuHG6omk+AwsQPADyw2vQYOJQ= github.com/gohugoio/hugo v0.111.3 h1:m98NJv/5ivJLkQ4u3vPYsrAfBTnDIefZPGhnw/7xW80= github.com/gohugoio/hugo v0.111.3/go.mod h1:1gb2es3022plbaNiZjhBTdpXN2cepIeqvBnL/NHnKLY= github.com/gohugoio/locales v0.14.0 h1:Q0gpsZwfv7ATHMbcTNepFd59H7GoykzWJIxi113XGDc= github.com/gohugoio/locales v0.14.0/go.mod h1:ip8cCAv/cnmVLzzXtiTpPwgJ4xhKZranqNqtoIu0b/4= github.com/gohugoio/localescompressed v1.0.1 h1:KTYMi8fCWYLswFyJAeOtuk/EkXR/KPTHHNN9OS+RTxo= github.com/gohugoio/localescompressed v1.0.1/go.mod h1:jBF6q8D7a0vaEmcWPNcAjUZLJaIVNiwvM3WlmTvooB0= github.com/gohugoio/testmodBuilder/mods v0.0.0-20190520184928-c56af20f2e95 h1:sgew0XCnZwnzpWxTt3V8LLiCO7OQi3C6dycaE67wfkU= github.com/golang-jwt/jwt/v4 v4.0.0 h1:RAqyYixv1p7uEnocuy8P1nru5wprCh/MH2BIlW5z5/o= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-replayers/grpcreplay v1.1.0 h1:S5+I3zYyZ+GQz68OfbURDdt/+cSMqCK1wrvNx7WBzTE= github.com/google/go-replayers/grpcreplay v1.1.0/go.mod h1:qzAvJ8/wi57zq7gWqaE6AwLM6miiXUQwP1S+I9icmhk= github.com/google/go-replayers/httpreplay v1.0.0 h1:8SmT8fUYM4nueF+UnXIX8LJxNTb1vpPuknXz+yTWzL4= github.com/google/go-replayers/httpreplay v1.0.0/go.mod h1:LJhKoTwS5Wy5Ld/peq8dFFG5OfJyHEz7ft+DsTUv25M= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian v2.1.1-0.20190517191504-25dcb96d9e51+incompatible h1:xmapqc1AyLoB+ddYT6r04bD9lIjlOqGaREovi0SzFaE= github.com/google/martian v2.1.1-0.20190517191504-25dcb96d9e51+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw= github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210506205249-923b5ab0fc1a/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210715191844-86eeefc3e471/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/wire v0.5.0 h1:I7ELFeVBr3yfPIcc8+MWvrjk+3VjbcSzoXm3JVa+jD8= github.com/google/wire v0.5.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= github.com/googleapis/gax-go/v2 v2.3.0 h1:nRJtk3y8Fm770D42QV6T90ZnvFZyk7agSo3Q+Z9p3WI= github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= github.com/googleapis/go-type-adapters v1.0.0 h1:9XdMn+d/G57qq1s8dNc5IesGCXHf6V2HZ2JwRxfA2tA= github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hairyhenderson/go-codeowners v0.2.3-0.20201026200250-cdc7c0759690 h1:XWjCrg/HJRLZCbvsUxS5R/9JhwiiwNctEsRvZ1Vjz5k= github.com/hairyhenderson/go-codeowners v0.2.3-0.20201026200250-cdc7c0759690/go.mod h1:8Qu9UmnhCRunfRv365Z3w+mT/WfLGKJiK+vugY9qNCU= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru/v2 v2.0.1 h1:5pv5N1lT1fjLg2VQ5KWc7kmucp2x/kvFOnxuVTqZ6x4= github.com/hashicorp/golang-lru/v2 v2.0.1/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/invopop/yaml v0.1.0 h1:YW3WGUoJEXYfzWBjn00zIlrw7brGVD0fUKRYDPAPhrc= github.com/invopop/yaml v0.1.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q= github.com/jdkato/prose v1.2.1 h1:Fp3UnJmLVISmlc57BgKUzdjr0lOtjqTZicL3PaYy6cU= github.com/jdkato/prose v1.2.1/go.mod h1:AiRHgVagnEx2JbQRQowVBKjG0bcs/vtkGCH1dYAL1rA= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kyokomi/emoji/v2 v2.2.11 h1:Pf/ZWVTbnAVkHOLJLWjPxM/FmgyPe+d85cv/OLP5Yus= github.com/kyokomi/emoji/v2 v2.2.11/go.mod h1:JUcn42DTdsXJo1SWanHh4HKDEyPaR5CqkmoirZZP9qE= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo= github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/marekm4/color-extractor v1.2.0 h1:DCU/FXg3PlAwig7W5PRZshiX5x38k0aNPTxYZ6/fZb0= github.com/marekm4/color-extractor v1.2.0/go.mod h1:90VjmiHI6M8ez9eYUaXLdcKnS+BAOp7w+NpwBdkJmpA= github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2/go.mod h1:0KeJpeMD6o+O4hW7qJOT7vyQPKrWmj26uf5wMc/IiIs= github.com/mattn/go-ieproxy v0.0.1 h1:qiyop7gCflfhwCzGyeT0gro3sF9AIg9HU98JORTkqfI= github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/hashstructure v1.1.0 h1:P6P1hdjqAAknpY/M1CGipelZgp+4y9ja9kmUZPXP+H0= github.com/mitchellh/hashstructure v1.1.0/go.mod h1:xUDAozZz0Wmdiufv0uyhnHkUTN6/6d8ulp4AwfLKrmA= github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/montanaflynn/stats v0.6.3/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/muesli/smartcrop v0.3.0 h1:JTlSkmxWg/oQ1TcLDoypuirdE8Y/jzNirQeLkxpA6Oc= github.com/muesli/smartcrop v0.3.0/go.mod h1:i2fCI/UorTfgEpPPLWiFBv4pye+YAG78RwcQLUkocpI= github.com/neurosnap/sentences v1.0.6/go.mod h1:pg1IapvYpWCJJm/Etxeh0+gtMf1rI1STY9S7eUCPbDc= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= github.com/niklasfasching/go-org v1.6.6 h1:U6+mJ80p3weR4oP+Z+Pb2EVkSbt1MUwweBbUcF1hVqQ= github.com/niklasfasching/go-org v1.6.6/go.mod h1:o3pMQpO9n6RNBXz2Oc2DiRkaVwjns0JElyKiG7yXwA4= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd h1:CmH9+J6ZSsIjUK3dcGsnCnO41eRBOnY12zwkn5qVwgc= github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= github.com/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo= github.com/sanity-io/litter v1.5.5/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U= github.com/shogo82148/go-shuffle v0.0.0-20180218125048-27e6095f230d/go.mod h1:2htx6lmL0NGLHlO8ZCf+lQBGBHIbEujyywxJArf+2Yc= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= github.com/spf13/fsync v0.9.0 h1:f9CEt3DOB2mnHxZaftmEOFWjABEvKM/xpf3cUwJrGOY= github.com/spf13/fsync v0.9.0/go.mod h1:fNtJEfG3HiltN3y4cPOz6MLjos9+2pIEqLIgszqhp/0= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/tdewolff/minify/v2 v2.12.4 h1:kejsHQMM17n6/gwdw53qsi6lg0TGddZADVyQOz1KMdE= github.com/tdewolff/minify/v2 v2.12.4/go.mod h1:h+SRvSIX3kwgwTFOpSckvSxgax3uy8kZTSF1Ojrr3bk= github.com/tdewolff/parse/v2 v2.6.4/go.mod h1:woz0cgbLwFdtbjJu8PIKxhW05KplTFQkOdX78o+Jgrs= github.com/tdewolff/parse/v2 v2.6.5 h1:lYvWBk55GkqKl0JJenGpmrgu/cPHQQ6/Mm1hBGswoGQ= github.com/tdewolff/parse/v2 v2.6.5/go.mod h1:woz0cgbLwFdtbjJu8PIKxhW05KplTFQkOdX78o+Jgrs= github.com/tdewolff/test v1.0.7 h1:8Vs0142DmPFW/bQeHRP3MV19m1gvndjUb1sn8yy74LM= github.com/tdewolff/test v1.0.7/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.5.4 h1:2uY/xC0roWy8IBEGLgB1ywIoEJFGmRrX21YQcvGZzjU= github.com/yuin/goldmark v1.5.4/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.opencensus.io v0.15.0/go.mod h1:UffZAU+4sDEINUGP/B7UfBBkq4fqLu9zXAX7ke6CHW0= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.22.6/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= gocloud.dev v0.24.0 h1:cNtHD07zQQiv02OiwwDyVMuHmR7iQt2RLkzoAgz7wBs= gocloud.dev v0.24.0/go.mod h1:uA+als++iBX5ShuG4upQo/3Zoz49iIPlYUWHV5mM8w8= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20221031165847-c99f073a8326 h1:QfTh0HpN6hlw6D3vu8DAwC8pBIwikq0AI1evdm+FksE= golang.org/x/exp v0.0.0-20221031165847-c99f073a8326/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.5.0 h1:5JMiNunQeQw++mMOz48/ISeNu3Iweh/JaZU8ZLqHRrI= golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210126194326-f9ce19ea3013/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210427180440-81ed05c6b58c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.2.0 h1:GtQkldQ9m7yvzCL1V+LrYow3Khe0eJH0w7RbX/VbaIU= golang.org/x/oauth2 v0.2.0/go.mod h1:Cwn6afJ8jrQwYMxQDTpISoXmXW9I6qF6vDeuuoX3Ibs= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200828194041-157a740278f4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210223095934-7937bea0104d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210503080704-8803ae5d1324/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190422233926-fe54fb35175b/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.4.0 h1:7mTAgkunk3fr4GAloyyCasadO6h9zSsQZbwvcaIciV4= golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= google.golang.org/api v0.37.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= google.golang.org/api v0.46.0/go.mod h1:ceL4oozhkAiTID8XMmJBsIxID/9wMXJVVFXPg4ylg3I= google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= google.golang.org/api v0.52.0/go.mod h1:Him/adpjt0sxtkWViy0b6xyKW/SD71CwdJ7HqJo7SrU= google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= google.golang.org/api v0.76.0 h1:UkZl25bR1FHNqtK/EKs3vCdpZtUO6gea3YElTwc8pQg= google.golang.org/api v0.76.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210126160654-44e461bb6506/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210429181445-86c259c2b4ab/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= google.golang.org/genproto v0.0.0-20210517163617-5e0236093d7a/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= google.golang.org/genproto v0.0.0-20210721163202-f1cecdd8b78a/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= google.golang.org/genproto v0.0.0-20210722135532-667f2b7c528f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210825212027-de86158e7fda/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= google.golang.org/genproto v0.0.0-20220405205423-9d709892a2bf/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/genproto v0.0.0-20220426171045-31bebdecfb46 h1:G1IeWbjrqEq9ChWxEuRPJu6laA67+XgTFHVSAvepr38= google.golang.org/genproto v0.0.0-20220426171045-31bebdecfb46/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= google.golang.org/grpc v1.46.0 h1:oCjezcn6g6A75TGoKYBPgKmVBLexhYLM6MebdrPApP8= google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/neurosnap/sentences.v1 v1.0.6/go.mod h1:YlK+SN+fLQZj+kY3r8DkGDhDr91+S3JmTb5LSxFRQo0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= kind-0.27.0/site/layouts/000077500000000000000000000000001475376161000152035ustar00rootroot00000000000000kind-0.27.0/site/layouts/docs/000077500000000000000000000000001475376161000161335ustar00rootroot00000000000000kind-0.27.0/site/layouts/docs/index.html000066400000000000000000000006061475376161000201320ustar00rootroot00000000000000 {{ partial "header.html" . }} {{ partial "sidebar.html" . }}
{{ partial "navbar.html" . }}
{{ partial "fancymarkdown.html" . }}
{{ partial "footer.html" . }}
kind-0.27.0/site/layouts/docs/section.html000066400000000000000000000006061475376161000204670ustar00rootroot00000000000000 {{ partial "header.html" . }} {{ partial "sidebar.html" . }}
{{ partial "navbar.html" . }}
{{ partial "fancymarkdown.html" . }}
{{ partial "footer.html" . }}
kind-0.27.0/site/layouts/docs/single.html000066400000000000000000000006061475376161000203040ustar00rootroot00000000000000 {{ partial "header.html" . }} {{ partial "sidebar.html" . }}
{{ partial "navbar.html" . }}
{{ partial "fancymarkdown.html" . }}
{{ partial "footer.html" . }}
kind-0.27.0/site/layouts/index.html000066400000000000000000000006061475376161000172020ustar00rootroot00000000000000 {{ partial "header.html" . }} {{ partial "sidebar.html" . }}
{{ partial "navbar.html" . }}
{{ partial "fancymarkdown.html" . }}
{{ partial "footer.html" . }}
kind-0.27.0/site/layouts/index.redirects000066400000000000000000000004401475376161000202160ustar00rootroot00000000000000{{- $apiVersions := site.Data.apiVersions -}} {{- range $apiVersions }} /{{ . }} https://godoc.org/sigs.k8s.io/kind/pkg/apis/config/{{ . }} {{- end }} /dl/v* https://github.com/kubernetes-sigs/kind/releases/download/v:splat /dl/* https://storage.googleapis.com/k8s-staging-kind/:splatkind-0.27.0/site/layouts/partials/000077500000000000000000000000001475376161000170225ustar00rootroot00000000000000kind-0.27.0/site/layouts/partials/fancymarkdown.html000066400000000000000000000010341475376161000225510ustar00rootroot00000000000000{{ if not (eq .Site.Title .Params.title) }}

{{ .Params.title }}

{{ end }} {{ if .Params.description }}
{{ .Params.description | markdownify }}
{{end}} {{ if .Params.toc }}

Contents 🔗︎

{{ end }} {{ .Content | replaceRE "()" `${1} 🔗︎ ${3}` | safeHTML }} kind-0.27.0/site/layouts/partials/footer.html000066400000000000000000000013041475376161000212040ustar00rootroot00000000000000kind-0.27.0/site/layouts/partials/header.html000066400000000000000000000015041475376161000211400ustar00rootroot00000000000000 {{ .Site.Title }}{{ if and .Params.title (not (eq .Site.Title .Params.Title)) }} – {{ .Params.title }}{{ end }} {{ partialCached "inlinecss.html" . }} {{ partialCached "inlinescript.html" . }} kind-0.27.0/site/layouts/partials/inlinecss.html000066400000000000000000000001561475376161000217010ustar00rootroot00000000000000{{ with resources.Get "css/inline.css" | resources.Minify }} {{ end }}kind-0.27.0/site/layouts/partials/inlinescript.html000066400000000000000000000001551475376161000224140ustar00rootroot00000000000000{{ with resources.Get "js/inline.js" | resources.Minify }} {{ end }}kind-0.27.0/site/layouts/partials/navbar.html000066400000000000000000000005341475376161000211630ustar00rootroot00000000000000 kind-0.27.0/site/layouts/partials/sidebar.html000066400000000000000000000017711475376161000213270ustar00rootroot00000000000000kind-0.27.0/site/layouts/robots.txt000066400000000000000000000001531475376161000172530ustar00rootroot00000000000000User-agent: * {{ if ne ( getenv "HUGO_ENV" ) "production" -}} Disallow: / {{- else -}} Allow: / {{- end -}}kind-0.27.0/site/layouts/shortcodes/000077500000000000000000000000001475376161000173605ustar00rootroot00000000000000kind-0.27.0/site/layouts/shortcodes/absURL.html000066400000000000000000000001101475376161000213660ustar00rootroot00000000000000{{/* the one input should be a relative url */}} {{- .Get 0 | absURL }} kind-0.27.0/site/layouts/shortcodes/codeFromFile.html000066400000000000000000000015711475376161000226100ustar00rootroot00000000000000{{ $file := .Get "file" }} {{ $lang := "" }} {{ $suffix := findRE "(\\.[^.]+)$" $file 1 }} {{ with $suffix }} {{ $lang = (index . 0 | strings.TrimPrefix ".") }} {{ end }} {{ with .Get "lang" }}{{ $lang = . }}{{ end }} {{ $virtualPath := strings.TrimPrefix `static/` $file }}
{{ $virtualPath }}
{{- highlight (trim ($file | readFile) "\n") $lang "" | -}}
kind-0.27.0/site/layouts/shortcodes/codeFromInline.html000066400000000000000000000011451475376161000231440ustar00rootroot00000000000000{{ $code := trim .Inner "\n" }} {{ $lang := "" }} {{ with .Get "lang" }}{{ $lang = . }}{{ end }} {{ $hash := md5 $code }}
{{ highlight $code $lang "" }}
kind-0.27.0/site/layouts/shortcodes/minify.html000066400000000000000000000002511475376161000215370ustar00rootroot00000000000000{{ $file := .Get "file" }} {{- $contents := readFile $file -}} {{ $res := $contents | resources.FromString $file }} {{- safeHTML (($res | resources.Minify).Content) -}} kind-0.27.0/site/layouts/shortcodes/readFile.html000066400000000000000000000001221475376161000217540ustar00rootroot00000000000000{{/* the one input should be a file path */}} {{- .Get 0 | readFile | safeHTML -}}kind-0.27.0/site/layouts/shortcodes/securitygoose.html000066400000000000000000000006011475376161000231470ustar00rootroot00000000000000
security goose says

Security Goose Says:

{{ .Inner | markdownify }}
kind-0.27.0/site/layouts/shortcodes/stableVersion.html000066400000000000000000000000331475376161000230620ustar00rootroot00000000000000{{- site.Params.stable -}} kind-0.27.0/site/static/000077500000000000000000000000001475376161000147725ustar00rootroot00000000000000kind-0.27.0/site/static/android-chrome-192x192.png000066400000000000000000000265661475376161000213470ustar00rootroot00000000000000PNG  IHDRRlgAMA a cHRMz&u0`:pQ<bKGDtIME !i:,IDATxyWuz53%KdƖwlo   <  1ް-[,ٖlY־̾V}TwOhGiS]Uu{=sAP( BP( BP( BP( BP( BP( BP( BP( BP( BP( BP( BP( BP( BP( BP( BP( BP( BP( BC ޴9:穆,,ng8 o=X T~ @/i%= Nw)dEL91Ihvm}*_B~ :O( āF.e:$ȫ̘W+p[pl7(៎_΂#jӖ(d g$8mSzƈ5A F5 G:ƫ <.04GpÕF}3&^R ܚwcCE[Z3wA "rQH dꋷ bjLړ5ɑ#'NW3C:mqkq ~RЄ`:X@)敶,gF€B2+ftvoR%JQ!aBLS9C1g+R2TP 0gtig | =Np$3'L 5Qt`nX//MV6:JQa&)9ObtGS0HG; gq{>^ iONE0,,7z,,Wi8p)MMQM2qp^y13]JVgln\8_"SlKgIY8s/\n?XYYֲIƐNOe#a$vm=nVA׶?r_,\w|l`P&Ws!_4̓?z9@ܡYa=H_2lm~ 4vqG^~FtPf.ӉC4,չ4C\Z-bZ )Wd&Š07.UISz ug9i[#d9R참~yY"EcZh{3ԤQH`Œ$#K ܵ hZGSz:D3t=yOh@WwGI%V\I/.`1Z%edM<50Φ}%Έ%HR|1*60M6Z ):xqN)u_½=tRR=!&63T5!Ƿf\I8'aYDC&5$y5+mHhԹ`sO<5kyhlo ^ٕ&9 " hqb0{deR=s'c93gS Nt +i˽ecŧǑlO3=Rg؏mBDDwҢiSWc1$gy+:hEj*G3 ްWMq#m X<Ӄ LK2%^mu=\ ovQWq ̪-=4wnDCsto%uA:qgv*fL$FLv-'95;:)sm}l4=|b+6a-9baplX^$?:3Iq@@0wwtd/@ %5l!`oO_VO3|AFGۓV*/_;ꥮRU~.rK ~p?-6x`sgHKhٰՕ ݍ!ʠ W&/P@d4#Y۩zd] uF?8vB?jX6.زVCKO|}_5 !?;HeXG>c!dZrfg%yKX`/)voaˎ*&e >e3n $=v{WLn( -%R|(nN!1B1oTX8=>&RIs~B@meGJ67?㍽iCp> 1}HHP#ԸL.>1FJAfUݎ!l"zD!$vEC I"xǫ70# eϞ=X=j{ ŋBqs?h:*yweDt`fW?g}n)x z gE!56zbD-XU[ɦ)5Ri-1fZX7H `J:r=A=pNj/n)x] ,Z=RNF'GI8oSxh<5eǷ Q9s.*iM }>fL0ޤC:>+!xqGmMtmJ^撵~f r}čqnXBj g,*tkVv+8nq-@PiGA{MrL~@8dG%) jZi$ArxKק+fOHv9@dNdI)%}}}'Ja6!3u4\ڟ 4g lّb_EWCg ޔ)Ɂjw.[$f:LI’Ē g2b )] [ض [t˽YRRRS[ÕW^A**MhTVUM)khdk5Ӓ8R 9+Zs<4T2p[pP*QtƧWƇq}f8`9q;ԑn?<qktox@tN9$,Iږ#h}^lhR g18\'woYwwvx{4!NnA4jgԖߗRNϰN{=D$/?KBPY&:l3W`^A$$X<Ӄn-knmleH)?aWwHIy`wŵ2)Ht&M%Џ; e GN2˯#-ܸh ⦸C :75컱@)4!/426R̝&%4T|  ^A$}9asPi |0t/{1-S<`nκ>B~A8E'%Ifv. 'bIU]B5А @*i&i\xڰm xx!I1tsfN1FSTFs8rpJcE te2toَfx9}bI],Ng?4[8r0ucV5[lڞO/qM]6߾[6#YPoc`<=6;hk*a`Y&䝨Ig)$1$Yd$VܤXgԫ )ć ]^b#ı-39bÓJ) 484,|s7iӝ8ǩs=C[hBi3FsMr Wl!xt Uaw,|1Nkͫr23f kƢNyE4-;J`Vxag)%U3㟡z$dHڒ=&IPha@|>pӧ|7Cϭ9GK) 0d5l-"d;hiB!Aނg%`ٻg篜œ03XBfji`4%|}O]ZY9Ǫ^@ۗnw[%>.\q>3o$yHh|۶旷"4m3+ 0w>ʒjָM@ U'mײK^ģfԳլX{+N?{EDKP*BzzhmoW7&`Zw8af`/'clHvh.X xeW^MJSohɋ;".ᣬ9<"U7]'= ?60ye ɚ4_Ư~k>uGi#HowGNA0-XЋe;h;PVߙQcI~P;f!qûhbp mdﳈ[c1qMioƮ/P69Vgi:s-MO6{YW(zx*\H{9fͬ\#JSWa^ڙ_T1V5.^us32>Mے|(}qYT.Xg9|۽kϘܨPY'CѧM{ xP9'#$CD*h+Y"2tM'Z$! b;6TpY-=­-C|~j\'xspmtC_IyfV| wf3:Kg{ܕ~~įync~)%P3(el?Hl,Yy9SgL #gK))+q6]-Q|5m)%`ܧ,/1]CGJIC]-Wsm$Rc]yg>Amu5S !'?=;dT67IJ>v Gi؞d$6'圿%F~;OM8~LplVrՙA^ݕEym ֿP>rQ!G-W'w?ڭ<BpJ|%$,HڒUl:ORZ!yLB̜;χ\XwWH CCM|[%H2waY=>OffdtЮRx45r=4W|_<+>qiڮԐNnǝ)~)mfB@W/6 у3V 7W.--_9j}u{E;?axTKMJIMLtYaFC7P<00 Z;b67 ̙>YaUHO?K{ٜ~-`+['N{yf57ᅵPj9lEI[RqtM`C3m~{xwϾ̋5mhDMvT?j(Ph-l:@GX3aͣq:n`i+y艧R2wV#\::y䩍 .8LNYR]YA23֬rj>~ pK g6p{Π<J2KHd&xn:~@y1kQkڿ 3n8A:pev|{gS! bnYYYK2r-bYָ*B|"2>z|`Cd:sSʥ#AAnv;pLXu9|QmX.|JƅjDL?/A"O>͎]Y|)|{?.iϚ`CtD7k眹7n" p7}8cV+ʏ̲mv:懌a,]Q x+/^;MJQ>5,D_x}~BŠql&JaelX~޾#{NtsKf6mO/|cwz¡3jI${y"5PӸ6NRu`P"aOJ 1$%%I[\DRJxݔwva:]?čW_m۴w44-؉ܠVSK;cē l4-;k=-d|?}KMu%}x#sX|i xѬw1Mj3$3n|w[Za07l ;[xд3t=}ضMeyi1wO ';VVN QUx=zz'DeN^s# 5}uNFq=Yn0wR4x#;"Բm:s^h/}D#^fŝt"D*iC$TFJIe Y_6 gOJm[1`LTHqBBen7cShbqGri먪;=dõ[6ۺL뮼aZYJUf)1ɐCU gO$W'RJ啜,wp켎޴Îb^VPH3` Xxʓ\H)Yb5A:9^W&M6#|[^?]!4 S" XMye;";Ư8Ȍ;! q0Ngaw4w9I%me.MoWLے- `|M&lµ ymi[HF:6R:9Ő\|q#,,F8~1H?8|~@hnG4MBz Қ$iKAY^&`LƖz+sLGi1=).e3LLd6LaS8$v:NaeX&2qlDzQl98nlB]-ྌrI @rla"НY$МDnRBCh:Bt]G 4݃05ÃfhE<>t/E3 @=Nv11V /~(L2KsJ9:\zz iFxtqrE*°8`3DY2L#/Ѓ?4B 3ucqZF׆^k(njLW#t~[ 7'K9ʚ9PZm^K`d"z7E᷆8r &f.+=q>46{isȊzƝݵҒN J"VNf@78tlfr/;ƍmMiM~>c"h@fu@si ٤МƝ^=tDt6|K.IGH'J\EW;qq|m8> d~.i$.sԴ]vs鋒pxd?^R#8х0<. }/S \7](RǟdFZZEN佔뀆~iʵ9E P j6 Pt iRrW ''=c^)XKXH8 z&;(]ƪ7Ggc[IyZζ :Kϯm) -~")!i*4<{Rz~?Hϖ񱬇yrr« ^ /ΧKgEMBid[x;U0^ _j)^N~F6L6*\tFi8b" OaHa`\((wB2݌~ƨ[>tߊ鋼IÝa{";k9?דHQa 8b A5ЌjWE@-^cFr6W%{żB`t>B~sF.-Ёb({×m"Q`/ !,hF  ,pHK *UYl>[2.9!2 W@BQid mu !|o #e Fu`I_!(#@Q~%F5+וEB/_FzDez^rBQ">V[?{#B]I{F7?/ M߀wAD镫neB  J@=~dba3k> %T!QoZ}BQ!M[L2$ *s^4/ PR|g\  @owHtclJey\KeOƔ7jLո$j\QRr8z¿ZOzmlQ_I46 ۜn?FOO|}55^3s͑OQx}/wH/(R`@[MER-K)LL0F5N`F^M;QmFALvr#',5ۆnFZO{+Ԩf~3EC<皩A)/jGCd\\P1>kA+x{K'%Rρŗh4L">P>#d@Rm|p0^@v5I[t4I&h2 Fla_GA+u*b4 WYT 5}Wϧ6T o.v e8e41%i{CRJ[7CɊjfBEg2.I`ڛ;P"Q pܯJP5T5BVPP3XVeK&dp/zP<'j]sP !+w3 Au%,x+KU`) h Y#\ J0L0{ H .EQoХxoYQ)rbz  ( *UBaKd_Qr@S`s 4xqqJʯnB\|E\ ˫%[SI BqrJ},2w"w@C[=j_@}>0 S#Ŕ XUcMYL|2`I/GZ*zwրP2_1iBK&,"9?n W<$bhl+2d @Jֳ!l*_Qzn }bn(UۈW hVdb.g *0Bj&AˁO>3 PSG!zD[F!*J ]&D@i0b@]>Pй+zy܎*# PBr)h|X}J4 !DN𪵊›8T!D h X>(`*f`\W@!KA,~ ibr~Cs ; P!4)l/@3䙵U}  B,`*~.x`)v흕,B17+"x+AMno`v; B1#X-hv@+&:yۊ㍤PByI5,`;H/BPH/ X\Gn =g(G,R2noP>`(" \O%ɸnw,dPͶa 0VJQgg\uAS`{]_[CER !S!9 e]ga8vVڂv Qnccw`IԤ/r vlug%*(P,.J1RZs> (R8!Uq !jIwɉ!]πnXTbzb^W)RVjͧP{h>`qĤ'0х:V?{ 0?|0I!.">ŒYˀ)PC0;#YK,@ہ/>Y8;Sn:!(XZe/lx%7ϝbv??b"CyM3'K0 u>= .QY/@9sٙcG BAn^ϮmfޜZͮOJr&dSKXKܹ=ȍ֛, EʒU|y1OuwpŠs!A >/@-<^W_RawA,4뀷0@|myOVQ>(/mk۹fQ#~ogv9|橷s^HNR<\QJ*5hn >ˁܙ乽I8 xI2{#-[~y>A>hw4,ž.~p8OvНNr d E[Ĥ.`p&wHEdowxY^»?ęwWnkp9bb)*zK׻f^XGвM2D'v&xq_?l^MͼND9:R o|焰XUc6芻O:2z$o@ ~8 bCc)^%E230GUb>˥]S[GV.X_Me{XL)IY,k4Cy+Nv9OJrwna*\_ě^vǺp*@`]W j Z#&})`Zp\lbV%I.^ρ<o%@gq#0sMi@o ||2>jG >5oH7|dވs<wR\!];lnj$?ͱr!KLZ&)W3].&hr;ފ- 4y*Bf,Cy՚iK׻Kk޺oN3w˞bW<75| _]v[;vg8–sB.kkTBƐAm`hqv{A(V|Y(z5ܳb ˪"ٲך!WK vM3ٲE/qbmO /7-6z"{vm?;G^ MY\ȧz"^^5m^ޟW;9&(ǭB$Ӛ'v&8i菹᩷<;ɩQzx!&ck,wL5IlP(Ҷfϱ NIogK\U>$"lon}4~x=fS.C.2L0U_Ng8ڑa4*XxkaQ@R[[ž䢑n'ԙz l ,\,VbC^$">: ht xRM^17u{F_Γ؛lx}V+dt>*B̒ګ#W{.Nu9/\، U[-Io)RC?@J3IHrh0cS>V v5MґƬ_dKIjHaIi]%HTT ú^DnE_lK|,kq߰Cgl 48xͱjopXE_([gx-NJ{c uN͐;3=6]onH9x̴Md+4 DU9v@Y - L?!m r g)> Ejc"۷OiR0RR`0rtkX>6EjxrV3q>IDGY$4E#x'W| Zl9&.WGcLZp8>rLy@"zWTNH6cKC5JE-Ͼi > [,6.lΤ&joܰ)@NCglv؜qLh[W3^巼ɀ>$YoKIlEiD+stKS.Sh 4٘O-bcZ3>eR!SijĔΌODT2 DYeIO(N1\?jMK4D{W\鞋++˓m`skٴF1&dUU}<1#lΧvJk֚T^3; c3cL~*17^g RkƧ܆B*"蘆E`XMn$4Wnc(F;\{ڮlU ;dOZk554<:l:AE4h0wI/nJF k.zfh~K\k\kr=;$;鏹Հk U!E(`0wBHylD)ssh1Y"|sUt!lh">_ٴ5S bn1ԽD&KMߋQ4YC9Kt\fk"hx^VP WUN仫@kPR+0GAp8ϐew+;-k$_:_C5nGYjqTstfq54ܾ-ć Շ֗ȵ}JAS];Bܰ)țR8oLHnރjnC{od4~jsάٛ9ڑas`*v&V*J NT^A1O(;!S{}8{f q:SːHOc.[N%q1uUUީYQAN(n`(cU`ǓYgS&[j1"t9v-"ܽëv|χ}4=:?us}jVWMvA[l[穷;g0yE?p꼵+[-N8S͡3^miY}cO?Ußۆ^t~5&xŋ2XF oWg|0S<'Iƞ<R nis4 n 8t&t lmc `Q,XN,ˎFk3,O&yԘ8Ý'Hsg.!+dG;#y;1LyETPETCW컰!&yi_.\oXOT?~2Ȯ1ZC{| -[jچqwhSz*Gh+5zb>6,XlQ6rMch7.񧟪ayŃ/cӀ۶+gz^?Y8؜(-$,$ $v.(Fk3>/r`'dXi+G-|@7ZkZWΫUuX>@slƛ64 ~oih7,q߽yH';7ir#XPM+S^ڗb'.[L2)ck>oo@M`Mkf-*/eQ=>Dg)W pυ+|Ot޹ 8oǢpuռ'g1V!OOsa Vb)R- \m`(fM%):TJ "%{NI b#DS1 `lbE;1$R^dO-AwUó8|6`+wV7eßLkvMk ^?git1PX+ tx`^\f" h56L8x8Yj~t9?5n H5p Qņ=~ Qi0ؕXL->\;Yr1^T31~G 8!vƗky,@a'I {G"P]e;|~6DaHJy%owp FXk5Ymɴ&D)m~\'J3NOCȯ̉n\Fj 'liua*D]ԘqEk7 <}2ioNq.Z36&4XG4I8T' ^rŮf1q1]%L56X7˸9zhs4\;]Y@N&I&60 KCaV@gz:a@,yNg;vy_A~~/[O x=xzЯM)Xw>:Į#i~(N1 FsWCq=ct )v  rbhX!7ȱt:V N76p~Lc0R-Ӧ(e58/&Wu$g^+% u$< sܺH5u>?9a34jۥ]qu< }nKwDXx k[ܺ--N#2 \k 6P0w}8ފPxD> 4!#3m~HDg14Y~) Fq9t==[~EUuk֬/VGX鏹Ls x4M5[`0>X &cL;jh7M>rMh c&8a|l^QD#tƓyQNejy59[Mib#Mwb`ըu95vr,<> 0&cضo޽p-:ŭB$=sRl5-\EkCW',~ [aiɟ||>'}w O&ȥ-r߹#5o4=jWZܵ=7P,$ ]ٺO]ϺjveBeGyzhv5Dim'JR[Y.QclYvImt} Ae{4Bgu¼ uS*# e):}ڻV+ǭޞr$ ^4V]RDZsæqְik&֢p\xbgFlȯ {4 'Y&p5F >wKZ^(شy;&)]diU clWyKx/NjLF H3Әt=OOgQ#-C\9F9 7c\>mΣ$@ =chх>[]~HMr4Ɏ\Nw\/_~˂WWrƯIf4*EhBjܗ7fR?z[%?[TZca/?M[|j$D񻿎ǚ6H.qyŵ8i? G %\&k} Jay)M*|S~2Vs&S(qχ=#z16;#$i0cYqLjAZk0-]kM}}~I6 QDFg{ ;'հuI;INvy%pà '\>rmxAZ{?wK K-z)[覭ANu=aR6̪V˛9E;y8WOiP m % ;!zLuQU5U, @BcNj{w'BInYYS`i0c4c'Mؾ ~$RRJiFd6FkG 8ah7'\sxaor@Ϡ_?6`\ٛ##l.;Au6-ODL|'F?XE,HIOنo9o޾ '4~c{2.hoxX@X%~Z#!VT^`8RCo Wo"k l,*{%o)Lxϗ ~!8uuu\24 ߇i|r&[5 tFs7o}pJ?IwIs䜍>R0.a!PD̳5,EBy(Dziۓ59Q){mc]5c¦N5Gh,65s"]t#&.,Np(Za:ٓl&rX*V?'$at'Ue\U @f+&lfď`ּz M<g({Of?w"_||JaGl[9v84 g/Ie Iۚ:>-Hr[Aj NfHlBg(gڷ{@9J5Hފߕ!P]9QzV)k,q^$fi0Qz HkXTgZ7#kL<ۘR^ވ3wW*Z y0_|wOf| lȐHkB h} 87첨`Mm5 b2`@!b"Fc46l LXq}8Wl6$֚D"A:] J) ~7&.چh G3 m U8rۛd8O?VͦeOf7Sjx~O;O裀c6݃.KN:2h6 EQ':mY,YQPq%1M3_HT 8g`u9t0'2 b455XLVUGȴM)hk0GvM,gl,a?~v ?!&D /M=?<ɮ )MT6DSO0(jc$;=.gv( s,Z'<5;Oy9^Mjn<8:T,f'C:h-VǁglRo:|#Chso]b\UK ]`߀J & arE!LeN@)iMk ĺw8]@Nu9ed4.Zcfwz y bIM5V'9XlQ1 2ghYMKsZRp\W{/.C8 ц$v2Lby׋ (@x ?H00 l.IDk*9[(7ѬMv )~ kU!6,MLw=NXQpwb5Џpj~fCgtF8W2y2*FϹ>>ZeU"a) Tr \ Wr4q[5 G5ilk] .z$mtrذPXeæk͟bު*< ijjdɒ%?~,rWZE(RSU^ 5fHx ||G *7Xl\>-њ2Nѓ^}knOqo rf  g{l)=RXeQ?4U{Qy4.Qj?mۚf 2qI9{=M f}~矽<@u._u,Y7ieK ֪Z \ xW) *^>k |օƔU4W,mF*Ue~BK}_Wi\ͰD}'3rI:Әzaq>Gi[sjB>- u<ύ,,s\lkHؚ@e(G㎻+ |%`ˤ")KR]]͖-v-E6;3r| )孛4k,>tum+l\cIIUŸ.cxAbbxWgM-M[7|9~KdqNv2îi[|dO/ G:lbIɦ">C) T Koe0r4{97p3 ~v}*s[_4EYpۦr2f06l#xZhpϕ!iͳ{v0 Wnc ߗhuW@uؠze\ɺx+)r;+T6Y[[͢H>) T(l?z}Q"ommV`K#-( z]/r3G(yk}lg+BOj8}/ɇ!ۓ$Wܴ%& Ӹ؀/deN,m4i6̾Z`(t8yf"M +X+›5})dI}/{\י |mA)F< Dr ˫ T綝w]+ (VX|(mLjq8e>.g^)=M;Àq!ƈGσPi367oD>6ɳoH0vJ8$]"4.0Żx8׮}/A!@AxQ[ fZ p4Ebj-?5 ƽ#5^dqEcA4hPQ4՘,i4ioh5gg]Rf(t9t:4V\O$\Fkܧ54ޤ֤&l )|陃 o^ns=]].\NuЙ &0PK4I:R(/sIG*ksxN-= 0џn!KܟN$4_6f5Kh"Dd gtgG Eh1&$ҙm{ '4ن\n36.CIo}~o𕻢|E]I+旯'p&WT  kM7,nXTgΠ6j *o4ϩo7G2z lGy<5̳{Wy{\ f @Lv8pIl<^.^,|`L[Nv\⾝;ԹXslG%\6Di͑6jXMFkM)s 50w5=H2xz߰˫at7a0u = JhРڠd1L]10eNR}i]3YpTFUVTG95,f?99iKWx6m?Ox/FAVH`VF2M~H_VJ9Z{Й Gw%ͱ_Z] ?gaUe^ϜKs v~ۊ n,HuxտCg3pp]/H jر7?¡36|֦z[4ӋZ)ʾW Wk]1|cP|ŀ'»OAQ ,]Ħrsb)pt'EwYN=wz^mgõ$RҘ (sNMkjVl󦬤mlᦂd+0 8iO? oyWKٚ. OTJkXcӝ+D͋Rh|~7g.lXM^y7E׀`ivM VZ,n4tyH,J!Ov.i[B~V K͜@k&G|x(LQi?d6wН,!ޒ^U - ;haA.V Lzբ84|{<|W|TFM_F)P?֚ŋZ|4x9vG%y o~|#tF nG R;6P t9<'5!HG _vA4m.cY"50h]ݟW' (Rug 7""@4ސw嶳+p#W8͛/<ͱdF( 5v*xWAӈ){)*4wx/KMMWÒF6G&܍=>xF!~$W p YpOLX An:qZs{혾aku oÀf Fm۸ ڶg(zs캜:rw^EKxJ -(}c:*؝ݏ2偓\%o R q⢮%8ͪe-Ŏ5|+I<5Oj{=Wh6Qwڜ|fY}(Wo&x r4ۣi7qXjqɯ:wnjgLy0MAkLklCU|0>kl9 Z߾-µo;p(uo^Ė+n#+hw|o}f+xnhu mW:D?@ " cݖexp2~1gf|,Nu9?AM>rYdM Xjp 7n N`f̌.>SqZ?;+*lpM8`O>Rg|0C4G[T<=-yVU CaYǭk6_VS@ЀM-hK?k@CmLk[s'?Oo}a}Q[;/$O)p<0M\rjL~Ut;;Q܉msAvO7\!f ]{4Hꓠ5<;ɭۂc/7;-E[9i?R|aݒt_1o*\g_ǟ$ceojfncR?Uu#ђX!]qsx]Jrek\7hN?"a&@F1,P8ʱ+ @mJ)c>{kP_WKCAKɮi|9kbE[A$8q ^׀1ı33Ը.\>0>ʠ:lL9P!9sr5lX_|o{w~@wo_YOfml}DkjG&2W9)lK;(b~ZZg9ꍧnTճr&iEAP\!N:ϯSי6+;;>;W[xlfM<čd$K\ u&*/c'O+7 pU{*ŅҾ^4Cz|;x=4ql{waW`1)řs8˖ }\dHiϲH%>=݃.{g ,o̙ѐ~D !EoS^)ߐ.jbJ?n kì[#S3W$ :K3?TyEP߽7~>ay5-_QBS]TAgVW9p/%ן֚%66q%b؎ãOf> |(g{~ 'lC8o4ךӶ+͵͵n ֤Қ@\x>S (j#"WjT]^|!O^+?^_CW1ME2?_}rI8ZU.]v'ȒMb@I% j[ (D,ji憫XmTαt@C@廿f {G+Vqs~E8{\Ϯ$›R|1w.DOeXrMI؊P=|8eIYm^ޟ׻L%װO{u?rpt6h1o~p^J EJ~gcCMs [7C=,OD3i[hM$e[?|=⪵~sݔ0_*{)mTuι%j;'՛=Uf;r$߼ΝTUVt?7\~7Jv  g`jMmC#ͭK$7!H`\7'-S ucV c.{z_spBOfsgm~J_H_Xh {{gy{ޡ#o*M EKpz!i9_ϫ7WŞ0Zj.ـR>ZXr5.fW4>0 ҙ Z])F+80,lq;.qxжs'ol;E}nWòFGOw;|W<3jkMV,XNJEu&uUAg)|TZLkz]v;9x&ÉN.Q >헇x~oah=Yp 怡H$g;_*òh[t_O|a'u82oI nA۲B!6sM7 x{~~ @֚P0[6fe;Ckg;:K~y/lg'> GO,"HA}śhpaV//xo3/K/4T†RdM"%]i{lAƆ?37EP~n,^5nF蓿0GkM4ZTJ j ;d WO4YYָK^:zXɗfv˖pm\ >>ُ}huy{~og+85`Q>|m#жM`ERy45ֳ~*V,{~>tu+.lF{v-5/IF+8&l=!l}"y$tA Lr0wysaK(Z{ (W0 Ik"U5,_] ^<9D3HQDždKK~ phѲL|:U\):9}nxp,Ρ'acߎR rq]s<ضMn>{ڮ +"Zޤ?oh^x"=oUW |y6^| bI4/}) xxC"jjʒX'\Bჼg]=yzMihXP7ޤm+$`x\ERt? /2y'f^dR1.O[. [DJgG&^pI.:4w,lҟdz7ibN̫@ Ǜx'5!R]CckgNΫfQk+o{X_G,R.$J){F6]M{?`Nv]d\A>A֥ XRl[駡ڻnsN^ȤIηCK$*dmC+m* Wz:& CyJ; mi;F\^k뺸̥C{z0Μ0v60c]].axYs P\/ l!^?RG]))~+?1@\W줿<9{bein[U> LA^_Hy@80;bȇ֚p(ĒEhWsx"9~75Pp.l{ZkmZ|`h86r_P0@m7QK$b`p۶󮵞kC 5UUD#aL$L2144LfZ(xc/}h$W9G*I^?`0^Ff2iGW<;hbMGr[.&W\Y6]h H18З2.#]ZXkQSP|p]E-|S+.Ckoŷz'4Wn?q.bh8/ ?tzkPO~[ek{|?ښ*bUˉC|Tv}_}wuf>5-\wlߺmCA N304xͷؽ=u]xyZG!Ma?jPk/q?r>ycb8yu9r-; g a szf{gna{khtp]Ͳm> Wo[8p(Gضiu|Ssט+'5۷ncwdem7^N}9#\wvB}kkifr5_د%WBkeYp>~J|ķR`5yÏPuQ3^ UEG/RKܺ-oMm_< y*ZkjxU>_ θt\ v*%*Q1$p6h B|~"ǝ]eI[++sY5c~fQS#/6]ko`W,,Q)ņիb&ϰiݚiQ)E[K3_gqǟ,v3_'ijkmQ ;wRJϷ - l^M^Uu>\j|U527 ,%sf?gM.k/Iqx5 !*v5SHSJ)-i' e9꺴4_܄vwBc[&l*Eq h$2˗.~l\zgZki&E!>vIk|N;K"$J^6I:k(dJˑyյ$Nu], G'L2 c.%]|❛hnl ֞}y r[ٲa=5vҶVnj=6&PZPg?!'<_ƶyg{ڛ8e^M^ŊeK0 I7)^/}(n q᛿&u"6b;l_M?c'̤l:\CaL/w+տ <_|Z{!y6sWPhu5Mm>yC_= A C1oϽIh?dk ̐ ^?[ayf5> {'*|9lcTa~3 żealyxyɉjC# |1uW^A pWx!zԾih9t_8xX(E"Jࣿٗ^CkMuJxpA>sSOmyiy"ϋAkM۲X$72o~ [P6+|N-?=߂5nv3go|'}!;!yU\םpdzU ٙk:V/p߁!yi0&fN'&+gԜyXM \Ⱥtp*?_+Jmԛo%LͽtTUה?q^~G#W [8y,i EkMc")gb@ g>A>zM6?ůxK?xIOJP,FoĬ>˺ZkZpc'Ng)Ԛ{r|I\ 3|?@eGi] ˛-U,o&?xo ݽ}s׮KSlV+տ o@,p@W>6ZCe-,S[]ŵ; 8cgYl۴ göut6b ?7 kЅVOy1Y'AWO/N.qR<0J)n,n_zUJf"}(VyΝ?x'NͽIYiajJ6_}OWE?)X9.RjeN]yx]I\ex}:C2=En5Ph.>)M¨A/C=A:g||__͋77Q<C{g{ϜڥmG qX.΋95yV5Zc$12 "U ({dϻ8tĤ jZd.uRj'h=s85HC"NdQKF!W'/9ec;uX`b9 2Ï?ϼ0霓B)Z3_QfvWOz|5 vWqlX7drR73m d26x$IO@`VbR![6 gu^ڟ 3%c=7o;)͵&q l^ ΏܜqT{iKPp.(Xb(E0כ5wǥݠZk"4.ԉ:_~'Mp= vf8u\5Ģ#o:aR]UUD8u~wXj% ,VZ2TFNF,F3!S|*)ּy̩LYa3Kw_ُ\qJ4t W._ޝO3㯾?p˲察Kw̤h$̧>=t:+x"?vuC%CAcScZSSUŒE>SJ׿(;m! W_?}SgVx:Zk(_/(zG٘C)yJŤJ0՚bQJѺd9Pxl]|?e߁C8rV.Vٰf?ƶgfwwQ[S=wkVӾt {=0mvU;PB)oiǎ6s878é3xeDbN7hUYbMk(/Uث*Eë ͝](45-7*ymT:MoH\?ůX|U E{ [ob;xͷ*{ɔRᣬ^|̯[8rs\ץ{nhdVoyI)E O=Ͻ@ Z͵% O=ʗ@kM bӎ E≠31[бb9Txk ޳&eΝ*Oɮ^ymu1?ǎWtR)^~c'\5؆\yz cqښj>q}eB)m JZ5mWbƒ<]wҥ'r4w_Wq!ѺlJIwD)E<_>MY:o7XY8v{]+]$ ~y8yC)-n˷"`o!~6]q5M9`3rW_L@rE<1 pryx{\-M,i]4Rs M^)PWK}mH./߼ {l!N|ݸJjz՜91kL3^zzp=er'o"m{5l;տFQ_?=g(A~ӲȻ{kM ˗,c'O~ʱSltRttuLٸv5@XL@)E&c384rC࣏O2FuWf=ΣO>1gCbSJqckw8>d?9g=1gM|y3lm2 > Ǐÿ 7~Qs[7Ó^5>.NfU@~Ŧkh_~v{oҾZkjkضi5U;yw&m346Գm"c3/CmQ3˖,q?u )xǾeZ.n%ˤ8QRZkj KV)ڄa ]f*Kg%eb2'٩5UVHĆywzT*3Lonyg}!%ZrsBŠom,wݾ =ɂ i:5͗e70}uQrk[~؅S61_qB'銫EyȀ]e"Lb(U~zuQ2,Y 0,3B\i_+V%›|x#^g7N1R=xI)f-ikRNAFhin[JmCcS{ ! l~ "u9sq8ى(UЁpV^NxheђE/!4+YvCQ i2tċUY)U Gp-ZtLٴj"U[귯7Cw \Y+U M&mV.{viY 77d>L3oU]bJ P%YyhM]S3K g/K>(f{{2Ix;R1D>EC$Z Gh_~.BMkMC"m~IK^@@zSzi!R$p4R.!(8aоf=hAYw.bݖihjAQŅ%qG~WD]΀\wԛ8yK$8 Ժ2 gWa9!8?v92`'A]sWI_\R}yKԕ(ʲo eźfQ" !*[iYtV9s"Wӝwd86U?U⢔28E6CU>\ gcC(, VmBuMhTUiX~˝w iC6dӓpɺG;| 8 r/.^)|ؠS̀Z4.f굒HEӾvWG7V\hmkO M'á~/ƽpJOqIJ'>p1>wC@mI'& e5CawW'f 5iᴗ<7@0:@(u 7П5P@,Z|/>(Ь\qp.Y <}51ve\3fvI9e̘~^3^oypnk+2PWDKS3T CÚ r%@fN;L|xx$َbN 4j%$d3_`/H׿({`p٥n/k}ME􂁀 S7@ß˵L&ūO?ޝIqlض>XkP]on55wtJblQʛԋR(exAax_ʴ0L e௪GS,}r'-(Rt- u5Zv% 0_]h/LvV^E:ƶ3ؙ v&I㦓8n&Nf,3uΠױ/oٱl0uw1SmQ#~50PƅBma|}~L ~ﱖeZX0p*?wsV |CNKLTW=K' +pk n&k{$8Hओ^PIegmuq]йd1f Ako';ܩ#=~ ^$_Ms rqn.a1exWֆm?3, e.oZc5Ϗ `_[~ 7rV9 }u#23< U?gǑ_Z9jᾊ7OG(]K$4vT' 'r $I *pݱ 8!p8 Aۣo_ؖ;yGz(X\pd]c*vQ_/J1U]ߓ5ֆL•u ٰ|Qa5֘Ǩ_[Fv#xɍ3FMh~>nғm8?H/>;}1t'1wN\/mB7!}!׹F=䂂\;a}&\;g# z,F0ckش{U7S<1_UF}}B<hOpnsiz 5zF|mt`0jG_FO_GSvO^;=v s.x_QIn-/?,2ݏrI>Q_ upvODM$8F/;1c9QwBd'2_W9zl @U_mꫭ͗ێ}/fSr ͗rP0[_Ǜ'(r/o g Q"K+(Ou ZA!JL-6=bi*lPEJ[H!N> P!π~!QٟU(ep5s}HObpnmQG45ۨ `-Gǀ6Y /sI oF>y2}H*_QaʞuT75b. !$ û?m^SiEŪuT0G hǫ& u@> cRMδHl 1)tJ9rS@A?fwCH>1̉*(F>Q Y @kM~/{)g}~̈́X0r6<ܽʷv/fgFކ W,7/Ĝ5'17!w;gslj /׎O{y{1 ɼ J鉯\ h5 |B}"Ĝ .5ˁ{_rX v>BIP8ʽB,P>@CwDB¨a(mb6$(kʽB,p !f %^e˽?Bnd [^B^/ .Ҩ !O& 1K\6H"!*ɕx@iHpFTn+`$ɀrLś)!& XUIp.n,N!&/a! @IDAT' ^b!DMBLA;!d2S N"֖{j>hRRK $ȓ4Ef Q枞uݝ_<b4 f)w7W*zBL /*??l >$!*U+HF8* Y3Oh c.[hؐ(11/H'>Ԇ>WFT1߷UǼ;[v W(B>mgBDB}]"qd)\_jmW~reގU@'R2&82  8) hrj"Gy%ܤ_ɹN C??]G 1\'8ȟV+䕎~.HN!& b !F+t@!턘7ᙥw$H (&?e!Ƒ'A z{BIB'A@b*gBz4<@*t1 ixW@NNB\PϘ1 W@qA1dHwr*$bBHn!& ? TH2@ 0[BL O~`!W'BVa`RC!< c"VϘ11(iOˊ1&CBLB$=B\`oӢ By4&?0.S?3 1nQ߫Q7z 4'g௲Ow)dL ? ODL!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!Bo  #!u$ K%tEXtdate:create2019-02-13T00:20:34+01:00ؙ%tEXtdate:modify2019-02-13T00:20:34+01:00!WzTXtRaw profile type iptcx qV((OIR# .c #K D4d#T ˀHJ.tB5IENDB`kind-0.27.0/site/static/apple-touch-icon.png000066400000000000000000000241711475376161000206540ustar00rootroot00000000000000PNG  IHDRegAMA a cHRMz&u0`:pQ<bKGDtIME !i:'IDATxyt\uzKbH*IQLkD[ŎIFII&ds|,v+"ljɌD-YXIQMR$}m~U@@ ׯ[nU!C!X xq`#[q8G8p-8lq`#[q8G8p-8lq`#[q8G8p-8lq`#[q8G8p-8lq`#[q8G8p-8lq`#[q8eVG& 9py^* Ҏ==%{L~W1ͅao .Lʋ?!Yrr`UI@&y( L*6"O`~}]qqzxq[q$-5GTgpXy_8Ⰳ\~x#2 kI8g$(,A:GG m),K1Y6ZxL~Rշ(b!'"`bTKAO' 7+pZVJT7կSEgHaqX?3 r, h(0}P6UgF uưG ;bKo+BJUQMm-DŽ8I |("Q~ 19%ѯU]*gi>\?9IC)Zfw;m!r*們tj0`PL^Hʤ 4iN8:hEK(,Sx9JA@GfpnҜ D%:+IYW:RV \ h+)|r*bdv☰0$c9G)?`]LGv5Aq&Ҡts#ɢlz+4!U!5u)2X[QoN_UR[ y.ηU}e=ݗOEGի=)srn4&~&tĥeH U̲p츊ϑtfqJWi>"+pp0ƕ+GQ-wޥoc]wn{yѰ)KMI" + QS:4K)Rb' poP/I81a (O.om`o'=,2_Cҭ}[jh]ݗ X0 B_""w6[ j> 7iw:w*nҮevaq9YBXFzuRJ!p"@!]PJY;b}#plg=?1RTյgkt`x )Ch+^]Xi CFk@i *>Ԙ45Kh )Fu =̈́3X>mɈ$@[-;tɫoNF#!06~7rK6}icڡ'{,sa$RbAMHʁ2%&OƪJ,$ԽHJP{ ;^YG~f%iqtd$.GfIJTrOXSϥ_ y gXQF{"}n5pxdZe{ E 3"ݬ i8e D1hwuόűhFMn\ϟ*43ۛiQ9DD " C?{|T,eEM'JRl3umf9"2B&2BTAˋ`f78!05j7)$BXg~D7!M}!r 'SRUJ4ز"+h@Ɩo>lm᚛\lfx`Li&T8RTMU؈7";#gh2d}9ߺBߴTkRZkKԚR~:x.}1*bA[Iyg8b$Ah.wwv&@qELb~hxj"hyaHfnZ!t:v+ xf2`LLP"S>ʹ qSh Loh]SYe4(}ZcGuY~pBa؝a]E}L__=J[WO{ B8&Gz<=0 BҽM-]aa:Pf dOI\eIU9KvX"dM_5V*Vk?W+P0mR<56LjjC{@X MBJS.\h@2%STWzjrkQb˼nfbKwVv3\5c^=>W &5T*}aݽJ -#}ӽ# o<|wJ=eOX!-+g⸨ QbtFw筞-GuQ O֘!²3`2tbv'XAL Sh z\}N-5jgNR񹩨Kw#BƠq  A@;*sHdf/iΞDvZ26;9M@I'DhA/ힴEZb8*\2BBP]\"D $dGD*GDFh*WP$IUQԫ~'+h'hA˄x#H<QUUU}C5AYֈ![ ]8&y5}b/JݛݟϿQ J3p\mVS`|u2ޛ绣yJG/=yqNWf)"݂68eFY޶2%"e6g9ywǦPk9\jx;?WXJZXR`b9mޱεIS8 K?h@`,Iy1goMK[WO&Y#IpZw[;+n4*Xe@ MR,flrwXx? =?syuuU<>]^U5QN <֨~vl뵢 gq.oY@"M >})d90"ʕr>p׺/w]ĵ̖-@c1*S+CyN7(,8B&:`06vE}&ʤә/XFѱ8XAE ƎU. KGRqM2euZ]ʵsP|H K{>qDh,]<=$~Qɮ2`y1OGHyװ +..ELhecZ~nח11ީ0t_{aqA~9DIRw"sn57G̵ܺ3tֺ"+ G~A)o\LDI-rKC>vwSV9x0L2-jV'o'KÖjEUp$"R-~Ӝ1w?sQmc˦6ڣV  rU mRN $F:s@w^}q"Ĺ`"VLqBt~ Ό):c c $K9!bfl犢JB\)| bRۨ7QexWj*04vũM:2hfV0f5^s(^R߼luV\i;/@T:}vVЩvc1RN:5k[[{㖛k8{m 3_]GWo'w>{ڿ{z4mRhk\_{&78GڤIB>|{Tͩ{g_|u&+Qu]cIYQ[5dOB䝥v{o@ X,'O1`咺M ~ʼ*LF"~HJID4քZR"Fz?VT|l/O*{`d6IIHBe|N^vzi*ֽ8o?a3icjzD i?>JyHWs-TFq07@9,WEJ_zҕNDݕekZݒꪊrظD(.)- ,a]'{εt IdEF&rBsרa .tYlȅG{}GGg " W.[M~o(;gx(fMMc:@QO>w<}p_:{Ͼo7-Oo=_O */b/g;|>[|w:1O& n UJ}-7eϥ+3oWoZZQ]P3&ve[*Vf6a0g<ϚeQYՒ@I]2~'>xT@S-ͭM 5Pw~Dͷ.߿[,!6[?憺{{cr W?{#^ݾ>DU+x9XV~~3TJJl{0bȁTQkˊo6`JP%AR~T.&{=7;\=xF_qk{Ϗy3x=X>UoW8*IP`_;yϽwo$k7T.+&-:c"ojJ]` ]L)]QڦVODwbݽS"c~KC!aV?A/2ziZ3,y|ul/8̖1SF_Y<e =A ȒfMwvk> ًhݪ~Oϐ6>!%ͣBfk/c'_ڦs]f4UX׬*ƥztH$:sWZVml1T^8\|l ̬v-2&`t|eۏk?ϡ'Ϸ].t1 w5V8peƆ9'elϗkouf~4G~wVntkM `2lF1̻d y|Cq*ShW|>7_ 5餙B}o|v~D u]Lc# mg.]my@eYӲ7%\Y-j:lj8AʹE+v @<H$SBʧ{ԙՕv a"DcCC}CX?=?'{O}d/{h,6Mf Qu-;\nTIc~e& mZR~d,_, *%PX{t21uxv$ $*US;B%KeՕ; ]Ӛz2wD\R]UY^\C@ەYwh)e]OzˋiyrLI Ǘz6o6+#dW$rT{3 Dp82 i8}k"ٝIlۅ1vm@0<W, *+Bn}隕/nJSp{}uMKQCW>{<\rΦqB$˫jVߜ۠`6mİ5Vp{~-K _'lq<I)cEUn UeA8-O dk\m .:4+<'"K%ꚖVTwwZ_o(*[Chb0%3(SQ/)м |۫S&nZ2Ml\:^'r?$[ltpzl$W] zyrg:qTqɀ3n]1lڼr`t$,LSZLi$,),)d"2iIͣ+TBk}l0!2g\e؏7vd2 qU -y X1T'1)Ԝ3iWGD),B %-KZ iL 3-MCZ4ͱ-3cdHJHB_$%eGx2 *:~mR 8?T\Sn֋820ޑ eB e7(8m,we%ʚRsS2^W$M(bTf"<'EL{]@L"S&D2k9,s8u}W\H5e8 Ә/,aYrB̳(*$ X aTYn7 oòN*R2ۖMlf{0끷 \<,fn ]A ̩T, c$ Iͥ .|2px/q#9 G8p-8lq`#[q8G8p-8lq`#[q8G8p-8lq`#[q8G8p-8lq`#[q8G8p-S՜.4%tEXtdate:create2019-02-13T00:20:33+01:00Q%tEXtdate:modify2019-02-13T00:20:33+01:00 ")WzTXtRaw profile type iptcx qV((OIR# .c #K D4d#T ˀHJ.tB5IENDB`kind-0.27.0/site/static/browserconfig.xml000066400000000000000000000003661475376161000203720ustar00rootroot00000000000000 #2d89ef kind-0.27.0/site/static/copycode.svg000066400000000000000000000003711475376161000173210ustar00rootroot00000000000000 kind-0.27.0/site/static/examples/000077500000000000000000000000001475376161000166105ustar00rootroot00000000000000kind-0.27.0/site/static/examples/LICENSE000066400000000000000000000261351475376161000176240ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright {yyyy} {name of copyright owner} Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. kind-0.27.0/site/static/examples/config-with-mounts.yaml000066400000000000000000000022331475376161000232350ustar00rootroot00000000000000kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 nodes: - role: control-plane # add a mount from /path/to/my/files on the host to /files on the node extraMounts: - hostPath: /path/to/my/files containerPath: /files # # add an additional mount leveraging *all* of the config fields # # generally you only need the two fields above ... # - hostPath: /path/to/my/other-files/ containerPath: /other-files # optional: if set, the mount is read-only. # default false readOnly: true # optional: if set, the mount needs SELinux relabeling. # default false selinuxRelabel: false # optional: set propagation mode (None, HostToContainer or Bidirectional) # see https://kubernetes.io/docs/concepts/storage/volumes/#mount-propagation # default None # # WARNING: You very likely do not need this field. # # This field controls propagation of *additional* mounts created # *at runtime* underneath this mount. # # On MacOS with Docker Desktop, if the mount is from macOS and not the # docker desktop VM, you cannot use this field. You can use it for # mounts to the linux VM. propagation: None kind-0.27.0/site/static/examples/config-with-port-mapping.yaml000066400000000000000000000006301475376161000243240ustar00rootroot00000000000000kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 nodes: - role: control-plane # port forward 80 on the host to 80 on this node extraPortMappings: - containerPort: 80 hostPort: 80 # optional: set the bind address on the host # 0.0.0.0 is the current default listenAddress: "127.0.0.1" # optional: set the protocol to one of TCP, UDP, SCTP. # TCP is the default protocol: TCP kind-0.27.0/site/static/examples/ingress/000077500000000000000000000000001475376161000202625ustar00rootroot00000000000000kind-0.27.0/site/static/examples/ingress/deploy-ingress-nginx.yaml000066400000000000000000000403371475376161000252420ustar00rootroot00000000000000apiVersion: v1 kind: Namespace metadata: labels: app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/name: ingress-nginx name: ingress-nginx --- apiVersion: v1 automountServiceAccountToken: true kind: ServiceAccount metadata: labels: app.kubernetes.io/component: controller app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx app.kubernetes.io/version: 1.12.0-beta.0 name: ingress-nginx namespace: ingress-nginx --- apiVersion: v1 automountServiceAccountToken: true kind: ServiceAccount metadata: labels: app.kubernetes.io/component: admission-webhook app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx app.kubernetes.io/version: 1.12.0-beta.0 name: ingress-nginx-admission namespace: ingress-nginx --- apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: labels: app.kubernetes.io/component: controller app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx app.kubernetes.io/version: 1.12.0-beta.0 name: ingress-nginx namespace: ingress-nginx rules: - apiGroups: - "" resources: - namespaces verbs: - get - apiGroups: - "" resources: - configmaps - pods - secrets - endpoints verbs: - get - list - watch - apiGroups: - "" resources: - services verbs: - get - list - watch - apiGroups: - networking.k8s.io resources: - ingresses verbs: - get - list - watch - apiGroups: - networking.k8s.io resources: - ingresses/status verbs: - update - apiGroups: - networking.k8s.io resources: - ingressclasses verbs: - get - list - watch - apiGroups: - coordination.k8s.io resourceNames: - ingress-nginx-leader resources: - leases verbs: - get - update - apiGroups: - coordination.k8s.io resources: - leases verbs: - create - apiGroups: - "" resources: - events verbs: - create - patch - apiGroups: - discovery.k8s.io resources: - endpointslices verbs: - list - watch - get --- apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: labels: app.kubernetes.io/component: admission-webhook app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx app.kubernetes.io/version: 1.12.0-beta.0 name: ingress-nginx-admission namespace: ingress-nginx rules: - apiGroups: - "" resources: - secrets verbs: - get - create --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: labels: app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx app.kubernetes.io/version: 1.12.0-beta.0 name: ingress-nginx rules: - apiGroups: - "" resources: - configmaps - endpoints - nodes - pods - secrets - namespaces verbs: - list - watch - apiGroups: - coordination.k8s.io resources: - leases verbs: - list - watch - apiGroups: - "" resources: - nodes verbs: - get - apiGroups: - "" resources: - services verbs: - get - list - watch - apiGroups: - networking.k8s.io resources: - ingresses verbs: - get - list - watch - apiGroups: - "" resources: - events verbs: - create - patch - apiGroups: - networking.k8s.io resources: - ingresses/status verbs: - update - apiGroups: - networking.k8s.io resources: - ingressclasses verbs: - get - list - watch - apiGroups: - discovery.k8s.io resources: - endpointslices verbs: - list - watch - get --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: labels: app.kubernetes.io/component: admission-webhook app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx app.kubernetes.io/version: 1.12.0-beta.0 name: ingress-nginx-admission rules: - apiGroups: - admissionregistration.k8s.io resources: - validatingwebhookconfigurations verbs: - get - update --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: labels: app.kubernetes.io/component: controller app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx app.kubernetes.io/version: 1.12.0-beta.0 name: ingress-nginx namespace: ingress-nginx roleRef: apiGroup: rbac.authorization.k8s.io kind: Role name: ingress-nginx subjects: - kind: ServiceAccount name: ingress-nginx namespace: ingress-nginx --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: labels: app.kubernetes.io/component: admission-webhook app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx app.kubernetes.io/version: 1.12.0-beta.0 name: ingress-nginx-admission namespace: ingress-nginx roleRef: apiGroup: rbac.authorization.k8s.io kind: Role name: ingress-nginx-admission subjects: - kind: ServiceAccount name: ingress-nginx-admission namespace: ingress-nginx --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: labels: app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx app.kubernetes.io/version: 1.12.0-beta.0 name: ingress-nginx roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: ingress-nginx subjects: - kind: ServiceAccount name: ingress-nginx namespace: ingress-nginx --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: labels: app.kubernetes.io/component: admission-webhook app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx app.kubernetes.io/version: 1.12.0-beta.0 name: ingress-nginx-admission roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: ingress-nginx-admission subjects: - kind: ServiceAccount name: ingress-nginx-admission namespace: ingress-nginx --- apiVersion: v1 data: null kind: ConfigMap metadata: labels: app.kubernetes.io/component: controller app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx app.kubernetes.io/version: 1.12.0-beta.0 name: ingress-nginx-controller namespace: ingress-nginx --- apiVersion: v1 kind: Service metadata: labels: app.kubernetes.io/component: controller app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx app.kubernetes.io/version: 1.12.0-beta.0 name: ingress-nginx-controller namespace: ingress-nginx spec: ipFamilies: - IPv4 ipFamilyPolicy: SingleStack ports: - appProtocol: http name: http port: 80 protocol: TCP targetPort: http - appProtocol: https name: https port: 443 protocol: TCP targetPort: https selector: app.kubernetes.io/component: controller app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/name: ingress-nginx type: LoadBalancer --- apiVersion: v1 kind: Service metadata: labels: app.kubernetes.io/component: controller app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx app.kubernetes.io/version: 1.12.0-beta.0 name: ingress-nginx-controller-admission namespace: ingress-nginx spec: ports: - appProtocol: https name: https-webhook port: 443 targetPort: webhook selector: app.kubernetes.io/component: controller app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/name: ingress-nginx type: ClusterIP --- apiVersion: apps/v1 kind: Deployment metadata: labels: app.kubernetes.io/component: controller app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx app.kubernetes.io/version: 1.12.0-beta.0 name: ingress-nginx-controller namespace: ingress-nginx spec: minReadySeconds: 0 revisionHistoryLimit: 10 selector: matchLabels: app.kubernetes.io/component: controller app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/name: ingress-nginx strategy: rollingUpdate: maxUnavailable: 1 type: RollingUpdate template: metadata: labels: app.kubernetes.io/component: controller app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx app.kubernetes.io/version: 1.12.0-beta.0 spec: containers: - args: - /nginx-ingress-controller - --election-id=ingress-nginx-leader - --controller-class=k8s.io/ingress-nginx - --ingress-class=nginx - --configmap=$(POD_NAMESPACE)/ingress-nginx-controller - --validating-webhook=:8443 - --validating-webhook-certificate=/usr/local/certificates/cert - --validating-webhook-key=/usr/local/certificates/key - --watch-ingress-without-class=true - --publish-status-address=localhost env: - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name - name: POD_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace - name: LD_PRELOAD value: /usr/local/lib/libmimalloc.so image: registry.k8s.io/ingress-nginx/controller:v1.12.0-beta.0@sha256:9724476b928967173d501040631b23ba07f47073999e80e34b120e8db5f234d5 imagePullPolicy: IfNotPresent lifecycle: preStop: exec: command: - /wait-shutdown livenessProbe: failureThreshold: 5 httpGet: path: /healthz port: 10254 scheme: HTTP initialDelaySeconds: 10 periodSeconds: 10 successThreshold: 1 timeoutSeconds: 1 name: controller ports: - containerPort: 80 hostPort: 80 name: http protocol: TCP - containerPort: 443 hostPort: 443 name: https protocol: TCP - containerPort: 8443 name: webhook protocol: TCP readinessProbe: failureThreshold: 3 httpGet: path: /healthz port: 10254 scheme: HTTP initialDelaySeconds: 10 periodSeconds: 10 successThreshold: 1 timeoutSeconds: 1 resources: requests: cpu: 100m memory: 90Mi securityContext: allowPrivilegeEscalation: false capabilities: add: - NET_BIND_SERVICE drop: - ALL readOnlyRootFilesystem: false runAsGroup: 82 runAsNonRoot: true runAsUser: 101 seccompProfile: type: RuntimeDefault volumeMounts: - mountPath: /usr/local/certificates/ name: webhook-cert readOnly: true dnsPolicy: ClusterFirst nodeSelector: kubernetes.io/os: linux serviceAccountName: ingress-nginx terminationGracePeriodSeconds: 0 tolerations: - effect: NoSchedule key: node-role.kubernetes.io/master operator: Equal - effect: NoSchedule key: node-role.kubernetes.io/control-plane operator: Equal volumes: - name: webhook-cert secret: secretName: ingress-nginx-admission --- apiVersion: batch/v1 kind: Job metadata: labels: app.kubernetes.io/component: admission-webhook app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx app.kubernetes.io/version: 1.12.0-beta.0 name: ingress-nginx-admission-create namespace: ingress-nginx spec: template: metadata: labels: app.kubernetes.io/component: admission-webhook app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx app.kubernetes.io/version: 1.12.0-beta.0 name: ingress-nginx-admission-create spec: containers: - args: - create - --host=ingress-nginx-controller-admission,ingress-nginx-controller-admission.$(POD_NAMESPACE).svc - --namespace=$(POD_NAMESPACE) - --secret-name=ingress-nginx-admission env: - name: POD_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace image: registry.k8s.io/ingress-nginx/kube-webhook-certgen:v1.4.4@sha256:a9f03b34a3cbfbb26d103a14046ab2c5130a80c3d69d526ff8063d2b37b9fd3f imagePullPolicy: IfNotPresent name: create securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL readOnlyRootFilesystem: true runAsGroup: 65532 runAsNonRoot: true runAsUser: 65532 seccompProfile: type: RuntimeDefault nodeSelector: kubernetes.io/os: linux restartPolicy: OnFailure serviceAccountName: ingress-nginx-admission --- apiVersion: batch/v1 kind: Job metadata: labels: app.kubernetes.io/component: admission-webhook app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx app.kubernetes.io/version: 1.12.0-beta.0 name: ingress-nginx-admission-patch namespace: ingress-nginx spec: template: metadata: labels: app.kubernetes.io/component: admission-webhook app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx app.kubernetes.io/version: 1.12.0-beta.0 name: ingress-nginx-admission-patch spec: containers: - args: - patch - --webhook-name=ingress-nginx-admission - --namespace=$(POD_NAMESPACE) - --patch-mutating=false - --secret-name=ingress-nginx-admission - --patch-failure-policy=Fail env: - name: POD_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace image: registry.k8s.io/ingress-nginx/kube-webhook-certgen:v1.4.4@sha256:a9f03b34a3cbfbb26d103a14046ab2c5130a80c3d69d526ff8063d2b37b9fd3f imagePullPolicy: IfNotPresent name: patch securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL readOnlyRootFilesystem: true runAsGroup: 65532 runAsNonRoot: true runAsUser: 65532 seccompProfile: type: RuntimeDefault nodeSelector: kubernetes.io/os: linux restartPolicy: OnFailure serviceAccountName: ingress-nginx-admission --- apiVersion: networking.k8s.io/v1 kind: IngressClass metadata: labels: app.kubernetes.io/component: controller app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx app.kubernetes.io/version: 1.12.0-beta.0 name: nginx spec: controller: k8s.io/ingress-nginx --- apiVersion: admissionregistration.k8s.io/v1 kind: ValidatingWebhookConfiguration metadata: labels: app.kubernetes.io/component: admission-webhook app.kubernetes.io/instance: ingress-nginx app.kubernetes.io/name: ingress-nginx app.kubernetes.io/part-of: ingress-nginx app.kubernetes.io/version: 1.12.0-beta.0 name: ingress-nginx-admission webhooks: - admissionReviewVersions: - v1 clientConfig: service: name: ingress-nginx-controller-admission namespace: ingress-nginx path: /networking/v1/ingresses port: 443 failurePolicy: Fail matchPolicy: Equivalent name: validate.nginx.ingress.kubernetes.io rules: - apiGroups: - networking.k8s.io apiVersions: - v1 operations: - CREATE - UPDATE resources: - ingresses sideEffects: None kind-0.27.0/site/static/examples/ingress/usage.yaml000066400000000000000000000023451475376161000222560ustar00rootroot00000000000000kind: Pod apiVersion: v1 metadata: name: foo-app labels: app: foo spec: containers: - command: - /agnhost - serve-hostname - --http=true - --port=8080 image: registry.k8s.io/e2e-test-images/agnhost:2.39 name: foo-app --- kind: Service apiVersion: v1 metadata: name: foo-service spec: selector: app: foo ports: # Default port used by the image - port: 8080 --- kind: Pod apiVersion: v1 metadata: name: bar-app labels: app: bar spec: containers: - command: - /agnhost - serve-hostname - --http=true - --port=8080 image: registry.k8s.io/e2e-test-images/agnhost:2.39 name: bar-app --- kind: Service apiVersion: v1 metadata: name: bar-service spec: selector: app: bar ports: # Default port used by the image - port: 8080 --- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: example-ingress spec: rules: - http: paths: - pathType: Prefix path: /foo backend: service: name: foo-service port: number: 8080 - pathType: Prefix path: /bar backend: service: name: bar-service port: number: 8080 --- kind-0.27.0/site/static/examples/kind-gcr.sh000077500000000000000000000030561475376161000206510ustar00rootroot00000000000000#!/bin/sh set -o errexit # desired cluster name; default is "kind" KIND_CLUSTER_NAME="${KIND_CLUSTER_NAME:-kind}" # create a temp file for the docker config echo "Creating temporary docker client config directory ..." DOCKER_CONFIG=$(mktemp -d) export DOCKER_CONFIG trap 'echo "Removing ${DOCKER_CONFIG}/*" && rm -rf ${DOCKER_CONFIG:?}' EXIT echo "Creating a temporary config.json" # This is to force the omission of credsStore, which is automatically # created on supported system. With credsStore missing, "docker login" # will store the password in the config.json file. # https://docs.docker.com/engine/reference/commandline/login/#credentials-store cat <"${DOCKER_CONFIG}/config.json" { "auths": { "gcr.io": {} } } EOF # login to gcr in DOCKER_CONFIG using an access token # https://cloud.google.com/container-registry/docs/advanced-authentication#access_token echo "Logging in to GCR in temporary docker client config directory ..." gcloud auth print-access-token | \ docker login -u oauth2accesstoken --password-stdin https://gcr.io # setup credentials on each node echo "Moving credentials to kind cluster name='${KIND_CLUSTER_NAME}' nodes ..." for node in $(kind get nodes --name "${KIND_CLUSTER_NAME}"); do # the -oname format is kind/name (so node/name) we just want name node_name=${node#node/} # copy the config to where kubelet will look docker cp "${DOCKER_CONFIG}/config.json" "${node_name}:/var/lib/kubelet/config.json" # restart kubelet to pick up the config docker exec "${node_name}" systemctl restart kubelet.service done echo "Done!" kind-0.27.0/site/static/examples/kind-with-registry.sh000077500000000000000000000044561475376161000227240ustar00rootroot00000000000000#!/bin/sh set -o errexit # 1. Create registry container unless it already exists reg_name='kind-registry' reg_port='5001' if [ "$(docker inspect -f '{{.State.Running}}' "${reg_name}" 2>/dev/null || true)" != 'true' ]; then docker run \ -d --restart=always -p "127.0.0.1:${reg_port}:5000" --network bridge --name "${reg_name}" \ registry:2 fi # 2. Create kind cluster with containerd registry config dir enabled # TODO: kind will eventually enable this by default and this patch will # be unnecessary. # # See: # https://github.com/kubernetes-sigs/kind/issues/2875 # https://github.com/containerd/containerd/blob/main/docs/cri/config.md#registry-configuration # See: https://github.com/containerd/containerd/blob/main/docs/hosts.md cat <UAG2Rγ]8JBTʒέ,-j_Bʩw%L֩J̌ϵYhpgu|dt{NdkLkngGbr`zg|gzOepAYgIfIfGbpg[tRNS '7N_y.fdٯ[ZCFdfQ:&''/[짌bKGD tIME !i:IDATc` KHHHJI22UT՘Y11q IZ@mԴ̬D]=}6Cܼ¢cS3sҲʪ:K+kvƦֶv;{G'ή޾ . C=<}|YArrGDr] ($,BI*zh%tEXtdate:create2019-02-13T00:20:33+01:00Q%tEXtdate:modify2019-02-13T00:20:33+01:00 ")WzTXtRaw profile type iptcx qV((OIR# .c #K D4d#T ˀHJ.tB5IENDB`kind-0.27.0/site/static/favicon-32x32.png000066400000000000000000000032201475376161000177010ustar00rootroot00000000000000PNG  IHDR szzgAMA a cHRMz&u0`:pQ<bKGDtIME !i:1IDATXMh\|JiF>HM.ZRZh(I)$ChiUH04!@PR7PR qmّ%r$ьf4_owo#YcYGn=ιv|!GOr/06沝Z0k%lK0{S"F}#'YK-4~}T G:%ٕ`ҡV.^,ީ3!6dz۞ᅣ܌rݞ04OeȖΛEn\6U򄑴j%d\ՑnV-[EVyZMH\3IE}gl{FOI%E&S\,CCDPTi7.]D\tb7l5*I#=#xJvT`#5#щXxKwwt\E!QA}RBvpcJl,=`!G` Sx}qŁKLwN W+NGn8,NڕMZ \, /<!HpVC@#c9T ]!L3'ٰ2)O21fNPS]_ߍmX(ػwTki)˪nA&UFpLjPo+jM9*T&6Z{A>I7g3hXp'wbajee ô K0t|4]c޾f5O96ʃ֙o: QʐJa.V ~4)%"(m[UzD!6 `ٺDQ҈( gڙ+Ԟ1kiw*Ԋ*ߛZ6\R`U?cW@qcH׽ҒztuZsЏġ`VCZRAg_OldTmeVjbRe]Lg_OyrcqiZWN=VM;VM;VM;UL:TM;UMf^Mx[RAVM;VN=|g}Kv?xcn`o6Éjd[KUL:TK9vjTر֜uu}txӠ[R?TK9UL:UL:TK9VM;TK9TK9TK9SK9VLǤH˄!w,q8m'sy0szܫ޷Ѯ߹߸t ؟ZxEo7Pnp9s?V.Qޱ)u&r!t,qx/syܫ߷ӯ̉ $͡OSr`XGVNY[AUv~dyw}v}v}v~y댊yfs΂Fz`|gzgpeOgYAfIfIpbGεΆTъZphY|ug{tdkdNkLnގgbGr՜vC7\Έdw%֓L̍JϾߵإِB[Ήf׬ح,Õ-j_ʬBݩܱx3xƎR͇_ڬξٳ̹]8ɫJƻBTڦ˄yл.ʁNکӭ߳ޱ΋fx'ЏԙԙԙԙԚԙԙӗΉw7jVU W W W W V T S S?kind-0.27.0/site/static/images/000077500000000000000000000000001475376161000162375ustar00rootroot00000000000000kind-0.27.0/site/static/images/kind-create-cluster.png000066400000000000000000001465261475376161000226300ustar00rootroot00000000000000PNG  IHDRXm˟ IDATx^{\םR#$B29SsJM|"50Q"͡l9l9 ?̱DY1D{T}N=t}~]ヌK$"k ($` " " " " " IF- H#" " " " " - H#" " " " " - H#" " " " " - H#" " " " " - H#" " " " " - H#" " " " " - H#" " " " " - H#" " " " " - H#" " " " " - H#" " " " " - H#" " " " " 3px.Ϟ\6s@p5|lWt:#FkXzrVjCx4=K5] (ܾ+Bi|`V1`A9}"UõmTv3~Yw=w m5 XZ{7F u)[~zƐB" " " "^B*jFMMT1V0 ޱMO(gHʩ|ND@D@D@>4R_nd;L6"1ʓ-)syjvdgRZg<);MhAjsj*gp=:ބ6\@riK݁< ],{B7U* wBXӴR2,y~\@}i>Sg8,rq>ې_ǖ?Z X1M ׏Ө헃)M;ם!mc^1?u.|N\z!Qtᄃ1 3-I2U8[թDB ܹx Fu]kmj XJ^ä g_lKU)U1nbVI}߉L7N} mW_j ɉ" " " " h=`yeEQWGpqB`;z&eב=Z[q1{gVg|Lz=bP~eq³c'(|U1ύpyܤXK;1p6ewD´r VdO Ht:ǾQxΊK%8m5`2.=XS (yG_]~ےGq>!e&ghpЛ *wo-*040)b%*][mFmkXK>~ص1ج8 / ~Gȩ" " " " h=`sph7E@ׯؕY|~QVv+S:_s&ZcޜE9ځ{!ޗ`*νHS;DNYUIwIZ?yUVS{*>[3Giޒap`鍭M]Mtun_nGL^O)ԭŷg2,S԰,_`TnwBFj'vmÃgz4}J`ln(-=R~D@D@D@D@4zRN+֙Ů^9m nH)m`2eQOTu1kl0# sb|V+jgt$Hx`awߖWE %Z-OJr6 ؖj)g%G7ҝȮۚR9 .`)]I@X֙NJT˴I#"Qt)4,dPi^b˧AcSf؞]ko6zhCt'۪1fm϶T " " " " 9ȅZ6< Ϲ;?!RZ XS8y&)`h(_5^#ǝN]$Eݓ֦ kwl#gL>"`eHDNrL X &4 XFR,XצlثS>M1,˨3`_.IC;m)=4HֵԺPV4[U#(,Ncjٷζ mh0;*avmO^E#爀H wV#-p&X h՝Ԝ];#XY|6c1N9=^qu\f37yݯ,;U<ҹf˭a$O益 q 5 z/e;EӶV6ε@u/sc:a2pg5y >A|H"T4fad-m^XeЇ_o/!~Pa:Oj%נԼ 4вVawk3[ӑ ?SaF¶O`Q`<-Bl n::+y 2x`T:M;ѳ6zh ήaӄ'wK=^'`)7,Amh$f^\,97I5Xʍŗ՘?xt7ץ'_"Ն~nkI}*6mȓ/,Zj~KlSOqbp8_/Wbw')9Ѱ| 0mb, XVKkE@D@D@D@D@rQ@V.J" " " " " " " " " " ( +qhK@VoMQϐ#TmNّ~!֚ܨ?1X^ݡ~WA>[UYsyR[*+KaҜ^Sк. _Iv&(6*61gM`c[c/Uˈ!3bX6M|t eʩ4V~,`䪀V872enc^ }+Ioɢ*h@V6$, XZ'E@D@D@DzjuOЫb6>ZSצ^fcf8`aL­hqt; hT( !` Nvr+Xg~pO:(U(;a3Ò\aGv,~ze1Vn1g2زt_fgM鄫,fw,r0U̸ c3M} =#~]Jf(z>U c`-U]Ncݤx mA<ə@o.%DC5KY~W監z4(¼?.Ǝ4wVTǂmiB~DwF]ui#" " " "v h=`M?ȿQזlG׽-qSK1Xl" q "UL?7>^jCS MzZzSAͳ%]+Sw+˵8aeG߇lppmpxwOc$ xE0{ s;嬫fkqlsj,yv]~uO `Y~mkXK>~ص1ج8R~:w. O35#~m.-ѧXK;1p6ew_u:ǾQxΊK%8mt]x܍"+,&ʶʀuçs3ʸP4UӹW_y?Nhd1^q/0x_cNH7jujO|ҵk~([R# Y|}}jQoFXr팇;ʇ"U5P3z壯)݃MO[vWF3VQ&I'QhI{If%XKolOmz&n20tӬn(" " " " iEb_-CpbUĻig?31>kc^ zt0(b#?{ ۓ6ң[JZ2$hZۼҋKT럧D:R Xz\ fބe,;U<[rkghN_ b2AMf5ѠZr|ZXw᧓&LYhO0%mvUeļMS<0HYr=\I>]Om-i༏XEQy yEi4 $&$Ny~MfrEെT$dz=/-1 N:mn'KOTSOqbp8_/Wbw')yӰ| 0mb,@i &}7/[cxrmsF"E8PE׍&%Ћٺ`.#&Q-O2 ܿ{qƗA dzv״^qzM!fyWriX~Fjny(G~giv[;3H'㦌 (W(Ѓun\Nz|nX`i/UTߊ \ĘplXIDK/ZM,djXYŤ`ltiuUED@D@D@D@D=6kcX"I)&q5+bs~x$`isdɴf^,QǶ0ydz{6+ e䥀ԗkWޫƈ䥀ԗkWޫƈ'.ōtrygϳxaw ||m^UH׭gA>[UY]xryao89f4W^}@ΝR?k/"Y匿cb]+|cq* Kᩎ1 Dsr5*" "  KA۩n+̛, sLށP lz5zaQz/)>K/|(`i'Ta׶Q,e݈>l%rUվtG< 3*oCVp>jS0zuE$%#[O$@C1kҚZM)xX5+5Oc)7ByjҙSɯ@WtDxqޠdO`@,o"9QD@D@-UUNyrr;_viKxAHZݫyTUW‰Ų/WT6rj¥OqН\nܟ9#smN9 XjC.Ճ+jz~D>:QN\g%h`[cʸv[?up3rPB_A+4q p9c`L= X=oG2Vs@uې7]DNf1bb6d17DZe#:nv5JN$gq껔俋'J^A\y&uTu9񢤢N\z!Qtᄃ1 3-ǁ9500^/50y~xtU}q;~LSZv)]_>#Ƌe :`93z[cAݧQbw.ѨRQBRT?W+T6&<1ސOcw\4w e7q4SRsf93.}rRRZ8vuLB:cRї#:>)T#;cB?);Mhn' K3)Ly{gE+GwM?̙wrؓR#JN4sdYNXn8W%O:ufݵW+R:CKu)E+L:d=}Vo;û{3$ E*b/}0cO4ejcƔc~lޖ8)l U,c6p%]\|묠̿9g,X0SYκZhIkA{8Q sCkmnG{8+.>7^ԧ6Nx7EJ7fR82`Ȟ^-񭸘3cpSe&{1D?H?ŦTP;~l lW訞ve}WڎR^1IVvdclʬgy%=>UJ^h0UF. z{Nsڵi0iUY'|TwĔ`jujO|%j~([R#,_)݃MTuçs3/Oy*~9oO-HnCC@Z4]X0I|Xp`rp`鍭M]˚ '< Rh=X/ʧs~(QlZ] ל7gQ~gv`mff:,_`TpqvM𹺋FK,3=2Ar*ʤ;i$ -i۟d41KQi'ŎԜx"{?gqQtؑ]a C8Ԭ2c;$n`5v/k,~&wkGV?4NZuI ˸K:zo0uX]=t_zz$&&?_FP)NcQ?ZDJҐZNPC oV~8.0+N:%5X(hs]Kw.Hw"okJ6̻ħSS|8|S}#j+"˔F=RՕSǬ_ ~6QfBV H1/V|:1摟=e!QgmKߩ5ׂy&$Hx`awߖiTl]}Y*y-d|7}s~u6{1`[<\p l68z54ݟפX1,7yӅD+:-y3 @Sqkwi񗣂V~RэMa{3ť<}nqtutMFtCOOqqTk~|@F4O9WD@D@- Xk!iє50ثV]3AuNcL-6M ƱvGV%mE|pW3tk*re+6f+:QRe& ܙ0'lM,ND ؞ZO>I5Gmʏ%;jZYQgp7X1]ÓD3vgS)v}FɣhzeԔa.aDbf$`UeqâZSy =6Ga?])рS'(|rR`yVjDqG_mw=-b4|[څL:)?˒uZ3 bdn#-Cn"1N(":۰w." "Z o+ (;GV:KTCm >A|hZۼvmբ^4G+L>Vg 0Ep+̸ݏ]6gɍߩQ;of˭&La{i1Qnދ-|߭"cǢt2y:ZoŰ+Lڭ&6Έڰqu\f37y1N>}#wRsv(Cۯ*\f+<ys?+QX17y mcJ@{~SLfk%fUNq^e}GL;têbR—^޵R;^`ʵu7 ˬЃflMf{dO a:X2 @7>!QD?\!pnБw]%`|PD@D + Xe)Wo46Onpܜdi1=\꽽]?ڸIa-Dƽ=a *my/df/'~<_^kKEBiɅ8=ݐՎ!Uc͌l2;wg jDF#}63bɹIiky 2x`T:M;ѳ6zjF֒Ulې'RڧnrU~{$5X)K g׉0iBC/ NbJ>On@bBb"<͐ZQFt3/`%5]=kn g^#J ]ޡ|6eOcr,rm tu] [,o[ԙ(\Piikr[ 'n@c>-n˳o`5:N5'׹~X`ߚ;}xa/zϰ0AwsACjkEw??g@m XK'D@D@țNT#a[kLTݻceQ ZQ[ࢨ@ݹ"1ٰ^d!fyWiX~F(m`]sLj/&YZ|AY?5%5Kʟl9ѫBm^ ѵ:pqVnIׄb{6uE bc~.=uU*w|+)(E߽͋8cˠt;嬍vkK.L11ރU8Ă m,G{LG;}+ dzv״^qJM^\Yk=?fHU,ryJzgxx[??ASRv;MFZjOa7#$icw_Y*5;7K0yčlZ„g3^A5ד尊Z瑶ҽhY>D, z(t05D$$&4]Eq^qyѰrpRu):mX£y]!YN[ߐsّAO:}o>k2ϩg_ˏI|9tBB{J\9*L>$" " IE 9(?5'ӲFzD\a񢶜}8=, H*&" " " " "! H{_." " " " "U XZ'VMsB%mE X+EZTf0;2#..-;1s~!wIE@D@D@D@ޘ7F eV~!lUMff ti] Cn^dq1#rUV~![y;ID@D@D@D-x/NLHNZTLށP lѷB>`)UϫSSjT+NR?zcSG%3_c~.)BD@D@D@D}x{rP7kl3S QN`se"|ť˶mU?/E\ KVuAeV?̀yy;^(xގe&MWbm1)++L`˼kX)kS/31N3p0&qt ],{B7U* wBXӴ)yO?2m×)E c8[թDB ܹx F|fO wΜBMs00>e1Vn1g2زt_fgM鄫,fw,40x kMRܿ|G2&}u*crf4!@_?OKQa7i1^C[P>Dr&K Q4ʆ9)O~1^J!V1p1Ҟ=M V܎ ˅\MN>`)gd)aWѐ]1bZU+f]I X׽-qSK1Xl" q "UL?7>^jCI$9`!7_je |Y][J} (><Gӧt6=U݂Ĝ 3,S(t?aPP$FW!1Ee^.9|RD@D@D@D X@]`:UC)\ )Ab24頣Oo`Jݛ4bEb_-Cpbn"wWθ?m')''F(+-dPi^b˧AcSfR/m}ߔ*oe\(j9>S c@-\N@ɑ쉜Ndgc6Zawx0Kjh<Y{ǚ俔(hs]Kw.Hw"ok†ytqJ} T/-~{qA~[w2vn.0>uEM 09z GzsW$7k-$-{s+)\>{q:#~?jr w":gSV1i)խ0(CR*˨3`_.IC;m)=4襀e}4XMċ$Tͅ X7L35`5`N>,K XjE 1[cgۄ6{`_4YuS{˧%f4E0Y"'ҰFa U=ea?e^,L0&`std+ X kK͍w$ez[RyQK&P/zrϗ݊okʔ2;EӶo>`)^FE2<+L6`QiO0(Q{0v?w@ž%7~G9|RF~ z JHYV_d}zШzGblp_:NkkgX.$g|0y=RFrFK (;d'U$hhןQE4DϒE\0sk7vtXܷ>]Om-i༏XEQy y\df/'~<_^kKEBiE ;tCV;d־T353j8TvVߝ%mdK%& SP4Ƙ`sq.OϮUm\Ϩm] |[xNZGÓN{qLǹMITfr*7K+Z XI۴qhbQ &9"iG5X2FK\P)k Q-O2 ܿ{qƗAL4d,oҳӰč7PzP|?f4 -fߙ^j[y-i6GfI]\ 3G2zUH:p9]coV[~MBnvEYL s't?k=F1n}x#Yp6݀ $q(׈RD.Ldxv`"" " " ;do$(?iwiuU[ˑ*Y jSu"Qyp.p>Ȫ i?9D@D@D@D@D  Xjh2kcX"I)&q5+bsNj8 v1dX$>X ;?V" KxGVY?0ҋ%:xT^*X'{!" " " " j HRINT" " " " " ",$xw*g1*TRf|ѫ;RoM|B{cߝFJME@D@D@D-vTKr*0Wg *ߞHڵge[e霻|-" " " " 9^̙a9Ը V9Q<_.]P0hE ݼ]1)hTz5IO >>u;g\XD@D@D@w3`)!jVj CeC}w4%C{&@6FeAum˺dUhRw\Ya]Ypϟ;F Ҟ†V6j[܎N{qr1.^9C;}ꊀ}oO?f+ɨLmGG(pƟmٰ=~k^]xq~P0U]8.7$}~2l_tiC\\qO3ߞ?=c 嶕vL P-rS(b)$JnYXKr4]TXQM(:p˜[ǙT9503L,Θxet4%*bb6d17DZe#:n6]agM鄫,fw,jf2b˸ c3M} =#~)4x kMRܿ|G2?3ԩs9;G'HtSU(/l%Z }9R%-֩wg~pO:(U(;a3Rۮ~WQ|7^>[~tܚ{޿w3-Qa<ɓ_D>!" " " "B \†ŷ¯U"4~w7ڗbH`uoKne6R̪1Ш;e9)AC68ge8s8'}1Csԛ73eav*YW -sNbЃe8Y1ɀrw4it=(OC!U~5ڶ Nq/֎lfXgb"'l|T]]tĵ)6%gK` LIkLҗZbf ~DB|"_,ui<T Y)Z fCJ n~<*̻\2`)ŜK-|7Z-NOz zKz9oO-HeqQ*V|ĄJ(u[0I|X0KL cbRFs2_+`vZew5tvҾߖС>GAÂIbsDh5^IP Mʯ݁v7>יٯⳍ Y*ɛK2l\@ٙFWeO>qBꬭV5 X{!+zLOژ+^> Ϟ2n炳}UG~@` K?U<T Xk!i50؛i<['U/yI&SڋȔbl{ނ!B:^ /dwX}Κ'e^kK+V4[U#(5XY{YF`#tOQFmKAi:w zЦUs>}O2X~9_rd!7le5lBɡ_$k&`tx%mMɅ6V|r$ob Vir׶QQ-) *(GS?fZL;}A{QˆS}V|:?K/,EcFF׋-|߭#cǢt2Cz~׾_ow>QA]==[?>fw 9uI^4'" " "  M* H|h5zK?zij H@Eu8=ɂiim|?imbHLH|u+ X/6^Nx(}L|v|5f!)^-uɤzw醬vȬ}gkffqܩ);m7콅(¸7;LAa;-wv[K8#VQfylC,wx9`vkY/H)cvgM;t + >~98.K][9OD@D@D@ޞ |>Gm܏]kmX ~^كga!pZCv]gVR+&W ]*[@9,i+.!Npܹ*" " " -u ~E64=Ɍ1l>֤QV(VDu#oM^K]7&CϛVt踒q.1:::RcΆ s)UD@D@Dx; hê7cf ;3f)-3&Z 6֟fu*QPw.`,g~bTQM(:p˜[Ǚ0E wΜBMaxYWaS,-lCFQs{[6cv sg?kJ'\%g7{cWYDL9͸ c3M} =#~)'v)]_>#G: fd>УǨI"pp|wG/)g-IŢ\'tQp'.8M۩&;tf-^| <~K.5B" " " @,S2\|5 s/|s5WQ?ŦTP;~l lWiޖ8)l U,c6p%d+A{G߇lppmpxwOc(̿9ٻ?㟛!%dXg NekĨalYb(Ƅ!dnlD0%d4c,{K*uQ<9}ν|=9@^Min]Scm XYq|;.dl_~AaĐY5- ʼnI9O/v?T,X-J7虦=۬G6`mk*Ygnw +MI4a,sLaA׭y}J7_%KvL@!T| ϙj~NEgg=RTD@D@D@D@-H(_{6/I$ch 8p+YRzP;ԑRo//ocF|w2KGb? 8iSYLʦ^T&7MzM9NЬm淮ɰD t]簧ߤoqW"y5 ߕpW@sg:mtgkJuW6 oE-b,qGX bWD"4۫jÂz,#rDοmTX`%p BTv?*U\|f=VΊڣ<`t˅kſY>zx5qKWBKy_morna̞y#`cKB"ilOhn-yKXRTD@D@D@ |4^mq1ۦp~9N$ܐz 38$6 wykMMr' X.lݟ]8`mv'oR ^=MleFoCU:qi]d#e6h`p>c!6&6ϫ=4ژjBV妮o^'+Q_6ɔN}LnZN_68;^M.o uX5};1*:,Xړ(2+PA8P4q,%y\fqz3`\&{Ncٯ-岄=hR2ǔ;ak>7L-5G'l^ݬ69u~+fSwOLטrVNٹt1vƴmQ\s[fcU^lZW\>qO8?19HD@D@D@>F >HCYrk%9[JI@\Ze֟4|,R}Oڈ;g?X\@VF`Ƹ։&x~>ɇĵD'!4 |P-*O9!D@D@D@>j Xuxd4.mM\?as4eTw7`> oT~JV*RAl O?CwZD@D@D@2;vrHX*Peؗ+!>rG, #\XN~,H`ѢE̙30C֬Y5_>" " " " ".$` U3Y38x C ĄiӦICD@D@D@D@>. Xu<:\SƒBUIDNcdrO?elݶgмysfeïr =ID@D@D@D d+Z,I)uM Q91/r+%&Xڷ!}͘ M^WR0k 9? _=SYcqhZ/wHEgUaMZ<^E1 ip*bL ?~˗/腱1=zPv)IcmCŢ8bh:fWY{Dy=Kǀ`,>71f1{oP3]Jwx Xkhm2[jLeFݺuybbbػw/R },K`0ةT-^ex Ff>NUgWU)bv# voI( QޯLx|7;}ŏ29] I=%Ւ&j0MF] {Y=u#.>S:ʔ1S6|q5J2h**WZ1黐<_jϳ[u`mGҏ&``vT yCRSM4h/8Ç=KyRRD@D@D@D@|eԫR{G%8}`7[J䚊qqa[s~LSI2%m[8sb-.]E)^.Yx!7--ǻuaN#`c0Nj/ \Aiɖ^xȑ$_ v?nF@Qjw~.vqFS,'|UE*O'Bk-\ t`wD0#w#1yKkUV8 /zVTY~o=Uc;Ur{ժO#og ԩyVO,YҥK={v*WL9ʃR?)HL)aV6620hZVWTЃMr(N#āaq,s>6-]^P+`尧ߤoqW"y5 ߕhj"&9ݶUU}G˒&ڗae 'lra؀3gN!K}J}GJԟ;֡I6ĈgʟDM1; Fkw6]̣Dg^,?72|89ƹ}B+L60CNiAnsp't!Q_ XZnEy&>_ai%Ϭ=ʴl+>>jA,umVJ9q/ߩJ.AAAir |5hl;]wm0ua앓/W%$AUkfﭤډ ]nߝȉWwzkd˔n[;5=UJ:wȭT[*%1sȥATHht#{ˀ&,z0NuiRdI6XR ۶mKrWRJj^B,0 Xw=󤣇AWr Q+>1~D۴g2#iT09^/UKls)1;,jƾ`)S)liiIjtr8`S)Lș%Vs_|t^Ja;ᄎuNS?(HfZ'-$NX'X+G񱷷Gz +92(0,C(f:d4.mM\?as4 ǥCkeJӧO?}v:l4ID@D@D@D Hʨ#'N:hTU'@ X2!D@RPYfGQe_xg$|2KE e=zhS|;JJ8@96-Zp%6ȏ#GعsЊ@)]:-.)SLϏWfgewFRBD@D@D#^Qx9(<(4EůLF[cw,ȣ3r2zzmA777͎( X+WXpٚD#" " " I wRZrb^VJL~z͚\7c.4yKzU2YFl W# [8K :LfdGl-};6=[6T34lZʲ#G|ɽ}@z.:(@+Ӵ6@2D_8`/~CNJAH5" " " "@ XYs@]Xt>SLMg,# _p"衠 m%e6]NeX]&(\Z-Z M4>Q,>71f1{oP3]JwxPUFS4h&`EFF*X˖-C1F֬YٰaC±zhJQ#`Yˀ@*W xWxPpal3Tu|U"f9p mƝD~@F IDATN3_#[n'wзgerG#c@WK,zӚØ7u튓/G w/ea"[]RɅ2e=9y| ԫ _\dzK]Ufյbw!7;xRѿS67Q9NsRH 6u<1S*3}8d92}\zr_ښs(3Z90±PwclxގZsݳyw|`6<ѩXM>TɅֹ92:Gh֍74/ܸq#{AqB2n5Y]0)X'37I˶^-zuA9SEr`g} WJ*Lr i9m9ޭ sjK{h7w\5mah3d'&>R]flb94+Iߠg XT<ahطb:P[ o J?B~5>YO1vXtڐ,H'6+糎[޽y1ӔTR!oܺuK$UCSD@D@D@D # |5( O=~.vqFsxL+}ƞL<'Gn.cHUmmLaDrS7ΓE ,(38ab0kD}_${\II4,^mp)әܴmqv\Pǐ'dNscl3uZW>HÉB6C(:,Xړ(2+,qtKX({>6c8sîNk:Zm؝q2oh<,f-STsƥ!zIMqWfP2WQ;֩S'5kJ XtfP XԤ$aAhXY`ɇĵD' ~2sjV4/6+\.Q~+5:t;w^[QRHD@D@D@2 >in~ў,_5ƥɕ5G6=?s47=UaÆOG "B)~wˆ/~㤐|,>~;PoV2"&[NE^`e)AތeVoGp&}ƿW91/r+%&X^Uuqb `){.l1d_ WW1CPj/*j^c[~7r9ŘqW(k>5`̆:O{Xͺ`HxVesnXr.GMP-OV R Xkb1UHCPl g I{XmZǽͺ PD@D@D <`-"im-vǽ熻g @4Gt%0ʏF&ɫxJ%M=T<@/HUE\elޅz~.*.b;y.&ݺEq?t!`+0WpXg p 9Rdq1MyYš=sZ߲tQGn~] ojjRԯ8^2m6/  bȬzKA{lĤܧ^QLM,ǖf%,.Xݷ.K1q9E!%:TuO6B52lfأnΫOѾTUUad:͛&]W 9ѹR|+nk/#짟&u&uKVY/9]T?nng#&D@D@D@D܍g؛Dr>?/U񹴋sK{e- xwAQMqe9ᄃ'^V|VV6i/$9]!U5u9Mr^.|_W:.{-c 7"R$x7+9)r/7-'W[[cM/ !s|.f[hׂ% ;-6Z{$z3<8MZɞz[:E|}i_*`G-?*VNKg{%!UAgn={[9+Qk]%ys'.Oj | +ZCA喖 |1b(& X*|ΎDŽa 7"R*RΓs ߍzGlG,aQZ {+UcdSe{EuJP9ʑ#hP3IJDYDT;_qKLXC~%E}7ۖjeTKw{0D- tŢ%7 yueLB6j<Ɩ;hr3MD$i?q A[=J|R5uzlj)9)wϴ3n/dT?fÍ$" " " ~k_C16N:+w{&Nجb946Je^ݬ69u~+f=[^ζ<'_~_ʬ=51dA Yz66`TPgۑ)N@O7 e::գp?+o{V8T4JQ1:Im`.mvA%]"wͺ$." " " ) <` dV Xu" " " " " epRPD@D@D@D@D Hʬ#/0,J" " " " " U@Vfy$`T*Ȭ2Kߣ^g1sQxP^/~JJ^+9Nʈ }uu#M/&'4)gk$J }͘ M}R91/r+%&Xijր3r~;k!Z%C[D@D@D Nۃq9ҭsW}#{M(}3dZ Xiz Xi& D@D@D@ޭQ6%q ozi^u&u<1S*3}8} FڝMHϊ~4=C(ym{{ǹ*Er<'cMM=51oB7'_^=l3Tu|U"f9p mŭvhQ}) p֢clIƽo QޯLx|7;}ŏ cv*%σǼۮ~σiqQmV$76bp'w "]uZOKC[sbneF+&Up@I[2Xt%0ʏF&@WK,z+M>ղ<>rU/.2FIF&WԯxBt[Q6S$OάDs=Z,0ain~=< r4]x#6CvqbRN~ ݏ(e&&cK R/!Tz`l:0vz=\0l5:&!klآYt7Uk,v6~;Ց/=wy pY ȡ/ 'HՙzUmvƪ6نOnSN WٸR"Td w¤`sdϜ?KA׭y}J7_%KvL@!T|FFYyf#(| jIMט/ڬ5Q}*rpưl4y[R1jAjT0 \:*dzKmힲk0+f$i'81(kZJ_@ARj߹0yɅT3 me܉coJ!!?CB01 m̀+g-DwJ0#w#DŮ8hj(4_Uʓ.*Ssի>9j}T0%z0wL>Vz_a%ŀx X-]UU.j- {e- e?%Z.Ln՟Kch[^/>uᛏI%fovDK,cyڂV }oP7 geG+=K)D@D@D@x7 ;ȹɱ$ngQbŃX O5;Q' T8̎ яv9M{Mz(';Ya97`]ƷgSA6YD"t=B(xbAm()?c݄݃s[+ַ/J{\Zw6VEN_HKcشtD)ۛ.y7*.|.% Ȉw%8CGs:mtW$T84Ǥ=` %KJskSoj*E@D@D@D ! Wo j%*S ī_ bGg>3X*`G-?*VN)`fpIUO}yUJ1`Q'lW|+mVk~U*N/MW9cDj n kO}vb9" " " "fPNL4ckO>,O/JY \j 𹨢Du}@E IDATt?n? f/r~Lj*_C6,hUnzyX|:Ib\]҂2CwY٫bEʯ8[}9a:ʥ;(هiؓqsp U1C\uaW5aN=oe55g)t!K4I{s̸ghMy 5qg+=}8vs9ClLl8(/C5Wu,Rp8O<H" " " o  ̩;'kL9+c^9)l6^W3bF%\m Q+>1~۴+/C螫fvEM['S҄D/ Xz eJJ.ytVD@D@D@D  H16]Ռ<%[3s)Mܻ#" " " " A@VFLFc:nxMsaEg @:H'" " " " "q$`es X|y" " " " " G@V+i@:H'" " " " "q$`es X|y" " " " " G@V+i@:H'" " " " "q$`es X|y" " " " " G@V+i@:H'" " " " "q$`es X|y" " " " " G@V+i@:H'" " " " "q$`es X|y" " " " " G@V+i@:H'" " " " "q$`es,UŐ<,NmU8/<2g?(m zJO\<;a*ދXGP8Ķv0?u;4S)oOB}_ZuUfŝ" " " op%ļȭ`MR.o\h֫Ss=0zfCOpgԟ;kD-dZ>ϟ,&}TGq7 +?=tjKgD@D@DHsW)mFLJyLT}ΥW20NlfH" " "yRqᗝ){ h0mG:MJy/+0WpXU<7=K}r;@f'}R$s!`\<6߄F$?%Ւ&&U֞i;CuEp5Qʄw#8Ӈ]8c}W朻o.:'912.+c(^"k$_"M6v:CJY8/,0J21Yx`.xczTg̃X1qny~t ]mVcq7*G>_I%_b~v]7pOt'Xo?gtɝk?*;sx*m`Fh" " " iZV;QpU[ !.( qksgfaj}Eu.V¼>%|d;O_MO>MVfN 6_FGz1d(v,!˸R"Td w¤`sdϜJ<+t]3XT<ahطb:EL6eI8&38#(D>SvR[%k͐]k#JuɲҬ$}`W07tYCuO6B52lfؓPKaߖDǧia?4ݮ3_ra͉JfdBD@D@2; X4_FcA N8ȓfkc_o3PMRB%7XWr%?暪:>vQcn)>P{a{7:g-DQt=XBk-\ t`B]UncF|w2[@b? 8ͲxB Tt>R_لLoD@͉yh'7hWMTk]w%, ,<|?Fy@H2wck 9Qn"J0`,u"`BZew~Ǧ8|7Yvk9Ts:mtgk ^.|_ͰſY>zxKakSA6YW4niJkTX`%pMMTv?*U_0%^FP%J| 4 Y2؊Ai4_x$*`imQ$9]MUMf]e5nzLk)*" " " w!s|.f[hׂ%j==Gpd`-`Y>H_\mmAm)-Qc;jQrXۣ3`262|89ƹ}B+&R9"ٕLQ:;G x?~i/`h1*Ƿ{2rVֺPKX (XZ}+b<[Z3Uƌ^4`wMJgmNſ'=Mze{E͵JPu9cDj& Xɗ:Jq"U5؎#~כ=[ve7d)|THi68TДn[;5=S.%A۹.lr hBgueq\hXXa0z%["8x&EhJǏA=gcKvU "ّd(`fJNJ[`ڙ S`mZAڬ;*E@D@D@*nE6=澵>6L;Y \j 𹨢Du}' X;=sDŲRqp U1C\uaW5a΄M.o uX5};($`p>c!6&Ufqz3`\&(dLg߾cO&9,(:,Xړ(2+~[+۬G"n/_R :w%VsB̫]I&b(~i~c$nnLZ'Ԝ gx=Y])" " "6w.rb-+2$)>LQz WOiP%&>*;}rhm;OcڈvԶK(-Gk:@fհX17#7HgR}$+|M2V|bold+2s˯) h=NC`n4}Z}NFC}t cBUQ 9sQxy_ &FdɢbŸy{rΦѴ269_r1~1糎wz*92: 2nJeϳo?[5f =!'!~ [-]}fsg?پh4&NN(C(q%Y C4 EsސTYs2;Z?;L:/ SJί(: U6')%wsO6aݲnL^ә;3~m^^/:~yD9^Yݬo,\y)ψ)Uf_?_;唟+g _M 7M(Rՙ.%m] JΥ6lS~֞)ܾ[~F/0c8A.ٱ?â)~54DcZCYRfC>MW}W$%>z)CEq7oh3_(iX([_G%^t p{֦~Z4c'PJaWD2:Ec):AE3]iu?X+`$8o9&ҧm,0p'v?hoY28WdG9'&.JR57'.nWT:z0Tۇ׏gb J[CU X6fH;Ƈ*T eJe v6k?xS!OncIʏOvfX6U9=#5Y9(c<;*>݅j5+W[:]' fOvK%; )r| `.+{}e,]쭳G&Q Pk16L؛)}"'Bv1j͋ê̦oP_=vřeq߹3Qhm/P{igE.L= ϧ 4mW^rdCqH^NjQȷKى!_c}~leJSh ퟱG5څ`Zl0 Z- Ds^9(VF )ϔYVse)_lŲ'ɫtAqO %s9JL 3DGˍ?>Lpǂ i1;ϩQN s!v*O<hjuaٵ7d2rÙ.5%՞Fnc⮨.>ɷY.E3QS: x渿%]Kj3{qΩnX_{BWr0i(9/==af]#PisGu1}vj s:U$7 Zl&۾;1.yڳϔw>0"+Xgbj7Qn-L/;O'Hui@lL9WY1NRg➵y8buMR݅|]8|72ag\}jL3=)q3ÛXb~͛?͋?y"Y|6ν=ޯHՔ|pEK!yYs֮5;C %Xs꽗0;o29䥂!JƘŋsGId.P@r zZ׾c6*L!4.m1/5Z٠[2;ر)}'tYa,,ڬU6- }kC ݒc/b_ÛWLQo2`˳YaNɍ`m59pyꡔ*3mh0\۔7OȜ6ڕcc}+cc]ihȼ Zi=M'$܀C4^w5g{-8ߍo>.LL2C›DI}Jpk?EP&Q2?+q.ZrNDz :ڧ*` 7&6טZ.5pukO;*ْqp'l4vTKî V~@L+[mֿ064`ᯋR+p& gxZEUɽgΓXv\xؼx4` \i[ L >L!q7)ΰU֜{)Y{?Vp; 3~y{{@eɋPti*VHdGR߹Ctt4 ,dZsJ om :{gg]c{PJՓ`Ɏ>M rc i΢_ @T4)[`).% Vəg" ;ę>`9uνxSⱫN>?'>_cJ'虣 R`)^zMvwŧ'^J|}VSʼn$؆Ҿ+LGѹ`)cq-R0`U~RXG%QGSJ~>Kߟ'XuqU8ߍʅCL'ƾkxt߼ꜟBњD$M_|)Sy| 7ix&"8`U=D[k0VO*@w#oΆy확+ԫ:#RSp5ZTՙu-˪(OTn\VJ?'rI6EpvZE4L`Y⮋IUH`1aՉ3.~#ե$Uw j|ͦi~!y3_{: j >bKnmӔacWWdI_ +o[Ф2e.!d%[TΚ|Xy)L<3~bcX;Q2Ɣ-({ ߬.h:'̂V,j̣I_ͽoPY 9OY27Y6_9U1 Rxbc IQC[oEP>⷏`9X'"hxupga؍.B1jMikK4IѽVr.kڢs)cJ0`J\aX(E(YnưGEy ^A:cBWqt5?S %M`aE7Owp|lfxuggqwJYa&#_̨C.s/u }GNA,HxŒ:)(sܜ p5фA'X@[8:ݑ~ Xq֎يv݅椯#1<,eREzd1U$!\~dJ\X﬑] 'bG֟3Ren"}Kqz Sgeqi?-*JxJ~m9skT#DAbc)fxKN `YoheANŨzkL0_rˉʄӿ=bh:Һ OoJՐ_ie41~ak}W L7}/sFCj,"/¶L2*4\pl;:54h W޷U2Ɣ-U6V> gvǴØci\SK4dd{r8=nDc{ esb!w߱dŔ#g>tKszo?,'_s=RgJc' %2c⮨.2}mVQRYQr|9!5GYr_J b 3u%Xzׇ㥬`0 Q\`B61)RΌWr|.>Ʋvd4m>=K1, pS~rfz{Y8~P Iw_V."GE0nli|Q3̟ꉽl9ߵD곹-ۛ3 o}kYP}ہ, T1Uz떒9J ]}oYAՃf3ѣ __etOP'L\E_[v,uR۴<֦<}]Kjzk3GsYSwz1%>ڬ`RRE6Fm8@XG+M˄ǴǼuұq>S9ÕBɺNY _3w=J(+2%*$#f+2O]ɜ:ZY[.CHf{O1D3 ەE%|bM\Yt{'"vC;-Rs1p}RF -;I>$~,ж3eBe~Ƒ7x9/`XRz'+iԇ\( V*W$2_kWṙH%mWt5b{«pG7mLc?fI?ǘnI%)@0SAUp f&a[qi`)i_;߭A/bm3ͻs( 9BRO@ԳSKƆI@,#AJ1" " " " " " 0$XFbD@D@D@D@D@D@," " " " " "`$I)ň$X2D@D@D@D@D@DH` RId $2#" " " " " `# He$H)FD@D@D@D@D@$1 " " " " " FHRH%c@D@D@D@D@D@$ KƀI@,#AJ1" " " " " " 0$XFbD@D@D@D@D@D@," " " " " "`$I)ň@N0/~a:ᅦX@NŬW.4J# g3wx;*3HB|zЀ!`IjJbZ$Xu!7䘯;=0ºL1_nEaaG]ii􄉕R{psz& 2ƬDD@>}N>q ̹~x##{` L)[42Nߕq" " " " ">HW$F_MZE}'?Ԗ.Qw ڎjóD+VU; "W7<Yv8+Xtl! դsbp }[)Q92?[;#&NϼJnd 3ΙrMqq4MAe7?]/ yW>鹍X+~m>_AcmsTu=” (agɋ8DΡ(" " " "  |נR> ߾~>[P"B>NƲA0{G E1>| VҸ3q[r~9QXywT86 | QM ;*q,U]߉r; 9nƗ5gqW`iɮ lQg?̃5Yλyc"zIo,'b[4^T]L sw6?u4]B[#e]/GJN,֪RrwgܮX+I-x1F," " " " Q%Xgbj߮)U%.rpQN{k՜,@~|~:G=):42,=nfph#a * 6QN]*uKϙm_` oo򂵙/ +MÎZ{Mh:m#XdwCͿПO24bc3 v\@Ă<7er^I۾;1.yڳϏx&HU" " " " "``:&mrt%"cljYKhjE,8 :%ϑ sSGx=ٕA i/cOaDpvwml݁EGqpefعnՇ6Էy)`IQ5ZJrQ7 E# rIC,L u!tTKg#+CV /Nr㸤Mt]]P,;gٽGq =C|"D@D@D@D@>Knk>J.@L+[mNiќ;O`FAsq!/c{!jB;f&We~9J"`}J\(Cobxe\2^KOn`4&:OsīڊMi>LƩD$sbkMfmJ_ %5K \S/NJd|6H" " " " "/>~̷w㺶:N>h%jsx.II-c*aˊ.~cڽVGK@n2!yG`4ZU>hr(B);X:iKؕ+e 2lyzfð]JcK<;Drz -J \9Vlx0מYi"{9ZD@D@D@D@>O=pt#;O)S"h?*X3^D4S{\ 'Iy1Wsh+0.ϑfcJ}Fx)yL+ emǒ 7+d$sA t:M]=g" " " " " FhR]@M@,Q*- ;l$!>=аbd>ԭX< og^IT@Fx5Z1{l-s#z3~Lکm K|SWr6[s̜vU)dcNmF@CH`}0Z.ugǔFX?/Si hL.LDMA>fP_Pg-:&@0zU}6gxПO} E}j5<)PΒq2*^C9`iSi mGBY"C}忕+IqdÛG҅#Z`qVCI-lJ "Rs:25e&[ŷvFMjAyN 1i+`y9SnܥdaWh盟.u{>M{eMI?I{D@D@D@D@D@@$XfMskU)V3n],󕤖knO\)myEptB6 ;b0-Ҏc< ƝI N~ *CQ!rr.DPu~9uYbKNdWdžq axgHƳ{x~ F2r;oLY<4[}UiƝ:H-5)2gmTx=nx²koRe [7w墇3]6jRm%q(qJD@D@D@D@D Jew'7=O{LTE:zr>9 #ZWuu{v.-ȸ-m˼#럄ccۣHXu.aT(6qW gݒesfef!›}[`m$14 ;ƊJk_ʇ^*$I pmCiKgD*;N:77Yݐv/jh޸¡TIՑe,wH~6.^ɶ/;o͡]}6]wkM7%Oc V6{Z #b=Іn`m`/:c+3Εp>DxX9h %FGchł+%xlʜ h E# rI3=U">@u y}݃U_NmGD`=Dr[{e߉Wɖ,mS -6*"yv`(k`BG*q ֢ ʖ╙r-0T nY%οmeC8xf(:gj^& Ӏ@ IDAT81~_)͗gU8UȕcuxZJT0zUu5"KMlάk4XV+qw`>=z+PU˻q]['_%',|Y>7Or̾FSC.`,sƜuR,ԞmM]I!BEi6F}ؙ w6"TK<;DrKT9R0̝ssNV*#袊}ְ{FUJHZ3(QF ;l#K2ꤦs{ntGv $`1Z;Rf+:E~T;H3W+0.ϑfcJ}Fx)yL+# bmV߯yo8{;Iqr`Ultܐ2_M./ُ%o9?MX*ʆVȤRzo?,'_s=Rgڣ$R||'X Iw_V."G"Q)?93,ϊˉ3:MbO;j8}{\=M36* +XQ3#R!9mg`/DjSR`ebPX;h^Lq1bwuq'?7M2p'>yzHHdm{lL…wӾ5KUgNo8X)v- 빮RK Z&刀@+L+" " " " " HFZ&" " " " "$Jc技|`}1IX" " " " " $Xnle" " " " " iL@40i+ Gg3wx;*3HB|za1B96z/VM]l O$6)C r6Km6B{]OS@+ׅ\Xߐc ^҃e3 8m*6oŒ'^1w͏72R ՘zI^GyH>@eM!L݀vxxQAY'c&{>VH0j[3ܡ!SΧMtl! xZ̉JÕ.9Dl .GUtd6jLl/oԂ:? mKî.7"VgN|,E7c̉#tR"CJoޙcm" " " " R YZUUu|%嚛r*Њ6$,MYgcu݋۬@9i0̿A(s=f/k:Ӓ]21ң&#)?*(J3AmI?.#sw6?u4];2YF>Bx_5Y#" " " "n`ɍwqӞ5&fZ7k0pmfVBC%?}ϭޤ4=nfph#a * 6QN]*uKϙm_` oo򂵙XA9Ǭm(Mp_#C>h:}(q :PXJy%J1+`\Yv`-,ϲ{Dgzw=DRg=ZT.cs3R m"ǞV1Èk:젛&Xr3غ닎̰s%ܪQ1l.V5 r cUd`EˤPBWLŽt6b_9_m(7KJ.+=`gy@z0~2Dr[{e߉Wɖ,︅%GGhݥhe4V*QBˏCUEzӕ-+3fr q3CsV=;iؚ=cS:@Y?l.湒~%dk,:" " " " Z$Xɬ`>=zf_+W?v\ĹM]_TgM)c2!y=änѪE } 5$rQY 9OTTv=m#iЇ}z -Ji^_U.b4VD@D@D@D =kϠGYyK,pIJIW,ɨ{Y8#}BF.5 sdh'YR_%cʸhӮ(R?]O9X*7$ڪEs(6=.:rWks?2,Yl9\I{kL$Jq^$X.UOIg\W" " " " "  V*K" " " " " S@W@*H R@+}Uz%" " " " " `T)" " " " ">$Jq^$X.UOIg\W" " " " "  V*K" " " " " S@W@*H R@+}Uz%" " " " " `T)" " " " ">$Jq^$X.UOIg\W" " " " "  V*K" " " " " SG2IENDB`kind-0.27.0/site/static/logo/000077500000000000000000000000001475376161000157325ustar00rootroot00000000000000kind-0.27.0/site/static/logo/LICENSE000077500000000000000000000002061475376161000167400ustar00rootroot00000000000000# The kind logo files are licensed under a choice of either Apache-2.0 or CC-BY-4.0 (Creative Commons Attribution 4.0 International). kind-0.27.0/site/static/logo/logo.png000077500000000000000000002472211475376161000174130ustar00rootroot00000000000000PNG  IHDRR6 pHYsod IDATxO&QA:,wQ>)JQQQO} 6:H;,-r'L6eLf2)8Ν~V2YSN7YrDTJ* ֺZ%`Wdjt/7XQFH"B"PHR.U*"q\z 홥zKr;dΩ/LoB~ar_@bX-)\y8|IxWqw ; nJǿՖl=XdBݫ1X_A f+Sv:"[P:Bq23E(҃/ezkC x3z0UNUyxޤ o8LRd# T  AT}rl"*0xúM4@E|[W@aa֯-n}Sqqln5§7>PQFC٪#^l U ~d::j㘊}X"a ^IQ@㣈cN 7Fn%'ň+* +{3;g xW'bdM% .H6?%͗vWHD@ٗ׫o_tƩÑ)I+,7E,+2 Âӛg+'Po_rTM|"-5 og38;}͸uLQܕel}xN&qKjM=9HcQ3i<'?L!CG~.#T8 mL⍸O:C^7І'E#V6KYe~(#c3Q"3X_A+C!WO` LKB+l_EuFf%uW9Tv؛6S7)G<[El:_Ful{:~@*n(36xɽ(<-l\.sV(տ?~t5v64»L {! 2>2? NxlCPis%22>8f`lsҢE&Վp6:mjl!(:/,y.ĉ4ʇZ&NzMcXaPoa}I\^ g`d*< K"B? 'kT;w-sgؕF+\ךJ%оO]RҴOKRn;Fqۇ+P}WT"pDAv@X{w.eR="&tu<\*=P缷.J'ú*)1)0f9VIެML&ce){P{! 9nqZ2qr7O_f^ܛpovPsCƧkttrdh-|8KΆ!s'ߕל (t \ٚqxӱ;1(U>oƦ \x…t]:JJ*upC(wbFJ;Oέ}zo.LPL:124w}9꽷3>図ۯc$ໞ8@hg -+(<TTV'A* 2:[cS7J!5+oYilgR{oKr!Plq R6R/V'&~~y8ekiK>bLPgbl(l"EՕi)uX=Kޙ‚yE8co0fʧv|ң eʐ7 &l:HAƤ|>s 26bڸ)k6]fG] +<2 l(# _ gÁnNg2f!l2Cv,t tiXD)HEeP nROk,zkhIיaUB"ڶJ4B >F" %p:UNǕ^ zezKŪjb.i4@kq>rSm7t.n%q#l!kx8]d H<]%9rBg& imc(ћ/JH{56HT)u\<|:0?_FwP[<@;ί6] _F6Y\:/D(حȂިE Δ  &ALS_-4vԍ?A<`cP76nBA+-ݖ;3fl⾌K6 Nyk tBWf͟{۾»;Yx4T22 yMh#3@Oi@>ACF2ZDHwMirqhOYFF|Tjsd Fw]i3%MNUnegD[)BcKh׺a;~rPx8>$L|&oxc7KPÿB|]蜻3K i[hcehgPfpgˋat?e}2Tގ8F?5kެ涢),&]^ l\ 5x@}ߟVRCmW&"b)/@fj*K+\Z.8l?Heg핰%bX5ajbs:x鲂Zc/+]10vF%smlFc tI=ȲmΫ"|\*˵欒+lX*Rq/?QktaQN; cଌO8Y6QsR1Z5p_Ko&Ý d.azC^|jxr€ &$ g~tP+ï. ~ha|VՒOyFEF8 <"\FEfpdNp7h|bF-CAԟ{;+=-_xPV?=a=z3Segc͟7*p% Ւ}q]Glv.Is395oR3k.t#_~؅5HfNwC#V^{r Pe>uǃTgʾ"rbU3]BF Jbo;?\:43zGY_#j.l/Auإzi?aB=h7uš\!CƚxoIeؐ?.ދ7Mq˵yn.hvekhtXq܉bht>p.&מˏ~*$8T>iP^iB^ۿ<N_Ӭ.@.Y|L܏vڝhiwV>*.>2D.Χ*4fE^ rX;帑R70%lcu]&A ʼ._%2ȫ՞[wC#|O0&#,u5 IR>?s>O%2R*'8!=}&`|otyiK>y?ڳ*v*|^䟜*s{_wC'^'0qV[ a{q˟VI{FaFjn `B#GA`/>Sϔ]=sGKY Q(!Eq |eb[׋- Z!it%"^ !'%Z')NEKlfWq7h//׳p'W*S"áx<`cG޸VK@ƎvUyc)gT6LX>zǏ.!"l(;Ʀbvc2b& .&HuYyj ݉09MʧNGK[<1|Óh#ce⢹)B)76&2Z-60Z6'76&T%1H$,&eª& SBrLZb. G\}Ek+pVbk^[LxOr"z g'h?o_1Bp#MD@s+N_(A;[PC*Z˾nM~\"ًN t@LD]4IJt -q'(pv>0%[i,qѿ-)ŭF (tU7qőm\·]Zw U@zM-9^]?^SkB1P•_#>Pt'ݗIDouzqsي%pgUn0yYs!kgsuF\-Kw?~'2 A% ܅FUutoDtbiH60|b4$*jG!1}/l? ZeE֒|th[ I +')a+tPHz?;^qfjܽKZu%^:)FS`i5O^3£Ks",ɭo#b eJ_[\Ta^u D"t QWr.ZuA}4D"1w"˫J ؜G,HtRo `bq|`yg^h z=Hv-`ű3m8 "q^a?0Y]{.]CP} 8w$j}I29uY} S虝;/랺{,n7+͂ΪkDc?|o NX٘Vx'5qҳ*|ݦkf _r++#1;{q'/qyC=OWj; * {y2]MpChS6E+}mU-Ըuu7a=6(:\fuepfG #6Fynvt ?z  Hn-;HIj lzӽK0O_7„w<@lqr9,[5ScX` ;T'F$a\FHC6#Dk&Cjg5[-гKIgQ9=g rEˎ]`ώ=?.l'e0 ` QE5臿zQQ4o5b}mؘh-%#V} )BLJ'NݴE+.̋ey`.&lpc0g֗*aoBl—HʆY;& 78yr_#v#Oz94eI'S 2RXkE IDAT+s&"_5tl&|nʉd9m/}Z NADMUDdB$ R͛5e̸g 7­׫P,sFSSgl}Z8kj@ 76>pkoF@LDb7lܾ R@~a}$Xtz=vQyz2Ūd6N߫K:p}~]aHvlp-;%L]`F 3 [DȕQm6I=GUVyM$TvgcױD4GC ޓk*(2 [ Fܙ$hs \-j+eL]{w#ab0˅  PN7w=Q }K.}ō 8#7gYsF$`\Tn"6*C\ RPk`?Y' ى-!E"jq31^ךl=Qm UF`,Su%klmoo[=Qf͛(" %e *cA*1svm}4m&m嫏;:B`$29(҈ <*Sd ߮6>R``j{J1^e enٙrUulUט 5fk q䱭mu A*:Fפ3? %V]$F** 42q M*c K\ݶl5M_:HulA|懄NH=6N)`[>\Dpq_ۙV%"arjX"ϯ _72޳ bHٌaуh3!Qr XLōD(WjK`0\U0K4E:3}y 0k {LkZ\՚l ͮj*yyA")ܑ=&pм8R`by]5 1xT;p-6ML`QcBToFFXX~⭮q21TqJ;JTر,t _^-enn`/ /շ)`+hP&Xؘ8exAXeу{G¾Qe2! [zS@U\jd3<%KPޗg-P}5v&A!1</Cǽj }$b6qoc H͒cb" U,6~ܫZd,lLQ7VJL>HXؘ51i YmiWW%>c ž7ioH&aRHcc"TњIMpD4 ,C ;1g cLPJP#t:Ֆk C'S7LՑ ai-@:6vn8'xHF-"tB0POmfn&☨aP؜Mk^ 5#r~R/Xؘbko(/ W*$ ezܿid?–cIY{թʭ} u@;^yqmI ?`ac&yU<'s| ; /3EB'1au [Ž߁0F.4>"o \+jhb:BWY)7E -\d:ypɒKsaUۇp ,ڼ9Yt0Ҽ*Ka0\l,f54XΔk(dFWcn)t"0lVӯTmx[R*i~Љ`BYkB--ҙEO҂x.l[,2:UҢǂJ:+b@) -*r?|&1J>ZAù3©4qlj ]e87 c/RxДRp͓66Մ)oy7Hx/O?I (TF8 9:GCd/g>?]N*%n ;"Z4,yߕo={E'v1Q-pLĸB ѿ{UK;_f2^]J#x8b8s=/KCQ-p$#F(1Gǝ_j|2д\4wm'/v6% q>pF>ULW[&r|J[@Bh"_  O<DJ**ɭ2ecaa!Z4U:λL3^Sl9v@TH`㛩`!ܹ8[-#.Jۺ5yDgiB'v^N0sF;Wke[{C\s|ɻm RbCꞽyT%pxw2l~`Hus!+2h:Eqnۃ\>s/8}nGo8/ɾ7zW=K+G=vsF@X\MgnҒ܀8eIެzF,lkۘCgO$*ڢ.(0󸱰CRԡҭQaۢ4,n/|SL-Ə^+tZ QƤْmH::Ν?X!ƒvC}RΐYD\-Du4I.qԳPnׯx΢FX&%C65Gh`\XeAw^3 [HTU9xK ʪ#,l*"c"jnM6NJa4WlbG ek[hnZ|ZtHP(8MC8{O~-l|a16C~*yЇUɭXV>Y\e76핰/s;rAV@S>g_iNHhȇ b+S=@IڣhԴ Ȕq@$g=uӡ+y`0ק |QO^bQ#|W=e[܄5?ϋ Nk,d5֪{߽SZ'%Ųwi:t{) &A Tk^8A o?1-篎dmzPOͻiء*W_Vl`[)\V;wtP =Z*>۾"tIYYa+S~X4p΂ ={n׊5΅+/:JA0͜*x<|KMaV੝1T<.@/n:p!ÎŮ5df!zVA#lrN)ޚ0h>^NCvqEN:޼w+P c7m.EN_ק‘0.W]KO8gg9'hˮ~mcLZ_OI?8v@!kʝ3*\ׄ=7KO̬`-;g$ԑ߳}6:FRy\GA}]S1X_ŻcHC{EfA{hPݸq`&AcŒ1.{QU|QIoV.𨉍IMJ&&AsZHzV +OXs.b] 7hWIYGra?k#RW,+®WЪ^ >Ľx<2`abzXl;)l4p7[+֗i. wP"* 0vSYzZ\!ʮiU^HLwGa' NiI"Z)틽)>Jҗ?-!vljhpyJrO8Zzz\yF͑nC8kxó-;~쪆kY`YN߇8KhN!*fJHEetÆP=AT)|?} ϶=}ٕi3LYB+9?,̦%j!3hJ IA1 Rng2[ߎsfe3uH{8X ;hﵫKQ5jr7 =ϭ,g]ˈ'pTT[GjXS͢kTߓWj/̀m7B{2\fꖩJ1)O$"lʘ> ix>\EOzi4CCb-cwҹvڻ(宂Y=b Kv0Ot="ݵĆxno^~@$k} i"Q4Q!?y&0;"Azp٭d%O$RzmT&w4{vn6`ڎ%:]Qdm@y'u[ 'k9Sjb$y5)*(1K_k֛@Jٻ(f21RPT uýg0Mr}:ϻ4eh hg/ݏ?gmz~IBΌi@@¾AZ0,skL;Slzu$O'ׁWPdm. jW۵b e2cފ xLky̱bh灇`/$aoC39\Ndx[w$xK/7G2^ƲI)zڑ0@z@/ib*cq b/n kf-.NT1._zz#VQW+U}v 8Le{,*F Ѧ;qD%}%!Rh&+z5֏޽mW|?Lg] 9|r~'C~$T"gc8({kf$"_&_13zb0UB9N(b;^a+*v,B"4TBR܁Vgח_n'q(,lî[U . |a E"QD p%n4'Y*j+˸`ZEfGN=߯' w}%K_|}|»pX;2eIq3~ ϯ,[>DžGszB fOaYc|@X5߂Ba( gw#O.jkѱ# o*.1+%ķ&p8wHCsʑ'}?27 + z-;>}'/A \}TЮe k? ?w}!ًu{oGOb(a~peTW<&| 8xr3FV;PnwؔG7قWi-JwFolzo{t3L8u4{3-LŽpu91߸é:eMd%sbzG5 Bj% v:_oظo7W\{&kKvѩ]8t<`sO%`p-xHQ':+$)ėz@*&~9k>ЖeNqбM({\lSHL\f¥ذi+LyQs-_lo/߾g?TW6HT]%^= V'ZPgQӅ5⳥oCۨsT(nwqo;Iiޞ .aG,1B(֍9K>R-a~e fSF6H*x{uΚ,S.3'\u^o_N(3z/xpkr#[wzZB$΢v1#FgQi`Pw8,s&zG ԄͥK(ŐbHqGe~i:X4^t{Yq=N\M&=[#_qx6ouC&! *J8H+TeaEQ6a}Yo:JQ`&?`? ~5̄ykϾoPmoWn (Y]68x>~^ F^7aG_~6ڼ5¯ k"QV1S?"Χ +a{[bw ml6bIA{/;b|އp8W:0Y-2۝W:d Yr:?r vlG>幐6"\*!Q\ Us֨B˾/]|q.dxv Ɔ9/g?}ٜ8/ym 47zwDe21ÑG=Tҗ2H&EFMe-{ }vh྽+>q7wIOz g%l &:5!d/T`0¦(SQkt g*r-l jQpTG$g򐜐!gձFۿY1~J1t5jfҤc-bz0L]+q9@M"QG Ixod`2<yaLFV9Hبr~/pvC:.Z3a?^mn?T"w92l`oHl.. IDATC,/.8F+\EA#C}lXkNU,:tUHJз›;yY`Sޛ:L^*ޚ<޼W  s[bR@R϶ՅƶㄙPv&SNlcU+JXm`cͭ}^Wf,F~g+D$\ad Sq2\:Ē73o$(gvT7:5RNjͻB$FHdbD/SP !v(lLdmYow(h3:j/)o͹؆R}{ dXɑ[db H;Zm\$g'DCVt#76z 1.,# {O: L!˯(t0KW9IB#RTUWḾsD,lTػx䔐`"W`M\ &bucW/vʚF\w(E0a޸]wc,76&l^~mg)ύ@C,l &QW(B#JY^NF:FXl4HdWĄ$eyM',]iRfC4)ylrsaaG&e7y <NtVYjX$jomc\vUF"R.W*̽7e]*7$B "MlMTWK7 Lx:7)[?Kn6`0HbJơH$M9};!҆ ?;%T/11<  VUpK^{e㥛L&tL D;Hl|K|a`0R'hCouëbh9m6 xgJUWlhNF:_0`0캩 m2.XӃ .~"h! Nfb"޶4* L#͂n]GAPx1jsbȅ5V0$xLaz'mED .cs8b8&75mo4m̖]h-'g"t0ii 9>u  :(S .cl5ȩ42&]5 1ـ#N{՚E I`Jbؚ'zL^ךB)dhfx\ B=Nu8A c04PKȯT7Di{Ix9 'xu\-$b1Lr[Htf+5)m[$"qa7\q W/N@i6 a Rb3Us:id|^J70MdY`0"Kߨږlo"uN;ܚMd ֵ qM7ި{ Ӣn^c3#p8z\cZ%|Ix!q8sx3 ᗬT9Uq>Nl^6鋏0E C|sXkpUyhacEƱl"~lޤ voFa0 ;W#̜Ƌg/Q 6Nx;1 & *e"n*@.5ci#c1/`0,A &+Zq[m0[N4bL@pn.`0@ˇHnVq[U^my̎Faή4waI `0!iUGf[;[]3Sǵ4b +\7D'U`0Ak?-(qԘ8QuhrNFa>Wf"TB0 8Rdp( zn'aQiZvKmr1x,D?nwn͢;p16Se&%`0Ft95@[]4H?:ӈ1p8l"hN`BruS5&ngN4ZR(Ei8U;p'4  ]zU9zЙuܶ43Uv0Ӿ5r  mzS\*!sjEFi,Vj且`0@ 9*0@)K{b<=^';m  :x Xqr9;mܳNA֙u &`9W*kUXp潍ԕyvv vܑ(%85K͚Yy< D#)J19z֙񸝛]c&  1麎SO c^Qs-mN`9m7\3G?m v]eN` Qi%F^GoWQj-N`%3NBl|U/2AۼΚTTK"Ӯb`t`0L$ˏ:ڼ?`E!/ q_'N;iDžlhUFf^ܸkZR3JvሕIaLj!Ъ(U7.SH&i"W[1mo91=nLmk{}|$2. 9}73d6#62ڷVĄ]nVy=S}56YU'qǽJ ;UsnLx/; 騆xPEgJN6-o/'*T;AIn\$:q$F9 ZD) @ ]0BkV+HCD s 6Yԋ[G +yMdC۱͔x{Y-" ?"J#tXQ{;m -iTuZJNߧNY$S@2|{.'=^;i0 ZwQlڼG K"ݫ-\KMm\iCgWa"D'Pw5vCjǗyed<"W4Hl$_\Nj1 "B_mACx}bYx5DTu#Kdr-\)-[pf F@cJ<M.Eܯ(KזIvJXwɉ2/XH%M%v8p̺;W(؟.U%^F&ʉ Ԓ}'  mWI/qϐuaݽԲ+(7 @g?X dMq7LfD &.Lk'ҚM Pdn&8Hm6[Or>[j2}F%V~oޠ]7S8u )ݩk>|~Pk&2sJ!usR I-D p f8P'ɊT4rtN=UXc!z)2U$1;[쪆k)]J bV<˵sS@[uӇAKF82)J^0 FGCVvlsr7`U51sY=(d;'/1@gϨ} s-?NG+2OG|: [YcJx՝܆j<n!n#Ts$s0Ûy3b$"Ϛ,Lr:^j_#t񿿫wxi{mwfA f?.p0?.B"hi0`* b!)Fqi_t].H%g4;mL1p7 Y Ue.o_[ï^H JgSv?R*۪?.4/GǺ\;~zfB D?r0-+gM&䙖LeI7ǵΪk7¾`Ҫ!GJߕMYi+:vZ$.W,P %~3| _B6;vich,"7[ɫF41t5 IK0Oա;m ̼iG[uc;xor0N_|ٗӔOȀXbsf=L餄WMъJ<"npQ/]<}UFqc"Jg9}mLCQߔvvJ0wVφTšΰeRH9v4DY봜"1edX ^ε; &$Y1(m%y9tW@_YNzPNC/[鸩(qSbPe@Q7%se΀^[*t2"|b#c ZF=g{vw޷,w{漽Lڢr1ﯫ}gXUS WŠ6gUa[ s }yMN o-&NϺi݌ O;YkBI uUN&;cO8j ٠s .pjØ |Nl4<\d{vڑ=x^OL*&Æ5wj5f(fk%9Q{8.FO 9 cYg g4oX(?(W~ILE&J5c`i{  焾Ӷ}HO ;o, aʢ;9tύ+6vءIc]֨L8ppP{?t6?ahp#ZJsc\~oky=2~a"I ɐ^&WW72} h"1 ]k+i9mA eܮjb[pډ % 1&UGkM:(oju>im11< :>mjk-}A핰1NZ {4މzƁ;MًuX=2 I~~רONLxڦ-nk~5u+elFw{d7I綐vL~zH9lį7I M&ҾoƤI̧f!}`V2 ;͸ KUM.,_fS7!';e``"$_Q0 {lh ?B v5T/)֕zO II$ vݺY]9!L BS"-ApXێ`2xllVcR! h:vO_8j>ug`8K&G纕¤y}&|8^?_}lҶp-SPǣmzx AD9mTK(%rn;{7pwlh8ymݜTL=M1>b׻3~_qZoVg({uT:׶JwfVE'yahQ|9/B6齗8Vdhѐ4E_& X^xjTgj35^ !0 bėwP_8!NCaÄNkt_cL$H% Whި"B۫<3c+͝fx {ڳq,zkED/6\0r2QJEi9/:wLl Fw~P<{Oq͚)<:%0>?{w'ULl'K7!H) GHX(i!tJt)v7-;=Λ=﹓]3{ω_X~9ƪ0%s̫/h7S58˷ @T6:=&Sol!V0GM-S-'KEhYؖ.C`!6!Aw72up#̉uBE")J.(S9ZbՂT ;0woC +mZԔ@F2hZ] l"5^ȅsj\C˚ݞU* u*S})tj(2!޷-x.1],[_r%gC6yyݯ ~jmR{ZzyX{(Ҿx1"^KdwYIl`v(M/gEp^!m۲6 5[rJ_ƇßO~b>؄+)GlKmExD𮎶QCjy3_~R{85^fS׿MEo')uxU(N;[&h\t4h1ep%'|lu'6x׾&pY9z]ł!M[{\ʅ>,jË$)l7T*)9v큦 h7 &{v:/vǬR ;ōg1XgR Ck" fQ@ּs?CAd~_0!}GhkܜweteF8O횺?z)v6,i518:ͧU7GiGU<<=)H…7:NQRWaזk0 \|Z5E`+8hVc ݀ Gߨ1`#OaڼW 8QOfÕBůC #!%S]gXgcBAj99_a˒`J@x/_s]SԶFMqPojCz'5T<!ߠvf| IDATwM[xж+`ϯn!Mמch)%Y@m#/4n=uZG.7|j H$8۝e<{ɰ`rk gtWS|{G2>n#lݾ#8,]u7LКx-|.u1r!/N||,xo]@ȱ̔[z3G S4}}D^7?mmIf'C&vR6#W0uм>SNAsG4i<](x:/TN#J㥌G|K )_Pqdu9?  I4E/Np{vabpcז,[NP/"1mոgmIЦ!bGmbܜ8M<7fÿmϤ$bdk}Ўg=;bS˲ddt[[832+i#@y9[p1_cu72ѡ (?[0)F3 Ո3.9 ɀ }``{>'IF;3ȢHML|cfTvS6mAp^"k5zmtz퍟/bg΃R9Ocp?Q9' Lwm u~o`*i|_*WKHFݿȁ[5kpYזx_әm̊NETs7裡%*(0ogqM\g;7LaGR~ʁ{{Vt4Yd.ߪi#x}˯Y;' px[a=c@}%Yy&3?vm%cRZmc/̭.ݡ^[_/OVC{9m8 x>"tvv<"h9A:C$4Vss͔.g oӼ|d ݘ/XĉQYLi.`s5=?dql\;Z?4ݳM*5ǀ,p-&"*U:io˚ Mo-V,o/?O|T&-#|LhR?o꓌]U{\_hG n.7vd ĩ ,2f{'p?+\lRFOf4QeadUARN2ǙM'h7in¬+8Y1=A懒T>zI&αx&ڼ8_a!%x{@ {A`[ĠEº>oއ3?#awiaGyҪ_?jer͚\泌P>'8#F6D[\bjN/ܵkT?‰s>WB7ag*==#v;: իT^k{[o/`G@&2ˊo.QZQwVaFJC*nqx̓(o(5Q|8])@x:~mYod fSlYdNj3#S6"Ba۷_}™S.ݵŀ /S Wv̝2:iiW>qזQ^pC:y\25d1G`J˿O ~:ZcUGzn&Auxusӷ\wÄD"f-=t$ssւd-yzUO&ʛ%vq.Zd󵾨 H`OϾ dlq\~eZWս\97OP5EYKރCLvhjgvR!-z[wm-`o2v;džf x<$| gK 6m"WbӜ fȨ} [i=:2.5i&ܸsQҨU{h|W7CmSqZ?גba0-ҙFrP^AKC߂';X=nc!n=22nrvAJ}u*3|]xv3hu7X^K&lʲDAavz0B,\ŶLCDyvjV _-]Qf=EnPUlg#XRW_W+DkxMyoۯDJN+b)?Ϩ^1ePlLג=j ~l]^fk{v K*o ~AVO^uWd;;iH}.DsXM7\R"hwrtո55LWzv/E]Ժ%09u4%)]Y}xfk:nhu'|6@ֻ4c]c_baAVc9 \tƖ>_u7,"%597\8װsptVhVju:f4ٺTP((<ϜgncJt/B\d;;DjjvQSއ+Jm SA3y]VgZ._2t|>ztlGwuxS}d碲:rf*nݸ UOk搔]۷1@$xv!OEU~e7(EJ* s3UC=h́u / 𗝻M܌ c &X &+Z2{YM&v% ߗ7{C& eHP6O1j)S|5l߽1δhfD#5wlZ?e${WN8bG洦LJ`gVmӖ_6YW|Q\f' \A6-5rr /[њ'zVMn_FJU틉hNw9gaש!yg ZpǦ)xb|Ck" m6CW>}`BINũ!CMwFqFrbc'A2;Hy;י*uPƏmG!wjӭkJo"|*Pz^l~gd[6ѱ+pp' ^t~8[U b:ARz]/4y/aFQưt)O}#26p2(5 ovE" Lwgv"[.p!DP$s_XMV|m J+ReٸQ՜vb~lA0E|h-v 1~|r! J+ߢH w\Oт tﰺKjgiL枲iflT9h٩<9B)ɹz@lh#Yګt@'ob5WOBg(!ƟaR(Į @.oBմu҅Fou?߾N֒oõ5o675:TI#9L}vXF jvO$B &u?BǪ=VB#YB< :'JNgnrN`5 \yWJimm):{6dnd0O D[B^}B?yrt)'M5-^GGt_s -#9<b E\ۼdMnze[AdnW  3It{tt\!>mnڦkkȫa6ks~ ytQڽ6\ \!ga6N9\!D߉oț+*]paf:h-> o{:/+~ R/MI*9?X]k\!Gʠ!!<8Sr;3uJ](3B/7o@q/?`Fޣm_@((39mOy.nAV-x؁l۝mFFѕ:}@k2h#OS aFyU2> 5 !*2*("pA!T>Ay ,RDrV)W=J]V)i*mKZӪ\!{JqHZܓej9F}8}@^B^eJc}4JgA< O+s|Pv4 !y9tw[7uoKtXwyCmҖ"/M$P3aRr 9u\?FQ6 fzj&Bg_L$*|!h.жd\y/4mo+d3!kuv?- w$?z|x@.zCq*`)32T2B!_җ[w-k?]5Hx`D-^8Fp8`'k,yFgB4@ F*^޺q(`+p !ĮtPߩq:2ˡ=LF Pi2Da@5,v쥗BY>켗lWE6 ɚ~l0H‡"cTD 9盿Yj$-\u&^VJ_is~ j6~;uBRB%AXeK.!#=*u@N|~UuZsD["hrRfFy \c t C:jmU1upDŀ}?S7l4Bx@!sLBRmUw/}6.j]aF&E["fەd gsehbMM_W**XىB\ƑD5j6'A{̓q :-Е0 B3ە0/%i QDh91tʢab"Wil};MK/~%\,#Q ؑrv!׫$dZx I #dn=1bـWvA5BS3XǪݚek [<иuf6X'D|oBh-fUN ĥ^l~kl L9B6ma$Ořx5)ܳ6UDduf,]Sww!Jfb&`M[FvΏP?bـDkLc~B"EH r$ˈ~2*[o08z\}B+dUl궮H9lr1\އՀBu $˒FgS*J9 Vaj@"Dp) b5|{Z8UbԏE!Ğ"8g 5q%{eB!"J @Mi!׉R9bM$Kșe !h#mZz) |%v\3 IqBާ};]۵zp/ z6G!zgl1Kk=8lB"COmڣ,]rTG.+U: ,Bu=+ ITþ}ՠQM)CHx=C!8Ks ~BVƑX B/swp!SaGI\j !/\ȇl-S.e]^2@ze/ [ sBޤM:SSP>H{z%@1nB!wrȬB-7\!TPdZC\Kbq4L#0%BRehYow̻s0rIv6T!P-Nz!ƑSLL]s:B91Rv?ygVE4 #, !mjL>Jg`'ɇbԅB[Q~bbiUXo9$`w\u!ꅊ\}eC^: ̑0@# 1Kca6G!0\{#7V NUhQ0i By R3%՗gkznn3 pBI㏲M?Ƒ] 4mgP`3(>e/u}RCZ7Roi:G!<(;!GWSoG6lS d !<ST( h(zȦEgBG0?bҗO%#p6B DnGe>Lz* !ZEegk!X/\ B>^F!OE>Ir\-;|0y #\ ءRBuPektybfƑYBȇ ȵl!Lc6䵨U N lq\&&hF4}Kqd J!™7l|Z6IrNa_wi 1DrB4z'gkdC4 0;KǀB>V !V hYo6BeOg$mHqa1`#Pi''J IDATv_CaG)q!Ć^;~ :)׽,uܪvJ&\:ۼ)q*ejF* k)]ۯ#Kg#l6_BSӵϧ żGTNP=a;J\,`3!ܣUNĪ=\CgӋX!"7QSއJTË$ !:>J)Kh^JW P9A)l r8%Bލ,EƒL:N]ȼSqN#L/`ejvR逍S!ڕѧteQGoWF JG8%B>A*ATɹt캟^ 1!8%BG3N,8gB1ږ1JSĴڅ-; #l6Bk2!ZqBo0]Ⱥ35*'qNlB z\~B!I`C: Uҵ8l !Jh%qg_LTw+/I$>Ƨv n)!f_'L^6GȖJU>G 7C/UUG/#4T|1 oaF0~-pyWvs@r@ecNCZOGO^SޛI\z3M#![oQr?"Tj >3iHK%=,٤=]A>N`pJg_癟ߥ-䅌A>VVOgq#Ek?Y'G$y?wٱy8; śV 'aFp.OQ<:XSf A]ʙO| VȀ ZxТ& i no_nu,+TVqJ]ݙ32?nv; ~2wՃ|*f35=㻹r\wSÂ>7{S::mZcWszh"3y Gw`st:Zgr"0FP `tWYz12ۯtOn겛8rL(+{CcL7[;4r gܻ1Ǧ0 WRAXt!Vh}*IGdE ꍿ|a|>{.8L#{/Jn0~H as+{m`]aL/әHj<S#G*QMU{2N&G}5Y7loj_bM퍴5EbD͐@_ KVDH`'{㥖r5t;dwdAS{@yNerhmw6#҄ i+1GWh^d=§V{ԄSn ,2 sIz&P'm),ez"T]@FՎ Jg"Bnja&}*u ȵQ/Wf4m LU;&*SWT~st';v5eEiJs" u$aqlīXlw\\"1zawT~RqgIۡ zhqj/\/ӥ\cky@жv2]Mn[E6&@{ꖓ;XXVvKk"ľ>w &Y6@&3;2CAÅݙ \uQ (5cr Z%rb~lʲwsk',x5IBrQ7I3&Ȩtg/}؁Gn/)_e-6BwPBߧ󾵺zΦ4b4{ڔcεSHUFW=Ե4`6Bv!Ȯy "d؎,`7:iVe?bt]F1v,Wt˞VdOETs&{Z#; UJcDe\jww07oVǀ ֬:0ܿZ(mCm!Kx \"P!:+E2Ci_N OJ[r8L\>lvB w0jU{d[%+51>vΚٴ=IB LJm" @^8Zv^L'0)*e2/!T T> ~@rV4] rlJS8&OQ$)uewAG:0`#dc` J<mpt \h*cZ dydrQwb áˮxHYwP_-O8c0`#6M8ScMPcRƆ5q1`#dAP?1L~%!d|%ͩFPf/XtY9΍-KZ \.kgy|9(`e@L⍇$©9UܾԵg:_2Qa:a߂H/Ll8i$rٕ+6rt YJ$*OeXr E 3gW쬸Lzu7|N7$hq*(6j\H`;!veN'tc ')BM/bۜSKk"5˵k㷟hzLGu0`#jƅgfm 站Bg𪀭i YQ2R RD2Oz3] ?-kI`1h[Ǒ`9+%*a,12B&bUˈཾм]##Th%sU0ʵTdv,e ̩)gG$j*nVAũNk6 >~)^yh(+sW2cJǯx1h/bvzh8/>5k:8YG_ 9PS|/i qT@f} ՍH:)"7M`BOx4DԶM.ɵuBQUǮ izxCBƇL;h<8sە1am*ھkK6R»nj_6rY^dvEZ6ΌV/RNY ZNmyx =:V%M@A {[n-`Ѿ=HHq`aOxCUxƚ_gl9(V:zQr"y<W. ȚJu*ncsGwfj7_NY|.Dw_4-~ *EB3X~@^^.&e2M2ldIX X'-Dă_HD}s'K>_({!eAZ7P!iA&Hϒ%צ$iLp6SOlX jgR ]@v*/so&Y|n•/(`dwk[{v"Ħcd3^ ,8=?-2-~KW!=`1J8uӵgָZU !{ĚkLʧaws   NT]p;.HpSݿ0]5L@+޶W9۱k}[əQ&ĕ8(ޅ*'JG6 2 3n /z‚dMʔQ{W;)Y-cǚ}Nkˎw-vѩKω೰)p1cNvOl\!-RlI},)唈fpl;bh6 hg~NnKo 9^կ IM(n¾̚%#[Eep$m@r3 0`#d/V0BG9xw4ϝCqpvBґ]du+wdX ( `KΗEAd=0A[tTfw€ѨrU ns7#۾j <Ϊ]gr`F /dҥ]Lv>=fxrlq*~>"r,@cᯏ#!^j6=~u}Q@[vV*qn 2 ؿ+$&8V6\"҂۴ ~4ny߻όgrc/^sNw4XGܲڜƿO7z6z~sӂ5 \wىj簝뜦4@-ދt^\{;%8x) `c!EQG|.`{X WD[W.P5VKq%ásπVTB5{W)AӺ#6ށI-J9;uuh ~Ȍm܋ ԡۨ"[d;3 WY_7̂EoWE鲌~ni JΩMB_ 2&9GX_jW>}%' ד]19):XsG$.AZl,Gun(ͯ^6Ҿ" )Pb:-rg;'97/Hk,D9Sp?~?<ږ,`<?s~2,4r ;i D>7mJgAZq~tZɧO\|1}rS[arH)UEƾg&\;2Mg,x[^mTǀFd}>Q. xʾe6Y.tW\!xZsWFK螊QڑzRqI*Xk+]頲\.B @^]Mn|3ļuaPѯ$pܭhk6'd?WRX_ mj܈_7TsW899^?P^A2#Opޕv_cW+ȞvVÀ  IDATJU tԵЎ~v,rs1@[ 8m m'%a^kRSQTcVwl u%cXh.+dR&s$@F*Yi 68mb~싕녊~fAe6F[ƸvFi4 -^ ~jaIJdvnK{3slC]>E#٫սiJSJY{By6EnF"[׿@CE?n`%`w,bℑr5 #M(ɼ͇alzqɃvuO/W: ,٠9ߎ:ǯJpWe;Wd,l9,ؕp Ȓso%oe'zf|}&+:j;OqB\HHC4 Z×%sG83 li^ԩj&7xs:Z]4XG*gX$aDSBFJmdazVӀhG$V%aeY&μ\=fl)`ᴚGa]jM(;&@͂;w19uTai&YfK q[VwY/*.rUl n{49wu}˦.dr: Ae ddq(^>_Pb"d|j Xl 6oK:hك E rnw~+pH{Pxci]sG}P[/V̜ӗ>wͺr~ ; e]KѼDkhO:ZB7>Q i#ĕnC&Z2զv̶#lہ.@n{ֺݙ%( ή|=Vi92JjOmMGwI?YSxZ.*Jο%\, opYbak ÖqEstp"[a8|~bqR;?|}cq$nmv]OoE_)̗S3Nh nלJ>Mʡ ,D)4U֩(bF$ K3?,ȊM\,в̂*L&jƀg-`]M->Y^8fwz^ݹ 'VJdQg-2&5EqBX7b ,M#=|[pHt8{靳áx'y- !OΆkGYOt(!?ٓU"utq$t)s@;wĥݾ,IaJ-zxu hT6s?ȏϒӗd2#ְ Ώ kSfխ=;:֎5-x&22 nkd}쪊Y΅S+LϚ|A&$Y&&nw6a?CbG;tv by@"λK*S~N ۑmg[Lx;d5]IG b XZ2YiuZ8T-c##3Ѝ@X'e,W{/h ><1?Vmjdu'u80~J<(sݿX\G[Ւ|ͤ=?J?F}nT氅ӸITEVԓ3܍Tmt He/ipSz[>l`7'p)tֳ̿KYi !W:vmE-Bk>!Npm~&Ҁ,2X/8鋽<-X_L7N]v/%_%Ҷ]zbF!=#f/Y'+x(h7E`Hے~w`FYpr_fk93 'IH̡3'\I!8FR |Ok1;, Bi-1E];/u >ܗKx$h[eGೝ`JB3vmJ/6f?Sqקx)iZphO7 {~Z{b̹{7]٦:7~ {ͤ8XNs^˘#I= ܋2[2R42HAB9_gŸҝoE[:x.^YѿLkBR&/`y\08mwDˎ1`#cEh A*2vbDezb`Y]]jt`rj_A^vѮ ?*BFnq牆9KAbM*.G#s[B2Mghz0ݍ5彩5^ =a[g7|z +sraɌX &r 5Q" ~iɬS9e*T ̞\6KHQJå#l ygM&}7JRnzӷ`xIБj -I4x+]6 Iw4^\vj/[ajY䙦/Xn~yuTCr~sR]rt@0Q6x-[`ZEMv;YS? 7zerqz'Jzko_ZX*qGߨ0"e"im@ ݻj0/ignمuIҗȚWUBPr5|q%Fu+}r_HSRaט(U\]>J)q^/ß?zjި>,sֿQ9a=jk 6jV/-w%8)r{d=/­uۮ&UԏGJ|M u럿j Zwv~65n݁_Ka_5l1;2< X5Tld={R.aط ~O5jڨ XJ֒ä9{\&!Ǫ-el[d,WqSDiH5C^9@xTz{P^ y'f"eMd>Leno/(If,\ׯ+lM?3gH6}}1`=]roپ {hz:VuSi`T`LβCϾGdltl$(m+pr.Q=H.9d3쯚 .\d$6n#q^svqtu//IadY;7h?vkڮGl}G9dc6 oApx[aJ]21Ҭ].oRY?ٓov3@vr">mxVx dpΥe%4L0 'G 9zviO`]0_&t:=|pNcj;JL]/,, <0wky}"z\=fTF%䍰\:!+RM?q +7noByYeᵅ-54+Rc*nJ}d;b9N-7]d6zYM}Z?|, #,;^l\޵e=b۲5BM!,$/I2BMd1'CzRhPY k̄5k X6S“,sM}G׍jИsx{FcEy* #mxT&m;z}P.ǔaP32hդ z8u] Ъ9t22'Z\M\r6)O.j+V;33M,B\́ -<|99<ɣD-|/%Ƞ•?92`7:h(d.XF)lO?L*)?㓥p-[& YiOL<9Pݽ3W70j=tǍm~{&(n6 muˏ]tgœ9/X괩gP4w7+r^'cF6 G͋΃bDZߙta$/R-N~_J>dXǛ}-uQ[Xٸ_OO=3ū-;v҇_|Mg}vW͸Odv+$)iwf4[Cu"od`#:u;&U'oYv}=?/zu^fݫ[؝uSMw;^yƏNsLP3b`L߇O2{BRrwCAȮd]%yp"铦Î'߿:i5oV˫_NҼ*e+i픛Eӏ;莇oW|_m;wӰA̽qt"%Wo{HƬ)f\S;wϹ)$3+;.P0R57~ʮ 0ЦCL4uݳ;E"B1>/u֓f+mvj_L^|<8oj|ϻX֨h }폤j駏㻌d{Lqaw޺1\[J> fƣ4mlٜnU09Kn Yk !;a瘸f҂_KOSJ5kli1gIeɿ,Y~nbRSЙ3I\2+Ncc؉+o7YALB#2TvIf:’bj_|${˰vCo(53jnKӵ^mImGݾlk 8隋@Il6 8ܗfӴ{Gdvͷw]x"uH]LBXbÎUltKY'vѵ7qf Þy-/MyIk^}N*G7L~8w;Gy!b1wD~$90ęTUng#]wE2W z6Z2(8'Lmw\ ܩ7vPλ~8Ϝy^@&VU\JߓTnB _`DҘx)&waK('Ϥ/~EsΚy䮛[-zLw0Jlb's&qC:M4&jaL=MMMn[s)+r% 岉 :TP,A TLuء`_v5xpdնFll6;DLs{M˳ה/eeGݩ|],=p"It7U$agbÎGl|2)_t(_ޤ"JJW$i~^i#2lnRfמW:|"4n0zʅƊ:JKIn|.mRIDuMwBIfϟd[G/ maHfޝ*rL^4]CDﰍa5.:>6KV'˽3a,=oa 7)֤s 8'^ݕE{M;Hsp~I/MqjvQɰ"uxų+l3g/߽"I_ZM>h,>\t▨<eg8ĊJ!n7I`S4/cRfm; OVduΧE#wnm㎞5yg2D};W )͐::[=~<ÀvJ˔zKO [r#P v?2eLzJzvųΌaURcݸM6mi7͛+Uh1-S4]*Ɂ]]8q("mgpI so^zA_Wč2%7A=} 㯾mZZj } R>~Υ Evahٴu;قeMPߞi[D_Z.nh֬69kkwa>^?i"]Q]2m׽.˹ZV%i涸K=tGc궶׫؂_VNY I e4ERi?QcT;RIh6'_93%^q$eÎ?s6}[Z^A{eB Ӏe?' H}L3$90DE\u}v56}=ަr2i4{} R]S׆|z,x)tзv WkY@.J_Y&l˒YW~>OFMw=2+n7=f?9ȤSοĬ&ٗy:>-V=칹gxt?fs[ko;5H[7 eY@,,E7z9b\/Þ] P73řӚ%?ƛ{('[_|Jg-n2fiպ mo#M5vss@^d6i5a}yO2-7M.WT6lپ3ĊKo_7H UA:lH:l3t'~l6:W͜şkև}E3M߼]ofo (Eo0ֳR`_ gw̍;`vD'.nAM>VR ?Јq8[N$+9mJ Mr.YX$Aun'uYFuxn<9psQMKY_52R@T5;g Ʊ{HRlȱ@}H~_%*HV9MYEw-P-9iK4[f6uHk'j WcШqv0v q^'v6qdDUqpƹ@6CNN/OH{ k[Vv[ IDATv2SlY휮IүO4ayYs$9R$y+sv24UzΌ[k}tLvn>+\:w-1 fƪtRWͲ"h=(CA91 z^B`ծQ.]ղ1*tv4MDe-gٝ$?$6V]1Z[XSLG'iVXu}~45+KduΧ-Ҹ⮇u>)ʁf7l8"XJxģ8rجov反q+chdV-qEz`6YM.#@*^w{emR/aD{;;9>Ю?."$SçK~H,=[}eM$EvvO@-vأtfc 0?dtUΞ9!Nvٔ}Jbͩ(j َ3F5 вJbHspH8#&L3gV !ns⮑c蒭4)a࢒}u t9'wռL8 Z 5-ʼnK~IVQT=G j"vj~ӯﺁlbsL b#JSR:(0^5I:V;9g[T.dނ!c%Wm*m&\u*אW uIBGBTQkI>umW&{Z8@v؃ҵ)0S WO8@ZmvӵΔ#8@-d͐)y-gqS}^{^y;:{h{}g'&Z#tkNKNYc#sZTyH\IĭQ:^e1[ӔdFEp ZU@hMW:WHcأu/(̑:h}3s dO!uاPjy1y/,vu J >")ߪUW:PV]w,-\ҥ ~*5-CsL:c:{F&eܴ}$brgmJ%)KVݼy^ 3'w3\t"tX]E; D1)Wش/qqi W?jamީ 14l{S:B-h*t ͛0%:lP:y63t.C@z~wE7ڕ  aJXU?QJ/\cyyS:_6qZS8‘҇.^t _pϏU:p$lm/O@6Q:p%dt }K w쉯)@͛)(kO?+5BXBpmڒ!^$\?)Ćmǝ$Y+JuRqmi⋫>MB;J\JcJQ[t{ $:l~A8pvݖ.qݸwp $:lW46~r9S8.nö@"RM,q' aKa8TF v_|B ;lnTvJq\)$\[kG$:lD 2lxx62l/찥%sMRa0 a Hu Oƭ5 my甎ƒ  ;l?R' a( 6āѧ{?p*^| p| p 7x xux~E >N2g;P`^^lN/ytPJKҪR^֒lzS'6Ǥ^A{ΚJ?=T<|n'ƶ~Z,H,Ꙣwl#A8)dw|ᅢz(%ϛzghRܞYXKիv+"tp%N1=68 ШCnG{A"@ y]>α dK}.p<_jx5}+pteNE^#Y:Ǣ1R j݁Ow4<~X כ5 c I]df R@=Xc_L8xYۮt\d,^v>.}xU6L;/qdW:.-QM\`wc<&@Y]95e،ƫFfi_Q:& QK7!D¶ c8?N/gԛ.c:5mS:.PNT:f8@<*w>~Syft7\5:[1d\4^Y듪^3j)ȣI2ϥl #\=uu-W~~uMR/Q:&VF zf}Z8gx ci+^\깒V0T*ՁG*:֠U|A%~F19+[5~7yo򞖞|sa~֏#qD^k-q[szo ?k|m1yE1@t/*Zȅ215>*R#38ɚ| #/|/Vn[͙Xu;kӵtpIQ:&h6ѕ.hMNE5 ͨQ8&z:.!wxnv_@|Ke][ֹ$ig>E[I2Ϝh ziɬER  6ÏtCev!s`ۺ CDư"i]%sO.m땎 Q-4`" IՈ NE)_W*6$mcsYUE'8+.${E+$kGrT D<ȉHl=y@]cJ6w,Esd[ˑCmeʽH8v҉S >BZԿ/KYѶRg 7d{=S?\tp)U:&hYIg}#&lݡ-cѪU[#k]+@}TJ{[w-K<::jN7IH(R )uzPдYst8H/q_s9#vƫHmШhx&l8(?M+߳jv;XW'yT5t,U̎dbb#q*9 lm +R6xO}q ˈ,#Ix:2"¶yӣ7iLkJБd׸No ]`9rr <,f#۬"]613 * @GR]d:Ql"9[]ro\V#_V tcBJKART?5>[S:mNv'( @ ).uHH#FEfT8'UCV5-+_ڻmϭwv$cg!&ْlIzV]FsS:xf /#I6t=S4٬eegT{[wk_`6@dL*$0U<~?VS]M ] dMT  rr(P;~*5omq~xnoJoBH*`$7}nҐޯiٳiШqGx8Ѩlm8>{{ rYcE&Ev(ha@1jT4`/о{|'SJk6$%9:ZR?ˊֽt,$s,zfb=6$.VEcY]V:}LJ+ZLf0]&.Dm458]ұĂc SȄ$:Rҡ-}̮~iϕ@i-&[k}FhTVOfg3;Eұ()h]T= EhdVOF716$IZT9@3R  ?(f#65ڐ&rNgzd.$C3u"~mFd}MX4a%gl ihhGۜql9cS$ *$Z%$ڃ3t2~Ow4< ab'J fIv?H0 lGӵʫt(![qo7N?t,rhd: Bɬ!oFߥt,Rkd׺r 9@rD6VOcN6}rWÍJ IG$֩1 <ˁD{SM$ڿsiFaұHY]'H6F"&*F/ct,R2]G '؈6O6U{=ƾBÒl?Oz7%SHڋCSTWm?nKˑCx$ȕF cDIY:<~m'ޕ&T K>[$-eh{エyrw7hÓl//TqlcK2iY[PBo8li,R!vxc&1x٘lJ*EFԘ uN:ZUJ_pɎ6TNDc"ujbmX6U_KxO{IhI[w{$[ Ė\ /_DžxϮ/ LpԠtL$٘.ȌP+t8agmY󄮆ۧP:HLʌdc8@LR #tT8ѷ{\},cF4ΔzLi9f5eմCoj,?.`^ 1>TlǪ藍z?mQboĝlҴ c)|J[5ٲ$/Ij1qC *hcw-Kr(0S:%FugH\=StTn6%7fhIM\]N@| ѪU4ʝңt8[R꾜5GȾ&wEqQUIU%%%'OYğ:;u|J I GבݱVF1:舲LM7к*/:F+,g0^i-br;PYn]KsZ<.',6`Pcp@k1Ь>dPGoO2::{KH?,s@*ROF,Xj,iQQ:XVU9kMRSu/Z`*o~C#;4եEkJ\N;_S>CetN&]6ޣ+UݨvyIYkԅdzx1]z|fzx'5z$>:WO5?|^nN5p/km6(SXY=@ zi2֦w3^?q9-b-٘.ަkQ]eat(kYykNtȁ46'_| IDATߕ}I-uRymh7UB>ΙlZj` SS{B]ߵBbJ֎Y5@Vׯg_ֽtj*\J%7xe"oq,XY9JlLo +)/IYdI&@8!ɽo!ԇ%m|4;.fE\bk1&Ҷ,Y; qzc.zLH:l/6io6_M-/e͠VNa;(]1IǓ v|ъ2EV|]JIL;>}kcDM>CH(n(9=[PFW Cg&N{:fs5ӻ8pbS-3J%'C}^Gm;ɍ:d'Hs I6Km[ΆXSTY׎7eթw-?ob뉙˔5boX q2vmw TW]t jP:s;g=ӸN)coϗ9QU bk4fg}Z/ߒL!6Scg |ӫU4&V$mC|֕59V-.\yjwÕJђI[$S Nݯ]3/Ma}vf~X޷kưbjdedSf.!6M.?dRiz{9KJXk?@qj$p/1jNE 3uvjg|J0b79l^oM~:26ܢSSW u*;6Ξol{_`U\F_@g]#IrG;TeKZ:JgN2IO7\C%V0p5 bk4PGk ~JSqѣB\vj @"aQXkZ*,W0ֿ}dlM:[O Ѵyt4&'#ϕ͟R8r 7ePr/a;?S#V0?iԭLCưbj%&.gWW[?a_Yʬ3/y$ +G7uqx86y:)kg$Mb[a뢣{6QQ}VlLNΙbvZYϊ]եVLm=[i]}\QvG;_"ԇn CrLB"+uí}\'6Ƥ茉&:wR-S[(FWze~cW!ILE0CL}~˝*:XM}Z^Nw@ѪUtH#we/:a{u@56d@Te gL%.Ӈ.RmK7|-eN1ғ-9bc9k.$J~]fiU~j,H j8.ػN~eǚj .󑽁'٬DW +bcdi葋RG [D\`3TH^+S;||7k+]ŖsSWFY6$chv–_WzTD{@u*Ô|-љVPU-V{ϰ6_mUkhēע/ioC(#*;5#L/^@g/;:ΤJ:.ꮫ\m]$팞ZL]^rډfsuTBF ɢ߭ϗ:e u==}yZ&ҨtZQ[@TJ[A;63Cc3RVSm)3 P4 )+v9u:ߞ+$$72;}oQ-Y jL4҈$[l> Ҵ??((5?_MzQ PvUGujJQ73hekȉl(q g^l]}~p@fFѤEKjREaxTk^Z#4/顊:L/Ӄ/8)HN?0 eUhDlQO^Vb]t5Qh~1r$$3H]H ;,F Z@ֽټ(BQ]}ر~Q;CQ;+8W7gǼ6gZL<ͦϰ%=^$–y}ӥo 3Ǜ(wZQ>_)- }$:y˩Cbԩ &1"rTSJޤOJS:o0 BymlhbzZ%dЅSmnկ-.^L̆EK&^POȉ$Ɏ3 Q3ؘtZϪGz:\gR.[_>Q-~FzIv$GcQQӓN_ ؖ 1qIvί1QnJ^ &禮lW ɎjvoP: Tg33rɒUhgҨ7KXX[]ؘӬbAEJZ<椧JGSNjkSN*  K`6ilV]{. &+?ۨ3Y Gl>Cd@nmsgh+/% ljԢ%OUM E{Iv;5TmNy}4PJ[?Ak8kx۾S)fϹrG:^FY]RXqO8nE%'N>R"v%dJ)yb$ԐF^C $ƈmPFR[#uт$^7Jv)@p۪i0<9Ƨjn eӝ?;y#TɚlMfKY!3r5̼4ˈ.wU'RYM{5B cLj3~c;)k:㾨Iv;)bd'ii"wGA⛋bk+uhaoԒ^'nH?}U>q_99z̈h[Rc<҄ih`F*Q *Xu3RVGCmPJGYG|*3͘(Iv|.Д nc[|ŏN  ;^qDS%\"o })T%;"..W=u~ uny8X_iXR'͛^U܅K ouUtcBZӐK?{SPBZklMx[:F*K Ɏ1(O<{ߠϗuAm3&=>]IjFثgIdeh_Ѐn^IC?=M3UAwӫ|G:wݲxUĿ}bU"jRIS;h!qdGH}yKղ/^Zֽ%%Eg ܼ҆A_{ǎ37_% l-6@wQսsR"z$G#|K*)+~2*[)ENfwD-ZL`ضWThoruݱmfh'xm`O["ɎaV,/&@ΛE:JyJ鿗Qtlzu7:ٯ fFaN1d&sd5qd 1@wQܚ:'O׼PMvк^H::\Ng~ٌ+-^aXzMhGgW{O:ڲ-*1^,1Mԉ~?5y-ַ~mi^+mt|7T^*W ,£\dǨtCo + \TXvxS؟+YU(~O>R!NMHOӾfc^mol4D3#4%i4U-PYK\BH[GW>WM嵇\=*mo/\N'Y׉:貓OOhVzceFG,~fTHnMMLC!?GkuHcTffП6ߠt(18tDpfuN G- GHŦPš)ީی&d1ыNE,6?5./z:~tQwҊ'sOK7iQ~o IDATo ;ih\?=N*~^ ?I-i]eX9oV2)?^9\by͍#u5TMiq5Qh;3gd*$1lҤ4tZ/$U.ޔVѣPP:k +h^zY]x\d8ܖϖ:ֿ{I*DqJ5K6M*o˳_BߓmiaU?&׉#3lsB{Iv$=*R *e;h'{#>FƙwE%AٍWAx[09 Ɏqlc($)94$)x"O=}T 夆f, ok {QҚ$ݖ\Vr7Cz qՠZI@?胿}Fpͪ)% ڼWKQ#2FU-GyΚKo&ҕkQkIS$S<Iӥ{W=wJO4@,\䒬#J@ƛmʩinPl އ.:>FGߝJS"εcE Gi v%8#2D}=Ŗ0 jȠJehSf"miONu+&f}d39V"#Q Ä>#t@ *M11H} yFkf4w/SDIvbETB +oUs[SJ3 Mj'yW/L=]]^<.1LIi,#/(&. w_J}oprk{yaAkcqmiFhU D4kאd{*UPVK'O:%˩e:Q\+lDZ=hÙ& ӫTSj ={ݟ+<^8`R5}~Z7yK9Nw97f&<1[Clhwn5*"p!~(lLP R_l o[ÇKؼLa/byȋ9+%e\QjŕVHjoggY$ql{q-ޚot.~x%uʨ )f^eTIvɐ j]m|CrDk2}5q|D"1Xm*J*I1Ys@&Ei}ɭ"Ԗ39'!*Mc/~$B"nQl&1JB+GF;sÒK6vlϭE,?\pOs슞^SY)t-] I6$<_NHlW0O?ZiMOOҫB`/F.yB i%*ڼ}y?uP o R O";O/"bbyU-1y|QnQ딠3kBf&s.\Ujy^:%?M3 ShycT 7¢l6Ϯ(.J(OB2[K\0RNec\bI5X]Tv!կD?_k tK|'(zZi~95n#ϗubedhlzѧ ibł;˷[s|>ꏿt]Ž+WP|Md>PS9Q_nKi]B\VL> ɞ} ht/NeR>&@vК@]  xak/1pۋ):*)½\9Ž@{\X=FZİs4jJԉx֏# '`ջ:Ѫ?YUD?hL\t2T-ZпH*Ŕ?*u#"܇Tnw ѼnGe©<_ Z72©<}^qیo?eU\a[)oa[ޱiJ]gmP24I/rX[ O-$^^b6+t<|z'F5)Φy-XO^V--!;7&r>^7:m2iuKl]&lOgب2P! GɩR ]* DP:-JNuh:MVǮ_QMӺ[<]0w΅qs 9;ׂg-XG2yfdRp t8 }3XTi+*XL xΚ\3PA5nT@5͂EhMXZIH~bJF(E}x.W{d~R>j/?KWZSH\P.l\mUvDrszgWmg,ƍ;b+X Jw_uM0>F~Jxz`}Ylg mE}>#\;~\`^BfZK{;ߎHss1%}B`nMc͟)J#Ϯ$e_VG.l[>ok/$W*wZGl vCN=Ogz6ogUii2,qh3oTVyGtk_+A-笎/w~'GN֨$\f>p*o53$a&&_Z٢7hͰl\ji"˿@f:kiCitַXk /G9,7R(Eب.[O;Ӷە"dkl6wdî YVoVΝFQYGTӮQٯşX,G$[ŵrf2S0RFߍ/%ږ=v&VٻHsftk+3,s>aT+KcCܺo<$ QCAk?cT%݆vսTz$Ҵ:=.,TJ2Fw k™t b Զz4anErկ;{Ho3_b[ԯi@k@@ncӲ?^WR 'l2f<ٌʐ4cCctSpԎg?sρO6}ūhG)M; ]?k*u^#*Ĉ<@O.ne-*E87 8]ܵUm`XZ2y4=߻т)jrYn FNxnT֛}5ۦu sGl)-й0c~NِHMNJEHihlo̢T 'y6׬M۠S 8CTRG35WZzuXJJ)֛xAZxyR~D !I|-\-<~*(Z͞B*eulТh*9;#hJ k%4}0on%W.ϭ$Q|)W,16*EFɧmeztN hH{'$>?x;),tln\* P+FzBXIȬJ=^*5Hrt ɖRj*Q-.fпhՠ1Сx{:q#nYngU7C4pY<=m}H6LBpn1IZhI>L3-αhɳy7gc=BpX }c%H6OU jOCzoK$LCEʹ)0 k%A4M\H򽵵 ;:MWso'WKl'_Q=ϕFnb}7:0OL.t_6:v}j~}ѡ}t̓܇\j_Th쾪LK2SEs^V(6lH%gl |cxw1R?Ueρ'h\zpW~2-ZwLJU˨e(t,dM,Zku!CxFStHXǦ>x(tHϑZc(4$ҴZzv%w9xoՑ&L r;+fX56Pŏ x9B%}1$lbjg|5@7p۷rk|BdnJ65gZB~*)I&'DL\0OsbP8U+n{kUzH\ݟqóc0\?z& 5n&`gz IDATtZoWҡ-h|{R|<)N[(@4k$;o5xgc2iey95T}{R,E7|K~CRٯxQ?xO}GOqe_.k79S),$8 +hhm^56(}&{^lu=vɣ^ŏ軏"HQEmFȻvDD=ӿWZf=QӦ{lxTsKB f 3f$۳DO֤3G Gڧ;iVҬOXRn'T('(0Y桽ч&Й xhV|DwPCYZbE>Ԧ46 NW-.Q\x3{(5ʡ1>-~ȻgSe[F"!R.i<= Vt^,1RRx juS9&x$=-ΥU/td$ylZoI35/Mh՗zMO4qg߃zvg v&ڰnmZf7G~48XT<-M?.&c"(@H&Q?+ٯIg+O4ux' *!ㄭwASlx݄C:3O DbX1f~QcȍlBۜɇZӋO"Qj̑Bʕ*}*+MwC41rJ8" d$\,-:faI ?D|#WP'BW~1/hߩGwqk[O{`}tVLB e4ltQM6:MQĉ'V$ɣ TH  @n=Z_Ա|=qM_ uLd?}({0ef˰us!*-O~r 6v4cdm#W+]IJ쯶r|⟒~E5DD@>N拣FB9kLt}8r<]yǨE-/NK3IC묂\B9A'J &k_1\IlOԢ=PTdD}Xqū{ !wbVV͛Ny"³^vϋi^کa]h<ӈ ج/bb-.@AѡYgcd|w\x7MB{&: tv+$ӯ=pFd3f*A`^mOH6=S˷ߤ}{gX@VuNc$z]ө8-$Kod3}>sw`Ӏԩ?4 Sq3gQrSٳ|z^xb;2`_yl6hX@=8p,d 77+D%Qy|WaBipϮTo޾Kg;y"hHnTF犠L&ںs-]'%;Jݻpm͞-̥7h]ܺo{[k4+թvTB9 qt)wA{(B[jQݚըfJ'plloIc6hI6^]>1(~1ҙ$:OGd7zfL&&oр>v4|NR޷y7nm~JeTJ횿C=:#L_mX:g*:p5~6ho6* c^4Owz|oԴ4w ZKZ{b, QZ[<+"HFGEXOXf}ٰ{t0t+Y< vb~֊R:wmh_>H5+h6OX9_ok1Aԥ]+Zrѫ{&/\ -~wK0K_Ժ[tyڶk/w@hZ8e˝߼/dj?;81z`=s%-nmk7y@8I))Զz4~(Zm#|WWLeڿ|RtYZ=&E\UZ&۸!}0x]y;׏^\K4>ujۂg7=y=6oXURzL?d[[sl`xX S>ynOj: ^&طE`(ߤT|qͲa>w#6SkBc?g9l fQ.~/N1bL*SϝF~WVҁ4am"m9Jr*wC©LAˑVcڂZtC?:6._d5+}MoAFS_X"[op:m-Woޢu>S-m3KoD=d|~uO[g?i(*ժǍb3 vx xXlqvU*ue;^IPyBtmgC͚BaߗUx9mk AcϥCOڵ?t;}g6z@nm# ,n/RMow&>y 8tz81}?x| ; 賙XᬕCH5s\fd;!4$?MNMQSfөsa_f}/lJ|U_EѴ L @ e )_qIKru{ָ\16ǂt 25ެv/S1dBk8O`a4`aJl v :d4&Pv$;((f;U^4uռu=:qO#-W2eź'َ\c}_]nAS)P߃Z<tcxﯵ@3>A~ұ#i<M< ΫSFE> #a8FΒll!{dIk}_D v u  Od"M# F_NPЂ)Fe[kԹ]+=LLR)ѼV7lo(z ̈́'W(Ytc-T1fcTTIvZz~l]vRG&7m$ۍ"i_҇TkE2s(ɶOMA[J_D ϿѪOfPY[ ҟ3::SȽHJZ/ m pT :C[`8-lʱoS5>I3I6/].,2UYךh:ŪV7VZ<{d׍b3ՙ;}ݺHæ0lD#*s&!ED: &`ZIZ[- 6o$;/̬h7KJN:8<> p ա㧨Ai>ԬQL8h㤎>ߚLQܣʨm] ujcbͤQ8'65 /%t9x!#|jwIUFzfWw!8g!דy7*VPl:k:C [h0I!9]$&`f+o6ѲSziPQm/JY(!LF,>Z]{l7̀aD.PtB;~_mp R*5ob5Nы#H$k:Oll1W԰Y$/y_4O[^h&k'/kWr=>hoݡE ٽuܮ,֗{̝4B)QI85?K3/spM[Q: ^{=iE] 6a$ۅCB Bq(~3Q-.z9G48go}ޟѷ'M?=;|w-_hդ1s4tC\`ߥX f[/,Tѿf~cd3mX#x~rqV԰y:{?cvG:{-6mCU+CF$B!׿v]Hg}}ު*mݹeq̧3&94ug>lTL)\,収"j+:::s2?rL4u,TN:| t0X]D[`/oF.O3& $[H5Iv)t(te )_c*H~u4 af.^NSGu~ ŊЧu !Ak-#ȭS.p+XIz E;z2dP&#_5zvxOd+|3S+7hښ,fq4;3T*勸)|ڱg?'ԶCc6j@C'MsN@g׃{vf3xED /t8N/tvC 8{K7$[L- rhv÷i߅Ehchd^Fw>TTqd[4k޸Cc3V͛ΝgE6l~s; 'hF#B@XAwtŝO{@$yi^#>ՉT39`[Ɍ5¹2Ut%hp_׼TP G=%+wy"iXܖ&ߝ *8d>POR%n,\1z>1c'~RK6eбK<~2^7 oHiABlkb6[6LHNIcy޸EׅX0.l/ST\ϙܧ_$m@{k??Iﷱᓧs|itn+DxD*Qpel1P$rA;{>qʝ =GMptL?VO,I$UG8xH6s?Leye~r^}]B2FLCs&~l4\`0RéCԿ[۸+'_~7Zz>ޮh}kw7z"d9ij{и\JGNk7ՇnlXKd6$RB957Ǿu[^ZLЮwuIL`|17#8cRI誁%[B f/Me҅ͅyPkfx:cwEIB2{zGiL:nwt9'$7nwԶ%ui׊*ct:2+z=7zs0{'+T(U5{jGQիqI+Q]OcX髕jMwQ(ϮtU:}xСإtjԼc7>Ӛ:!wG_cIMB98 IDATNYujyk껻# {{(u* j۽ݽyb G}& 7*g(vPK)c85]'ZIv{&n+$! Thd4M7Y\+L)vS%o5 v*!unjk.91WT4rڕt|n~*vDI5 |J$N4+&$t,`l h劝wt#]PҍfG.80+Xs/Ț[ϳߕ)==ggJ_دhnTigQ%Cz٦M#tmyn-->-f,Z)ɔ'TjTn=Z?NWY+\<={ VYĨ 8&WjG>$ە#V5.|t,ȟm)2uMK&]:.=zg.ҟz٠˸zJXbZRJ$,=MXUOVe*ݞy>3E2 /xz[UW,T%^7=t]x# t$Y$ߐNf۸zYyvQd_vۓ{8b29 IJ;U(3Vv]F^xrB%Ϋd@Ia)N?uV8@g" ܐܦ#0[=wzHLF۞=YL?{?Ó<.ƚdJ2.daT*'ɓRyyl_d,{rٺ':2Rީd%5ʆ:p\$oQoӾpE@@_/C\;oe Onc-O/2|,sX^xN˓GX^hm$d?oFɓ./]f{; >s{eRPR]J3I&'?3H X;ܮ5-B(쏕^274\*IoRH=^^"cqٜe5L?|Ko1ĮvjYr)ieIL0B@'RY,;Kxʑʍ,TH  ˥M5ғc{,lscx*LԨzJ|Eg;qy ƔZAKObD"1o&Sj'<[t$TѿDc/ϥlXPiDK*ʇ7Y@=%J#=-t< tY;l>ӝ*E^V [L f|馫e:sA> 'AJraˇ7 zj-^LC}T-0OY873~rI"f[`G IBUkoŨƳ텛$:KLT66\TAC5+H;ғSdGg |`Iv,ByJ~TmN)KT$ ':E.k?dMT!*-v3T%nn+%2+=<1vJB :$t,hǒn],tlac^oq&9`x/ .Tpp2$rt/';w{-w9?哩2 Ɇl?~|qmF6EUc H;W*_5%ݺT9+u[XI6`LO >0KB| cuHE}3{dτw-gЂbJ:_$s1?*)tฃmo02_б*$fǝ\89 kU5W!Νwu(y||3-/qćBd=vSkg~!t[]nBkd8}rb )tZ^ŏ  /A,fپ=С\!4:UX|lvh?1BaԦZ0WFxEX|luK]8P8uve; /t,IsSM87܏_cvH}L#oz~N"KWбx3$>$=!:Ɓm0|RR+t, Iʽ3zM7QmJȡбx+$\>I+:?oi<  :o$jƚw xPCx^ㄎ y.É~H$bH-Uvbl߯a"x׀Y2\b݊$V"3oL$_Xi[q%O:1~}D`dԤMNtZm_PW=dKot&gP%7B#S„^)aL.t JONw1A#X*t SҦ(-[ :뇨hF%l$n$:}+1}pP-d1Ugt",1}榧 7B"gScHFq I5Fl0;$i$ p?}iAY.JBMdU51$f~A!qBMdYfg.lBd_f10zH}ؒVZx6\WC6}bHui!*бKll #뺔0$ &E jH`PtB(F,(OޫB`/1 1fO$ˉ:}qNGLWC$AxTMYKM :G6!r \l6bb["XYovݳr[AjygcrN1^{BEO'D*5 /qqDj#IENDB`kind-0.27.0/site/static/logo/logo.svg000077500000000000000000001772231475376161000174320ustar00rootroot00000000000000 kind-0.27.0/site/static/mstile-150x150.png000066400000000000000000000175711475376161000177310ustar00rootroot00000000000000PNG  IHDRxgAMA a cHRMz&u0`:pQ<bKGDtIME !i:IDATxyx/[R%cs0pBH&a$lHs$fgfg&vΆgvg̓k!rpD@6dYWTӖd6|^Svۿ"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""r13}oPOK{e;槪@e!òK9q)2 %r aQK0GXQ"⚡jpO}cڙ@e8&nqi2 !DȰ*Ĺs$#kԚ>f oL_J! y@1;vNa\S[BP\P:qѦO;Kh JfQNLcb"n8"1W'zOZ"lX^bȥKoL1)pLL9PA8QM;d릍d}Zp ,(w5>9>1cG{%qwh1<1sJ &T2~sIOu\\>'SY@ \e=&,!**8uTRvAI>HBY_n1dģ=l>~@LdPZ9drY<267 J9cjXp%|1Pl'11奕)[}c(I9;Eߗ xvl -s8 0S@cbǩ 5PO>GKoZo L!!Rsl H`;%e\Q xW;;>US]ғief}c uɽ15XFP?cmNcbʠ8 n{9{ sT1wN {X\.\`[VQ ˸(o{ ږh>^L) ǰ:i\rWꒃ2Ruc{6%N}Pb28|ߣ7;rt0gao-dTWs^l)wF-9-fX[3?`U l cvQA8!h]LqviZ;XXS١l{[su Mpyϲqg{ kTqeYLQ) ǼÑ>\0$z,:Bvo 18Z89c1 WN7kq༹,7]=KGo[ +']v:xZ ˻x*ێ&=/*w_,]9[|K6] z\_j> ;l\}3Y(pLr IDܻΦW7ޙ+1(-2M+SqXpeQȒ5#/\~5K:gqK7@(6ۘt׭q07o(ea®4xYyc9FXSV܎Ko.OG6C0"mbxo?NhX哼зݹEt'Iu_Z%Oe~W↣Ceno7k-e1mNSLi|8[=~)CUK˹yC9.KV+X-~˖}y'>8O,pDj"89۹a|*ߩS;5.)]:,qS, wxDNk=L_ACcxGwӞ>+jjHB %妰"YAo{lPk,R[wEy~[{ͥgG|m|3iC9_T~˻=;vkxm,> ֒N&H9hrܞ|g\,_c(pL˓lh05ZîV{uk˪8fPY> iy|Y̩t3p7VDpaہ=iC*Mn5!$Yz󖞂%[=ϽRQ`}˵^sQk\CPY⢯5='RbD(OJ$k)ǐHTLMH}Mk8p F͂e` Cz#ģ{Gm][< D!1 I8^ƶld>ϒf|:^Ж]vֆv^_-{6L+m|ss>`1A=B̡8̚x qq\:`Qww,+y1Aиל#w( YqǕqn.)8oeⳢ'\=T>ټYUlZ:°;t {  43{`޶K+:S#V_JR85%m}c[e/Hdl~"啴i*]Z&HVI}Lqe[96q\>+;s,cW9汻@]m.h7wT;|ǝf?o.X87xBkWTR{ GFsÎTýŸ := =zC]r  3 5_w(odl&3|};4\>JrbLĩXs%uׁfM-6sbu}-\6ʢ9!烝fso~n6]x={ٸ#oAc 6Yfط uiLJ2<,Xrܰ;Hiԏ?mKt uԟyc828&?pqcѲm:];a~bò<_=ʇ{S.A%xawS}$o4m>۫-\tV77+,߿2)6VsLˢ%qgXJ%oF % u AVFr1dÅ:̉,XZ͚/<^Md6a~U׭=Wy@[ ˨u g- s05ajl|+KÃ]nzp1;?JuC?feW [*Q0E uɗfz1;1?~S}.!9{$@pg?Nf˧ȇ.)-<|ݜa<u]?PWc<[ /{ an{w7?rGZF Z"(z=rz$|f28&SsZKFtZK"/?i.<^gYhzZk)|.8oZ c |~۩-cSbk)f;mv?iqK+ύr?T;ܳ?aˎ] `Z,XĬ󆝟zC=Ьfކ^b8&&]Zi B,Y~ sYf7Ɛd{L.XoR *tD`m-˖,:n1.sZY42h|5m.P^] l;`GOM煈ǎ r }OO /0caULJUioPCщ br~p3z,^A4#޲|÷*ǡv*sɅӑG~M[oDo" i9qسO=q?tݧ1.ׁn ش'Ǯ}-ܸYK|'l%9{..b~ӕiEyL228& 胠7Ƶ2gBfǸHutBn ΟGͲ%|sxk^nL$Os>Mon5Y8)9ËW,rЂ-=H_&{$k-kλy G~=E4KcbZүy?R`QFܴXk^Ï=ɖo8Nե1\.=;vayT/YĪ5L_:3ƍ_?,]VN8d,HK3De}=rG[G,trs.tXv?h~>NY-.x!TG|c =4563( l.Oo_0חq1@PG宏'Ҝ%b"aÊ9}_,Ӟyw>|T>S)iP(yJ Y\k{. eU&noi;)XCfP+ҥ+ Q+>rheў3(*\弋H$*`[Z3>=:s#xz[F) .XxCp+̙gcMVZ&mĩ2x Fsvy~=ִҞ'5rxdž䩗t)1q;ZS I 1v Ps{i-e,^>\T~;zN^)MsпrҔ8Az8q1pB` GpBa>8ٖ,ys; uɝ3؝@}cpYi{y`VLW=WF!_  ^X`u/s H \/*b007 B8nS wt `O<)fK u_)qS_cyT̐M8mm ֞PR'eUN(p#H1N! Z &y'As'-#* 8NR}c$0H@BTiPP?}w-\^P*t9XH==u纄Fޙ>)ao;fPdj)2 K 3}SpcEqL>WAV*j'Ga&9w1ځی'CUde'd?,-6]]𝳀7&1MvvHjV> ٜyL|grۀUI|_9)pLo\g h˗|:3}δo~kֿ"3}l2T1} QsOe `ی2 Created by potrace 1.11, written by Peter Selinger 2001-2013 kind-0.27.0/site/static/site.webmanifest000066400000000000000000000006521475376161000201670ustar00rootroot00000000000000{ "name": "", "short_name": "", "icons": [ { "src": "/android-chrome-192x192.png", "sizes": "192x192", "type": "image/png" }, { "src": "/android-chrome-512x512.png", "sizes": "512x512", "type": "image/png" } ], "theme_color": "#ffffff", "background_color": "#ffffff", "display": "standalone" } kind-0.27.0/site/static/third_party/000077500000000000000000000000001475376161000173235ustar00rootroot00000000000000kind-0.27.0/site/static/third_party/GitHub-Mark-120px-plus.png000066400000000000000000000051151475376161000237360ustar00rootroot00000000000000PNG  IHDRxxPLTE_3tRNS  !"#$%&'()*+./0123456789:;<>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrsuvwxyz{|~0yIDAT_wks%|Sۦ%a`%Z \:7cnRfl,Xv#tQD9GKNv?%z~x|bM-h8>0@{ᆊ%UM<~}ܘ˞i(y:oc.iSiu*g 30X%U{Uo҂7  - u!}̷` 6ؐ&*r*i(x?-!*SFXQgHi+gţHaK"JB%(s +/:l8N k`]˽]3ADLP x.L\~4)KLs!ٳLt3*43^e琤0D!!0-g`!yd-blSηX]F[H[SZbyN XăULv!^KS Av޸v!LR8&lvۑˉXL dd6Xd  `Y /,뤠3(eÔaiJσESG|"7LFXp.nh ,:HY#nRE>ʼ:EQJLjyXS1',svPWLp)_$PEd4JR[ E^-~JC A!J1J*oCjJv0%ˡ J8(hF;CP<| `3P#Qp y)QJ ާ|>lPc,2ya(SJ)d )2ݔQJP$kx=rC#n(JQ0Bv("?Ev(`?e[V(lx>,?LYA~ˎS ]XTK:Űx J,(B=I=WA*RO'nE]dnXsw"k'h`2i+i Ӿ¨{+0Vq*fts5&,|!8}`Q &UL%90W_.3ZD99ȭ@Q%Z( ;_wLsQǨ|3\uATX^Fʁ5+?uv@.q*#e:}4 `\ LT]4VORg{w6ejHVmD: 0 o;e=0 SrBP`(Yg 2`x%{ų`.:N2F1,o}i.,|Fј«Гè&:-@7Oȧi7˨r"-n|ɡ iɡz ¨C.a! DD 5FiAJu|3uE ~D90QOE0QGTe h}T IE=HC J4(]kLd׶!m2xoGL:f`@cf ?K`QQ}vXe``1yC/pR uqRNPؓCZXc `Μsn)3PBL IENDB`kind-0.27.0/site/static/third_party/shiny_goose.png000066400000000000000000021226541475376161000223730ustar00rootroot00000000000000PNG  IHDR;DnsRGBPeXIfMM*i&9BZoiTXtXML:com.adobe.xmp 1024 948 1 e@IDATx Y~sY !9)SQcC$0ECy)`Wَ+TR)W I*U]] d@26DYfF3wz~;BBˌfOy駻wO?]JD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD HD Hgγ:5'@"$/\eF_~/޼,;;Nq!HD H7Aj.HD xFxka:裏tZEtr J!,`6.eY.t F-ڥKqgy-:Y.X|`QfY/)P@"$@"<4&@"<W#_=:_l4Ϡt'Gă2J+%|.0^F_ zKJ~"QYϦeFɚ"eXl~qpob/ ϧ*$@"0H eD H>w/_YK;w!/!uzL{uc_6/ɗ/ɓpxٙWqCym~Yz@_-ڟW|dגt#+WK3a>2!}(?6ȿy衇tCȐ$@"$gdHD H^8h<'e/eS]>.tGk{p+"__/d@/Mì=]3@r_lLU(p =ܫh8=-'Dz|rGi2-ˠ?8 m08O0>~U_UO)@"$@"E02$@"$/<o^˃?q4)3X+nfϙ_8/߂^WfF[{xK#4P|pw#AguCWRz!F?X"з PI>"2?=)# #)hX prtTN<ɫ c.k9Jy7S[Ɔ;!HD H@?YD H^pW_dmGsfc H^ՆuwuC`+^zr'^ p\:woWVfl*C"$@"F"G$@"EkpoUNa35~ge^~J<؄]B|roaiY-W (P=i?ֳ0 4:,Ґzit]JDt-j\I`6 R~[K( `xܸQ`doʈF7p7Ga,O=H/ZD HDC w3'@"BAC=]V)Ͳ ..N>Y/c=UouaQ؝zߒ*]P.mWE^nŵ_&'uº$nFj, |9M`_8j PDKG>!(N2]!8g76%>C"$@"pg p每;c9D H7̾wׯ\?ó(o0ۿɮ3o?)/&3'Z0TBoEA"~:wv5ϯbu2 b7yj u? os@oZ~=؋  o}..1z CB .XL'0+#QI?dyG޵.ln =`@АM;X|0!HD H^|oX9D H??ܻQi+Nò{ai{1Ap^g!嘃Pm_ [X啓oTs묻qCգW!䩿7U?)XШ U6TW|@}0P)w#@ J70\^ߍKHlGwi eS ~i<>UN/xbA;8{g齓b~מY€ABFa.Yk[pbMڧCL{p/!>( O|Nt|X, #?@N<nmN0g"$@"s!HD xpg{{pfvewg%n"0|GaClK(kyc rUN~C[{cC+3}KrVwVix%_`Cl]@ ч%yA# 7#].01>U._+Wn-6Om$fx`@'cb態rHEN)6~ΦqU*Zz<^L,_`]CxD"RWnWu|(s&0 Yݹ㥻ozӛ>NZJ2$@"$w<|ǣ$@"<'O8'3h`,Ge,w4 d\Ge*TVb}͏Ԩk } ssE⹵GHA(b5W~UI =0VL8r5`/_]+7Nvhگ<>LD hÆMD x.y:yl%3fOχ8/dKē "dDj\:ojg~䟹6a^:e4zژV=?"ݐ`g#@DYSCukvd~m000@0.GKi5}7cF~@a-=W?\Mƈ {Twm^;S$ 2$@"$/zO(s@"$  ݷ7R6~!psfe_K+}c`:(DP6>@KIRkpབྷmZz`% ?k`Oٶ DK7Y1@+'jl5Ͱa^?goF7AYd{vgkbLX޿FG{gO}&}5aζ<>jh0/Ɛ{e볧FlKFGDaHh@ ƀru%66z~mIW2$@"$/:onX9D HGM~hI1/!1S.87t%\Y`3`2/aK[r.<3y m9Qwh۬FZ *z @Bk=]藬@εe 6 KX[l{s;3eY% },+_ ^_ q{ڭr!!Mq K{VC>)ׅ̯~OIƽ0H/X/GHɤܸWn } hdv/A],crYp[lY i[麢e7R&)6"}@el{%힓=2% ߌٞ|_@mI6h>XЮۏTcAo%Qٓk%q zxWCly'zW!ƀr1C0N kk;k?_G@~$@"$/@r"$ #=^ؐBx.*em|@’UK$k KyXl֔%A +4EۆWƖz͋r,rF>/2Vz9g|w ;|H{0i@A9;3Fr͟^mdocHFc\.S {R]胆/C^v u5Xnw4O'쫤>(C?l .*F7dCp^ xk&`1}`ɯ.jEЗ@"$ f"$ 𝓲Wݭ2l hI!rGY]]O'6|HfTRw) T<,fy++\UCA-VrQ^YÌn}u^z%F$1flD5[r/̾7CGyc4N0K%/s3\ fv)-0-Dy_&S7'FU#r 6G@OW}6iVeєˈF7ۺQsF֫ɦ | Xࡈ֜ش.]L0<OC6,Εݣ/?u7ԙ!HD x! n}LD xa w#˦MZoqU9*&bau]??E!sg %Mqcp/il%:(G{$ R&Om٭[oYsՎ4Dne f:Bdƃ,c;&l$LWzapu!m||*۶q :$w'շ2 z@]mɿ:tϕt-eMZ@y!_u)kBq;)K.GGj0~D۾ZcCG,J f&.1`p+W,8asYβy~cc7[DD HDyD Hgᵃ<~4]u;spM.g ?-f\؏YC* zGx9$j-̳mhj^+SӲRn%Sd㐷L}(ױZr+U 1> 7): >bep75`"L3.c@w}ocD= m#"#Jk[BI UvWVL%jn j]=G# T:,ᔣ౽&G!NgӲY.]{ <Z~&@"$O'@"$/H_~;z9 Jok+W[ŭ@[I$U?tM~%rAL>^-%a&Ay9yWJ╱։^l[v+FGkQꬳIk o?P@L] ٶ͸$5͊vZ/^]?d[Ʒ#'ڰ*6C@^;1@@+Fԫ}EiڧuaPyM;S0 eھpwKO[->]j="R{Bz 3xeI@bp:o7ʵMNC`mcl?W6wwwϳٌY:f#HD x2C"$@"y!oo:8hgx#vd?WVz. oK~^WVMi?m0Bs޺-1GŪ)o][& R1~ntFZ~VچcF~hڐk6Wk8Xh%R2M|cb6lD͋*g!BJ#伝GZN?HK\Gt WcjQr.G5{y?fqJ9bI/Ϝe/˵cx[2t 䒀,?D H'k!HD x/7tkv6i ߐz.r3@ݮ%/g%AɓpSgyr:ȨӜܶ&&]u 97AVUQ֬u&e1d pd[x,:msŠG>xye??x_yC; @"$s_/فD H]ƝqFw˸Ǭ43mygVld* ϼi?[:A?MzvvMiuڶϞ 1A 0BFJ/\fɾz ю#`٪n$ԇ>됨uꈙhqҋ`^kʛvcXcA̷~ mytX]fWѷ>E/֎zA4 KZsm鰡%U8Zc,#]+` x?p2.GSӠ5fB#D H {ZφD H^|[./|ۜԧeXfͲ?%8-d]S`K$1qx @^#T4B4"ˆf9JD<Gk@"ܒ1|l:jPg5%'VIh v50 Yf r?z DW/zU@kh]Yt,aQ;@_Fo-ohV#epC1GDŌVMYpj>a[替B٧[N,)D HLD H;Cr[rY!]؏U#5ܷĿ5H%1s߇8^\Ad#]_1LӺ{D֌8M b_fIkSџ(#3dC6/be'cyblBԯJ 8(rl뛮x0{ԆLCΥV@۟0@I=lJ!Τn려4a33y"X9uI5&vWD'W/ck>tDQ J̍{h#Uto\CriUCV/iP~, {kW˵"zuƟ}÷PժiACS?M-l|K`%;e}9Og9PT r \7SuoJsKN7c>$=;pKJd@pRGL[Y)[lJl.^Ϳ!תo/R;=@gdbuŠE03+R=:/A#1@,6XEmAc|4qMӺRa%h-CX)rچT2mfQT᧞!@_`@!Yax+cվ:N W6[}euZ^ t2"Msj˭ƶ- "Gf6]CCCwM?mK 뻢!k>2X+ܼ78p\f'_}$oQ8HD@_ @"$D__v.A_v݀c?C 1M q'oe_ S`C#־`EDqolz"봾VQOR'Fi0MV^L=i쐄~Q*{ w#c,<7!z6]B M| 1o"ߗZ|B ^ۿ:4DLKl0aOmm!bZq?=Y;HDE}kWC{^/Ge0 &\% \~:=Bm!E1PD `iC/+򐁷# ydcJ%iyW?`]@{.]>1@\k2 ^c6:4b%s8ad\(kr$t"DhJ%Í f&Z=+.ğPˣ3q"YWܙ\>(_R#~ۥ/ӌy`}`z{GOa1`[oN*W6VZEz{ʙnA6,_nxPI9e#nrȫ) 9L0a KcTǯ>Y>~t>);6x@D HDsB̯T?+%@"Hxw__/ӭ285=HWNV)"/<~@QȨ%}2[z _=Brv#6\ŠW1խ-10G^Yl!F!t'v$ %]`6uwwg%LK머qw ~yψ;,97ޱ54EdCqtqUk>hR6UumlEM/g}QA&Kc21ycl mZfka{kH=mS͌6Nb^&_DW_( "ټQ't QYY eUV5:.֣W {w j kZD=+)6 |r9/~> @"$g_Ϫb '@"xxzyE&Ok%(_Il}Z@p0[T@\g$W$zȻBKz݈(צۥ2>\MHll$k pfҺJZYgn*(kqMHo )RS΄kX%ՀX ]xeJh =4 H! I;kAܘWb\*q<Hk9vhgyBy`V:N3E5ɚ=e žW[/exT-4_[=$Ut|00ٓF5úA$ܵ6]2418ȿ4-!ېV6 8 ơ T}}0;F@hϥa_}pw!ә){,mFsF"йUNFԩpohʟ.F.U/twDW2B`h@'^)}gGg ƥP@"$A4Y$@"p ?n\89)K!t0i}@Cf뙙CVpDH!aCM66$2{myH.ZXB~CfGe 򿻆=qG9nu@5}싞8HWn8/q" oz.q9>4"5C^?X<ԥbqu28zkPm7F ="@Co{Lc^CNGJ]6vƞVm%].1ƨ%úOm s9 g^P~dbn<~cH \hg{}v# q6=qZNpmVo7r87.h[/JT#aG"ԜeQϵ8bB:'Ƕv׿FEF@"$'!2}RAf$@"Y@4ޟ߼ }^y D>|YI,_Ap$'bdc ?k{e7?;AaCMі Qv'JB'Yɂ5v3ҡ5fý>\iwC<#;KM:믃~UGAsGDͷCܴl*jpt#Glȸtb]0O\sCKugUu#D#>a}Ǽmz7Ԟ=cзzhdMhSd?9dˀQrg'ť$&TKxC6b'?t3 kX֮8ɢ_ qg^ɳ"~m@+^ҚgmsZG(DQȟK[ƍ#:&~``Ax]콁Q`aN,\3odir|z@ ٯ:LD Hn!/[D HDwQd a@]m.Y}q-88z}$5ymM "Y`=~T[q gfù>g1F첐f9Sf4"iW%1 " m[Biy jW2UI n j a$}ZI; N!Gs1h /V2mK="]˔ 9'@+53-mChCaGXbjh.=u 1KInҿiW>ȳg#5VauNymVW׷k˔ogWNF#AۼfѶVm"F u ci`XA6_Ij0bY<@=ˍedth7{!HD 2$@"w|O>:mff d8}] ?z_I<$ؙBhn)H: uvApl'xR;iqbaS!lwh^ίu0liRD ֟: V&ڟ 4gV¼)KkC$V㪈n2x@c8pFN/^3 x##{05TGٶ,0 M,?n{=2c,ʛWgMyz Xbc4uk")tAc50MsC %n)_fY2\g6ibv<*yjcO+o]Okޙxe,LSތAFM6+qBzԽ$0e<x{#߹I;+Gr2?:OD Hpɐ$@"p#+'uw uz| :/c]f{q;B̂AbȽݷeb DUci )SK.6Aǣ76eg{\k,pz ٦2sU:,[\JQ5SCM/om~KԹtkLЏ|\1vʘ'=e,f{g(!:< -R(5r]Zi>P~}Q̼c aĠL$Ǝ =>s \ok&SA8/(͙H`zzvp!/iapj PC@6 e-;c0f#si } 3"sZrFa\ԽB7O[QIy0v L r \+u5[>WlQf?=%BAONpʆtQ6}*},o8e9v_4B{T$>^P_ մu<sNO{Mu7Q˴jo# <"=&G@ ̛,Oeƿ)F9Lq{mXp-顇r# @"$ws7$@"p. -3#=̺C`Ѝ`"vDN3%J#u,o8O#!]w%A>8b8r߸Z/ʽwmKlQO!Z!e5v5iO]{J*s[P YDUV^}X#gr>9)󰄁3@ 㙳Wrb(wZe"u1N !ѯULxG687]b٭.iwk@:=x\?}l1\c!HD@!&@"܎ɍAo@ Mks򌖸KNj6Cu_n+_YHǘ9_gF_%#v'e E\)w ;3llZy s[_qp@IDATAďB=@Z${ Z()uj;z<#7k~9=>-GG&3=@Y&];`6pT~7q1(I7}e]:3}F_ |Op$}kh3^BX\=$?/:?*~ʻ[r?); F@;*N M鯕Ɓ׶ N)uAt*cqi`o2 fc:qn [e9$[~kɿʼnrh];ˍf {tgiw OhԚ!HDŋ D H4~]@'u(&rV3K,u5CrY[finH6bU{c0-6律rE?1]A\EY -7n? A"5c[lk8eӷ)kuKKhcBz+ϖ(V/ 1}NAXkǗ*}:/=YA6 ,ziJC 6dy.Era, v>s{ug˗S򑇟*}9>-Go|{wʗ{EI98IwbVl' f1Ao%f8fwCZ4 r;C:%ɿ|=v7фhۺ/25V]#k6N5"2ou|4q.XubS}}.K6}oo]/|?}D H^<!HDBG~6 7ۆY! IեbEYaKdA*uԇ28i=]eZU3fo e6k@s] bk=[%\gm}Ϸΐ7 J}}Fon6a?/e>\oŪ!yo7Ɠw'+7y<\)xOĿ?<3ssk͞V].ظgCC{?O=\f}Ru#])yZ!ܞ`}7St?;.qo_^}n4sia r‚Bƌcr8Ґ vX2RLY2w_zfP{{[_x_q$whH,(`~\)*#_& V-WBX>@u2bƫ%uٕx_+`/Ҭytk?ۨʐ$@""@jCHD $tstOvD̙,l7,N`YRX% ^6w :k6xu/@N4 pbm@eIP% he-ʥAW/X5leۀy.zz;!} q[ߙ-1spv Ɖ wvL \qQ]`3,=NJ\[>z߽Z>$33N]6_&}ƇK=%qN1xoT9﫱yTbtN3`5p.B,3KCpn8@2e1kz?}Û9 @"$/\_@"$1?=~w,F w G?ǺA%纋k_ %`&u?f_C, NK>_ GKw6  J`GXmT8kFg0<}(g[$/[7?x2ckUkI9o:6صS#'Y|q9d7 nw ̱0#.zl][g/P48Ds6H|KV@20r>H! M3`8ZrR.p/m/ܳY^y o < ؓOb, 1Z0iˢbH 3}x>P>^eU\[֏+S>Gkjcݕ 7 ֺe! x$/k8n[gػQ!HD/dD H>3~~Y.mtq7u!)A׃}p d@$%1/Jn ):zjP.< m=s|L꟔yzH_qx:C!]7cM_^-g$bY6ׄ`m I3sϔv Ckqs] g<oanyCh#7՛r<Ҍ \;DX =򾜔9 z]H2%Kɿ,ywpJhfI! ,+]Cf~қ`;a` g>` B0,!AX&QN8-w6+ EerϹse m3 jdKW$kSZUSCbb6?+!OK 9FȃMWBo z7N.vxp!345|7Kx:Y-57D!p,!zL0p<;[U_~{x&1:gHD x#pa"$?=\]{ U¬/͍!S ~e^#@= D4bHR2H M1OD?ȿ oš|i.8[n.A!*R Y l?3ܣ%6d}ݩ)*1;SƴymJZ`%mCZM|vⅧ<cpG) ('1LY_;_'_Y_1ϳ_pr4ᙌ6ԝɯ{4hNhO\=b?|sjʘ{l ȭ[#^j5G,.ˇ<)h~KO8??,_ڒ׼_v<N琣 dAWyPN# ;!C{=c\61{{dGhXb02>Վ^"8s4OUʅqH](TDߘIf𪘑z,W2O:&IJ8)o\fNlnozwD H @"$/vO'7=\w(3H,|ğb@PZ'*Er#`$z7|ṅ3!kI9.5*/{=`R78 v#-9$O'}K%?>fӽ$ QY'f{Ck80ݒӟmо!cݤ 3Ȱk>:eIM {Ay;?W>5v_0s>('iƳ .Xɤ>hx<׫vHO%|x5c !M:G+!6k1W3~D=m=\:FMsx7dW[K/m KO+ AOT vTz=:xN0T#mӡz5%w(zNAiY* .bEj=bicou^ i㗩ō.e]H ,Y`wQf#`X~`лz!D HS!HDE?{?uQ[N~Tga.׭ص~f5H`-aPQ Y x #]<{u۬0}e%˩3uSq,0耤F7}h^}?飣rcG}r…rr]w{lmm.X5"mXFM\^ez"ύpA}C8}>, XaW'ʇ=(iyƲthw.=axlS_x=7k6“u ^Ce;拡&.JQ>:BERej9ČtK1a rڦoC5Xaܒ2c/@^or\\[/Va/rr@)yGSƺ_:xApʲ(yo?b}763M>cigͷhEf^mYzqG}x^@fȊ+Z0][u -BQdZ[hx%`h<]Rgƍ~>8GQ)_e_rz.GʵiMq ҄yl3Z`x~N: 9џG?x߹^>~c/ҹ{Z9- p{E7 (kNN^aʼn;棓QLY1] R\B7ZPށZMK P16!cc8b{vXǧGq|irrool@~|?ݟ}Rg]gcBɿ!}c%4~k=CT'úJ_ T(?zߺ+Yۉՠ㋍ouYxtxFl/AAxr O';zy_02$@"/.]߂\vb.B$YJ̿E lYR}FDδnߏ}cO|9!W/ =4 d@]nG;1߻1j <^qGIY?;\71cBܶb6?, V%g:Xra+2AB w}NٷH1 53h< ʭfѩIqa2 PG NWD|<) 6Kćv ~rt% Q V~}VPSL PC gON+>=q3oJMpC>};1kn#γma`[Gs &0\;/,߻^vP]s,}msrsfОxDbo/ @m+Oĵu|:PGγ&m~{o? *IjCБJ Jj$ 7B\rgū` Z ؎I~z 8oAc[CY{|C*.]*/0p(m;9ԶqlTѠivK 8~vX±w5a)W*T>(_ L1qݰhτb_>[: wo R@=;*Z?az `F~&aչpC'WmlS*tH0g-܄4oնƷ+@lw\Hy./_~qdrp(ӓuGemyqƄM{ c_W ՛iI}k&\F1sU_ӈ4r^SBFd`%$F3Ce[G/0.)洲v+Ko-}w˟K7R c, fZxʑsvF߯yO:Ƒn ,Mv͋wo*|_AnRv-9eS( FxIpDRLg̓=:緯wys_j*, ԱK@cJ i6&#fE';fiquvDD¶b(jݷ߾fVVMUYY797{޳,l9?ӷzi]!!!!!ZOMLZ,|JwN%};ȼR8+0r[g(5!I<&} %眈 Tk Z+ JeI>Eo6HЪԯRO5Q.C;I"IϠ<*b^ 8ȑ#.ї?SbH@6; ip BS4G(PBb)k/Jky|~~)2""""/xAn۱Zd "ăyЗ:H8 A(< ePE ENz|"E(;QJつVuN^fѳ<Ж o" MPDu o@ЫFWxZj߮GvzXFVEKvw_ Pzd:ﻝ("*(D6ع3vqh49>!\8@"A>9X2mP C@V,C0h&GZ n[-[Y?oRu;0\@yg֘΀Zq~_k8$Kek‰ ~Ŋv!׉k|U$v0W,{e-/y zIi( j)}&$ Z\d߿C?G_ٝ箻tdt,/V|,oGYrRzI2DBԥ " zB%1v/̓j#PPh@pVJ:ӏ#  >u թv'/%Q/'o5\ S/%[TJ(!MBBzDЋi4/-⿮S@̂f7[]k@] U>xՏ/E-'hw-cYkB;ذ=p]z0a -N+PZ{:Bc9Il!WFS7R'94䊊J~#bmU;i6&_2[iaNH @Gm6Ruv\AOEiP[b~8LC:)/Ha8x5! [*^ &׮W!h=JExzɣ891GylRٌȕEWl|MpTek]@[e[=y~qh.wܽJl!EDD['%:b/l[H=(7Ha-)KS\ 7 DS+S f'L{[ĿV9IHҎ[ / ԩLwD@a 0iJhXt۶j[cGolkmTkwяjo97|ZW|GDDD<3vvgNc#"""^[WVj.u`yysz:@lCYw"|APN|@x%As"1Dg=r AJŶ>+کc%$mH6iȖ0TDHҪ7꺑q6>tD ZΚaej2f3@N>hgϞӧOSOΤ/-Wvw3 ^NP ZyʯB_Pa5$YԷrmܠ3f_|`==mbخ5AQfוCDjg8?Kqӽ0y{cM݊S?k&[`jXT՚3vt7c&. Gih>gy=iv"s D;@ǚ.%l𴼝1w]ߚ,<;z·/g$&'jq=UR1y6! fnVi-;ya鑇c-0 |oDn[Ü%*W^7)&g@xĨ{6Y;KD$C$ KkD4*HC1p<܋^S֣I!WfMuitIIB'eCx>ʁ+w!m#i8`@HBg4 -j"4*WJqoHw_r Mi?u;_V]K.B/G+qDC+1(Yr0|&897n[-5 0x @v82D_`~tˆw JNG)0|0Jؐhrbñ+vqeo 8>3if<,,Ø jPN30ɜlsDzܾըc;9l/bH򷖈-8|41L5@~Ņ8QrEƫQU0zdlf6Ʌoo'S3x*"0!{ˋK835e<..$8 \tE0(x}ϋPNTA*BC^JY*3@IעO!t < N|N [\L @VԦc>Bbo!T?K1PNnDj@=b(P0 PN`'CJ A꿜31fL?gSq6m%*dcϣ/m|zqv@jBy|@DFCk.'1m`3Q1V̧NV_۴,d<8cS\92Ze~7c֬ŷGߧD!!!!؁EEDDY|_xɣ~1kW Ri~An{vi ".! @P'O^^'i_Φ18;j<;zޞ`έx"8Tg%ĴQFVl  "Hu-1]4j&YmK #GL_q?PR!g(yG rSxIsX'X:ATF@Vխ=ܱ:J1:Aq 9bWkC2n@rC #aV7Ĭj\V E$شKCa:6;|1$~2Ft6 6-ci4%7*$'qܣ2e= ~7>BL ӺRd x=*^~ҠT.W[I+̴5Vya.l7>go -@@@MFcRs8ӄ /YR\/o!B!]]^*°A@1$!t^t`zd!ea|(t.2b []3_!=e#VWFKY*:G`EJ@g &uf F u&_抻2ԊHG!P7]̳t"u_}O3PPxKc@yգIA+Ca_iK/%I$"r9l#|xq!@KRs.І% >PrmD2yI?},{%}[C#GZ\9s-?cu#Z.wS RӣطH}={7/Cu6X-1ϕ@7藵.DHe5eF8=MH.B-pޒr \T"߁ \?jC ڡY,f2]W:67ձSG:vq͕%p/mn L9-[1p: DL=NMH`҉,a~TjXR+ѦHVuuS"JH&S)gtRK0B>N$Jy[ΣܣCB 9 ] $hS9kvqsdr.g쉥mDe}!I2Pr/'8lpp&!?U`h 4v3m;1t| &yNۍ@5QbmM 8B5Tũ "ğ/q4jNEXPQ?rS%V;>B!AK~QV`L( ˙D;^}ΑPf˪ܞ1HYNTі>@ke6k^*~wa -{7ݍ 035勋v-' E)`|ÿ@A'qWzd'ܯ̤|ND-kpU {L9ZzͶ/ZgI,>hsV7i$ 섈ȂU& *bHÀ[D*9ıYb8yrJsVvFS K a&Pܕ )3\/ Kq;Hr/VZE[ao+JiX7flYogpB.뤶_-:tb/13yLp _04X#> ,Cۅ!h0FP @CG@E[qc ⏆ƝWCU"UZcZRti-[o}"N;{HNGZg@~gxTX:D*Tp@ 7NU'0Qv2h>d:Pְg4'Ɗ%N ɖ]!BGz#۲&6q"qB% gX/Li!,Q/L@IDAT6VY[Їkg.~zk,"&'$f8 ai3D\q`ڿd~XXU!XiyJUHB@2Zv/a(_|8CRIm!@z^Oh3jWܟD{E;IDϜ#hhn#቙mmye.l*P `MN6/Rxbm4b{oxԃ{yoX("o/)|8ñ aa]Sw-J1FMxf {iѣC;0e5*O;||@=y4 VIErU)CL5]%(=z'o[*l Hϯ/ī EiNI֙$pamV!p<1ŕnذxE5Wc0u(pq`@ /|/_w9;qxέ PHqf3(}C*+z "Dt/'$:QsQ 1D$!9qHr\zV cy+ߴW 7ut !d-@:yrYX j6(g>u9;eaGh 5>z T)CnmN@H(X!.RJ us">}̒-'bJD)ҝu|O$+~YQqIuV+36ٴ\SpʉIflE1!0^txX&Lk٫.'w!VONj.{*1RkESFyw#VZq;9[sogv~fi;x kҁ)`SAm4\BN32ֽ fXsvsi63$%C(o Ր+'\ySP>a| 2@>cLS öt;}fɭf7{~E!!!pB 2nڡ\[@ B\WvN R ;w Ҝ B@Kծ+d[*BI戼#L}4q6*T-r!%+tBJϱuTL ?)Nw y-bL[N~{֏@Ģ2.BUdþ/F:ϯSJM$\NB_x;mI[7)I2|qtc#u :1U%.ocak60WPG04'Rm|1K P r)ԥ—@ _їi8X\W/WRٴpE03G-pJ؉cu ku[ht54[B!X|\L4_k&'ZM[ZG>= ּ??zZ8CwT$~ uu}bo!Z>[-%装 g>2 7ۈDD|_~73611 39 ๊pgtȻѿ:2M,MTXyr-ydRd[CP^Rʿ6\nkJdӪ˻: j^0?bZW^V=y6 EܫM0h7׬y+0ju B`xdm4Tt^ڮ/_Ln2KlOqj}mmhm Av$&sik ]LJO_6sk  t{s]a+ј/UúUC֧n pe$,Ue' {! 1ތ3kJrހxL]"?/KIfGh^`;C3ims}ઘsK{u]@@YF2#B B`A`qu#Bا'8*ncBED>P/ɻTrᜈy'pGx#%{QF8n C{b<<F~_Qwz%cX')ri&3:(3? ^418Fjѭ `+`] ]HkɛpCh_'*4!Q{se4oEuX9p #Ҟ KW=vtÄO[*s& AڷDϳ֗3t]-Na=l 0؁ #l㛗&YTs!7|1qX,8:PW6M)D}@#52vO3AB2D/N޻)5uYaO>8kG?{֖V?VV/Oʉ.B B B`B 2vGD[|/5?tv S\ն ͝Hf%bO\DwWBs 2REHB/R *GDlH`@#Dh9*2<6XWq8jBB%5"LΩ^= $Ж/Z6h/pd!+Vg@TU~ZEJJ$$OOhK;=2w@DHL!T7xƅssuk6i9OgFsFFF R\9}F! fd{v /* ؀0k3%;qj hcj:I@!l_"UNz8*>ڥ4㓈d)$.eC!|ԙar5JΤzSbJL]rC|vOCR "Mrz;쵠Y_ mmVlntBRa5?5 A-go>cʄ11hGPQ3ēV'NeCLU= e%oשJ4^p-nH`u!G+భ--󶹰K'~}gqi!!!"o/ŦEDDDN[un]H /rJ $\4o=BTo*^A-7xsV/Dۑpޠ@|)YከװPlr޲e{h)HB܆ωA7 \H/ya@$%=*Zړ[yOrZa "ID ԡ/>BM(.ԋ Á·ޯ-RPf;Ɓ s#dD1/P !a,`|!(_ȕt>#{U#lp;W,_GSdv>fC"<1 )3ɔWziXTJʼn$i(AL{MO3< ,}w;^Bia SUa\0GjRW> V!%4PX$v۝w٫~GMVzݷ|@.B B B .B B B B`/@sܿZB=ew5a;!~vdqH$%,@=?A%^8,,xP8'&`H 47cr1/om~+B)EDD8!~q-*B B B BH_?\__g.{k~#m%HYrBSYwI^]N<K[jzt zDlFJ }n @a[xvkژ=fE ׭20:]3 xI")#fD+! }ރ@-!1qR؃H'09Q6 |"l2ui{oꔜw{9CI@GU79ΕjJV$J-t*aIpt \:'}z.x*OI>4>!e#!!-[]۰%=lq6BiPLIii$.">#',dVA‚q4ӛۯ_!;OD8DyI'(i=@+B֐rJ(NN7-6=w&gg)Pzo_w}ԯ2F!!!"7EDDDH!}|_<Hcڑ@E@9]RBQ%YcY0]I9/"LHt{B[q"T O@d)DH=I`7 a/Y@=iB7r^ohJ/&!R5H9MhXw =DtU^&Çl8LaHn_40aSA[R! KKv|XZ]\Z5BL"j9"j#QDqWsq!.bn' L@2aر%Mluhmnn'O١C9b4\>2'!_t~PcH+ G"焽)V8vIPuQHaM{Nx"$Ia y IuKy-!p Re̚0a:mq]wځGlӶx&_>'_855{ġ.B B BExQ ClD@@S 8{R'ja"7"qAE 9w+=~a(9(A'iv8J /q `-M@,Ŀg"ڝj?(NmSItWaKOpE0^@n NZ=α @ %MϫIY7 wI4i䑺~0vrvh2 `hVZP*A#"ު (mOva}iʰ>\tav‚>;oNn;lxlC`=b#s@۪"a6$̵`TuT?6vW{Kh{WD cawU^c@g]~mqB*K_2w*ț`:Fz1SInWv-l⼭/,ť{>'^׽E!!!pc!7@ӟ76V=w,md^S65]Wq gn+DTTۯe0dP&W}{Pb;]ibA'bHDB Rɱc HND y݉ZM18@yBw-}H>/r:$mF:'詜O"µLj3;2SE = w(^GA4Dg.3% oF /sF@XT !!M m0}4Bޥ>6c/ڑ#svFS\ g`& _/8O`XeC(C$d>=̽|?ÈH\+z=E2Ұ4Bus&.e(!Oa&1Xs58\T#0nCs2?oK/Z_Xl?ؘϝ7n)7#""^hD X_@@@೟+++??jrlzöo,;]*g9 -\Ht@J\ YNq Ux"w^)$v"vhNA{Hg+^Dc Q+h)lBR|ȇtě `HTֵ8N; GnRSċNēZzc#Bmb:ӷpeℕ$rYlq ۡt-y@"f2Qqf$40( ir ;6WƔ6ЍJ+rY@%ѠP*9?VZsRx~50fPӗƾ{$1'rayfWG,e/^! 6`12+p`k ۚ?mFuAM*"z's"]ca5Djb ]Y_?.jqdG9#/%+.nr|=pH % K /Oٞ)0[ {iFq` TQ1k`W?8МXf@ŠCxgmyI;zjzҎYYv L(0kV*POJkI'O.^O*^C_Ӯ{xv0?$vU~ĿIGs]U8 67TVLذ2r:cŒ%vq[8wΟ/4~ !!!p!ƱB3͕́/ѳǥ6{(E!ߐ3pq &sn^S0kj AqV[lDVjkOT D: BDBKW|x+Lj2&3~>/ !1}BŬaܚ%GʾP7kڏKڪ/#W}W__@ ^ ͖,&1N )( i(V*_.w$0ΐx搤a.Em u`jGf 6]ˢŜ\ .I&24oȫ߬OՀ}.Z̧+ P|4(Fk`1]'`L}%[\:o!wyxz> q{[nmzA9x͍CfhKgvHj[MF+KW'9;u:>.]E%ݔNA:9ΐ\K+¾>./ ԸDwzNvI֫' Dz쮱W҄QIf9gYOdLqF[(5 ,_߅|њ 1 1(Y{:fɸD300 ͧЮg0] ^^]kBH7x}ti ^z{3aCTxFr1tV>Փ'o'ܴ<ˊzo|""".ݮC%k_Ӳ=yK_V@AJ!Re@Zo1H4 TG,vJbۿe7^o!5 q.8׃ pqSNWLN81 T&eeEVlY7nD~C6;A h9?|v>t@E ^?E4O0{*5Ļq93uz~2~FG~ؕv X>Fzbpow8k(rzշ|N +ٗزoiՑF54*@'`df+h^@M]GC؛F|0*MlW|Imf~AQ|ΐ&z=Q91z=MIOD_؋Cy٥SNL@&J.BגSyOG .eio 6m--o|׵6ZED{4?zS,0LCV\lh^gqBz渖NO E*T(w;QKJBpwD$_ pZ Oؐʛ»_a:23J0?6Ad9Y dc/CL78p\ />8Ws~ H \XfCl׻= 1 ?.F၅:[3GWa/-oj70|Y=lݯFH^@]KZ=lnn@./--ZRՏHm~8qMj?0rVknDsM:$S{sMds_6 |!gO5\|So|w8f*.6Uũ;w?cw5s,>&xZ0dMAP`nB =d8{X.٫ .;Fh8܄zavWv qFL oi8v;Zq #OK8e"e$5wßaFi*)|@baBIZj׏%ѿJ~?y146Y-oFB=H!p;_ZB|GDDD\O|a+CSrUYX eZjI.*~ABk# G@&؆#0 $Ul&bc#"uאaOa 8/_R =ZHz喭? 1;d&7m/i]@ jm GNY+z^QCch8L)` _ a}D 7{r_ݿh_x-ٓ2 X΀!nj-LL 5^cO#ʖ)>7~`z C;thc/[n١fŲYrD6pjNKd~}/;N̫ ɵv9OK~U0+ePqq-<1\cAIg yD">y6׸Z\ܡf{ԩ"""3ܔ!B B B`oC>?nnunj>f9L19rs`rYUt~'O눮V!+$0C'# Ur[ |Jq I~w/W@V0s@I<~Ľ2ڿUεAnQT TAuA@y]+}U  n"ɕek P@FńC0U)?7&eEքpQiki6 _dhw 4tH(3NijÏ3j2́8Gvlۖc_٫1;-q`PZIAYsH_*h4*7v$ )Za!ɆMm%x%}jqK@cm4j_B>֎Z:IRn3'N*:0;uwr]@@Ĕ)wg{WWhU}SSVþsX)g V)N#`-~ @{ :o]0voG B`{VdhO<@#Q)?B= 81@$gP Jͷ9o·l?(rƾUp99;E0z]!x܀3nc]`ӭWZ鎿o㯶v1d:yd]+FƍF`?:gĚuuήԹ äU9gbT<,Z2a!ښ39愮0O!OZm\'Dr8PnрCvx&'4?Fb&F{E1@:t_ZNND`Su)2`#J5|h}Ҁv(yCG&ǥ8E`=Z&[ Rew߽q]"">;lUmjFk!SUA0DX:;@8)QR^!A;هo}/-5ᥥ$Q5kAJU$¨5*TD $"z>!&)\IHB4UtEd_xyU8 |a:r'5[Q>aN?tEH}UzQ⺤խJKݛ6`VF `ZG[@ŧ}klj0/'`IAj~6t@@WFg+U0Vor?؇ }8O>ȿ-b ?լX|y)+$xFb|Œ El;](O',0 {x\՟N%oœ=g,$HuJ\G(nD_'R =bb !O4D P]^w<}`Co[}U{gXtTrb1!#)"-s07 +? Jq{\aV!/`HCW5.77b0^"KK[6|g[vik V1.|bH#ɇ 7~{ݤz7'K׶ി6~^|i"?=n.-+fȊ؝?m4O6@3&kAj dЄmtHKS R9Cb?om69a0Z[̻m?wc~϶}acEDD{|_̯_@x=YTJ0FXw&N|r`D;R&mSRߔdLpr"|z KQ@Ga=DDg"M:}S3ȡoeS6!Cy||v=¸v&F낀Aع,?<]mwa̯\Aqfr<-I0'$ >mStnޙ\m㈀`k o|!=ԅ3bu]__: [FH(sRgԛ4 '2$:0^-q-PBR) \D;t'ﵯd_z[ 9'Mm֐}% _ǎ]äoYN'g/5hpk@Ǿy/CWa hTӍsjީMz Q74)ڝd vh- (eF@g5ϩNO3oG76鳶r?w;xvIyʯޤ!!G!{tb#";/oumV .}luj$:!v" bKe$MBT[3K4aTܼN"|qQ;҂ Wy6oك焽{gPhڢu]oVqS靶6Ә]DZ؟t̸BΨLہhyi_ ´85nm-.{>ߺ6aWs=벻 7ݐED<|}bO<84(t$s@!Jq@g:Es\'lU V":3RAy#_}!vaKrIq"]]# AFc VIzU[VV}Sfx:o-jҫnA Aۉ~Z00ڨXNn6 u vrk܆KHsPu[5\e1EB ANP~irr@?1 f{\M^ :ba֦5㈴[}1>!b=.RyQ/DԋwaFaФfu%.WT잿>$II$I:`9>`l|e[P8~5(F6هL \E(WP`/lya{;ٝwGj0H}LjtԿaHonZ64y|vwy(m,u"wm$k_kH2viE(+M Кh$-NacEwoszC,Fޓ/zm҂͟?}Wwpng~u޶SaED4r?i:;!!!p% :a II_DQ!Eb$Mv@;>H."h,Ď oh"D39&<:)C9tm^tI(7>29eZd{x`L(wmgA;@Dک CW@_i0M:kR.ɛk+Pos8'Hbc#D2e\wòEm\4m""_-ЇV#BǜCH!O-m^zkMF_x1bMUK)' `1pҼUxAru_}eV#O4xȧJԾ \,Z[6չ-,.O,?z Õ}lC,%"-?!Kg^ dNZۛ|+qA@飄I:-gp =͕?iR[G{^>W,~ @e!Y_?H73u[!!!{xb#"[K67HkA0_אqDg)Oq"t%TGJ14oTnJߞTTҩD\+ `ӉcG֤0mZlc+ k_\=w:i'Nԭ>VA[GW~n>Xb+bߥ106}ѴGlU߅[;14MjGP _Df; Me_-ӗ4߳Ĵ1|l0sĸ/,f/,,L=MVS [ 5^/3΍igIY,J#! s 4MR}HSQj%BƬȅQb!zN}e,P-Xf qF@:[%UEzRVRbY5iA tRz+ DKLp8Opxr}YX..ۅ%~КX6LҰelIQ&r-k:mPR^!NӐ歞=(n:[J[ȇ-NczkThV:j9݌k=3;v'fo$׉cWEХ́ Saw2]1ǎ Y I'%JZwm@/߅΅!91&?+G~d*ʰ;HIkwnG(ҩ]PM!nfi7uCikzƮSb^]+?sG@&FICK+vmU*@)Սz<@D&P7=zWN_dd^L@.+qNݺ ]\$mB6]! ?3{H~Z{aG꿂D2V$_̡PZ)  :FSP\׿HrȰǎ}N_։lqH婾S~luʦ-,\6u0c^lZ؇)K,HTY\Za:خaޠW;ԯ5孉Ysb`Vrя3lYZ1;0ŪŢ;~*[s\K+/~&SQb$ $ ^j>f' CQ"F6H.v J(>irB|G$h @ÕNIJ%\; '"=vN{8/E`̻&2Hi[zx秔*;l.bJT"*ZsǸZ&ӗb $ T,?nNՖ;6:/|Ѯnfj[+*8MNC;8gZJM/lZdޚPC?i91▨έ[垻b"F$ir?@cU;/05Vvl؄%L0 t@j,C ~V:!(W|E!- P*&MTOWh%E `n48ݎtɎgnT`im|Pf.Lݮ\bnWJ{dr~V}-jZǽZe7XbzQx!5NXxCͧ?oE% ܇HGI@ km6>3[V' eՒٹ{=åtny=vh՜0u$- nCkJB)ïvwE?`xґ?oo=ũ 8{B:ɑMBJk+12j2ρeY_`͠Pݏ*m{욝nW66m?8#-!a u0WhVv ϔ[hc#5+A0gE!apv'DH].݈  N7UWĿTtT` nwZk[e*|fF\lVE[}'; QvM3Ϋ`$qbptRxcK][0[v~ׅ)904N9"g|2gRV*[e-/ZǮ\=o/\NOةGJL(oȥA*0V2Ϝե[ a.P=cLg RZӼ^ fh⎥KJ*p=pa{)k=~ڦϝ/[Y%? HV$K @`7|m} m,c/]$[3JA,n@|:cQvߢM(sᮀEtYHҺYN$qT1)i;J 'MMʚ+Zz-stӾv朽zi9kbWx%16[e,jͺut1eRӛ6Y1[p Br"Bur}WbJDmv`{{C__@U _N|^1=ݩ٠d}2k<1F(Pu/E>!'\[IBW]ڻ9XG -g|0D!o%'b*ܽ$%K p;)}.ͫ*|kוI 7`(UF1*m1T ʫv4FG駏cnwSW"KXjPs52Y>wuDҐ*c_lڭۚD9/ZH o5ҍMs̩ҵ=?Gm1G5`_ʕ_3%o%H -Om2$H p' g++]]ٰZCMţ sC7s0:=p 9%u/E:a Ἦ'HH$A- aJw#BώFf_Lg kiܵk;x6=mD2]л"E!Omp&Z}C/tZISo%EG;27qqo\1eѱ&<ؤӽiAR 'YÇƭjf1lv1TI~&N\kQеާ葔=t܌_9ș8ڰƒ9eatJHD"7⤻p;Koy'N!CEusKbd9& "˩K p{`ֳѬ._z֘ᎽØg~m217~_#G+8`OC=N8P>!X3@[XV[_<˖cwCwRW;% 1c&Hp7숽T-zX&jzkxa%*fQ;p&+%7Ux~V6Rt3^\U#:֡nxSMjCqwԆoP} >Lb("1O ̎l4=c/WFcO4{~}+T/kXABCd9) ( I@]DgzF“*!/t;SR8Nu,Hq ᔣ(st76A8J??S29DRy}~o^mqWM#? `6hg~1/#l $# dcm o!"˧[6Rhdf7^Jz f ?E>0[G#e4N$*uN-BN| w`%VE#@p+x{n,{c"2o,bKC AArzG>p?4oNЕppX̩QK p[P1i4vG)4는P\R>c3)̢M[\mG2 oNCC26d-0sP3Uo;#nvf ||1n;sH8\ؼjSJ:i8<[ Y;vԣ#+g?ÿ1U)vI/@ p7@ xJ+ _}ax|$ľ)} :@B%j4<܉8>P NLD=BBI܅Q@(c?owC:k9̐$ oQ   Eft;誫j/\kKvJ.kX`ڙq(e^ ] \׎?ubKtj/Nl`sMcn;.Bz7#hV? nN~~/WƎ$'|WER¥Q~Ȱ@l@L]SJ4H~C|kGH#J E 9)h!r]<@Ȅ7!';I_/rV76=l3 [ol)OMah-> :-Kutammvb39,b  NB#ǰ V>ױbŖeZlڴ拈a W2ı`#b1a]tׇs$jfK'z^(,a"ZV8߾OI4L5bܦZdVP ՁKK@V!ktEs9sO`HJ}L3oFd_]t @z2sWtGs#0B`Ď6w91|.T-ـ楰 ^91v&s%dE55&yv<=]x/sZm?,'n5I$HwI _xhq~܋j7:XTv +XNxivY!bBj/t]Ŋ>hiQ# $B1ώ h#dB.@d.Ƕ}Yq,Bq_B̈́ +;'k7[WTv!WLȒ׃hϖ=cܺ:3Ukvs{,ubqxjߗb?kYk@tUKeZdxLR):߹%qWц ˓(8BˠkA`Np xj*M/x92V;`"8 EV KTx^FQح;^ԓ(qǨk!D呖>C~}1.f Rb\yc I +L=a#f /&D"^ARd2PH3}z0>*}_394B x#$+rSH3 ϻD|>q~v$z47K4,2H wF/ z<ݘFmӞKcSva{-ġEZjEEl[5R]b׻|$~sȚnhX8р=xvK8ʭWaʒ-4!-SPi{"bQī5}\a5 H_mHk.ՠH@RIp԰'xv*{a ԬnxƍOh;*bDpa3H@d KwW uޢ42~o1u-DU[Hσu *mKz}U=c~`PqBW}.`+phyԁ ХIAML"]va$ Qsec'kt/7?}}Rf+ 3J%.@`Ƿgw,$ ıp_Ts L}*'ԃ'V9BNɓ0rv`?@Cq_dΑ|1 0ͣn$b 汙e<-I*1j{0w5ʜ&jA w76iNpQ5?v(t͞t' UZG[oP$k`PlQxX][ʊ?C8@ @"pg@ xOƧνxPD⽻>O3XlfvyfWʵuۀܐm "(#j}o9'̅y@2g!ns bZt8@lh"e?n6]Ro8g@;Uz1u0TAmݬ!qn/z((H]ȧDԉ΁(Pm pe$*뗧iK?7-Y>e@dAB!z,įB=?pd DEP.!"0n IfTC$.C`s}(z՗wܔrTf0jQj_\Kܹ~ %;pن#le0+:Ul}b17W?a1#Ն]`yTtU)?Π%oC у)#n(% YL,[U;j׮~X_~[QIX@ @;/|Xm~M@篅` ҂:=tJ MĂ;/^ U7< ij[/*LhSpUғ+Bɱbb%}B !v/vicNV#Շ}.`Og$ Ec+uyCȃ浗MfY$!Ꞹ"/"=Zz:غX\ܴyx(L#HSf Z b]T'$)6#Z=qr:ɌyT$b]SYiΈYar>IR/n] cO٠s::)$/[! M-\_2_w{վ%H pH vso/X'-S?[0A'<ĭH"ߥT@F9$x TsK_2(x/a xy0 AFE$Ĝe!]WNɻ -35S/ oR.a,bƺʊLo0bO=ycc=$+`NMQSlfԥÔSiP7fm_5Ls[7IqoLxVO59Ѻf~lw:a:aT,btu&4/y!{W*%! DmH@ @ _Bu8M' y,c|  a+`7_lQ*Wtf'_*tN"GҎpD{NqaY7݅(Ld/̄yHqޯ0N#i ]RffmY^d%қtА!d`z桢d\Qҭ8p!}dޠ?h9-==`o}N ^}lfTd˭~慨io_݅H:IYOs^,7NqC6:i ӶQ8e2aU8/xF;*P(hGe_],j( @b*u}0RIIi񜴖Ώ4NHh1NS_Z_٢]^B݄q!K A@_(vf_d‚٥0Q]EQguoa'GO(ـ3nQ_*Gg0gH1+ yi IpXTb?0:ٙռ(-{3ūv{mt16BN4:i@Xl)KWlezҬ WT԰_oH<S%N pB?뿾jֆOagENBA9OpHVdoD7'cw=/b8oO@4y/~4Q:](@@;I["!Zmp&7&܉ pvw>=2Ԍ?mnqɯͮ5{+ " )&;mP)ŻyW+gBc9"T[p KЃcgώUH,Si{SA{Ūy+>5Fc2b"s Qr[H_ʮ_60B!=̐#"]K=fv.UN D䇡/6BVJ1*@+Xe`Svk xOZޣ=lI]qvY,BkB-bf#!,8-[aqDT)n)8Ccp>^΍V%qNwg>"@u* Қ]ev(zjGcE=qrC$|HOz̒,vR3BH*7 E.D7q,=\a:@_c_91_{w4:oFkt, _" }R1̈.Z>߭e;fakG;T+6feL #dcN *dOR] i$G^`sҁNHWv49]a3ϖj~.4ؙ[&߶I_艊%Nr|nr,T~1tBhE@gǧUp">ȥ]3IR_]P+0bH`dTTI ߘiWkgZvik4|%x Zߜg~qi(ݵiv\NO{ @IDATsa.^r$Y w1ҩ: U]@ xgœW>YV ]t#~A I}!iY!:Z#/M?].Dwͣ@B؎^tQp !OQC\(q\(0Csv`0O)>6ȕ6v,Q& mqf4n(w!zn"*UpP;EQIܪS*Qp lvU`T?X$+`Nn^е[- #:B0Oaq.!Bo.+1[Z]݃v=f+5lXߑS%xRdW|x=&UBH]:byeK;~cUWcN,6{s;~):{$!{ D0h/<&.uzs}抠v~ѡFf8fdL rBc?;G~6ncaVu@ou_)DoetvUuY#5fч-f(Z=yppEݱ& Rʦ1$xh[UT.&"z~—- C(pD.7$VPY =5]U}(1-Mf'ٹoK/7&av(٤CV dT:yR@&dנWтhT\<ުӫ:0'Yj. zSWOFPv0hӏ١#Gli-\~ZXs嗸oT$ 7Oڝ@`|_u sB>*uEACj?KwUY0| H0 1D|,[8!z* e8ÕV.mW)|WJmJ0RbR$J jN4\4[F_V֭n1/}}ATa Tx< VL"U%^D}"FS(x,Z@L-R#;![_siw6v)t3(]aMZ&+V%O{^\u'n]bĉWb4G>0՟y`P12q Y]clG~$viKI ~w}qgmMQ/s.9;IܟslGi36 DK8@ ?քͱ>0pl81`SdV굶MNF+G씰-xcEcQ HtAghl,BC£(4=sԊ(@lth Sv/șoC]xGb:A{ZXšZw?װy8 8{\3EC‰}wCT}%xP!&"Xu1>x*Iţ%a!$/녏Zptd#VTFBk5V]b?~:ZƐV x M1.3L~Kx%yn'{q9Lo LvW9kRtjOapU8QfZ5mCV:hչy[pG?"ע[73'L ;8U[,\0;+K,v"\4BUY-Zv}ȁ yCVz==;ہD(u&VPAEt7C=݁w}CF_^\ ]_Jq`hj^gsi6Ds.]<-3yffhq$,BBN֚L=wm6Woڙ3_A`);}dAŢ"س1*- S2N&tKNe'=Cwwqn)dk%ǜ*t& ~@E^sRD"I[^=z;d)[kzڽ6Iho/$ o&k?c^̕t#_aW¾eNyC6 ?Lc0*[>\[Sd#W_˄ Vj? ?*2ĨgQir5[ )cd0uY zQ#v|bصoDUē@ &MJ /ZYbbP1esc7x,~H B }T Q⾩~jPpb$m"l`訿\2vJ 8Cӎ>jW70J{ɀ+Oe;rdcԀHiDsZj\pIԥ:oanߚwH4iiYrqfػ:AUp TKd B:c@}JzrkѩQ۟ם'`WP2$oZ-J`з{q?!]tBs_uhQ6㫓BRI &8tB#$y}k5kfaȶ?{MQw7ػwAm A/_csH:u٨RjG6уGag],LvF``&85~?sI  ,'q |Bӝ6l¹67!,@YK]~XYZGh\2 džt%7ւJˑ}=RvZqbD/湐F e"FDw"J)U4 ٽ ekЍuFaaS@'3XTPYjG܀Psx[1#&kuٸ\)";weً bT[Y=Tz Å˭HvŊMDK`ƟT UQKU묺_l秖 F^J[[Fuz ߺ݆=!1[b]cv]y 6 NouPUTM@GID{G.)̖'!5+6`T)YZ#DF(P~50,+??iT 2FwXd_"0I$83~iXNG= UO\{PݻNՏC_Nx!;}!SzPIjT8v//>S'ٱU_8)묑#X/V A%'9]0̳0}II;}ۣ4~SkXS=ɛ{9`~;aGvLbmT1?<3OeXWo3~??Qػ.Ey;%[Wo{ L+þTZj]B>iE~~u! KnZhIuY^\" 28$d~?T@D(: pv.u 'bkX_]ߴm[mvlcKuɔ9:zX,/QUB0@Fu^d@`n8f @II}s˰C^ Gg8\RFo0'_G7}(.sNl {B)W vW_UJWPс@̞Mqk@O!0"&x۾釤NCi} xq_a ^ EMĐT+}az]p{w4Sb$ա#JTԀ@g8yb!>݉y6G,GDlE?I ƁUy@fzպf_]t.] p؎geߏQH[Bz+b%g#1-jkfYOq" Tjx G!Mx߸ѻ奸CY䢦ۧ[~d+@2uz^mԉp]K/}W#g]jHWH@AO߽չ8|<H. ☡熅`E'ط iptu8j=*v( uU.akTU8m`%1i%1u;҆Ѷ/ ;:үE1Y%5@|UCĤ8?ja1\T <` THe`/٥dy^*ꌤ[Rz :NCDx]BTn. u 5!Rb`edʲ3P;tΔqBMD9+>DZʹt]lˈe"O4+k7o}$gd/}Rzb|j<<0Q_Sc.T|#۠"u1}5%p}K+da_+u %.݂MO'Ǚ³)%guul/ܞ>ND~*x'hdigwVq{mQo@u@*Jȓ0?D<z%ƒԼ*êr-?nW{l7!@1B6@Or;2")5Zm|n;M؟QwP:2nXD\(9.QaL f yCza/&TpGuUP#BӶ!mRU;#\!`>g!MjB m>$:mbge7w} UF&BRQY5w\BND)+Gw7\mC~$O\7"iLƌI3k|΃#`$.GHq"rI.ۙ+;ȣvsc!ZKrY]1fG'X>5sAe{5OxwYi1|)KDEUQ~p4ĹѹĞ7r!uI'rSFE,,cp>$HN:*Be / ʷize pu@0J`BBB)^ ~'XzۻzNRU$U"aI>,3dTGsbhiۗƱV1 "-O@(s a^; #-8'mg/5/զ!t<{^m==a jܜW6=oK)D D4_TadAS(BQK!| zaIzu+/B?q#|hK/moQ-Qꨪf8p_8}#YnGyRO)u#ZE5&,2xҽCzDhV폿zH=v`K 0GčѮF ӏ9Rb1)N1 acVk! pff6eڛj%o##l|3ejNsro0ǚ(HWX_espqӭaک|Tn|~i2#܇?nިN:`X@m! }MK{DȻ.q@྄0%H @mo|n"{;v(mi>?O@qC>~ίpuȃ77B|P8C-eWK! +b4ZDq~1,]vQ\-A/B_lc$ fA"N:*Rz.\hN؝ϡ @1P-g8/@=RW9DR@߿ `uzVlsu9.5g欜Z|o4d' ~zA"+1<9Fo y~EĿ6} /=}t~t4uK6߮fm;n LjG i? j'x\C ^jv mQLwDkse>n1+,`Ό1H(Ai,nM)s[]^Uڑ#v҉'{g sg1Yf WcWucW8[ܦG|Wv*;vq 8.YG (0k A \Ke{껞 `7}҉+3_oL8񾷽@@GRBɿl~R%DX ۓi͎"ȀDeB-YZ]B1U"~RyϯG4Z!H.N{@ENY]NG;Y?=dC_õ֊KR 70a`Mڅu K Pvb-K=O 6I`uŊwk!/1WtlYcb%`)s$WXruƲk6[ ._L#. !AH;A7bి"b0/b%}VrpKc FصΨ]n2yLa"[A]H`# QhDv?\|)84J#q@;sVOhӷo:$vF&(GmWѓ1bu4}SW `Le 0dECyeAI:ud񜌎q`P`Jj6Lv[hN#]M`h Ysi'/.C>EP뵎}M;wf^~`=>eO>9fGþa%)* TƱK Y04>6Mpx+9N+;Et'HjDS>XH$)}w{urj,hUVEW dk'(A"& yhw#zdׇ@\ D0 znsÒ< D؉`Ƀ/f;ݲ ny ;;zVDٴ(6lP*Evʳ>ćp  j+zu8%bQ%rVb>(PT֭j% Ul>>5Ea!~5&>F|T!g;aќDftY C+6;״vbΎgNC,Q@8N)\{P jX0Eg"tuM7`6%!<4ʧ7Mc|qz 0 iwCsWgڿ_O?zߕ0[$ { Bwͭ(l\jbE &4 ȐLI~>Z7p÷-^"=㡸9;Ą✨No-"e$0,ja_ڬ30ߗʝ\y-!D[BB8P)'A>09JRqA@wD**4w9<-:IS_@iAwqдlgRC6HmYڶԱ0DEIZLv݈M®y7"0DIf@1S-v=eWZ%nmٷ GC"㤩4F`J䌳e,a%A*\P=Q\0@*%1P@"5uN/)x=**+)sn| F\Gڮ+L".,eOC``nburbѿ1SAF)S&҈ $. .O 8 F>D4hBgDЌyW|-g =շͅ [ӛvr)ڛji\^oR`N_S$ŧ-_3uP9mwԻj6PSXFd#ݯn(]\?8=ძyPVU{lq;z0.-,?W;ch2q  ={Rh![o~޶m_uҍFF߽C@gK\~2(_Yx{JRh.1@BN[ 8AEW Bў$A)E4t=v~tA%a^Ut, R84)$.d-8/sX? %prXd+K[C@f޻WowުꞞ0LwO߬ޛ7'9y$ߺɪ0݆?Fuxў{4`𦗌p|QZaDֻaxk#~*w`[#aa{cG95` A|(tX[YmӀ<:[#jQpU`~##0g܎Btgo{&Mͭ۽KۮRUgd@h>а 갰qK9PDfUS4@4gB[_.AaȻ]QJNJPlDtcr;A؇y62;!T/ӑ:tvd>F&'[hR}0H2!&gE[h턶0e }SpBނ|u p (q24b|gso :V3./<]fha~>[I$Z/izrz9K r c܂j+\T༎@EDuLG1Iv"+2+UN[09q89_I۰"#}ΓHQJ 2`ا.&i˽Q~w ƯG 0S.6?Tv_X( _1ÌBFM)h4Z0X2["߉v`kic^Rzsi:u(/bsb-!J㦉doӅ<-E6U#!:B<}@g?)Ұp22^gQx"Oαr1, Xp)lXţ,vv'Ѱ| ڼ3~#i`cg trL'?6!aL+Q#~JU  90-qE@bQpұ.Vri猑@ۦi@Uۭex .s#]|3}{'ΦO~txvYlM}D3/+KEn~ç${df9nWܓc~u')bXW+&=a?mmaNZ>hL<oi%(X?TU. z>1N'jCί bt* /,3($5RteVŊ*mBuO2C!!`$'2'|Ϻ<KF&}'SLwJH{VE Z/`H,(-To~n?]ƘlfBUİ[X~JUޮaCd~Z6谇1tb>qq6+pc&4@1Y3`کêD-2,Yf/+9n@^"E=t;|[ϒf_&Ľ^1-!+r\js]j>+kcX&'vKn&kqM{@\+WAa {+Q":E6p367@F?1*$ϔ l# 6v-s^ڸyOI<}:=©gNp f?4HdΖ.RSӽSӜ>2}ZQH YlsKPGh¬LW>ױAFpF&@eSKO?tK7^?qٓBe#hKU** _~¼?~GOS')h`nnrleauERa1 ף|TL<>ż䳳@6E2y1S, Hҟ{ +^G, " vU~}Dш2;=~=*7epU~ 9`& af>i_2crsa~*Afw&g9#hOӼ}- ac0LLO`}ϨԷ;fS?*IaUHA=#?B+CNjaӺ侚΄)V70okAm-&k3sNU uC|1^uVFnr@XZZ fIeky^5)j/ ħ%\aP&~Q@@j"Xj-4 4U^ f`f7ZV'I~%79 !2!a*<`~`0^պBɸ#|Oα[^iw5~ p`bY5hi}]Uq+K[WӉ#?l:ZOii!kHt4#wwQRx9/5+C'B$}JZ!D8 |v'-g`FY@~:!ӧOtҵGn^6o}Ԁc!A|H p7ޕK'OIO,M9nggmr/0nRmC2"V%|k溸 iΦʻR\ދ( oiC! O??8cv-&S>O/ |LO55}&Υނ L?k&K'Bk)i Sd.jrU`s} 2g9.(H0…O^Mt>90\>/̶jVXWMuD{٭a8X  l tR%,/fl}ɶCeMf~ y?g2{k"T6@IDATdd9a/?ltSNqc.b1]te?uXQI7ژ7O|Wxq̰4q {10Ǩ`@-u2! è{A(ڧE-f٨g߱#ܛ~t@Un%3AGّV\rt>!Z ;i&ϸy:FߛqBx>FŸ',-Ⱶcqywv@Dze=-62NUlB"<;|{zT(4˜J-`d~i"8fO>X:~xvڹ団_ϿyLU ߏ4*T ^~ci޻:sdZƪ5su;glaRD77ge:Zn.-nZ:٥t!+OEΥjXLhνł> ٿH*Br~@5q~2nn^ҥ=_# Sa%8[+cbD'0C~ޟS>8G"og Bΰ k}q? aUqZon-,_g%A=8;£ ?އr'=2N-mS-kD(g+;m X< ټz0efT~ά*af~21%lRwϤ[Xz{.¿_ߛ]9;- 3ϧC0o{L !p hȴ9/ x uu \F9F5yQS0-NAP%AuLn1s^#?x*O$Y6}pG_brHה#ϥ_|?v\h1e ui'$-3~hg֏^o ̴ϔvkWa&/4AdB;LM)܈11H&EF&T*WA@ّ(KQ0gĝ!bȻ"aP-&|=Xr&+^S69]fX;?2=xd%s컂63$ZpỚE0:GL^Zqr BoS,jy@d}cPC:Ӭ亘Hǰ2-Ϧ 9n+r|ĜyZj@FyNe>^ztFںg?<'8ȥrBir*|X@aiR\,t֗Ϧuw!'S*el3ȱ&`8#H[yY*ʭG*<({2w; ;n{WR [6bU1t BCWpZOLN'E4 !Fgmm{GqB, UPDL+G[:?Ƞ8U|/D+UT-BIlƧ9;GGI%}A1gB |SHgq|?++mU P 'UW;^|֩c Q[`_֕q-'2h̲%A؃焢N>tG< kŪK 5Cpo1h;B]f1M&4|}"Bq0ք|`c[&0ex/_`]Ag+K?s . G #p6}@?nўd9~qIbտv"mk~>19mlAZQa@0,v7XS^y/ ?[#q:H`05_j̷0΄c0T'P}ܳըdhX_!D*_XGBϸ~!"`ccs4OeCFqŎV..o߿vGibq 1jM6=tY}\  B0YYcloE ڈMr;Xz2:}u{x >gmj1E? ^r_f+$ /2KoY-0(г6p-\Z&6z;H>nO巾'D*|gZ]Z=vt±/C\jrѨL >nw~GRrg.yi?'MkRN$3ǼDMe>3 LfgJG `{'.=ʰd/Gy)1 I13G -D>tQGW.uV;>iu&0/@;Rh7yVI jbȇ N"6PL$- '+xj@ip="ਓEpeƟO&{<[M{ikh69Oo|qؿmha ϧXwRA z0S1>g崃YTN5w u!|`1>^ZiuXp6c4"@ /S%\<ٮ <>\ ǐ /eAB 5&ON9~}6C D" A^59KQ#E(xY"C`.;KeT7"D|( >Gn3-y*p<Iƻ3_fWwsF4vW҉gғOΥRO/S2Iy(mB26mqfhb "[jڮQZ ܗJ&?B"?4RpAй̑V/'ӕ/g;_g/eЊUԾK_?׏'OյPr+d00i['-'4Jާ18!wnQwIr'ȟdgc=X2:O5U0:v0L셾ϱA[[:0&<~ʺw2MGL,*`%6I;tƳ.XO"&CLcMm}'Z0z>*@|wЮWӨ;JN@09FMنget-.3gx'}#NJ&*iY3aSۇ5>1gۣT:^M7!je< 889ɀ ۍuV0`S&}]b>ʴC8 ?͵A"pv[Ljc<ЉҝX*@XF} a8-8y2%^H@5#gM"\K'ٓxy ҕF-ʇ{Q/ t*dAHFB'3C#a7pJ4ģi?]MFZ%7vmK-s( -,̌axTAc0rrڀz\L7gU6Ѹ)qǵ~zM'ңv8 ó@\걢 `×lWՆK~VGJĂWL\]|]̑wQT &8z-ntkcmoOxGJk@%x_T=~_OD~ͫ0-tS00{,N>sy5Iq/0rRmPi[EΤe'|wċ<\ybˌ .~1D3:inҍRduڍc\eFZݧ} 4lm&}=>jSH60g0E"`H ^X|r=j:BzC ^*:zϹ]G($/uV%H g><ɧI+8v0~{wEeufxWZy a ]-WrM23aZfcIeA9e&1}w~)7J[{z>B baw>VRdόIOrrvAK96 rԶL (y fg mX,iZD5unxIajtiCV'-,,NZ0Q[ _L똽_Y`ks 3{t[ hD!ztL̙}7Ĭy^+L߰gg>s7Cc9չkk HcD>{JIw~B]--s8"0vMa4ɶp5]IV3꺑y;n%z=j$ݲR |LѤ z*~v%9_s:PJa_MM\Y!;X}M :0N0hhK9:Xe-`LDY`p HhԴ߃ZnAD NGj^UT[0km~#m聰_\pX XX8qȞ54ac{"KWFe|'JruY$1ְ;dL}3}prCO $LxsV=%xw摙AE".<=z:Aj%l@0H  )E{ k`@aud*Ωޤ WB}"WN _:$EIDbN2vgfEڈ}aA)P5تxO~M]wW))^XIvi/ڟؔƴ:v#gkׯ]lcL=8A5!|e@{񍄃i(,0ʃXF"^pUѓ< Zױ_mc9wQrP d% &sƍhR?{K+#`MXMifS -62f (0O "&3̿]{Se>{En(p6`YL#'rhLA0ǖ-$!܀ͱ{߂#T38B䊔I9-#wbl)jka D,W`, ,m5\Ar?i…)ʐĐtS/T@_Z){ N {oG2e)|qChϲBh{%J4x$TGS`V-KB=Ze4y+Dmȸ;ǧr*|K⸌ʙB?Lۑx enK3?B3Jݴy-]zth +yej3ROۂ c~Đ9gK%\ʰ32x$ۀA0/)H~Gξʛ>A@r*^_?;Aڇm1qtC~M{c0Jf<s"!v1Y"um:eyHqwFҘIx S* _(N=]SQ|}\"Y=X6Lif@K^`h< ۹usj0<;Kz}L 8l5"VQ_`e _c쫄ʱmƨ`k{F #j'ߟ-cbseHeE9&VaN%Xա%bIeA~0e4?+G Y"䎶LE:.D r?t.M)&e^o ƥ&SF^6 e= 1T?%HBSl|alx'.u W)X3PQ Ka<ڄ+p5'rRdeBup+X e2Wnwܹmss=%zXb<RU V8/<;LgOrT-c/rDZf/x~*Tn!ˣ{м9 3 Ǯ!cb) UP/=&@ƚ{Gkiys+k c{f};s9֌^C d xY,r?G[X(hO H|)wF8ix31w:k;ߙgN=-#~~o߼x$!@%xHCVٳ.?..a}s3Ǻ^O[R=b] _o@<3Ny \NO9YoaY0p4{tN)-u- k!m ,#U3&}筽QWYݴsFk#\HG؃@y13hZd2eoX .( o9ty՟bV0KV*rkbMԂ?a:vJ,M`.g 6C]co:3?zC?4,=t*C0 ^C@E0۷|':Ih#V^ 8 /̄'mKo޳Z e$(*Fa>\FaIN8mbZG %- ^YJ\ '49a~RŏQu}J֨Wpј%}6w&O Tm UWeݣ-"%Ů;"XJ nΔtK~'e9ɕ8v qLD!N `n!Oa9p<&vEY[IX++8:a05}sFEۘV*T»8 ?q7Gk Jcee\\ezOԒ[IK+aS^nna,Jz[oLSt$0M-ZEp l)"9aV2g8rp{{n *3|zPucFZD >N^o7\*n>5}::}2UԤkM aJSU @xetD*XT:WTL/Ys7 ''SdL#GFW9G:QNϼp}}Wc2WU˓7cc nw0N}c5`6`^2goEa2A{| ~uG e%-b6̹0kҳ)feƔZ[`+},y\^ZjaLM` ~c}z+5s\8G-. ^6 )ބuߵu\Y 35BҖ~Oy ψ4ē}hch|J'; r>ڙpV'[ 9p/B.wƜ}#OaΧTBq;p)einSa M`#0 _:VڿѨ@߀|YbLh( e;[ Xd-AíUpgg}b6@Y֢ t}x5u}ɪ?OQ}LELlrD`c5kB#4._z|~o bc;|*}\$JȏBF3L?[Ps{lh!mbۥYF -u9'ҳ {v/ 78tU.OJZQwrbk9@xNWUMnKfu&W 9 ϩ$<4@*}%c> .>Űz 'xY~J&489ZJ>>ciz0}aP LDRKp U r\!BP|~]~! vwQ#h;z3eo4\~ٿbٮ* V؏F- M~d# ;0 0kH {0?G#N'`|)AMW(ΜG0>H&{J9bQ~_ brYaW0, @T[=dR8ڹb>QhSYV1A@u`*+QG} TR6&>qD, ,ඁ,w׾ӎAXU!hc[GV9"暴Axcb[YG c4w6Q vW0wNXJ{'ԓg҅Hk)s!  -tb,wi(qLy5BZ{2s'{_ʥcf9ݴeFZZXJ=X:ut띯]}7I%L)*=/}饿}ɟ' W%!X=ɊGuz T=;AV|}9ch&_}CHNF2^bq7;ݡ_Ny79 #%K"5 >[˷ ʯp. ҵ?PQHxy\ kS{0]&fd5>M`^Bc*$\79Uj:}J]fo 07ᛀmY]1/U}ʵ 5h߾_4_Gl ٯL |m] ^EGFpyR_zd -9eȂ2ޗW_k-VHYg\ET߀g xs. 9= bjRCV]eSU$KH7ڬgy289 qjj|ト#Fs\([,tYq.۠ ȭPg͊3+AZlr&m: G5Q9e,I817e;g͙c&uU~Ἂ~qRմMcg61gђrsHyi֎l1?5#i %,Wb2AH@-KU}x&|f|Q hFlEiDGq$01Μ!COsmؓЦ * o{aJ(`hCԁ- {(*jE#"!̳,8-R3̱w~џT^!ZZg8($scþVjwO~XwVy+Yr*|#KyƖc=/Cx9&-r>&mL\zf)H([M@ampj`8GĹ3.#]&%*(O#?V*ދ8~b#HX{80z]50;"4֌zEg>>/.}0+Va@%xZ]!/|u9XD% ḚC8Bxj l08WIOJЊ˿ѡdg|۔3"d`|S&b<.9C;".Lߎg !6 L%ZGnֆ>@vzkk+]0iVh=s6NW,il88iUn」;| HoȋE^᫟oYXn /[NU Xca34C:;sJkv1#- nN 0+0N`M6Te_ mVµO14@oTڞJ{148S{35aʇ Wf-W 4Ѫv;Qaww4 " ME2Ӗ{10m 2h{{Z6?:fɘ.7)PjĞBɴ~Rjs:@)\$/>{!洩;gT닩Kܩ_iu FQ@VG1Yb;HV)q;U GZM_BT()⳿,*Wv?8J1jO9mg]FqHm։OyMos<Քˀ>E£z]LœU8Zks-$.~*T GyD ]3Qf2_K 5dzAt6Y۞Ãiz~4f{;^l!$bA/`Q.~7??o*ǫ=|W~fkxOGNP#zƷ{&4C] _xOwc; ukQ~cSA鍫[+{{iaGj4{/)3!s?\fɢȍN0峑}W=CrrywGyeDۍ\6+H/IgXmVh9G.N8:. M&9t9q-Xc@ xAa,-.B\.p![3h͙;^wD{ ^R҃#"! X,D[Xwm "&r=A8quO؛3 +WA .>7qā;|኉= 9̻޸ylz ,s,` ӱ%E;A@?D~~Wsy Lb bKwT eHe O<+̓WKv4['OǠbz__^Ç@%a\pA_}ou[x6~sұ=  j {ҩAwJ)Bf># ^Aݸ"u_@tQF5!yj|9V2VL!.vB)b)ޙX!o:3vDzg@IDAT0+ֶ5msc~LWnm[0(C{cP'SL@SҖe,/ӭ.wJP'k# /tvk!_h i8LisW2O{?bd-fPy}(8F9`zl# Ν2$JbFg?~ "Rf2cM ǃDg5d\ ӨﱭcvBxöΪ[0F8i`v@}!;K@8S). d>;g|#7Po蜴D&N7!d3JN~/fwVVYs)!݉֏<qeL͡K@>Gsdy/~O9W%ŚYaAl'} L8/ gufhjhM{Ŵ3hxOa"Yv\N#+c:CeP[l4m͖ &h(A;`_26 LLlh66jwp*m٥?,1w19Bxבhag0~ ! /.VTfy1|F'Sh`r5%Y0 XۗŤ"T"h*# PAA@,,wL1hZ(F_̏lD>O.k7oz Zԧ8=n:~b8\u>q6c.ђFYU!+_^Y_KO>DZ@V+NMVƕ~ApW=C` Aq/ sx&CoTylT^V裤Yb`GUA}9$8~d Xk'FPpv/A?sBP+Cr0 +QRN>C^by#ri b:W@RӮ[:<6ZF bc= F>Zu0)v,P8!:CچV?Wt`g}JczDbpУLvKQo`r!Po )'bd5s&htGʥ!Mw6_fmi͏g.C;H!~T-Tq07hOxDΚ1{ٟ7_Z?yg.6u\&w+WA']]r̋f^45ppPW)LGriȷ: ƭʹ{3]Nj?GMN3m 39GD}ltUmh_`Ā"ysP\Q\H%1@jA[L ?ݺvǡEu*1@%4dU/?c'^?rMXw7--st*23hϜv)JF\bTֹbJas78E1$2E޳!9Թ'URYJ"{`p>@`IY-[P>cm0*cT޶:a۝~zV.cu,m!1tƅ0ƳVi l4X'6 `QeJ:'}/ۈ #Ԟ`,/%EV!C|Z%wA> gs^7FP{`vZ@t$t3wa 碽9Fz}%m9 }sء|m|>>!vbdUe,M9Q  9B&V4M| 4`ps5T/`NF{lKn K4iEc[aoofO6qCF+V_P"s? i"CxQ*> ,q*P T :2v|sxRMuo-Υ+BPۀy B70eSZ>&[~ӧ~3҉ˁQ'x'"J% !'bDC>;{;_טZik;1vIp=XЌolsobWY P O W!k }`V]%;uo/3Bf V(bg!Vރ'Zƒco?Ү  y'#;8q0 p;!0!yU}: ~]Ə:l@Ͳs_6͍vpE5xl߀iH&7KR٢. *PrRJ- t ae~ȝ P^‡yϱARr,=.hlMfOzPV{i&i f RfB>SQU6 |W9~ߋ 1& E9@;|.C౭I5o- 1VqZl!z&9s*=r{dZ]Uwrm=&8dzk6 I5 ]$DE'(LX&Y,^th:DN'86[CW/^W_g?x^U%6x/B;XjCd?a]+Q B ,D.pm@D,߉7q,NħR2vcUq{ۨWny-TGmB4&,N shʕzwU0Qʔ \t~U7a,Wy؝pE; p ûE\ <q|̐_E:2"Y 6p4ltY㎝ɱiwG1̳Gm%6c0;y1Rysl̡Isfx/uPeZ7d#"T@Ɉ-lĂyôu뗾OzGԏ{Q*{a}W {?eW78^զ1zfQ;/W挗`f'>y^9?q\]i~k&8c0x B ȢWvc ֙d05Tj#ĺ0yq{޸~=]؀񟥫X.Gy񷴌q8T= MdCWȨe,֩у֛i(ss->ʲ108?;>0>x)C(1:`Wb.[-ƣR΄`@00d CY䆄!jOYrDo+Q7:{>.LJ1FyVM}Vۨf˴gO sKD'q.ih&崀`F!7y>EP9_W7.:us:Ro{b ßZ|wE7ag c:{1Hτ|eW @A5₃@U5,cuTМ@Oi8^3G[鍷;W/e" XM/tڱN[JI6FCIgx'[,@"=(rvXfv0$E3h\r>woQA P >ԪoKn״]"1TJX{ֽY4H`e2ъg~cjO ",WЛsų?_ 9c-1 H˫7nm7v՞'0N.,r[fVc{@e!H[c^̰7Α`.=\*e>:Q")a @Ə? KG|wPshjLA}AJQus%2* :0h!uFcd؏8 {|fnAA~lZZ\NӭK>8jn!<¿!],opMhalԕEm_>yuoS!fYRÄcMDeٟ;(D5Eh|¸h@vU@n}qwne4sTa?F%M<^У4fcNTM]޼z+N 8~j;.6g M?QͲpi]*ߥ |H9`)TEh=Vasju+O%bm7UWT+8!j7z7sԙS Z-uƨ½֩IX~HN9! "S5KjS0¯2:oS$- ?de7F[tqk?rc3]"6M#ǾՙQԚe  pf2ߋgG)>l8P> !rP'O?T܃18"ea[ئk. "!qMovsx֊GP6ₜ_-ܳt D? ܒsb6-P݄pp VrP%XEXѸ gKi hcOh0jhP±Mc7165ZmI޹4[Ҕ=4Em@~aXяD1Qwx('hߋ1RTb qy=iD2s8.$(F,D~`Q,I*{&ͶoNO9:膐811&TUTʮr8J?*}T@00`0BCʆ22 @BH:::ҹ}{^=s̜33=Rս޵k~s_M; K׺1&WeV4Z*1h)Ig]nJR>cWZ-~%ý+K]z5PSI>EV'1X&R !UR@)CtPS L XUqlo:ow6y2Zܧ"; DAG7Z}+k{ce6V7F;|[Dnw;_wvMBt5rՇ^w~5=qB~:s%2挧2[o7#l].,BZkxqǩ¬8>R0u .@W J.G?,oNʇx9P o;߮~ 8f|>_\12pZP[[hGń`6ܸFMP3K{ntupNwG>^: >~[xVJ$h aӍV>| pĭvHOm>>mNKi8|75՛~_'?{}p = VXU%H ?Ԣj.K LY6P8$ѴˈRT9.H 'Q)c-~T;Te V–C$!T%QU',aw1eŝ.H{03|)f0)hlDE3N-UkaGz|w|_a3u h )!~!C2X_ԏ݈nFt7қg^I綉hbY^T͸Uo Fx㘣K Oh `+X9&<Y+ȏΡu5rW'&,Өߚ Vyw5C4E@ W SDbљAZ2yzԆwPyu bḩ\^Oc,%FW[;>?D:{_af')lRQ*}E}ƊCqEm1gi5Mex<%НTjR CA\ _WW쫦Ζ5U|B>cC7(A 7lڡ>))Ѧ" (a@Ͷ`SL CcT㮂Lz~FY. 1y_NNt(/|J*yﯜNfCȹ}GYDvN1E8iEA7?!X%}!}x=' >ԟ$ĭ9[Z8Qx!@Ƥa|57;ʲR| g>v#Namğb7J |7D@ҍ퀝p(4d4ISPw܇$~ 2Ϗ yWZ? 4,]ZU+*GS72 x˸N22.22=8wi/_`,@Dt *JZ-j1'h6ݳ`-r&ݘ`roX(/"\6o-#(%=D= C0,@2q\#tj{+Anl q9wM6+“,(̸$@W#%8LoA.n}1٦onH~~usKbKӍ1`ƒ:|!2یs5UT|AA(ܶ3\~P 48}B\Sf, B;NpN̷o5p~7mm<#M@lfwf sqۧ=6L.9ⱛs2Vßçϭo-{fxuZrG~Y߸q~;~O1]Bd÷ZJс]Y~twvE FHzflh-ho_νf:uz+;w"}eՒG<{-ۥ -˒υEu/#6T%Ix+{:A ʜ?",./zطN9i8 enqԵ?0?1@ ]A(ӫܐ2渗 v#1o,[%5J[#HڹJU/36ԪUz'O=6>I|^1w?+6%byjp]K4E2cJfB5qZsYw1 k6İnk)}7җ1YU[&,K%cռ{+mJ!5=r}ɥ㢼2^Y)Oeɱn9̣1wM}"b|B :,HJZpC\;=$qCa;^'sg3<5(y"0Џjf'f>vb5B`K b\GqNr'D` ,br|R]CiT w  ^ ]p s}|BƓқ0̗_I]r"XjmdNЇ.2DZ:!"P W*hkAZ |{O\b}G{iO?WzƣSdMT_Uү|I]A4̞D~S/n{ܧe.}[I)A "Z0\u,BtS_ߏ  I`_+/ }c %X"V{g}.Oݶ٠G fΎ0s2%qvc9 ˴oWp!2Ù?}5)/itg $fڙbI-%#w/ȏ#I)*}ڔIa;ͱX[[KO2^;ch,~Kc.D6 +F̮[OtHvWC4 =Tog|>v6u:P-0/p3@?z#y-**xKL %Wk6 t2NƄYYg-VXw^;%} "qvnУvQdpoRb#YzeǙ{Z'ƙ؜@a8}2faj洭tJ/^:?#+_)^B W~g!5ԡCKF$A[j%$%d[<;0ٖյi ,Is~[N^jT0Ӿ^z=ū:R1k.F'7鰈;K&ٚ6XYqkz=B\89h2=a wfs-8x)T5e%"Ƒ|fjz0(ݔc!8d@е9dm^ Z~"HOi~}.\8TyZ~GS>wf&ggr5_rSߐc g%m$]^N0侸f [[|Nݫ= .+G3=p 0Weae_T B4yBJ}IN҇;.Hbݿsj(=i1Q&i-ؠ 2d\xշ ra:vzO/|?wzڝ!PwO=A~~ y}4)x j_붭h+z=,E^4sCxp %YSqk+]f^M5D%kO݌a 0u, ׁ5@{MQi썹]GIΎ^/CtO!o9H[ BG^DəlCY8@aȄ=/1.h(~%>H>.]6@ ;F3b6A $\|9k c%l1H/VFQj|͘dO Azz;1t4 _b=Å @,/؍>te| W***x'hF >C 4 !y%++ AjA[{s ׀ӈPArm ѱq;<+M{-n(`pyU+=Vyޛ.SGOkߵÇ@<|+< XteLfBbj9c K8@w2qG! =nڎm15G9Ke_}70뿀AW/ ^JnC`]K|mfAs܏RaKZ0YT{g+!8Ed4{(KA:0&Fwmnr=ax&O$$6Z|t6miS͕Dΐ?v& bO!Rttƍ 'dm{c%𔁻Ĵj>D>2mAV5| Ao* Xo<)l":^ w;Jy,7N?J6A>.~h˦n?DZ?}-_3>6P"!hVy@,*K_ƆQ |z.Ej4B۰a@:ԍ<^K~__}] P  r|h; T?5Zl/YG)Q#8;PhiʯF)`C i>̀mJqGGkN /L9={ 0f9?AWP$W3LGA%h0 O5ngPM5k:|Z.;3l8Gf,ia< R*gi`&a\ hAPt|8Ngѐs^Qӱ@%@goTK8#/zKXZQ8{lz门rU*BRf8=@Hxe+:2c%&.SkFRבV@VLvj<G+#i^!q2Lxx@+)-21ث=x kTS[ tAL`(&&VbpTU@WpDI^/r!|tŴs`I02`n&=2M\khAx[\[ir 66(~ܠ@Eꋯ|jC-*?yR_6 `~4>R~m3(y%ؖD҃ vS>6j|*y/ooM4_xy?5wQٿLY@"u5VJ@@In0m#s;H[~[N|۽hg[FZ(ɳIvD̿sxW󄚎jdDiL+~LZ[h7,#/7i^~52{{{a 08.cy :M1fiP "Ekqx:NcMX|a,[Vr;_aSM***X-y9]U zN _YT|:}ۿw-/1]n{Oz` cuфf`+3BO@@ݯAŮZ"X ?V **,N 9q[Uk]~57,۾ɜ,yMg9Hם:l߹\R]#em4Ec8 G,Z9K>p% Mo߬-ӵkҥKNMy[11,G] z Ms]r=D2R7H+بbVrSS[VR#;i["W!ZTTknM&$9f\!e[-4]@3R7 |_ڟ>^|Ϯ_?K/:b`W:@0iM]R:onUٔﳹ{-nC= ~aξ\z`J@@1PM5O}w>$=Y:k.rՠK-B:)@v T)OI%Y$Qt647]sRC$4g,갓>KYz2Ѩ'X DD̦HreRjK6׋%(E0ݲEskA o*e;`h<0Px/@x,=w$3!c Na^3r3׍ ^)oS4:lr9}Ï|Qp!AIŋ`}} Jzr/GnBDa-^3Q~N fc=4 Uf-p ,Q<L4ER#t0}zUjd\o-"ۦP`"DЊ oYӬ.c0JXީ)>h47a@@0 [)?Eˁ;EKv2Jf ;O=Bw/L4!<V#\fX -z'S_Ш{OSbZ_4Gˇ 2:c8crQr礲@en1W`)mSٶ& F,%\+-HHCJh5ՀB^ˣX:@@IDAT***OnrGm{%W[^,TZ$SiGy 7a>!>O?n_/^}"uI#Q+3Tw\Cdpr :ƹCwZz}OaŐ>OgO Ԇࡂvn!{jh52p޵}j+ [|8MۊLc)A%I/@^m~rf_6k&X]Beӟ~%]ȟ*R 6׿ߞ=DQ X Rf?Go'2ypMBxDd1@ Nx/~uܙ_A>F@K΄Gc5=JNI@1sV}~pwciPv\A$)\$Px 0ʕ+aݤE8'Vl>F?ܛȪ$A<Hf?F9ZG~cXUgh~KA+Kuں @ᐌԧ`ė&B q%F$1@Zi,V _L/(7m`JFsfia ݦ>n0^% ׬=mlsivZL.&[qC -_WݧN:EQˮݢn( tI@`iG`8TcAF= O_Ƌ;}NU#{~oxu').Kq*๕&,MevkS`xSe y{]@@@qO^[kՎ 5(ȥQ(x$]MB?̐.wq,Qtܒ ǝ+ĕ<%;dMM߅LX| =6K&)2LMKG((NJ v+@PW@3}N1IR[Q5g+mtum1jbk{\ +;Gu BBBBV\");#N2Ĵ 1g6^ lgjSCy+W3m_B0[W1Aw$Cˡ~ X }N! \ 1VXglA=7/e\bLOxH,c2hd,)Ks6?GAn3x^f%SFXQi3,vǸ>?+S[y:q~߾~HHB5.͸׾Ko{Iqs6;׹]$}v}u=MqYDZ"ٍtc>K[tpBృ~>&P}#?N_~}[ >N)9rRKג,eq܈%WYzŕZcLϥDz>}iZAs-V$b  n. 6 R=ӂdazUeqhZzRM'I!(y wfnfLgVie=I>[F'1 e |D#dvB.nK#ﰠ{<;CSoq{gT |[|g `lPew{*}y ԭf'+iZb圯Je|;QݩBBBC n8֜)/qڭ)3$q)A_ptbA] $KLm_dWRXٙ{rX!з?C<rI)xJ.q`!e[@SGf_;;W:l;113*x2!$mzk.^C7oϩpP׼+c2yj' +0H2r2idcsGZ \ÄIUFq YkB/g^^! A5˄gN3@MH2hrf7"摣g<cvنs"DZ?n c_5UTT4Hmsn0cXY6M?3}ҍ}Qv h{s89w_՚;W֩SO2o,?{^ч*xX3yg=3"hR~͇?XO?f?"c_JW(L>Fa".j~ @]ө%+zy/%?Mџ!#n"ÚN2`8:$26 _-&٨&G`;N"@)I^3ɣW#QAk|DM+}z88w&>P`ס^UǛdDbɤ@b %Gc }x?<V{z)¯CA6myц"0H|90a_1^BBBX `=VgbB3Z&e-4rO~|ϾB@gJ(. ~`Inڷe^󈌿y,(Оy3,t?s?]k62Rx|?g6{02la2y?JrK۲/ӏ3#`RMM}:lk,Qzqgu&nWҩ:5H01=:껜;x[1U2o33$S$+k8Z0PHH0)$d7O;iksXhl "&#eP%0ږ}R fBطnnfk_X >^LQJkX Cp#.QdbNk3\Ydzs&e̟ө/KjFƧ29 3 phw05'KX==*!>Phk/Y([id_ :Z'T!P!P!p"!N7`a9P0 RV"U cdpg|#~#zkF:{b@0"o\{KK՞m ںx@'>_5=J0 T!p\kŌ)kyha϶ۑb0:%kuw^a;ik/hEUe%R`0sUPzwp-y#?g0ૹQ.p=Y(협n0mLK$i9 s?mpA\ԁuZkUd+J{[,@IK& &1Qk" @J3!9#77#<㑊K@)#;ͅRb(-hppϛhE|3ͅkQ!P!P!pb U\9z+,`K:=%W^|2 ilq+Yv3޻2jZ?Up/ЪmXŜ 3~qdO 3.w=sEy hi(9@p2aSt/_f mFZ .m .Z[XE&sE=(cel,EqݱҥaIƀ1*wXsnli?Kb0F'Sxdq+@ h7{ pc0d9@>aA乁LL_KWPˮ q ~>~& %!'@X/󳭍Dn.㟙,pHVHkP8+ ]}M'  X3v&'o!BEN<O= Υ^|t XK\TAS[ 19=@g\#cwlbV7O=e }^!kI0j?`~sMANJ)ejؗ]0]ieL??ﬤh^/_.1[!ʠ@s* }WS@|ٚ2*2o' TAl&rf"f;BIg>Gdxz. ٰT$`Z,\odh{ NX*$ )$Z/{)V deSpy{\;3<‡g-@b=U&\- d(9k[i4QM';R 3̿bh\4^|y+ pCa e!(]啊qq;}m}扳ie}5}>?w{@3 I~/B1'9sL>ehk-+ ku>@B\Q$sbZ@WGVZI@̊yvl[q0mU2PO%3&gY ;b`E, jky5#xGr \9Ü/@b=K Xo@:r@활w@T|*/.q1 "e<:2e|=`S~yGBtBXpתBBBA|I| R,N $L?-W4`Y>|`"2mSOYEc-65 l khAz>'U?hO~Gn(@Aœ_c %>^ƞ[ϡm-uq,CP"O@;`^zi(!0Huy?"R>h;L7GcD.QSfǞqGT&d\F98rB쾬P7+U^aF&G^)C b-kQ n++:Ĵ%\OIvBW`$Sܮ}48` |{ +} yn(9t4m¢ V|3~o@@Xu@MrUjUtZm0"nXvWmK/^xt}ty5P RA@ /Ƴ tr?O=Q/6S,x'O]Sɽ4!hbik2]"J[f*}#D9OvSD t+#*q]fqrc=iQPh&O[rS6673X?ns?ﵶtoC$uYj.gd%.L;)B+URS%4Bs,#@To0eҜ`!"v* 2\y_YY+*]Ơ X.;Svș8`?`_L}C B c2cL 'Ng aa2km l: Ӈ靾wLط0L 38v+'0tVcoM/xB U4D|H[5ਹ< ,>RhJ1 !I)cupArt5c 9"u|larQz~55q;g(c_0&'T!P!P!ܴ8"AvAǪI<8Z^g^7ӾKkC{H3#_2.h&6 y՜5>JMMVTA:Δ{ i)3o:kzǃX!pWֿӫgR4(bO h?4B?7( LpUwt Q8#V:Bp7H׸ok4jI HqT(Ni7r}W) [B  x9 L1(Uck<. b8ǹ@oysucd/wr˔`zZ\I߉ZsL>*:\ج@^Ȁ,:]?'A:Wf$Yo`"@ 1A2XO[8k5/y!$c !(FxFSIg~DaG&]1VTv| 4AdPoS $L+,dc*q,O9ڂhK4^K,xP#})>QzX>s qzX.sSS@;.|W"8D`USpnfDe7kEh#VKԨ6JAα@kqyJBx 03S~$`YFD"1 Ґ)yHd64̕5crDT'5"& @-U ~Rat ¤O@Vq;tE!Rd3vt'4GA.pI9f(X73*5\A`*0:Lֈdp2Nabl/ҨģI?+"yƬw#Eo>ǖ5;A )nnd=[0 AP`*x3 u4Bl(/[1Fp9xRuYٚ~!8IJsNR=[}Ppl+#߭YFMDV}0j},WhQ 0K_^>4iqX lg t}3&87yX6ضQIGhh$qNnyq&0yv]X+@p<k DmXdpjf8_Ԥag86d{SaQ@FA ?/ʍФILZ5}Llz!$DxC,Xty}Þnp8\ꊢ7AsG r)@hadi'vavزafw saEMh|jc>$y h` [hyYI fMWYwn gǻ=i93oXugxT)i  15S ~@@ɀ+;ů^L =hVZ%\ U ſZO5BTJOn,+looK2vAc>>kww,'괍^\cV4EQ_},0цs|F M$Q(C3A]r˚*GY؝2vJ3iJ0[t•ƞqǙF>e@w Sf\ßKGqGLNjw{zNP0C!M"Wa6VQ`1 Kz;|S(wyˈ}zǠcn킾ǎ2J$ʘȉq21w`@b5~&#̿.1-JJﵬ8  2KӛoNC:Bk MF3#hZV7NTia.M ;_wҬ Ԯ+na|8/,D*sZƶBe%by@|#HLUs:"M #s >m^&&MmDk5U gl '\0ُPSgLl @#L-Ax̔(-& hOtkr3g;9~Ĥ;=b=vkh .BA+\I3v 6wt)]9 xbt\At4j㒼ry2ծw:ts?s0˽eݾ۶2_s!~Tӧ_I!epX!@e3.gKBBBB`?.WrVau>Vj2 \IB[ݻ ޽O0N 8Cd~SDZkirA?~B_#{*h;DRw##;Tmc?kԗR-{`%ax" S@ziH5H(_*cN-, 3] 㹅\Kʠ@ Lb^S0- i;MkUIo&']Y&h]莙le[j%20}muӹt2 $c{0Wf+! eo8yqy <t\y5bu滵]9P}%#ܵX;6%4T!P!pb! x4Lc.,] .$1]~J7붷X"MjzH}Hݾ=;CȥU,`C)rR `@L>̿l2u\dD.u$veVLz zC()ykG'>ƞ{ax8)ygqE'q,x3uYp \cɹaOo^̻l4-j_p%V)Tڇ,wcpa}w~o䒎n(o}*p222N߆mx Aj3Bj㲁pif@A64Ӛ***N@C4iw.\CMZZ)ΨUn-с+0YqKv9m}J#q}-r8!%gȚ*)v6k0=":Lbɳ6 B|a϶(uK@ٖ>4s%LD⺆eӕ0hF"X1-Q'D]c ;BỈH/XJƠ10gZNߋ&(h;a9n4\7Kmi @]C@Ltae'Z_wJmD;3Xт.ԹNg6SH;oߜ( $|򸹙ǝ r0=B*x+@fe91MI72py\/0GCր|6 }V1fMRUfO- ?~8{ׯ8uumZw=]:M\ bF C/A7P'y!5b%ga5;AnR#VǸ ܱ€ hY5=$T!v Z!pLf(~(Z|ygg۶yt6M@:z/!'=P`. ˬ`ɏT\ZP.z ՈbI0]Sn~)1 b9 bZoA0@C=G:!tKKv;bP~? |AM3šgxPr %Y ;dB9n[ϱw;;MV0Vm@ɸ+u@0IOkEPrBΦ;i}7pWLs*5UTT p(&nz] 4lV u[` &2Y`'H|,"ulxQ ![ۭSlF`\kz}@]=~W>{6/k~9yĢL}ly[fsYsZ.V|>}a~ (B~ T08yi8r0ݦs*ڟvY\\ -A9gtT®܉?a2 ajJ+YU߉th{S}= > ѧ7X Hm9:- k+-W:]._CG p)+!3/|VmAߎ7m[–R0^zߓV &-xïdBܸxޣDlM'b?q aZ ezJ¶HWd0Kz*vH.,rLf?~!,_GtS_' ˷tgXGB`cB@YĚWp-'ٸzX+c?.~a}S})Xrs ؚsNA6N|R[yG=r/>RdƬن7FnqXr`(k/y_:\O ҩu7N: f?)zr$"ݗóqK$`ќ~Ǟ漴ks8 1\Holtw}OT~N2 糍5-3[5XBBBDA@ {qN|Yew{De"kaYj oCGr!6;y9LT4B#UlK yOmu .o&5=hL}|t1p5F2s3F-I5h݆h4M5}ۭ*,wȊtwsul?H f0{q ǵ53߅d$b2@cE-\&d#f#AUݸ'1:覍A: \a G.w9CF Lr,{ۇ۸ch>lu#(i\OxtjVQv?{IVȜ3 P{̘bn8s.3"***N-,) ._+W!(z"ˆ@'2@2L2$"lџ?&y] K|pP^S#!dDFQ6G]Z0RNVKLЗmO@F\Sd[s#hX5-Nܗ} 9%'N*oV= GM3QtqcܲjS&+`p0)3*8 yoag<=yftX5j=,HMBü,6]1;zo6 D҉@bوwkۓw}Sso-dxe sc{nʷj <@IDATLnq9"K(v`e}eqn{Bc<&ҁ#.g!,D6ޛsO+ y.\4Mx/-0k;,{S)[[n/ԍ 1jŧ 8Y?nЪ,q' {,}.VgDkAJL;i!眷ʪHqB (8kgw)p"/pWfFDEL\d@B)22ivAK{Eba:*ۍ~ mkKh:Yd*^K;$]Ʌ'QaE$[űf"ҲflǾ;alByv ,8FKn8H*]kub},E\^~d5  1AL=^,+f kO"kٍ RV'c\_s)x.gן ctayߘMM h[Mԅw`JM!r)n dÉBW?uZ[:88t2$>xAKCD.drv9gikHBW.8SҐi4݂{?m\3N+Z~&? n~X7)ivj]3v| [;,ܭSXQS˛Q!sf-Alt#)dME_Nݜf@#mdılMq1=0\ C-MUE4rq6Ǭ sro~W p Au u?DUdO<8^hkszJ\L&/M4B l[DDt`H,Qe]8Bxkyl&5UŚ"wb.4jqz}4W8&~͘9ee rȂWåegn=B=Y $Plx̲N1a؏8f@GLv5dq JE+ㆡw p:~OY~t4QN,̼L, u^@`Jy~Ń-ų̽㣊hFƭK/]m U; FY÷l K{)M El{uU>G˜;j;HejJa? GqS۶4sKK|/_vvt5C^ю91X,ܩ2jSIzNנR0,=FQrby,+#nf5=L @`<62.9,4HA<vN*/GC*Z5\"p{gMz,K<{!=挜kʪb7[dS(5hA4WҢ"ZAR  @P$H " dS+̘|v{c="#"<2c~ߵkצ{9ǎv[AX*y}!!`w\fuw>CQ_U3yj'̑~0=Nl4z`81.ZaK^`JeB`э_ a=A ,0pG/r;׻iGUbYfTWT#ON*0gWtA\)/͠d7|syLxYoÕvo90Z)f+C2D#(J_ToO 5"GZ,>$igg|K&glKx#8)*JEqCLgޚf{̃4Lf3e v!BAP"üh;y Mx\!N/[$/ ;OKkLKR 6 1.18JT\@\Gk:gۙ#N8/q/`)gaOӡ #`[KkLR{g;i=u]mc@cc-7d׫= ˾8BFip;:+[kHL%Q 4mq(aT m dt++2/qΝx=yp֟_/:Wd֥tRew IdB{Pm/E8l~e\+A@G rծ@ Oz̪jnh"ϱ)Vyo!6p*@g @k^9~pb*pyG\oͳ<:Kim?ӆ\]_/'g:aPR&0C*28%DA&'0ų.O8ڴuajDHBHR":qԢ{kWC@JGc1i:Vګiirjrpn:]`a0L2 >Zkn30.s"qǫ5eHLdb`!Y% }5-[Z+m8cѸV G$^eT S.ʷ]Y_t~{V<xϣ[7?Jı2~G-hr54b# ¨OŔ+ʼndipI٩'.jS&9,3}~IԖ'`=5j<]d݂8M=! zd>d5tx_-Մ)r5_e2nڿ-`Z|J 4̈S A -b,# }_,g6=|~+cuh!(3ƈwUC V \}i_ ::W'dym] g3}sӨB;!d 8-w#Lʨ#w`ڌc(Y.q C8RŸXKv9O?!mڠx j$0 DNEz~iW qx<&/UWmʣg{j @ 2S< npzx_ɖ[]EN1L?% O*7 grϸm\]T˖*ϖYcQo8Dpj>:g%J?5jx:̆Fl#l6qL⼳nkxOzYZZ؟@4$mA?Tg2_2DWEE|?Zʂ6bIn\CGWĴ03Jj7>Oz[ff"*60Lh{ D&0w @[R7]*I9- Ur=x_k Z@m.Zawd\|fg7G39qJ;ayNӧ]R764bڥ] 'CW @ΆnW1i&l項f] 5ʝWxHb ZwRWij)\+s qS0VL?8PA(һ~ .5o!D??>=)jBNP'" U|:c+d '؁K'NEď0̿`" B0 ,%V8)!!!*1: K]$ ?y]MKLX17m'lEI<ҿduX ]SḚO`_:R`>+>/>ѷ1^']*+#޹0u!Q˪^q|[hLa`]1œ ߽jWHE /E Mamۍ #KK؆lJAL1C 6ٛ\G45@٧w !gd#P6ƨ휹lyiA5 D@c+}`6)F:H&r}SC@ }[r1"y눭k)IE$B@>avs)@"aL F6 ~22oll l-iG[W] i. {W4=%)D\ApzG) s ~j ){K䠰AQDgʏ)oq|)xٺg{]/߃>}(<[@XG芈,jMpބ>j2-R꿌 VVVr\}W.Y-nCGܣQ D|/!G>t! *tt'&T)u)E.dFyis37jE"^4'c #~ xf@K_A>K[Oog/%Mw-0IobX?^NZo F0UWD9m1|^ Q+XV \f7U9vRBq8:K ζ)im1dGikW:plXmN"6;Yl {nēl({JމE8b6SNw4!!0ᡱ|hd<5~Dݛzxw` : |'f $: >ֹ?I,ǟnb{:΃r~vb=_E*fZ{!-g_8s:gs$N- ̩ %nG&&2݋R}_c/481L5^=+'K=)LgA s Qy\4;5`bkãRٓ64 eOAU6!2#%rd/\f\0N,ߥ Zm Q!% eú4B>E{;m9OvC ?.3T {ZgjxsP= r1O݌|n"؎DpiB[BӭJ.yM4: ;!ݴqAp-.Y F#abp.tC1~DX4) u B34mE#*XTgeF;qv_~t @9 Cf M͍t{AN#*L8sH&ǩ[3]Nר~֌2(gJ>g9+ u T{+i2`‰:9ON/o)Ry!u@%]t\Kx, ]u b_ p 8ߩ/y zkHTaĞ2QZυpA+N3H#LSg!fS萼$TܩSu_+Ao3'Kf/{ds?;kQqSc'4gZ0A{0-{^fB>=6Oϰ'xbLzc7ǤAbnip&?٣qSu7RJwL'x#܏10-w/-XcĴ>*Q4I)<+SY<+eC~\6zkHWWz?{ 5ʌ/>e^NmGzlRXGf2ʿ̿6d3SW<ώU.5ͭ"8 DĜ93}RXNC,4qr?_ 䓓f3Ub;6^oƊU  wi3{t,tCW~EOacßqcsϊ1}"IAF"dHXȇ} !P~=: 5#]G -N =pvB'8Oq{B!V-@ =svFXPn(xyΒ)S,=g߼J=GEZ:O-8?O~z@ `Ts$G~ W}S W;E*xhϾ ʾ~u`/i7v<,i_껿Ųμ RF;]>~5xdBx W&5iϖ{+4M6ovv1$퀃.`ap4EuP6ügtby3ńp1!P'  3+Ń8GJI8U&M8ndUh~WDem'i'IB;]&jn"ݾ==JW6YnV_D0h(?? 7LE0~i:I[xU~}v7L|SsMhAG?Z~_;/jy!W{('o2O|՟+q''W5t! k7}8aR01Qa0C8q sBIߨB%g_*og:ɍή\& Օ`vIC ib cy!hOFufK(PEjj!ecL{W)bVm4hB `=ܪfzc8t[1 G΄ZA>Ƞ/pWh$=ibs#/yfaR;O 5LStVtR5H빶P`wO?v#Lf^@<2ya{% DVCc;jX'v;}[aӷmf0_#Y;>ò2v/-hڽ) Zg0o-뿍}4؀F\6| m4,ދ?^m($!kTG.9~W@ƞn~NfXUsB`s&x0YA6Њ| >'$$?!.?_40Zoi d`PMH-w05E<3H(X? {!񢺐S4wdQ >>w8'[]Zd1T"\h Cƃ{l% qoiVɕ1 2- 2O P{xS9;XBf2M@!I|hqJ_{S ϱrKVHSɉ!B6ҍ+f? ȇ4: Wݻ_ဌP35<wڵkq`0`(3T?WwBIy|o9i."?0I&1'Ik~. <?7L9sRk>'DaU;pjk2Ö vkc඗V;eq]EB:2_6MCEw`2C}# h(oW O (ǿ!$,;a㏁W.W9}!PǯȺO:=?WmWF6 v>A ]0sK;CMBDt">qx!?0흨F>ԿI5fqܟ <<}.U*~2T9P <~]y=nN53*w7sXr!fnSk06gۉl'sN7k֖m]E1H8u^q1#?k]ʈ{'BImUd+O `%BXx"hW ;5h+.Ź|EC&x 8%^rKee0=XK\o8%v{'^hfh2v1 _'ՓU{edyX;e@!6wFaΑHPcz6bi02" Ppwxԡ5)_s*8AnKB|6euK}mf!6k&Tvj>J1 0sDcsˑw;`K/XgjjHx o0.t杺{9[0=ܝyB@X%/<)V2$t|;"`/Jĺr^|uaۋQ.PoO<ҵNZI`F@q8m tҥPTe (.RրXw ~j'``[1[5oN\ڧӶ8Xj<$CK}|}}=.cq~]㗈u V1GOsx칀 ǩlЋn4ӥo]CM7>9L|͝4Ы Ss4h<Ԑ&T118_~M:}y8 =/lHܙA_e¿47F78&lPBN6tݸ. [4Ĕ,YTRČ4gg~}PROs ==M.+[s@huCh-+#e1B3dMH₰ Br/!H, x21aZ!jsô|DJBxk?0 _FY <]϶)=ݪLoBͳ6aq#`ᝠG{49gG50.BN v;$=f B`B&sUCD*sx9%ϫ`P8 Z=~{X>0+QO00!#'(^f|{ک>~pMq_bF?}ƅݷw_k RjwL-=cVa?-Ko @zcz:)D0M{wƇgr2Oi0ᨪ+w3`<3ԑ᩿4e|i76ঃG7Oߍ}~qG~Ӄ/0 t)Toft81,m"~m)}[itSK6ieg{Ȓ<ȉf1H@ZkRiPv8[O'f}%.+}g0YeiB@zngJ|A0b{+igOw%t81>`mo.3t>2o7QV.~/ʬ+)'xy\3N˜4 ҷoP\;j9V'y8Jƺ ՇA"b+C(&O` hH$g9̸9 ^C\yf| q㉲-߰ڽϸBwxG7q'ՓG2~$aGXFz3f6LrEj/0$5vs}iγcmg4U%@9'F lؤj S sF+2NkQ~_4z''tZZ1g)m kDBuʔ RL/|:c3}A /.o.-O `.%V2g?Y}6B`qʾ' de[]] M+0Ǒc[n8媂9UqKz| z;ݗc$~~c~IDXJFX>9o F<|y561 ?~^~c{N_M>ΏG~Kl\g7ҝGøns4(ai)u!2W8%dDs=:uPC?qb^{!`y4GV{p 7h{k|h;"V?+lBflܜ/ /Υ|o4" j]o~;3G)Q&>Wo99$~c De9`_9 s$x~ֻ"*$|ăcyjH],!fgLz#Ob%b)XM%**%s}{^4Mf9(3Zt73NƥW9oOتg@+iZ 9ĀI1tEc_9@IDATeB׆4fCO NgA-pU -UeUӔOh [wu+#υ9?c;V={}E.~ztB;dQ%$PB e97Дj6zKrHQ8+vkSC8k0Wi测i;mN>fv{+ui2&@;:櫝%yl>#s".CڽH'߈>"Tb%3*?ټ"EhIs{S,7Acf%py#V;!ps@hH A=E EFB2WV'VZ@ Y-1 I^R%[ocOX;Z4<}Lb/<<lp(OdcU_0ܗ$yMԓX 8 X!^J[[MGl98#P]ŗ`Yy0>Cw1管>$ 0F00? 9|s3,|~lݤ&yyi? oLBgzjʮPL#FB7B![_f+ŷW&MVFW;a19”;{֭$i_'@uY)$Ӎ7@y";ŕ:k6j86txO +1Lde />*}p;״3xx/myφ,Ӄϼy 8ݴ_Vpduμ{"OfߒOڰNS `ef5a@0+NO^#g7wP ny^4 qh\H[f BktA }~{;bi7F@!+@Wyvph3]t8 PL:c`Xjd:dp(]YZn؝{)$zF0A~dM<ކNh#'yUb=*jBH6e1gƒBL8Kܸ!p,^)aEJ@N"鐕}VClC-"3fbڽ(D=pM{!=,iԯG/ِLMࢊp~'e }A|7`_Qg5pV=S `d&)]VhpAx_ڌ1 }WV,,f^c,748àxXbV$Q=ǽA:ht ,S' ̻0c'lY׺1P*kGL]Mš2r ["c ~0A`j'tXI,O:Up @,Bhᇡ R/ɧN}gg;ҟoߺq0H,{X{ ??/äQE`k_eI5ύ+8bG >6qM2* 2c-<"&G#Bd:m)5yg9#qL;{mU;0b2;G_uXo_oNG Cc̗aJ/Pl*[^^;Hm0!CU cfV4N_X!'Om)m>uGׂsw7{U"`yWZ8Е''^PIDeg:^ٿf8C}h/AV+G@-x$0Ց?яZC@k?& j3}~a%p':2q3_٫J(H.I*9'0}%WI N"f m_}ĜHWc@٦v5l^hр0V =LlC̅@ظ$0A<J0B\<2$Rdt}2209E #"DKyX Dlh Q\ڮOXs` 2et'GrErsz}@2_=ۘ 3<ɠdw$Yw ٺxꨨ^\![a_{k/;$57s=IV<xpIQZYCx @mC #H@" & N!Evya{͝ԅ9Pgx!̐\vI[a%*N6@{B FmSpnrD47e aN?VJ 1r:G0vG~ӟ_~Q*MZyxYO_4&p;Wd}o%{>?eLdDNOSݓ܁O^֩#{L1,*rYůJydRYሸHXjNH$q,ȹ+*` Aa15c}PDv)c}t+7y[ҥ- S^Oo ӿ7#c PKlqBƟmJ[}iYT^^l̗8 2!ep.YG/ċF0~5"Ws;I >G")ZS%eF.&;9:PÎoe[Ca4sX[==$DN6"8nj}m-'_e/N#x~ƑDFS ~M4 GZDžXK!p`9Sgy=VXmlxdD* `T}d2ԋs79_wE|Քe!ljZ76vRzNuߕ ECE{`5$T{=Y=}8&{`،mKgCE[X,/jԴMX?Gt\y!MfGǏq PISǭ!@~hh I,:g3/JpqdtyJ?gߊ@&!(G 0D0jWzhª D6+}t",rLsO=m Ђw%Uƿ^.3V0b}eԫ/^/նG0&*n fo5Vȏ#Y$n#y8H1$>$zy{ZsOI Ӌ#M j)~~ d.&ۓ`6U)~˙\OUmDX K:y 1%`Q l0~<+V필Hx<_A =t9FH)&%B "(әlx&i/ˆbU40 r-l7:(DBE"WW<)>E"$Eݨ!gDmoȌ8UM5}G?:BBmVE<&Q&G"[U0@LF&b04z8HL# g넝4>0&u~!xɶ.6)+y7irE J<\3N@`zFNWj';{{ȷ 5ZywGe&?dD">5|swQ?D%xR%v6gc,L]i<ǀZ9w2ԡMD!j5=U@d*d6-Kljq]>%VBC>VYʋJ r\ >i%hBbȅeWvr!@uh?M;_8oݻED+j}K^!S1g_{}/_p!4|7θB\g^^Z#O_!w~|Qu*y.9Od \CN|=URc3L%oH_}kWInZt1TcJ/V6Z,>EpviEJeQwd@c04lJ9_ǮH2(~Cvs7{!kw╺XlEWk_e `Kql&#궙0ַ[\82 [CsK-@yR`:8h /y~#ٿOns2"-JlV=7z6\D>}_xacC@ ^AVd;oWJ, r~zs]e$|4-Y^QU_Qg9-*@b:_\$}Z(PZ#pybյ,fhVF`6 _J$(*, H`G<0NJFV:qtf %_;铝ݴC -ĩj T=0*ON2v#ˊ3paZe )z Z-]N'7#Ui\og]mk*Hc0e:#QH#-(a&,IoEtvbZj s"Ba=_Ze35cՑhOWCUv-woMa{FjA0"yE?AE퓠C='ߘ#(5bu lYtL,u+XU|rz7t-`S'OaNC_/ַ=a^U=`;\5EN4>}ܴ'ߒe pr'IW, Oa$OíKc>hVT&tfܢ09y[;By3 bDsJ-rO!\~+"7V/_[0 J_A@\xn^bw`. ʸLvуq>Azz;H&+mO.`q֩^ a%+0G@Z{+]FZc˟ XJ6'L.DO[le{cqlp5񂆫i{^<0 tdeBXgke@dHu2nTrcOU{{]pd]Gؠv5G:F_!S ^E+Dz1P_a}/'w%0 !>vvP;>ri։̼SɆ`X7Xaž^J|L.*k=) V!6=VTwcWZ\a}t:y_z…oo1OPIwܜ~UVΥzK,|?u bu3E~_,*sFxIr3q*_ԣ;Fv0':[-V0-}tDA{0f6c)҄s6KJ zCYtcM4{"u-Qmr?!7J|&ڊV]'ѐ!lKRPH[SR-%MQjMn˹ǜ~cߩwF(22^2n(q{i L@U}xΗq;VNVEXuҫJI97h'Rɩxw7`j9zU\VRt$ GX{a|S<{dʜNȧQ81xY}g͛{58Js+]I%S2vx7h\#ĢlFpCbkmL̍>ޣ:Fݦdfl9 ;[Vg^j`gB@h|Wߙvo^ND_^leCg`pf~Eh}hgh1|[=CRݭD <0g(.Wc;isUEXa?1E T58>ƔM;'v҉ " e?0 @N@rЃ pL`)uuD qTjCv؛]yj:*@XhP¥f#B=.pUcW5ôYڽ)qVG#b}c9Vu~X0@^hLc_"93ԡ(x,X-yQ<yK鬳Y78sl__< <$kD;VYrx.V Wԓ1]VyNva'0{1L[=hC1PUKcy` QXfQw짋%xRQFfs*TB`r ^0OIykL"B_gF&!h/)3MԨr;biںHUت;,;?D01/Vp [hq:=ՄSa9v<>!L9z򽷯O_~p;ײGb> Bo>/MY'@sa ގ-GZsB> ^칿?Hty}`Ε6ֿ'<8$(߉a>]&?)vcҩݣBQO!ڔ{1 8 ~'DE*`T ?0~;Hɉk'`#oa MUCV- &2"..d֛Lpi[W/7$]}9aA ȉ$Yzlqo"r;+VfO TLױ/pqk#p)}%O>؁pqNc$ay"joB+wFG |ÈՊZ)cw)NY*6*c8.^3&ve 5w 2ֲ-DANJC ~Bbq帓h>r-[ϫ cSㆪKG`[_.T0 Ca}b 2fƐmaV2#;~ ].r]N22mll};߉(L{vJt9G #w22hX"$,L9LܒmK/O&wWU 1~Y3;"Ztjs3[g_洧OJa6Q.1$=kptfKO/s sOslmo`(Jv/IFPm<.2͂38IgըAX}9VA>F=gi_`VNWEJ܈z4lda'o-?G^bq3߼?)GB;1'h|?cmddOtT1YnGΣ}g"3;7CfZjm`?tIQ[b`NSl_JWBNQJFʚ s! BA>uGhu)s@zB;I'Ocl6hIʲ}gP>^Va;x4%1fTM梽v= j@PɟlCs/pbb"*WƠ șw1Iy.8no?>L}Fk[C5:P`d(4R=:>~ò9BY\ǟ +NdXH̰5Q6!T:Hj}=E#:sN+eЧK1r=҈-pEqF.ūLsoOtU>h]u{۳|:#X0sAlԋ7x1Ofu;25 (>#]@ uJQ+nN[Z>)2b8C ?2>ubBj`RDջuU_!\{k~|f̗}~ɕڝ5LG^qۚsؗşӯ}0@hێBg%=.΢.GAmpbh-"LDŽǟ~g  $ LC"$U\#Et]eK@% @ v_1dtrڎ^-GNM[z*%[0myl ҹ;G<0r0m-=ςHIƕ;%+I/BNU͋+ik>0>ߩWgy--s J߳2y3sLJWFI'r|"<pX\/~ЀBa PP_4A!z0aV40C`7,P" >sfpnB0=lye=]~[P8?L#lXD)__&p#3,!Vgnl۔"*U*KQs4A'tA 0s!bDm_~H2}v}]>³c!$md5cp8笣ZUwg{(Ţ@Q.V<)ӷoFQHB(  5UW( (&A}sm'ܡ!}VV;/ۇTs ZUdCQ'U9V0VM#> ,nBZØ1s>}gfea;ߺV{fj-QXV 088p$B($OKAP@@0D8-QHfnvw9߷9oMUͮ29g=^{^LhMQ^w*cHbn GVŻ$$ 4*.q[~a^zC!0S|(f > hn\ 5"WL0g%"|9&J (D׹ o?/f L\Kޞ XG92+T#ғKeGe pC C҉¡AW(9!~Wza)ʲ**UPC|;N(iB2_}0ܜ״O BU%xO]ʸHW}wImJISO6wU|G|ttqMxyNe>.G<ϢMuJ\Es1F#CU2r ysz FC-mhr>8k 2BU§+פy|͹ tݓݲ|Yj4cgz#; (xn2}?ޅR+ABOo0d gk?:aO9>hp8:` "W̳=X8gw#B 9z Ox[ư}} r:Ɉk:}Α io^D@I򙅣@0}0S| ͒<#=cy1ƃ9QՠEXݝƓ!zaA8pHoB'WR K"-R>cfkA >y\C_3;@]Xgpmk,ϩe _qѫ!<ろ啘Y+Ԣ*G 6Ynh_\[y~GV0DX5 Z (GY~*X(ӱ{!9Kzcm;2wo̳ϟX\g|b\Ezntn=V0NnQΖ{vY1?`NKqCC{-6(\=!d^-6*ը#(CB60?Q& 7&:1Tg>5w~*  5Ru | &{ǹVkDVsH;n&iϤxrS͏JԠsl3j*77`b{G >,8ʴ}cOb]};2ay7_;_ί{P~69SCCWp*~mmS2&n4;h)mc7Feu[^{e| ˳nlɄP|RV`1, ݒJߓf<y~"E2GI N]W[/;G̥c!sk86_k{Px?|I3؇<2 ۓv8Īnt M}ow""SO΀}6dO;"M$NkS'$g˚==3 3HW>O't&ɯƔQz8xO` ``;s]fߓM7T{J&љU:VCTbY$! ax[eL5CjO0VtQ T`;te Sp4Z!}Uί)K1Nؓx0Ew!(!}sMuha hݭ'/o{\|RO~r-x7=txTh8_GD\Ӱ~:A1Oٸ`ye(2s>GH>ã&ߘ|$J0T]4rOB@Msw_\et{o>Qr!&G 6[2$`z VIt"E `I9+GZF:]9G0Q$Sȼ-Dw%6c͓JxW} 0S(`ư#OPT,S=IU1 )(ZqG08Od*Xe\ȳm~Ę2giY7@4(&7yY$ D~؇0 Kse}|~v#u6Gu1cOhGn@&BA(G¤IH•9 4o^%o)kYs7gE϶K*VϖR5`ӓy!ZPw&FCX :ÉWC+6`žL{;f}ī3g;/08Rp8"`%y\1Aϕst%-4'xS}C{OP1N0߽vfUou~re}O8&0&\c+Ls.c)qo _~Mu+q"?G^>pm5WZƠ??^1$|ZGw"=iB9yQkB{OOً2ºj!{З7Ջ VW˷|p'7#Xd2pJ]M=~~8 eLR}zn3w1{84i#\ _zuƯ]*?O5ns4>NK8XO kh]Clԟ)#LU4k|=lETĂklj]ni sʿՃ @)|48jg`@tVYO||P&\nlgڲO./xMbQ*տ ^~ U+QmݝPħ:L~3tW`' IgkaIba0L'I>:2v8!!{P=\\3a&X@kIec+}78^YXaE^֕eeN'_-}g 8T]sRD,Q.Bcɰ|{ ekl !XqMUVW\uեLb:z4g^9W>8+K2FotGa.&rVm_++UD]nSE%ط+Ŭ?R?ʟJPzپRwȹ<Zy1'xE8:fV> ¿\󙣍iEO|qܺ ҝvgY^R~S!ÌJr-Q+9WuBQGC BϔuV9  ,qJ4b+8 -tt{?O+A4IaT8 HQ-rqH#ZlÊk_+?;( ;lsC¶jiV)'tUZJ5;e1^Pۢ7N>%@p Qj洲" G*8y@I:+ (ۡ*Ѻ&_@L k޸Yzty]-GJ Q6N_:!'1ΛMVy[ag5F?$7w` 0JX4Qk0L0 #A|O:qLVw Y1EGi"Z,}戽;(5 c)qMx\'m+ڌeGD0 옳8}X˻Jy!dmGl ɤq<7V唕'a,<>a9!W3IUUUzO<E&z Hrm{ˀ<+ #<2'C{GaBN'C|Ʉ|}>$5>U,Gh4PyՕ _~s~~ '8/ۮHSjrqgy"yl <, '0C.X\rbQ_0iO&Y8s{|<\sIްq{?$s|ŲuTأC`;Aq'c\ Rg{}[v,0!qYbDB?ќ wnsQշ ź]TbŊ?ӑFvZx z V6(Gf۹T !̳ri8N=(!Pxܺ봰̀ <3&+~I'oG9so + 0G#&+ `̕gxEWq9,*L:௑uYqdzeEd}(JܚD74`&R^:]UJ}1JQGM"NwZ@5{kI#ʹۆ+N>;-{3>'J͉p613f!l 8)זX^,W/]-?)ooO'|3N8&t;{(N8ߖ_sf^-JfUqOj/j__LXm ]A]syE;MC1x/ETjJ_l^-e:Vm۔Bjedͪq5>OهFi(z%iGc >*sXʠ#ޚG,Ԍ`H-S1daSU-CGc(wjsn_q\tU %%!!wT<cpνX{[^h[| ^{Aଠ`83uӈ .@ <AcO37J|`Kb NtjhXz_,|>!^bC`x5 |rL2zF6PDE0N f(9)pG`\އ'LN5-❭`UROvW=f2^gE*U&Jnhx!cw*RwJhSeSS5qvyLCa5`\cth>+k(ɤPk\]$jZu]e{?D.09fh> 3Ah@C/c@"/ (ʫT;) f=S8a&tB } AB!%˝<53 )f?[’` c( l:ІbEr$6z4*E!&񊫾bAdfk'>{o9ܘmm"-EWt@> qMVhcF`/ 3o svIbIDX18i[5:0<,ڒyjA?VQ9V&6>t_FX/ !V8c;c%}X;IQ&\g#s#O~clwGXh@8)Cd ExsyElժ "B<'yss3橾WI`pj戜9aS񯾒2N#*LfL"oMk(—9@l ĘTJTELQ(ATRV:lwbyq[y8UP W;p1JɸZ WUE<ѻǮOe<-oQ'{keU^x밅g˅8 9nlS~vh#ӧC&u@zw#xZ:@QA|"Ҙ/T5L}f{&[(G˛L_;s VTyFOj[2.+EAoȲek_ ;2W\Ry1 +G?HqjHz MA VEٟizZ+ς<[AC49S>^RTqql~of t<-O䞱MLXM-:;+1I2o's\=]6.+xy)WMH[|K2/: @f.dڄQe!Ձq 9 z⯴5S" $8!.F/>PbZ *5QLI:݁LZl,,7@ [僝sW@5XZG*?F`[돸k %4-d_,N4`"=cRXk"(1-ce9s~|=E$hTlbJtPVN-àG^Ql qeP!}ثh٠ۍ'@* RME&>?~O"Pl ϟ\q=80@uǚ@&OO8X0v u186Q)TGϩ!GnN?8l9/tf&Uw>&ik?Қm}PƟI:Fxa\),tOPA?P^RʅK8 \~3c PA(W0pI՘$X*K_5kI]f+^ __r6+aI7Q_yZ^,k(]@U"dL_kIl8CHw"SlEe61VݑoX%~3_GO߸us" k j3C>Va L\t0V4a!躝}HL}vewxy WNJTm^&L!JZ8P<>qb`@K;8j-{Ƽ qu,\*~fTɳ;?x. U4s00QAUDW pL"2mK?Sna^׃i=t?Dc~vxl{0bXaG=0q 𴥋Sئ#!.AYhȊ}-#\_^[.΍,'Ѳ4ˀүԩgQB(#V5hP*g(}͊T>dRvb/g(&Xia(q`G꿶6Y?>fJ]5Ow&5?}(8ĺ "AD>DLW滛8]Iփ[c֜iҒ=> ?2AWUUi. q@ҘsEiDR}Ļ#(oBY[=`߳qL˅͕lݹC bq C}%U|œ.Z|c'- nIS)/Uf7ݣ-NU8*_1̏hry-Nnb#eNQT?Zq5$(<=ob́R4%k{Rq!o1ƘEY36R~c*jAG"R)Hxjvm,|RdEL]\//j`\!S'D&Yc9rUtiABH+Ê$pD)GJđwĭ G8n٥1J6G6qv2O0IyJO;^=?6 ?2cf\o 0`#{KW9;D@BsF,ПܐIP@B:q5?macY!Ay<г0~8jD`NPt(ՋaX&Lq6*ʂ:.V\Ȳډ`᠑1J#bLu]zzW-AwT6no2ozUSt *5 ED{-8j #~4yWwKTOg""Ǫ|T8c1*_XDlTJcAcCH?UʹQ!@ennw^:W*#p#Xqz,U??)ӒH P0'U 1cx[CՠQO48(F}I/UdچL'"h]L" 1cdp,0ǤJ5ܼzUx|o_n*SPVMYdd!CI_iD.[^UqVZK7Vfd<hhr/Jcex0#:ӱ RV|9YU*)b,p+VV zL tE8".gI{t 3jaK4~r{\k?lxY8$5WZHOh˃`X:GL>sT혊G2Ⰻ#8jS1Iq-?m̗–9Jb|:_L/HGR  d)U9mUx##q5TE=/_'u/ŇB:8ՓYف9>`#]Z\.o^P|[?)|Gˡ8bamcN55m@/~W~rc^J<O&{yoPcMc_~'xJ- k/^-_r<]Il_CM:tr15;ml|o+۲Py6W%V;ߙ2;{]19y-hoݫ)<(F?C!q @z>ް>`!g+uap.->NYF A>Hga-R[/r>_8O#@]g2wzpF6daCLvNH[A M[|Nq!=WYUEmxKq_5c-ֻz2GٟO8 +}E{Lfw*_XыO|]kte.vzvt`H SgWb!LQR**`Lt>ՄAgu<+ zr痢{_qV71l;Q.`xnJ1UaOapkz˘қE۠LۗC \md~޸/r8:(h·;sﳂO 0c6Gu}~/<ʷA -SRPNك#~%9U<&B,mKpb#[coqE'K i889\_󋫭t eg̑÷ 1~9.~kLN-|wѺnS˴qa'{7[_y`|i\;|=Aui^@O[>N9 W`BL!կ~?q=ϔgr8_ g!!3]/SdClwp*]saIf^xảT3d85 Dd { ,]3~RЩO_e*?__iq1Lb﬩WC|_`hC!#sm=lr v '#ih v::l5w~U^8Nh%ʁce{ѕQb-zZmE*B^3^2Z ], `#!0S<4V v 1հvֳF5xsr}&6CtIm!.u,<r95E?QFFWdfNg?h8-k6˯q3E;+ F&tn6qO>N@jUhw&J*ʕ"k"as`g¸]vc=U0U!8)WsVc_;h z#@EKkǘ1o 4WAb|!@7o]2G9y'" uA2wamߨaҀ)|ŇJoR(Ũ,r$S& 3Ws*qn=22E,Rw&?m_J셨>gL㙫xNBFu)$monw8ќdl$M)~s\Fх\jE_B6.$7&=$Pn|Rʣ*巈aWˬ1C0<ܠoaG=xugA4Z`;,KZ(YCQ6cNҰ07yX@0ytR-L(0u~duWXA#6ia% (gȃ1>{ǻHѹSqȓPmUó3³IŽ(& e  8:wϾ/|K_[,32{(/'/3Ad' $P GqNN&Z|;{YAØOWc(q5Ⳗ|,¦USu⎱qzMWJY9߃guvoT[YތU(WdXz1 AS9Ul>}n(8kJ f2JZg5J k(VGyLw5#{0^%k{n ϙ5=0{a4N a;Miɾ[\Xaa\"{l4?|Y5"[X v96vPZQPZqeM6U՚8/4\oԠxd*&Ms^~Pg{6Dtc=7p>vKHQ)鱎nXZd5&ۍ8 rTMxm%8Ă@(+k}F㼯 B=>Vi.,xR+$!)H+|hmecqP^za|͋8P{9r\~>2[&|UcA#$0v.Oz!47y~wƈOĘRߓp,L;&(Lq)0 YL^6G~,h][cv"V/_ ѝI[^G=ވ=8dBj"+T0w貞j d8;n`8Y4 c>wȊhZ:5~0#t*Qǀ9OE#9&sym|_%o,RG@ 6[pg"5Icl]e &:gUD(pQf-E\az'`xg۔/6WT;n/?VH;?|IT>!*.q_'n GJ~.W򵲌rxt( >im1)ZE{|x!MGg:m9^G=%G21jgm(%ǡ2hu(4\p/dί 6ES5vB93e(Wwx6E{{8cm46۷n^\kvRǩǐvJI8e "%sabqGP4X~ԧH&@ģGktˁi] dKsp+/ɋ?xZ>=ga [3xqr2 9Hob 5y! cRf9̅!$C1uF? +N79SU]5[W}5NS4f>y5xV{)ϜP~Ͳ;8ChvE`Zi0E#M&qҕLKaTͼ>x/ )c;&WGd1E`:,WM2 T'8CPEΐoor߆*0$qUtWiwSGVfGWa"pcEs`/l"Bxmi])GT"(ho}bվ+8 1FERXSe>SU|m橄*'qMVuFcQKwF7ѣ=PJ Qp=űf;8ote1bs˅冤/-,PPFC AhcUʚ$.O'A% -@5[ KoƋeٛZqEyi `"H}5{m\$2}㢈xHݎOCA?q ZW8?^y:6~esksIS}~:m,+9(7( iS:!QW !䭥ӁCV[wC>*ۃ'x\XLyfٻyM oPK1*E@IDATJ9 j?$#ؽ]-|"ru[K(zuCt2Dq+x]/b6&lyww. Q{4ؓ?ѧ8e]9M=`p2}ԩq-4$'8.=O x g2df"iꉂu,ej0_6\uq"IU yU\0%0+aT- WG:5 G;5*TGL1Nu5s(m7eܺՆ’}=W+=#!_=Nؓ{Ke=:sEUӠ'o^N&Ψ PPh$C+^H^ cV cfA w5˫˼qom{G#_IK^ϻJ8f*#ZT i ?V6sw^AYehbMcV݅cEUMͶͣRJ\0 ~t X)]e~}{U2X&i !}4 >|hḎY<5^}tS ֫B-=ysF:넀/N"BPJ'XB5(i.HQqx#/Y;(M37`}+x//`鋘p̞{p- 5Mէ)H#b|I;>a\Kqs3RxjJv-[P=,r h?~Ht,BOs<6"x⨶X6˫ϟ.cnYwz]#hj Bt+i5g%w%*ep[bǍ[ۜ&4@ﳜē:k)s,u3^u[fS<=ifw?G|ߴ1pY&±X :@_ȭ 1x% Ɲw1aƬHvq6>bIVQeQ\̒uQ!PWS]MiHHNx2@F/^~ f5o LZW#W5 U6'%Vg˧eB>H ?rȐq2)Fr G0Ó1ϪZygB`s+c5}^-#OqccQX"(Ύg|ve\᷾V 3Lm.z`O̯s(,&$z7ƙDZ7ɛohYSSwmMwo 8a@X]ـ$WOd+\| R7?h`B?I\$qQFܛ2UM5 N`9t˷RuV[[oKV9rYxD苩xoAWmo0>f:>Ն&HG  ƠU|& #slY(['lÕ8w(3~dm@ZikK˺?36q0uIfCNqzoI`,#^GYTۧ/_.˾2[Ր0ˬp2!QtcV4/ ~s +, 21izb@XJvqJKu(\?*83u W\iHL8r*(yĚBHimjIpnl,s9G8ÎG=Z8aL0Pqv=E1PN69GVb qg\sz'fǸu<.7E7֗o|yNl]HQsͯME[p5M AK{(dreaq! (yZsH|F˜i{ b `ҽU WQ+;rvc*ӆcF>^ &2dgWnW8~ccݫ 8e -W؎q<(T#q@AS}W[m`wS{G%KP wp;+8GSWiǃ~v3!z6^})h3p +B7BlFq<(V4٠}kZk8!-ݲ͑rҿryy4ZڃݲG z*7?jTs\*]jdW!_/!uy{?1c<sܵ_Yp^Zq x%ҵQedF['8Nhw“;Z2‡ű=87qV98xRɅl̘ka '/uޭ}3æK Lszb$+Φ1_|Pl#*#f2Y`ŬS~g[/\q;IteFtх 5 xKKz0cSe܈-qDoxCH (ً L*I*k .IyC8.Y!$dr l$жDL15Uq"_ӟ.IONAΓVyZy<+Lx/( (\2 8v̗y>zĊST8JF^|!ѦɚLk/h#‰0%}+$C`~^ĒGƽhFj qfN`8!Rϙw Jn\–B4U8W<{HHKA{KZܟ Ob|p!MK\5r2*;+0%MK3뷯kb,tgōr[PF ~,@4= }㻒QP%L™Wae&qo4ף3wP &s'l/򙎶gf]*g^!-LZ{ ʔ򍿸BeqᙎQ 8$BCC-SNT.F"WWmd%݂FE(jp+кh~Cng|O: מX~ ϕq5Y2JA?'!G' {ÖވL۩h2P:9uXN]3 рCcN~( ^*_핯.r@݈bro5l*iowӁ"D~=b]P2Ɣ r;1/ W+d:ȤQ 1plO+u6T:[7w,yN!0Sbv!w$t`$s@4]Mx{5 $ `pUs|o=FWi}4Ap&][+Nye g0`s*+[/48FA1eBaED Fm$LP#1]0L9S31D\0kZ8Ic2f~2A7KF'幍/rYX]僃s@DXy׺ol+;mG@EB9c|,5vܟ`qfy҅r ˅!QeV{ZLsh~r.ƷLak-N(lAKy2Ȍ+#M?j>H6N$tMl4t: ypV \VR*JY\\ iSЭhǁ0Wqfx?_CB?סVt(Vw-Y|7Ċfwt,Bvt{_[*/r94p*䤁s#9'GЫZ;^p*T=+'Qg̃2anOueL s$K_u|_?Wx?y1lH0c?i+Y,P lӸ Rwl3z ntXX` @Q9TѐmA_Ц/fC\HAZWk'q!(OZubnog57orz?jŸJq9rN@3SU%Nj(k z)6f-oT'Kc|.?.w\s(bZ(/LnN$A-`CϽ`ZEVF쇷y_\jnqtk SƜar5o|gmWvߺX> xw`:+ kJ+;3o@9;ekz7S!ȸW\ \x\?2TWqI"bp#O&0.^ڸX:_or^8=l](=1y6`TS,iM`Yk(2JH7/#0.arڅ啋"30u|~+XE V5EQ Vџec:JRYAkwM\U (ـ ~*i3NxGhM(\E䊃$X2 =mwFwt/0کOkD2mNI,2&}kY^)ČPDeVƥahwYaҴW֘2~(h Z73P'T]>7,<6SΏ]}3:87Ћ_\ۄĹ&?p_(7*:`\˘!M'}̢e!'`\^~+s*% ~ Xe¹fYFJ]W9FxU3Y?Ay*#\ t%ˡI{.u%Y_1iBBpZqB@XU|lpnjsƻϊP(CЭ4 `9@O*BdR[8NCQBJNeQMUѪu ${2 7/^ׁ_ i+ *S rz.xsUmEA~Qk-]a޽Vö}w>/CWGڷw M'͛]n -ƹu&MC,#,FcZ_b/#t^곡|Ewq6I329[׳ex/c SʀzX88FF+ :͂q[tD1D2s$dq`JO9۷ͣ//o<\Innh]ʐ״Y2y/OqH#ȣ& ^J

өa k1JqZ7$/i-8+ӉD콭'nώvzE>[$|MT Lյx;Я dv /* JyH?ש}H UWXH ހUSܓRPL6m^ۚZ,u]gwE Q_(YζZ8)n.N/lg]/YU[=.(7'8%h*UZ"| o5kTPѩIU*if無TۃRv")G.+|,!;?CI+N.3t/mI7ZY(|T<ED=Az_ X>Ų&&ׁ^U5,Q͖RV-xc{emʂa^K6kx0؀l^FÐ ݒIY8"XUY7Ow}7II|79眈81SXeGA+\ ZَtՌw *:5v #jL#5eE 'H@, ;0p |ҾΓ*Cͯ]*;sNy{廯oW+wVƟ/]MV8^f{-s& :V+]7v᧙j gG `›jSBboC`4QtDXZ<;zr]fxpxyLj"l tJx$qptN4IlB)昒aI 8廳dѕPV3nG%AKd2Z=4 ǶՅeo[rws|W.v˙EsU-=cg̕P*IsZ9VˀG,1{$n;-Nj)I`. Fs 0g9mO0 \*o>*opvF"~mfM>QԆ?3Z Xm99!&a6 PBv~s("h<=3Ԓ!tVȆlc&wu{^E?X('Drc$2c} c(a(FlvWyX'mBPBb2u߭ '5l%4Lkfl;ٍv^I9޿^ؗ|~H3yd90pHZ&08;Ԅ=6Z+ܶ,`9|^yx'N0Ocju!CzSwш5~6r22tU@Raltk_ߔ!ZLa$ c}ܸ\޽5*?xFk7-eR鮞)s>7G ;scmTp6LJ5<2-x`c#ߛ{r C` H,ʈ1.ěK6:ձQHcGTV_f"Q޳3-0tňIOx7 P|/ q=xa2L|FBH@N ЛueНqr&f|4%X/}DQqAAX o2#ӝw5ɜ+M78 `<3٧^h2TΘušbq;jڌX$1c! BYm2w "_x Eݽ9!$_= +7 MU]->)W+onwpE6}jrA -v?p7ZF_.PfX4{?J-%)a\aRheܠ4r9d|= yq0`Eh)ƼϙFfff--p.y0B@QDAqICEcN@C_Q?` Xd&Sg8cJdGxDBs=VUt[nCx F`b:_7ko/ , ΰv;VN,npe1ya7 CNbe|K5@0K9$KxS5g³L/pPA̷#-=P_`zoz<[|yµ7n@12g'AT}}b"XbNFJdXNF(,CC!E5sԠ Q(Tճrcnq[ H WTD(Nc|CRYmcFw{voc =`~#2IH?CN|m I#gtegKgzŖ+v+5kkh\#u-g K "C[ 2qExv׮wu#X=Y:㘁a'xyyM9%l몼Q|?q4C$4"a'VViFFJ k}cP~}rRY-bTpvyK<;ゃ(ǘ5zx_\ݢ]lEL$i;e:sc/y9EV{v8{Ny9Vr}snYC.zGCV6#:jon%m(ZssͩWtVʅ 3Ka]=+Rd]R>Eq3g;6@q1utЧU*V]qr;2;S>)^~{WvFk>γޣT] [,Mef28WPЬפP3# M1ݥ3nx欃:w2{qWWy2z**q5)wnG̮&h U\] aL,UBqw f Zw9ëo`W .*:$+ykoR{ Jc%B:Z71dџ`z!ꕮқIdd4{=Z- c'Cʬ  %r m2O 0(+:,[,7.oBZ>W*],j!PrG'œ(a+But4I̭ \FH\(l5v'5 hCY3esNfyZSl X`[(9Rncr|yPx¹Lz7 L+AyΕsKKl=Xwl Yq^gN0!~}> |0 0t%%l\iRi癭 2-I `Yb` p5.O2v-Kj_p qgӶa{ҴvxwA;awWA9 `? 0 O~N'2|H+-Valp/˔:LXSȒk ƥ&{ \:{ .Hf2oդXܳtWFlG c&Ƿv]T*?V&NqP;z8w%"i.c/62~4`>֟mK-i|i# >Hs9Uv;k&-qjbQ(!fP Z`&Ƕ?_L ޟȴjbR_*n&^Xts> C2Fd"2Gf $30<-E-ُaI52vVNJr+ L6lnS3WUonfc,N0B3pph6ZV8J^A #ª4v h+҈ qiNv?Rz-8 w{kg3n8b+/Iyά9ZGqUҴX 91<7/+H>\^~By汕RQ86 C:jEi geH $u{hDbpCwfCRg M8e9JZK!#WckH;lf-smB!IqcŗdϜAŋV&k^3YZB|F\Іfę&n<c۹9e8S&a%Be܍`HLI8M-tZ޼oW o-L$wg~KmK\Bd:4߼5(ȏE8ylyNV{?VYZykQʸGC(P.X:vJowk'=tgk8nD6wP J9v^EiVƬX8& c!p8T骝[= n K↡If 9A^nd HJLق9ﰅ~W.^(ϱ|e+[\S'Wa[4 vl؀eaQ aE-`Z/=0$̖{]࠽p} F`\~ƻqnk TN9nz+7n-s݉e@-3-?uz|py95jנ4<4-߭Αgݽ.Xi~F-VYn\ge{. I."^ΈD z&?ҷ&!S`+q\h_.\@BFg=[2گ)%;oڄڣt>JN?F؟Ƿ'?p ?F`X"@8~&Gu~u, =mj)ѝ2Oq7:%pm {)wrryEub͎ TSGċ\m>α>bĵ|~suӽDsf]Iv !^wɴl3DL4j: &ۻr]Cv))qkemᱲpEhw{KO `UТ`sp|p#WʩeUnW^Dݜ&]~ob h Szo&y1G#j~fJ/rAySb-B5sZB[盀kq[ɮ9S^v-|}Gm0; &w8>^SEc&w7+s8o],at=M(JJ0j3N0̬>{aNz+nuuU|jzjVL9o'd.[?ԏ<<VtFdG+} G‡3HȢ3WTNIn/ r>+_ryr< ڿ[v6iKZ(L:1ϸD('''4#[a$%d 9f-~E XUbZhE.SEvYϔ=nIFns/8OյrryQŵ۬p!:=Q 2u8ڱO ʤӸgr݃vyay\GX?U[n޲f=FXS`,;EIߩSZBQ@gmY/hصq JXYc 8 -"T&H{կ­"ocxu<0AVt$̿]S]Y |L bv0w-0\nqiB8X,Y\A7d~whkez-Ac7'r.Ey8Waa~q  ٚ:`BX-(dųryL˯w}k+C(<%}K\xoߍ2 xݔ;!%ɠLC}`u<(%t290n3׉|5UHfn"!.NB 4:`";Gchy E>ı'y[y%8?M*zɜ̧f~~-`FGsӏ&~ݰnb֩atO.}3O}jͦC{}ݵ dYr8r6nސ5IVYYEN=?=(tg-g̗':Ρ# %l`H4e<.TȤ|czݟ~SfV*QwD`̆h;'h>:m2m,&K"1cA .)/s˗^B s_T5KS Wvϔ{y:x,iյgny2{~Qjes(T<=nYpF(C1;*̌|K\gi2fT|6ڕ{n8S|_^{nOQ q ^ ] yWn Ur  |S1yZ@ ոb;CZ\ӼD^จH8k @*6a|FXܑycaE",^V' H1؉73ʉ󬲟Ody ۄ;EһzDފN ~<`|-a-Uj*G\D%V'qَy4[ 5}P7L{*ỚVg_j['qW*o?yN}foPv!'U/Ś除tR7@&`[\Yr*tcܐ)<֓>Z;FAȭ+O=s g1M_+WMbQܢǙ%o~cL?DF>ٷcm 39.WYf7`'ndYd;j`PίS_{~7ůY4('?'a1/exG^[bF 2k SDYʰF1!$7|Qic`BUk85Gcw&̗% w_?(?z{v;FҚF8VƱv4Po{Gخ !@g?AVv Џ\b[h׿-PM>iwir.+E!@U97i$ 2a36HɁ5sC.8"clЍ1ʷ[kop37=0m 9LЮAJ1v ~ůs 2DH.!n[)Qq[,"=֌jyZk7'ة</<5nQ?8񬒳]i4V#3E"e~4 1ҹTFwow?V޾~TZ/_:AegAtX1zM_eF-<3O uBp*+z j7- X ;7KF{~E\C뮖/_^fwv˵w`su2-ur+ S89&H$$X^W|oA2U ><'mUݭ+J]8WyHdcѾ뙕j q\?@}dRG:"gn:3E5dJ'hh裇Bcw_[wJXaIؙwltBqd ^ &Ɯ=R=4LMk5kAp}R\~7O|0ܫ{s\pG<MVG7ʥwΖ^z<,GH "UfwSS˕.j]2Ԙq&ȮrKx7~&#]]tw Y#ii"$:Kean *[{e9U}d᭲~|'׾T9se7^bWd, 9jnwz-q\R˨ 4wuX cy_'`w*-CEziEK`%&:1߂'SKg׎O޸Y^ysF*}KvPyȼؙg>!WP6enoҲQrƍvh_0T3C_ǯ>*-} \J_u=1v߭qDQͅH;YO'!M\w~rK|T w(BSbWHDy$Q\X{@pff-->oϢRUhu 0$ѓ؁G䔤K5"LZ덗ZUN%$4,Ī'a>V\s MyxN" ?L̍ ߽*;+?9vʬJGsG|MHFBL{xCU vږ%1Xׄ?&31rՐpŬ5Q0-LEI֌`6 *51a|2ZS?Pg;p}jo'fu& yx3a|vrY,\RGF~JumΨaNoEY d6l'@!ᡟhRvg#@xG8 n-,VG^'{8(ow$` /g)͖u ?9VC Ǥ2>w`m mx<-]Ǣm62,Mn ʫ7s#\[v_0%Z@ay!f o‰GoԿ1F[h-~01y˄; G433kmk"w A:Q&19lP߆TJ1D41lUBoqswg!"7M;̣c5AMUt>@#'g⧇>D\)/](]Fu#wex!5ͬü5>BH`##MHgQm8uS*`6'D^\Z''vLe&h)!LX 囤8ϮB%GRȇ6qJxl)} ; pp~>招ȔD|LZMf06,_ }f>k تbáil#O?~i>·ݣpo(%sTA3SYa0k?fj[|u7mmūΪvn_o د+$V{g؇&D |;2'&XL|7)7; ,M}1n>Ǫb?x ;|'?1Јn⢌aE8N(z9w<.db FD ٘m )8N?MpctD. A4m}bKS x] xn,/GsrnYFY]6GC9>3o*Vtݴ/ # n];~n[OYcH*P_ ;cҖIr"`i7Gkѱ1OyY.nW"XFP1m?q j)84B6q3qoC3<Ьѳz\a(r'{BPBnE64;Ȼ3/~Zy7ݡ{ý _~'iLOA-&q+8_k.n.qzyrV)cE;PV2&o<+V^@M\ L/ƴ$1zLp{c—5 `̝"H 9A55i@F= Q`2*u9*6 $wP|'DphuxogvemnjƟ_+r$k;UO-n]s_rk;[K\I%|D$̿e%wcke:R@wU^#p Gif~;R򈩱dKXG30ñ B~ ( +ldk:w([x$figzgʖ[Q&]><|dR=m ܧ m;@m>_4LtMp24 5E~_ LΈ8Hm}tu{οP6pm>< A{#NEg܄Wǐ4Cz\2ĘDgZ6.bGdчv^G[χΑ}"7cep,CqOS|FIkc.=ycaY%eE+(Khsz<&\qD 9d|*0nD L"Z i? };k[12OjuU>p'e&JE$t*so@<0CLf_Z!\msRĩZӷ 0NZ5Y*Vʝg׶o+߽S6/\/gk8@Q3)ɜK`SH I)YX|WGQH UPf&f2ӉI v3w0/5I"fC8bj)qљmBf\ӵk<$O9$3d3QNWbkݣ BhN~`HAG@d[f9 73?f$U8F '<'\9%2N}f7e8! i?`$R y']yl_|ʍ+gٳN ;=sDiE׶ c sL9ϾM: 7-wFqOGڹ%Ɋr*0[dM+Å$Ӈ1D7G2%y2 i` d},aslJңLhA"hyr[wNRrzY+wޯU[ι=}ps&VYY^∌G:h% n67i Է_O9I-,>"w^@,6A=PfUX`+`K?[,40I:zEx2)Zf;Nh\O_\\Ö0b Rkԩkb: q89L$3x A$2\%ŝL|Mkff-[@owv̭>\wXZy\/{on+מ?]~3skM{!N+*q@ IAa,ʅ&8YsK`GD$*iZ>%%粢7#q'1y]0](g-e =%`q}\X]eUXiZlhղ?WoF^/wY?W˜&=kZk:RQʪ3£#mEZ`s'*Vz`P6Qq+CƋCI`7j|(4yYplCpiEiuIhp'_ҒpWhn[7X''?a}nS~s5اv"d4;R^^">EG6F0qy\8ttv[|~/˻?VQ:0YJw)"33kOtȵ~}K2$M`۾d{uvٺ~;dy+*Wnx;${Rc/F:h_ 9e\!WUܔ2Ѳ:a1)6Ġ. b+ SAɎ 1W'OWuzFgmDۺñhs\RP jLpH?'`0:{{w퟈G _6E0avAay OE=[{hK=*@چt  %Zሁc@r+W{W/~BW/ӗivA=@9߄Q<*KSGVYXs{FLB 9gdpuuL'[N5Oeu\7`"BNM'2Z'@Y5|<ڭy ڪqPUWBPS0<<@_ͪ?4倱sl ,?z](?z稬+mn*:qm7\f.j. G=;4 s#er753'e!]hCS(ͼ p]*?zJN,ݭX|!b{%ni=/pK5tPKx`8 4cp`wDC=!3 O[M.χyfDV9f{2/1-0)>o Q8R+@WS6ayZS@)l9BwˬY%y-33ypno+?V\ZbKh&kmu!ll]v5pDMlZtמp\'xiNtA<EHE=|)(&q G0cLiYZ^wr&xn+O)X`7rN'oQp#]&2ؑ)S 3믠M1^_>u #OW0M ;b& avˁ dj(\"cN͞{Gيb|suWݾ}P+ev(O?x98ӿF>!7<+oONf_>9˾;aB0W~5 H'i PA2M7GX21\oX,:ijza7/,/FD?4x7ޤ4)3ZBd%(>u ߾3`lV;?9,?)';bB8ĦkKwv|2 7ϺU|O9m7׿r?47OCŝT!Z([Gò7(^)$MMFy 2x&K /M WGFcŲ'C$LNp>սȪJl\1[ͣ/gNwru0'ɉ &;"T {9oޠ. ?+Cd|scl݉jgb.`0bҿD(eT$r%43 BJf0/wpH:c8r@eS`] Jv&_I <ؖ /6`ZPap+%3=>)c#]52k25nz vB2oMuD1%>Ңߖwx}]rͲPv{婧8*0": aTQ cOmΧ :Hѷ#yNotI'_Gǐ& a4#q"=|V-'OS>pΙV"3uYp&PzdBK*CXlbr(뭖7+PfۺPD31~RGZUzv8%]ee'ISu_T?"a*!>L.B]T*D3u :y _81õMa.d^,ПOhg~x s?:#gAo] (xZ'#^v J\l֦Rv8v*RE;'Y"tZ@-#)v¤;YD9 [_[)y̗no`kϖ/<{ovYZ^c:L`P :&qsB[8D )͛n21͌&ij );MG[a|l3įI&ޓ[ Ε(4D Kvkzq~I^ǼYղ#ک!#9~&UJuDl^HS *efᅥ> F~.y' IWo.F*C'y4'xN58ȗiѦⷢ}ѶUmpx "[䦀9vE9G~]_OΕW8X:qJWLJA=MI1}sư ֹ>hFD6BVk/ݏé!.dh1?Ԅ ݫZ'LrD"wg4sՒdy,_Qw*Byl?r2Ye{t@3-G.T|8ܧ&Pa)2z.`C`co箏(ԏe}Rf e2ԇX3_aNXs硘P!~zk/ ڠ6Ef8"~8\4 u9a weD?I_B%@{t^f`xo:c NLWx{?Hg=XvDl%!%^bد>ZQAKI3 n4#:F1Bn\ff\UΝm@E.eÇS]tw޺VvKO pK+ರ9XXnbTKao`]%'BCʘ OC3_H 2RNY5'0c)D~tSDCcf 7oFr ϨdJ|+w:ق-#cƎ׷EҮ^8Ϝ>C 4m*ԋ (:?vΘ]W w&aqd0=TpnƒxRM\+Flur&|y٣CnH{v[//>]ĥ5uNңgCj%ym /~e6 SMƒh Aj8ۧ :[?L=~݄'Kƛ26Y]Ͻnz0OEf*-q4um3BeFmӴ<"]~\yT~AJ?|tWпNSW$16sbt?dagRY0cJWaagn' +!h}"cvS"ѡDžyDb8G1?T݀};(!`8;.'?nݔ[-7щ dshZ}OÏ;(:2eӪgvhB;@imkR3S›9 {׋r1X`Qg  )_ Z#՟gWgtHGt`;@玄64 gO8o05f #sBôIal'-(vlCAXSoE(<;߸\^|n.3e R!]4H@nvL.Nڗ}lL"b aL~m8+Ը t7_aW?2>V8f0yejrM9 ۯG4c䅫vs!"?>;1:+H˝nOoovy=B^FiPJk+vPF<auQ 1&˚v5@#p,%P?cQk.;\1~ )qiuDn\i_#U@9(|@qpiXiD3^:☥ѮZD.d6~7ao qDp]Ǫb=@Tp~P> W idMfff-˴{NhL\x)7)KVU821ns4:|\]/ׯ2|~\Z'V(C^%]6D % I%* ^eh_ڎI1I &o栫}N3oW0לKu@N8O$5 g>)hJ a[Ah‘ .C.GB~Wf|/_IF=9 7i7! ؓH~ӛ|gf= ocY7kXgi]N]Ln} B߬,#_^Zd{Z} vkeok+_*C⯹E0EfٛOpջ`)sl2N 2F|Dy&-|g@ǻ ;F$ÈIt:i:U{>^Ɉ":+ ~Y=E7ҨR^G.dw\68m4JwM̑>Z%kY7JT+57aSj@3ۉht&Ey ?huMdlnC^,(Ͼ'n{8<6n< MO GϬ y"?wL@dHZJuI/V>qS.B>WHP~g1ΫY PjC>nݮ~$73 - 'Sa` 3GO`ہorJXe4_#8~\yt[WĉU9= 2Nvӕ&"Dip5|I՞8?n0GF?cD~gu~d<ik]dM5Ɋ &?අ>m1R [m h- @HT-U S vԭ2?~~- XeA^N-pϊ暤 &g"z`0s y9T7~A?eOk̵mfb|zyjyĻ JqeF?xMh%Ŵgwoҩχ .Q9omTX__=qV.WzqUܹr~n1+ӛۋeq)v Pe;x)Хϻ=6ъv=a;mhq#T]=pfo[L,W𖾈-VH> ;yF:&,Sl y|S5."MV7 HBpőXw1ab[X/(N;W^AӿZ^{/KUdon 5n_w*_68&a=>=k ڸzTIl>>go 7. p䁷PN E9F vHǰ  hhM##HgmqYN]1Ɓ8xFglx'|dĬ;=[FLd VBvW'PK.S;&8KXI p&k-v M=rz2Hm3(g AWJȤ$Rt ;Y942N +4 \OD>;sB;{vUN 7I#o҉plÒ9[b ǜQDAƿo"銨2^V+hNZ`$0īQ2E[1C* JXv >S:V]"E[ !vx4^b,8A;͊;i*ιm¡ -چV*,-q0/@IDATА!WȘ/!wG2; a9]Rч];.' - "Q ;~w&6) ^#A6"G^O᣽ N暱DY=1,|q WB& Ĉ4b%wA?<ل}94dK"o<+gt V;K!}<`/ww˿ ރ[_8w|/w&€S]BRڄ~9jdQs52?|`Bqxhikl$A83љ9Y-2HsHf+h]Xp|3\6NmǧÍr 91;n)b\ߌ--  BepUuIb¸Po[,*`Ynvbh<3/SC43m쟶ˉK8GEHf\RXa 3ȗ -q37`LP@x@wzg%5\@p] &Y |`&>0InlqMpSmOϡ8spidRZz{̦Jڽeĕ(Z-&Jx7D&I%v&]92 &N5A&N^>X݁*g!J}b^ЬE ! `{K- 769RRF^ڎd"]ҊXMLL*,x03@vgF > t-rtcOQ=DJpĕq#HV3sAHozDanp r  :65#>:p&W#!|t0F_{WcۢAfR[Lg-Q+`nmvEuhb?ýqy({\>Vo3V,"|ݸ`ഫpm١OB a%oP  l@`ՌG8~ܓ£5}ݰ@w^ӭqOtXGMM#7 J!eS¬@s"t˻׶7C3bަ\[n%ZS&g''qđ쎕)/%3΋oqQ<.Re݅㇑r it-L@_A0s*X0>҈c@0d!?"b'w*=q T΁_;g/u\Ԭ> 4{5 (I0Eb Ll%L#p楩q':'Edcdi7d:;er7 7ym x}= 옥3['nZ4w`[P߈干~]+O_R\3&Vَl1vPZ&|YNjUo? >nʫǀ\g?#vKEA ":]˄+-k1X9"_[׶k?[*JO~n'g*!& ;~7,NI G6ۋ' 8iPb~g-Jq2,@[ߡSlS].+w`e* FF%] DbIbVd^{2%n@NtF?1rSe*f LK%PǑV/es[ 8D9ĻNlDodҍճvyv a w9:쮕XݖfM@qU1 9]W\L.f?\ С1pOca/Qr6A!9׼?}ό srcTttϷ;Ƭ2GN?)txPa,Ё`}7pk2HmʦgC_[2H-(XVX7pȏ:Q/\(gWr +{ee!Tg9q{`[;ְWMGPGv1Mc՞}}1߄~azl5Fzi4~56F(8Mp.`-3lϯ)CMYf-7Bc8&Myv}w6vBqۇiDs+enwchP^xbY8}B:lkϺ FojqQߵgOŒ`\A]:"0~-+20# ~P E %Ntz}މ>=-MR{d"D9y9O~R^b1fL_r*>`jd9%vZ~4>RTuo5lEf5M|~=ǾshB .{;D{{{>8;>exL?r--y~i2leTlz 3>F|{Gh^f\2 /EXED7"jq@st<9gP-!I15WxN)cso۳sL%sW%gq7/l6d^FU{?m}18E=Np_xfZJ@.uWv{c,62<8`En"24'p'I7qs.ό>J' WdZc=jRgR5\H;Jr$~غ.x5]L3{Hbűˑd .mu>"(S5adb^1nruL#y0eWLZ5%(/\*Z!0"?]  o:0:1Lc؈1ͽCTӇ0ݕN9ۜ|#؂`Eh5=ͽ8E B_ګM]@;AsĐ?|Jž_ %z}9|I oվV{p'}/~-4Q%aQ_!j5V~d;|,2n=r;JuWϳK8 ߳[jYH^+,1|; -pW 7ӝF)"/E[#C A0q ):`E`_];GtmmC y ;鎬UXX-J/peC0%y7k?]'G\CJsv.D|̓\̍B=q- qXwbHfJW-nju۸p fTҔM$~`cNҽ#N`#XsL 8v !ytγNPŐN < k!n(AyWfU lk'U(FȌke}pzF1TSJXLa"tv7U4Yd8l/X+YCa˽5_V0h $H*a C21?7;L@?$!hH8^0p65.%@!@X@ Q?ŸMG۝tt2ItSخ*=R.y~=[qGϑг&|>t"S_CʈL`O h/[ܝ#7݆of?Hb0Z|WឪGި@p#,BPC+y& <Ǣy?GJqg#qY99kTQpB8^qcw©2v?+3Yo)ui\_@qQQ8ޏZ[!{rp Va]>nw`[q] @~ `=7ͧ~G?Ujج\!+.0cfT0]O^=0 X4C[\U1ړb!SEUK9Qu]iKBN|7;l9I͊';]a`N MψU8_LVaP홼ӂ`Z9"-e 0t{wCzq jUer$i6x>Nq]R[:etn`wؐPۺȜ>IǾ_￝^r=jḀľ)_Z|Gd|tFi{3xyLSǗ{z3iHPH[waZ%mG÷r%i)6hrNh2 kߟu:(C`esKUͽϡ2 1ⴊ70 rOkFnw_`bN3Ѧ% A$]fA5y~Ԙc2zb}eD[\0#_|X{4I-:OKy^\#x^޳/NPYmHhIO鼿1l> !cB~CfQy$@4.&TnZ[LGTz>}n }o0F5IGE,c!LXAҐX)djn=tJZioCE6AwܘU0V`-a2A1>3 @N/tyR `8ΣVy \ bF|X qB<'^UO1yVfKhkwN(݄ K0/_H/_]0TKUnb1!&޳)e!-qݾ _#l^J#os4X涛Zc5c|(Xxb']d ΋[i6t8g3 8A`0CU{G}q(sI-Nh!o>w2 旻 t5~wJi+ UN /kxSQߦ 7Go'1vPfxº8wos5mm\IF=Xb;5:$E·x(7FI-an5(1"W&ދ ??^O[VŽ }?j#bO0I 9E)3+;ƛ}и`f`L:>Ka6\.ھyZF>yh> ֆyy>Oxo˲#^רlwļ]%|.n̏?smM3<3hzZ\n-' hV1<V0Q{3'+Q5A"oA(ܚYY׸!!RH1Wx0ζ.-/_XgP{:s1w2K@U媒sF }1)TK2fz>k'YvEO+߷JjCV`߁eֻX=d[X`K.q<ث/,8.=$"`LW}yE&?(FI?ܟEs,"6Dp*RxU\i-1抜8:;P ! Fh;YI^`,mtҕuVnj(涡C0Ξ€א} UvAc%ǖ.+IHj:6p֪N!h). l+rn85 ,]{*;O*Py nlPcn?Ep9mc/`w"`UΏJ9(>B@7s ]m査uy<|UjlIQU~ǟj"OW䱄;$fp_ j݅uVIM?O9ev E<-@<0 zVL{q_`TϡBc`xNHB(z< 1֛W z,. ac50g ĕ-pBjy<o96MU4kcR Ap1l녟y-r T08*30 =nq'ч5yjz끘b5` (9f{y3{1WWSgjaZ~,4el1^,ëp9+zIPBϬTf"NELC*|•@/ť6R![8JulI:k/{./OXM*ݙFJ]:Dz+2P?]~y'pB`t=\|dQ}zMƢqURL-^ LW, '1(d֞@7j 1c!NXPý 0I{hvԷG ʜ9.]=]rk!:%bz}0I"޳z&>|`\l_ Z?EXYzb/~26EH D} 7q,V{eW/wCJz<srRuER"^_9N͒E0VwoO{7Gi-VNYYʭ6=@2,Ǣ!9\snV;л YsM/ntP'u7kB hϿz 6#SNޘ-G$|ǒG7xb2aK<{0C_Bo_O/`!+l Tz>.`ُAk=$ 3mA.9"~oս*àpFgs7ȱ$#SwVF{|'(mah{v+xŃ|;M^ xJ^3 X zONyN/k\θfKccWetg&HYNnezE7:8i?&QKzw? kF8y⩼t>\~gswdT <ƒp?2Ψ.܅/58/`ERxz`p#:uat{t7;Bo b;=saʽJ7K ,Y= S>;cCe4G,ŷz~k`Fs,{EVxDU @vpO{rw^ZKEԵ92 h#g,0x#BiXF)=^E@&rs7Gߠe< cb 2"l V%OqyIal3",?TpLJ;\YFq2ʈ]0,{UHH1^GK\0t#8[pw;GcJMh.AV%lpv= |u5׮1G0x–6;C] Q o[WR^Z^lEVO6”?)1j묷Vcj<+-#-ӇYY9O=o)&z K;[̟ElÈ.N?m擥tZzKA8W kzQcˤ-\G(ʸU@ Sմy_tL;D*eg z'v?HaH~yIuQ?ύYNidLwXa  ',AB(R. IUČoe6&pLle|Plٰ*nfDL6+!Ƭ1 <,^`Z /B}}m)7KpT%Em>daQcr֧l~7OS[rKP,Zw M2}N194ypz' :_`kaiv5GCz?Lݽts(aK*ז`SGP1V8pD}],h)`m#]]8!Lтzn+ߺ^^`+ܷ8$a1!`~9)OqT H|}}%왇`!UegCgժ (?9+ ~;?`2]M/0 <{'c%S%XfP04=>5Y@@@ ,1O ;^=CAaeĖ 3 ^۾Flߏv'E&m{a0^8 PG~ÕLo7^6`?8v7PIHҘ0yںqsԹ.иpT!5<-竔d 0@z}qͶ6cq0^z9gb.\==6AdgKpGiҕ}kc惡 D =dj{H`f@5k|s/%Tkc,Eɓ\,i3&Qui~,z@:\)[] JA%Ccc.WtV0xOa:l BSw0kR.0* )bθ`[+FO|@U#83`OOgўN`\:3rh8! LJ `vLD=ut!"H,DmɌ8=߈~Zp-ʛ{i=+t xc !L[6k'XE$)T+cZD}JC86=qgϯ`#g]F|?Y8+xBiW ^=1cԶjcu\x=+cp2 iٍV3~v0D0v\g;|J}[A'\fʊ9c a kg^\/ Qc_±a[GMڠ?lx̹:}V}]th}Dc`FqqDVrXU_ iww!G?-/S$7nJVl-ԣn_l_ݯN Pjx52irI]$W8CĪU4m(H2>;[تzjHv6 A=Dw!33?яwDS0 ̌ZN:Y@*E8y"_LPt N Y@!K!$Ш U !*Ny i\_L@|kFQ@/* -.17_:p0bPA,SwS^b rr!ظϓEr'`L5vJ{uձBj2X0&H3i#lF2PCi` b'Kdl[+$ym.‡jb>Y$g}/1OZ#yz Ĩ­,Cl &1 ZLW\^]N؇;Va{}wMK-7]j'. X03k2-֜X)E(V bN)&|BFc-:9#ӓ{l+靗 9k 5IY=9sIQ%8.*Lxy>)6د?wV%U縱02m4.m]LxǷݽl'-*T6h1иg\OzO(Δ.:qw/9$ 6l%X$D=iXB 9v :w$NeRdy ffq9KM~C᱊ЛrZ='J/_nf߼~y'^gۣe.ZFӟx.fUXO[w/=͸3%Cѐ5HV3۬^t9귮_Iw߻`e^fNaL[Tj8F<4E:ǵozt.R/z\ POimU~ƹ8<\n[hl]ryQ؂xt'`FG XN.<9^aFY _v ~BncKQ|2,j:۴NN4QC^iBji A.Kb;T|mk`|Řq<9ωk4<^]`^GDAG+I!D&Ȕ+HI< gA|pvV`kvvP^Q^'uNdQR9a<<˪SR_Lj?X̫:ֲ{z/]X eH?[@VQ>KqbfgG mβdZC #4(NwXM{{/O3ӣwS' hQ6ʟ<5=CS d5RASV3 o]e` B${Qn ~7s>v|;&/X]\*bAp*-eb3=/x[Sރi^]ZanS8 tc^@C{tM'S![*︯;C2)a QD10_ß® r qD002j idmaڇLj_kҫi``\O9`GN!5 GqXa?v3V%=`^OFE@IDAT4/#g:kzs.W/֥on{Dy54POŚ_P0zjͽco4 x$C<˛x6@Y ZQ\IgW{kˮ/k'6po뿩|&L~֏R#aF&#>%h<=F<rt2w# = "M>ɪ(Pҕ{5M|= DSlwCtR;}EVzuVw q~L7jWak+ n8TAJf)61yvP7w& g {YTMY{G24 Ʃ)" ̹ul749} ?{8 W\̿b5ѧqZ_;Mo__Iƪ~joc o E7FCaQO-" 4M.ΆፒC韈l%t};^ZsٌMԁ[6G%E|_Ki~OGޣE J_S-,UUmM<=%GN&i"L94N]i^0RAp( z fkp`BMp۬pIFPUs{{`8 Xy*yP&zaY䎐I\-*74\LLC 9|NԐmȥګ[_@/Q̠[+mIv!6֯Wd !O %|yg*a-|9y w'<:YBv!h͟#Ũy,}~]_YH++kو⯀"̫ϥZ]NW_Oug=Ӓ5&n*Wy/y6 ,ZG|@=!V1LG`z6Ufs.A9 +wW],eCDd5m+Xlr/=Vqc6Lk ʕik ~G;/G eFZ]`x&t%dl MKNE@CmD.<_ݓn<,w9Bp,I[t=wYG(J : o%ٹ;g5M"?a?Aݞ8mn4Z;^2u @*cЅO`Hs|B ͢ʿ~ ~B @$Iys6W_;,7#ߦ>j[G V 7Z+/ rYމZ< (A}R!V15;w* +Җ47 +<> hS{WÐP܅0]N0+0ِ(L[/ˤ w߿Z?'6<= ]0P@0gZ1r 3Ku6gme 3Vޑ[ή.l tjg ˣ~ݴ*:5Kh p>ژ z}V8I! }5: 6a-I0mqTbk!`]t4B8уN26/}2~Ⱥ͗.ЮC򳇷{P7eMXpL\tFO>bgY3/ZQ=l fa)rzy3& :+a9B7~zu= ~w~c5NmW Z;/fV * 8A;|^6  ]|TEA ªOJEL{ 4/d^X1x'#VG+K761*l3z1* KL60u)Yq6g{K1%VNtwW˪z>Fw^ZCV ,κ+I+q- h7z_@FhaG goa-(b ??_XnKN_Op+p~1!4sbxr,bl3>h4ۇ8@ icc%v>9N`'7/-=ւdдc8OXW3lCZ\ڤ}A7B0Sn "6`/Oj޷wBŠ#zm`z"b-MzA3][5ɹޥ= D{{>rpZ#GW~STf>SZov鵭雯\HnŁǴF!%7M|y{@ο@pAFrDjX5x5OqfrAΰ`R)jLL'.o~8997@!@ E(ƽ `ZҘg&,S"cq?M[ ~(I(U-(i\W@J3>SYumqWCV7ӻ-:,aڳpKA"l@W 9l2g daN+ic}=[qmup85m[[߄@\yt9,/`7C(0ĈYf1?Wh;"G[pϿ\i/G֙zK˲K8e!L߸gj9+9CR1\uX?$ UUq.kW֣|zM ?m^S\ʿ?MF$LDi~ 7,`a{yV-N:!liq5. ι{p10@DAȘjd^z~+1čeQ8`OA=ܾlq1;Ik80d*q zh|v΍Yaمԏ $l薣<3z bT+HX2Uf^+[x,[*,0pfVv.<0׃KikIJ0xAE><U`k)m/ҵwzZOVwQ"k'`D6`zs[2 h:e9I_zկ^N|}#]$aT|ށgiFB. 7*obH_uݨN̻{g1> <)|//\O~6ϋ @v6=WѾ靝?8WƩqM<= ;ݣ{q ti==\ g;ٗI~gCzPN_Syh (>03[[K 'sIHJeltyHJ`3|yrX !3& {!4n!A=I>TqJܚzG[a; R;VʫX^>~8h$ KߨzsX䟨IB(|xZ̡iX p:}XGmo{T6#Wh*!},`05U2>KMV=K4KFssa-.0q`\2.#g?)eZ )yʡ?˰b|JZ?&ǘ#ć*"p>fQ,mp *q4mpN҅# [/l_b8p6@+C58V.B)ea8ז'N^YM廯ᄍwt#R[ FK~ xWIc01!և9}0T)_W&{B'N~͙ J#>K~FMrӌ0,W%GxH=H1]'݄f[/?nꭑ>(iz)X@0¬c0 -#Cv"3#<UG UpWͧ9=F+ݿLE?ő)xK 13˄╙Z%v $@Xj/ o *FBxAj*OZm5+. H%esozz Fc8:G!ʩ49mK~đX>Q^*QUc%Jx@mWr'2G,_'K3?w(#=?Gyr7mI1gvX<3̘V 'ngف'9lu]J /!d @$G&r0b+̇׃ZS +߱Xd$ζ(7:F=<=px.E3 HsYyB LЋ #փFm$`syãv~~=V! vj6v<=.K|m2!i=xu1ܢsiJ[ۣbq-ߘ<9} Ysk}&7үb_o 4WXg.zL2 a63={j߹1:fIxo&ښID ; ~̃0 E Z#c˫+ib{5r% p!]['EO$"t3-أLa2hIj7w73<`۞/[G u4aZԂ-\`$M$qnjfk>dr<+ihd*W:;9?`bn`!OСpac6z]ΎOi 4AWتZ'}Q?ܛ@toc|#yҼ+/_H{bzʹz ֣0nꁽCûefDmX ۝1>(na4}/v p~?&0j毺~sA!ǟM,a~6,BY %} 浀y( ?OP杧v7H3բwnQ&[<\Z vAǦ~絗Ӎ[ti9Kn'k[Y&\=&eրZ8 0ZEɞ‡c 'QIAX_َA3k6M@!LJЃA96)p;~Z'?7)ǖUY~.UT'nZXc O['}S/!ve{eI{xPg\Xo"\(qL%4E2Gw^BkAy15[\Ϥk4藂_]FzAL>i-i -.#o=.K (=zD[ډraNQXj ?M4?W^٨@,Ŭb]1a,ÌNoW^ꦷ_\$ب2G5@;vrQΓ?ÜUpSe'\',Vb巊nH CxU .@5qv{•X5g2ĻuY.s<[{"E܏Ln)FRF(vfk%==PFz /A%<8u-0}Rg%Tg۠q0Q,p(4طBԅҘz ܎BsYYҦZpzDLՀ b:z+qNP&60=_ZI'l_b*ǹ]{>y3 Luw&E< 6;ySb{Go42@b>f=V@y=d cJr/iKH+ʷrr\EP0qM<-= PeA+UUFZp| Z㢧A> `WX22"i#c% U|@,E~JAU DWuyOV'ԝi;3,|w@cg%EVqJ|9RC&L݌{٢ee g M/w޸~vѪvM|G_i&۲At=uKDߓ+pOZVS΂pKR< PVK PBpIM3զM @zA˚sR p;{ VM&ؾ` +$1K5biQc1Bglts~W`>~tIX3 * <+pq{@;p-Fj:=>`<5=@F*bjm<8#?1x pkzή9:K,2!".Qa$ÉeUAPNyy(mYB?{&`Z@1Gwee?3݅ LzRaaoVT βa4%F_[[ y@gel8^A=a$V`ZEGn~R_p)gv'02@'%np#eigx_ O'`́u ӿ*6G b:@z9+kP9Љ0dA[UN&PҸ2aSV!A7=y*ݸ̀&2!B ЎcXmԘ8.l@{dgw}#sdj4) PDI7 4x VGfWaCV{&P\Uی; Yi#=˛:+ȉ x.5{_hA]ݔeɹ#oax(uGWփe4$W";o{Mҵյ- 6~4lj U)bE@V^q\)фEMt$hEfs~S s2v J[(K'th (Ȉ9h[yBFІ3]g=w= 4OG6z*ı# MG i%\Oo xщx$l~7s>>[<[oO|4^GxtdBP L *_ ONHx +50]Ig\@ŞH&EFb i˛H?NiGfAʹe(%="`Z=W7=V+0+~wk-1-v}m+QO'VSǞ}6L%_=6N'zL+22Z)ewB*~՚"X1_nʄ5~YmKK"?7Qa֋?nNeMW~$6 3+^A ïrY^B`0`^\7Oz 3<DNQV eTqV<Ƚv`CMt8N .սYn# 06v; E<N8VW`% f%'&rS\<*]ƽzć^jx!*8דUWUqƏU(i[Ά޳ɧ$M&aFiEfI/gh_LuSoǷ:;'Kۜ氵"l!]X饭ՅPBSfqR}/kn*$DR!dwo(m!xTaҢg(qMxf_;};B,`CP1HpU,3Lӗ8 =h8(0.Pq_!o~?;N'rW)L0>f!H%4:Qs@@1Xmy:˸XpxWTP^ydNKCL.6Qڅtm6<p' / 8t '˴Ġ3YWbŚ5_Lh ^\,B&PpT9=hϷLXekQkVua+(+䐹w5g g1l^lx]2= %Msozk16H 쑁.@2Œ$P!gӑͽ%ް#~$YAD[2||) ܘp]G2 -Y4A9uV_"-!£? 9pħ eNGCܥ(λyX_sͦrFiaz1v[MLAq>qQ!"AzBJ,=`-KiyM VQ_[@ *24lE'IFhjioil8^`+xq;0Mٿ=I7[0sO>S5x LGYʍh~p|0$a 6"KJ{q0=Qc _bF`)poR%T*|>gۘS.bx/AVST+yRt޵ ._6u{e5=U[1iSB$Ę\bD͞NY/!Xq2Nb~6'mH+Rp|ճ{.i97J mL׵^ ϸQ>z[z[υX R6168e~w*^m,_Lo 3N7߹x:58|,X8_FQT\)/oj.#XB0 l[1K~>3N8)_`!2N.|̤˦%Kű_~zGkj;PV.Iч \V˂$MZ`֚q:ۿַu?ooPx>l~wmt+c "3;gɁR_"q`-R\j ja YrdBv/,BuY cwYm5a챳6tZ,)3G+Ib74ymDy.Y9ŵ t[a}<̌p1w7&S1̳ cb:@34t5ׅa/ʽ AfI}|\>g_L0"LYlzg4(KY>VkY,1mq٭bl1rs>. 0(e},{D^`);Fj ^*#b\!Ƭ澌_!P~˔ kOf ئtecY|dLG8E(ۼ!hX%ŏgztp++ũ`+U620]XOr^9%Eǜi޿nKA~aWS@ԁ2FMKU:[]ZD=r?`ѳN߰J%T#ty5HaOYDz9p8LkYa*H L-0_,wq0:Jwp8y*H5;v kE*2fIpc **ޠ2]·ƾ$?HA 4Dx1אHbYRZ2W%@Æk:ؼ~QE}#ˎͭ[ugivS;a=c`z] FޠYrhBQyY|]Hʳe<ǬSޡ20e#pʀʨc!^ I4 u)׏"(Yhϳ º .KP>n X>jAZJtxI8繂! 8AMxCIxos׎|,ϱ$܏}l -w_8dԀq!(f7-kZ%nSGiQA߹.Ϧ9,Gt1ma~nSL2 ^U? i["jf+TX@v{N0;JCFN8N飧Ɲ1q\Mཽ:L\d7*.൤Wã6KѹG^E zFF遌@IDAT,DB[cm0pPw{DΓ**uD{ekߘ-6P\`XQHk3mɤź~svwP Ĉ4"ΔX A{)F͂oouks HPt:ig' 8+ix3xf{M/܈RDAR@>L1>&6fb:pKTV/ ^R AʱrZ ։^i:{+޿ξˇ85f,G0羶-iYQ0x3dyF?!֙ne=VY!,c~By9- #9omKK|V)i=1*a{0]Arr{B2!`=/H(X Π\s Gk#%93l`Z6E_^ $Lpe,\M)+ʘbR(,gL˘CSg**U̱p܌伴ql5He`,6i8>bt#>7(W M[L\i\97/'\ݖu'{HSF[ ϵ уI uS5A(7=bmΔZoO*x<KpY͙۵:ߋ](U{iKqԂŧ|}>M(\XZARD˜\pLؑĥ.]z=Km=.DeRj0)?o_fe^_9U`jybuh]WwqhD'}>#yI2}2dڴaD  4qG0#MfKVTCN2a԰̜ g]&LƋinӻ7X?SIZ斮q @C9Kdĥ8KEyDyM+(12ct2~<ѲpYKkGw҇߁LW[78N^(8l l g3>}}J \N՘շ9y IAy 2xV,Lk>+qc%5@YlZI1U*gceI2A.53,5d}DѮgWtެ,bzgŋs\lFY *4 ԣNBFp~+m-Ӿ1_y瘰`8/IʟY?M}#fǡRY?k1!T|ͩ${FZTfh=鸤 63ll s 16S-Ђ:쳡*Vm,ͦXc3ބ¥%*wxǡ4ڷrK;UH 3,thU魝l%5ҾG%,Om,-SW^g;[+`l/IMw򬤩Ҽ&8Rna< ޤ>";^u ^jAlj/]sNx9~ݠgZgNŴI~$ܕa /9_xgnZ[V3p6#w 1T̻h;84`6"v$/7o7K0x7вě1$,sI߱0; ID81[")!B4 kS%r|ŔP_Mpˠd;;h9=͇UZL1yf0*o:vj<mѐI+ͷnf}Y'p7hl/~3KOSw|Z$odtl^YmS1={8kBa5S8„ZX4g[2Pt eyG*V{l^hh'%W !;9gکʝ|/xir}!cm{xAaC) -rxۛοt ˋ1^jaypC(hʂT$d+sg!kʎQ8h5a:Aou#M:b~j%tuHV^71$gPX?@Uԥ ù.ݙܞ 2KU\4XMXQGsUf:&]݅Hжq7:uH:NB 5)U'Ed B`h='Q=CbTOE$ILE   @ 0f2pʴS8m3G4F`Hb"A,1*}<\Q2jn2xDl(JSͼ Mq{0f\;R=f0Vm5wwma~/V f,qH¬ .}̛ >B{hƙx+݌w{6eC02#gx_  P!@`"}9I ǫշ3q.|Y.u)n{MV#Tȯ(#(Q8; ĭVXV)#@87#F~r?lUwtwNIuә pԣx3*p.; g1OeX)K3NYe,,wE9anj-1f9fYu[\U^+McNfڐ*gyI^N壘;2آPf>\~ҝ{8Yam!hɓ.X~|ZHՃVX!5wjtJ#JtV|Z0Ho P1MV*i@ cSZDH <юWyR/ A`x4zu<?؞]"rcw!S !L#4vz vh!iQC놀C2,̅Gb42y+wz>^k<SA.RA)%c mNZPcXhZw~?M?$czۅ>I[8sz5Ā]q]l><5V9>3yxd3B9|CI@ƕKy]+P!P!pÓ1 k%h%[s@  <IS+3@xô&08,}~#:X2wIonm0+`GcٔX`fT.Mb||GK}K?+Еـ3Ĭ9'o!g༤`>V*;n3H6pv/=8_KS?SމaEǏe ~*zow ε\$x2-4WwM^.Ng5$IkT/^7-oxӾ%/obm㏗2-)bhCaY lu3c9.ekq/bV4DBJZ9N:E5Ju q5М|f9#80̘ sMg$wO0}(=8qly(}~0J'y즯2usc[-=&lE_c L$= Qgޯ<嘒=_MW8%SU޺- #_,J~㍫BBpBŭSu0:p&tCZ2_ 9Ü|| a;;Nx|t?}^S86cx|yk魭ۘ 6:2lR?tr zmiW3>(xx= JuGOY: hƌ3nҼg||m> ybػo%ώ'_t%g seCふ ˂ !髼0+ϵv bj"fƘq`+N\}β=*Y:ѫç???3< Fs`Zfzjg>(f 0ɥ b eƵs@f#o}a,)0 k{70#4l;n}/p.7667-D}X( ŋ_Mɧ雷w`kY32Qq4չHVĸ;Fۀq*%c C0>~Du "927 4#=lۃf`޾qc-]g!Ghpin, }-!Lt]W ~Lo{L  vؿv9t.o:cyъG_.*-MC.0M[oʗxI;8\˼3=:P:s+żMu^&h Sp8N4duxIz=+.iQWnי_w1BS!("^)[dQut;>񓙂,I}c?{Cf5u |1vֹ?L#ewo `z)wog(B_ϫ̱ BG"Ven%2Lˠbo=_o~j -s60ssk4-k8Ee Qo^CʀZ;wj sB@mo a{0W"t{g}adXӰp6btK6p~,=ʮfU&ÐQd]&SgCc6fn5dp%G,عnjzҋ`mk2 lg`pV7_I[)t P1%ʁ/.y\;ʳzx43鎎!U`s}J]w}v=lu; 'q7St'4c!NZ(H;qiA6Ew\M{!(~_+Sx w^;OY>I]5>U"SI»^;4mwQ ~#9` ,xЂa%||fe;!ʡ1xa׉y .\P6vYVkuwk>(z[-ڡ*{j;wlW8.M2~f"$/A8Dy]֍aF(ƽV#iFN}%*\ܥ.KW޹K}A:ϘgǾyn21y-c CGGǴsm%l7?>H=h7w0fލcR;Efo|&is:EtW{,g~nr fƺ4i79/Bז~זYZazozA6gLeP!P!dbUXh2(ǎ! *P#@ƒD <\FJglN:V(pwXeqzi?@tjalhr5g+3k 7SQꟍj&&^Dz}^ Q vo`~x?.&s`0[(i%겴-wnL>@3١ NwOv5d|7RIoxShP/ .;}L``LX}ͧ ^!gGmSOXD WIŒI/Y*ޘOq ?]fgqѹQArlAXvuH}Q4Q"DKi_fB '*7 mCtS쀈>ybtO3a0ZΌKbʙA@) -L~YĵS&^x'&NN1um6׏%`A o BE/糙@Yvt  Ewֿ(oE?[_886 F&/ I Ĩ:E|b:s9}&NwonZg0Vt>Gx!%Cv00sF*^KCz,]OV29 D 0 ȦǿBLN"*.4;^E\,s\b3|̻7ӏ?PWWw"{a_f-RN/s@CP#کq%cc^!qЩXzbpLT1֩ S FOŶœ4bu1|.adh1C@L^Ncq> FZEBg\'oU ~^BX睹{X\am{Gۀ2laeC-/_jA /%|c0T\ x߃ypT}1"zUbj4kc'}qOC^v]*X ϻVv( on?Zzo FY{1mo!x gU8WPE콌3{߾y_,3_)Ɩ26 @A *598y*Nd1@e#C [ 9:TU9,rglxB!Fɹa(o`%P}2BK~ᬿ㈁Q8G1i| W_K߂Qw\84񭁝q?zcR;FE~e-sPТ«/չ'Vb Sphx {J:PW+$C,\J+3c,D&&_ 1sxUoƏ'[k닎}E:Zu0}f uz-`3G| wP4ǙYzM`ׂhiW34ԙ\YXӣ_{Ĭ 槲UJf Y>Lgß?&c ֵ}V{Jneݛw6x*bƒ"V=} e=~i A_7Bek}ƕku "5T\!> يU?b܋ɖ9{D~PY\}b#f" 36u;[%m&:gcDn2GEOYE(!_/_(rE}/ lǏBC wqOtH%J|;/$Mff|/(+_%rيzqU Q|ePQ78;Lt~3g LUĀ# yGS㇅xro~~6s\ِ9+'A`ro5q[ pԗggNAcF\B]fuF\$nQhRG-!`ߓ-3 L9w@,s oCEH)[光 |Ÿ.3Muuzz{%4從+* zVbWG)$,(\y&qB>(ɋ63 wJWܘ-zo9㉂0P&Faq~օqpTs[X#LW1bV(@uNH3DT+ -Bצ'S WY /}?sasp=);X{2.r[m:a@X_hE}m].sUd 7C\v3z_WqxWbQ.e`4 U'E8nq"?"oP&LD1!(!޽Ό' eG0],Z 1̖Қ'ǼGa :zװL1 ?3Q:cκv4ױ3Ow woPox Ƙ9v7 E yhYQx2ef!Ǘ5鳂ة(|s)XVT<#IJ7]Jm3̌LJ)7`1sv> P)Ef RO2.i"`]T8U>s"b|eLVS A-MQe&dױQBWX&KhY^x&2^\)(v\M_pIXO??q;sX\^~;gFޕ#%hXt1uW}ZeA`r.klVzZ }n%ÔOx&#mxq ~BUi.E`TE-=[wZ*^  28v["OҠ}vm־ r P<#$4^6V#O_Qgqak##EDH[EI=Ga^ѽ=t*lA8Z.klenMxS>qJ cͲwV~}Wr"{"k¼E1P_Kò >k8?Ġ@3)䎒%x!SlxZxGcK;8;Uk!eɨ;al?:2eT SwL'9u\7*ܲo|~)˂=-AX NQ@|o'= _;4_ekQ;yjˉobN1]<tP6:KAni@8 ^Gr̰* G "ǂl\Wq.2_b}a΋B܌i6aJVu@  %wQPP$\ 0ni~rt:H3nMw\zJ rjii2+"4S Z 8KC~{l5._7I(5SKKU/XFCCK7݁Yu"^;A]IۆFMɣ1 \Y}wa]("w™NPAc?drq+`~lk܁W4;`1I,:e_jRA?xd_'=Eg&eQD7}]_\PW=՛>VXLg9 e^R*kΓ0]?^IK\M vho(4䪠!@DkOk=-\l(wmf M%wdBxdnB?|{CDN%)OϚbUUaÚ| f.f{x.P$AMK8) "d44E7}>(V. W'p#"v9}2Tpu6qiN2G3R^Xs q#ļȼ1CL[H597'}4 '^0,ˡq4 $ @@B%|{9 yl+CLm30cn[raC44XPW(U.c{17`X0I~ $W2<]ur%_6Wf hՐ{#ѫI:r8࠹tP~41~ Bu!'OUb0*q6b6xm%FT*DFOgQ&3# a <-&g`9'Ϝ[TJYI̲ݺP}kԻ<\?w'{ڌRN ΰ3ǻ&_mwn[ׯESJj *V^ Aޱ8u8q%l٫ ˆ%(M>{u=>޽ȊF}S:+r[X$Lbao 'sVY(Rҫ[-w{j=L}L1N)B9c 7 ͘C6,֍0;FX2[фe"1)q{-}(cv&X;Fk^<Kzx;,ϸ=;d 9_Sʍ6JK~<ϻ[?P& uf߽޾6mAf܉k[m۳G "? FڢUbO]v-,Uu@@@˂4EZRI> |YUr (T#? 0wHͤ? ӝti[v4ߜٽ>κ{-3&P]#*jpso o!λjR#-H?GOI=q( 90ۮ!΢C(c[U ۇ&yn즭>?܇f_qF#3M `M8Zq6Hc~Ӯ>;׆8OF6M5oQ(Z ]nNzfz6mʑhClxCY{@# Yb?ׯ_gP,hyI%V-BBBK )WV7+e:OBzH< UzK.lG ?̧nK8A{7mwp<>f%Efy>ZZ;};s\PW=h]0?w K뱞]Us9ZhZ]ZθblA\sUWU\">~zwp8_f8]UƄ_k 6cӖ!HsG| Ϲa~) |=Z*,vVo_L;A;twΌQVUXLP_nsc-}n$exU * )^yQךPwfȎC3ϴ0G)Ƕ{]k#hX\#']SA[Q̒$p%!'&⇉y-N\ij*#kQx +;f3{;q#paߚ@f%}9ۿWq6ݩ&x ]+qM2iZ:>ˎZ wQh10F!` vzu޾O>t*び'GKYKB3E`R-Th @QXyφX[w55OC M>2^My%%%\ >mQd~AX.L8{+y~nAeTcq( _*)ҹ*z).ݔ?;3/{xӁpLa{xE>ke3'|xcu0ۋ`l`%Uz^yr lsx?M+ f'uPKE!\6D^%stxXF~eX6Xu<92| 1ц0kw>H{M3˵cvAg&]4l_5h#:eN07vEez.j` *dXfj:&4Wv[ cr4:++h )PBזMb<&_K~xOW뷼jumc!pw9$J|[3"51ѐ%F@̞#g%3+^.BBBBp 'mkBV%@Y`I1BX9#.^\A|"oyE*rI3&8]f|~3vҀe)m Eׅ+gV+NuHl//%i3f8Sf ^]R[b+-ĐC]KfDTVӦKjG-B![l&f Κ=W^D1Oz }$d2Ņ8u9/81ӂl9Ɲ{;{x"[=յy/ji-},v7XɎ!X 񀇮oCl"KҞ޾G\~k{ҳ-%VTTT<`fS<+4ߚS3 ObSaP`rAEӎSҖ\VQ9}i m"^PWk=CyZGC@1g'IӖ#ݛlP^aDCS3C$[jaН!LY4mO9 A@h&f>qa?=xm\Qzx5<̒> =]Oz@ D3}>,$<}ă֔\D=9ew!^|ל~:ӝycIڄmr\>P `)ԛ]|%w^L}?ky  /1:@] 14 nצh~.(AUH}AAp8!cQɭ I|S8Y7u)`6їi am &04Rq8_=@%8 3ǧdF<ks)K?.e{֏ҏMOʺWu Nl.HBg40w6O*aP%"xε~5 5S>A58mm >KwX0xt0JAz7pd!$WC@@NĻ%Ġ&xgiֶo#L\&AM:N_?Wڌ  , `A8pd!.KgˬFF0"E2("Ԙ%m2Gu<;I ;L⺻X-ҵKrc0J~9 ^w$}~Wv##ޕ6HVO{ri <<ކqZ~e%ZU+9R ,:7AZGEV@㈶',;l^۟ >uǷ6(W}z0(^ R=^.s&# ·nwEʡ.ְtlR5p49!I3|=N̙mnoA.N:4WVR +͐P82-(/f[_W%TURhL!O$ a;2ѡ⇡\gô$‚Ҏ@5HDcFF*Dhl]F =~7g L'n2tt#簼(O9= 9IP'cDxOs'/ys?%RGɔWcq],OW׷C7߼險>3}G gXyCf Xy!(08Ѕv@D J rh5}*`\ kcw;gpׇyc7u.v:]RU&- {yJ(Fɹx%p[]Ȍb<맟ܣ 0CAhu  5O@4K)ERh0RH d̦[x"W4vhG[By>E>#;8&_X_L~fH'xB)3<P23PCݿ@0  VT/t--74yrP8h(7z45XwhJ \WؓrmCpĦz.޺u=`|.Ȏr~̌5)M^=.O<-˖cH"C3f}a`CTl0pI?t?=8ĔB hXMMk.m޹n l0,z{W!P!P!P!P!X,IGWH:U!IC&,UYYs&.3A*kP,TCT G@u2ǥ#fEȽ>Y,SZBT/$˂7~7_W*^1_Fuã28CZWfrt{qZ{#f#-^EDF_~ER+עaTBȄW87"-}>k7wӍk[22waBAν)ά<w ?әvcs9{SG*2$$}59BiJ6RnBq;}q mv\2q1\~ZW{]lلwݫb@+***T9N)[]y¿KGZ9BߞXjMӲ͂@@N-xWvkc8G1|(`e:y u:ѷBW7EMoK&aZ͂[#YwݵA >e05b' \gƴ8kᜍ,X\ x6(^a#_g6Lyzgw#]H{_ә2^Z䌃Y@%NmTە``yZ6o8NZqJ?d?M7ؽp%8s*+ d0B/xmnlu$VP!P!P!P!P!pnH ZѕPUZJe 6JKz*o6jL@T&DzMزEm1_sc>txr!YYt JQ,lX,p`?j;-v/X+^ܗ_ٍk@{g?p=y aE i.r h0V5'G 2jY8}A9oK'Cwfٞ»ŐOx6l C>E&\9R<̒daa Ò2Pں~:kި~`gH(xpR̪seNu3 %T zx27s|gk.ʹmNϰJ zō眣( 2U,w8(O"\]KW2eg\""/#av|Ť=1Cje Q碜̪5aЀhL׶F:nr tYgsJ(o{>зi 'i :%||u,0nU} BpD* *vJ]7w0fEw /Ggsq6/ <FKctĽO&C%=&=p}TGb,I u&^GU?Fo#_:>_|.5ב?g)K~:p-2|͟8hh1Ĺ BpKGJ@}07a\BcOUy+?9%y4h%g/IQ7W7Kt(b>#Y ⣊BDky0tqޝ웋ZV  ]!+Ef&]ij* /BB)+W]Ƭ~ ߧD#X 1iObS&ɓacI˖EL|R]26@%Zw/<_ןpE!o=<ͭC:O5zVP/]܁lй b +**** c&l٘G tQ*orIegJ5@N 'ϾIBstG_ ,$!?؟vrbrNP5?q?g_ݿr_m,ɽ:km_KOLp 11 ڮt'`pKX/; ์R>&7:LEY`YܢwKqָ @f3L´196~>iK;q!4,hH(^l{?}ZŘO8Tugɖ[SUGr-]cBП"؇ F("SGwu'Y}jaŨ B Ǡ{"L Gxw 4Iz,i]\lG>.׋+ us(wg'ftl_*үZGGmd'wD*r:#j-T焝撳3,gB`w6;;3wE(W`q_秄,$vz5ԘdV1i ڐPżuB>=0Y$%,nNik,1=n+N\)~|,pwru9Jhu)#a?2Iqx λ>ӊ}ףP. gis}fھ&m=UTTTT<9QHi\;CKp @6O 3)hnWD@~Ip>}ʼn㇄.d#v4chI pc9eD%;2Ÿi ?Vֶv^Ky\L| U*/ ÿ?4Ch(JEY_xeQjBɯ`?Ѵ8b ؞CCt)85Z6hQzx؅8n"քM&cl[;i 6[Z\ՂK͵\J=[~y1L!b2- I0_6ҕ%mDFtY-h3t5X[_ /I̩,͝'"98vY1Y<ɣ'S'?[qE?ȟHQ|LYv#۷ʲg;!7]΄2( FRu/䣦KR/ /!2Bx]{uU8.! B\4oqWS>w<k֥*zW GVSsBt⦗Zˆ ֍#M_G1a.GKy=k(9ߓ~%D,P$%ԟBr k%E0}yOn0? yhc냴Z✅Y_!P!P!P!P!p P(J9gh#[:jSj;[p9}"Ot{e Oz&ڧ:<eA gg)G?jGK>( ^$fJ~P1)u  7Pmdn}ko:/r)sgolⲎ*k rx,Rs0v$0zd`a鬌H -N|EOsrP4e#5TGz?۽gr_˄Ec 37( `onŪ0fBĂ L>kh6c A=i;{G4NؼNWâa|6P> FmC;DKzZL@@@ ,\4(7Jn4>@П fe j4GZSp!w,H|㓯k?yGl=~ugS m8d=gK)В~oIq+FA<DŽQ~߉>;6m7!F76q}ֵKcp[3PG_Sp33( \Qi @ Đ;ZgdX}xmz#*,[L?"1^|mSSZY[Xā%1}oYatp>7 tp/}1}Fw '#ʟ:c)0L #)ۑcRK MhwNi,Z[9S[w͒M=WeѮ\C~K͚VUĹ3;4ҁ\( 0X`% 0odd2rՄǦJb5bMi~tѦ\D260Rԟ Z6^wsJ35jT  ,ҷQK`26:D<]={]'fO[>n[ZNF"/i ClH;C6$ !z>x+& !&A?9|Ʈawh*FO@@@@@@BCqdcq ]vBG 4v&7eI.-G$JS*g+_(}`]dVecLʣ8hR֟N<*sCm{D/<'aY y" 0],PLo%ϛz 7<]ͬ`*3lPlOh$#WX*-:8tkxȂ>#"}](&np>x3e6|xd7EIW'NƽuRPzYQ a;< I%3EytX~GXvw*****.+E/UY'Yi؎Y}"y6mL[̦Yv:uL}yڱXzM҃*eńN(ɝw c?i}}+ PydX⿆K˶4?lm$L^ő9[%*Cnb\Oɸ}gQ rM ib*ź{`Wjhum5m}y_*V!^YE@D޽<@<}1<+ aɝNnW?ft{Vb;E{8*oL,Ib~^jZ{? uW령DDAB1ry/fe'[췓e)&17>V8,"zLNB:>1'/'zp`4[nOD`?4> X(rVo7$p<p$JPtrGD5yYyxU _&sP~7-i [n-&Y?a6^7K;%gG\J 6M`꥗`3e+c7fxtiBh]Q 4t7Ѓ nѸc%V|Z3D\$ֱ>Lެ~$'|jNU\f ='D .(=o#.ĤQ/VAAAAA\au"J '(#a6gvઞoAE!ŒsUk'jpoࡒBJ6R>OKiSKk0脅Ͼ|yW)xÓ>YIgw-"0]дIdnZhx)*-b ];)0i[w+1 1<{wV]ݥt1 u{N- Q *pIXN9k:CsgԵW{LmdtRsC:<-B@@@@@9?u!t1r5QP@՞?}6XsIx5w qWNb%lOqD%/lhDs\kIbj(|)û]ҭ5oN_5HݲAU>W4zwW $ 32VDh9[=sIE\NʄfV ר}n) / ͎޹2wXڝr=py`rחŐ<5{k>gȉ(՚ # 1wWe-[m4Ȗ:x}[hx%jB=1r%̇-KNcpu v},_DOX*S iX"{Yn+:Y&7+&Kl5qhZFy}gc_Cf'tqE\Ł|V~^GȀ2ԫ`נ.8Od;1]hO  .[n'S'.gge\j!]>+dL\+3Iwe2=vvYg ݻ?EoOSu_>q]"w7Hș"|`d dIX M+Os2sަ[T@Z)L`.7z@ xuGy{(- #޸Tt ]\M,n"֬'--' _mO&L(kLu+MbrS J@Q;F     /擘,^'g%cSB៽e`m&^*Xx-LfvZo!àHMNk<ԓ y+/ .F$*lz!TpZSW_^zF}/_l~\t˨YWREf^;%@#Hp@{A 06q8AZ!4 vgWx0 &ُLJETl2eAcu<_+pK0y/ӁD ^km Mߣ2+$ɼe,˕i_,XkLAGܖyAo ٯ$\nB')G    /Mg9@j#aʴ[Ned?VH@`~i WW rbɌEK<`zzlmH$B *XY*U?g)N" 4E#^O[zq8+T _Ri@{nhhhhəf`埅+ӲXcFEZL K8qK|M̑]HLT̢SsqWGrT\O'r)i"WQ/ hk >BJ(/ }- #MWCb%O3#PD!/ X[7Z/>E:xcA$c<҂堽>c9NE|MP=UYG\QKHsIhKz4֢Xor- Eò٫Td m0?lAAAAA3C9Y7/:00;1v{1}83 8 R?s@StRA&gE M+٣|jT>1y8qgM%ALZϬ_)WS|`@(?}K^*ޒn|\<@AD``cxCQg7;Qh6c=ѓMyǥ7ʭCT͵L @`[I9::,-&A?|,8-#RtM }1x;Ckyr( sAm.XmRvLo>[ —>ǣ !$٫ B&֕u \qy"L(-38g*hKL:xK ՘ lEt& ŵ\ /<7X$'ZWMXA!qO/$yexY ]~G)GoEUH Q^o}Kߦ%wFr`;mSz!҂J"`|av.:ey\B預rS#S8Hx-4$ǼDp,.E2Kxq|>޻|5G-YWx؅|~uRbmky!]Z\Mf ZVmk itد51 t=AAAAAp1*&CM S"g=>,,C9.y-4|Gld!Ϯ/p29AN⬼e⥸fpE0E;:G]#TިD=/'匏s'S#N10\=os{2 iH{VRD>F:i`o\"fAkj l-Cv)͵CXCr#.IpmxVc QaęN hWJUp}v䕸>4f-& "S$*.鏶7~$V&'=p]GDWDX,P̉ejvohhhh$P'42w`p69 YH?LfGhA!0:9; 峧,Z9'#O=0THN>ss/pIHZǡŻq䩺HN yn*y {=ā$@&庿͈2%>XF|h*QX2(y!jL jA!Pܼ ZuQzE8,g0!~\2V Icxf+'zL@Z1 i{,}cNg~js:>qjr995/z =7j*(0y͹,|B-A9-PK[ػm/+|ZqsB>2RU*K%@qw]|Sك@}K I4]?k+)$wz!o!w@IDATw0͢Sb#[WFuYK͙N5X=4jzWwM:AB@S+ Q7-Fm8 *`,a$.>EmU7T`=]{XEld'`z(1zNzщJwQqv % " yE!,(^Ǘr:ݰ68^VA !|VGۆSINx#-Npt ]\C2L p].@꘼d!wǕE?2=.^oOO=i=(?UM[Nc|/ PK$VMaX6sQ+tſt"렌q!N8Z ~l^g1;Ad*}|n'`wQv==4o7h/.h -s+CeZ@~^^4&|RH8?1Ux]/Q,RjZBA@ GRǣp9Oզ\{ skR0f_\~ܳl:b`;a7(~K~voIgZ7>_e<> ]@,+2+HL +ӃH݅P̳*ԨqZ(DⱭn2Jq@ű2)TA^>$C@#| Ch?{{ԜVbME^2(N%t#7MaWh6(n=[74444|Ĭ- ]o+!O9rw\RYIˁ^57叅K8]xq9>'sq#, P][#MS L)mr)B6'&wn#P`Q-8 uƊ)rx|Tfio? 79;-hDz9><,U+WW6جP|VEkpWaR 0$A_Lw4V4kh6cN\!ʓ3le?i`< t@i}Z_'14X%Q+*s)N:$/>pkAAAAABM/ -TɟqټdZ.̟J9=zn_2YK /yCe,w@?>/w B0$ prP<'[Y32Ӡ+X JS΅Ήk/ؗR)[_~wM:<~̯X`ŰX'6="^)"CQΌ@~-˹s5/:wRq=F#*h? 70ZlŻXDgh'os$1p=T~ll{^"o ҂GqUD31 ΗJdB|GN!?(/ l~k!Zgҕ#]f~P~>3r> [e0Ev'Wr6yat߈k4C':}[ @8rx6C p:_QwQr |QpLqP{ﱊS{#H\׆S<>t?9Q65m_xNfӿr'2p!cEz\!*+Lt¿x' gəظf;9В^x˷:Pvr~yf5\PWO3 ,x_JS>GUuZP`Nw6Iq2U0mq9)֐ƹ_ɯUCbqm)gZ<p(r4DF;7Jw_@$ey" ? !Nn y.Ah)B cEݑ'3%-"2Fr1i+t> 5ކ9}2 <9W:P{cnj: A,]i^"%;X \eB` z1%sȯ|FKjx P ȁ[sbe8 Z MO\"˴?ϚyqdT{*H"^:NNY<|TsDX'3:^P(PJnOjvCWgtM]s1GŇel&tY:"c@@@@@LAh;;>?KΦǾh,GYշME[̰Y͉S4ֲ4|B@e"  *@{}Yg8N pydȗ0d<XOE YO@q.wQCFԖ-60eH,"{m<">`\au]5׸=9=s{^6:d"T  Z,`=#JX@ ? ~~:e*"K䍺8U2Ϝ/>,k?=O9~ Rۼ1 B-_5Ac XZhhhhhPGBcj\=V^?|rU^ |Xg[6[ldMsv!SkFqI&,dP.&䨐d%x/jIgh:9*z@ '!(h#3 ئ+<܊z+z:!wF:+vA@򉼉y"M!_%@*:TgV:y*SH@Dn!ҍϝBBba,Cxa&+c~DkWs?xW+kɯ"Ibx Ҡbҝ Kl)X? tqP.wo[[@5%ٿ[@@@@'AD9W(VD_\{ts0 Im {EQ嚂9p eӚˣO/'er?ѕaR}c2o }e|JYnH<6={`Yy2ұЗT^m{nCЕևg!pyyw|ttj:|ƝD"?5^Y\%_wyh :GS /(\4vBm*{iЯ2!اp{}V0_>#CA2cL׺FF*\wդǬUQ>%f}?M'qy)Hamģ >mc-_sg{/d %W,)9όxOPr3x2WWg|HiעiYT i3.WoC$pK!ٮ{0+ţٽ%C?Xr"ċ<™B9ͣ5q0B Gm.z8df\8Tc;=-ԯ׳[h(ц,i 1{@ \ u`ԭS3 ?amc{JFhZd.tC1Dfi2] Mp~o~-Jdp@aF]jHܖn P quRD÷p޲\ Aht! !FbRJ _X |ݔM9G]_OGkH?eM9A_2*\M9YB)@3-h3DI_UQFzAn̯s!&/`Tlp;{#e{t̘Lt(P Tfn*ڞ/HpIIl   V6} =7X }Yy$^.մ''&W~ɸ~DȿYynvε oAE ?c=!űk n9wί{8}%OuC?dwNN^aTPY.F:Q$W Vo%+; 7h- =ak?p(Lԡ{ KlO_!8g찔g",,fi>3 34VrP`()"q-^Oء=^}9,w( 3!cΉg qo6:rT$8yUN@Kъ fy )| se4N,sA {2aXQ[W%.=+'Nl p1VB^Y\"GyiC[ kV޽*|S }bk֬yW@y~ssC_ ?lEkr])kQ|jKe[4iA ९GN ^!{Ep)"yTųkU͖" ZyBnT|Yhl۫UېW{̜~93_9iCA}k1B@@@ *; 18EG3=UGg,,D`V;/V+b5+RLYSD(<@YY%69(8J]j j}X W{S(j|$)\g\XǦE~"%zoKr^ڻ}kBrUMSNW`KH}*w$rzH,"[.j4)3@h;53m{-`RB D]Q7颲%{ji濾ޓsd{I2P$QKZuH͢Q6+ 8ٻ >$9n:ߖmg?]5444|U!"|dr2y)$I k*>&&Aqy)?~x^~cq w:l9-=J[}_UضzHs ;&(_`)< y*(sVY(.])v(wJ]OFz#<7~|+:x?Rf Ơ P  ['ME@( 4=')D̾4U ʌO/Cp' lq 9;){  Х>&`ʺ )0N'o* ݍ22ޙD?:rR ^AABPK=5XDSSX3D~ 1]`ʦ> ʇvZa74446).F(n? rIDl>wQy>?~xUل,pZFct-;-4|Py!+-'aXs9V)KXd!cѩ" ,v2ɮńOl^̯Le[;S@}kl} }p !07{ J 2">g ,B.;EV|Yj]z'+bs)^i*jtK诃!>8C\]aɩ(GRMP'|w\!\¸ϻ!Ӫ ؏5eMn*5M[1s4 gD>pq yBe2_`2`ڗt5444&?!nZ8K08h,3w# ?.~|V~'W2Ys3:+Y[wY(j? /X<3^ V+DK Yy_ ?TE1.L =Wa'ML3 *yW'ENi1poכ dZ {(ie} m+O`#" gg)/=S!hȂ:"P[D@@<r։@dIIoNs NVi#N3Lۮuyi#cUӻx*=z4Bw.~|) |c)Np&MP'~^+$<|;(YXSS|2̫k]ޝOՊ?)Pr{rkZ  ^eaM~i0*X?>b /fs_ -nA+._6:=)"ir6¿} ~ 꾧clFg/>i ?]qZ8pɿjajresO$]#^RV*~`t '+<ΖpH$n!7C!\-?Rw<%C!Yuyu:Oyk9fN3+/AoABl\ ~O7ui]j6ׂkqZ5׷+^(8':݃L#9?9Sv<+r< [<|t^k~;Uޑ( Adry8l`,O;Z\P˱ۃ/_H1mzP7+!w+ l uUp}xWceH::@Ƞ5n/O =ƴM%"llO ^:kf/T^ :3;I*=(M]mrTu<=>r'TЅ!Q68, {qN I[CtWWXW_>n+k2-zI$FmP`w dDh~ Ci^˹(~"ѰN|Q~ޤ< ǫfjΘ('bf:S)^Q%ϖu<JgWlk>n8rJ+8+2nē?|cxwJNg{Gݳnae ߛ )LzV_!_Y@J~'ĉ"΂`9<<`et Ϊ9£ɒ]ITv5{aEjjAvB@̫c~i\g]b;y]251?V)EZ}*5<*vvP)}Y)M:ߤ>rV.'s| d=j[hhhhx p=;:w\òՄ}~p^G8fVWCsnYYI'mdA}@޾ |P D}ނ3 ۇXA%RQtf53.Z2Q@` =e-q M?'UP)ԃb[뺻ʁ53.H,͓ [ L_A,pP@QYQ4@N-4_izhסdI_RIqݼVЯP38 ߽\UrJ Z2eƑDZ tW bS.g24Ӻk`5444NVz7dsiUe:tVWXw?rMd]kwujegPcb6]۷~8&V -[ȷr+T^pyu%H/TrVÂbX[SͳSt[)zs'w5 w-l:GG_[;TOݳ>thb|z>>f1 q‘$N5, {7*kA&!5bDw@GLɂPJޫgz\YFA=Ph'Qw]z=毦 ӯ~C9*Ij!ؠ:&sNL*mAAAAുLb% ?.q$>j L{t^>8Ǵz" GG9dNx\}䀂GR2`\Yo땴儡ПzݯwLw =G\Oe/D˅X}@R4Er Kxs= \\Nd~٢r_'U?}yʻ8B=N*]T͹ T粲:yOxlAs@rti/Uy4Or@o4Fi#dN;*!B0=ҝp_Px߿T =H"-KhS|.}71p^gcM:HC'_`XVq'cs|GQ1@\ тi%8Sv{qν[~VB 2BA@zP<'J &D WN&Ijc#mĻ1<Y ֋+t۵WZ3SvvS[5m=RGz"3eKB2q}kySr5a?+9~/ZhhhhA }7WnxDB?Eß\ +̘gU3٬0dNa-#_Ҝe "ɛ8fg d8R9eqWl}\<Q"+5gdE 5W8x+b\}E(*$z7^^M P 8rS~# +n &W8Q 9u&JPGVd" 0'iYCA:,cAb6/GGq.{8 <)0M]hoUPJj({ ۖZh9RM|;?ˎ+^1A Mx&|-oijeUYڪHEy4B̻:F瓘ShO\4KTͱ/[hdK} 퇮@uܧctM)UV21&#K&1q==8=ئR0CӘx2סk:'kmAAAAΟ]TQ39<ϜpU?(\rڐ9ŭ1KH9fݵxܚ”s*q186,ϋC +[s)׌8(XWkx%M /`;i6!۸#&FL2<\clce4_pJVC Dd'7"t0Vz8-/c<S4yP_$@߶rHA"\ɩa@%(P:-4)w,.-4gTC12DHwӨr7F ֑oVU5›u;!.y3|\-5N}%-4444>p gfAH(qQH81茉'6{ 7|pɂ3~ \!'?'|e> my2a\SsY>%n˞}P6Њ 9!@SE |,cv3_@|D9\N/o}ݛ/#Dq剼3y|&]:g|0) qԱW 8{tarxU7A_´n;V:\M:ǎu^bhXY.ee#Vt ?Wݮ>?o)Μf /^089)/|f_`XKfsۜp ]ϜBB@M(OBRU@>DW ܫs{YF2VO qyS; +ʾn~A&}w'{X:v[ּ) o@n9c{I G~/bV޻B )АG!%qX, OXr߄Ak 1 @aՂYPxi? 7I tb&P`̓e*i R<1X[Od+^gК@[*Lq]J2NR G}N$aRfж]B9,G˽a_Onp1~A/Rډ;z=yvw„%qqL`Aet_/?|xUy(Óo' "(X'ZJ\u-AB@?Ι D>YxB&ocAZxݏ܂uNU b=VꊊX{ Eq2Hh⇸=R]Vt݋1 &-߷@h~cȤfs0*sz KLO7(LRG8,t H;KI    _-(œg `< .'l@P)>b1017<*hA ("f#*9vFX:_bpX~V-Ӵ|*ȋ8-t>"+E<4$h^.k52"~HPR=_JfW UBcں\p{ap\N؃A? 1_x-)Cwqwж H =V1eʾ&9ø\<[3 ķ*nfRƒP>Hqpi<ߕI& 2 BɆ{25^?mfT%+;TU)KCW{c`SPh)ߗ^)\G>_ьt5۔sO9y6ޖ;V.9BF UMO8Ga{_ fɚop늿Z-G&r*}˗ Pa}w*OZ.B7w%BMY"{rVԆnY؊ ØCf)YnJ/ ^y)5+<#tʼYy~U)^O{ev J"{\Wz`% %ge04U/OBʀe(arV?bĠJ ( QY_5;“f 74>5O@*үTƣ2KmAW⺃幜V ʄV(s'P5y-b#.Յ=px2{ج%h7(ʀn@CmeQ+VyfWry>.އhaI+<*AAAA+FX±5Lhe*$~Q`쳆xn #S}W#|R. *9E_Hh}VYy©gc9Ba?v7 "Pbp0R¡z'gZd&@Q2=hXx3XЧ! VՅxuؖ&7Yq H]ED+* *2V%DUtH+Fh a}_t0M9UMSi с.^1>Y#Bt2q hu҂o A%Ǫ0{#J)DI ?D3 p xI# q5ģMǤ,cJS䧉 J-ya>Q&&`vfqL')ԧ_T*ddS6y9Ğ1;O &z+L{jZr( .YYՁ8ǘl6444| bҹB삭@MXAq2_TZn ;K$y4fT_@IDATxtv,OV5bY_"<(1Q/}9.ʘ_fyl_Ll6;n1iZT*t_(9-F5*(~ƛdU)^|:Q9sFZ3V>.ҕOd172ʿI17,6<4q@)SG;A+mP}ykAB˸@ET Ғ(:da.Qǩ e hdЊ y?S]jfv$T@T pP(G|t@yځc`ϱC-27aɖrp8,Fc͔-V񯝔wr@:}:`?&O    _98_!q"Z?< JLWTٯ^_9@@ܬ|QL@P(}-VDYyᓳ2[} I%v15>+%c)U/z"5PH^'oԼ//߸s.q\^Pf'>^]L!Ċc`"QƑH&VG+௠[j>Kr HrI +C p ! X&H!K(~%_|u*>i[fk!"}YΉ#GlCg֗Bɏs5?:dǢC=34q(DP NNǃS8~^ypw[ob,ڶj3ZhhhhA@ ЋjY|Rde'UO 80/-4Ao;%8i ,cW펺LzLr~Q@h\C-ihD(y$"Kay@8y:J _D}2[S4?Sr޹ǹd=b!޸" %1S9nfgq=r2%|">*r:&e{Br͈il[SuojSalA %J \I ̬M|PZ5) *gt DU%.s>DR$[DoTH>{` ch4V3r ܿD!] wU~MWp{P@o"q砜grusr7/}߸w!GW`:1Eo\c j!_)ױڞ8FdXGkg/sL)Vd)d2WuG-1,[sB 7*(XVPb0H\[i>'Y89݌쾂YB>݌ЏT/2s)j+ѢMwH?Ը.(R: 'IQ{//q,^:?}{/9*(cΨHly [} {uXC@P@/#V93Ca6e:e_彋~f'(!J9A""u =m,hG pCAt8>\-i9x5b{ 8܇\VmdAY ij)ě͠Obj\h!=~3WWuA-nqX7(X> U-\}-{U"_r&>9V ;~1fi['G;t @'z.Cw.FW(.>td>ƀ6kLJv:+[c<0୅WmO~̡Eg83郑^3^c8 l-0\ǭ΁ZQ?$fB"8u%qXbB9[h9༖,=#WsV~Z9Yd=z1JѬ{rd4d{=Z^ 5$,dI!%H8Mw3xQv<:"lo~YK MR_1;E9rh]+]dfK$B\%tȠVk3p+Z'WeƌAϪ`Q>$|^ F pS T 3GB6 : !AWz2=;;Pwm^ɑ10z EgQm-y94ϑtɧO3Fyz=Af#f #AwM;ͻGfUq mnU½n}$NHs];tuZ{jhhhxFambaf JL˱?2%l9( hsn;:|y90:~^9?t.'ObwsDA>͚ELShxNA#ɉС AF0DY`5PxoAAAA6@ fHh>,"KN5d)Wr>]?-Ѷhɭi5wW+-Dy8Xy8 6A!1?8-NWXEk #/4Y X-'R{P\$#@Iק7$f'4D7XJ5/PϾf scR~!(UIUI|xArs5+̹ >g𻏞_Ayѝ@ 3GTF ;׮ 2-4  6I!>Cl9rb:Z|&yYa?fE`+!j{*'o@@@CمuC:Bjޘ̴b 7tF9@?Wr1]uX|$yB%sA>փ7Oz _v ~ 6Pp ƅiQ]=#hխհ15> L> i ."9hR≓[͹V fe4ꪌbP#S>HC/d5&󣃂eNie8᢬tX3d2ɤ ek'":$ %K~.rxLJ~oB-44440ɤwM8Gy1o!Tlޙ9o?l1ss1Ɯ0c%3&}pX"9\oxniK b]^lʤDZcxEY(s˧j2>0_Dh'/=H#l>/8=-piȂ|2"RWoop1]wNBKUr4=[%75S0M͔…!|Aꏳs)7;`bΞ=@yÉƩOZ =ĘKeб Try ::A<9$ KfK>yqUuJURKCc'F5`'2<2؍1<0x6 `!MFBH6}󼿷;'{of̪;;bŊر^kŊiCZ`hh^Y&}"7199Nx~qŶ ?FԮ0%=;!i %E\4P^ٲ` 棫q{0 [.r?=3b!Hf %˳GS rhX蔱^ L<9Z 20/T_?W~ iT]:-_OZvX⒀ d#XǛ$9 f89"??䄭9p.p =?8WiHZM@A'B0a ~Upl&·fd +?2=dG˟}7Sl|I3^lA"}GM<ܻeɘ9"?1<| YqAѣvz{;`A St=-Kt%}wg\|8*뤑CZ C4ɓE3`c`ve(>d[afoG:ζtV:gTUNzA`%;Gmßg~{j M=,gAe]2WE&[aIWX}1cȿ%ʣ$邝ROW aoBh9VSWMЪ 5+W0ߎEdwyg)h\ l)ŊfV}  `H9cAK)|:ol 8eV 3gkF{#&D9BF(YϹ$˨pCV:|o0|m0C -V[ " rHU?GXy; 93BeFX#Y0P0*P ah_@/sٳ*v%gٟBsu1=K0F6 5tX65lfc&f_8 vr5#5t0𾵦}>o }.v|HBȌ5QqA=_/yYW2f(:Ӌ_O!V/CQ~es<7"ˡB H"⹧>e3fӸ;kCכ}7d@ k3#@i>(z 0HA#&P\7~o% oY_>yf~qLTcL&mͲK?,%p ySU3RU|CܡgrC > nXGLeϫ@R19TpHIsY5{&hiG_o! 2Aùw(S/TCj$"O,Q@}bŝ2@?{:DcP%䤠"F1$RJU5nhp'te6B8cC -0?-P<9rI`CtĔYcyWl7c`r4冝D)J)?8^ ebB! - {d'![^>&!}U?ȌDS{7ŗN-ļQ9R0ƫNpEm-k9 _Une;s _|ӪȤAQSk1sYvtX)fa4-u,H5Z! -Z>oR߄)DOY\ˠq8L 2u0Yo#ޒu?bY@Bν 8< zfg>fA+&S;MMs?HNwćt3|;bǶC -0˷=0*ؿSc_O|\X1\g>;g[3w?"cݮ03*굸au|C \$0fIzr/ڃ `M9K;$]0ƿġSw$?xȳ fLYoqHܽi?p Nż}nw:!†~kw4添30T_ jsqr6m?GޛyV܌]TZ6! -ZҋR; gg߅? {a;3>>3iasݥWǿ@P8 6ޯ:.sS5;hc#mga-Zw5 ]L?|˶<<*PEo 9iPZ`h~^[ #-;^Zf/|4W52̕0!XcpLt4MBr6PR23>QSv/Q|(9:M08νg?_~Xfymkjew gW>K K^^6{{vޝ.}O1IJR40{-YNѓv);{-WZG2whR;2R 5SW~Ldw7sH_# ZP 6Q˨|@$ g\q6eZ-̑1SRBheM#Z3f&AW3~0vć5 823N&ݬ,0C -5z("r!,Z,aԟ5S:5$pVp#d{ˁLT#>2NBYaK3s+ƼCZ9~.}v9O~i< 6UvT G_*I}yDolFSk<02|׫կ$3F}j+Nt9'K G#a قCHh()`IP`_%zi߾yB:1OS7PR15J=>[d-F'l7YE"dݮ4ƀte]"%FƥCm:R*ujw7-#Z)?񮸪C V(?u Z_WY\FxXgKSy.oL"[!x^f7Ze= O!>pC -0ouDk3gs9ݢxQxd‚jfbdٮ4įKȤ̫Ppz Pr *TqU-zK~Q/33Ʊv;9#w/{GLޥ it#Kc't "[4$z1e6^@7EamghUl1/˶K_z ˆLZg%V,Xna˻!0 vx|߁a6mlw!QK`!ոSu'Ho@H'~-{|Ƽ-=\{d@S'Ӊ~ a̙wzm^+;eGUll~iUm~~72a$twp[`LuX>x\Kzkuޏ Kv.T}s$w[{ibjy_?ZA3;Ք&$; a GcJZLa8['[;?Ȁ[3(bvޢR~Z{07Т<3p2,>H_;!^d4A[\A:7;kmӏ`{}py̱]edՑWuC{Mߏzܹ5<>:ͫyO` [4.QE@ep-ߵ aZ{leoh&M0J3RY|iTV#rC -0lgʓ'@D+`.Es_軇z~ ,/ٟ>[F=8'ljz9Sg:0{ݒR ..d o.:E ME+h_'յDCZo-}x`>Ѯ..Yld$4Ϥe~.$1BE+`RfPc:KYhK(w@?Lm;16$5 ڜ/Fߺ+MK-%bs5E*40矟2ECҭ!Z-AGÝ,vwX. e]dƧA+3[jM& { _󶽚Bvk=:lvGXVM70ӵr".};2XDcgZA*}A{ʱl)ҸHiYYhQp1 ]4R^LYye[7(rm'A.8F)ֵ;@)*<-0C (yt]$AP8>}t) ]GýSv8xAy;a;F:dyGʈJ~T:O =bۗwVd9L}聍s3j\Asyu>lf1wD^b^ד4ELhtGUwknY[.v9# уE m*:>b |'0A K -0dgsTo;ACnD<8d gft0\ȸ&.LiXU9U PmﳫƇk޵㄰wDmʚ.a8ȷGҹZg}绿˴g^yo59s0ub|*T*K%yry*Ab@~enhk/Ks. `ry_a7t<|CBPzkghZ Ń,íݠ*S+3 ŵt(;t@n b9)FU\ĴNiOxjlr!S7)m*tǯ@oӧmr HСufd)W#Z|!L_ppr)AB#ѝjz Dl  G )#-xY{j g*= ޤG8?>˥`zU-K4/5 +܏ksUgZ.`tX0ly;vkʾd =87qN+UqoNL/ 6`ޢCafwnsؾfnݲ^qToL%sPl NrPW ZkE"ߤ6LUx:0rX_{\9wθ5:O9HZ(4%͗r[Y |j;Bjne՜tjCZ`hx-be`-8r`9G6uD*#r@x<|~-17nx:O>]qY8?Fz kYʩ*K9dpi0֩n7Ս|I#o㌾q7\|gZ"tyc&@ KC%]O_|63[p%K1Pk{?(hSc~^nf^U]tY\7+寸k 5g??Y#gݿ/K$Tz2| YN{;b> p] =7~ -`?u+K-KXdȱO+`+` =.dP#()٧~lYY}x}Lw)ڶq%[F_yA^;TLeOr=.Q;,[- DAv0m=k@KXcl>Z`hx D6Kl."gś,y~#o̡:He_z())f{, wr˶v+XMH$5#Q+K2e{CSW\nk$=PwLp A'_?>m}e$|e$Qo)W|\ߜHj%0JyD?,ZF#ͳX;r'N"P`xmMbD|/?Ó{T|-M&'<;iR #Ic<12ϖQ{^ONNmɺ(j_Oy mMv0hH{ iy(Bl1)a]qmF(x%[Aٝ=wFFf"afDSyLC]XXFޔ0w7gf9O{y!^nmWk֏gU.<^跾i@ⲉ-/R .`4ϸ3K{2ˆ]s9 -0C (seYbߝI J䋼\"H)P(-s<1( 2=./d; PXh(e\G#I"V&x}ބI䫋Ϥ C T 1P9HYfi@flwќ>.%rfgS`i+<l$>,|,7~uѿJ745j@4 )*Ƀ {w<ӧ$ t$@a䵦Bzv)2%z_-Po'O&U Oڔ4NS OotM&y-PylGHǘK]`|.xgmg݂oq#<㠈]в{8x.*kW7\Ockg g{=? Ӯ{'M)m3 _i;g';}t_#8^[f|1:${;% H:8%ӊ+b ]GŊ<|ms{‡| Z@ 2oҎKv|= 0dp;!$Pr9gF\!<۲`mG'kf]/xw}p}Rix.Vv2YPaI;'X>:y.Ģn( TD Qa=lW O9u.Kp)SŠå > iɏ~"I K^p:-0t )`fy*>4- LS=-v > g}٥9+O|rO_F)'PS & i*#0+`$;\sx}xv{1 ;m±L(-WaߺdQ~_}uU|ED8)̸c N'e'3EX:^F_0Wi>#`=IgU5gdMg .+&X=GI'7P#ϵFh;g:fO]YPQCZ`hx;-̂SXr¥8xsCKQ>Rl2BN?NEm|>Ӧ?hY # :Tbtdga=>}jzu=h*c=N1GV8kQ,EC}O:$y  9e>5$-7^O'QYzq5k=2vc+Zs)vgr`h78@c,˳|d5S҂~ƠSn{]ͦ3_"khįBw߽;;LJatg%V*a -!3VT6^QDp4sv@OOw;/h\B5S)6f檴}'e}{vtTv0"8`4PA qd" > Ԝ >ع-Eʔ\sO^pY&K`ew,^GAC)7ڙ>=ZIpSy{eX!qО (a,quw1ō<⇟Z`h7tDYwJ֡bQ~\"LFuõjF'!ӣIuH,Q%r{6ݝ.xSy{tz_\\;]\57KO2BǷ[5i>%Nö5!ϖK̗Pi n yz7`NS֙Ό^a8O>;puc%3]1@I3.y]0CU:sVdT S⨎>W%2i~Gߕv^;O?x-a0f|1_ZV7RsR;f[F,\t1FjbW1J>vӁLNhgP(Q-e823@ĨzMrT:M8wj{G%=`f%LLʞ|C -0ky]Y1[qp_. )/W~ ɼw.0Dn!_KL%vϊK:C A70A6zwty.L h/óvSF.萡F (lʃ 9hZ7:1M}r>IMz_@4m|]]zlISDkHBSY$/aK?=eH4$Y!o/8PאJ`=9=G?ց謤B=dTX6ʪ8'' O?/- Vɒ_!lQfsf7z;wǫ[s 3ľ}Nuێq{0ߎ9ˋ6`4tK:֮ba "/1,d7 ZkC2g ܏#|첖N횣J(܇eHTc wgaOyt<~鴓?FOa9$kC#oGxѪ^`f;czlJ{cAwJs ?C -0[i<<|5k]"Hk.#wiʶ"^YeO2^#o83R 9C=w0#~x>Đbg\rƺ)N4\?l3G_VH`)ò<2gYl\g5B@Uy#3Sr/5)ء>vPm^wXЪ_JjGUw pXuɌ<оv!zע;}83^G+~mޣ2*UL/d $;s:$锭X;gONH-Aiӣl-x %( bܴ|WgQs~:[#x=yHq*Pg+3UU28挸|G4=|*Sb50 -0C |˽!_#Kşe{df:Am|sͅňgB^'iՔ:;'(\0X`Oe3S'8%MZJT{-o3фq'n%h`l; hV#3eiu-ڣ{F3/5x8cu@ͶC#-B/#CՔ'xż3|kâ#JXQ$Wf[QN$IG]&zڸDv7߯گ8~0^Fwo}܁ 7A\C⫄ |~ =J>+zI}Ry[rt>z!V&L%v٢ f)ͷڅLІ {ubD8rc]\S9B-!]/sY"yw$cш>K˥nAveh}ѓp4 ,eߡhux<gGvtX3_:Hƞ<biL=YrA(3H˶"k˛Uw|dgL:b4^DH\Gwc[kZVxbPt /+8X2DUPcLXGjKh=Ơ CoPZX'ٹ|šW:eܘ=L={pmHeҘ8Pܠވ՗=Z`h^rkG'vvޜ)9(pd};ps UrīRvǻ+'1g9L#F_JZůpR(.˯zYC}YeT3wUWU*˿+({/^2P=k'ϥN^Wf,[hpe+0}e M{&*6m2bM<_]2ɜN>g`+#R Ɩ[@ QԮ_,t1..ƪvU8e >Oy 6^}(C\_5!fbs}aAP=Y a;hwmY?w9+MbxGFtDk/FTI*=&cEXBYt#)>![nA?2ܿXLuNh1f2 4ݦNǝE0C BƥYn1bch6t-O f) |*㺿z yy\d=-@)Ƨ!4Bڅ^<r&O lDa&>=f1!.={~*MLYԡ[S0p9p@= :K{gQ[~R|U~Z`h^n0YmD@vX̑;sIzϵɛl.B;z`Q8|Di{d}:@_W2&(٠?Ajiv,qt8~쨭(Z7`$7Ԭ(ɬ}Qփ DN3;m%Ktm.T}}N]cl)mԟSc.ڣGv̠# Ft!ɑ~pE^ WNi+K{=x$Q d[v{ ?opr ܄/FpvvݣdK$rX St@i}},X'Gt$ԢKOOoGe^(b iɐ̧OǛ=QHBC[x1~s.M.}B[z{οwHd+1nrp0c.ҒL;YIȘcw[]_gr;O:Zy9:0L7{) }g yo|W}Լ*t#<1`{1tԅ і yǴ ]F/":_ԘNlsǬY P*$SH1aCPrI0C -R-p=p߅"z(;38DOd3%ɩu~q?30k8VUz3{<[ET&rSP ;8ν8SD4S͋HStʿ>A?x>A{rr.Ζ>lW;h=8y~ J42oZ iEϫYWAڂ1X𓓫'6Ʋ:!p!(:zEm '' . S_ >6kLr4B~`Da 1o- ջ>K񙢏4eiR&`C17#IꈫÙQmoo3d_g# ъ?>oGGy2; Y_-zz#*;:YlynݵRR l`'O6\L%t48X6fm!6ۂ;:h?<>l0[8iqﶠuT,; )im:{-w*vv ^S?sC&&}B`<8}RһG" u|]X3K lۤIo"TC~nq3T帶~٤ݽIٍ_D$)##>.9~pEm$L%ʶ5w9y:ofHIZD1m٥a)i`A9RL-yO^&<_j.4 ahZZ@~:+[26lʜ>Rf|N]Ď/UJ/Y1:I])cX>h_>l{SvϩցΜty3z){ys- v^RfU <:WĻ8Ƙ\:M*f7ef?17?=t89kD@;.hEE?!K/_߈dzJ bG /֭0_~ݦ/eL/^ppıKLg Af!6si!2lCWjYG-=ߓUGGͮ.َxs}pcNþZ5BsD4>GvB0Kٹ}:qC|d*\L{qGKUȴf`Vt\V>Nj9Փ `r@a]aN|808m#$KC⟆3K׶KR>>by&Iy)[7a*vi<7D=e~ LNHI{.<)oΟ:1Ěe\&s9~&gF6*OS[< m I ?C -0_ka3:DNK9PRxouI#:*!?XUAb>w!Ώj]!P9ߠڑxyCt ѿ3[]η܏}ic^pѡՌR{DKď Ԅ\2+Vj6Nnna1wW18GhF(py0,Yl=,OE=ъkSM0Aq㵸P0ؠC|ߟ t?͈o~]eQN9P9q@6V.]z?~#GҞ:7Rк$&q'FUVl ҙ*xRԭ5Al<+_Nvg4޿ x0^^y}l=_Q(Z1t`_g R^X?ߍ9[R&n܇V[@jy ;x8E}x}y`v t+k{<=vЙt0s}M޹ a(Xk33det{Go Xl08 t{l˽uf@e) @Yeildͤ t=`h4!;ON%g򓞲SZicT0K MIŕ&}ʑPIBUg ahZZ@>*Ku{U=y.F!͍̙{x'nPo@؀dtJR6ꢰ?XsܹY^Uޔ`*"3pfʛ^{`uN,TU|eš 稿 ,?'fVܥ/ݻ, Α?kQo:ɼ^c@{6.[N(=x>CMWJ@d ,4Eګ-mK茁W߆FH\Wț9^)kB @ӳ4x~[#;R[g%n!29#13v?ij~Y^e+zl Ge9[Y6wfv Fwڿ0LI Ekiҩ_Iwp&5vϱf˃I6Ibk,Maǖ3qH&.q"娚$Ʈ@>cP> 3O\&cm|};YP[c\"I}\b}s~9D]}xԫ S'%-;N|f(1y<&>;u lf8i5>ͽ + 49tD&%/)3' c:ۼ g7о`K[;ꔵx5u(`$ʦ|N/ml#<M푬5rUZ*o ?C -0ߠ\㮼*ӏ]iEA%/BZXp$Y;USG!PVd[8]Artrf"(+2u>qXA]蔯9;V7¹#lCF᭣7_cGg~.?⠞a6:ePл3sWjoCx;- X"@twxbǟq?Pg 'ם$lqT+惑M>;Be8z,in]\ Я܄_oTb%Jr$_+Bp(s` z/da,Cx+h,j?}nF8_fԟHm چ|}tQUF㻷_Zo>Lj=Is)8W.)T,_!~#y M0U /})P(tnlbzֈ.Phs̩A_[m傺g4gAŋT@PWe4,( >?ʔ}yyO#5*08i@}A9f3G,m #m9iYݥQPӟ] LYr=\I# aֱC d<\G bȘgŊ$ _\sY 5nw֓hbl$,ۗɷF⻖?->fF1#mJrkx Cm?Xǎ8aX-ZiY&XaCwhZ@W af,ch|Cxk_>5"cSt Pmpm@Uҫ2-,-ש}^eD(3R#Wn䎩T (!C0!S5[wd))я/Ju^ Gr>pQ s8p@~O^{~O|ʮcfiNΔhbzi̋Ќo\㭃'ULy):+QX%%RzvH:̀^ IV - {wl1:yHVe`xVy.s[ '2_r:z0f$ˠ_6Zp3t3?eNJ&D^J&:ًS'x=CuG9Sy=+Ro^0ކ̤ $"D)3DCZy6 73'lg^12$_r)C)qe~q?;39mg)enwo3%)uDf@B-Z`h^LM9rrY)M3B++"Or.Vw)GLiRR1_ }a#CRuTU^O!1wB?q« 1͟.|e+6OBDuF -J|~Eۙޟ[{!ɝ4yYJ >o>͟x׾:裱 =hY`FŚ>VR&ZJkջIMY+/vy2]F[~cj{@kչCtc [,+,Ʈk]}r2mON[`n['R܏L5~Z ^`XG FU03@{HӬKb)" *{ >6qSn,K_ |֕ +7g 4.;_2Z~/>CgSX(N|7ִ x퓉/\1zi+U|B"LlrA| UK smǾ;a}x!‹Kw:[L%d!WWʌ{u8`YrJ0+`|j~Yl x@LP ,!C:L>O ahZ@0ʞgmwG+UJo#XW+C.7YYF~v7a%u` K&KBp[jDBJm( 8+ԥ%|h@^QƁjC ,@furDž5߶F̶e H;yuyҹ>h>f-~?6,!^`77I?'=l?dc?n qbav~P'kwտ| 0V+kb؟.x0^鞟~\=j}?oNAKF`8ZuNRZ|3bTL pAhAZ0%"V(p`?_ |Ё1 2Jn;=o2SLYs\fû^QYFmqzޟs/s!Ϣ H"#FB+Fn\#Sň "kAMnEpMy\[m\r_f1)K3|EY:[B]}|n.It$+3aoůzZ<&sf)#SFuPEЩwC'؇cŶs)qkE'e[GvKzLEp 3,H! -0C | DdlZ*+ + +$^/7baD ۧP=.Phk*u ǫ>˓TK"#ӧ%9QEVHAIrv\zye'+|]eic?HIt~K(r9mキ~=~tkp{jz1"r:XvۧvN+lu|`.'1Rxfh:z8~'@r{8|?/(t0?Ov|O|s-:uvKn=7rO 9%K ^^>`4kE BJ$!ȧ<ƕ=6968͛c[@Oz[_bPNk5YOjk5W>m8R/Rn 3GR!+X;>u,6BrN (P(dE#xԩ+<'f+͍mTYAEgE,nlvhpk>wa$Wt槤2NYKgslYyYd.IZ F"H[q1 j$l5+ieYyˌ= {6pSƛ<\WkN'N3C -5[ }#u8cFDa9W@/nBFQU2J5K x+=̳ffʥ2WZ:-GEgVMemuB"Ru,. #;mYx P?|=w v + jM^,q{x`5e?pӎ{hxEZ*iHITΜx~(WvAB/Y e mC p2 &f>6Y/8s^R { T(#3y(i;Bm3 Mi}TtCWnX e5ށVx#wmǚգ9ӽg `BX\R;T(CM헱pKh`Êb!9;D uLn]c픺SO5aD=\kU%,SBu|SX`MȷƝ7O]lr-*5MBPv uݕOgP*TJ1lIKT6<jS>s0 9+F?9 =I;NKab#g{Z ә8 ?ruX,@qn'F%t40`6PFI˫wh. 灞 LfA i-f˷TVN `H)#.$FKOE"wvtʼ>{Q.6Ff4AiP%קb$.`A ۺ_dAud TӇ;VPƈ:e6Z ;[яi61c JR ~jF/4G'i%T9a nH|o%z}芢G' ]sRCpk%`/n}~ 7^$ˇޝݶ{ܫl($_' ٢#h/;l?dq|-1IX-[ ?o^0/bT~;m.+ဴZ00Ym/xtX0,ޫ=CkSt+Vn{T2SŖQa^Xhg lgUøFX|JV }Y`)`j:^؁SEZ<>Y^!\c|JYa(h86A{Ɏ st !`!AbsFc 9Gx 5Y`~} BRJ!V_QSW\U}?ONM%[=@S  8O'>)<7Q~e(4jG!Amo{>n}y3K)Nny! -0߽3zȟ{ԓ"=saw gxBLcaM|o(]y*92<%5H2]&Of:M_TEK\W2Nd/)o;֓_!)xltJmR:hI2_x"o}ă~L}E8@sjʳ*9I1^as~q_Rk)ǡڇUV3fIFaۓvۿg^ȷB'a^-Z*Q\u:Jv[JC!q|1m| s|}vA`_g0J}I^:[[uPB!׌ 9 BxzFpVOGeI+o s*LUȀ6Z@-N1IUGUo( I)~7[{V) uP+219r.p#p:;p Alw0Vhc`ث7pω1gl6kw}X[1mN%mrppv2m":+fBλzq|6*#4:ITxӵmǭnz#@>O=pQpr}u1>Y.!p9oC:Ƿ]0pd{1 ۄ~ `}j6:^AOxg.9sf##4iCZyW/=D -0[k *2r[1<7H2WWl5 k' U\tPY% 0Nq#_9AE#g 3{EوOX!!oBRtp,Y:zS+AIOPX>ݘRX"mـ L^'AT]IQ5^Lk=Ujh`d] 8=zo|"^Cl3f]BZ-nF<8`\zv3SM6z)@RAD |yڼ!E 55X?O`Ħ*0CvdZ!p+wzZB72Av:z;VXǻc⥃8{Z5Hc4|6Lt±(>tܷt4`1bm<% ]lg9b 1uN6  ʅ쐷.v\{2Cyx`1k Y~#"^ [ON8ӎ-i84{@o2lC {gBx3(Jԃ6y{o}/!*+ovA{N. ;8I9EPg;L}yvfk\\Y0k'w=D5P-%&#^/;C 2rJ~`=b c0ŁjÌpWlz1mK"ٷ@OEdy'k!./+SeݝvA{+B!w֏>fwۇƤ"":ZW^`Qe xwg"1/^& iO,:4yDd_#1qG]k}S_ȗ@Dr>r)yLs6@qY#J°x]!_Zg#˳>)mZǀc;F U3=e FG.NPVvXN'AR utQ)X0 P0YN4Z\ 51|xΩUj3qv%>c@IDAT<-쯟jq>ߝ{6i%Tpy)IҬ|9n Qyx;ޝZ"n'^ZOSyn;LCR=<G0g)ta@7[$ilujeg柙' i%M0d&#L*$6WiZS ge5%M dHtn]܍@\5ӯRrԏHonԑ{3a>!./x7VG WkA١Pn؈&ݻZW[׿wCy:8:=|quhD^?&-dlI7 }>`ѓsx7._U//nݑj@[?66:QJ+]т{yٓQ;8IUDU¼%&D1;nӞpӅ8N)ׄ?E`k-V&r.W"ѡpp13rl&yxU&oܵ!PC+X8;7iu'#4A\v+A=C- yYtU^!N#.kjK .Y5hK= ZnCSBm Cj4QHݠR_2P$u{ 563aEJU|ܗ9靄IJeV8~`d%]3QѫH}h jJxjL2 yyڽn4$[6>[y[z=X?ܢѥ0ڙ44=vv=jF/>f6i҆"y#Zu_G}E200q;WfK]z ި('TϹ_J{l L30ts{N @hvpN3@ "g[_T$!Gz̊K)uq %t[oxim}$=;Ikv6,\~Sp5y~5v5j P0/ r }blh851{gŊL*c=3*ɝdjFܔ3VY,͝`&#hMf]^_` k1! _C`w6+kMQJF@>!6Rs)fbf<{AQDBڀu]3Y I25cBp'M ?eKÈ:O]`H("$ق/\K-F%3#j}bֵ{'ݽ,V'M,8qtuqS^[b_=d@!M.$K!Aѧ'iϑ04RHB%27AoS n%X*P )p *Qiq0;w&o6=C(vP}X };ʼ ua;6k. A#j_dݏш-;} 4dI:?s |݆)KU A3b|.Ȥ͝ˠ>ſ%L|NXe-H| ⻪:f^nHhi()r}Jp~/}6C3գ ߪ Kc[ 5> WW%"vNʸ+N w-<;㈠8OfS zsbNUdsו>ݤ6Lc? ˦ր92!D| FJ `fN`\\Eߞ isy#}so911}J*X+,s7P,џӋQ?OLۛ y_:ǜbW&$<|XSAzkPFowAEm6,iY 20^Q7Dڴ iz6$W8&}ػs:[oTX@  =B0X܁Ap 7A @)4 m އZhnB;֘kv;37OnkIh)x? Vi>hp{3J;[H86)Gڔ jM3wi2,D%T>N٧*!g. 3:y-"3F1!k2l60_A >`;s8'8l/V{4)'Lp>lJ[D2LWJև'E&%~t.@FVsdxZ@{]O3"aMIjL/L_&A>i:Ò?D,DA-lv ^c0YFO"9=W2ATS>Y~˓Hʉ,"~J_@cU~Q\k:,L%gXgms\30A2<؉Zةh3)3gLX՟Z3Vo?F0h #+bb)c0iQ KXR_G_K>Mmc{vLሚc%t\LbAɹ$&jDS>p~Ƶ6^уtm{[ -Rb.o=r<ڸBD"|G~E`Q?S;:M^ֹdP3v4z|\OCΊ{q)]Cfikn th\}ObexvXxKd@_ ~ y(,uwҒ$b5-"}yD󷿶T嵴_chh,Qt2[͆̽"ǫ^t!ms0K TV(IRj(SW(1* y hꖅ{pJϐvъ*^@Xb:M&tw*W{_cHK-|zH$Gؚ`HΛP3&} g#91z? טJb^~VNQ]g Ĝ~TLBFձ}}>Ngp68gc6= POXŪ i=&)TќQx+)06YPPoЧF{W}bJv3e։sHTۆ { IeUTfsHڨ8+8>ӎrD҄PB7"v3.\톱ulT YZ,>˲gVɘXshrwhxe^qh+^7z7GhDJ_#w6Q^J/J@!<-7PءDXv17@MLJYz!ő?mqqA0dķ_,O`'d84 ]wS;lrA]YAp ̥^Y@\g:Jp /+1T Hc䕅fK8y 3A$Tr0cIaO9&$<־'$9an>y+/N~tg%}v餇ևRWwwשk!@@D+BJT{hyz@LAAzmH fg#wzq5m@҄9􎻂Zi'Z+PNbs *50+ij~:H+ih \Kփ"/`hA/ x.|'룳ÃB zz^B_JC}9Y e N9J+}? +|ƎdzFPˉa8HտbYѤ2$``|i~2}$NU哑 I|ԟUN 5<ĺ D) ؛5.Ր)Zgc"P\!$B~CQ bE=Ǵfw#=` eo;Cb 4S`8:;,>T Bm!M(R*ާIz~H=6OR^j!@! Cp#Ҳ>.B{1g\q.WgˣtUnw90뤕RC$'}(On9c2)n u"._Ql 6"^px cN #|oumu0mj43afo}aߖm !ksK[Ud!@3%'d-b鉶{ A哴rI$X0[yb8Szָ 'qJW)J"Wo"&p؎oǞ-tO?>dg߈BH_#eL?a/KƋ{=nw5iWV X:XfcNQz}xӀ̖Jghw{W} S$=CpXn@d|uǺp" h"xǃۢqF:t9Zja){c(eK?;NN){]Rc@{@|&MqZ}9jDhv#zmZ H]ʞŀ gj!ۅ,  #|g!t&y 5Z=\Eu"~/ m hb ԠƱR3w3= `ї 8F -icVV f֮@ 5^@rbvQ2840j$f1W+sߜgd>t pӄAc`3ahńx=T]Lbʳ} 3q$!_3k6]вBw&(c}&3Al2b.n`,5%H-[a1aHRk7T(`azNɽ-VWuMs<ض `L1gY5`r%IH5wM<|r=H} g=܏+= w5i]1U*3Hh8ج_ƭK"c~a`͕lRd;=ɼK_ӛY|[mπon@ZO&eE8вӂ"J}"bŲ+(S05ENp>m9+Y?}{?*6V䫚dĊ;Y-JhUW[~GygCEW+ʹ Ջ-0|n012rđFFr4msyx*B#&? H"}Q\^Õ_G3{di~NDNFBMYE1Mdnq5.dXir}rnsp#aw0  C&in[mj\!PCpkpdOQF)*K!b2!ev@OaFHZWqQX"0qQ˷X:  T;9m`W+)S^%7=wl@Y?#j~ɥ{]UA|ߟ:acGXc3CmV R-wpV+/ŊuG KLB?A\ST61"P  uحa>T~O΋lqң*-ïf$,ȟeiN^x D QGW?c+jFEcS"5Т>XædPi--4'MT߯] 5j\@ S  8RnjC3.2!9Bsu0;Fw&<!xYA$ȕKiFd'89J hv-Ul΄}E,H9q7G{ڻ{ 'A~V-)^(ݴ>-X(TUw#xAv^_(URo[o'0Gꁐcho~}A0ڽ_xG\nm aτ#*ƪevd\ܮt,b'eHc&-Rj?o˿,Q#כ=&zU\[F驭n寗~#[n' dr8$t"e90J+U'{oNTM? Ԏ ΄t&tt~pq<1‡6yg9'r9d9Ǔ9tУ[  #7x q>R*@S[|TFD!_&&ƚ j#:Y&Ë0J6^uy҉+Y0SQSX&ɟmnUcP:+ ckw+G>*H8@u$]`fm{BH ھ`ݭN8(0APT7D\VK8j_8gW&ps>x*!QP{Rf}!PCS֌{HkiG#&L3wRtJE>yuMqTk CVڪ)s5_nqtIҥwQ'|*T>"3Y,f-`̖k- d=,ckw }@q$jBF>/H@eo Ǩ/UxnjA h`'p4,iq·orc`Žb$|R磋ȝ怔@ҪW4ѧN &eq 0MpUjalS9u֛w:&6W<2*с`+<*]Qf#]ϼwƉ!jҭ1l^<pN{<#T]%fU0ee'd9̾zw9֢ 3Xļ!4cP6[ST"~U0o,\xz aOVON0JDS4@&i5cfȫHƝ=ZFj!B hK|wFwFsxЙkjs9\9$Wagp1{YsV1ٍ58` M_ `ɾf(+3X˹,?_xK[wUh09S3h,m",gn'[os<.:'_K )1r6QU^ܶ=i!{!oį1acOlr4-Olg˓^:@ np{sM^{gPh)17>41 W[>pTo<QEl>{Veg)M>U]՛\뛧S\AÖK"Vb0Cc(tr;@z>{xw~dt<=wxñy3]a29$ 'q.@A+AܜP*y 6=9č(qV9gg7_εp^Ț?Ug&+]]вxWJl-&.p-11aMw,}3EnpŪ3k? ږ}8>0|Ȕ~rtv6M(==:mmO67} lQWAZK P@侾0#av9>C*v0lOg0G@pb;GAvO_ϣ":"Y#璕y!xw`#,d`SDaAtx]3.ɽ$3=߆Jf!=BYz•Sb6DJ~;sp>ݯG 6úk j1Buzp%]οI6`/LP`7]6`d|,!(RR&%D-f}=Mazi8 58r 5j?3ONQ#Yf"}r-0BZqXXGs& {0Yerz-]0ڸ c&iZA=N,߮ckk4t)BࣵY#pw[LuО (8gkA/=}qHC[axe:ZzSa*pŠ-@D7:OB{L~,5Egjǭ'LѰ=O=_wY^ ^o毴[Fnev'f6+RBݨܗs-pٯ~!`+Hu;t&OG0ft3yH=4JְȜ/lheޝ_z~L9b? Tc{6Gd@$5v@SiC0#C?W޻e}N(9ٖGCV 0fð߅u!2ƮuHCL/PgzK @߈I=L&ŤO&UIlD _rx݇-ZW̆!϶:ݰw:N??Kg[)):r] 5>B?p'"~mCvL$V.6WE'&ba"z =G?zrQ.}T6Z(}5lk@|}ۃv7{:qLWWiiu0RIPPO2NBXcxghU.p ?X#gPM ^-/+l!^݄-j 罂@^U3#' ;`E'B*x_o̦<ЫV.J !5^|kAt Ua7NsmX2iXg-av4fwhW^$5)p6NސjNvrEY[]huVPc_w:H T +3\Krb@?Dn'SDbMXK;2)gwwQeA-OB6SƄYÌ@Y"[N&m=. 'PQc`;a`'MavV s0h=t?qXW>!Tkz*D&ԃ(зz׆@VZhIi?Vp?'he2,)oƃ6q78N ƖOXi.>QWw^66CU H1((G2?q'R,ȫv5j!\VəN2W;O xiYܛ/C$#m@ztm6{&/₄n~' !͏0*>< K$aE?H焉] 0_9t>{c] q;G2LYYc +nc$sUL0m//|q)_P<r3//:oť&S]Ѿ3OmɴXn  Cc2'{E=*|eݠ!'xLV͘P!Ə-t~Ju#-@8g`'`K"x'x cPyt"$b9b-[UXD9GTySWŒEJ{Xw9--ԾvѷJOn\WHUW]uK#]cݰٿ>}kF1 #kqR_t5DDMW'-^ʟk-D3GnU( ) &-{*r}Տي@B`\~>f#/\J\| P?9HNӯ~5WODF6Q!<$A4\RwG\qF/IPnr {5+‰h*8 ˾i 'G9n⡓_r[TK)J$(j^b!dփZFxm ǫH|2yύt'.2u7H4 Z܄옌BHPnY0PC"9T7 ?_C5^>$mFC E0N+S@^%^A}Wl1@  8@T3s-zNFu5|K9i؋6/R@~aNYݟ 3cVӹ8tE5'-Hsj@VCa!:> TH9'@\<`Z~=щE]A dB"淤<(!3+SPK] =Rvy#{Q>P{k^!`DZ`Rd+r!g$n޷2`%>N%1 C|y$-c4jȰS8͵N^M48BcJ*'_ι) =뮎CK'UjTOY L"+E9%CyP2~x#rj : Y:< ~J2aeiBU1M6ғo}7e/H?Kdf7X6u{`BzDžW׺FI#ít|v0V dhTĴު boƩ7N͂4"`x[iJ_?h]Zor^6 ۪ds5(>-v5j)!PP)&(&أ^Nil'Hq;WUtK2È<18oõ4*0|dTٴ2HU DʳFɹ67p}~YHrZp .o= TFWlsl3)4ݤL^at$ݿi]~^cmJ!c-#nSH+Wmwm'LK_[DA@| uZCd" Ar~[u~/=GW$^sfUtӍ+Y'wG $uRy`MVS7{N`w^ij|2('b AA)ImoJX e&E,GLUy5+FD\W|x Ǚ!8l Obs?J~+=yηs.hzL=JjC%:R;nR!0\aCkvz`򇴽6g#:Yj4fg6g(tcW|+Է5j'Ɂ s-\'^Jkj<;?il"L2P )d8Ki| ~͕wG鴳N? {#dRWCrT5._\X~yAkBy2S %r@quaD"=dL36y8C~gν h::moV<Gg GEۄ]$i?v3AX3kPDꦔ* #b?i{QeVQjw]ƴnQ1cO!d3Z taDg3D~{ g|70XF<0<V/(gQw灹g\.ށ}o!.M]no1outl`;y{4^atIh6UƷmA&$1 GUҎ0W7Z&L6REҜgre6?9K=& Z؏\!|\7~M%^ ÈBلU Xe,m`LD!Fs21wYWC赫!PCZ|.e='!4fLfQj;z@"_$Q| ߷PRKQg-;[`҉LQF/J 27{fXHW~]˶y{}Ra?2˂q<9U83qDV&f2V<i c1ke׬VڣƩH8hc2EEXNLS oU9{`v oow8z:H~e%&ʙ'ɯ˨iȵ!1C@8X ͋k^BVEW~l?#h!n1YV_Z-A 2_ReP䴯ٿZdץu&Chv:L3.L众gfRsU STsbW@1 A E0׻}^!PC@`AbwW4囂by`*{? +!((0c͌S Ux)[l5QhY1)(IGm_b5kCaM"Tts 1xZ=Iicm_*^m62<]gr1 O]_!Q鿉ose:@}iқ")RSd;;NcX a0a5M;o!p1̾ջWCXTl888c-6h&!-K.q#w_'cu} ׹ BŽ՗.xcSdL"4d! ja*/0!*EG.=k!"NN{0GQua=R@WA8k$PYj!PCZKv`k '*+'2| 9k9z8_`"k ҁkU{/ҷ'Gkŕ\aPƦ b]z wQ{GJ?%ߌnUl17Sb/{1}Ex᥊`Aɣ)o[o^쌕xw"A"ngJxeVo 9ewPBI 1޹bǙ_j$ʨ#C `|y3FDNH欶&t̪q?}ћp&ɡ_ 01eb "3o`'o^,,*:h5NHCl7 0V{LWAtr>Lgv:MŬY4CƞFu] 5!.pObe.S~2c8?&C4؋aCeU>Õ8Vōp=;Zs w{w{nW9 TՌ:OSC#@GWy۴QvBp棶Pݟ Sjv7_f$KN8Gŧ!C`qu.[]G>/J|vǪ%KTzqN=.>EʏhLn{h烁ңjsWK0?L`ٻZ~˧3ݴvnҭ{5~pu/ƩB3b^i*+<G+aİҬO''G +:+:9~iN#_#=7xgz\ үNkKoVWCRGkWa M5Я!Xc,aY/E!/^3 "a?:gElĺUmBw]gI^C@ W)rj}BT?k5'#h/TZ$Aj:{`Iaz`QU+ fQWO3ߏI7V^(+p1Yc^Aqk>prKc~C /rt#^d'cĈi5@#7b+B<%WNvo>T Vn캇Nv8"؜BeXX#쁹әI bvj|8LudQ1XD.g+5cz;f&l%c1ck#%b D/U %Xz_}y/S*[$wrxtg[n'(2x)Uߨ7,(POI0%WZUB+ډ”!IW2J^uˀ>o 5 jZX*Pd'w',NbICςI/?xsv@z@M_ R<.>KK yd2E1HцW-b'A1k@fc5fI;BH`.g572bo9; @`GzG:@__v9['^ wփf7-B &GDoޕLMƜ:OP V6R/ΈTj%Ku"#X Gj|(XA Q"W⼥&+C&ȀN{_3EM * sb\]:GSL (s'eq'q2ڨGh|?Eu*lzGôjZڍfvfɋ \(!𳃀y 䉥LSݪPJ.n8:8b;8ݿokxix=qɇs9|*!sx Y-\|'R>D7h|&[.5g*j⸰xp\~bE99Ļm?(9']s`E`w`TḼv5jx-!_zq$!E{G; c͡!481%c;IDuv' -TlD[+=2*XJ[α`(Р GB1S BlR4 aNvhK=cIKW$.c9!a\@4Bwժ[J{evwZwmW 1;X`_<}FiiZa/Ƚ1+vAT]:ԮO`Zjb::3 L̄=5UX?@aa}s+7U̜ q eڻLFBq^u6lp?/">gp-H#@OTe.gɣidO {x C'XŮh+ Dʕk!!|y\B)&i94ykHT:ov٘(/#l"halM#.q32h13\Р 4= ܅<%gčQԐ&V ][-:αH\eGN_x _"&~ݢT[,,58?[>-C`}u= KV ^~Qm Ӵe/9+s 9tۗ\!3ryW6h'V`q;0X3ӣ3srvQbuΙ&!Xq1_h?L:6Ȥ"\FHi6A{hį܁m5,Ύ_W-s['շ`;s2t#8[(3 3T$ s Wk!N j.Q\ r?ԓơ àċ @y9c`8x3,o7ǧuCN.MYS5J,YIB"x޵O ɪ#A5$3VNUत,`q#͑*8&x |/| kh_ l0a K!N |'^[]{Q_\ď@Ng;I/2ag2?yI ݘ `+M%o&-8!5xGk3,*˔ P]!Lk[`Ñhݕ'q N}{ɂQ¡;+h/8чK}p?J>]M!oJFw)P ,8ޅ+V?$ HА){cAl7XW\.gZC@ yq4hkNx1 )1/TN1&-- Wͳi]Dx\h%ws$f: +X'BT8aAZ1Bs,;ۄ@ƕ[/oSo:Ƿ`S=-IQ6q2%_wcj||p ,⨨3gH'aH2`¨/OR&yqoc|/Wx5}5_! 1jpBy/uoެj]mő1N*?v~H'}jvdl̛C:7vŇ*|mi q VdI*2]MN}}?:pXOY_*=jgַtwuR ўyk0 A_闿|ij[ؽ Bs1K5%A `O`%le ^vU@}!PC{ ~!gx1pBt?#rɅd(HyB:`+Iwߥ#.xM+PEi|Ě\J~/BR xSmFbhɗp-I:_}<`z"17~__(#cB_$H:.IZS(p0d49P;Zp']wޯT-MFos)d@)i>YK܇=[NBgE6*[ل-w\W+Q .b5G̗C_gb0 V;3OҬp5-MŰ-vنÕuwWoGmo'hq5XiB5)(`XN*į")!lK*`Hh 4~ U*vб'0uJG |#EL˼bkWC@ 8Z<*c{6qxvK3&p\'K o%o6X3"lFԆGdv)<=gc&FT5erچ-u ?QRk|tpD8?Ҁw n֎1v>-m<2xs 9:?/Z T/|@&ۄb+@a48&xKG_ѳtv|Gf)@9u& mȵqeҧSgtܵ@LLNXdXѓ9y;{r_z{_/q{ 6 ]0s#.=[նƘUj/&͍z:9kl@ƞiy?*v3c\EH,{Pл;ނGb=qniQ6w$CjtxvN;?xptW+S,aͦ0L |;.Ҍt. U4g:Yݞ?:+D(eWաad+>)Kb2VLb⭊<'~L!O9lb[IJ7[?,eov_|'aU1n+mLS{XͲ0ooխznMy,R$%ˢ$MK%@%'A TdYQHddj=k;}k[ͮ:LZ{{5\#A2J.rf%4h~ݥgN1*&sD7$n;=V0 qo! ;$Хǽ)@@@-$2VX v9'b#OV=d2;ivW4?7\BV:J+(^u'i9 I| C$> Ɂ[׫^}ccV]pvZhc8Ϲd==p:50 Il-p,VՕPP}hf@l/hjH347 06 ?7IF[/rS Oҥ I2L-~Xr E3#fj&{1nc驧kh*=] CЦ^]ScU?_ZQ Ά@VqWotӏΜM/xqo~ə1֙PvAH\t8ʹxX@%w֎rQ @CLC<>/D"'AGYL.u5 Nָ.XՂ,٢-LR P P Ca|,;.jq}p>}ajY8+J( rkv;WT Epq`ƕԇm&ALp+aE0X+].׵bZj҈$eulS\ƘdF$6؅Zm[0]*9kH.awv:wa3m J7Q4P :q^w :N4ګmo.$ JY<{>ui ׿HEC&|p >9ab44?7|?"en? lp{NnCޔ *kM iKgo@@y84)# /0LH7 -gʗix^)}4@s4=5i4 ?@/PJ@@@œ^DDԭQ`2bN/{Owo:Fn؆ټ,άaƣpY$_+狷(n7FLIp{:Lo$p@-uMm&s{3ᨧ=j(7K5ňsԆj^]gQᖺ[.xGA`c⬵Af7 wK/aW ʟ/ ҭ^$1'ͫ)޸2yPEB.Д6rc$11Ưs|>=NMQoըP[ϥP԰ZEDŽ$ &L$.Jړ(6N)el.7#t3]!1 *F+/5 l? h/^d:]62Sg?[K[u<|g)7y4zc/OF]x[+}=W{"m*$ZVZPIJWG=+(= 83?A*rM震{ð(n|ܼ'~s?Om^^O]FH#BW*`L.&Nj 4TWһ[N}|-<"`ZP%Q싽vwR  ޺`כa6w/*sak8 Jw<jQ]^ϵx-  Y*pD%eJOI8ML?nYoGѹC.a(0P!dm:N'1j1F-06P44đqOJO>~ݻ Boi$8_qB@~e&ֲ'}Ќ .\#nk 9V73V$_@τt;{-ku_:(RQcFZuZ0A!H#763Wڴϝ^jI6szhz졵t8 G?$uRW7\YZdrev;*o^Y'71L„R$I#E==M\i})o\O TZcfTN2ZPLǡ aQfn!+.1|:iD2eQ(R P pSF>%VCq\i viaN>Wk9⻫?eI=t%3Q ZZ Y15|bq\a *v[)KI"_ (Cw97L? 9k1@0O߮/-s!y1DaEn:~c"|]d(Zy";<+$q5}HBKyOωЙɊpLv F»HodCgG3Ͽ: 揿3 3n./֚3gl+ XF n:2?HIe3 ~3,[Mmαl.lU3>}]Bk~.9 \_&MV8wy7๭B/-dm,(B)N*ygAeN?nP=!d(BԘ]`E=:,ø˭eqb0A5vqv (4oEkAn,,/?$nwrmldV̔;TJ@q 9Xh{uЀXOB kmo@`?@#s S]HwgΧ6ҰT@^a]?}yA92S<0ZFs_z*{Gڜ9l zSBCD̞m*ث `jQAC" POEއ?IW [@[[mst X^XG24n8bMY.K[L>VYLVXu-Tt->_AHTz[P|o6mW~g"g2O?yPkC߆9-߿tFiw"ֵ&\1,  n%0tMҮ6y&zzk0nȪGur5<;#<2x$!J`2[6 7:P_f@x&wycᥓ ~^?D^?_^x 9pq5pFB n?]dPaw02Wғ x:y> xutFk^ \f\ 0+-גcMF'xf3mD.ANv%0x.9CʉҷۇR   HAZaN?ȃB@ %nu2*AØ'.%a0r+Jx)hsdh\x$Y>oחWjX'U/6Il>ByĒ}ຼJ%zPMó;?GF\:WACxG6ӧw。(›\O,,ŗϧ^tk;n /,qnMsuƛL#'=Q\Z.g?VIB;Ǹ隬R@oR  ^Ț0+,%fY9(7Or͇'kn|offf3֔KV:WyMƃ>V1)=@;=ĉtr 'J6B< jS KReU:Wa>s֝՟].#P9Ҡ R*7_`C +0?d +b#]}vzR7mtQ&@"PZ#\4%oB5,׸<=M؃o&Fz7Lj0*j 2ofc0_d2g|\*Ф_o} o_mmlgϧbFT:gTZ%'-˺'8[PVJ:ul>{]` QB?Vh[TD8ګзR pwB@xj?Y/!ous/Og/܀͑HvvT53 lfMITd@2-Q "A*=pb2YtAZేL.=:%I"uaөRd֚Ë]ϔ8//qѯ[|T)odJ<ҌliŽO8hRc/ĞMҿ=s9m硕P9<9dsU: R  n_x]X)=@?}һeVu0xC㴆с'Bo iLE!So{q/q\C:פDdc3֯+j\Ux ܼo rk𻡧}l$RɽnWV'tv,=i_\xu?w=~I?'z%d\-uٯLɿJ)(xudG| @ fY6X5\ڧ¥.]dpd zƹv扛y2 Zwe`Z#`Vtۯؿ[lV vxBB,mҙ ŗ.'H©P}+^d..EP}ע 0Z)n&2p)QovyZIsK~MWIZ2:0xdV|E[ :֙t/((x r5Kg;N3]M[W_6?x{awΕ=HD]C[8s\Q?dd#F6FtEb& ׎Z& `|7=+,W3%?n-0 fobYcطܗQupT<M_Bw?j\d9mXyxJHVwc3sn2+o 3+|Aֵpq+B} @9Hǎ>ѽRAZCLůk)-_ Hc2N=hec-N?o*Wm2LK4boB2eʊG:h#WmJzV9q/^ٟT(CR<[)w5*ʵػ)eX$%bnZ_JO|eg ipe2x21Ěse(s)ܾؐl2׫-V\C{ϭ14_55@o>ڇW>yK]nwÏ= ?;}QӠF"6mvx/,mVjp/_.96RS6ܫ+:X(%ivkɤ>~'Ӥ4ꐘl1ubOHPVuwݖ eE:Z與!w3rZ+R pA@ ~/><8D@lR=, h?~ȱt@s橹Eq5v\!"8"r ^3ӒJ58<#"urNEkԆg_޲f52O̧= =餧5a#{n-@6^}j={$-sB~X[ 1 \VV̺Yv7 /3!6!dzpa= :P<1\3 6/G9:ф"ÿ<[aOݗ^+LLEtcxNDcR pwBU/ !&Ckmgͺ/NO{gOg'һZ@kG;vie'6x ME1 :77cS7PX;N_( ՠ =rK 酗_BqCoo{٫wp??O3| r{n (CW0]U`8L Z>*(Ulg 8cyvZ[Jȉc 9N6ܝW<'.fDOxI)w%BC̗"IW3P55@(b<i{{)bx#6(=k7z\y6n4?>lZgN_Z\`/:N~Cx{7IGHE?0!p ]O՚x3_K)((qT'W^^UO4ΘtL?=;8cig#2^Ͷpp04*a~:SK(OX }JY~WtkܝO-Y&$/dx648UK=x e BS<Gwh xm0ziǒ8 P7C{֕?oPN=ȂGovޤ?Mk5;טg<σRmB@2FCr2)1#Ĥ?R0/\Ih=m ЫJg@1n~ƦG8ɥdndoC=FɄȩp9r?VuC*:HGalHu1/0JGA'%t{5  q"JYuW |1-((x#!ih\ڈ  dIkA  '`3VVvK4 B~JzEb~iȰ0 BhK!IٕxC4x#U p'A c.zɸYND13gd2|(ϻܰu7Y-MDW&!Ɍ=ւ?O}\|{͔@=upu=orh5A6@`y~O=q Z*`3fq&-&>ɰraĿ٠ڿzת?!&Q,WI.]"M50Ø,p F)w%t<4ngi~(0Lzu&x`,4w^݃k=ۿ9!:G#NtN}m\'ލFO؞/]L%(AMГJkkr6!ik*$gU$٫+W   ^/**¢9N< Ax_ϱ !DN-u\kW twQ\W^S ~ j*ϡA]9N0Pꡌ*XIVjm\T:Hag; %4u?c5dCT;,K!J ףkk= B?Ǹyd!踱f7HJy |Bww_o^ڈ +s .%'kGWTaZ=mϤ~4}&mhЅ^tq)w'k2]X#~H` ܙ5`v(9 /xhJj!^`Nϫo╪O왜.vKie a~NCONOd-&BsaO5S[O;̼LLЬf&o: C   n/.p~]29oB+4pD,*N"I?³vOd~\7uzb#UFn@W"6UoJ_ML}iK)((Qd "Uh@RQ=(mφYz5 E-< Y|J"XkIێ!`~KݴS'ri1VPو dt+ W_ZR :kL ɾ_\Z,U.K L>NǖȾ$#VsǦCZx,.4 ,Y{=tO%k{IKY^ oԇ wwRo[[\[w{Xqpcb=' ]sqĨ9t>;$SjWYZW҃ͧO|t)eϡ X'2 h\0MRًI:Q.x@j}Di>R=sD7N/HW6XڎI"bp,g}Jʪf&\1rf-[̿x8Rn'3C죓\챼VZZhE cuM>p0B&lL|ѝcw}2`K)((xC!ta0 cB0s :5"^_*GU}dϐ$Cp9fϓK%-tw,m!a*" $Nc鑋(jYN̵PxɂuHI; قR`2xsw^{[*0tBϡsLdu]<"cYܟg)'NNf~XDWrd1c+tk0&96"VsB  Rg9 :]k@`& u{Cp᯻պ@*턶BcXy/`K=/|:UB=ļ6Q%4(8xDsB@oZ -%,뷒6ݛS+]LΕt䛦Wse8Xo91=T*8b #"9tx yn"ہZ K~-Y1N/R7~鱷T|cy+ 'X!qGRO TFUL>>t[7\j]gY %@ "pv:>ﮓ(,#ȴK

[b_lPG$ѥDz{K܃#Jy-bjCA zhɐ UJ@@@ S^Mu} xa؇'V06UٚK]yPX/\HeBw}%x$\ْi*RK)K! /PjȌ0T>FCGy(}'LP胑B\2u P-b=z|_@@oʷYcÅlcC9-a?mwwSw4[3BJy!;?>^SKK0]ǚO]\eMOz|7-5[&O蓑Alj#OڱPD\Acg]fsJX5Xn;u=c3qy`OwۻuXZ`F(Әbv *onypP󳇯> 0W]S}fAbFd֛͠hԭW\O+ QDR3]#dF` _=o$1vniPi0Wh[Lo0-rLlRCPFxer^L3 FC)K؊5+[} *Z(զS9l6 ͽ3_z<fzoIZC,X{T]:v_N!lЎ\3$[du?֨ˬzqY+K,X!9<".m=xaE7ѳR   ^?f4Ejr@0D3~+IJXkmr}p#(>|pIp@WAzV:?h=UR7T4\"4}cO} 7p;2<v,䵲o !#B FD/>ʎ4q=E Ws v+ŷ[W_>GO-llsV|1wdB\6 k)t1YY{nq!p%mmw 8fcWnp;8k,RL ,I\X6]a:q1nuIMLY5 ߾p?g y>ojvPyTOڭ﫮*W¿uZղ` [uFb-a^Zl#$<ʲXdJ',g|3<\@@F iR&_7);xh퐤u$x FȚ3* m)Z:Bx/m;3Ww;. it"uh}A>k/0 ;* 68sP)w1IX$:aGOifXA=xmz[3'*iW=HlLc_2t4(%9r,ඇR;q9 .J)((AevH=K1L| `s V%xQNYu>h2E%&RJ]N]'Yy,{UQn$dlYW5+ A( ~x^ Cѭ,d6qP-Fr5W?|}s_|$ĻI.]Z7^Q=7_ثuɽIp|< [%Bc?t2B C^OP!F]?޶"dJ !8j~2ZpAFk`p-[͜*!qfSrlYݘdȓIܕ9̱~mK}G/O~Ҕ_Jrnml K̙L -uL3EI)w0 GkGsLV8$cϴԅc<N_*!P XLDh*&,p}yA1u -b_'kNN$k,fC1}43,-ۤ~i↷4;Lc&3,LHS`Kl`QJ@C^GM4=U!C^܄YOY$6Y͈J.-Sh9l"ILK,ݦ]7 q+e@?1_D7y9} R T8~FpZqFC>R_c}cA'Z ڄ{}'>zz!ufi_JdW@W}vC sZӶBIs펆jyFE߳?o}+zg߮")6oo|gmILaߘ}6&6? QǠW_dѭ[Xg՘`ݚH-yiDze\xAz}6d3_b)>kt, <_LQ놵R pAQd G2b3{q%\gg&t=ۺÊB:vm@I)Ob^𸊗 >Zτu$UxnŨSU]zo4f#~Ku}a:/x$sn6Vy#qg 97ڕP,@@@F! ) ˌXFh,nd8ITnq QY $O݇'9h/#ҟJ7,zêI5=0(! џG@@nAA5_є{:&5$4k GnL3,|]c zBR zT<w圇?ZrWCQ`KDz1* 0XBt˿[׃ŗ{1\Ly2Z/ <9nlqԚ]z/fVEo4o^}/-c|OJ"pl$|7l=Q,?HYJ@ ^XFʏ^D ʅ'O= W>ٸo;nn$:waB ѤYFp8[t – |nX~E0 ]=:+ $"$4PHɽj0hJR0Ns` sC`бwy@ܛ7ţ^E:^=8,* nqc_>O=+/琠U0,d5y_(4B̄ 5e?\ڜDVP3xK뛛_}ERngLǫ^82|k≠]+kĆ|Ǯ,٪,zL.8xc[2Yvr:we75\4p\M3^| ~Ф+p@இe<(%fϟæxpjL|Q|,N=QLPy;8I?~~.Obq%`.a =I0ͪ0[Ff.+[gME֬/δT W\gUd0B @{`FY{]@@놀#A2@IH AsP4FI;!$2 go?=^AY^];q)0ÙlxR ^-Z̕#D_kg2I<ѕ[;O}D:}nKX7qYpMC;U?8T8/#$ _q[\RuBY>syOn6[.\XO;WmE)!_:zX2Krc&:| jf;Ueܕig{sҋ.~UxK&qnBq`%!MyR@a(M|&$_  G!ɀ\{KC$L[QVPnn<`4GT2A8!SI7LnRyiONFÞ N[WkJ_ŷ/|3kGp Ʋ>Ad Գg_Y! &q ƀaTQkD>I-m )'ktn9NWfCW4˟oL0i\ ]77.ppIm2k LsN<>k˯.2+GFU'߷~u/K>{;Ļ=9_}Q7z{m_q;L]0ѥ(dMV@9$m1Lay.55"9@PO=s1-&_ sy?ɔ%iMFf -8(QJl wWp/#a`VP@ht@n1=W#ӍS} }%bL\Y) %y y*אwt OfCI/)ø|T'J-/]_R*%9^3koG9N,Bvr'?XG=vyꙝ.3U FDpKO>vHW6Ϥk(ziۙ q^vxBDmO@@ƿfnQ?{yG<)cr MM;U0L{:x& ;iz\&\\=BTάCq3;jUpͼ ~h ЂQY 93@w(%Ȉ+{\[DA0#iA7G+R 8?XDz.̮_WA dQݰͯ\K`O.;٪y\la\|w y<=xz#4׌7 jInb2=[SN l >z6e<3 M H??CV:> ZZmNZ+-4d/pyCÇ`F/_Z FB?m(@d]m[c!ÒH;ܹód;.BVт CuUY睝tLJ6y^uBK^Յ6TurQ}\n'_Ax%{|6`eۀ#W@,[J@@MC:1YؐE 1i/Xa!2q~{wf#}GnD{i64V/RsB r#+f4 ^JkO'XNKK?d*875y- R0%z v+3@Vމ (*`_b#iJQvJZ[oYVg%\4c֍։ߊ" K`^#1c2I)Së1p)kqk6z;ϝOgwI(O<^Sxi-sY^+ >={ZN|Wȯq%-! #0.Y^5E/0(X_%++|2:x~ێМC&.S<\cc{+oowwoRBn_5o{K_ _j-,'-&1&7(@*䈁{"gV8DK j6ctE&3a\d t%z28#La `Y^R pA 0r=&V:l:Edk Wkijq8‘5{g>u,82HdXgafR  asAu< #sK)ŠX54R!7X/󔉉i\L} ō˓_o|vIv^[;?77['8V۫$؍89 *mUQ"W"tfA?byT1Q. |4ՆJb\YYųz2(R3➈9C^+@@@;=3(Ki:^8 o"nDҚeYj s '}{o:}ϜKw2y6H@&^T**ge 4 [UdB?B>rox{= {  a?{|y߷C)nRߪxe0\Kڄ5*f۟@k7El.ڰIk` 9=v#[ji\c\LKGR7::)HȉbF^Sw$*\bVR=yM|Z)(XaxCCG\󃐀Sr mz'pnds\ϯuIhh׭HS<3?UZUT&3քdk2eqV5\J@@넀TqC¿AZG@ߌE#{wQ xy XǮp4nvyKdKA$tRn_[ ǥZ; }@>hazo5SOO#zҼl<~\c9treVfLi]&}Tzq# ˿yd$yh#qY(8%dJ`OiwC>D.GԺKpD/І='X[8{k&$M\s Gq##T%&Lx&d7%~5JC6w#kGiRvlbvӰIBܔkhͦ,əe񊘖rF\(ߩ}G} d`dYQ+]K Xt!t!)<[u1fe/NޮC,'+=ꅢ1?삯3EdyHO?7ӧ>t.#.ȫGPEq jpME>(ԳHsGr=HY.|mIO4hk7/\_Ow;O~~LKVp]@*i9\: hb<""4`fo^mVbڴ&>r5ߐ=Zj魯=]<&bA[N<_<cD9i}w~ kA+}!p$̣:OKW:tpu΂Hlmpk^He\9:;Gl*o$19g=O qQ5?8Ę]C;?dM_'D!mJtM璭&t8YWiA/':y6y 碞<5<7p3?o:GwRJ Y'LIKE0#N s HWP`l2yNu0E屩hh9-w&\o'oޯF??VW%fG/~{y\u! ]hDX-iF Wr~co m\"1znh!F:Kҿ?|B,մG[2b`pk12)(=fs;bRdѸ;yW䙴\d2挭1K2 ynH.Ý .:W~}_]rh܃$LX9)ys`L.zcY?N˘dLqnQ1Wסl&-wV tɹ>.]I/^8]|/ ~_׺ѕ#i\62z `;C>:XNBA22Ƴ\sMc]SďIMN602̊ K.KjQ984̉ tZ3uSWԈcC4c 0뜬dCXo $:uXF d@͘89Ͻt`ϣ\[d "ygUy^zbDCm;ɍgN Y1Qt :Ƿ;GO'#{ٗ-!*wP9jď/NoTul17g%EU9BC]%Fcr4%ڰj,[eʨwFr&Fn UoS~;kD箁 Y9@DŽA7H `?cg=Y{]b ]dP#xaT?}q=h}/m# YhȊ+2yߝ~rP pgC h´r9BwMBZMݼw!\K;[$ _Nx6ϥNL?{+-F0n4b(ts_مZ g^ QF(1J*4lwsދPR&( |즍vϿxU^[uW&Ȥ6y02mǣ2j (*h+DZ:2Z 39߉H@3@xh]@o6BrT pe7^W,AeLHF=E: .v5>})w"ᾝSx sT5I %&Y[Z;gq5WiBlrfzڬl'_;QrΑ7ef2l&cKxVlr9/?͎wثQw^ X-Si#3:4w- pgybxGt+&Kr$ L/O ND/̘ o5Y`yYkN,Oq5;ٴ/Tߘ߻ B=l}9q1`bT^o/uR}G@!K->c ~/O,8h8Xu# ?2e?_P )% }Z'ԮSa9d>(mJd+ŻxO$̥i/bnוNܠpٮ O? ^ Āqe7,j{U@>[3Dܯ7B3E}ʀ)^L;(%cO2M -^4L<]ZEk c551t2at)ܺS?3Y>ƀpW?uc\q~,*:Y5ab!` s^kvۃv^owUniߞȇ~zms&wL`am̙$@W3gw;8[b!/\-8H܏L#<`l|O{=yiq$t~JzՙI Aλ$cJЧU-X-̴po&0Ps4Q¶Nƭ%OaՉ ˑPq8ɟxeK٘;D-I9~AD_[|HlY;܌si:^Rk*JGF>D)sWcxrmYb|퐿>YpRfEWmpƐ:ǭUG3Jc ?\#_y"/:O_`E[db( XwKs;_)B3VX!{yP/Zw=k)=}c/W0cV*l @B$qm^}de d\]jI '7 ,yޒ?`xrzϦ=2O^Cɕ4F]!p,i)1Ba}}1-jR/q!h2Yc[y+AOY<8 9?3^GZrwQ xwp 7\LSB7W Q~ügC1lq\Al[T) N̘N4u$=3i}z~neVM#ShC !_ vzϗoU+ \  e~=Ě.gXkࠇQꮬ.{m-ppD9!y>;d&QxwٗX%)ϩD8eKGmTHi`Р:yc^Բ,Lt=a>Fޞ~gl j->:Ʒ\qbYhw-l)WQVaS "$N]܎/Gsb&W񂶁`ҌMmk"5'OYc!?Arc+B 2qMҍͣ4Ģ|nʕM>5^ZBK0ji\!W}I 4f vnenOI@ 1/EL4j˾ӫ7/M:ak\+ZSĿc!4@:do *P<*Gm& !Y'#4(2bڼq??u~?=*̶p`ͩ$1 BH"@ ҋ @R>ya+8FD!@^)Afq&̽8vB#40n!R&VФPW&JBsԷUU ,J 4x1O?B>FX+L}-W U'B'$y4ʿ'6Z@L7*Vܠ?<[rSa|,OٹiLRmBn&sQp lTjrpBo ?!q|_緞ƽM0׎x7BB8X\:?j앦;Ghz Q(1=mk~0St,n;xB,{jA@w)};"E@jH}4()xDžOY@PgaA pȞay0e;'1qғWғ7vGqw"c@P+3T.GBAQ^$ Op/h{n{\w]Hn`G!6E.s0zꕼN(1|"!o_h(p%o;ۿZwZtfv1f$ub(_ '@U UΧSm #1l>~G0-&>sޟ?sSo鹃V?N4fbMXTAanINi!%3Q#D(N '>AE~~XIV  fiPc~ݹf:$K=!;Ld}tfׁ7|k׃B>_eP'S@Xo 'Cor'0VRO$ !R{.4٘iP5sTveDl,1 %6)Wt BE -0g/E#$b ]P{.EHX,m2y4S}d7QR3c\h7ie,ǜ!#FW!C7Aeur݉F'Z\/'$ }v_<-^ 8vP.`7YScʫM:'ƭ)&c QSAj)Ϧ8;p$eZ,PU ,F drNԜ::އ6,;{8GmSh/s5eD~E-`Pw>Ri ӗ `jR^hMR0>n?M#*ށ/V}߻_ڻ^0.z4*J ?5}WJɲ$RʙeZ:'0urhNB f{ crqֳLI?o陣A=LJqZRڪ)ȣ0EZ`![yY9'`';'ajƐLM`"艴פ6\3x5pgݤ~:p,eމ.xvkA&3|ƑE g% 4޺6C Bv LzT#h{.Y^QwT]hR7wn-nr+XxP_'oN`GcWiPekm| 豛 QsD{j!*~B6Rwiwb0b7@,+ h/8h?㔬KQZS'stU5̓t1q/ [ Cw/b=|YJ}8{hgYA'X #O|MVE9 `pk7pw@ ˸P UăN'?ZnluݡXl9No˹oj`OG-f3HPe&aI>+S%}a6NJw2af֞t C;ikixKg^| Սd>vTjk!PX na2dIaR8G gtM5C,d3UeF28L`Whfe,unjoW$!DPc&@ }(Ilb qG"@)a;#\\FO;WKC.^Z?wJw}b7E+U 5F`J'C*rxȪ?6XXxFI5L;1' @WuV[du4$ [95ztiIj^BFGj,S<ݼu2'Lpڝ?Z/N 8>;~K tTE6X;__+m^zv˔SR H"CC~1/XR\ y[v"`|B$鰊?cq6J>cNo:->J,1(Px./!\`Y8 p1 17:%̂<_ 4CD ijNA$;0omQxy5[w;=}ϧ,\8Yq^@tB 8@ P#N @ C7g<q8 0 `)rp &c,MuVPE^JۻYZ/pMē &_eZr+Aĉ9EpCgL7#,CKE^NB6vis%"ȐtYW;ֶqT2o`/I|f5o#k}`" ~{$ιd W*#jo.soW9ϹKg7n2"k#ą4O]p,S>R/ml Gh_b /1!%5PXhe\\mN:;>,+-GE1bL3^nqar\K[+laoϭ.GWN/h῅1/xTL@7i[@[,⁋OZld|b/`vBk}|g=mMv 9s(!<@d^(t9B ?/} r*a1sk[ Qic$*"pADU9ט^cK[\p_OifBKVHѲ-fr_ s#=\ Et2O%^1#AKν9`#]NX̞~&V]&t]/jk ɷ[~})-;QT`fq:^wZ: 3%C\KO06\yPE a)i7mޝv>>Nȷ|a70Zo 3P?oRF^{ifMyy[㯨?O0- .+;)4]۸n258Ez9&ObhTғAM_sLҐJz\ cIӣE*HklxkX{/s)]UQlV L7D?I)b{1:+cW-f ( pQs20 Cʘ*=*@駎cP^ȗe #XfXj7J.>,(A ^tvͤtVi;{?3*W |hoū^:tP# ?&> .1zuagwZ  4ᑥiTQ4S+6R"pezpB\mpf~4ET[G7o[ްJƵH'q]c?2Ș)+s 6L\!/B+Xc2]Ud0,,0[ySC]pFc@>(x≁s{;:>AyvVή^fOYŞ_F<]aq88m3+s'ꈍ!6X`ڈaڐwɴF=mY3ǐVFO jG@՞Z:0zv‹{`(4? 2-rU @Ƞ)N`a?=w(}噝4nYGbDF\`ڏA Ee]S4]L* -a``;P߅ﱍI:ZeXIj#Ιw]l1l_sNit-c c i(I7>g{oHy9*[~Ǿ{'@Ye #8\į;8sk2"lp3xc虲hd%:0w>8c$(w =JrU ,b ĄvۇDŽG 4SIs .$5>:y6Bا.aKE _m |#lzqV9BIB摯KIr&]j׀`h汅{ 6'-ŽCs;זя6v 8* X=gzl1;| Hzy0)6ZjM9kkDFfw e9:Z[v:;{#3̰/m / 1Ax`<e5JGCu-A[ =OhRXa5,7lzw?~w`U 4M6ܣ !4[j5{bFkl?3̽2_i"Ʋ?a'SIϹM׶7#Eލ܍??=76_G^ᅄ ,Kyp-',0t\Rh^cЬ$*l=jFQ֍VVX:sZ+kjC 2J'.A`0 X1GL%|͚(7,3 gQgQ8QuSygb4l>b`O_Ws^C4ŋߜNhV(½4}2d=_NxpgbwGrzq;Zt%VCueL-oǝ|}Ik[hU-!Y$.m9LMOw;Y}~O{LGCқFc -Fyzԙ;& Za 's5EǛq-.<!`ݹ1&`#`;LVz~3+S xAĸ׹(2`# -/1ZngUj;ĂU~Do\.,"j7ZPsNnf.egҫO=Hz똛[,.0 ?t!4} |3xl1x`lc6s ,;RR_[|Ϧe3ZE~z#ޭ~st]j+>˕v'-st,~zii)-|`" 0 F)QJ ]eY@&W6g.O/}V6P*o?HWo `a̧UGC`N-OG [?*~W&@6Z7޴9"N \w\ 0ͷVP,\F'ϧÝ@3sjPê7wRz,7|k#[cC9x:ǜ`,==# gK/]-iipc\b^X''V40p=KV-QT gMHSp$m='7_q~>~gBWFfw_a0=qecκՔWf,5Be)'Hm7a2ϡ>-1FYW M0Dw?NZ_pzրe<JL6S՜(kcoVV8łzY7[*|euՖ}@5=gΞ|tA,vǂv}mH7~LxA$؂b?^CPG0@-Y3畳B/4Hp3E=xo_H}iqz0'S$R{ x3蹵@~ >g|xZNl< :aX0wزz6foS71an8#g6Q@l+#[骳Y Qg >Kj. ehYk<0pOu afဏNԇV;EuN'FNYߢ_֡oJKhiےO:s_ƾamTl#8G u{ V?29ˏoZFAR­<P"3G(S wfnMkH-zf7ǐཌ.7EDW .;?]h^ٌ"6$"v*Y7P ]D /*w'@x 7Mw<ƃ@IDAT(!ąTż^u_)$Uö1@Ec irΌ_Lzқt-d ,.Bxf9qguLĂ~8j">€֔LHɼ'/>'R3 3^o7زO~gjߦyD-/f>sח9zZ$g5P X-.~I'pL>uu$kM"w$ެB9zs8hPGY!GZGb5^k7Ϝ? [§B?oe~Z`a[@x ^>aBaώ5>wUeb'9 %^ W m Q a'q~RGb?@(@0D?L'l)j( 3}Ħf5p~m+a 2<3+lwp0]fsOOo`=爾#OS#ܷiC"QPoYKx T]ퟬ~_;zW߯-b5V`hm2f7i%U9|tF +M}V~ڛQ,G-1j3@ql#nUQ{ŷJ:gGMv0qJyzkI2LK hO_Ujm#?΂x?2kg牚b@VM=M`鑇Ϧo94x% 7qw)G8];wBPpe|ߋ#%$qt#czckS~Wir _?c?:wեesUGzh}Av%poX4b [22y/#&4d!b$)9nTZjCAdKB#TsuG/hB5*2@]dbHv^`diXdeC H)4@pt\j:sj|fS+06ZphBeuO0O9""ތdD( aTq iwA uaV]1o 39Ål",ⲹ_xX9OfI_EKly+=}2 j@|s7"!ǿFCԧU)`Џ?Zam+vG;!(\?|[9CPy>S1C}фju@Pso`Vel#miK$XM5]opƨܝB9{CI%iWOO=Ofz>cU4+P 4 YS;cԼ׾|*ı`<.|C)O /e5t9ݤ=':k4\q \Ȩj s>*;Va@8Bu~m/Z[؇Wޮ LeSԕ^lWmc;닫V-BJ ~;f>'͌SaĿ54O607M{zm8s!}h޸4@0\!i>"Lpnז ǁqBgb?3Vu9lCxieQ@om|A0iY ?VEHF\ %|!aD_Fp@]xIYY:VF̝+ 2V2a^X_8N Yd *d(B<+*&7g@DAVSsD[FhooG}X1v  P-k! bpqey53tnu y bcrCl cځe[* av?DG5$,ȺO[ZW!gŽZYfzOZs&w|j $bzkv#90bu!6' J hT\aRhi~w86+-v:yx%Ah%=4=|/}:0v&kS`F MlQEU`qXN] \<# .?G:IWĂMk$%BLt ׄ\Rڿ")tFm44@[BT [||U2G\/U--PN~>sd@j0IH>v1S0g_Ak;=`jά(@czo)'O'ZcrURFqe= qN5Gi˃Y j!+AF]8A2FPOsax=@@'}<}hE;ua~v~<q} 6ί4nz=q{= rʓl ɖ̭ 8 Ɵ?~#RFa~9#<{͝6"J"||3³>E %U+!.] c[Z& ',e_?8~b6os&F R ¶R] oc02 ț._LG{{1f50nuVbOQAK&"N2 d"-RW-pg/ 3yzX=k9Xu(Zs&M p==̆Wap6`/$% O'^Uб,^0hՕ:y; !" #d |r``zW;ʹ|Rg )8fB]u>N|q ^<̳|cU;K~3K_wƊw&xT}OВ➇4\': EKh@aa69H{>-5T[Or78CQϼypH[!AC[΁Œ9q5_5.oSP N0ۚ/Hi4ffn74;,K0i^H={^z5i 2W@HMb| ?#k(b oM;j4 oe 07t>hy ]l;G.rsOO:, /?_գ^wÀ59׈!k2ֹp, $ s[\;*$ ȸ1]!"thC Q]"]0VWwn%"7هtDI]Ty,̬EAVC5L*W@@)ऩ˞deeBh3}!etKc'^& 0AɐU*Z(i{58d WH.HnE.*j^A!6(jz xzAɤE/Wq%_ #V'>&*}Ur%{@$Wh5Kn)h4XUR7 O~~eFi1Q~Eȃ0kGz} >Z S!iEjV s5(h5qCs!MKR~=2n\_9CA MWq˽KSNAQm oRj  |.-On6쯲JUڿ`2f"*(b KcA*mxVwwX BXm/0 mkhV}=0hY& a? :NEYܚg<eFL&'. L3W<^LP&ƌmmn=>XJ=ch;@SBTBD)Tm_.jhh+.7W׍ܫ+X &_:O ɓS,&yRFҨ0|TsXX3@ oI0n1#09g=Z_O.Nh՘teCq:H?s`x"܈2w` 5ܔ!6+<єm!qԲ*lL'P.WO[_Jg\`]O%"Db6AnY@#Z|f=12;D#yWhD1s/&df-Oc s4*++`5?I 0e]oZ'  F#a٦ ?.;YC F!q/2-,߯p[ߩVRǕ+4;CBJb\"笓)5ʱmo3)㳁yVfcx؞[w&։/][b@ |'I>;Oo q4夽!Yh4' Ύ;roz[/;9At3t~D'd.8C uG_B#K!s9釭|Oip{{VO./ՍͭJw%?sWlY2,$G )"h@ e/ P)!H%%\9 5Y['Q{?頊\>d75ab˕.u廢9Z`[@̹*[&pO20z9H54X]DSjy ]` IV>Gşyf.C(M*n.X5`N'/ A#ay.m3ɝKT*>i&}l>bDN :s@з3VQF=T{N0諔CSa‚>2({S xix{c~O+O .'N% DaFm2Nb#ha^ir65dF~7ԔW27X'nk?wb91tR1J(D7 Ek74&Z3 w~#;q_:a[ְ Pg{Yު?PE^' Faa3O3VZ&ϤHk|9xϑcP#sQ =sJگ͚wsZhW _cg~ol fh QzX HJAŽqs):dUnbyA7`puVjS% H)R` 5ZI}0 g1@ 1l6!МաD<|XL[B3+C Agj#d@!6sXN {2^sn2ˍF]E=\A7R/1G.7Or4]o(x. S5W4L_+7(%K MF=&#sE44Pќ€Y;O$ش" Q%npi|UW@m/ug+ 4PEQ-Hj y Z9e݆sflx9j}PE. 44>`N9cpη*rauq@ʽ[\CCpwVȧ>e2j^~g>ؿ/qF2. -Vo&=4W'P:h7/mBOGz LbU(wObS+{T8zRG"6~/< e e^jYs1q `F' j䯍5`FfqCu(wxvF0=tf%݇p`Ϋ C76@ U:c!3ΏڐZ}2&R L x&vW} wmBrվdsM^d_-Uݲ<+?PMՁFF񭆛P13Qsw*1g}T jtp9q5{ii2KgWnk$dq8P| q"\匊CL.E/C"cWaѪ(W5B@VB\ȵ "&>!T7_:θ9&"8~WZćťx(c:YtICaB:7ф#RhEbt؋C~T|z_TŒ.Hxq;NtLħ8}As6 8-w>q3̺O:Ҙ:_/7 g.l @9 BZ P,Es@?#ZՋS,oY|{zUxht]PJ<^ qlB$Q=0' WW*I[Tw/Fcvm2|_; q%+SC!g2t~`kɔ?|W;c VD^vL7;Ҟ-9`+WV_Kn+TL;be+ر1lԳpaAU$iFH/n՛^- )r0 }WHB}AO8A{w -E c7ON!vzNL&N>W$ `U?W[ E: ЇqUHSڄRo%4y Ƽv601Dbf_a+X3-A "njL/fnU^-Yx>}}:I"œ^_dKgkMno;{]ov[`ԇ0G#LZ!s ag•Z)/QѡJUq%v+C-<7R[w*(JАaO``k#A`e{A_:HӠ?a5V.rP?%WBfmm AICG[Db?g}yJ|۸&Ed7 s~Y/>[f~69["/|hQ/Mx~eΏئS䦋gw"D=cCek^1  9QQiA _zYVtxm}}FoFaW&NZCGSHRMÔc3=nztYGSr/qDhZ`ZhLq@8oOAjri5 !h9r/=ӛ,l浂HK@+Y9_Ov>`AQCng$ߺ"y(\6ȇW3lD>Q^>Սc?ޜ*-R=h '~w?760SW@aCbƒLf?)2@ ,I<J k{sa4~jG?qiuW rjXrU T-2[M! q]=+0{~5 @c8V@jGa(> pC0uΛsԡ4Xx ~ fobCK>€Cb< "jz$ui::fȷ rߢxUD]hY?!2|~I|D%6ڎ ߒxw-7gj#ºsq5 /\|)nZSQ5B!q~/ :UPOi O&4=Y=ZE m1MHdnp8gM*j:(Վd6Tpp')fŞ?۝1b[[7Eu/ٯGސh]ї u7V\2ēKb$ֲR$,"b&岧9`9+] lA2RUC)OK8ݕZ`q[a 8q ~j07?|tt{qe%QicثMW^Mbf{1g f,POHpPJ}N`pD~vPg7Wd2S-o:V`I*#~c0uZ sk&OX 'SH,-JaDafK$WܟO-e)2K}~oκ, ƹ1sλSUm)^1'٬ʼeOޗyF=r}Tp^481=}NW7Hg?r>0{ o}Ǚ+b-@/Wj}w ,o(A 3-γJtMrXNd^D=q~M0)kIA":?|)At=AQ:X`&YɁ3>.o^tmwC @~8È[0"xwB]#UgϦZt C~gl+=DK|U. L̟l;PQ!r*_G271^4 ߺLٿ7<~Rse*?L}q! '42G"@pMD}Y%D!E %Sض O|[OŋG>8Mv]Q8-~ζ h"<ʟʷ_g!r*u +lx8* g!"JQyrt+6>0zUL UL5x A-Lr.s<h%]Yq`x/'V=V\f533ND0- MbD )6.vW_- f;Gi)q'h7L>z_z9^qyFsM:12JZ%@]qwP/;.Θǀ|6 KZ\x-+ZMKq.Ե[Jch5 [7&X)r\oٛ@P%eN/&dun1@Iy\줃^Ԙ/ oZ嶀u V;wuxh(P+n/8XH H$Ycksis jMAU~KHKM@/0XtqPo4mC:z! ܛqXGM{G7͍/幪 8ć}  F@X*)K~,u~7kV;eNn='R'3qɼѨl/ksunw3Gl;HW&!Ftsmw?sL|ɼ1tB`ѯ\  brՐM([ V}?}Ƿ6g6[lY̙%leLx>*ElA@C #mƞQ D1-p'A0J𲛬J` ||3osKg!Pە`%bYA nz6";I x\-m{8"H#]$ѐoZ^+=æ)HVQѭ5WR 2(ZjE ",8;I;u{Al3 ( o7HFpwjZ(;zXq_=ZpƷW߇l|֋3jOբ6]u*ȃPhS*Pb#M X5L#L>ryV-qCtF gB>~qlɣw|,{gL$;_GOcD>،qyخ!`c!d%p]!2͑_5>2ڃi~ƘO+€ٴ=%'R;$>T^"Mڕ%eNGY{&6Rlvvygx8"sH;=}lcT' (%I Շ/l wՀC?mk0m71Rz.ކngȌ tb[g<<'} |~KSfxE_9 C,[.}^ ^fUOZOwo>l[6jw UiU\m E iEqP{tq>ԉ8x!XIcAO3kϙχا;?dՅ<]t9j^~ J'Q5$pB_}'1JWv6[ttJ}087 8A{rQF: 7HMJ$1@B 2|o<`}[t|_eNVi _[,DalxƬzqsVVRsS#ؾ&[]Zl1',t5Rh`fi9jJ[d&P)7+l󭶢V``\1KQܙUkhGE$BE湈G[b;ҕGď6{KIuS2o,ʋ -d8͖+y u}4r /?Žԣfȓp))k;;y _˖pjC<ŷJKg6#2 \w9)?cmT0>J![e0LGoLoRoLCJ=@g(Ƙ>1[BU-P-f&}:Ʈs?mzw<قDӦf^6rKp:n W%_)b^"̾8W8]~03KsT-څܕ+3Ul۴rU Z}纲y –U.gN0rE1%a_ۿ^{vU)ҐR$ʊDiAK(F#G Il r,Y1b1l(RYĀm-LO߾;uL{zӷᆰ[{{sN:8!#Wop>siiLJ9Hbf `Fy^[OOugsc#*N3ažt okv!hvpܩ 1_FƮ!.ň{9wCVe:t-;Wvwj-6XV 4Dv̗۷|8U"%|X\_WN4yD9U"1šhsVGgX`\WڵX2]{s~Q}\ҳ=؀]Vz|_P7䓨Ux/\k%˜q2܇+Ƨz_U`fO0?@*0TGA`0 Bkq},XYIgrws>폦D޺ }l.Ѳ+va!49C >ɯ={!/~b^Yxxh7moC`QW0*¾0? 4eSOwV W9yܪJdi,7t 6@j5;ܜYMs܋9w_!Jz\]c؟Q^f+Şzlد|GKiaDlpVaWY>.rtH :G^-\/vġZ%8nˡ[0!j0S&!p["Cބ+1ޔ%K7_3Nk|JX"0ڶ-MyfE\r?sZ^.d4nVWVԛaP37?H}Σ1 mu.WwoYFyEu˜q2W}q>!*֧ؕl|o!9H~Cffbܑa>gj-<+&gbjWC~3s-MKm㽴MGy4[F R Zǘ\b06?.>ǛUVTg2^zۆ>ȿ+WKW pE0L~uTWTz?y٫6rjά*p "yt1H7/@Uފ0$"m_Fx2BSq+r|*n $8ʊkWC-!P桟~c<@ θ*GZ.L8*dc{gٳg9)v0q(A3w Fo+0h>0Q a/Zwu1 ;ҷ/ xiQD!Cp$n\on:R.0|rn.hJN@NGɬp=y*mdN} ̨͇'K J K Z>‚ol\3`lsi2חۊG Ͽ敝IX-Ö {ϑshPXBd^Џ+A@83Us+pV2|?Ftǜb-~3A4F\WL#pSIKli>mw3+h;+NPtQ 7M",sY+sw-exc{oү\b+|7F `p֪ 7%*_k󢒙E=!M" @VF(0]"O  46siKzv+Yۜ1%˴ Qw&l`;Hnt֫ K%+М[!<7F2UAtᥭtm>3#MidY'2W|s:c>Xjܗ`4R'h;ӓOtqô^N2Gz-&fФ0 Xq\5n G_|UշN\J?ty@ævoڴ\mPwW7ol+gcu yw:"^_1/~>D(A/x2.y#8?A@sĿ#VX9:~H;̫gSkM! Q, λis3K8w ?-V/h3@اH: JD4&Q*o}X+t@p ?.2 bDxJ8{y07@rFpkUe"-i.psecKQV-η]m˾Lۯ‘%s`4GIG] lTl5|OVx-T}M`Jock= gH7 !+aZ ]E['8{V|s.&B<'yZ~D7˛y`Uf? bŰcw-$+Ϥv5O(顇c}ߴ:@:tvn&Ԋق'O8ƹ[ wB*z. p71V~?mmn+W~zUru?AguW68{HB;>" W <H @n 'd]HJAVGf--jeX[JOsӣ k,lAM@tC:0hurYeZV] 1#7t:瀩4 4;6T_az !k'`q=lAJ\pnyOTmbAܱl)2h0T-}0G0}Կ+.V4e!Qp db5-䧝̌}~3j'? a28! -zyUJ8T'xFmkmxvorѶ83~z 1⸷ GT  _d_C#a{),G8m@Ž|Χ rk2"pi[ I8U=&zo'Ŝa%2beg᜷EQr w%h`/k_KY}x{a#goP #s9YϻgG[?W| &daKN6PaxyEĽ~P$Km_HΩdrH|oh|K-eRW/t0\Kdz 9O3[Ŵw8NWGū;QTC%74a_Dh~;b!Ag7Ԅ b4VJ·R!@ij_:l;:r"}}QkBEsvOQ5W2.U',] Af#Se";G4-mIpێ5SE:;{饗.#K{ 廘[uE|~\9k;ҡ{֗`#adGx89(՟9D./y^$\z "K-@vRz"ހ1Q!u~~:`4v5k8ʼ|&\|Oq10_~Jp4ªnk0GhQhE|ra$MF(IFZ͠|h۝QH0D+߯'i`ɮPpЋӪ).qVq_&J'k~uEo쪺_E^ۍܿcD}0+4k|85-)~Vi4/'m1`0{ˑt#W^ -]bѫ[g_)Ϣq~5.`B5\.DY;wƲ!v@ 5١M!@ ,iZJclg>F4ߢfg>?=*ps[0SY+|xq; @#|oU0&B!}Kis{S+fp¹v50|F{gr̊Ȉlc ęH_?܆s pS\Pnoa A:^;αrmn[R5u9xH\؃!p'g1e]cEs sqǀM\.YD/+˯t٣, yf}"} b!Hz.sƯWa5vW"=VKUK)2p)ݭi{⪸K4Yϱ9 3Wy>9<3C JUEF LRO??܇_? `bLģh6nz^ǘohhv5WȈ ZE &=hV4jzJNsfzVGޙz-pTVJu߾ C# ΆWKm, ,bh+:kC~5|{ש(]ݸtF߻Z%.Z ?Ėwt0@F ICCN."=ԻNNZ9/s< ݣ,tusUͰv5j> \9U0V!Zz{4 _`33h="2fv'|ӪxKq2p`D=g^*WCV*[f 1%cr\U\[iL_1ǎ1lƬkң^OPS]WQQgzs75/\6`Շi[sQx\ΰc,ޖJ [＀gng7i{ea]AE}!Ulʹj**-#jxe|_^5~g)\g~%D-0,hS}x+XAzAhLPSfYZ݇=j"lmG}*m9Z)Z__H+Xrt1=`J'wcu-"=ql?a,#p7 i$UXZUڄ~ bطµMmT?;k?uuC H]K]A W'>.x{v{;ip3;^D)$b P/RKK/äVi"bD<3*:P#6W4[>plz8ک +!M1IQV֍)5jT:򤖵#R"5_ݣE  ЈRl0Ǖ`Nc#J 18hGQ#GRAf gTϘr;vPؔI^tI U&sqmlK؂bπb2O9~ y cgT)-R/盶aUٰP6w96ooZy,>Xm.]fy}#n^\<0 xmoW d H@OӁ'VS'9^7-@?Xb9h00cFe @~u7\/<Ѐ 6_l]=sL( @ }9"ₓYeHQQA$HxA7ռ |Jn.VtVP2rsn:gF_ր#cGm98{oH>ja[9v_E\vVON6r/B<W ܇aUgB/Rt2bW[QSŗeZU& Pq|4PbU㴿=H{| Ď(-pIZB0it6x3n>K|ܽq1 v#a*ڥnp# /bl2 RTkf|Ɛ^`b8<>񘾸 uz/G?ph:@`*%j89hGcstSr\;)p#Wn;rZuh!o<?"kTo ~~ߟ,voA}JJ* $2[Md>5% "%!\u~NsKP"mLg1#+v5j)6qj,ya?p0aE.yă+dȦtp[.̜$2Ա9 yjrQe-dM~i_g7>JRsl5l!_??2ALѣ2(?e3// 8WaEwyn/(*Oni62,;sMwՕ,Fi@27`ظY}#8pf M>"~L6F=؎x_Gܸ:J8d_n>9WtZ(hܐrbP5wD투UG-~奭~vd2ΙwOt 9-wZ[i}0} G0JTx) (36}:=R,",!ym,LB@@W5QC .>3}Wd>ݜ?+TT/fOFۨUnId7{*ɽ:#ۈU} i1'}Gc *WdW1֙ z0_P8eܫ Xc\,@(Le_>ռ on#?vS~ZeRd@"t.X5Wq]%[0mww2j}nvc+@ lun;97\UWj.~q?_ W #ҥ^~0I{`٘F/WS2h!ĘA::x1=p#}_|2=3&;iZv~ ;4]H(5P" 7ELWˏSG43̿LBTI屪C4BIK| _xS!Sj']q1kOGC#+ɴv "hQQ.aT(ԙ[ˬ,FBw>[^N'A )X1|T4mo)h7zuc5>PoЗ`0Z-Ll *niߕj/Uqn2Upz28$ږ&#?2Ci,bm3DV@UOV*hc"W],3>c-[?WEEiK?w0C ǣ`` N0tYxpGk r hg7>_y)ڊGnX\rC%rcq{_bl4@DZ)j8U壡Uho L|WXT ya=nF=Z;xE?8jSݙO!Ǩm!pi.HW鏾.\O7FpI=V506pC^C 4iVjip*GuG黿ej v3-`e8:&ߨUt+4j_3TMRd)sࠧpks;]|O=.6L>!Be7o`'4:*nBcxi2iF.?ѐhp0yD!Xp}EzvI(R}t{+ث (GY9@l@J:Goam5h̢ɻ`+o\ދw 3f!gN;NoBO #^jxF ^)G'?Q|!RÂZ<};oAǎ`jINyJ>|Q/]r>짫Jj Я] o ~'~诧ɕn05 MvC+!_Ěu$UFw9tz$P!JIͽ",~ @ MmVh G2EbaCqq**9|qe]V˽~Ȉ8O$\CPGjneܐEcRm`!yA}Q蝼"qHj?MŌW17𛿯#:S vjLSk v!WԆ#ҙiyn{ +oYg}0egv0y|eV*>D~͕|{7>+>Q.v뷌5C3Qn.-5s.pqB@p]t`06K,o_,#ʓ)}Tsehײd%sϹxt8No\C8'ڿtP\ғrcTB_?w'`Ȫ $"'4D \qjܷh7o6F|l㏥w?ex-X[6OMC TkwgYU&R(qbfi6ҥ~W~Wfiu荆(!Ft!vcx^2eEy%s 1JmvMY8x:O6PMcHfTw}K AҊZy8Bz3i .~j]})SQCaS3D<HSA2O%2'4 gR.6ߕ~ J ڥhLiؼ'\ W F83~Zz;͓1lUTe1S,.䌹{9p߾1KItHoH1TSj;~?۝Eh92=Q0mi\꒑-mَӠ%8c[1$Y%d({#Ք[Y?:FB`=hsﳸAq] K $1WLO=IFCr&GYL+|SlM~YagŷY\!\ nJ_t?Zڨ=!?3X9=$]@P(Ք&QEE4;u-nmT5kV iF0׿v?Ixzt:{'S N;P!p_C"j{ϽKKtz:n-q +#c;Ļ߼DZ DE U\;x3f%3G{/sF`X}Syٺ#V,/їal1Ɵ\\⩛c8ͥ|S\^dاJ)띖Ӫ|Up 'C]/~N='F5A `k`v 㴿miu{ɚsq-,o+۽%1͘MqGsysE=  ~;B=N[LyIǶVi#P g&B&R5ݍ8&|V5Uwvd96\sw]>o_:c6PI $A"=?J_"O9/%j$߄AJC "Ǎx")f;N^t }:bƁ5 `qtI?5+p½7!H\SKƾF Gdk: * 62l`In3YUG 2^kmK6BkXy-3zGFv8V,f(CytSk=봎z3 BV==T뇰HPĘsbx8YQmn +6BFߦ}wE;KL j>3_ู萂ۥ+x7Vsh]<뀻PWƻE$~;N^xzcؼUnTSUԷQhhMmx=^\lL  )1RY~ ;R=OyJw\%pϾyJ>sݙR5̘w酫Qy|Mc zSuj (|Ν1(^`bT1=͜ 42߭Eh/GX8=qv6NP/g9|w&}zSP4fo7NCpSs u8wӼ$5W|G>cvo6zԮ[|~ e2A!ckj!H %n2qQ""1#qHCb;V"4 I cI !4ȬJ'<{h[Zd8I$P-y`\6az`bu6= u] LK++VGڢ'ps >ƑF\zA>C `cϺo8,K! W%-7m~#"'I}C?Fjn}%- 8B JJ/&$ *zBrx)[O[g%f/t*]M꣩qDMԅ H+RQl`#E:ymI+C*[.!!6\&*#B p#{)XZBS'"M(UXx-h7ǐWz/m'k'Zu&pr[JG k!V7X<pN=Lnozj@z1 T_ ߅-X/б88#T Oy3'#NㄻGGic|Gw0[{,WOW^__?o:rԮ[?|׿7cpۄ] ­qIp0+;ZNeˈjJhNIsY"*NJKP(&0' 8p#HONcة9V9]ԺetI=LW#Ϧr4XeVP育 :y.]/F+z%@;=|~%=yz5?q.-@.B0a pU  {;[RqeZ ;|, ֢#26 6FFDVIDUSej8<Nqky"_2fPϬ#Z4@IDATceUF/"?rnma c5u.7Sy *=ߙ#luMq%,`g5{=4ЂV~Fvg+To>_tzp;j:5 *&R1'%d56fp[ńA)=c!H>'']OiEmՎ4#WEx(1 ěծgf=B^1eUcN~0=`P&Ly-A܏))#3ab~ΘRA0{QRMapŎ5jUsw)q0NG[{iwiAZ5+qLΦi --VmX a35ǽ8?3p\x!*y3 ߪ/` $҅\ W0vu3m#M/MT)~io֚m׮=Vhb8{-5gq|<0K4!ߣtaN%[9c[-cmA1%ߩʌsijqu#]څK?g~N6ծ[ w p!TUm8 %B m{DXd`ܚQ7g.9Ra!*2\F,#]V̳K=Q +V`i;>tmEdsmԿ5Hꏙ  VV3g01] 8e\|L%H]#s嘸8xѰvӅ^LϽ\^O\C^ BFˆqj0J3(S;!:.I?F2[D'ӿ@ =KOv#ҹ'ڙUT<'+#0 A`[Bу.ZQB&pIa0O+E7@hzlNWa6{d.Nϼ3ça > ]0\uFW"[c.kVn\"ܢaڧJTuL/fE*͘lg6LCX#{'?a^~ V9~.v/M`7Y?rZ坚&蹝І[C`5=tyL8}}H(+dxϹd̝WVOz'ԹM%o˰>?c?0wo& _K,f gem>$ȮjxQyߔ!#+\3;jIչBp׭ijutbu?}ǟJ{ pg7--zsijUߨ<'JieFܹ l-j|YD X=:K/^g/yuɻZpWXWr!C?}wj Ԏ,H4d$[ b,%L[x/sRm!M[D*{`Wgza{;'kam  F!r31S4FtX\o7[ 0CnZhÏ><y!4 l̼BЀ<hٜA@Nf/f"YE /6?O", h7: ϤqXiAy#\9[T%N'2'F?un̖q:P̉մv%avzCke=ء\eH&jbkӋʹv0#hSVjˎ0/w6Aao{|'z s9 \} m;J_yF G(u0&HquMW>+ 3 Pħ#{f03N|:݆ZTl/W~_[ꥏ|>|&=s h,a'&^o{653 tW\ag8ϵ 9G ɴ߳ _}_'NenZp xOOc@u=D BUf<tD:\\bEy3T2d&b3+?m C8_XCjt"pr#oX8ΪT Qڎ} !P(VV:G%Lkq+1`{Ms N(x0B@(Ojj!B0z1s~Ͼ6HJo6`Xa? m}ݬR ˀSe$LӪNs.pDNe.lG [P$2 A5P7@{'{nX?ynÉyR:8ᵐN.55 Jո#cL(G2qLk3C~ZrP /xG>#3L:`Ջ`0G smJ aeϒ_6]WmV +>P2n`*P]GC9/1|fdYXt,*n꥗P?;t3>t+32g2~>cgDeRepfjIb%ᜳ,jҟzՉS0p/-. zJPBKhH 0 A˶c꽻{g`ጐ(6!n "t_f#O`އ-T^lϦ GJ.2P 0AJt`$[O#:sK+ii.-+lj}Gqk1AF3T *|U8V癥%#Tw1~x?@4Є. ]B[6?)L87UFF(oaHpL_ ?B}vzHWkd0;ȋ/vxnک.t={qG\!j:>z=lANGG>@_+M`W<Acbc?@t΄;wQy34#>/,+H+pp_& :RFUk``[Copb+6l,5FXyļuC=Hs<=ˉ2t#HwBðCX'u;t~mC^音0pTLnGϫ/8d].BE`U1xN,\׻}QB5+ ^F*" WH+w8̿ADг3"Jt0#XWHYyZF`ogVV~ EK+Љ_|?5paʇnZ[k8 cʜp%N4cZ(đjL 5𝅆Cʳ2x,BIJz?fz>ؿ Gc3UN?dB8R<$^p_x5Ks~~_dX'`=-p"Di#N\Yibi@NdꬉS wY.I>ZSNV:U ,/- lN5ƴΑ'WNbtp!,bH} Iy,-T՛H@Ɩm#2=g"" 2=Ew3,%f>8`e5lA|d 2S 'd& )!/! lIE-vtȾ!v7a(ܳd^`*Wo jMV#hpqIHJ:ђY- jĹ?#[H0`Iqɨ0f aNN0jeS -.Cd!s+*ZY*3.=C>d4+MV'1hm1(Qx~u 61ç$~] +l/igs7}_K{[;_#^nZpwY@g?pU iuAW$@cEM@"PDM:`V͸P( " æ& 6F3RE ,msi !{ȾeJ̉4*QxB`8pw^[\1}졳8/M7K1+c:V{Mv3B ǽNqJ〕rm;QK ]F& .+wY}A!ݳ5Ć}[uW} 4~!CC"Lpo#1#:>#}!{KpO ayXG4yH&b-,{,Kл0XM_E^\N=@8`҆V̀5z+į!(XY`l~꠭! օ*%Ȓ 5#u 񳠤‘Ta+.t 0222so[a.$Q|~ A:_d [-u˜v&Ȓ ]H@Bwl z1f Fuo6 :lQ8Ґ 3&zϪujtZy1g֔@<1s xBI+_X4FQonl[a`;v綟8Fm7{&?ci58伾#PQgmxS +-V߇ffV1+0[&'QǾ9}rԵtb K Y6{b:G-u1iXGok+;J.,Ʉ܋iwg~?7Zp<{z?_^K-j`( "NbB2WN[POc֊ 2 UaImH5!(!VO_,h>zKw2eXbm22hb+8UCrzQ^ZOaAn^>?>!xfƧOR{$cѡl?`#/u[_Ns)o'%[O+o- .}S;'q@+dEУG"`s/{:\!hG'9KBudb"/x8q[o#/!p3/L {!_V2#LvC gPf>΀˳0duVƛ29ht=VPۘMcy89 #6O_;{% Dbܼ]ƍ7~[oi*HlFɢfC~We g6\ƉǘBpY90SFo0`y 2KphpBѼ<½f0 6BLW]䊂#̻68# 'la0GwT; o0' QlZ|=!u8T[m}p[ m ­w1ǒdTwu.ZCTm[0ͬsPM0E)m8v{^M;%2f4 Ib/g3Icވ3b2 Mv݄}>W.]I._KG_/\x=-e7uo~;fX됸hIa)$c&JDS h]IZ؜_[W\֎YRSsUGCCg5VDD֡jWCm `dPV":wc%#6P\j˩ǜvfP$2K3x4WLFKz@4`)]4J_uT hx&b{ #H*#rCl4⮤i*-](3Ae9SJ({IlP4Y܏ _D1W-5r8zUBR} WSsTwEYR&Mqoho>:O&>)9-cx`Nruʝ, lCyϲ_}ZXo.=t{W.ph`Dت0(~ `kCCuFLgHmrsuVUjŔ>u1Ƥ|&6+^zs {)3!XCob<2ig;[=VӷwZ-"RI[`Rb{Ÿ 1w2Dnm4hh;78ksS`u04hθϷgS"B*Uu;D[ae߅S mSC[KJGN8ď9?K} Zt85d"LW:U9Uy'1ohC!.sT~}M lۼ (G/~ڽ!P ފOӫBw6ˆ꽙JVtDg !E}^">\N @\Ig(T Wܿb 5TZX6bh8U֚oYhUVnjWC,㕏*1n OCΆw Ȃw2M5e9G WW02Goi`<pa .8I]`8{1"}0c z;&3v$㏾- +"? +0+c@[\A%Ybm̞{OsNe0 ,pm -YAZK5 B|P>z@EV[e}Ep r0l[G\:`qugߕ|b_g?|w쓫YJ][Ptl`8ʇZy̙J1ۧjȑZu4%[U8Fn㔈 g@ AK-*YmoSl'=|~1a6hMR.cԘmEm^x۞oT0 V+q7屰sUn>짞4CnZk'} ml`t7Bޡ=[!p@75#:yߢV#];`Nwo.'CŁ8u2T3^o2^TP7*罸Rߒ݃?Rz= >ۯ.fA oVku;5>y 3Qr;@V%fJJR'S:) (P㹕s Y b~"0yGqc=k*tD>W"Pd]  Sd8GpᘆڬVU644tYy BƉ*a#\ 3|yt{0ύEԩiC{FcEr2Xc &s56g]yo\JGZ%N @sQX̭nG,#J?YCu ~^jT>G\)JxlQf% 3NP33VL0K^l/ZA R{Z΀ۨ}MY73W9¨c7ր2ީtȭDyWp7\plY/sDU‰עDGl<ssAkh[_œhwL} Rnr_}5=ƪI,'vgXP&7fgL~Oǫ%;B 79`)~NC]PfsgZm>O+i9 &j.AD@AIj܋pfʂ7س-!TCzG AH"trf|gDTՌz{`t&++2\ms2Zp]9zl&]0pU8=W:l~D{LGx_xnKv+k(|G!&EKc|7,;[z3ӜEP wZ\DbIdb,[,2ƘDٔa H(@p?r, @1X? 'P$2KuUޚ;zӳug{{nbh(M%`h4տ!=\:Zڷ`  ˺__n ~c"|%5N s-Zo.IStjUmX!QG|w]y4e{Rqg"-b2QGm/-hZ}-=C 4o [^j5Ϟ^J{O}4q) γ dB5o X#Q@2eGV t g?&}S q1Xx-~GK: ۍ^hkZhwo U3\w ׮_K_@'Vz}zoGg/ByY*f}'R%Օ b_MΆtKo@ al5Ѽ3o TD?ELA28kJFUidI6dAfzm.nvaX2P>6ς0/3ߚ_MWt$aL;듙L+FSr >rt~SboSX Y/7/w x_Xx"K鹃v&߳w7a+pZXձhW/YcǀYD 1oXGkMT2 OP㰯i" %M\Cv}s6isu?eWBQx0w]ZB@>ӥ+WEd~?[=ܚks&Nl#R2^B5oq@\),|QOK4vn% G^ 8pĶ2oքZz|# ªn*LP6)87ݶeaA߉70_R -Or#WQpAr[|t֭!_\pwx~ucщ]?]':z#_'do O}  И9&BZ*A 8?=::xLIm=p;xT˴=2=]L+逳8k @QԍikM#Ϲ5>[SUjޮPȰ=3Ackyfi[OVZ,|68"n=pQcD#GeF#.4%d&΋QcG\sDc`=qKm xV4؊>XeFT4AaGn2Sԁ '_N x#bB)XAmm yV8H:ԑK1ϒhsiK^k ݵju޹r8Z?Rc5s94R8C1ϸuJ~`#fR?7kp!<ֵrA+/kO]%</CQeM5}0.XQgnk :'i0mbCx =mavϐl!?o_ 6EoYˠN[vVwBZui:gu/@;8'_^|+S}` <Ng kO|t<%9E$LhBĊ[L+EBx\[Ι[jV +[,w\G W:՛l߬9EڨB5dL1F} hv防w28J 2ng fS_A9gkK9u}m^:1N:sg@h<;Μ>{C ݊ 2qxe,Օq~W,Kٹ|@:u*VѳLIUV7@SٚA\~Ø{$=tA GMc!#yS"1h-|!o[1D|/~!jL !^v/tMb1ZƱ]$X=˴X|xr@|P&Vebb/~} fs=Tsa?: }ZH:L[j@ŁQ! P!49mB\6a9F:6nq< 2ߔBLm* j]?]-ku ‹XC5`!+iTwj$,rW5ɐW*6Y\M9s>OHbr?|)]ZA¦):rD1*Dr碿y[˜3*Zwotkv_IW/] =Ob'MUj|'Fj;6o&H Pt MCjaWI09۹2'<`$]Bk: ){8 mEo[i@i&ј=vCj%#?,$铿d or)ٓ+3+! KI'2QxQezn#ptObJq>1^OnaGmJ4)OoIoyYIH@p\GcQ?{q҉$BW Y Sf8c}QbvZssiaľ&-g>X:}jW9}8_iϳd\GֈfRKW0uŕߜN]J-{4E V ' TށPTj_Gݝ-Lh$vVȘu{CPq,CDEak;Hήhyǥ@ѨJtE㗐ng2Bܖ?#P6{T b"X[x?M:VG8UAe; h>k.ڍA:+.Bסd=Lt 7lUV3`^baٲ챵ufٸpӝ'RcBwk T\Y ȵ<a|qŌm< )Vu=Vi+0 ƧLl!UeF"^g;88@Fb֜;Bpsҭu},PZw`@IDATR >׉ + "3g]]+2=(v9TS7ж`*' ( v5.jJJ`fZ8ttl1gYHѺw0\_F/oj>;>h>y^0&P hIGfW̥w';GYRd)y,t#< }28nka VfqU4>>шnZT#@f3lfA|A|jΝl vcwz0CGf_@-p~0埡`>Luf #8arwyyuvuWj޶kG>YCdLLN7 QYm'-u6}OtP]B0ԁ[=t >`yl؉ڟ I@8SB}1|?-,NmP/?eڛ?ŷa<~_or9X-+Vr:F9!Y!iZ$C#O#cL=FF @ȑKN6Md4f۷? 0Kׇ'ǁjg! 86z1le6>:SüoBij饿;7au`FB `ȁoONB8|U!\w۰(lfCX\Q.C$~3׻h ‚5w1fͣ3(X]i.ek$\  cޥ ## a !VP!4=\qZ^rӇ>r!}z8䄎p|pd ֫@a5wD\Kfe'd{?q.lK+_Jɱ~fֿLկd/'~߃a!Ӏpy1Li4Z ;A!,7cl˂LܹVti m ?ZHhXan d. [je>%&tM᳧0aHn7Bm17ups" I"de9C?cL))h7op,D:}lsH OTd]9/z3s٥7zl|r]h`:L?ض!`>HKv7,C(^pjZch h8kf5.״I3(PD[{Pc0ό~,/X nOo91r۴'Oc0M/`]{P;3vY/BF'taSawyQo)k~O>r:OhpEme t]Ze"^7KQ=sMllj 0(o]$lM|.V?Nյf ?"* * 'w1:?jH2aÝ5*U0S'B3<h~RiS|;w$ Gh8뵛쉕t"V$JjKm;2>x 9 2h,+ici][Y>F7cr1&X!?u;ȁ|\& "ڈ $YsTݪ5h=F^pd~#|[3Xn7=Y[`{ٱcZ~곦P ~ɳ_+ptuE1c@|P1-C88(n ~dycs=:=Vj/ Zt{~!5h ;Pフ_$=89Rs,bӪ7Ձ" `S<3緻 Tch:e?`5Dzg-;oo2y~@>j4LZŕ#E0uuG gNyNzs}hE n IVSmLn{}GF.旙/g J| K{GX;.# \GS1vcW$L=ڼݣk#a΂N('{ Vs 3ž嚞ï-pbo.4a=EcZC5Zdo D%!+8-,mJƂJYI#.7{|l(yL*T#NkmWdAyµfzB+<>UC 63m Zi;k~lq\"fBIYۃom[i/}홯ɽX~Cѻ틪T#prZ jz##8·xgrIW 4'O/XHP3e"B.a ,M[.BtZjoz9 $7;6׹k$CcigOLط]_d5铬 l_5؃'s_bQ^*5.gߘ7ɇ6bfI.O<W ,bϸd_Iyԯ2<4%5aG\װ>}#-3#D?S$eS;aԼc4dž0}9uAI8,I4N ڻuИ+T'L+]/s.ptBk 8Ggm}` +H!^[jzo^O b!{6ĄSzo4ݺ_||ﭵqލ_U;O+ߔ?VFBLAܾ$^d1IK8pe 2B8@vU uŽܑ0!ȫԕm1S5o LJ-z,]"!R8@O_m_]58>ݡ`o+ nɫW^N[81j5a,q%6u 9y`e2w`_Wkd _4VUqVkW:G50A |É0҈-vM>wxLrH!,@arڮߚ^"gXahfJ@,h,D=E^x,p @Jdz`OUFmpй>]:Dx'>Ŵqݹpk-G<et%k,Vc 4?;x/􂆵CO>dcݼ}{i*)*T#plFpbO:&zXU1T wgǐ"8"ydL3O}: yv>&^>fH],e3K<ӺŴVK+*4 ŤX~Ǯ࠼fQYӃ?§(a+tJ&${@Y)AH۩1ŭoTWfV{D~rq .BY;`g/\Y>XPcI7n9 瀚kO.Dj*O(@CxՃu̡x0lfƍS}tEDG,y@wnDL*s;.I!&-rN/ >\/UᘎpQ<{Y繞!bk &jCݶC!1GdqB:pugp pm(kH[VK N(U~@3fd>G^sP s2'3TOʈ҅gSoѕPDqLy]`\+ID?WjPm!`tm=.._LiN Y@q[j8&`3Ua#loSa0חx- 6q)[6nl`V7)U>#p;d=X}S5GFg ӕ9Jd[΄L0&0m#D$(s'STK*#&qBȂwJ\a<]XL{p``m6DW}gNSχ>\YHgǹNE 1;6Y4$Yq5W^2A[+3 qd õ8díNpyo3΁&eC@/C,,\IV@%@}܇h~j}|+q^yCN _=̣.]Ni}]Og@J}M:!L guK,-wt|Ut69w8l?фah脂ܧ A.Y:7' 3Noٳ CӚnVZlp!d\NY|% ;RBpLxkM昃rzd~&&8ū p)8:{7X#' `cN ~D%uB- p#@RWC2V,>6ioaE8|Y7]\?< T@y‚Amb:po2OL gPHI=pxG[w)(4Hf!Т*|B5;}{ZVjR3llNptg5}C։G@T48|]o/q҅P. u)Yo9RrbDZ{i#}=6p3]># gSjL;ḿ5߂БZ;Na*c` wF຋e 0.IvkC'CB0= ?!)l7*԰?hG=^9հ#j7xx07ۮo}*T#pȰ)3`pbr"/. jN?z>=xA%,R9|A+STFVB:W`7zb|=#LѠ`} `Sr֮YRo*9RhUF؍?奅m _i4{DA"&]4J]嘌%L &Շ{-MqsBulE c p 75 ,HDyת^P)d8L*C2@3f#UNq&>i G+{wo~0W<56Ч}:=H}\>go+Q>`#Xj| ?ZSoo3IBXƹPjAT2A Q RZx/׊#ezQ$g_(兵4l|G{P􀔆h%賾d"D-"!636D·*Ȱ6DL O<G~( 6́"rӅӫD| 7 57YqP@4rKZ6< "9n# 8 ɣ4Ď=kĊh7tr[K͡|T#Wfpss-sz01"MB(dUpOtsm 0Ô ۋlݪH .}GPa[wȫy4Ȉ4,X=wV|:_N pp<ѡUV#:b]/ Qk-pD3!B(=J'KPɾ%5D0]c]?筇?;+LB \%|5czPK/p-]y &?X~buG 쏬q*haʕv"pU~Ly!TyC8x.M}3$%S\jW4!?G+Lڊ p.Ky4^Y@XhPFE61\[9 %e*T 彄_a\eZIq#.̿e\]]I=g@n]Vd^fJ;B5E\ r;r&?ze9ov=y[tf<78} e(dR(NR4$]9ʾ,guqDYIGdz.d+rIA^0Z!Za9Rh?5K@4o!ݹ!}%= 4 ~RĐF;u2G9Qk1/cE]7eߙTT*Ni`i Vg_S"P {9go4IZz%V}Niۍp .22d ?gWfCj ?kWo_N?nZd x|kq *l{2O-,DD&V\'PgIn8ݖkHL:BtI1SIC{GHgp虓/([PAG{' U>߫p|G @Af0++{-&bABKV:e-:X M\d|xph3rB3Xp~0Eçƍ-xڣa0v+tyʌHhU|Z@kߕel f%XjJ93!5j,a.DH%[U7j+_&@VjPU)̊ <曐+V[2:S *3-`g7]{&ynf P82NƿؚV0Y0-Zfl[ ,u>ׁtnz?dt}Z ĝ^g<*T#P )p'h>C+6Όz>Sa:DdT&{BO߈9=(]2:1J_՛ʳW_U'B8Vc?oͅ3-URJR :sϿS9!$ nkǝ#X_tن|N>S6FE:8ntx, LWue-G5-RMf}. cƸJ?kb/,jNSz|r{~,o3a ۊcB1s>h2ϖ3c\a6 #f iQyzAdKKR)QB5#  7r׵|co6N&ʮ~h9}CNhi#i~Z]Μvbs[ʚ{s٢~Z ![~)8/"xdzr,4+{G,`@,졌xJ=CF]!j7F`y2Ipwv:B9k nޣ ܻw(IM$y?H0֭JEma7n 4,iac@S-K1PϒB_\K bfbX4tn`f ^)Лp%M=c3}q0#14O0 V ?~4Z)s4=`tU gn#u nsUh&ǎ{X$^~S2 F0.@Zu¥-[xpfm Njza#k!yPtƑbzAw~0z{6_gFbNc~"fِ'o?q| ^";R&n7vV7+P+緜Bbx` Sˏzds{2Epn=>әEHe-`-w/d?5!uυkFyoϫ1 0?bYHo,]z٥S?F[=X#P g58SnM8KgB^adz3\!P/ʉgPgr|Kl@y5!X;bK.k>mm"!Rev WTT#0?͌2QOvm8 n{ ƣZH(\k](}x3 =il[NsR^}?-r@>l |/yKk1Ҡ]2o6a(0q8զ{XUhE桏aW4hkNa1sҚ~Zį1W*T#P|~f 4M3igmf&:RD.\.% ̳x3eJ(%An"KwT3iXtK?ΟXN=k#H)PPx2E}Fߪ<Dwqkh 3*G|XG `wF Hfe-+=}'v`aE02.q<$`r g8Le8dJd,Bcy^TՔN3M_w\7&Dc۩9sqL2r T^ r?|Q<;q m41~dI1* ? KfVDp-3Z' 't5:rlITr>Ӄ wۋ#@e) }`I{'O{f?>suG:=4voRpggb]Dqq^W㬙k)bӿVzυV?Ŷ-,p@kU7Z31V曽GEw,dhq|2&_ZO/|Ŵ~W;"Tcg>͎AXjw&Q͋ɲ˛yvnShÞĿ07&F= pCgꄊ(ZJbQg97M̯@sd-K5k`f55wsp{yM|%^8KX/"=8FfY A@ !`ݓH< ?cx"i^ E1wPy0ąAطC^ Dkʔ-S<3Q 8Gki];ÑCg| ډr2M5k~4gêRv =|~~ߟn:[[- a+ e5%~w>0:yߺ6mt2u?#LXjm/.,59e<"^cE(pf\Ḋ9}~[Z9O>Z, >kάEFj:{% v8:wI[Z FxUtl 8x:A.n=9gXh`]< e?8W>0.H"=|. ,s cv}<ŗp 1Pg4fC<1 -b!Ȣ|`dʠ& , t6\C&Qݝ}eKU9+n ;lj ;>`2  $ Gua馏i2-xr sF1+i9`N*C*[O Pp` giEYB|[N0j2'O,^+4X :(p\#9^#Ppgg(q)`|=dS<>FZaA<kpT=AX045-@۝۾+oę1~<[=|-5Wn~:T6wP΅Q>{w66y"~y.-,Mx/<:b/7zfqߏ4U6YݏPlM&uq(]\Ѽ5%S0pxpa0 ䷍B s!!$BƇE'mlN6CHaa`\e>iVlip~*? 7{V!κ  m ,+?l;fU:@K }UL2Ȉmb5#"|;l(,dT,7WhnwSviw[FŚq[t9Y>k"og"99fN!4өSKM@2' FD*b 彀bu QCc+ŕOs=M[l\*\a3 O?µ)s|4S!uXEщ{ y-qzbORï|+y~U}}~z;h\5{%99wFIsr_7>WPG"VeR:rD'=O8|a@=Dw{5U?r ;"*6-OT\߄w['?!:x~ \2R?|jj)`V}1.nv0h(H Rgp>F,3p k9" QjT<"[aNXDeeyL,9#PBi9gV=>݄K0ػq_N~\MeЍ ƚ4dϲݷ.Nejz0܋/{v{'ߪ֪zB5|>{w~*Bj/320Y3l]"KJrH w7 n&%!Єd/ǔMTm`)fǖ5 2b8yYa7w!8 3h%reya3.۪±]≟1\v`QLM [f¬5 8M4B@(ā` (m" twϖF8iYf] KZ ěd&)ֺHxJ/xWKlX{¡3pZ+ a dFE]ªĬruz,yhҩaZND>'wlriE0}cOm AmMxC.>'`uLh-avlmk|WhA;G0isg5L'㴄Nһ|"rC[hdvBzH R1oPo)~~Um ,j9r<,5qVjְA+V:ڔb2L&{ "T^ǡ 9oo|& OvFXPL\XN{n}7;'q$!t/{^ i}[<?DU&S򃕁ys~FNXY!m"K1c@lU8#<>WPδ9f`vxp tlJ?zV:L"  "dDS:('ˆv!"\\:_4ADYK3Pɿ} houqE\_+_;SjFuF~S[ݥ϶Zh> lf"-&h&xp\dRv`D?fɏMqENZ#G O L6@A$gL@pm abJXq296ʾrLJ`쒾9c{@ǧ@x b\xu@dfG/;}nsi"jjP 8RYڨJ`Y`lߗ2su-`}r $ i"q++zϴbm'G2:j1c(`!7OMSp6oQ TcQ=U##?S{9cj :b$Dy̯A`, 2 ]jDdJ@.W4mZd`t@LWUc,b2#$XGj! !D!K2HF-`}2fxFaEl6}{7.nG͊e C!\i*].k771'8Sv|+B >ZB?M| i"?;iSjD Z{ 6dV7ad'~BOAX%q,hQ~U PWxd1>_bGiTֆ>k&I7m4׀SN A-܁AKaN6XpBE5V9ze` wC/^HSW#9b׉sߨGc6¿֙a5eѬRgm&:}|γja12 !*j X "x//#2NZs- GqoAdz E~/~1 T#G_O-t7>485g +&m0 GkSUj-CdP>F^^=s MĐKXe;H1<0GLaN^# 1! Ć F6{CIx0c:SȷOP˿[GP䔽"ZW(|ʔ~eecYgn4qFJW.D$6{.2qMdPw]U{Ԉ+B&hO-~>~YysgNiՂ<@=;~+ ؊P[DvSuPc 'kIL)|f~݌"`+{ߢ L+єyZh~-t++hi@|7hx>Qdnezp+}7[Pg⾋ZzVϤ3@n )0,S(jjxϺlqAM[f}JK=:ۭ_W ZjAѺj/~\ɵ:DMkPV/2S6H ]Iʳ!H0#$hQT%O\XJ_8z 4; FjI?f76?B5wP#^{POP:b -@_cq~cO8=L\ȜsΨ+)-tDžAUt j۩7?m5mŒ3?ͯG Zl1@LG?~}7Zf2O-B6g`FA`cAfXwJ=g0 ؼ`F\[Ieސ#l )"tf}qXSB7ܴؿ`י@8' 4k7 Ts}.54Htٓoy瘎k^2~ZHb MoIG҅G4ނ~42~\QDëcn Q4p_BpCE\ c~[,xم`0L4+p|h[6 s lGk509q딃“?tδ2zhqڦ]G [v4 :kjsluO_GY\t~`ksdr>4{op$4Œ"ԗWPG#1&T2\508Ov" fN%_fu PΤx |3mGF\ >fS"LjdgT?a*FFxgu9DcP߂qb.xcVZCd P($bX[ Ř`8L\nB)GdZ/c+'b1d H%pG@(+sA\}75Ȗ#tIB1Lvag.]YKk`Ev YAX_DUAXcaj#mb;܄Ch`7'0-.&?BtoQv2pbnڝf_-~y8;2Ôc*io]o#B P30nkB&J HʰyxT߭b  LvCwxoK/nquZ Xh jT 4Yg#Ca)ї{-cQN]܈mIң{AK7KU8#2-L5[4Rg铟x<5XBVW3 I>/c*gwj [ w{o>#[uYF6 k5ot~8Dڻ5#U0W)ɗ{њo7bL)Eˀ{uv=3#pLC: 5 lj@ s9嘨˰[TM὆wWJL֬XCص?N/XGmGk?̎eG,,Qvf`0`ߞDI?_ :U &iaХ|SXv=<9nZ<#rkOh#n!Dk֖ V0OeA :p)?f Ǯ,ƚl&0O}5 S0RG 7{~ pi ^6+vqmO[^X ΰ ؠ#̈́x/gSgltQ=`/:&W鑇3~cմe}x6<}*GP,A13~I_ς:J|`3xݍt5,]WkmTF`~5EVT#F|3zy^ah4%$+s;f\@t_YPOyd|6v 'iyߥ PL=&l[ 'fx^'Yűέ4hpփx,ǃ&I!f4Fg{ihwN^L R{CLhhâb]wHE𒘇7$ex9vFZ㈣k76ShϭǗX!;'.̌m5pgˍƐ-M%,p{.Ѯĭ1ԎVmDhXL6.g9#dY&oo#7 09`a^?\B:L XiL^œ9pZ'V!`u+0Kޭ(m[5' iwI[#LӠ${hٛ]pWonXI놅`L1 24ϛBz/^w;G ꉼ0n=lu}W !vbSjƱ&t0"{ť?”Dh8L[4n.¬ucn,(sRM}!t3}ϾvYF7.]X᰺^@1zF^F'/Mvw`$:sLɐEcx˙{ "D2'_+V B3[42VXxЗX$N-#VRw;,RS2(>KpzLZ(!/g?PKmM#_?k{ }[5֋%Kgp !@`K6a>v ޮf V^~_y/ QJTkUԷxa8 l8퍸<3- WG|/21L+Nfˁ[< _;dz+&ڬo#P6N ./ͤ aaDW^\˫4V+8vPQ[N g{l{qs@E\3 3M',{}k??эhϦyiY3WiW]#:OP4"k՘;. xywFTᘏ WGb,2Vz꩓cym3><m#?ц {_1rv:C 30VX b ("5B3pX+@ 𹾌`J8`>.QSZiCa6ʕtT7M,`M4kpRj]v=څbH!\OL䱐mravR>oi[Bn,jw; cZϴX }2}/Wh)99 xXě{b.hG]W%LiZЬ:'rg/k~{WW8 TQ~zǏWiC4%1yFY  aFO}7XV$\Py%Ra(FG XL?YO4@og- ƠԪEl@H9LdJA* ajhmөE4|d2[7B' 1C4͗ɾd|G2 >s֝hxclGVaO&uk0Nh`pr$00Sk̂7lٜrnZj{UD3,~|SFttyLl7 H{{. 0w7IZaZ sm<$^ҤxXyR"bis&MRŐ|ȼvX`0=ww~7zzX쮯2z@]n:P`\vw?kX,#v_p)@+Xhѓ0oܲr!갑6`81a .am;&Mk|#i+n0-+l_@Xn uByaf9 ܗwxFQ27Um,U\^[Ḳ~Sh27U³F ˶t㏿'}[YVMe/V5tO~_#_q+(s36MJ7t?/~|j^cϪR ?cv~aisD5-'5,J]fFAĊ̓ 3[ꆣ˳@^9Z?ȴ; eFLB*yB)&mZ2.0=vɅ=G@upGR*HC<o>w mD_*V *vR4 N슋C8U~p~7]AsYhFOA[斋-L.bߏ\nsL'H-~ݐKڤíez.)&yťj@`y2._n2~eLxޱU1n& c? tDpM?AZN,-O"%.'3As=tN@,]8\c^ b/Kƪ-Gc!tӦ~Q S ϝ֜ٶh*ݩFXGGkiee>ǚ shzI/0 \yD K`~EgYk}"Ÿiq@e,po9|~b2V@?Vk&|я!bp%AXT^$S|q\0WT ;QqYc)7ND_jBq9%^ !bzQִ81: =sҝ|b0 v,N`kۓ=@t6Pu/\IB%" b)R˽ޱrG]'Jot? ,jǏ13QVN+;Xr$ h+u5-ߍ m:]@ 4E ZtMS0k bj9ǘ1F̈́]b>aKΥM:&49 53H'tv.4zWK/=-57(%}:ǂa<67p|5mb[W㋅Y΂UstzSG4A( e_ /=LFc;TAsMJyTcKk_!gNT7߱(g;ՃW̻̏|Ƥ!;$ɐ3u/T7>{̿) VStXD~SY>ʐWBV#stO`K rJ;%AK'R9#m]i&7M>{!qqOf +Q)!_7ŹI,}s|Cԙ-, VГfM:bxNx6ciij{ t@kA=[q!"[X0}1x&`6N L/:j{8!SH;9bug7el۹JZbjO/{P%˙{9 `:Ԡ/5 jwaHsČxH=``K@MҀ,j aA +0QU:x thgznA…tgVhS "*U p!3?]7w%P@%nhZfBOGxI'T!?\\\/ `S5lc 0C8PSby!fMSˌ\fjDۅh9IAŌU?'+!S($3<lA#0-w8yΊL[\\v&)nb}_]ypK1 jxIe({=&=eu'TZuŴ0Z2G/Zļ:7,fF.8J:9 b!VUC-{̆1 & I8lro\J20p]D#]3 ۃ3?n#ic\b ްG6˨61ǵ x0?:T:BEMZ27.q˟mĎsmc.[gpe-oa!k }%:Z?|lM~9/_WG#;=p.^#c6Q ˒`¥#Y"kơNvkNLE- `9XqF  `;C?Qes hsGׯaE`Ҳ!eK`uw}7T.mwqeg$jy`Gz.F1^ !y_r1|2Տ5&ƁaAc4v']կ+XJ9#42W.d,9q,3zd|f`d 1߆B#hW]Ng.HF.c`Ҝ<3çᅀo-!CcP? =3@[W^Mx$ab`5dm 6fg#3ln:xX '51(*_xfi}>.ʚǡ1!Vu9oSh=HKmaQySA5@UVx9|_>ٟl?1tKqVkа31E&©%OԘgANbNwr3ǢY0 qݺ9NY_"Ae!樷y&X\M\jtda.`bLdeHprb{*J|;,޵;AfyyNͧ4IMd&1K߹oF$2O10ژSћTl箤\G0EW0)\}s뺗t Pyr<=+U 6$xmDžFZ]{>k.oԻH?FØbNaL&ژo'o8S'Gg qBK'`ys,Zbr̥hv_xKK,!$fIEXMp_+!ǁkTQkP纔Yh'=7NC|O9K' Z Z҆(5:-Vo)% !]KWәΐzw*EbMͲ=-t0Q&6HReh7[؛k<;V_Ooq?N6.㍵l~Xt}tB`&Ѿ1z$Nkp}hiִj 9 ÏT0X փꮷ~Ps/8f\iK^ .h,anxꞍb}Wh7,`>&S p8Jdt>L,DDG{Mn˝r)"e-CGK68wqfX".>7Hⵔ9s1/zpޞ +*xDQ ~JOeY!F>n?ӧ?960ƌbʚ)97nJ7b37oW!x~?i1ool@%x9T*}G|_MgA1͠pj ;\JzPjH,2|qb}c5+G ]c>:;S0A9["g+Ȑ^Q  GԀ02S55D3kô:&p#I zaDepyިro.#_6H_A?QqfEx1B4~ =4'Dsh74ldnZl \Ow/Ӌ,zRP {s @]l"m%0F<B P1WvdM K6k)X;I=b-l3|R,:\ }( [o=7mh1fi8 j%B8Jtl9`#:xb"57`!K3;-{,eќ‰7 ;Nlϐ .^Eȼ9Nq9 Z4ZI4eRA\Z5yXk_^L[}хKT&GeNWCeE~n6F4Gɗ2Ύbo|g?\/5`bR:k<-&DuIi VY3) [9`lB(`䮳F:2#ڡpO!f]ݶ/6*V|*F$Psf/yE 3W'F/͚Ǎc6jqc"sR/ Q1[![V8d0.,=VМCͽ[Xmc5tZϩQӃeS,,d F*kSX;tr}_γ  w|(^`Hp.,73(_C >KOJ7\CC #ϥ?pzNjH# ]QCa5^l36Ϡ xo[Xn]YMg~MR *U :C~;Η5Z3Lj+fʒ , ǟ<e>2Iܒ8c+dfs9.S)S^)&ttϑp)_lBUjul3|e*^7XCHc0$q%!@[f&Kh GZ0+;isV<4Zೖ/f:kk1Bu4i}_axg_RbdpABLPwU W# [oM'XXs USYX}"-y:{^%[ tG+ 9<`~M]}YK*k;ii⓬h&U:|('>~8,ߦp#[:,5l:x 2Ggec~eC1Õssٻ `0߰ A ¡L v=&y|DJ+~$V]]K++i Fk |/l| O4C} %vZǭ`DS:[^x(LJHq@of#Zſioph58J;#/i =;8b#A_9@0s Vp%xg`9\ fʼ?JZgt]z?y0JnTAd6{d/m(I^7#0ܵgǚ&Ly҅{V5@@ qfBAӄ@2.32@Km pE /⻜&@.Lshnz0"YB$|}G,O+h0`oZ 4, @:Z3E`D@HJZ,dn7Vp)X.5i\a3LX&&S 76[ A rڱr4毷cҍ|޿㡫 xEcèÀQK0E1r2Zi&k7wvjXܥ.8 &>g/Қ@)OB@̀P~;v.3~}Zs"lk i0>U% F;4܇Dƭ%*c@ΫSV $g_-~8pz'ԶTm@uJ*T_A`m$k; 4oZN{Z{&$`)!LqH(}{c5˝l~MhK_R4}4Sk-̶I''+ҡdM ]aʠK | dP"'0s!YRޡ>o2f2 5Z"QAxh4mhA6+Ntwav"&˘/]o+2A2VڜL>lSUsqK<c/3 F\*J8Gmx]뀃!50fA|m=-pH]<A+w ֏ƒ-qWk2ef_xst!."+bt&D`UV6s,Yw;w2p,T< 8;=#*m pZ P8垲)4HLhGBR,>;F'MB/Xa`Hll`9+[IGfǴwcj;j"0j 0Z֛;8\I]jH! w,Z .1\IO>N5 )x$niulb}FF4g3VY'Z%-XTcIA{LΞOϲ՝~K//bU *UAA@?'uzNXo-fhPfNO19v~Ln!L)<0 <ŚCE7m.>$9-473ue"0A;TC2yu?̿Ր+ae\]#bPR\BIKhi·}O |Ee4Ϗf3O0K 7 F<%6E=M  Xh t@C`ґ~Zz59nDׄd />5p_2rH0yF{'Ue[*^8&QR8YkSsʚ|z9Csf)NtߔU&V{#z%YBs G$-Vĥp nʽl=e|֔vXm)K1g. !qdH8c2'1uK}vge}K9/>J!`>9‹իXOK4^]:=p7ѓX51}"oBm7h#}}CW".vA\.9\ci?z~sϼ꺪JpUTxx?٭_ Dj" 30>2J!V )MYo@IDAT)|e'*{]gXcܚ4FY3iĉ4z4l33 I%! RL*>d<y?0&cQOj e05 `sO]~ǥ |lN`)>h 淀Z9,j@t4ӧD?%mۊ瓹r vGmtZ){`jкl˸4%^Cj-i5UgVp}" X o~߂ ?pՍo>JI戯R^{s` 1V># k#(j+Y Je:eS[o<ԆMmM`a Mdch77$cy?S f&f0]@íA:˩>w;+:TWTO3OL[9o1&V]2pdQP9 ?3cf&WW(jiS!&1F5S'pXڛvDk(a@v޺8x=扂֑i7`cCۨ0`pʄ=\&!!AoEI"7~S縉O>&M\M]Jd, P 2m4BcX f*9?$&>'a `:d=k؏ 9B AaJxO'$ Maj4ڴu%AZaV:=@~i?T~Ӄa/^Bˌ5/@Z/p3`B$^a;M]2m#r[ԚeƲ09s8i>W` <֍dZ<&e?7Q<uY~7S\)ތ0~jsX!vaǹsi}|L1BM1XWq`@PbXދ&qqV瓌]Ņ["k מI]P(gYJ^_TU^y?í~kwkm dx[ˢs0|KZ/<â"`]]A5D!s\yuf< &rZ Eyhr'7tyԶC=[[W#߳vvw{EGW~Js xK'1K܇KAhgoQdJ`ؗ,0i -e-鳏@3ہe:F]`ii,o73w %2m9>`H,~pƽX˰VW/RQ|eX6uѕ `5deFV0]ŕ[<9#9U HtXNlX/-]xq+3y$|,^U a+sm1gp Kc>¡<2^*ER{w|#鞻`ċi _<X h9VP_n`F7KMpX6 Wo,w*V8 5TAٓOx6@WCZg)b$s%v<.y f,x6&,NJ,)pbaR={yi܃ֳŤW.կ|-0_)ӤuV0A@] ??] $n`ɯowѻG$.)ڼNΞc:kE0G`~uV0h̾B&џ\#`M.ϧX1X\ J/2&jcq(-DUTc'97cf67:?R rF< ڌnDE}V1cI|g0Z->y~Vj+ڰ@QnGsD\FȴB>U詴z&tW` fl~TYi4Gfvhc-C+[>cxVsNI4k)sL ncY(v%,Sγ>&Ls"H~x&Z&]4] _42m%]KϤɕ[ԣ6n4d0I?b ȾL[eX~qׇU0).kUKM,$:x.j j|&;-8#<6@V^( .s=ܶ`{hx@r\zSn xʵ[[bE]FLs"6̿l*˕pZPJlP>_iSq>ɿD\֢9FM~*^*(_Gv.36*MЪV'?-],w ªV'Q5a,MWO2ZOr޵(Lg`v q#!rkck`mss]tkfQo>|?*Uxc P 8WTxE}ɓ\s]iKu<ࠅm:2YV<]11IcdLB^ILPzd4ژNSMc{a(,ր쯦:-?/ h zku&t cy9я1D6 ه1eF0?dz@|3$?EKhluuLDȜf! C92zB' (+˼fB[Ô[C͎ۻLy 60]0-nׅyTHk~->B~Yi5#>KBL`fXMmx5j:a>>qS -_'d #HqU!"G/ܲF#.H7O$Y5h\m+Øntrc2ErYq$WkP5}55`my'2|a8bMDm<0bZ U:+h=?A% uƓlm Ym D8RE9/$OQftAY51k ÿp$m4NKGmͪ{F(|@qb/V40⛶x3>o;|G^x}<3Z{ՒhiFXA7ܐ^|}ʷqt0{7IGZѥ}8Xo防u+7ba,0383W~܌9׼b:̹>r1x yk4[>LX{c;}_KϞϗuV o$TA[?}? gs[W`!TS NF02XnA`\ ɍ-ȳ 989e-3UNs2N>Ef[::gd @tn%*mooQ'B n-z Fz#mS{Z~7~G! w`Wژcb.?/16}B,, H] B.t pOH\r)gM7yRxyi^Y qX(O}|5{f7_ B{7GS .Gh`0juPmAڌ/#]K%~~ ;# _27fK}bo0Z0=ozׄl#>92c=-Г\ ~b"hHC`jU\We'Y$(o(ҹPe2^<Ko 7Fej 3? 7JA?`B1~{uoG樓Aw$J$ ίy-ަK\;K'% ^) >tVP' ӕWF~$T7 U~'pcZ4 1'FSP9A>/NxNly2(<|H;|f׽,,GX:s9_OhuGFPDW1Z}i4HGrtEhg/3KDZdx}.A4Gw2Nf=P2gZI¹J V0%j14VCGA&lAp65 ޼17ߋ<(9we4f5-N9,~gz8*SAy e}[#ފ].ίƜ2l⣋h yxGOkTX (1i2qB'eAǔdosN=;*s<< +0V__t!}lZ]XA6@ VUT"(4MTe3Q402NN!4W8& moWwL1H0h썫U嵌c`kh~#A4/bC!fN;0sh{Dg5eJfz1oSr#K&2kk|1C1(j4Vv>|f7ȇ(x܎P@co (&mp5ݕ.MOOc)AZ ot^aΔ8@+i-Nݽbww?:vHv;O̱Bwp=;XFeߋ64z<@bX8ykmZQAA>XV5Umxǯ?GmK5N]N^}{.,Zn5iB KإCB']4[qA:y@^>D{ʫo͠yyKCr^d{mM 9?˄O}MWZS -M0G1QMC! ;fhG9Kh7 F)GɄ5^b$Σmy=L"xhpŴ^?әtnx<]0fxG`}n+a#$x8 `YB2j0vYR@Dxx7 7>';<2&<lY0,ʱ\= ߲| p+{5qʹ'g<[D1ju#$)]6(6VlsLe θ/i$j'`L8.h¤q[Cyb 6WLd.]Vxs P WVxE<?ߜ!7 ,YZ3lj.2ٗ "8@LTd!G5/4 'TM?cr䯮jz+Ki z3GZlH vҤ.iMmt??A񛳄~jGfF7ayo䭋Yo^b57 B.m(.2xj[Dou`+~!Z+V?EcDI>C~%i}MxI0'@1s/_ r>k)jw\/z =k Q]z0!@?Hҽt~9ta&2<@cb2OD묤ei=K brt&;,M0~o׊K" +oB`N2 FFGͳ&nkF*"ciFb[8kn\AiWׯc'O'}Ë|`}Z*x>h*io諆+2~oG?[VPLBoD YC'V:$;A9Kn:41E'99LY! NiKS-N/GqP Ѻ`0 _5F0'>0v0D@:Hjزoc,ľ3-.t0CA/zF@ $3fT]V@"$\YjF2nN/>?@{Ϯń nMC4]tfzO:.i憎&:GjxWwup 0d}1_2Ȩ_(㍝LΔ7iAy/!kV)Ǡؘ Dl>V#\&|Ԟk4g59U p +rr=qnum4\O+ߟ{쮴iԧb9̵uJ0YYa2w_ޭf#At!~- K`'羓xkU+VT Ϊ ~`7Og!p#:f01[I(p/ =SEc*3%n5Rޜ. bM]<X&pkzZӇ,Zm!_d> `\BӬHaxa ![91ij9>z<}ꉠ?yv:cGwo{|_ad_-K@y磳#`L}DDn3.HT^x"p>g[sb fww2.:3|)Ȱ.gI%&qtse˴ E]jߓMOgFw#hw̬b qt8!Oel ocKc} q=5,{b},ceSr vxUkBp>Q,}bE$ [R|;(S @Ʃdl6LA7B+`ܕwJCq".,1LhCj$t^b!fR(GFQ;M6CYs_?6}}wu*A>XV5Ux #.!L#'WI:%b96L7y[!H~3u{_ZnHZJ[ҰV?#ik0Nir;"C.YirL _A?'1Т!ȯf_?U ӿݵtw< pa+a?CNͽ\v4 .o8רвg'ab@m\wy׼oS>7 aBcQ9 dH6^APyy1\}^)3a! #3 d$V} =7Wҙ]֋|4AACpb@@2u\#D>7f`L\1?.om.QP$~ZąxFWE]G1ؤBqLiJu" x3n 'ow[v@`nN֠IX [kkaRm8-_wzz,C܇8c{N3.=7Ü&R"c͝"g|2dUok:RPK@|{g+"'>ϗ7RA @%x]5SA@໿ǿ/߿=F1]MBQ&jB̞-5$1]ʘ#|Nn,EҚJ+]IUJ'qГk[ӱ!mf޽vE ܱ ۰Z]`w>L:!;өt>o]5êja!4h2 :tX!Dyl^@ {|f@upo+W1{Qβ/N\/ EeATŷq*1C174R8tNӱQ7އB2J/x qV/3VFc2.n$R{@Lq 3X1DnO718RzJfe|h7NvLC# S 9ڃl`Lœ#Zjq0eƢ|I^>)W/g֪7$gE!#{%˗=yţg?Zҵiݗ!cd)]HIHmkJf%3so';O#~4,˃A>YlmjM/`+zc6 hKjȴFI"""PT#AfoD! ϯQQ3תTA  t z@eGp\а_NK<[;ii!],V 8o%RP_)I9 "A2eR͝z3mk/\Nƻt{?v{*?J^v+T 7~?7.1fbSNrj ؂8/^wq ӤgɄF>DdL2D{ZMT|T@7}LJO{ 1/`jq 72`&5b'xב,ӱv:ZND{` ]ދX5^ (>bJ BqgF]b3og ۔6g.GA iETC$,%e!re4Bɗ^D0E"K&/HYٟ3*_ bk1l)JWk#ch)mpf7p]:..ߣV L&9Эi?Eg !` -uo JLCCW_vha̛j!Md^evh #" {&ι._%J4H)41O!mc!4S[C1[鱏ܟ>ӔO ~HS96'J(7F蛗֤y0pwo>`m1u [翛fI~J:3҄'x՝1U PAA3?“7۸v ?3& $d%2Ƽ(/Grt\|J(¹S+%f\ Ks#hWTR|LTL7B=g{!ou.3WW9OsWOVb&xH8I-#S!dx# O63u5a{)q1f*VZ;@ RGZz^_-ao EPX?JȧǴ#R~ 8DD^U>W5^E2"/_s8ƻm lԏt~rWmP~1Bzs9koGk;r-^wΓXD1gDWyU?Z|o,mo|<"L+H7bzd <$npR3baaKS3ui&8S,½Ls Y̔c#KwFޓ>Gݧ&37{&*pZ͹.5K ³{|H^?7,[]qV綜x/\֒1"hęŋ![J12Z-h+s/j[*\A @%x^5WAvA~Rcj̚ $crٓtos{Kd!-tD-J” EZ1Jx`D:󘱏X|5$hwxulz}I~BzzڅOiOSb.l7@m Lb"6m Sxe+,kq1KpKD;n|.;5[I=0cF].u䲁.Ľ {h}$az$쑇7C+)dPQ:輨| qP3_LY+9N{OP)ed섭-.[濓.7MHfY raݾ~a]dķqTB 1BqBuzlQChVN Mp=;u378:Jա,c?c,Ӫ9vޥ`@5q~[Z~8T|K=&?>Ε|B-9v}k>mk-c=PaȃB\ݜΐFy-OVT]ne\:frv7ҵ W5\x]Z/Vu!hJ*Rwo<#@{92: hE'8V&\N//C'&ɔ5!3wf(FuDIɺL֘B;Bl6X,-B@57Dl{sf28h'Dei&ڀ{Ёp=µuB6C >4'1YMHY0|FH ׫tmh!t@zj|ozat4m!u m cȳr_6@;ߙ!~KV9iϓ3Cq fn- 'f8iqq5=y_ZOhLȷ5TIWg> 99[)T_R>ؽ(2(Yyo;g\(%{-?#E~y刱+ :Nb>uƅ9篤=; [k*5!P ޚU[Ou~~k(Seˈ0NrTyLybӡ43hD1f&)Of?&e ;eej2UD+A-أ҅SI }J &ڬj0kiCDPXU<: <1 HH@HlC] IcݐZWԭs9>ڽfL?Wy# |2C4a.3o_dDaP8蟽M ]!2qQ]lM@`B3݄D@vVp jէ(mitPzv|:=s<=s4v.y^rǾɃ K| Bǻl6Rkjo/BD`C~Aލ%-C=gGp'%Xr-+ 7 Ub,ֈw$*,FV/̗D,,>u4}CwsA۫иǴfEݹ..v<8h#NKqMO%QwZfXx0s勗>_(TT8%U] JaWV'si0r} m!1rLqydȜiIN6%`,Y*CbF?'N]G:;;ͪ;_#v$5ژ?agR5ujkk Usu$@IDATTG~7{Ԓa@1Ak 1pOt}4&v*:8\4wps 2;XNZk~w :@ 1uȴxéK2+$h+8#J |Dͧfײ?pG F}IMWk\V~KOaP:3~ J+h.E qaLuK~_fro*N8^uI|u ,` pN7Z1$a{`qo[bu?HLتb!VC# .H=sDkG+qJ@A.1i?vu5{ҚmN"(O2U 4wD;*~ePo_[vߎҸ93 phX^ܴ1|uB1Fd&iھII ,u/XLİApo&EZ"01 X.-`;\DSĂa#woeb'h6;ఝV k=:4CH$+LLuLS0<ݓ$ڹ&  }{ri %;\5bj".sVM = w\PA(I@5ƣ.c'5OByՙ8gKR`pBLᇞ6j<ӱI>vϘ}UCG` FXsho+\Ǚ[JiՔr~a2vԲZjۇz>܂M8Vb+wH뷞ߦ^]ňH+5^m+7PJ XkC@ A~BC~jD Ed/WĵI&fUM{McIR~ŜG>[4gaxb%Zkm噢?[D1Rmi`hUR|sw/U%n߉ HGeWZ+_Xz|kCxyٶo!XξLC+ YtC XlfbUپ2mhS1ކ+"bTD~ J)݉6]fDۆ!,OU?  8yQ EAKaЅJh$ [-˅v3ؕ0?lK}V@&68rnX@IG`7y j?.wGO:sҾ4e)cTјYM {h"@E>wp#6j43@(:zqUH.FGc,"<<_^xyp@8!TB'] pGILOO|>%2sY{!L91uRǷz}Y%flfBX:LUͦZ,75bݮM,'?|./-ʆ43 '0쵈u'A~ZLk%׮GD`w&A@kI"RM n.#||5#}__;(ycAx5Q6h7;F`УPw*hLքtgB0 >3Kl"'rM0:!6=`t?zOz-ӂD,Jk~ wWYj%9dWD}kqYrms< KI@kBB3VW;?ǞGv..@a [L?W| ?uc 1?Kv+эu’Hšh|¯?` ֤XWh6}c*i 3!34WhNDX$%]RA3?ďNt3}oȚ0NڿW\FgS5MgK"<)AH~jPN` @4\o2Yr.7pӗh--wuZF1ٵ6KU6?1ieR_D@XIs!199ewC:EA0r;Uq)mTrS!ڂsֆ/1{;b]Viӄh>X FZ6`> a`OuWMX{%S L*:A܂%z   VՀS~؄鲒o$WJJv&mc`Ɠ}ƏyI$Tو%)_fE{1D8R)HoT~ ѨDud~;L6}b8) b|uj#N!+͛tIGBr()!*Lfu{_ /@ |PZ"G곥HߏyŶ0;/NLH}1kuѧ돳I^ӻ&zb{(Dۢ V~2f,WXL>8مu8g@ƷZJSZz ^e??g9E?0n1UMF#LεXvsbrBZQ2}5$,kU(C+eʾ|OWݩTA\Q`jxܳ?vK&-L`%"CaֺtP1;Q??~N O oӶk 2 GpW;@p:.;-甲}v'mW:xDÖdf3-+ |A#|5d 4o`^8ċ阌@Y.b-Qy0BkبDIqWI9CSCpb7F05NXc:e5S?g`)ͿBhԻ%EI`! 3W9-E_ahGp^3J`KW ڴuko浍/^{+5;y-r|O _x~vn:\}}ʘnOĪUq-xXxMzX5icVNz\EՈ_f)fk6&k*@^xc+i$cPMbm";D(s!#ЄEp81:֞?Sp ] ?Lq9bf3fhKVE@s2Ivl'vv ≸1-;\y_ܱA(pGIY gXPh:?:Wg 1p6(E9guA5<wF]RJAN `5-̚=>{u[##*e y,5k6H|eyUvkr1+.7X3'>%9n/| gm}e&j "ﲾ-&QMڲXwe߇g^ft:,?t_ϳUv jŰFeֽ^gB+Լ7ӲZ絛] oTV. #QEMlw2^ NL:s)'@ڊT# Ao./s-{>`R]uj0K=T؇vpV@ oް8 &uQ X_u(!W~OHQZ$!%1^Xxx- \.&lϖ*GR!;UzrxҾǽޯFf1>]7#+1 WҼ=hrlO?y>_1i9.l|S)~^ez_ˮjdӌ:wK^T38m} k/}̭C<!pC'__鶺:;,371d,:^!t|q6&TFrF> ~uZĄ&w$q * j/ ce+Z-=VCUKk{WY-g \} vTP-*,K`D'K x` pUuh׼_ < {2$#g^˃iu[#K)a-lR(J#-e\.p1'-Ħk`3@C?&-.#kĕaVl-V>fG?k>eym!{ ,ݠT5% R5]VR—}o;}/mP|wA2`ٿ5{dF Z_ b`Pg aXª$'Fwp p)b@e|9t zw[xqof{ad{eO'4ϥs]c>7"J0c'>|=xy G/RbS")Bj>̈́QBET}aUc̮7K+v^5;PD7O9.LSC;CsS_Իܨ~a򱼍+wo=VX "!6aC/TEa>q3 ݶNs'Ѭ:EqgK֟4^Yaa2EApSO5nr𖾬jF6 N’zyl&vO"K.D"?wQ{jńi(ST푵j7pVw=YsA;# &Fal]S5;Ӝ"OKM@5ͦh[_hy7K E%h_UpmZ zzFvEoF}Uw7z.}<}!.3X,ZviG1,ZWRC$"#RX҇VAHFv' Pᙄ>0fJBE-C9hѻk#H^J <;Kǂ|;\cgǎ-|C `7ɳdPl^,bo'<u]Mq_7T% M"#*DuV1ZvS\`G7 <r x p+j:j60||"YYvg4d&䛪|Oi(O4PAM&˰{[o{aUm+X-/(ͻ"\TR=d:@h_ZwQr[p߫ef!`Y@;OAǨm5̃g ,͌mh YXS%E"rVD[Th::Jz:kM~l8#o/,hiU kڅY4Gm]l?ng'ּUlz4}Ψ@F^xBX?@_V $ÊU{_ʀi3rMG;癉^j{[Ga4b,3; ?IB ԗ4t  2 pG9(LK)5~gz)HKV/t@74ݫ HqCx&z]F@'ˡC=a'0kGW#LոɘtAqbĹ,Ai>sBppkmOEVКc7WmEzVy?r|p! >m,|+xܩ/<^ h6˪D'Zt:2atE=€ 4akja3g*S[[63U9 Oɣm< *BX !y.Q !۵u8\M@ݩozOe`S%`^0R<ΩDI^2*&6c"j ,y"Tp!$ 㽫A}}S|_^ʟ[eu-OwCJngDb-Fw%ƿS&kCG)y԰fo%vNIBbBO*GI}TL׮OY-s8@6ea7mlJF!p/A KKw_|77^3}ئgjVzL >L@K>3b i}cYmӠ^2ծi, 8q3׉u1+>IZh h/2 * .TmW^;Pf !tl1.ZR;E\&x@O_yPU$+fLA)P_,184늰L)`.rHG yWm{}< >BRmҗVOhGk ^*[]=c?C'9[O?+T,(".TR8W]hJj%C7uU9m?iD:?z",%"W_ٟgr=՜xwx?}WSN> cI!4P,r(XDS/;_:insL==D,|ZL}Ŗ~kWk6aI& Q?d7 :`M4Ub.uK tVSdH;믨6ڨ~ح(Y `YKqKP`$v--|TD&G ϑSѐ vW9=? vԉ$FpxPvT3kؐ@`H`AeU7Kٙxft }Wou垴˅pXo}ٟvu6liqm~[cU-"G8Ή*8ЮP|7otP] A0CR%2Q7x-Ku@'T~C$_%ڼ2}F>fDe= dq HD^'k<Ц<qǽJ GKDz5+.ocn=(c:e|Y|SGJIG{n@ 2:Ξ|b.x^*c-8 VCX`xA{Q7[]=-@vzvus 7*_x9U\W5{nozR'nrb˗.%%w6>\9!{1O!p3x>[/\9J:R 0>a=O;t07U8^=]5fwWtRz0b;vlf0yomպ5+p&T< VҦ{SUp]W!QN;*%jEɏs;3nT}\&߿I@"Ed}QbEi=_=b%$D%d&Sd.嫺XicR7]riKHWcvU\ iNnu/K`H~nWR/D9Baio4HkA` l  ~2g'`Z77#l2S<5¤Cz!b/@íg9{diN$T*֞,r-+ @hhHW6SܲRk<dz2RNe-1PύQ}e#+.K(Q*b(^hgt] XjkN#'Of Ӳps~sEqb87yc׮b7*ݗ^򵝜nQ= /7~w;=Øo&r s* Iys>}C vb-W$ye>g?V؃ز5IXS& I fhVQ>n'P[|:=% 'u(7l(28MB '.@Nќ寳jąj'C?&u\[FY~dvIGv^ٗdyƟG"-QXt7me V~/;G9M{]!!mU'6'm߁Yؕo췧TJa\0lrfkϝҗ`̿٥UiҨumԻl& u'VW-γug_? ^I a6%6 2t$D" B.v5.*,)tw` `+c;cEÇxۨU%(sW#㝒bR~h>#o3$%`_I l@I]<#RP5T{@gkCm_&{ij@6 :,u) \Nd0gJ 'n&3XTٹ~g3vfm4KȧX"MPP:sJ21\u&!1iܑ+92a?Wd~Ёr-!x=PFit&W E'?~j*cu 7Q%zPL;[Y?x,߫T$T%Kx"O{>x б$_{~s0Z=AsV'C[ q=a8e4rn(jw2K;v}?Q-3ȺVyN9)\pp}9 ^z׾[kҘxf'lIOWΜmOL>LYJ"{Mкuvsll`pCVbpYujmԂ}cls@uA0@4"}#KS]l;<(稚#Rn+4U5baҼuYy+fr)ȡޒO0N|i&~wjJ&uh<{YG tG^ NxS^N#cL {γr)Q, |hx$A@=LY4Ӷ6nmr{εg1khKlBu!فV~tH fEx+gC&2`J`Y@h&Bwo>woʺH0V?a_eERTQj'T\^x =|q2BV䤌B5yf(?x":NNDqEbN(W -i0`bW\l>ٶfu(E G7Q4p>g;x1- :Sz VTm`tل/tHφHL\k,H0m[Hӟ'3:|7K*ƩEUs_`lb;`]*P ̲EJT`ayƧY+A Kvv`]bL˶']Z,JW?յn:24wnEͦ]D8!x(QwH|>7[JvjxuMYsjS,Pn@?I[0FRWa{6  Jk̓~Vy@B~˫>A@㫶, ޺v?@jLsxشri<>g>w9Ej0u2XlO"+<rsi`p^}RBHUxScZeez։ρJ3w9y{! ҳO>$l}A-">5jvU>1z><%#~:=8ױNNz5x !\7P@Ao u붯 칓0o׮Y5$[eB%Vn'2 81emSܩZD]_g.nYAa?I a>e+hq(c_@> l5E *;L`v/xBSPOPx*G+0b1<PV6ǹ=.>օ|s.KN,}RҷI$\IO[uOL9-XmWwzp'ڠ%0a5"@RdIɪ&O9ڋLW/Mot7A@̭eؗ#rD3\!}KdC[,I`:-كU;\D_]űd&uB0CJ|ٿX FGR?<;^7eB R!gYHyOdP_uy׹cdg=w$SVot{u;+31#$@-M!Wm@vfoq뾬$Xa!s.XY2gF|ͭ& H:>=ibd9:B Vќծ٪YeAY `,FbXSU,g׷p}V$zia:?[]|D :qt/ž`TK)og1Ґ*pׄک!>|E;noo Mc;I>kUSFM%=k52hʼnj#dq"43$$Ѩ.A9Բy4G8yO1!x`.ͤKx<[TPr_hx~0]Vl;sTa*4V@”WV23;BH-ODen;^ETqP~Vbz?`>wr솀p"6jŚ-899=#e%Lo^}f?SDuճaSK@˂\rZ<k6?W;s[e˔\yL2}e|̤t€ @ݞ{rή4Na@&>KzՕ,."FDUYStM)[WBZ۽ 2HYp^#"SƷ8hMISD,,bp\D-M&LH"vvjCKN|1,m `I2C*y < Pj2 h@k*fXHu' NbtI;lmB>)u2-%D#pe P`쩹Î"pM0\:Q0 18+fQiTY~# TˡF=XzRODTFq/Yz^8'09QBղ c%KTqPU2ScGN3HԳ q:XUpXFo!W9ݍ7J;q%~ I=37x朵Veŗr5.-~|In>gɾIG,fݦ1wšًXUʎEp-&ʵk< l:>Zo1Zɑ=X\ -u,VO K@/z9'mNKJdg.guuU=%@Dl Vce {i!g0'b%Df|` "vPNMaeƸ&S*ȏ 0J>Y.O(<%3SwG="_[*q~ W79[+2~ ΄w˄C];pK;˜:z`I1f75G+终;y]9 #tA@J:}f4 ^4vA\Es^B).s\C5U^MىZˎK *Ob J4O̿rhm{>XuGpN.0hჰ9<_c1)@@ F/ Y2P 5[\fu<}k\z 1p v\¢pKouG^:NunYg[$F|1__j{9v@=_j7ΗzQGt ڸ@V"2 {”3Mgj=s%>.8 5/%fleuh6G?d+K6u[n_'}&S6#,/ /8 SPR]v'?S +(4X @&z92&A 4cICpBX5ɮ]FQ)a -vT.ahS 4B= &[HWh&EvWV?bZ:S1WaFa–e}.NCvq|ȮSR̵Kh&F(+vXR` $Ph]VG,+_AB?;0S]tw@1rE ߁|QOVBI'pe98K U-G ?Fa̐NtH@zwz9㔶xʰ̿Dl#T_Ͽ+9{| µ"_ӟ$˅(ٖg+0g)T`Ԙ_O>h~b>$!,"S,dvߣLON6pS>>fSO]1/lnSlbfp< (F9G{!pqW쇟AokKDV=լ:=4ebSbٙ6vR0|,9#Uh{st,3SĊϞ@k s4OKSe}%1 TN\oI:\W| rD PEB@&mbvЦ]{ h63aZě{YǖCLPb*D?`K;;lot?b/Ϛw'&(kbܴz A"e9P 1 XȝBQ2!g9NtWA@B<_pX2U@UH D0 hc2[Zf7Od͏؆Wmz .RC90)`D>yr茙W}Lt|sW_q"f16zN`9Tn]'r&؛%텏ޯŏܪD Pl!*?= j K\۽c \/ 6mbaWapTm1cKT5,5siWu<س{!!37pr bV{0k"fҕiL5,3''bl?|Lb6 (a ϔ=~Ak=6ueɷRs&^( SyTNwJ> {' ]t8"i/f AH]BS4祐^'9ʬS;@ FZ. *sRi#n 1DSOT؃~p R~k$JM CR" GZo >;?h:uм %R1UpBe8D}j"mW-`$);6o@! n2m8E` 7o&?[ڲ}ɚ`_R[MKV`x0U o0<_KPZMB1_w?`eEnI`@Ӿ ~ʀYQ'We5'8fG 6{b*;V *A6W 7./DS 6!pC_^͉80ŠyiκvpГ+7h^uB.u`{hkh#`+hiK0ebYN4l^O>sma͋=4=ևcUMEhWs ]@Ʌ 3FU_D[qa\rs{eFvh~W̚,NrlˮBz?0@ɔB;YO?!~d1EK/%qXUde"[Vl [̿@70.҂n ZWA[nd>؃"+pU*g bqFbGZ}@6Cy PԸ 7(QB{k5dJUX)_ABIIEY^SˎGMӘ c\P%\ e ܗP*k$. (c.Ưuif` %w͍6i+/ӯ}7g.||`>S4["0ŨfyldMo-:*T>y1㑟Δ|n&<s9Py}&} CMlm"n[D(&y Tص T m%|+I]v绳7$7`!SD쬐v۵7i^b517fg{l fseLظ 29.ϯ&Z-!]>0`$XAU |߅joWOڹѼ}W7ۓjqºI5'q։^`Y@y˾AU`&QuSE%9 R2R h9<:v' 7vy/? >kcSiL_Y򴀏?c_eӎh%:h5EF\+jROLjQ_ӿ fH}N8Mn&ba#gܣW>c78 G %tv^H1UR+5YѣvܮEE-du3$hg#bi]u26EF̓>e<}5yv[ -3 Y9kIXsfs뻳;;O/\P y6j { P8rԒ!@obO][ӶF)z Ԩ`1 zҀicwC@;ALZWmzF8C/V&Lee[ԟ?e_ɫ*&h!+u\ M'pU]䄮ʾ7Non|Ɇ,(a~Z:0egbvup$FD$Aچ30+8#!)Ba#{(`=T:ks5w$|_j,VҚ"XJ[:l[ţ'_f>R gb6i.S|K̒C_k7_0Q]-#gR9c&^{se(csLudJsю>CZ~GVZ.c |{?\e2O+F&n( 7 ?5ڰy,TvDǎh* `)?$,W²Ngˤ2j/A(^ؿ#A w a#WY=9R܋38j*nT*3O5b&\CZ?̓G:Y\r,9 FT12 Xh gAcBk80Y] }X7)cs. =8W?^k `kz5h? M=Lٹ[N s7Xd.IOmcخ1A'g]دfs̨aiةW_k/ƕEp/[{Gs[6>sQCĒ@,êcbh,WvkJ\mFhC6s"&^8pB̧ePAtiiֵWX(>`?W`ŵSmbcf\n_ӁὦWީ*/ЯUx{^w5pHLv݁(KGQZFPa70_93ڄSKl$V pe7$#~,^8?& [-i &bOxeGzad ˔/.#<-QJq>;~H^Ƶ% FBX Kdjkk K+um[h d`8 w{rx8|R?sZ 0@2~hƨ*qEs k ]\>M:kq V x,#<Z#Gm=شW:4˹cҠNoN=L\ˎww>h NZL8XclB9pf]QW۲:DKԴ>5!1f;L ~uI0HT0u{!:b0h>6Ot 㠖T+ik1ײ"\CH$1 k cƾZox }/cx8Sb* p)"eS.*pIÁ;,X f$`+x 3d }ç$z=ma RDv&7 JΘen?fNs9޾oL:I^?/Ube4C [W>9Jbɢ&,iL`9FZ4NKfQz=;Zo?0DfB  p_kNYZ&340bJ  ja޿eIP&A ;X((cXP"L!e3a PsB-u6IdDP7WktMަ ⚘1102aF 0<_FD &'M;HtJ[g~&m(\QjvbRxtXN  IIF8S3_by a0g:#,&n .ȐKo5Aڏ6 6б MnڶT*͉5@+pi`͍Eb>}#a9=.2:F2L>ƿ'8E¿,÷YԻԭ#aԴ qʖ.^X!ZK?kY6>@@.1!@ ૿|╟yϾ6F/ JMݙ3r׼.S1\BL.jEt4 o z^oYGok}k͖6_ ]1>(_Z:i=Яnd$H}-{/o\#^##Z:]!.5[.Ix໱ ^m`ɱTj_UQZ pj4B-7]5NB@ x.m 1jwU{GKuWQI a&]F~wIoY~;?~ <_ cDL|pPF44ܓ呬 I)WL`#f0*bVMbʖ` `GQ1q<Qmvѷ9 ^cL2=CD5&c+#< jZwo&A ?ǜdxv6ɷSä'aa/\>J~fj?C`@ 앖ο3[_;~8-,aavU1*P>Ocb4IL?=nσ^,B 5b8!Yԓj; ڲKką!X! &4ןKItroǂŒ|$=x2˾)c KF>DZb-:A3! -U AWWZ%ANQG;m}qWڮ NJAwCS{#WLh7m!ٴ#U+D؁2?G %F:XTVj.;jLb_څ\&+@WB}&$"SNǏ?C1ᯤBܖaG02_'q>‡ cmma۫Drd\ymUJO3# 2Wrs!FjlAZ0=-?7ZLGh Q]G ?aԒDE+)p"D;o_n#r%bbSvfUg& а=yK+r~D- m YVc;p@x,Z>fȟ@bZO+p,;AqP5ha96R#Y۝867w/<ؓv7ba3El(Vnwh'7v]c5SbX=`db?>p6#a$ LaҔ wgXdM94w BH,~HWΜ2;s3׃/?!pruOrԏ\zݡ<(KMZ G&CMvfL"zGDhOI]4I]C;k6HL%;y)붺t^t }濌+DS{kb@T/;A Ӗ1zV &"jrf^朘#qVԗbCJHU_lITQ7ݍ*_8C/}2U?F?zWyt։-}v5Yz!.BͶiNc^o̸% <\:w48nzK\ƟVW MVnUy5A.Ó??n܌WV_Wguwz_yZ{]5`%wY:]?9r#r;&C g~3gׯ- Gm{ue`4΄{Jٕj*#ԷijM"Bz8s>+b,!kVc yJ0B(eg@‚-T'ma:0W Y(Px}Fӗkk̴n0E1mbOc{ԓxk;p dp{?vw|يaQ㗀^XWPbl*Aϝ;6]+ܫ璟!p#A%C MAS|杈T`.MNp$ԀD4J{#l?vbiDAzSzhF ! !bd hk"[֘oRsdY`09+@[F@2*$e tK!ץ:]O5\$3`/segO/.4bb 6 YLʹvμs7{*觲/?Z] 40D{ e7R6M<;\2V,YM+?qhL_p0>2#$( B9R+ǭxA .w2 Kq$g]_|HD1&nF!H |oiLt+M[FhxU C U+y?}6![;Q5 elo9t"e^"aXQ{%o'gYxB˶PO>:B>hdz8Tlg{L̖`l)VӃyӎPtlVIqF5!N]Mψ<Jn3*2M9\Gۋ񯢇c)E/orΒ_-`p }o<.<6~EVcq4e,pG"BmH%(!S{䕴:<)om?iK "D8ְ/am=# < .E-ސw K%{#!:[C!B V^yL =_6 s4F ax։?](y!psȨۛ{+:@zyjuw_~ "$;_-9$O,2LEEpBW79<@ &7"fIB X>eZն,չjOCgqyn VQ,{܂2̆ɟbWEӭ@@6#EziY. Ͻ161iOrox^':P^l N@@7ZC{p SZE{_of0_ǿiJZ ӶbkoĜT;HO+W@7CVS_q4&`gqTc}D36B0B>d oܛ@m԰I[QFLq ޛ떝y뛿}IRKh!6+.lb'RbP#d9&)tПT~S23±ddSNRqBNc%t++woVWj#@{nHz8u>>`,.7'w=0@P }NHp>p Ǫ$':29f#Qܝ+&i2ˋy^ #D5tM_QG2yls\'!Rgt%H^8fI0Yh ef* eyn\40a@`2nseŚxS^)qb0|߷7҅OW?|'M*"T ?E]uw~]xnDcdBsR~yI )SxDhy.~D1t (rϾt]HT)'#@[Ȱhky#59洽{#ݘ5A;8gȔRqqܝC ZXHc#}H/ݞǝt1)Ü$I&ӌ -2 ê's%,˻P fIF]OS9hJiҕedbHQ__Os]Fڿ1>Gӡ__KdZOd!<.gJW@"PH/,5Y }FeHra4 (:#LK{I S.|P۔kgaE l7!Z0ahZ0cnAmqj=T ,,VjDwfm@ Ǣ;4#}W-rcK'J8mHiq~&9 1iͱbZό e.:\rk1r>(m߸>|ol.U wrN*G  _˽?:nDz׮Aϧ͍u~XOFN D^3Mqnm$#e2GXQZPXPԯMo_jݟ ;7̖h4[0V=z@Fűaʽ1#Ks|K^gIʘá/A*d.f R~-)idMJr'G8+{|xF}?rL_R[[ R AY~;"^qwB띜n}?0Atm#OGיִLnӤس/|J&Ґ*w9zqB]`ȱro?bFbXǸyJqr}4۶YC54}o|k )olְ[iܪ5oL7חs4Cp+"9wz>[64S?sgAsů>D2Z0ıĊ;es(E]lT2h]<Oj Mi_+m^.!qJSsw]i@* @ OWoAk_OO +H&羐%v9T#ԃSHoC'?&@PQ 3Il#WQoDeޥGg1 (# 56Z׸yh@T{`eIHDH%oLwoumde)Ą_^Na,ǻ LB?Kq:0\Ok7҉tbz9`l 5QKnN4@Ef[ق8Fi3}-sm\Fi±_d,jΧӭٟ &N;+ UrdfYqGV$rEYq 41d;.GlH{IC_o |dXnc;=P#lk-XŶL6ywӥO1RwZk@@{*@ o_3\Y^={%'@ #&/m\`,%Q̒D£dd 4Oҵa#wI>Wd O&IrZlFO{ X'(kdb:ǿaqFHqTߙVs鱅k}K`b:3 /ᕰ]8q2q^A4+qf N BX{UvaP-J>ִTf 4a4u2 cs<> }mj`>ow?Z: 6nqlDtӱ0f}K]%`1$]iGEs{鏴r+dE4VS /4ר?䓟IUU@ wpW`2U p_|z8-۝4a%V`!1*шAE@"ɁpSy5NОVںz==?]3Oz!} 9C[A_U PAm~WQmr0;*) HC2iH JgΜMD>X-VkHDFWCNMv@1$@Fm pOC$aN_q?]t_o?݂ȿN+YsC85Y,丱e'S"/7 pZ5cL!`p},mo^zC >Chs!GccPe|C(~`1Jb h񏏻{tԂP>EoGB^^uj5\ZAVDh! ЃVR_Hbo mAORgb#)tsjj"L,"PRZHН@J[A타wlmC Us'精בdg>tzkNQZz*-nV`#8fz@;s QwP3߁4:*9yl 6f3GhC. "$Z7HMBޥZX\׿hBlnsKtjc[2b _'Q= 1I& ||{0mϣ-߲Fz48o1u=e'/PW|F! Œ@IDATB1 "mhpp Ml/Q qxw7}azO$M Irf\ o*VZlUX ^k?w>VǠ^Ase9C)m(rxFP :g4@^0pAՈ3*R2<1PuXsD XtcsNbאa%[dQn4 p@u!&uN74/+0- p4 Ibڜ,-10 vxml/A8 2^oU7 }㵭Lrl<G1Ke;}GJ8F8=R𨳦cᐖpUymoo=?9~{ ?8F7wEݽtͫW\ T[VV Z?_G?|sl'pOcMN Ba8$P\#3@Z_#Q&)𑸙f0P&C ! ۔Z9:|*]ښ[pcdӮQWy*KP֯ sXb_uz \DĂbj.J1qo7鴖 ;X!1 jJ%Թ4b@ It2AM%ʒeZ ,2}Y.zE|u;ue8m,@/LF1O+31hhh9na0VMs#G0zN]Og.|LLJ =`jqR]v>9^zAASa^5.'$>>]|4迕v_FK  W@ ~?]{;Ý+nF"aP1 WW$w 02rSb%aq/b[ ОN;{,v/=`-\>LP$O*cp^+ˬ\Z C#ah=aC[f ey$-0985lu`^b֧F u6ҿ&z##>jp54 X0rل590j¦!ɴC@#2SJXtARoǵtN.zbTNnr!x3郧 ^/Q!eiJ8rA(A * bJZ0 0dqFZ?HbY6\K0a6G\;' .s]ykI[n `Ȍ#ΈNvj넝{& v =Kȥ =}tu7Y1@t3$Md8d=}㏧;va»vnb ½ts c?}?L?@؜o"m_.?lڹq3N6~W꧂@ 3 *&zʥk{-~sX/ny7(2J$( )ёr.mn5}mR4m%Z j*WA^8 ZO5jpUK[fA1L>47¾ T3HFk>9,5g~#gJ V/H\ ׬? L wdf!dQɎC2ӱf/K7֭Q+КJ nQk8Z{;},7RUK;Ox C u&g˱bk s|C1 ISuY¾Fyn~Lv3QXWװ~? w姾֯\e:aw%`׏P=U VA]Uʭ PA _Տ67{aq1G dL$AHDT?󋺘ƋP6&U? + X,BʉioO<{faF=&*1z,WT_$)?_GS9|*(VT5[ 7kģTo@;|"9ؗ4VXьis_Gf]ŢTx@T¯:j,o>.} ./aeF{)",Ejכ,˲ cy+ʌ5X|{g^q<8Psˌj]HY9:f__D{%r:8 ! (n{n-OJ}TʁcZt0Qz>^7.OІ{F.iOs Wɲ\' YJ]sfNn-?Nd ݒ3%>6'( Gc*>S4?|:re24P(#i?e+pk]tF_]ӍV3V o5|T@  /տ[WWO1A5gHba!,QY Di4>vwXv{yN}OBM$M)K!H+( j$w]&2;!*&ѕ l*3O~3!^)ʵ}4lG=e"nBcۢ9iwmb7]h3ոwnhiS'xS;0&Jmy?yGqgty_ AN /YPUg{n?ޔqݸy|{>m#5߃èt[4:(4qx`!?1(͆L7`42 T[ɿL=3X# r=1 pG,f7i&Rg0֏_W_[[G!T=U VB@r*Tx@?9yx6@~<zsآm jahR  //"؎4 !16s!3ecq\LdDNg{Witi[V-,. $d}d[JzXkv~f{UFSKvUC(apF5&2PPgbJ4||qאG^!hl_̥SEx'uFv)nF@ dF; KSTw~+M3 $nk>G&v ? wBzF$X%6&YA@2>6gq"P+Q&v@Exsn w| u]Xa}s֞2ezeb.W`߬2%LE 9܄qt@N6W|:\f>t҈њ}͂W)Oʭ\<;̿!\8^1`/7Xeܡ| q (1մ#ׂ@0'iei.l\N=x<}sP E=V*6cJ ̏w.ݹN#Hw4>\)Ly-1_3EJ;K7v9Ѳ .T U=*n s '@ha4ɱb 9 (%8LUHd,`Gi "EEXU!C,w^NJy-&D ZU M* t5(Қ@A2hø4P m]ٽeDu L"U!.5ǬcS矒cYim1kyNgb..bpc1-.p+Ϭi/Ri02ʨQVZ&cS { XZ5n`M6SRrTwкiO-оϴ$A_nUP!-/Da'a dž*#hWLnTlaLL3Jl͢HiB"[ Lm,/?`,ľ78Q'3IQrDy/_njt "`w)Uv#UӝN~JW*_ *)'WOϿ…^:~T'ln1B F`eu jE#BJ efKĬ 3Dӎ'@UNw~;{=;S Q#NJ+3Oc.q-Ԧ'V64Rg$%c @&6$DYnQ@u p\ -IW&L1 `AS¯?6{龥v:8x+u&h Pc@0L3,,!q=F׊u@\P_٠g>TJ %#`B?t{a@Xp'}3KV-7-&_>hly>Tw jy,4vӱVongÑ1@ X^Yl6 _2,󃽻k?:LA_Ӱ SX >ƲVZNICY=V'}<ykE {hͱb/ %_Mze싧|ϼ6NS}+_y*];9 4\T @b#`* PA }lǟ΋.[񌥕u`0J8vwPe&1h rK)I`|#lRXC6[*H2A ԗW H,b#%뜁 楬" ׬I җ% e4Sd(#a[Jh*(VBUL3Q5[l6>q_1Tef:[vR qa Sw-&`jLw!1u !5Tͽ 3ߵ@85i-=]ZrTtjq]ȜTcY4xdI'kAPSDq+-W%² 5%Epf2n$J&erUo߽d5n lLh8m0f/nMPnox2ֵ_p@q~}0Tܫ@~v8Pt<>HbZovwS;X OC1$094?S;$1%۱='zNr 9ZpbNia3[7ϧ tZ1kQW AbuJ PA BS?cp4%6VRͽtr[}83"fsqq ipAH^[݄蘬ޙԘN[&Z].SēLJ{wmu0eLC-XSTC$  bqLOك<G#pݴƠ^XehH۫@!YaiSFB PRs-@ ѭ3C졆]rFL c=@$u`0:N*`ASNZ{>m6!L٢sLز+֨%ܝ+K2!\zK>c"<2o)tv?% rw |E*,LQ9=.`A:D%n9=۞E{ЈWMm#2](e~4PHNcie0&5bF244ߺ`8=ȉ9S60n! Ems8MӸ#wHClHW>4x鋳ͦP"Eʐq:S_K/>4r8Md7>!I+WA;@џ/~?/]y}gN'xQ#ы9tI<r+Z"%B)u08 {N (;X7`993?\EGCϱ-`SY{l7q|֩ԯq{ ّ>%JŔK "P*҅4X籅Ɨ{@H{EҙPγP%Ɯw G~SQN)Vh wُc 7z0Ѫ B7$ ep' e@aIu^?iƄ#!1.LGO_QRty"`|b>ub 53((@FzY=jBgRYK##\E˾9wJD2a.)h8W]tn~|)a4m.c.lv&7565T8`'^R:5z1p"sq0> VZ9ʀW!io2•LtG'.+10g-!t9e c k|͘3 k/+ a>ݦi>E Z)^zZتÞiK'ybw۶[#߲@Fٲ|*}o UV5N͊r^s\cÇb8Gxܸ.|kՊ2 >(ê\ o|?~9[]Y~ z7x68{l (tG <u ^њ%O#m\z]DS6N=mV0ӍWuG L0U1UCVƔU'fWYyhy9Q pGu>) A8tw;ldKDc||*) Q`0nrcTI;"Le28>+эJ49 Hn֐lXֻlOq's:G!JrR:c yf5LM|rF@0OȐ ]S$=2]qX\nNɠfPD"hT '泩b/JC= 1Bq ]Ce0T|xh]: ƴu" iF6.Ql ḱZ4ȧHEs|7a5*!2΀o3ta@7ajicB:s_7=G?z:=G2]~v` ,{LƩ-sș w's1u<9\EYn~llmFh "̠E_t1ޞ/2_u PAU PA]/pxb?֎%;3xH"7ft&w4#  dDI%B2xViT =#՗E(j<32w'kzNaxڃه_O80 Lj'_~O^b* (xoCUU*Txcү~iTR&DRQLgg.RgT 3(J189NRCi<˕KJ ialSKv$t<جb/q~ӣ P Ό0*h;D##)q:>g4ڤUx`%$1ם3m%&=kt+# {%/hjͨC6=vΰ c5 Pdr b~5.O (-׆f6 pB8wcJ'u44NVTqNSk .Ń0ǃM* @ 8Tm&|/OIKKK=-A.ΝNhї%j6)lh)Pf(cS)S-&iZwAZmZ7sua鲽'k6-őUK9x-X-D>d@^ (pgCC?n2CƌfHŸqh!okv06#sӃ69>LZgp<ǼR#r>F˜6785;ֹ|`lE4?j ?/|nU Bwp*T p@_O|iss?XXYb>!ݛ/ S"_2">ɋCx$.&Y[_{{!2ѕ,%bG[x-XlN- DFۅa 9. -ȓ?:֢S:YJ@# z!W`k+$̰,@V=y 5^"!] X/Fwfj&Re(־A2% }^,9צs6Xf\@M$419x2#˹T_^GaXrtZA8"vL[i[m ji!Wy~ cb1l2GG0e_!HA BLfˎ ?G ">B#dFĶzgn9>nv<1ȸt,ӈqr 4h % Z^6ٝ>m`^?_2'?%΋p{wg#OGG> PA PA=gg֕s􉴸_`` x D [-3YQјͰ"F$7GTe2c;Ò8XxȖD~'/UMǺC.?v@ c=1 `p^FУhx-mo;@&dɥdeY,}'NuIJHUW2fq+, '-(KP$4b)S=|D0Xg;cQQkX{ޱ6&Gua\WzZHSi/NʔmՎg+t l#Z'i$Gh;cGچ c]'̃Bt^0.yn HZABZo MHゲ~yҟZ'a 6˃`k7:bo6_NG/='gZOZ @I#0(>~ClJ|Y_S}(LE06ٻPc[ޥ?nշϿbD*#ثJ+Txx?{Q}q*- iPK6rI_`e'H@rPZ*g"4D=j)h ^`-j"E= tqdRZLǖ3G^O׶vͭ}{p"Mi; T%=WJr)QmYY C LH1Lw$~H= U 8"-,lsuEaqbM5 &H^J%1ZT8PS SEe;95LK(oQgin0Q㔎z:I<صv ra vOlCPram_=W@89-rGC? si|I`&\CҾV>j}51AacݴI+LKM?;5ؔHXb9ӖpamWJqUDIA7=5)%wdT>)_ηe9Z#`;^;;tCcl:bst\z3\iuhQ젯AMM>Jc+kCO тsߥLZ9m`8sΉ8S_K㭭˟Ͻ*o5+Tx |/?j:~$<@]dK+`S{ MB:Xa?+^U$%r  c#NXoq)-7Ƭto~t>Vk[N;{<}N~!6QD3W!0*] 쳿,nrwK HHIPXe4u,E(}RIH*P'Sy8 H6t"MnILp$aM޶K :}:`M}BxM ~2€§h4#IBf=TE_Si烽@W䩇4s@'5T5 N;+ʸK7!kKD>jӥC-15O.W3mapL}}+>^QcF㚯b^DCm页!Q=6NxLl|M fEty qnej"{R[?1sx{*p[S`nQ:{fyh9= QVRwn8m_e7444 tϢׂ L罈ēs1D+/^HuVwU*Tx!P1vWV fBoo=$"BKKaL/#>Gk!A=ȐHQ22öSR帿ɠgJ$ $˴qGy=a&N?dԹls陋kk\o{.48 c>;Ҷ4 pQD;DON CуsQJxEe͒"AD M=+hK^++($r_RܫE_wr٬Uw9GWW=}:©'hb ʘW^Zԃ6DCQd[yEͥ%LK#LA8,<=@fmsF9/@1c֟2o8&WvWy E"*[G@u< 1D9Fobn7ƾ˭|!&)K|eyzʁp.ôq+uȵtnZzty>-^Hs+;H! \0Gqk oˆ/[Fudⱶ 5OҟL)]JX'q-p CjWid^Y 㣪w&2v҉V?7Nu9iabZb#jsuW{CkPx\#JeKF1imi C'N,;Ξ=N$;>3'*,6Gsmr/e)wnuq;r6xG¢jOsY-7 v9~۴kW_g>UO*[!P&ޭͫUA@mw;{_:aΈlj?#G9L 0)2s=QE"Mn18uJkC2E8wˆPH׮C-5t]7o>襵T!*[!2TbeZ]YW(64ZisX? 'h`x|̑2AE,eNQFkIQ7`T >aDN^iaH}[YxmuHSD=\ĉiF/₸B9מ۔G[dz˫lR3I|(  `&u@A̴{ib4>F@IDATS;H.fZFjƲ~cƺH]-au!57i8oM՗ÿJ>8ed1@ְ`판G>B}"$xHCS>2t,pΟ6SfĄ}q> PJ͵ #`E_{;lJsL}szL Ĉ ́a' &0/7's鹍3Ii5Bemᑉ7Di'k9D:/ʻ|]bHb5zP^[.3B#f%{[uXfwPwOBT.DxJǎ꩒-Cd ϶M_^Gn="gKhOg>fqEln/ܧ>sG *T' r*Tƀԧg~?3?ɀ͔HqEBAEDDxHU Pf".O2&&g"NE2AxE$2k>ad;$iR9>pr5 \Ā Zi{{vCCTQM6Flk↤^HD/⸀N]F%e:A`Gj*33#.v^:2/rPlK2R/1"YW-gOPHFae܋J,Z07VƶXbE݁8˲'gӏ=ex6}&|UN(*UkQq71ug.p?PE 72s{4+sW}i%D齄G۲{~j0c ̻9,Xw2/;.?AZj^|Kx'>*-aF 5 iB*իM ]1vC<t\J%{ɨl, c&IS:TZ0ju2ؠG Anն94!z tcA-7xi~N!'0-͓Z5c8t Ai$oOmaKm)|+9))is;йbk9 '" aBۀwa JnՂ_Z>_襅F(>돦aӀ3:u7ݰ+uL3BzO[?` MR!M#WM&UT*K!P^ޥUA@__O:apOuqpIp)E(N%C+ k#U,o73:%87 'xcÒ:lQg$} YF( :=0fe#yձ"`0vvihx ʉ;Ę[=Ȝ[@%b _t^IȢR0}H<10^CZ, ^dx`dEc+t rc KٲJ7-FqxNGFs ۾]mmkt!=_qe: n$ܳ;QɊ0 !Ab&sQܘ-"~] ,0>&4F'C$a!Z neQ~Z#/ܚYgnwaYDZkyr 缥zr۪KԡEӂb=aNux8tbK-$-7M;TDٛL.ъ$!>]•iw¿7ڄB 6`Wp?rC#qAvQalA8C(KQw-4KvC ξn~};GF[w38--9Vw\} ! ӵ>>N.cU݋P|]Lf0nNzve|} a2OA~=sg3x0y-JuaJm pIFJƈdٮAh`Ll 61mrݞ|α/k CY*.sjyvtOki??kB+Tx7C|X@ w/~._X\Ju3d'1X\/71q RbF)#X H'rM*dv>a1x6y` u701Ձ=z_Ql:DDU xhψ&{taڃS:dcʕq0`OZ0 (KƂ-kLIυT>8\3!';͢L) 2T5yl}]AIhj^Tlyb |I(.7zt\2쌴hx:¢ɶ(\!3yQwee( d4D.ȴE])aYvYYF^M{:-::hzï4EZv~9m1 fD%$sEgV>sw~17s!IxYEr}R3>-WBvNE &pbE>KtF :[{mT ^o%xԱt9Ǫ3E{{٦!U6qI(LdpKO0H,2nRc 6; 5PԞ1vji*Tڞ2Fh6l?v E;  -iX7w 1m8[!rį}U,8Z*A0cXC--עШʰOu2|?6Qo}&~9ЀF8]n܎-+fNkKr5\Q<<(f}Oӓ5\Aҭ.Whg^xiUH  Wު*T#|K2 ["i"Ё61DAv\#e2\nq-Dx[Bi#ZĕV|񭴾 'z킕uKOī j ~Q U@2 (6'tc xSF>aĥDgJ5 a `cԚ Kd$QM oAsB;+qy8e7yPvJ2sU)#U\A/ʉ0:F1NNyeF: Xip;b<4zL^?$OPB9BxD风\4i x׹g#Ne8 j~&>o nusjxgP#(H G2sVH]*__b=2]nN g YQFۢ Y_#D#4N7q/=RZ92&2Ц3GU$L.]U<}sa Dh,x~`Q/~aYuO\OUdz78z0I8;Jw[,|C3TՀCs=<;FuVcuQ}afobu;?q>($R8v FMEۋAQ 0ʑ LUH F."Q*D\Eq ij7-(njH띙{>wNbgʍ_o.9.gy7rGnx`x聺-6  M_[O!}MP51~7iӅyb8;f Pd8b(:{6p8 vL84j.AFmh+5*9s9t1A9>i`}8 Lbw |'6L \$'L`#xjL G_3` ew뷩LoPn.\ZeY=|$2%S\x2W"Ӝi,QN~Y}~xKߣ/iZyѴ-7e@Q9)(lEAGEh[sL뀊(k)7|>PZe492$F>jvUgc-lKc4כ" %bWjvɾ+on#a Se0+yyWgթ(HiQVjV/e~#䛧iG< NuP6?  0pdr‰ RNjr18" |{p;}+}V i7i{&dWe^(`Ep?r`\LO&ױ]X]8D_~^ÎY< 1WbPvykDӅV_y,З~}%D_jWl'}6#Lp􏧳ߜ'*Gvx`x]n_/Їchu:}@A߻sN{tnhx'ӎ '73"s^k,lnPrCҹpA%,9$B(XSOk1pa]m&Z>OFX%4c pQ|z2[:5juKs>5 bҁсHQYh )i֭ ,ɣ ڰ!G9J4+Zvdu+0TMo|IJ(Oc{(O:غ|`iIC^[G~ȱ䕫u S{8 O=؏CdҎ??06dͽ,x??v(^JA&sDKQ oUZz 4혿BB䑉\{Fu!G{mC<*p*7V~zD)߭=_r ^Rl}Krȋ.wKop/= E_Migo7rj wu ޮG/˫ݽMO>9yBqwu:0ɁA#=N@piFvԵ0kSNcw>2 {F˕FϤ,TѦTHF`5rm/^ qUUy-An+snӧ-kMqb|9Gh[*iBQ7е<]o\ACL{(|b)A },P B  Z3I'96F~ᵌP .B#J=hSW %J|[:D0hmm;$4>oRm. b{9[txIM?XDv# Oi^CM형3nPgp_φt;lշ< A@hҤ!Blu9t P၈rM)à}\/MLNYUKމ6,k"6i~i>_/|}C{k ZkPa'˧Z#'wm UJyl0 7p@UL:Pw(5P KC T#r9.y(:!8#J"u:lvYW`PYe9wfFޑ:_?p+@7@<(e wm(IBߟp!D鵴Sx e_6f"_4=xn)@i,H6mN ZTei`´GgY?.70OMkY"t"bT63M߬#z7c&]:`vDxl l-/ȵ_d @`+Β^crK(^ ~IQ-PӮyDXr4z59aipR*Fm )U4-iDnQ]3ymڻ&CSr}WtG)]%_:iw T=B'>l CrvPZ]`I UN;5OK~&S}3k%7PLzĝR ~E<^~e]Rc!k?Ó6,eIth;.&6$ oe[Kvk4ZRo2૎!;N.YQ#ŎxG '-7,߽oGix`x1noawOK__ϯ1[:ak{\?! 7;wL=3H#30v` N;@3PN&04   a%@rJo>3vS| VE&o}4I'ї;xד~XP nL`a,K kzp$+F j$&3uYQY9тX$`Kv:QAO+d$ Oo@ʱEDips= 0qGZ|2CVw~))7!Qh~UX ̉L~?"\5:P6P9e[B po?Odj! \1,,0go]!S yRo?/kF >m9VUu}kVG+ܗ˫uI?mZ{:T1I()ʐIM {Y@>]?nM|)(ޡ]Hn+ d VlꨉC?g怛cM)fMtԼux쫫)ZpTnmIkxp Y܂&r(2C[>&Dg&M;{j{".XV~N~F4wšQXivܹfxbܿ7?ntqK͏'|.u;q   <~s_'76 ➘?O2" ?f5:`2[ 1^Is E (‡ a& AyBe-5Ћ-%e>i {u`\= K*fKᕥ1\/<2U/0SQ"VXd +~42*W]ʥ|HȫB~ۛѡGv˭L}S_zl[y8AbP(.pk,*Ʃ0F[8P ++L3y glJ=U-E!ˏ; < ,yR8~J'zBfRib^;NA҆~ ;|2}/$k3-@'jw|՚LKV_w YWYׄZ9q-mxeMNS/G6ܬF#[IP<}؃JfћWF@d]~ܗSEڮڡ_u,%"7웢u3[NR^2W&$/_'ؕ]$UrthADrI]e=IR:~  ]vȃ0u9g kmcp=cROPwL7yakׯc}z+{'<0&'qTbx`x;O}'ݹwӝOq&X@|-[Fno>vè W9"!\79 0И3}v1g LNXw{maII^Ah!I;!Ժf~|M~E#~R"ɕ #WYCiT>er +IXJ6_QY{MDSyo"{VDrP|Wm# h-ަ;|wA~eLp2ss.5)Tء=IOEJ'pf]'<5nL>ၣl*pxM˃8n'"j襁:yaRF*>i& \7惘zU]U}~+ɧ\L OCjִ>Nx]M.<':I cC :*ԳHԳ8o~0QA5 E[?Ժ ɡ>I<@3sj`$&%[Spi:C~Ƀj?ߋ٭~VwgHc6  |<ϾΨ|y:rƾvYyd F-:p9s&0C eJ}> S #s>.S=»rۼr,,3*@GJ#;aFbGLp8a2vumxz>Q8䉋G;})Na8D/>Chc= wWFQZ~ऍQS,UM>+s_ϝ0R܁9[|gjK'kak>\ jD.ѷ,/=! WؾA5 d+|83C.5 ת'mc0!>.U2*"uzT ƌs8u|^ nNg3aBN-SWiw8jbw` 1KWUϞ!V(*{o^N蕥rr/Pȹ5i}/[:3ȃN 0p 24ͱ=yg18EMdsv:' ݠKw|k>+g"[3@ y k*X9.~~΀)gՁ~M8_aqd=*Czva6UǍǔ |<ȏY.mXfRxRWu4\qQ6)vά ؇F.29サN)QR0.0 ]Dv+?X2"+XtLjE2&q,ɧc`>yYҁ"٩Oj+FbM.gTtO8lF-^ փt1UDzG%O=Or)ԒQe;:;ܔ`2 #㪋 isB/' LMWLK~TW P W2(6Hd\r '\aoj=hT [&Tà@߈{pnc'.xhkn*~p썓L8 L(A@Ɖ1= 3zxHv&&jAd^E\ "pdMV-5@SH4 @`=5Ug`}an[) Pjg)c9`/D}6-'h[>=Շ 0؏Oo镎i$SykI'McvhFWkd >[3z_cfn-JכVu8-m!.үn9?W%^;/.)6KC;AX%G^SёpIg K~[ݾP_; gLl '?51絝qX2pl3sϗγSx>]_N?e;z:?>gx`x=Z=1ֿ~;?]e`F |W] b0uA2hF @=yX7&N~Yj]196%,}hZr)q"ftPoˤ>)70LHq . )X[= S+y;y# eQO{ IJ> % \\ o?U=0)eO S=yr%7Ҕ j[sB|l ;eJ1[(Bŋwi WRnԫ}"^q/ SkB|ˊN&bRx;r52[LUwAU^ܦصq%/NĕPd^j=UϒFu=%WY:`ZʚcQ42|tv %YآHjQ^2I$f[(.f`)ɀ'U錚=M=Ș6~l jc|`qƒ}:~Ɠ͗oL4{ދ<;{c⣾x?_7tiux0_!U9 3=w8P?hx3ȆWI A#BsR1#Ĝ آt# Nqy@@K|֠7Yu>,uDa<kL4% U%2*5(s_g\8xir>&ixf9O#pc)##i4sMv@9h_I.[ҩB|&d+56o= RGn;m/β"Qa0adEn!4&(uՏ,~⤓/8~ɯP5iij%eY4rzw|IGߜõίFēV^G>NvT/i)_^{DКv]y2l˓F6K)mKFPb^MKn`,.T!7&|󆀜ny}YG4om4]華}0/Sã8S.;ʫ:vg_?h G2<0<@g><<0<0ڲU i@j9y[Re!1 3]GpUa&zL~qf:rak{وW psY)ӕ'c[x_Ÿ'7DCKOgTLHx%|\FgKIOo9LNl҄vo#ip{w&˅CKF W4uҞz=qiJ[yŢo7t—^:̚/yzeRC‰ ˖2AL劃]njk!l2]qc?VجկcuoOwo}sgFfx`x=큱=o?ܯA__yg7p2Kg>A2s01)Y8kRLJLd.OA93bIX((!;+ѪpI(7+AS`a#}K=dm`H4Pbyx -s&^{U% fء-N}_7gΪN5ڞ2.NZ0AmeI9 oE},]_?E?"#,M  O>0R*MhfI?&fDvx 52{2WheiRrl+LhȣYԦ_/{k7TRyot*O _/nu5UM۵R3m-3NT ,]K,xȖ-6RVL- m6na^ګj3Ҷb%uQՆM&?c2sog=tㅯM7_|izƛgΟܧ`#6/}߽}It1x[=y7fB@v_Beg۶O~ki9>LԂr(/+|pͯ*ӾN n?'fZ~zOYc2Vy E5ٴG"kAǕ%J"q_6e\! v|7^kq25GGjx@HٰL\ $- 3ZT/4U>0=Ϥ : -v&7+u"2v$,A^OmF3˲2 7Ce|狄& 6R&;<]8\O{5]rli,a%CMy%<ٙ?e(⃙ۦwv+ rK07ʰ)H( :n t~|v;ޜ[/L7i}δl;<0<0H0#*T$r +\fu8X[wr3okɿ>g8L,KOS'P3L~YMX[6!aIDATiHCoqBILv~lUJ;TSVx+{< 7e_AߑcLµ`;jI52r2Y+엡!A5U=e QFz3 ]С~y >Bff:< tϒO&P t+ohymxp2=ute}>w/itG~O?Ҁq   <[ tx`x`x`xAXpz}#}wn>Zgi8c9!O[C˞ +hި:vL2OyJD"0pL@&*>pA} >YS3`pyJ9LlѬ(+@ x 7:;'J #[Lt"O08i|f5>DO}<;h7O8to߁>A;F&Ki3δ?_+y裏t/ z^dLfd'\+' iQŪhaO^<uG=G>P?# MQH_ɣ>V=Ԁ gӡߗgNLESFx!y'OQnż{i]ޯrkB&:D~Бi#3pLma]֌;04O_i4mBOpbĘ:9`mm?0л]Ouәߧ=M(=j%@%OņFn+ٗ蘚d- XijXC l3ȯ&F/]{z/Ԯ̣φV2mW*I\\Fc+HZ2%]oM,~`f؋,o#8;a.AܸD .{I2n?G˴'A9Uxˑ}bMtޕӡ#thiUttVGw${d&L\l/}j/x=e v ᖻmoI*l>tqIB+f'v␠@ouwwoߛ/}!8<0<0Go`3+\_^s~'tx|3w4c;֠6'>q?-͟3{߿û׮]^2]D f;2nxL{3Ϝdhp:,@ 5O 18O'OSs(s|1r g%4X xˋG`[sAP\:kK\xkJ- s+>7m'y{zȫzYRQĉdlrG9WP?8 Dg rԊJ+ݎDAe-%o ej:znI*7O _\NPEh[e&?RVK԰&Y&&} ´ Së.VP#htx^zo% q%Kgpv 4*3o^Ŧ S/)GgtC{,͕/=Xi]NnGiC*=9/ou链tK'fſҺ[T^%.S{Ē|&!e7~!s6#_i M7QFye5 baSi6e`m`7FϥsFLQҙ`RF uX$uVk"_d>aeMXhf==Z*>}ؑ^[TMD#'zUNّڥUMId\'ȇ_ Q0, U0c0yNĶ>iy0#O^]Sy \q%׃ੑ$yz7 Mfoho-0 zh Exg?O_<ݾ==`>t>iO}Ҵdx`x`xy`L# oOħ_G8wK_e k|MM|}Î}0~#@1m$,0j~;4Z,+5@wP^K KNdDVG KȈ%[;uTT[5ShW1|' ҧ$o 9|FvXБWh i56Il׏[+؇ۭIeVzٚ_!eJI9B4ba>zz;-=xq {FM'V ]9,䩪RM +}0k9A:J9I#,tsYyGyiqap n~sU@\~ [8k3>}\E,MC=XtfRJ䃞-t+oVa Z <_TlP0AF~' JV1o km;,e8wrݳ5Kȏk77oܘntv'0g>ɯ.yG~x`x`xz`LSၷ? gݿ[>uW_:=MG, vr{WCp-ӄ#X_ZԠV³t |e:?g<_Spwzߩ6ԪAs=2:2FYF`ߘW ZBa8 (B=fU).)D-qσRt_oi_ԡ<-Y*72IQiWˇRÕW p_Z*Cڀ3I=4 Sp=r ״WI7yM&M1'e&']6`D51`I'o0_+]~W"i Dz !*:OO+=]xU^(y~IQG1r/ήwUp +Ny,+~ٯh/.Sh%ޅkMVx|ť]!ֵWy0SRJi)bki_2S߮SK^M|p¶+,Ԣ a> Q??(ʅ"5/ l_9=x՗_nܽ&^9<콿̗?M{wC?y{!++~C j5(u@t Ƽ;EGNl+//)!? k?_xpFh|`hB`( ";ZsV 8A 8Ζ#5ug8`h3g(G's 0 %RHy~ '>F{8z6:OPfH=a,\_ 0@+(~"'#MO@mE(<Ձ>u!CSfIp;loO$@5hwnB;' ٬1h x?*qQ1`Ϣwy3dOWUmy*Hv Mi*L<EWy ho]f[&+xQIvdle=Ϭ:<~iGWvEM[_;+JngbKOr;8'k`-ihaQSпCm M\=0>+ Fαĩ{3p71RFl=y%2@\SX) ]Vr^߽(Y.e3YGg!+tޮ}r3 y.OޙקޛNLkng}uާ?Ϟt#ny1gx`x`x`xx~|m?:`=T=e":'[y@I]0M{A~cM@ ]:-竮pU@& 80@& ]x` 4O$#lxOT|>5POHޠ߼) .y`l=EidV&s i; `Y^eπ=+3ܗ4A !2/i]&e*y CR4rWI.OYt%#l%~ c8@PK!/)RbQ}C ]3e N-Yvux?o)ߥɀlmŗ`:,y[ ^}Clj>ug[OND0.F?=|>ch/SN\۷N;>LQK::^GG? xNYN N'7>^_1x~Ez1_fZa+''Ӄ6;wo:Itz4 7P_;>#z7wwS5<0<0<0<{ڳZ`~poEY쯮V?(ԕ+׉@r&<^XƂZtrND9&__ϫjeB@ ASH-r?f (evaЊ3Pgʕ\A#_4 ()Yi$D&Q-hO*+&ArZapƕV?<ɻtز|I35My{Ks&up7ma-Cvh6!(8yu4 i2f~H51PWsNvd9=A/|ok*m4!.WMӧ-7({W\[&g2*:\.KΙ2YKxOztv.F A]rJ.xBn;4۰B'ۻN=kbӼ-tp dDkFh N*א2sGn euzz}Mқ7x&kǫ~f wۻwEP2<0<0<0<=oGWM~?+;?|ΓO<ݏ}cɽݳWwyc9)/ \m.V_` j6{ vw-3߃;;gKpGVd736 >e!!@h) KGe^i\kX9xN]w y^ݳS.3N0Nř)ٵ鵳 H''l?'7wu_}?_'7n'?S/L4< q    =ޝȏ؏|+_e                                                                                                                                                                                                     |W<v9Gk<6IENDB`kind-0.27.0/site/tools.go000066400000000000000000000004071475376161000151730ustar00rootroot00000000000000//go:build tools // +build tools /* Package site is used to track binary dependencies with go modules https://github.com/golang/go/wiki/Modules#how-can-i-track-tool-dependencies-for-a-module Namely: hugo */ package site import ( _ "github.com/gohugoio/hugo" )