diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 4167110..0000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,26 +0,0 @@ -version: 2 -jobs: - build: - docker: - - image: cimg/go:1.13 - steps: - - checkout - - setup_remote_docker - - deploy: - name: Build and push image - command: | - echo "$DOCKER_PASS" | docker login --username "$DOCKER_USER" --password-stdin - if [ -z "${CIRCLE_TAG}" ]; then - make publish-image - else - make VERSION="${CIRCLE_TAG}" publish-image - fi - -workflows: - version: 2 - build: - jobs: - - build: - filters: - tags: - only: /.*/ diff --git a/.github/workflows/check-links.yaml b/.github/workflows/check-links.yaml deleted file mode 100644 index fa50239..0000000 --- a/.github/workflows/check-links.yaml +++ /dev/null @@ -1,16 +0,0 @@ -name: "Check links" -on: [pull_request, push] - -jobs: - docs: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v1 - - name: Link Checker - id: lc - uses: peter-evans/link-checker@v1 - with: - args: -r *.md *.yaml */*/*.go -x .cluster.local - - name: Fail if there were link errors - run: exit ${{ steps.lc.outputs.exit_code }} - diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 400ca4d..0000000 --- a/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -cmd/kured/kured -cmd/prom-active-alerts/prom-active-alerts -vendor -build diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md deleted file mode 100644 index 3dff98c..0000000 --- a/DEVELOPMENT.md +++ /dev/null @@ -1,106 +0,0 @@ -# Developing `kured` - -We love contributions to `kured`, no matter if you are [helping out on -Slack][slack], reporting or triaging [issues][issues] or contributing code -to `kured`. - -In any case, it will make sense to familiarise yourself with the main -[README][readme] to understand the different features and options, which is -helpful for testing. The "building" section in particular makes sense if -you are planning to contribute code. - -[slack]: README.md#getting-help -[issues]: https://github.com/weaveworks/kured/issues -[readme]: README.md - -## Updating k8s support - -Whenever we want to update e.g. [`kubectl` in the -image](cmd/kured/Dockerfile), we need to consider if we update `client-go` -as well, some RBAC changes might be necessary too. - -This is what it took to support Kubernetes 1.14: - - -That the process can be more involved that that can be seen in - - -Once you updated everything, make sure you update the support matrix on -the main [README][readme] as well. - -## Release testing - -Before `kured` is released, we want to make sure it still works fine on the -previous, current and next minor version of Kubernetes (with respect to the -embedded `client-go` & `kubectl`). For local testing e.g. `minikube` can be -sufficient. - -Deploy kured in your test scenario, make sure you pass the right `image`, -update the e.g. `period` and `reboot-days` options, so you get immediate -results, if you login to a node and run: - -```console -sudo touch /var/run/reboot-required -``` - -### Testing with `minikube` - -A test-run with `minikube` could look like this: - -```console -minikube start --vm-driver kvm2 --kubernetes-version - -# edit kured-ds.yaml to -# - point to new image -# - change e.g. period and reboot-days option for immediate results - -minikube kubectl -- apply -f kured-rbac.yaml -minikube kubectl -- apply -f kured-ds.yaml -minikube kubectl -- logs daemonset.apps/kured -n kube-system -f - -# In separate terminal -minikube ssh - sudo touch /var/run/reboot-required -minikube logs -f -``` - -Now check for the 'Commanding reboot' message and minikube going down. - -Unfortunately as of today, you are going to run into -. This means that -minikube won't come back easily. You will need to start minikube again. -Then you can check for the lock release. - -If all the tests ran well, kured maintainers can reach out to the Weaveworks -team to get an upcoming `kured` release tested in the Dev environment for -real life testing. - -## Publishing a new kured release - -Check that `README.md` has an updated compatibility matrix and that the -url in the `kubectl` incantation (under "Installation") is updated to the -new version you want to release. - -Now create the `kured--dockerhub.yaml` for e.g. `1.3.0`: - -```sh -VERSION=1.3.0 -MANIFEST="kured-$VERSION-dockerhub.yaml" -cat kured-rbac.yaml > "$MANIFEST" -cat kured-ds.yaml >> "$MANIFEST" -sed -i "s#docker.io/weaveworks/kured#docker.io/weaveworks/kured:$VERSION#g" "$MANIFEST" -``` - -The last thing you need to do is update the `image:` to point to the release -tag, e.g. `docker.io/weaveworks/kured:1.3.0`. - -Now you can head to the Github UI, use the version number as tag and upload the -`kured--dockerhub.yaml` file. - -### Release notes - -Please describe what's new and noteworthy in the release notes, list the PRs -that landed and give a shout-out to everyone who contributed. - -Please also note down on which releases the upcoming `kured` release was -tested on. (Check old release notes if you're unsure.) diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 64a35d0..0000000 --- a/LICENSE +++ /dev/null @@ -1,191 +0,0 @@ - - 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 - - Copyright 2017 Weaveworks Ltd. - - 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. diff --git a/Makefile b/Makefile deleted file mode 100644 index e977440..0000000 --- a/Makefile +++ /dev/null @@ -1,35 +0,0 @@ -.DEFAULT: all -.PHONY: all clean image publish-image minikube-publish - -DH_ORG=weaveworks -VERSION=$(shell git symbolic-ref --short HEAD)-$(shell git rev-parse --short HEAD) -SUDO=$(shell docker info >/dev/null 2>&1 || echo "sudo -E") - -all: image - -clean: - rm -f cmd/kured/kured - rm -rf ./build - -godeps=$(shell go list -f '{{join .Deps "\n"}}' $1 | grep -v /vendor/ | xargs go list -f '{{if not .Standard}}{{ $$dep := . }}{{range .GoFiles}}{{$$dep.Dir}}/{{.}} {{end}}{{end}}') - -DEPS=$(call godeps,./cmd/kured) - -cmd/kured/kured: $(DEPS) -cmd/kured/kured: cmd/kured/*.go - CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-X main.version=$(VERSION)" -o $@ cmd/kured/*.go - -build/.image.done: cmd/kured/Dockerfile cmd/kured/kured - mkdir -p build - cp $^ build - $(SUDO) docker build -t docker.io/$(DH_ORG)/kured -f build/Dockerfile ./build - $(SUDO) docker tag docker.io/$(DH_ORG)/kured docker.io/$(DH_ORG)/kured:$(VERSION) - touch $@ - -image: build/.image.done - -publish-image: image - $(SUDO) docker push docker.io/$(DH_ORG)/kured:$(VERSION) - -minikube-publish: image - $(SUDO) docker save docker.io/$(DH_ORG)/kured | (eval $$(minikube docker-env) && docker load) diff --git a/README.md b/README.md deleted file mode 100644 index 346a114..0000000 --- a/README.md +++ /dev/null @@ -1,307 +0,0 @@ - - - -* [Introduction](#introduction) -* [Kubernetes & OS Compatibility](#kubernetes-&-os-compatibility) -* [Installation](#installation) -* [Configuration](#configuration) - * [Reboot Sentinel File & Period](#reboot-sentinel-file-&-period) - * [Setting a schedule](#setting-a-schedule) - * [Blocking Reboots via Alerts](#blocking-reboots-via-alerts) - * [Blocking Reboots via Pods](#blocking-reboots-via-pods) - * [Prometheus Metrics](#prometheus-metrics) - * [Slack Notifications](#slack-notifications) - * [Overriding Lock Configuration](#overriding-lock-configuration) -* [Operation](#operation) - * [Testing](#testing) - * [Disabling Reboots](#disabling-reboots) - * [Manual Unlock](#manual-unlock) -* [Building](#building) -* [Frequently Asked/Anticipated Questions](#frequently-askedanticipated-questions) -* [Getting Help](#getting-help) - -## Introduction - -Kured (KUbernetes REboot Daemon) is a Kubernetes daemonset that -performs safe automatic node reboots when the need to do so is -indicated by the package management system of the underlying OS. - -* Watches for the presence of a reboot sentinel e.g. `/var/run/reboot-required` -* Utilises a lock in the API server to ensure only one node reboots at - a time -* Optionally defers reboots in the presence of active Prometheus alerts or selected pods -* Cordons & drains worker nodes before reboot, uncordoning them after - -## Kubernetes & OS Compatibility - -The daemon image contains versions of `k8s.io/client-go` and the -`kubectl` binary for the purposes of maintaining the lock and draining -worker nodes. Kubernetes aims to provide forwards & backwards -compatibility of one minor version between client and server: - -| kured | kubectl | k8s.io/client-go | k8s.io/apimachinery | expected kubernetes compatibility | -|--------|---------|------------------|---------------------|-----------------------------------| -| master | 1.18.2 | v0.18.2 | v0.18.2 | 1.17.x, 1.18.x, 1.19.x | -| 1.4.0 | 1.17.5 | v0.17.0 | v0.17.0 | 1.16.x, 1.17.x, 1.18.x | -| 1.3.0 | 1.15.10 | v12.0.0 | release-1.15 | 1.15.x, 1.16.x, 1.17.x | -| 1.2.0 | 1.13.6 | v10.0.0 | release-1.13 | 1.12.x, 1.13.x, 1.14.x | -| 1.1.0 | 1.12.1 | v9.0.0 | release-1.12 | 1.11.x, 1.12.x, 1.13.x | -| 1.0.0 | 1.7.6 | v4.0.0 | release-1.7 | 1.6.x, 1.7.x, 1.8.x | - -See the [release notes](https://github.com/weaveworks/kured/releases) -for specific version compatibility information, including which -combination have been formally tested. - -Versions >=1.1.0 enter the host mount namespace to invoke -`systemctl reboot`, so should work on any systemd distribution. - -## Installation - -To obtain a default installation without Prometheus alerting interlock -or Slack notifications: - -``` -kubectl apply -f https://github.com/weaveworks/kured/releases/download/1.3.0/kured-1.3.0-dockerhub.yaml -``` - -If you want to customise the installation, download the manifest and -edit it in accordance with the following section before application. - -## Configuration - -The following arguments can be passed to kured via the daemonset pod template: - -``` -Flags: - --alert-filter-regexp regexp.Regexp alert names to ignore when checking for active alerts - --blocking-pod-selector stringArray label selector identifying pods whose presence should prevent reboots - --ds-name string name of daemonset on which to place lock (default "kured") - --ds-namespace string namespace containing daemonset on which to place lock (default "kube-system") - --end-time string only reboot before this time of day (default "23:59") - -h, --help help for kured - --lock-annotation string annotation in which to record locking node (default "weave.works/kured-node-lock") - --period duration reboot check period (default 1h0m0s) - --prometheus-url string Prometheus instance to probe for active alerts - --reboot-days strings only reboot on these days (default [su,mo,tu,we,th,fr,sa]) - --reboot-sentinel string path to file whose existence signals need to reboot (default "/var/run/reboot-required") - --slack-channel string slack channel for reboot notfications - --slack-hook-url string slack hook URL for reboot notfications - --slack-username string slack username for reboot notfications (default "kured") - --start-time string only reboot after this time of day (default "0:00") - --time-zone string use this timezone to calculate allowed reboot time (default "UTC") -``` - -### Reboot Sentinel File & Period - -By default kured checks for the existence of -`/var/run/reboot-required` every sixty minutes; you can override these -values with `--reboot-sentinel` and `--period`. Each replica of the -daemon uses a random offset derived from the period on startup so that -nodes don't all contend for the lock simultaneously. - -### Setting a schedule - -By default, kured will reboot any time it detects the sentinel, but this -may cause reboots during odd hours. While service disruption does not -normally occur, anything is possible and operators may want to restrict -reboots to predictable schedules. Use `--reboot-days`, `--start-time`, -`--end-time`, and `--time-zone` to set a schedule. For example, business -hours on the west coast USA can be specified with: - -``` - --reboot-days mon,tue,wed,thu,fri - --start-time 9am - --end-time 5pm - --time-zone America/Los_Angeles -``` - -Times can be formatted in numerous ways, including `5pm`, `5:00pm` `17:00`, -and `17`. `--time-zone` represents a Go `time.Location`, and can be `UTC`, -`Local`, or any entry in the standard Linux tz database. - -Note that when using smaller time windows, you should consider shortening -the sentinel check period (`--period`). - -### Blocking Reboots via Alerts - -You may find it desirable to block automatic node reboots when there -are active alerts - you can do so by providing the URL of your -Prometheus server: - -``` ---prometheus-url=http://prometheus.monitoring.svc.cluster.local -``` - -By default the presence of *any* active (pending or firing) alerts -will block reboots, however you can ignore specific alerts: - -``` ---alert-filter-regexp=^(RebootRequired|AnotherBenignAlert|...$ -``` - -See the section on Prometheus metrics for an important application of this -filter. - -### Blocking Reboots via Pods - -You can also block reboots of an _individual node_ when specific pods -are scheduled on it: - -``` ---blocking-pod-selector=runtime=long,cost=expensive -``` - -Since label selector strings use commas to express logical 'and', you can -specify this parameter multiple times for 'or': - -``` ---blocking-pod-selector=runtime=long,cost=expensive ---blocking-pod-selector=name=temperamental -``` - -In this case, the presence of either an (appropriately labelled) expensive long -running job or a known temperamental pod on a node will stop it rebooting. - -> Try not to abuse this mechanism - it's better to strive for -> restartability where possible. If you do use it, make sure you set -> up a RebootRequired alert as described in the next section so that -> you can intervene manually if reboots are blocked for too long. - -### Prometheus Metrics - -Each kured pod exposes a single gauge metric (`:8080/metrics`) that -indicates the presence of the sentinel file: - -``` -# HELP kured_reboot_required OS requires reboot due to software updates. -# TYPE kured_reboot_required gauge -kured_reboot_required{node="ip-xxx-xxx-xxx-xxx.ec2.internal"} 0 -``` - -The purpose of this metric is to power an alert which will summon an -operator if the cluster cannot reboot itself automatically for a -prolonged period: - -``` -# Alert if a reboot is required for any machines. Acts as a failsafe for the -# reboot daemon, which will not reboot nodes if there are pending alerts save -# this one. -ALERT RebootRequired - IF max(kured_reboot_required) != 0 - FOR 24h - LABELS { severity="warning" } - ANNOTATIONS { - summary = "Machine(s) require being rebooted, and the reboot daemon has failed to do so for 24 hours", - impact = "Cluster nodes more vulnerable to security exploits. Eventually, no disk space left.", - description = "Machine(s) require being rebooted, probably due to kernel update.", - } -``` - -If you choose to employ such an alert and have configured kured to -probe for active alerts before rebooting, be sure to specify -`--alert-filter-regexp=^RebootRequired$` to avoid deadlock! - -### Slack Notifications - -If you specify a Slack hook via `--slack-hook-url`, kured will notify -you immediately prior to rebooting a node: - - - -We recommend setting `--slack-username` to be the name of the -environment, e.g. `dev` or `prod`. - -### Overriding Lock Configuration - -The `--ds-name` and `--ds-namespace` arguments should match the name and -namespace of the daemonset used to deploy the reboot daemon - the locking is -implemented by means of an annotation on this resource. The defaults match -the daemonset YAML provided in the repository. - -Similarly `--lock-annotation` can be used to change the name of the -annotation kured will use to store the lock, but the default is almost -certainly safe. - -## Operation - -The example commands in this section assume that you have not -overriden the default lock annotation, daemonset name or namespace; -if you have, you will have to adjust the commands accordingly. - -### Testing - -You can test your configuration by provoking a reboot on a node: - -``` -sudo touch /var/run/reboot-required -``` - -### Disabling Reboots - -If you need to temporarily stop kured from rebooting any nodes, you -can take the lock manually: - -``` -kubectl -n kube-system annotate ds kured weave.works/kured-node-lock='{"nodeID":"manual"}' -``` - -Don't forget to release it afterwards! - -### Manual Unlock - -In exceptional circumstances, such as a node experiencing a permanent -failure whilst rebooting, manual intervention may be required to -remove the cluster lock: - -``` -kubectl -n kube-system annotate ds kured weave.works/kured-node-lock- -``` -> NB the `-` at the end of the command is important - it instructs -> `kubectl` to remove that annotation entirely. - -## Building - -See the [CircleCI config](.circleci/config.yml) for the preferred -version of Golang. Kured now uses [Go -Modules](https://github.com/golang/go/wiki/Modules), so build -instructions vary depending on where you have checked out the -repository: - -**Building outside $GOPATH:** - -``` -make -``` - -**Building inside $GOPATH:** - -``` -GO111MODULE=on make -``` - -If you are interested in contributing code to kured, please take a look at -our [development][development] docs. - -[development]: DEVELOPMENT.md - -## Frequently Asked/Anticipated Questions - -### Why is there no `latest` tag on Docker Hub? - -Use of `latest` for production deployments is bad practice - see -[here](https://kubernetes.io/docs/concepts/configuration/overview) for -details. The manifest on `master` refers to `latest` for local -development testing with minikube only; for production use choose a -versioned manifest from the [release page](https://github.com/weaveworks/kured/releases/). - -## Getting Help - -If you have any questions about, feedback for or problems with `kured`: - -- Invite yourself to the Weave Users Slack. -- Ask a question on the [#kured](https://weave-community.slack.com/messages/kured/) slack channel. -- [File an issue](https://github.com/weaveworks/kured/issues/new). -- Join us in [our monthly meeting](https://docs.google.com/document/d/1bsHTjHhqaaZ7yJnXF6W8c89UB_yn-OoSZEmDnIP34n8/edit#), - every fourth Wednesday of the month at 16:00 UTC. - -Your feedback is always welcome! diff --git a/cmd/kured/Dockerfile b/cmd/kured/Dockerfile deleted file mode 100644 index e835e8c..0000000 --- a/cmd/kured/Dockerfile +++ /dev/null @@ -1,7 +0,0 @@ -FROM alpine:3.11 -RUN apk update && apk add ca-certificates tzdata && rm -rf /var/cache/apk/* -# NB: you may need to update RBAC permissions when upgrading kubectl - see kured-rbac.yaml for details -ADD https://storage.googleapis.com/kubernetes-release/release/v1.18.2/bin/linux/amd64/kubectl /usr/bin/kubectl -RUN chmod 0755 /usr/bin/kubectl -COPY ./kured /usr/bin/kured -ENTRYPOINT ["/usr/bin/kured"] diff --git a/cmd/kured/main.go b/cmd/kured/main.go deleted file mode 100644 index 482525c..0000000 --- a/cmd/kured/main.go +++ /dev/null @@ -1,356 +0,0 @@ -package main - -import ( - "context" - "fmt" - "math/rand" - "net/http" - "os" - "os/exec" - "regexp" - "time" - - log "github.com/sirupsen/logrus" - "github.com/spf13/cobra" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" - - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/promhttp" - "github.com/weaveworks/kured/pkg/alerts" - "github.com/weaveworks/kured/pkg/daemonsetlock" - "github.com/weaveworks/kured/pkg/delaytick" - "github.com/weaveworks/kured/pkg/notifications/slack" - "github.com/weaveworks/kured/pkg/timewindow" -) - -var ( - version = "unreleased" - - // Command line flags - period time.Duration - dsNamespace string - dsName string - lockAnnotation string - prometheusURL string - alertFilter *regexp.Regexp - rebootSentinel string - slackHookURL string - slackUsername string - slackChannel string - podSelectors []string - - rebootDays []string - rebootStart string - rebootEnd string - timezone string - - // Metrics - rebootRequiredGauge = prometheus.NewGaugeVec(prometheus.GaugeOpts{ - Subsystem: "kured", - Name: "reboot_required", - Help: "OS requires reboot due to software updates.", - }, []string{"node"}) -) - -func init() { - prometheus.MustRegister(rebootRequiredGauge) -} - -func main() { - rootCmd := &cobra.Command{ - Use: "kured", - Short: "Kubernetes Reboot Daemon", - Run: root} - - rootCmd.PersistentFlags().DurationVar(&period, "period", time.Minute*60, - "reboot check period") - rootCmd.PersistentFlags().StringVar(&dsNamespace, "ds-namespace", "kube-system", - "namespace containing daemonset on which to place lock") - rootCmd.PersistentFlags().StringVar(&dsName, "ds-name", "kured", - "name of daemonset on which to place lock") - rootCmd.PersistentFlags().StringVar(&lockAnnotation, "lock-annotation", "weave.works/kured-node-lock", - "annotation in which to record locking node") - rootCmd.PersistentFlags().StringVar(&prometheusURL, "prometheus-url", "", - "Prometheus instance to probe for active alerts") - rootCmd.PersistentFlags().Var(®expValue{&alertFilter}, "alert-filter-regexp", - "alert names to ignore when checking for active alerts") - rootCmd.PersistentFlags().StringVar(&rebootSentinel, "reboot-sentinel", "/var/run/reboot-required", - "path to file whose existence signals need to reboot") - - rootCmd.PersistentFlags().StringVar(&slackHookURL, "slack-hook-url", "", - "slack hook URL for reboot notfications") - rootCmd.PersistentFlags().StringVar(&slackUsername, "slack-username", "kured", - "slack username for reboot notfications") - rootCmd.PersistentFlags().StringVar(&slackChannel, "slack-channel", "", - "slack channel for reboot notfications") - - rootCmd.PersistentFlags().StringArrayVar(&podSelectors, "blocking-pod-selector", nil, - "label selector identifying pods whose presence should prevent reboots") - - rootCmd.PersistentFlags().StringSliceVar(&rebootDays, "reboot-days", timewindow.EveryDay, - "schedule reboot on these days") - rootCmd.PersistentFlags().StringVar(&rebootStart, "start-time", "0:00", - "schedule reboot only after this time of day") - rootCmd.PersistentFlags().StringVar(&rebootEnd, "end-time", "23:59:59", - "schedule reboot only before this time of day") - rootCmd.PersistentFlags().StringVar(&timezone, "time-zone", "UTC", - "use this timezone for schedule inputs") - - if err := rootCmd.Execute(); err != nil { - log.Fatal(err) - } -} - -// newCommand creates a new Command with stdout/stderr wired to our standard logger -func newCommand(name string, arg ...string) *exec.Cmd { - cmd := exec.Command(name, arg...) - - cmd.Stdout = log.NewEntry(log.StandardLogger()). - WithField("cmd", cmd.Args[0]). - WithField("std", "out"). - WriterLevel(log.InfoLevel) - - cmd.Stderr = log.NewEntry(log.StandardLogger()). - WithField("cmd", cmd.Args[0]). - WithField("std", "err"). - WriterLevel(log.WarnLevel) - - return cmd -} - -func sentinelExists() bool { - // Relies on hostPID:true and privileged:true to enter host mount space - sentinelCmd := newCommand("/usr/bin/nsenter", "-m/proc/1/ns/mnt", "--", "/usr/bin/test", "-f", rebootSentinel) - if err := sentinelCmd.Run(); err != nil { - switch err := err.(type) { - case *exec.ExitError: - // We assume a non-zero exit code means 'reboot not required', but of course - // the user could have misconfigured the sentinel command or something else - // went wrong during its execution. In that case, not entering a reboot loop - // is the right thing to do, and we are logging stdout/stderr of the command - // so it should be obvious what is wrong. - return false - default: - // Something was grossly misconfigured, such as the command path being wrong. - log.Fatalf("Error invoking sentinel command: %v", err) - } - } - return true -} - -func rebootRequired() bool { - if sentinelExists() { - log.Infof("Reboot required") - return true - } else { - log.Infof("Reboot not required") - return false - } -} - -func rebootBlocked(client *kubernetes.Clientset, nodeID string) bool { - if prometheusURL != "" { - alertNames, err := alerts.PrometheusActiveAlerts(prometheusURL, alertFilter) - if err != nil { - log.Warnf("Reboot blocked: prometheus query error: %v", err) - return true - } - count := len(alertNames) - if count > 10 { - alertNames = append(alertNames[:10], "...") - } - if count > 0 { - log.Warnf("Reboot blocked: %d active alerts: %v", count, alertNames) - return true - } - } - - fieldSelector := fmt.Sprintf("spec.nodeName=%s", nodeID) - for _, labelSelector := range podSelectors { - podList, err := client.CoreV1().Pods("").List(context.TODO(), metav1.ListOptions{ - LabelSelector: labelSelector, - FieldSelector: fieldSelector, - Limit: 10}) - if err != nil { - log.Warnf("Reboot blocked: pod query error: %v", err) - return true - } - - if len(podList.Items) > 0 { - podNames := make([]string, 0, len(podList.Items)) - for _, pod := range podList.Items { - podNames = append(podNames, pod.Name) - } - if len(podList.Continue) > 0 { - podNames = append(podNames, "...") - } - log.Warnf("Reboot blocked: matching pods: %v", podNames) - return true - } - } - - return false -} - -func holding(lock *daemonsetlock.DaemonSetLock, metadata interface{}) bool { - holding, err := lock.Test(metadata) - if err != nil { - log.Fatalf("Error testing lock: %v", err) - } - if holding { - log.Infof("Holding lock") - } - return holding -} - -func acquire(lock *daemonsetlock.DaemonSetLock, metadata interface{}) bool { - holding, holder, err := lock.Acquire(metadata) - switch { - case err != nil: - log.Fatalf("Error acquiring lock: %v", err) - return false - case !holding: - log.Warnf("Lock already held: %v", holder) - return false - default: - log.Infof("Acquired reboot lock") - return true - } -} - -func release(lock *daemonsetlock.DaemonSetLock) { - log.Infof("Releasing lock") - if err := lock.Release(); err != nil { - log.Fatalf("Error releasing lock: %v", err) - } -} - -func drain(nodeID string) { - log.Infof("Draining node %s", nodeID) - - if slackHookURL != "" { - if err := slack.NotifyDrain(slackHookURL, slackUsername, slackChannel, nodeID); err != nil { - log.Warnf("Error notifying slack: %v", err) - } - } - - drainCmd := newCommand("/usr/bin/kubectl", "drain", - "--ignore-daemonsets", "--delete-local-data", "--force", nodeID) - - if err := drainCmd.Run(); err != nil { - log.Fatalf("Error invoking drain command: %v", err) - } -} - -func uncordon(nodeID string) { - log.Infof("Uncordoning node %s", nodeID) - uncordonCmd := newCommand("/usr/bin/kubectl", "uncordon", nodeID) - if err := uncordonCmd.Run(); err != nil { - log.Fatalf("Error invoking uncordon command: %v", err) - } -} - -func commandReboot(nodeID string) { - log.Infof("Commanding reboot for node: %s", nodeID) - - if slackHookURL != "" { - if err := slack.NotifyReboot(slackHookURL, slackUsername, slackChannel, nodeID); err != nil { - log.Warnf("Error notifying slack: %v", err) - } - } - - // Relies on hostPID:true and privileged:true to enter host mount space - rebootCmd := newCommand("/usr/bin/nsenter", "-m/proc/1/ns/mnt", "/bin/systemctl", "reboot") - if err := rebootCmd.Run(); err != nil { - log.Fatalf("Error invoking reboot command: %v", err) - } -} - -func maintainRebootRequiredMetric(nodeID string) { - for { - if sentinelExists() { - rebootRequiredGauge.WithLabelValues(nodeID).Set(1) - } else { - rebootRequiredGauge.WithLabelValues(nodeID).Set(0) - } - time.Sleep(time.Minute) - } -} - -// nodeMeta is used to remember information across reboots -type nodeMeta struct { - Unschedulable bool `json:"unschedulable"` -} - -func rebootAsRequired(nodeID string, window *timewindow.TimeWindow) { - config, err := rest.InClusterConfig() - if err != nil { - log.Fatal(err) - } - - client, err := kubernetes.NewForConfig(config) - if err != nil { - log.Fatal(err) - } - - lock := daemonsetlock.New(client, nodeID, dsNamespace, dsName, lockAnnotation) - - nodeMeta := nodeMeta{} - if holding(lock, &nodeMeta) { - if !nodeMeta.Unschedulable { - uncordon(nodeID) - } - release(lock) - } - - source := rand.NewSource(time.Now().UnixNano()) - tick := delaytick.New(source, period) - for _ = range tick { - if window.Contains(time.Now()) && rebootRequired() && !rebootBlocked(client, nodeID) { - node, err := client.CoreV1().Nodes().Get(context.TODO(), nodeID, metav1.GetOptions{}) - if err != nil { - log.Fatal(err) - } - nodeMeta.Unschedulable = node.Spec.Unschedulable - - if acquire(lock, &nodeMeta) { - if !nodeMeta.Unschedulable { - drain(nodeID) - } - commandReboot(nodeID) - for { - log.Infof("Waiting for reboot") - time.Sleep(time.Minute) - } - } - } - } -} - -func root(cmd *cobra.Command, args []string) { - log.Infof("Kubernetes Reboot Daemon: %s", version) - - nodeID := os.Getenv("KURED_NODE_ID") - if nodeID == "" { - log.Fatal("KURED_NODE_ID environment variable required") - } - - window, err := timewindow.New(rebootDays, rebootStart, rebootEnd, timezone) - if err != nil { - log.Fatalf("Failed to build time window: %v", err) - } - - log.Infof("Node ID: %s", nodeID) - log.Infof("Lock Annotation: %s/%s:%s", dsNamespace, dsName, lockAnnotation) - log.Infof("Reboot Sentinel: %s every %v", rebootSentinel, period) - log.Infof("Blocking Pod Selectors: %v", podSelectors) - log.Infof("Reboot on: %v", window) - - go rebootAsRequired(nodeID, window) - go maintainRebootRequiredMetric(nodeID) - - http.Handle("/metrics", promhttp.Handler()) - log.Fatal(http.ListenAndServe(":8080", nil)) -} diff --git a/cmd/kured/pflags.go b/cmd/kured/pflags.go deleted file mode 100644 index b6eb5ab..0000000 --- a/cmd/kured/pflags.go +++ /dev/null @@ -1,31 +0,0 @@ -package main - -import ( - "regexp" -) - -type regexpValue struct { - value **regexp.Regexp -} - -func (rev *regexpValue) String() string { - if *rev.value == nil { - return "" - } - return (*rev.value).String() -} - -func (rev *regexpValue) Set(s string) error { - value, err := regexp.Compile(s) - if err != nil { - return err - } - - *rev.value = value - - return nil -} - -func (rev *regexpValue) Type() string { - return "regexp.Regexp" -} diff --git a/cmd/prom-active-alerts/main.go b/cmd/prom-active-alerts/main.go deleted file mode 100644 index a2dec5c..0000000 --- a/cmd/prom-active-alerts/main.go +++ /dev/null @@ -1,23 +0,0 @@ -package main - -import ( - "fmt" - "log" - "os" - "regexp" - - "github.com/weaveworks/kured/pkg/alerts" -) - -func main() { - if len(os.Args) != 3 { - log.Fatalf("USAGE: %s ", os.Args[0]) - } - - count, err := alerts.PrometheusCountActive(os.Args[1], regexp.MustCompile(os.Args[2])) - if err != nil { - log.Fatal(err) - } - - fmt.Println(count) -} diff --git a/go.mod b/go.mod deleted file mode 100644 index 62b3b05..0000000 --- a/go.mod +++ /dev/null @@ -1,15 +0,0 @@ -module github.com/weaveworks/kured - -go 1.13 - -require ( - github.com/googleapis/gnostic v0.2.0 // indirect - github.com/inconshreveable/mousetrap v1.0.0 // indirect - github.com/prometheus/client_golang v0.0.0-20181230203121-fb3d5cb2ad57 - github.com/prometheus/common v0.0.0-20181218105931-67670fe90761 - github.com/prometheus/procfs v0.0.0-20190102135031-14fa7590c24d // indirect - github.com/sirupsen/logrus v1.2.0 - github.com/spf13/cobra v0.0.0-20181127133106-d2d81d9a96e2 - k8s.io/apimachinery v0.18.2 - k8s.io/client-go v0.18.2 -) diff --git a/go.sum b/go.sum deleted file mode 100644 index fb472de..0000000 --- a/go.sum +++ /dev/null @@ -1,232 +0,0 @@ -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= -github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= -github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= -github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= -github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= -github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= -github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= -github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= -github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -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/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= -github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= -github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= -github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= -github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= -github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= -github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= -github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= -github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/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/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -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 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= -github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= -github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= -github.com/googleapis/gnostic v0.2.0 h1:l6N3VoaVzTncYYW+9yOz2LJJammFZGBO13sqgEhpy9g= -github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= -github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= -github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -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/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.8 h1:QiWkFLKq0T7mpzwOTu6BzNDbfTE8OLrYhVKYMLF46Ok= -github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -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/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 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= -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_golang v0.0.0-20181230203121-fb3d5cb2ad57 h1:+cstagqzfjiYrs1QrmypZRndHJafhB9W3ab/zPiwl1Q= -github.com/prometheus/client_golang v0.0.0-20181230203121-fb3d5cb2ad57/go.mod h1:PS8H/EtMiYNSR3o4jWYMuyp9FkDf2ptwxn1kTerl5EM= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/common v0.0.0-20181218105931-67670fe90761 h1:z6tvbDJ5OLJ48FFmnksv04a78maSTRBUIhkdHYV5Y98= -github.com/prometheus/common v0.0.0-20181218105931-67670fe90761/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190102135031-14fa7590c24d h1:iSxvUGRUGQTUgEo5+8TqMVQoH5AdaphEIjh8AnM7TPo= -github.com/prometheus/procfs v0.0.0-20190102135031-14fa7590c24d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= -github.com/spf13/cobra v0.0.0-20181127133106-d2d81d9a96e2 h1:gPjqutnNhLnq5t4hT2UfL06h/mvp4je4OY7Dk71vhuk= -github.com/spf13/cobra v0.0.0-20181127133106-d2d81d9a96e2/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -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.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/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-20200220183623-bac4c82f6975 h1:/Tl7pH94bvbAAHBdZJT947M/+gp0+CqQXDtMRC0fseo= -golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -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/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -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-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181217023233-e147a9138326/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-20191004110552-13f9640d40b9 h1:rjwSpXsdiK0dV8/Naq3kAw9ymfAeJIyd0upUIElB+lI= -golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -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 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw= -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/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -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-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190209173611-3b5209105503/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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7 h1:HmbHVPwrPEKPGLAcHSrMe6+hqSUlvZU0rab6x5EXfGU= -golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= -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 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c h1:fqgJT0MGcGpPgpWU7VRdRjuArfcOvC4AoJmILihzhDg= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/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-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/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-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -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/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= -gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -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= -k8s.io/api v0.18.0 h1:lwYk8Vt7rsVTwjRU6pzEsa9YNhThbmbocQlKvNBB4EQ= -k8s.io/api v0.18.0/go.mod h1:q2HRQkfDzHMBZL9l/y9rH63PkQl4vae0xRT+8prbrK8= -k8s.io/api v0.18.2 h1:wG5g5ZmSVgm5B+eHMIbI9EGATS2L8Z72rda19RIEgY8= -k8s.io/api v0.18.2/go.mod h1:SJCWI7OLzhZSvbY7U8zwNl9UA4o1fizoug34OV/2r78= -k8s.io/apimachinery v0.18.0 h1:fuPfYpk3cs1Okp/515pAf0dNhL66+8zk8RLbSX+EgAE= -k8s.io/apimachinery v0.18.0/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA= -k8s.io/apimachinery v0.18.2 h1:44CmtbmkzVDAhCpRVSiP2R5PPrC2RtlIv/MoB8xpdRA= -k8s.io/apimachinery v0.18.2/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA= -k8s.io/client-go v0.18.0 h1:yqKw4cTUQraZK3fcVCMeSa+lqKwcjZ5wtcOIPnxQno4= -k8s.io/client-go v0.18.0/go.mod h1:uQSYDYs4WhVZ9i6AIoEZuwUggLVEF64HOD37boKAtF8= -k8s.io/client-go v0.18.2 h1:aLB0iaD4nmwh7arT2wIn+lMnAq7OswjaejkQ8p9bBYE= -k8s.io/client-go v0.18.2/go.mod h1:Xcm5wVGXX9HAA2JJ2sSBUn3tCJ+4SVlCbl2MNNv+CIU= -k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= -k8s.io/klog v0.3.0 h1:0VPpR+sizsiivjIfIAQH/rl8tan6jvWkS7lU+0di3lE= -k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= -k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= -k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= -k8s.io/kube-openapi v0.0.0-20200121204235-bf4fb3bd569c/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= -k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89 h1:d4vVOjXm687F1iLSP2q3lyPPuyvTUt3aVoBpi2DqRsU= -k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= -sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= -sigs.k8s.io/structured-merge-diff/v3 v3.0.0 h1:dOmIZBMfhcHS09XZkMyUgkq5trg3/jRyJYFZUiaOp8E= -sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= -sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= -sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= -sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= -sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= diff --git a/img/logo.png b/img/logo.png deleted file mode 100644 index 50bd41c..0000000 Binary files a/img/logo.png and /dev/null differ diff --git a/img/slack-notification.png b/img/slack-notification.png deleted file mode 100644 index b8797c8..0000000 Binary files a/img/slack-notification.png and /dev/null differ diff --git a/kured-ds.yaml b/kured-ds.yaml deleted file mode 100644 index b2df4db..0000000 --- a/kured-ds.yaml +++ /dev/null @@ -1,63 +0,0 @@ ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - name: kured - namespace: kube-system ---- -apiVersion: apps/v1 -kind: DaemonSet -metadata: - name: kured # Must match `--ds-name` - namespace: kube-system # Must match `--ds-namespace` -spec: - selector: - matchLabels: - name: kured - updateStrategy: - type: RollingUpdate - template: - metadata: - labels: - name: kured - spec: - serviceAccountName: kured - tolerations: - - key: node-role.kubernetes.io/master - effect: NoSchedule - hostPID: true # Facilitate entering the host mount namespace via init - restartPolicy: Always - containers: - - name: kured - image: docker.io/weaveworks/kured - # If you find yourself here wondering why there is no - # :latest tag on Docker Hub,see the FAQ in the README - imagePullPolicy: IfNotPresent - securityContext: - privileged: true # Give permission to nsenter /proc/1/ns/mnt - env: - # Pass in the name of the node on which this pod is scheduled - # for use with drain/uncordon operations and lock acquisition - - name: KURED_NODE_ID - valueFrom: - fieldRef: - fieldPath: spec.nodeName - command: - - /usr/bin/kured -# - --alert-filter-regexp=^RebootRequired$ -# - --blocking-pod-selector=runtime=long,cost=expensive -# - --blocking-pod-selector=name=temperamental -# - --blocking-pod-selector=... -# - --ds-name=kured -# - --ds-namespace=kube-system -# - --end-time=23:59:59 -# - --lock-annotation=weave.works/kured-node-lock -# - --period=1h -# - --prometheus-url=http://prometheus.monitoring.svc.cluster.local -# - --reboot-days=sun,mon,tue,wed,thu,fri,sat -# - --reboot-sentinel=/var/run/reboot-required -# - --slack-hook-url=https://hooks.slack.com/... -# - --slack-username=prod -# - --slack-channel=alerting -# - --start-time=0:00 -# - --time-zone=UTC diff --git a/kured-rbac.yaml b/kured-rbac.yaml deleted file mode 100644 index d425ce3..0000000 --- a/kured-rbac.yaml +++ /dev/null @@ -1,63 +0,0 @@ ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: kured -rules: -# Allow kured to read spec.unschedulable -# Allow kubectl to drain/uncordon -# -# NB: These permissions are tightly coupled to the bundled version of kubectl; the ones below -# match https://github.com/kubernetes/kubernetes/blob/v1.18.2/staging/src/k8s.io/kubectl/pkg/cmd/drain/drain.go -# -- apiGroups: [""] - resources: ["nodes"] - verbs: ["get", "patch"] -- apiGroups: [""] - resources: ["pods"] - verbs: ["list","delete","get"] -- apiGroups: ["apps"] - resources: ["daemonsets"] - verbs: ["get"] -- apiGroups: [""] - resources: ["pods/eviction"] - verbs: ["create"] ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: kured -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: kured -subjects: -- kind: ServiceAccount - name: kured - namespace: kube-system ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - namespace: kube-system - name: kured -rules: -# Allow kured to lock/unlock itself -- apiGroups: ["apps"] - resources: ["daemonsets"] - resourceNames: ["kured"] - verbs: ["update"] ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - namespace: kube-system - name: kured -subjects: -- kind: ServiceAccount - namespace: kube-system - name: kured -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: kured diff --git a/pkg/alerts/prometheus.go b/pkg/alerts/prometheus.go deleted file mode 100644 index 9310729..0000000 --- a/pkg/alerts/prometheus.go +++ /dev/null @@ -1,52 +0,0 @@ -package alerts - -import ( - "context" - "fmt" - "regexp" - "sort" - "time" - - "github.com/prometheus/client_golang/api" - "github.com/prometheus/client_golang/api/prometheus/v1" - "github.com/prometheus/common/model" -) - -// Returns a list of names of active (e.g. pending or firing) alerts, filtered -// by the supplied regexp. -func PrometheusActiveAlerts(prometheusURL string, filter *regexp.Regexp) ([]string, error) { - client, err := api.NewClient(api.Config{Address: prometheusURL}) - if err != nil { - return nil, err - } - - queryAPI := v1.NewAPI(client) - - value, err := queryAPI.Query(context.Background(), "ALERTS", time.Now()) - if err != nil { - return nil, err - } - - if value.Type() == model.ValVector { - if vector, ok := value.(model.Vector); ok { - activeAlertSet := make(map[string]bool) - for _, sample := range vector { - if alertName, isAlert := sample.Metric[model.AlertNameLabel]; isAlert && sample.Value != 0 { - if filter == nil || !filter.MatchString(string(alertName)) { - activeAlertSet[string(alertName)] = true - } - } - } - - var activeAlerts []string - for activeAlert, _ := range activeAlertSet { - activeAlerts = append(activeAlerts, activeAlert) - } - sort.Sort(sort.StringSlice(activeAlerts)) - - return activeAlerts, nil - } - } - - return nil, fmt.Errorf("Unexpected value type: %v", value) -} diff --git a/pkg/daemonsetlock/daemonsetlock.go b/pkg/daemonsetlock/daemonsetlock.go deleted file mode 100644 index 9889b78..0000000 --- a/pkg/daemonsetlock/daemonsetlock.go +++ /dev/null @@ -1,123 +0,0 @@ -package daemonsetlock - -import ( - "context" - "encoding/json" - "fmt" - "time" - - "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" -) - -type DaemonSetLock struct { - client *kubernetes.Clientset - nodeID string - namespace string - name string - annotation string -} - -type lockAnnotationValue struct { - NodeID string `json:"nodeID"` - Metadata interface{} `json:"metadata,omitempty"` -} - -func New(client *kubernetes.Clientset, nodeID, namespace, name, annotation string) *DaemonSetLock { - return &DaemonSetLock{client, nodeID, namespace, name, annotation} -} - -func (dsl *DaemonSetLock) Acquire(metadata interface{}) (acquired bool, owner string, err error) { - for { - ds, err := dsl.client.AppsV1().DaemonSets(dsl.namespace).Get(context.TODO(), dsl.name, metav1.GetOptions{}) - if err != nil { - return false, "", err - } - - valueString, exists := ds.ObjectMeta.Annotations[dsl.annotation] - if exists { - value := lockAnnotationValue{} - if err := json.Unmarshal([]byte(valueString), &value); err != nil { - return false, "", err - } - return value.NodeID == dsl.nodeID, value.NodeID, nil - } - - if ds.ObjectMeta.Annotations == nil { - ds.ObjectMeta.Annotations = make(map[string]string) - } - value := lockAnnotationValue{NodeID: dsl.nodeID, Metadata: metadata} - valueBytes, err := json.Marshal(&value) - if err != nil { - return false, "", err - } - ds.ObjectMeta.Annotations[dsl.annotation] = string(valueBytes) - - _, err = dsl.client.AppsV1().DaemonSets(dsl.namespace).Update(context.TODO(), ds, metav1.UpdateOptions{}) - if err != nil { - if se, ok := err.(*errors.StatusError); ok && se.ErrStatus.Reason == metav1.StatusReasonConflict { - // Something else updated the resource between us reading and writing - try again soon - time.Sleep(time.Second) - continue - } else { - return false, "", err - } - } - return true, dsl.nodeID, nil - } -} - -func (dsl *DaemonSetLock) Test(metadata interface{}) (holding bool, err error) { - ds, err := dsl.client.AppsV1().DaemonSets(dsl.namespace).Get(context.TODO(), dsl.name, metav1.GetOptions{}) - if err != nil { - return false, err - } - - valueString, exists := ds.ObjectMeta.Annotations[dsl.annotation] - if exists { - value := lockAnnotationValue{Metadata: metadata} - if err := json.Unmarshal([]byte(valueString), &value); err != nil { - return false, err - } - return value.NodeID == dsl.nodeID, nil - } - - return false, nil -} - -func (dsl *DaemonSetLock) Release() error { - for { - ds, err := dsl.client.AppsV1().DaemonSets(dsl.namespace).Get(context.TODO(), dsl.name, metav1.GetOptions{}) - if err != nil { - return err - } - - valueString, exists := ds.ObjectMeta.Annotations[dsl.annotation] - if exists { - value := lockAnnotationValue{} - if err := json.Unmarshal([]byte(valueString), &value); err != nil { - return err - } - if value.NodeID != dsl.nodeID { - return fmt.Errorf("Not lock holder: %v", value.NodeID) - } - } else { - return fmt.Errorf("Lock not held") - } - - delete(ds.ObjectMeta.Annotations, dsl.annotation) - - _, err = dsl.client.AppsV1().DaemonSets(dsl.namespace).Update(context.TODO(), ds, metav1.UpdateOptions{}) - if err != nil { - if se, ok := err.(*errors.StatusError); ok && se.ErrStatus.Reason == metav1.StatusReasonConflict { - // Something else updated the resource between us reading and writing - try again soon - time.Sleep(time.Second) - continue - } else { - return err - } - } - return nil - } -} diff --git a/pkg/delaytick/delaytick.go b/pkg/delaytick/delaytick.go deleted file mode 100644 index 4673bb9..0000000 --- a/pkg/delaytick/delaytick.go +++ /dev/null @@ -1,22 +0,0 @@ -package delaytick - -import ( - "math/rand" - "time" -) - -// Tick regularly after an initial delay randomly distributed between d/2 and d + d/2 -func New(s rand.Source, d time.Duration) <-chan time.Time { - c := make(chan time.Time) - - go func() { - random := rand.New(s) - time.Sleep(time.Duration(float64(d)/2 + float64(d)*random.Float64())) - c <- time.Now() - for t := range time.Tick(d) { - c <- t - } - }() - - return c -} diff --git a/pkg/notifications/slack/slack.go b/pkg/notifications/slack/slack.go deleted file mode 100644 index 30ac4c5..0000000 --- a/pkg/notifications/slack/slack.go +++ /dev/null @@ -1,52 +0,0 @@ -package slack - -import ( - "bytes" - "encoding/json" - "fmt" - "net/http" - "time" -) - -var ( - httpClient = &http.Client{Timeout: 5 * time.Second} -) - -type body struct { - Text string `json:"text,omitempty"` - Username string `json:"username,omitempty"` - Channel string `json:"channel,omitempty"` -} - -func notify(hookURL, username, channel, message string) error { - msg := body{ - Text: message, - Username: username, - Channel: channel, - } - - var buf bytes.Buffer - if err := json.NewEncoder(&buf).Encode(&msg); err != nil { - return err - } - - resp, err := httpClient.Post(hookURL, "application/json", &buf) - if err != nil { - return err - } - defer resp.Body.Close() - - if resp.StatusCode < 200 || resp.StatusCode >= 300 { - return fmt.Errorf(resp.Status) - } - - return nil -} - -func NotifyDrain(hookURL, username, channel, nodeID string) error { - return notify(hookURL, username, channel, fmt.Sprintf("Draining node %s", nodeID)) -} - -func NotifyReboot(hookURL, username, channel, nodeID string) error { - return notify(hookURL, username, channel, fmt.Sprintf("Rebooting node %s", nodeID)) -} diff --git a/pkg/timewindow/days.go b/pkg/timewindow/days.go deleted file mode 100644 index 2635c84..0000000 --- a/pkg/timewindow/days.go +++ /dev/null @@ -1,91 +0,0 @@ -package timewindow - -import ( - "fmt" - "strconv" - "strings" - "time" -) - -var EveryDay = []string{"su", "mo", "tu", "we", "th", "fr", "sa"} - -// dayStrings maps day strings to time.Weekdays -var dayStrings = map[string]time.Weekday{ - "su": time.Sunday, - "sun": time.Sunday, - "sunday": time.Sunday, - "mo": time.Monday, - "mon": time.Monday, - "monday": time.Monday, - "tu": time.Tuesday, - "tue": time.Tuesday, - "tuesday": time.Tuesday, - "we": time.Wednesday, - "wed": time.Wednesday, - "wednesday": time.Wednesday, - "th": time.Thursday, - "thu": time.Thursday, - "thursday": time.Thursday, - "fr": time.Friday, - "fri": time.Friday, - "friday": time.Friday, - "sa": time.Saturday, - "sat": time.Saturday, - "saturday": time.Saturday, -} - -type weekdays uint32 - -// parseWeekdays creates a set of weekdays from a string slice -func parseWeekdays(days []string) (weekdays, error) { - var result uint32 - for _, day := range days { - if len(day) == 0 { - continue - } - - weekday, err := parseWeekday(day) - if err != nil { - return weekdays(0), err - } - - result |= 1 << uint32(weekday) - } - - return weekdays(result), nil -} - -// Contains returns true if the specified weekday is a member of this set. -func (w weekdays) Contains(day time.Weekday) bool { - return uint32(w)&(1<= 0 && n < 7 { - return time.Weekday(n), nil - } else { - return time.Sunday, fmt.Errorf("Invalid weekday, number out of range: %s", day) - } - } - - if weekday, ok := dayStrings[strings.ToLower(day)]; ok { - return weekday, nil - } else { - return time.Sunday, fmt.Errorf("Invalid weekday: %s", day) - } -} diff --git a/pkg/timewindow/days_test.go b/pkg/timewindow/days_test.go deleted file mode 100644 index 8f5f00a..0000000 --- a/pkg/timewindow/days_test.go +++ /dev/null @@ -1,46 +0,0 @@ -package timewindow - -import ( - "strings" - "testing" -) - -func TestParseWeekdays(t *testing.T) { - tests := []struct { - input string - result string - }{ - {"0,4", "Sun---------Thu------"}, - {"su,mo,tu", "SunMonTue------------"}, - {"sunday,tu,thu", "Sun---Tue---Thu------"}, - {"THURSDAY", "------------Thu------"}, - {"we,WED,WeDnEsDaY", "---------Wed---------"}, - {"", "---------------------"}, - {",,,", "---------------------"}, - } - - for _, tst := range tests { - res, err := parseWeekdays(strings.Split(tst.input, ",")) - if err != nil { - t.Errorf("Received error for input %s: %v", tst.input, err) - } else if res.String() != tst.result { - t.Errorf("Test %s: Expected %s got %s", tst.input, tst.result, res.String()) - } - } -} - -func TestParseWeekdaysErrors(t *testing.T) { - tests := []string{ - "15", - "-8", - "8", - "mon,tue,wed,fridayyyy", - } - - for _, tst := range tests { - _, err := parseWeekdays(strings.Split(tst, ",")) - if err == nil { - t.Errorf("Expected to receive error for input %s", tst) - } - } -} diff --git a/pkg/timewindow/timewindow.go b/pkg/timewindow/timewindow.go deleted file mode 100644 index 6ac1a0e..0000000 --- a/pkg/timewindow/timewindow.go +++ /dev/null @@ -1,68 +0,0 @@ -package timewindow - -import ( - "fmt" - "time" -) - -// TimeWindow specifies a schedule of days and times. -type TimeWindow struct { - days weekdays - location *time.Location - startTime time.Time - endTime time.Time -} - -// New creates a TimeWindow instance based on string inputs specifying a schedule. -func New(days []string, startTime, endTime, location string) (*TimeWindow, error) { - tw := &TimeWindow{} - - var err error - if tw.days, err = parseWeekdays(days); err != nil { - return nil, err - } - - if tw.location, err = time.LoadLocation(location); err != nil { - return nil, err - } - - if tw.startTime, err = parseTime(startTime, tw.location); err != nil { - return nil, err - } - - if tw.endTime, err = parseTime(endTime, tw.location); err != nil { - return nil, err - } - - return tw, nil -} - -// Contains determines whether the specified time is within this time window. -func (tw *TimeWindow) Contains(t time.Time) bool { - loctime := t.In(tw.location) - if !tw.days.Contains(loctime.Weekday()) { - return false - } - - start := time.Date(loctime.Year(), loctime.Month(), loctime.Day(), tw.startTime.Hour(), tw.startTime.Minute(), tw.startTime.Second(), 0, tw.location) - end := time.Date(loctime.Year(), loctime.Month(), loctime.Day(), tw.endTime.Hour(), tw.endTime.Minute(), tw.endTime.Second(), 1e9-1, tw.location) - - return (loctime.After(start) || loctime.Equal(start)) && (loctime.Before(end) || loctime.Equal(end)) -} - -// String returns a string representation of this time window. -func (tw *TimeWindow) String() string { - return fmt.Sprintf("%s between %02d:%02d and %02d:%02d %s", tw.days.String(), tw.startTime.Hour(), tw.startTime.Minute(), tw.endTime.Hour(), tw.endTime.Minute(), tw.location.String()) -} - -// parseTime tries to parse a time with several formats. -func parseTime(s string, loc *time.Location) (time.Time, error) { - fmts := []string{"15:04", "15:04:05", "03:04pm", "15", "03pm", "3pm"} - for _, f := range fmts { - if t, err := time.ParseInLocation(f, s, loc); err == nil { - return t, nil - } - } - - return time.Now(), fmt.Errorf("Invalid time format: %s", s) -} diff --git a/pkg/timewindow/timewindow_test.go b/pkg/timewindow/timewindow_test.go deleted file mode 100644 index a629a45..0000000 --- a/pkg/timewindow/timewindow_test.go +++ /dev/null @@ -1,60 +0,0 @@ -package timewindow - -import ( - "strings" - "testing" - "time" -) - -func TestTimeWindows(t *testing.T) { - type testcase struct { - time string - result bool - } - - tests := []struct { - days string - start string - end string - loc string - cases []testcase - }{ - {"mon,tue,wed,thu,fri", "9am", "5pm", "America/Los_Angeles", []testcase{ - {"2019/04/04 00:49 PDT", false}, - {"2019/04/05 08:59 PDT", false}, - {"2019/04/05 9:01 PDT", true}, - {"2019/03/31 10:00 PDT", false}, - {"2019/04/04 12:00 PDT", true}, - {"2019/04/04 11:59 UTC", false}, - }}, - {"mon,we,fri", "10:01", "11:30am", "America/Los_Angeles", []testcase{ - {"2019/04/05 10:30 PDT", true}, - {"2019/04/06 10:30 PDT", false}, - {"2019/04/07 10:30 PDT", false}, - {"2019/04/08 10:30 PDT", true}, - {"2019/04/09 10:30 PDT", false}, - {"2019/04/10 10:30 PDT", true}, - {"2019/04/11 10:30 PDT", false}, - }}, - {"mo,tu,we,th,fr", "00:00", "23:59:59", "UTC", []testcase{ - {"2019/04/18 00:00 UTC", true}, - {"2019/04/18 23:59 UTC", true}, - }}, - } - - for i, tst := range tests { - tw, err := New(strings.Split(tst.days, ","), tst.start, tst.end, tst.loc) - if err != nil { - t.Errorf("Test [%d] failed to create TimeWindow: %v", i, err) - } - - for _, cas := range tst.cases { - tm, err := time.ParseInLocation("2006/01/02 15:04 MST", cas.time, tw.location) - if err != nil { - t.Errorf("Failed to parse time \"%s\": %v", cas.time, err) - } else if cas.result != tw.Contains(tm) { - t.Errorf("(%s) contains (%s) didn't match expected result of %v", tw.String(), cas.time, cas.result) - } - } - } -}