├── .dockerignore ├── .gitignore ├── TODO.md ├── Dockerfile ├── Cargo.toml ├── src ├── strategy │ ├── mod.rs │ └── bang_bang.rs ├── error.rs ├── kubernetes │ ├── common.rs │ ├── mod.rs │ ├── deployment.rs │ ├── statefulset.rs │ └── replicaset.rs ├── resource.rs ├── metrics.rs └── main.rs ├── README.md ├── manifest.yaml ├── LICENSE └── Cargo.lock /.dockerignore: -------------------------------------------------------------------------------- 1 | /target 2 | /.idea -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | .idea/ 4 | *.iml -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # Pangolin TODO 2 | 3 | * Respect Prometheus annotations so we can pull metrics on different ports / paths. 4 | * Implement a proper Prometheus metrics parser. 5 | * Expose a prometheus metrics port on Pangolin itself. 6 | * What happens when two or more autoscalers match the same resource? Collision detection? 7 | * Put together an integration test framework for verifying autoscaler behavior. 8 | * Unit test coverage for `main.rs`?. 9 | * Put together a CI/CD pipeline. 10 | * Code contribution guidelines etc. 11 | * Implement tunable PID controller. -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # We have to cross compile the musl linked binary on Debian due to Rust proc macros 2 | # requiring dynamic library support which isn't available on the Alpine platform. 3 | FROM rust:1.41-buster as builder 4 | 5 | WORKDIR /usr/src/pangolin 6 | 7 | RUN rustup target add x86_64-unknown-linux-musl \ 8 | && apt-get update \ 9 | && apt-get install -y \ 10 | build-essential \ 11 | musl-dev \ 12 | musl-tools 13 | 14 | COPY Cargo* ./ 15 | COPY src/ ./src/ 16 | 17 | RUN cargo install --target x86_64-unknown-linux-musl --path . 18 | 19 | FROM alpine:3.11.3 20 | 21 | ENV LOG_LEVEL=Info 22 | 23 | COPY --from=builder /usr/local/cargo/bin/pangolin /usr/local/bin/pangolin 24 | 25 | CMD ["sh", "-c", "pangolin --log-level=${LOG_LEVEL}"] -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pangolin" 3 | version = "0.1.0" 4 | authors = ["Damian Peckett "] 5 | description = "An enhanced Horizontal Pod Autoscaler for Kubernetes." 6 | repository = "https://github.com/dpeckett/pangolin" 7 | license = "Apache-2.0" 8 | edition = "2018" 9 | 10 | [dependencies] 11 | async-trait = "0.1.24" 12 | chrono = "0.4.10" 13 | clap = "2.33.0" 14 | enum_dispatch = "0.2.1" 15 | futures = "0.3.1" 16 | hyper = "0.13.2" 17 | k8s-openapi = { version = "0.7.1", default-features = false, features = ["v1_17"] } 18 | kube = { version = "0.25.0", features = ["openapi"]} 19 | # To support the alpine linux target we need to build openssl from source. 20 | openssl = { version = "0.10.28", features = ["vendored"] } 21 | reqwest = "0.10.1" 22 | serde_json = "1.0.48" 23 | serde = { version = "1.0.104", features = ["derive"] } 24 | slog = "2.5.2" 25 | slog-json = "2.3.0" 26 | stream-cancel = "0.5.2" 27 | snafu = "0.6.2" 28 | tokio = { version = "0.2.11", features = ["full"] } 29 | 30 | [dev-dependencies] 31 | slog-term = "2.5.0" -------------------------------------------------------------------------------- /src/strategy/mod.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Damian Peckett 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | use crate::strategy::bang_bang::BangBangAutoScalerStrategy; 18 | use enum_dispatch::enum_dispatch; 19 | 20 | /// Bang-bang autoscaling strategy implementation. 21 | pub mod bang_bang; 22 | 23 | /// Autoscaling strategies / control algorithms. 24 | #[enum_dispatch] 25 | #[derive(Clone, Debug)] 26 | pub enum AutoScalerStrategy { 27 | BangBang(BangBangAutoScalerStrategy), 28 | } 29 | 30 | /// Autoscaling strtategy trait. 31 | #[enum_dispatch(AutoScalerStrategy)] 32 | pub trait AutoScalerStrategyTrait { 33 | /// What is the next desired state? Return the delta in terms of the number of replicas. 34 | fn evaluate(&self, replicas: u32, value: f64) -> Option; 35 | } 36 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Damian Peckett 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | use snafu::Snafu; 18 | 19 | /// Pangolin error types. 20 | #[derive(Debug, Snafu)] 21 | #[snafu(visibility = "pub(crate)")] 22 | pub enum Error { 23 | /// General HTTP client errors. 24 | #[snafu(display("http client error: {}", source))] 25 | HttpClient { source: reqwest::Error }, 26 | 27 | /// JSON serialization errors. 28 | #[snafu(display("json serialization error: {}", source))] 29 | JsonSerialization { source: serde_json::Error }, 30 | 31 | /// Kubernetes API related errors. 32 | #[snafu(display("kubernetes error: {}", source))] 33 | Kube { source: kube::Error }, 34 | 35 | /// Kubernetes specification errors. 36 | #[snafu(display("kubernetes spec is missing fields"))] 37 | KubeSpec {}, 38 | } 39 | -------------------------------------------------------------------------------- /src/strategy/bang_bang.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Damian Peckett 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | use crate::resource::AutoScalerBangBangStrategyConfiguration; 18 | use crate::strategy::AutoScalerStrategyTrait; 19 | 20 | /// Implementation of a bang-bang controller. 21 | #[derive(Clone, Debug)] 22 | pub struct BangBangAutoScalerStrategy { 23 | configuration: AutoScalerBangBangStrategyConfiguration, 24 | } 25 | 26 | impl BangBangAutoScalerStrategy { 27 | pub fn new(configuration: AutoScalerBangBangStrategyConfiguration) -> Self { 28 | Self { configuration } 29 | } 30 | } 31 | 32 | impl AutoScalerStrategyTrait for BangBangAutoScalerStrategy { 33 | fn evaluate(&self, _replicas: u32, value: f64) -> Option { 34 | if value <= self.configuration.lower { 35 | // Below the configured lower threshold, scale down one replica. 36 | Some(-1) 37 | } else if value >= self.configuration.upper { 38 | // Above the configured upper threshold, scale up one replica. 39 | Some(1) 40 | } else { 41 | None 42 | } 43 | } 44 | } 45 | 46 | #[cfg(test)] 47 | mod tests { 48 | use super::*; 49 | 50 | #[tokio::test] 51 | async fn test_bang_bang_strategy() { 52 | let strategy = BangBangAutoScalerStrategy::new(AutoScalerBangBangStrategyConfiguration { 53 | lower: 10.0, 54 | upper: 30.0, 55 | }); 56 | 57 | assert_eq!(strategy.evaluate(1, 32.0).unwrap(), 1); 58 | assert!(strategy.evaluate(1, 20.0).is_none()); 59 | assert_eq!(strategy.evaluate(1, 8.0).unwrap(), -1); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/kubernetes/common.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Damian Peckett 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | use crate::error::*; 18 | use kube::api::{Api, ListParams}; 19 | use kube::client::APIClient; 20 | use snafu::ResultExt; 21 | use std::collections::BTreeMap; 22 | 23 | /// Retrieve all the pod ips associated with a deployment. 24 | pub(crate) async fn get_running_pod_ips( 25 | kube_client: APIClient, 26 | namespace: &str, 27 | match_labels: &BTreeMap, 28 | ) -> Result, Error> { 29 | let label_selector = Some(build_label_selector(match_labels)); 30 | 31 | // Retrieve the list of pods matching the label selector. 32 | let pods = Api::v1Pod(kube_client) 33 | .within(namespace) 34 | .list(&ListParams { 35 | label_selector, 36 | ..Default::default() 37 | }) 38 | .await 39 | .context(Kube {})?; 40 | 41 | // Extract the address from each pod's metadata. 42 | let mut pod_ips: Vec = Vec::new(); 43 | for pod in pods { 44 | if let Some(status) = &pod.status { 45 | if let Some(phase) = &status.phase { 46 | if !phase.eq_ignore_ascii_case("Running") { 47 | continue; 48 | } 49 | } 50 | if let Some(pod_ip) = &status.pod_ip { 51 | pod_ips.push(pod_ip.clone()); 52 | } 53 | } 54 | } 55 | 56 | Ok(pod_ips) 57 | } 58 | 59 | /// Convert a matchLabels map into a list of labels for the kubernetes api. 60 | pub(crate) fn build_label_selector(match_labels: &BTreeMap) -> String { 61 | match_labels 62 | .iter() 63 | .fold(String::new(), |mut labels, (name, value)| { 64 | if !labels.is_empty() { 65 | labels.push(','); 66 | } 67 | labels.push_str(&format!("{}={}", name, value)); 68 | labels 69 | }) 70 | } 71 | -------------------------------------------------------------------------------- /src/kubernetes/mod.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Damian Peckett 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | use crate::error::*; 18 | use crate::kubernetes::deployment::{KubernetesDeploymentObject, KubernetesDeploymentResource}; 19 | use crate::kubernetes::replicaset::{KubernetesReplicaSetObject, KubernetesReplicaSetResource}; 20 | use crate::kubernetes::statefulset::{KubernetesStatefulSetObject, KubernetesStatefulSetResource}; 21 | use async_trait::async_trait; 22 | use chrono::{DateTime, Utc}; 23 | use enum_dispatch::enum_dispatch; 24 | 25 | /// Private shared functionality 26 | mod common; 27 | /// Kubernetes Deployment trait implementations. 28 | pub mod deployment; 29 | /// Kubernetes ReplicaSet trait implementations. 30 | pub mod replicaset; 31 | /// Kubernetes StatefulSet trait implementations. 32 | pub mod statefulset; 33 | 34 | /// Kubernetes resource families. 35 | #[enum_dispatch] 36 | pub enum KubernetesResource { 37 | /// A list of apps/v1 Deployment resources. 38 | Deployment(KubernetesDeploymentResource), 39 | /// A list of apps/v1 ReplicaSet resources. 40 | ReplicaSet(KubernetesReplicaSetResource), 41 | /// A list of apps/v1 StatefulSet resources. 42 | StatefulSet(KubernetesStatefulSetResource), 43 | } 44 | 45 | /// Kubernetes objects, eg deployments etc. 46 | #[enum_dispatch] 47 | pub enum KubernetesObject { 48 | /// An apps/v1 Deployment object. 49 | Deployment(KubernetesDeploymentObject), 50 | /// An apps/v1 ReplicaSet object. 51 | ReplicaSet(KubernetesReplicaSetObject), 52 | /// An apps/v1 StatefulSet object. 53 | StatefulSet(KubernetesStatefulSetObject), 54 | } 55 | 56 | #[async_trait] 57 | #[enum_dispatch(KubernetesResource)] 58 | pub trait KubernetesResourceTrait { 59 | /// Retrieve a list of matching objects from the k8s api. 60 | async fn list(&self) -> Result, Error>; 61 | } 62 | 63 | #[async_trait] 64 | #[enum_dispatch(KubernetesObject)] 65 | pub trait KubernetesObjectTrait { 66 | /// The namespace and name of the object. 67 | fn namespace_and_name(&self) -> (String, String); 68 | /// The last time the object was modified by the autoscaler. 69 | async fn last_modified(&self) -> Result>, Error>; 70 | /// The current number of replicas. 71 | async fn replicas(&self) -> Result; 72 | /// The pod ips of every running pod belonging to this object. 73 | async fn pod_ips(&self) -> Result, Error>; 74 | /// Update the number of replicas associated with this object. 75 | async fn scale(&self, replicas: u32) -> Result<(), Error>; 76 | } 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pangolin 2 | 3 | An enhanced Horizontal Pod Autoscaler for Kubernetes. Pangolin scales deployments based on their Prometheus metrics, 4 | using a variety of highly configurable control strategies. 5 | 6 | ## Why? 7 | 8 | * Kubernetes HPA only supports a single scaling strategy that is not applicable to all use-cases. 9 | * Kubernetes HPA has a limited set of configuration options, which can make it difficult to tune. 10 | * Kubernetes HPA has limited support for scaling on custom metrics. 11 | * Existing third party Kubernetes autoscaling tools are limited and generally tailored toward niche usecases. 12 | * None of the existing third party Kubernetes autoscaling tools have good test coverage or are battle tested. 13 | * None of the existing third party Kubernetes autoscaling tools appear to based on a solid control theory foundations. 14 | 15 | There's a few major sources of potential risk when it comes to autoscaling. Many of these sources of risk fall within 16 | the scope of the application providing control authority. The hypothesis behind Pangolin is that more robust control 17 | authority will lead to a significant reduction in autoscaling risk. Eg. loop stability, resilient monitoring, 18 | measurement latency, outlier detection etc. 19 | 20 | ## Why Rust? 21 | 22 | Because it's a fantastic systems programming language and we need to see more of it in the container space. 23 | 24 | ## Requirements 25 | 26 | * Kubernetes v1.17.0 cluster with RBAC enabled. 27 | * The target Deployment / Pods have Prometheus metrics available on port 9090. 28 | 29 | ## Installation 30 | 31 | The latest stable release of Pangolin can be installed from GitHub: 32 | 33 | ```console 34 | foo@bar:~$ kubectl apply -f https://raw.githubusercontent.com/dpeckett/pangolin/master/manifest.yaml 35 | ``` 36 | 37 | ## Usage 38 | 39 | Simply create an `AutoScaler` object in the same namespace as the deployment you wish to be autoscaled: 40 | 41 | ```yaml 42 | apiVersion: "pangolinscaler.com/v1alpha1" 43 | kind: AutoScaler 44 | metadata: 45 | name: my-new-autoscaler 46 | spec: 47 | # Autoscaling strategy / control algorithm. 48 | strategy: BangBang 49 | # Kubernetes resource kind of the autoscaling target. 50 | kind: Deployment 51 | # Selector for the autoscaling target. 52 | selector: 53 | matchLabels: 54 | app: my-application 55 | metric: 56 | # Prometheus metric for autoscaling decisions. 57 | name: response_latency_ms 58 | # How often to pull metrics (seconds). 59 | interval: 10 60 | # How often to evaluate the autoscaling strategy (seconds). 61 | interval: 60 62 | # Any autoscaling limits, eg the number of replicas. 63 | limits: 64 | replicas: 65 | min: 1 66 | max: 5 67 | # Bang-bang controller configuration. 68 | bangBang: 69 | # Bang-bang controller lower threshold. 70 | lower: 100.0 71 | # Bang-bang controller upper threshold. 72 | upper: 250.0 73 | ``` 74 | 75 | For more details about the AutoScaler resource look at `manifest.yaml` and `src/resource.rs` in this repository. 76 | 77 | ## Building 78 | 79 | ### Locally 80 | 81 | You will need the latest Rust stable toolchain installed on your machine. Refer to [rustup](https://rustup.rs/) for 82 | more details. 83 | 84 | ```console 85 | foo@bar:~$ cargo build --release 86 | ``` 87 | 88 | ### Docker 89 | 90 | To build an Alpine based Docker image from source: 91 | 92 | ```console 93 | foo@bar:~$ docker build -t pangolinscaler/pangolin:v0.1.0 . 94 | ``` 95 | 96 | ## Control Strategies 97 | 98 | Currently Pangolin only supports bang-bang control, however additional strategies are under development. 99 | 100 | - [x] Bang-bang control. 101 | - [ ] PID control. -------------------------------------------------------------------------------- /src/resource.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Damian Peckett 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | use kube::api::{Object, Void}; 18 | use serde::{Deserialize, Serialize}; 19 | use std::collections::BTreeMap; 20 | 21 | pub type AutoScaler = Object; 22 | 23 | /// Prefix to use for all annotations. 24 | pub const ANNOTATION_BASE: &str = "pangolinscaler.com"; 25 | 26 | /// Kubernetes resource type to scale. 27 | #[derive(Clone, Debug, Deserialize, Serialize)] 28 | pub enum AutoScalerKubernetesResourceKind { 29 | Deployment, 30 | ReplicaSet, 31 | StatefulSet, 32 | } 33 | 34 | /// Strategy to use for autoscaling. 35 | #[derive(Clone, Debug, Deserialize, Serialize)] 36 | pub enum AutoScalerStrategyKind { 37 | /// A bang-bang control strategy. 38 | BangBang, 39 | } 40 | 41 | /// Deployment selector. 42 | #[derive(Clone, Debug, Deserialize, Serialize)] 43 | pub struct AutoScalerSelector { 44 | /// Autoscaling deployments matching the supplied labels. 45 | #[serde(rename = "matchLabels")] 46 | pub match_labels: BTreeMap, 47 | } 48 | 49 | /// Prometheus metrics configuration. 50 | #[derive(Clone, Debug, Deserialize, Serialize)] 51 | pub struct AutoScalerMetric { 52 | /// Prometheus metric for autoscaling decisions. 53 | pub name: String, 54 | /// How often to pull Prometheus metrics (seconds). 55 | pub interval: u32, 56 | } 57 | 58 | /// Maximum and minimum number of replicas configuration. 59 | #[derive(Clone, Debug, Deserialize, Serialize)] 60 | pub struct AutoScalerReplicaLimit { 61 | /// Minimum allowed number of replicas. 62 | pub min: u32, 63 | /// Maximum allowed number of replicas. 64 | pub max: u32, 65 | } 66 | 67 | /// Resource limit configuration. 68 | #[derive(Clone, Debug, Deserialize, Serialize)] 69 | pub struct AutoScalerLimits { 70 | /// Maximum and minimum number of replicas. 71 | pub replicas: Option, 72 | } 73 | 74 | /// Bang-bang controller specific configuration. 75 | #[derive(Clone, Debug, Deserialize, Serialize)] 76 | pub struct AutoScalerBangBangStrategyConfiguration { 77 | /// Bang-bang controller lower threshold. 78 | pub lower: f64, 79 | /// Bang-bang controller upper threshold. 80 | pub upper: f64, 81 | } 82 | 83 | /// Pangolin AutoScaler resource specification. 84 | #[derive(Clone, Debug, Deserialize, Serialize)] 85 | pub struct AutoScalerSpec { 86 | /// Autoscaling strategy / control algorithm. 87 | pub strategy: AutoScalerStrategyKind, 88 | /// Kubernetes resource kind of the autoscaling target. 89 | pub kind: AutoScalerKubernetesResourceKind, 90 | /// Selector for the autoscaling target. 91 | pub selector: AutoScalerSelector, 92 | /// Prometheus metrics configuration. 93 | pub metric: AutoScalerMetric, 94 | /// How often to evaluate the autoscaling strategy (seconds). 95 | pub interval: u32, 96 | /// Any autoscaling limits, eg the number of replicas. 97 | pub limits: Option, 98 | /// Bang-bang controller configuration. 99 | #[serde(rename = "bangBang")] 100 | pub bang_bang: Option, 101 | } 102 | -------------------------------------------------------------------------------- /manifest.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | name: autoscalers.pangolinscaler.com 6 | spec: 7 | group: pangolinscaler.com 8 | versions: 9 | - name: v1alpha1 10 | served: true 11 | storage: true 12 | schema: 13 | openAPIV3Schema: 14 | type: object 15 | required: 16 | - spec 17 | properties: 18 | spec: 19 | type: object 20 | required: 21 | - strategy 22 | - kind 23 | - selector 24 | - metric 25 | - interval 26 | properties: 27 | strategy: 28 | type: string 29 | pattern: '^(BangBang)$' 30 | kind: 31 | type: string 32 | pattern: '^(Deployment|ReplicaSet|StatefulSet)$' 33 | default: Deployment 34 | selector: 35 | type: object 36 | required: 37 | - matchLabels 38 | properties: 39 | matchLabels: 40 | type: object 41 | additionalProperties: 42 | type: string 43 | metric: 44 | type: object 45 | required: 46 | - name 47 | properties: 48 | name: 49 | type: string 50 | interval: 51 | type: integer 52 | minimum: 5 53 | maximum: 900 54 | default: 10 55 | interval: 56 | type: integer 57 | minimum: 10 58 | maximum: 900 59 | default: 30 60 | limits: 61 | type: object 62 | properties: 63 | replicas: 64 | type: object 65 | properties: 66 | min: 67 | type: integer 68 | minimum: 1 69 | default: 1 70 | max: 71 | type: integer 72 | minimum: 1 73 | default: 10 74 | bangBang: 75 | type: object 76 | required: 77 | - lower 78 | - upper 79 | properties: 80 | lower: 81 | type: number 82 | upper: 83 | type: number 84 | scope: Namespaced 85 | names: 86 | plural: autoscalers 87 | singular: autoscaler 88 | kind: AutoScaler 89 | 90 | --- 91 | apiVersion: v1 92 | kind: Namespace 93 | metadata: 94 | name: autoscaler 95 | 96 | --- 97 | apiVersion: v1 98 | kind: ServiceAccount 99 | metadata: 100 | name: pangolin 101 | namespace: autoscaler 102 | 103 | --- 104 | apiVersion: rbac.authorization.k8s.io/v1 105 | kind: ClusterRole 106 | metadata: 107 | name: pangolin-role 108 | rules: 109 | - apiGroups: 110 | - pangolinscaler.com 111 | resources: 112 | - autoscalers 113 | verbs: 114 | - get 115 | - watch 116 | - list 117 | - apiGroups: 118 | - extensions 119 | - apps 120 | resources: 121 | - deployments 122 | - replicasets 123 | - statefulsets 124 | verbs: 125 | - get 126 | - list 127 | - update 128 | - patch 129 | - apiGroups: [""] 130 | resources: 131 | - pods 132 | verbs: 133 | - get 134 | - list 135 | 136 | --- 137 | apiVersion: rbac.authorization.k8s.io/v1 138 | kind: ClusterRoleBinding 139 | metadata: 140 | name: pangolin-role-binding 141 | subjects: 142 | - kind: ServiceAccount 143 | name: pangolin 144 | namespace: autoscaler 145 | roleRef: 146 | kind: ClusterRole 147 | name: pangolin-role 148 | apiGroup: rbac.authorization.k8s.io 149 | 150 | --- 151 | apiVersion: apps/v1 152 | kind: ReplicaSet 153 | metadata: 154 | name: pangolin 155 | namespace: autoscaler 156 | labels: 157 | controller: pangolin 158 | spec: 159 | replicas: 1 160 | selector: 161 | matchLabels: 162 | controller: pangolin 163 | template: 164 | metadata: 165 | labels: 166 | controller: pangolin 167 | spec: 168 | serviceAccountName: pangolin 169 | automountServiceAccountToken: true 170 | priorityClassName: system-cluster-critical 171 | containers: 172 | - name: pangolin 173 | image: pangolinscaler/pangolin:v0.1.0 -------------------------------------------------------------------------------- /src/kubernetes/deployment.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Damian Peckett 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | use crate::error::*; 18 | use crate::kubernetes::common::{build_label_selector, get_running_pod_ips}; 19 | use crate::kubernetes::{KubernetesObject, KubernetesObjectTrait, KubernetesResourceTrait}; 20 | use crate::resource::ANNOTATION_BASE; 21 | use async_trait::async_trait; 22 | use chrono::prelude::*; 23 | use k8s_openapi::api::apps::v1::DeploymentSpec; 24 | use kube::api::{Api, ObjectMeta}; 25 | use kube::api::{ListParams, PatchParams}; 26 | use kube::client::APIClient; 27 | use serde_json::json; 28 | use snafu::{OptionExt, ResultExt}; 29 | use std::collections::BTreeMap; 30 | 31 | /// Kubernetes Deployment resource kind related functions. 32 | pub struct KubernetesDeploymentResource { 33 | kube_config: kube::config::Configuration, 34 | namespace: String, 35 | label_selector: String, 36 | } 37 | 38 | impl KubernetesDeploymentResource { 39 | pub fn new( 40 | kube_config: kube::config::Configuration, 41 | namespace: &str, 42 | match_labels: &BTreeMap, 43 | ) -> Self { 44 | Self { 45 | kube_config, 46 | namespace: namespace.into(), 47 | label_selector: build_label_selector(match_labels), 48 | } 49 | } 50 | } 51 | 52 | #[async_trait] 53 | impl KubernetesResourceTrait for KubernetesDeploymentResource { 54 | async fn list(&self) -> Result, Error> { 55 | let kube_client = APIClient::new(self.kube_config.clone()); 56 | // Retrieve the list of Deployment objects matching the label selector. 57 | let deployments = Api::v1Deployment(kube_client) 58 | .within(&self.namespace) 59 | .list(&ListParams { 60 | label_selector: Some(self.label_selector.clone()), 61 | ..Default::default() 62 | }) 63 | .await 64 | .context(Kube {})?; 65 | let mut objects: Vec = Vec::new(); 66 | for deployment in deployments { 67 | objects.push(KubernetesObject::Deployment( 68 | KubernetesDeploymentObject::new( 69 | self.kube_config.clone(), 70 | &self.namespace, 71 | &deployment.metadata, 72 | &deployment.spec, 73 | ), 74 | )) 75 | } 76 | Ok(objects) 77 | } 78 | } 79 | 80 | /// Kubernetes Deployment related functions. 81 | pub struct KubernetesDeploymentObject { 82 | kube_config: kube::config::Configuration, 83 | namespace: String, 84 | metadata: ObjectMeta, 85 | spec: DeploymentSpec, 86 | } 87 | 88 | impl KubernetesDeploymentObject { 89 | pub fn new( 90 | kube_config: kube::config::Configuration, 91 | namespace: &str, 92 | metadata: &ObjectMeta, 93 | spec: &DeploymentSpec, 94 | ) -> Self { 95 | Self { 96 | kube_config, 97 | namespace: namespace.into(), 98 | metadata: metadata.clone(), 99 | spec: spec.clone(), 100 | } 101 | } 102 | } 103 | 104 | #[async_trait] 105 | impl KubernetesObjectTrait for KubernetesDeploymentObject { 106 | fn namespace_and_name(&self) -> (String, String) { 107 | (self.namespace.clone(), self.metadata.name.clone()) 108 | } 109 | 110 | async fn last_modified(&self) -> Result>, Error> { 111 | Ok( 112 | // Retrieve the last modified timestamp from the Deployment's annotations. 113 | if let Some(last_modified_timestamp) = self 114 | .metadata 115 | .annotations 116 | .get(&format!("{}/last_modified", ANNOTATION_BASE)) 117 | { 118 | Some(DateTime::from_utc( 119 | DateTime::::parse_from_rfc3339(last_modified_timestamp) 120 | .unwrap() 121 | .naive_utc(), 122 | Utc, 123 | )) 124 | } else { 125 | None 126 | }, 127 | ) 128 | } 129 | 130 | async fn replicas(&self) -> Result { 131 | self.spec 132 | .replicas 133 | .context(KubeSpec {}) 134 | .map(|replicas| replicas as u32) 135 | } 136 | 137 | async fn pod_ips(&self) -> Result, Error> { 138 | let labels = self 139 | .spec 140 | .template 141 | .metadata 142 | .as_ref() 143 | .context(KubeSpec {})? 144 | .labels 145 | .as_ref() 146 | .context(KubeSpec {})?; 147 | let kube_client = APIClient::new(self.kube_config.clone()); 148 | get_running_pod_ips(kube_client, &self.namespace, labels).await 149 | } 150 | 151 | async fn scale(&self, replicas: u32) -> Result<(), Error> { 152 | let utc_now: DateTime = Utc::now(); 153 | let patch = json!({ 154 | "metadata": { 155 | "annotations": { 156 | format!("{}/last_modified", ANNOTATION_BASE): utc_now.to_rfc3339() 157 | } 158 | }, 159 | "spec": { 160 | "replicas": replicas 161 | } 162 | }); 163 | // Patch (update) the Deployment object. 164 | let patch_params = PatchParams::default(); 165 | let kube_client = APIClient::new(self.kube_config.clone()); 166 | Api::v1Deployment(kube_client) 167 | .within(&self.namespace) 168 | .patch( 169 | &self.metadata.name, 170 | &patch_params, 171 | serde_json::to_vec(&patch).context(JsonSerialization {})?, 172 | ) 173 | .await 174 | .context(Kube {})?; 175 | Ok(()) 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /src/kubernetes/statefulset.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Damian Peckett 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | use crate::error::*; 18 | use crate::kubernetes::common::{build_label_selector, get_running_pod_ips}; 19 | use crate::kubernetes::{KubernetesObject, KubernetesObjectTrait, KubernetesResourceTrait}; 20 | use crate::resource::ANNOTATION_BASE; 21 | use async_trait::async_trait; 22 | use chrono::prelude::*; 23 | use k8s_openapi::api::apps::v1::StatefulSetSpec; 24 | use kube::api::Api; 25 | use kube::api::{ListParams, ObjectMeta, PatchParams}; 26 | use kube::client::APIClient; 27 | use serde_json::json; 28 | use snafu::{OptionExt, ResultExt}; 29 | use std::collections::BTreeMap; 30 | 31 | /// Kubernetes StatefulSet resource kind related functions. 32 | pub struct KubernetesStatefulSetResource { 33 | kube_config: kube::config::Configuration, 34 | namespace: String, 35 | label_selector: String, 36 | } 37 | 38 | impl KubernetesStatefulSetResource { 39 | pub fn new( 40 | kube_config: kube::config::Configuration, 41 | namespace: &str, 42 | match_labels: &BTreeMap, 43 | ) -> Self { 44 | Self { 45 | kube_config, 46 | namespace: namespace.into(), 47 | label_selector: build_label_selector(match_labels), 48 | } 49 | } 50 | } 51 | 52 | #[async_trait] 53 | impl KubernetesResourceTrait for KubernetesStatefulSetResource { 54 | async fn list(&self) -> Result, Error> { 55 | let kube_client = APIClient::new(self.kube_config.clone()); 56 | // Retrieve the list of StatefulSet objects matching the label selector. 57 | let statefulsets = Api::v1StatefulSet(kube_client) 58 | .within(&self.namespace) 59 | .list(&ListParams { 60 | label_selector: Some(self.label_selector.clone()), 61 | ..Default::default() 62 | }) 63 | .await 64 | .context(Kube {})?; 65 | let mut objects: Vec = Vec::new(); 66 | for statefulset in statefulsets { 67 | objects.push(KubernetesObject::StatefulSet( 68 | KubernetesStatefulSetObject::new( 69 | self.kube_config.clone(), 70 | &self.namespace, 71 | &statefulset.metadata, 72 | &statefulset.spec, 73 | ), 74 | )) 75 | } 76 | Ok(objects) 77 | } 78 | } 79 | 80 | /// Kubernetes StatefulSet related functions. 81 | pub struct KubernetesStatefulSetObject { 82 | kube_config: kube::config::Configuration, 83 | namespace: String, 84 | metadata: ObjectMeta, 85 | spec: StatefulSetSpec, 86 | } 87 | 88 | impl KubernetesStatefulSetObject { 89 | pub fn new( 90 | kube_config: kube::config::Configuration, 91 | namespace: &str, 92 | metadata: &ObjectMeta, 93 | spec: &StatefulSetSpec, 94 | ) -> Self { 95 | Self { 96 | kube_config, 97 | namespace: namespace.into(), 98 | metadata: metadata.clone(), 99 | spec: spec.clone(), 100 | } 101 | } 102 | } 103 | 104 | #[async_trait] 105 | impl KubernetesObjectTrait for KubernetesStatefulSetObject { 106 | fn namespace_and_name(&self) -> (String, String) { 107 | (self.namespace.clone(), self.metadata.name.clone()) 108 | } 109 | 110 | async fn last_modified(&self) -> Result>, Error> { 111 | Ok( 112 | // Retrieve the last modified timestamp from the StatefulSet's annotations. 113 | if let Some(last_modified_timestamp) = self 114 | .metadata 115 | .annotations 116 | .get(&format!("{}/last_modified", ANNOTATION_BASE)) 117 | { 118 | Some(DateTime::from_utc( 119 | DateTime::::parse_from_rfc3339(last_modified_timestamp) 120 | .unwrap() 121 | .naive_utc(), 122 | Utc, 123 | )) 124 | } else { 125 | None 126 | }, 127 | ) 128 | } 129 | 130 | async fn replicas(&self) -> Result { 131 | self.spec 132 | .replicas 133 | .context(KubeSpec {}) 134 | .map(|replicas| replicas as u32) 135 | } 136 | 137 | async fn pod_ips(&self) -> Result, Error> { 138 | let labels = self 139 | .spec 140 | .template 141 | .metadata 142 | .as_ref() 143 | .context(KubeSpec {})? 144 | .labels 145 | .as_ref() 146 | .context(KubeSpec {})?; 147 | let kube_client = APIClient::new(self.kube_config.clone()); 148 | get_running_pod_ips(kube_client, &self.namespace, labels).await 149 | } 150 | 151 | async fn scale(&self, replicas: u32) -> Result<(), Error> { 152 | let utc_now: DateTime = Utc::now(); 153 | let patch = json!({ 154 | "metadata": { 155 | "annotations": { 156 | format!("{}/last_modified", ANNOTATION_BASE): utc_now.to_rfc3339() 157 | } 158 | }, 159 | "spec": { 160 | "replicas": replicas 161 | } 162 | }); 163 | // Patch (update) the StatefulSet object. 164 | let patch_params = PatchParams::default(); 165 | let kube_client = APIClient::new(self.kube_config.clone()); 166 | Api::v1StatefulSet(kube_client) 167 | .within(&self.namespace) 168 | .patch( 169 | &self.metadata.name, 170 | &patch_params, 171 | serde_json::to_vec(&patch).context(JsonSerialization {})?, 172 | ) 173 | .await 174 | .context(Kube {})?; 175 | Ok(()) 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /src/kubernetes/replicaset.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Damian Peckett 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | use crate::error::*; 18 | use crate::kubernetes::common::{build_label_selector, get_running_pod_ips}; 19 | use crate::kubernetes::{KubernetesObject, KubernetesObjectTrait, KubernetesResourceTrait}; 20 | use crate::resource::ANNOTATION_BASE; 21 | use async_trait::async_trait; 22 | use chrono::prelude::*; 23 | use k8s_openapi::api::apps::v1::ReplicaSetSpec; 24 | use kube::api::{Api, ObjectMeta}; 25 | use kube::api::{ListParams, PatchParams}; 26 | use kube::client::APIClient; 27 | use serde_json::json; 28 | use snafu::{OptionExt, ResultExt}; 29 | use std::collections::BTreeMap; 30 | 31 | /// Kubernetes ReplicaSet resource kind related functions. 32 | pub struct KubernetesReplicaSetResource { 33 | kube_config: kube::config::Configuration, 34 | namespace: String, 35 | label_selector: String, 36 | } 37 | 38 | impl KubernetesReplicaSetResource { 39 | pub fn new( 40 | kube_config: kube::config::Configuration, 41 | namespace: &str, 42 | match_labels: &BTreeMap, 43 | ) -> Self { 44 | Self { 45 | kube_config, 46 | namespace: namespace.into(), 47 | label_selector: build_label_selector(match_labels), 48 | } 49 | } 50 | } 51 | 52 | #[async_trait] 53 | impl KubernetesResourceTrait for KubernetesReplicaSetResource { 54 | async fn list(&self) -> Result, Error> { 55 | let kube_client = APIClient::new(self.kube_config.clone()); 56 | // Retrieve the list of ReplicaSet objects matching the label selector. 57 | let replicasets = Api::v1ReplicaSet(kube_client) 58 | .within(&self.namespace) 59 | .list(&ListParams { 60 | label_selector: Some(self.label_selector.clone()), 61 | ..Default::default() 62 | }) 63 | .await 64 | .context(Kube {})?; 65 | let mut objects: Vec = Vec::new(); 66 | for replicaset in replicasets { 67 | objects.push(KubernetesObject::ReplicaSet( 68 | KubernetesReplicaSetObject::new( 69 | self.kube_config.clone(), 70 | &self.namespace, 71 | &replicaset.metadata, 72 | &replicaset.spec, 73 | ), 74 | )) 75 | } 76 | Ok(objects) 77 | } 78 | } 79 | 80 | /// Kubernetes ReplicaSet related functions. 81 | pub struct KubernetesReplicaSetObject { 82 | kube_config: kube::config::Configuration, 83 | namespace: String, 84 | metadata: ObjectMeta, 85 | spec: ReplicaSetSpec, 86 | } 87 | 88 | impl KubernetesReplicaSetObject { 89 | pub fn new( 90 | kube_config: kube::config::Configuration, 91 | namespace: &str, 92 | metadata: &ObjectMeta, 93 | spec: &ReplicaSetSpec, 94 | ) -> Self { 95 | Self { 96 | kube_config, 97 | namespace: namespace.into(), 98 | metadata: metadata.clone(), 99 | spec: spec.clone(), 100 | } 101 | } 102 | } 103 | 104 | #[async_trait] 105 | impl KubernetesObjectTrait for KubernetesReplicaSetObject { 106 | fn namespace_and_name(&self) -> (String, String) { 107 | (self.namespace.clone(), self.metadata.name.clone()) 108 | } 109 | 110 | async fn last_modified(&self) -> Result>, Error> { 111 | Ok( 112 | // Retrieve the last modified timestamp from the ReplicaSet's annotations. 113 | if let Some(last_modified_timestamp) = self 114 | .metadata 115 | .annotations 116 | .get(&format!("{}/last_modified", ANNOTATION_BASE)) 117 | { 118 | Some(DateTime::from_utc( 119 | DateTime::::parse_from_rfc3339(last_modified_timestamp) 120 | .unwrap() 121 | .naive_utc(), 122 | Utc, 123 | )) 124 | } else { 125 | None 126 | }, 127 | ) 128 | } 129 | 130 | async fn replicas(&self) -> Result { 131 | self.spec 132 | .replicas 133 | .context(KubeSpec {}) 134 | .map(|replicas| replicas as u32) 135 | } 136 | 137 | async fn pod_ips(&self) -> Result, Error> { 138 | let labels = self 139 | .spec 140 | .template 141 | .as_ref() 142 | .context(KubeSpec {})? 143 | .metadata 144 | .as_ref() 145 | .context(KubeSpec {})? 146 | .labels 147 | .as_ref() 148 | .context(KubeSpec {})?; 149 | let kube_client = APIClient::new(self.kube_config.clone()); 150 | get_running_pod_ips(kube_client, &self.namespace, labels).await 151 | } 152 | 153 | async fn scale(&self, replicas: u32) -> Result<(), Error> { 154 | let utc_now: DateTime = Utc::now(); 155 | let patch = json!({ 156 | "metadata": { 157 | "annotations": { 158 | format!("{}/last_modified", ANNOTATION_BASE): utc_now.to_rfc3339() 159 | } 160 | }, 161 | "spec": { 162 | "replicas": replicas 163 | } 164 | }); 165 | // Patch (update) the ReplicaSet object. 166 | let patch_params = PatchParams::default(); 167 | let kube_client = APIClient::new(self.kube_config.clone()); 168 | Api::v1ReplicaSet(kube_client) 169 | .within(&self.namespace) 170 | .patch( 171 | &self.metadata.name, 172 | &patch_params, 173 | serde_json::to_vec(&patch).context(JsonSerialization {})?, 174 | ) 175 | .await 176 | .context(Kube {})?; 177 | Ok(()) 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/metrics.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Damian Peckett 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | use crate::error::*; 18 | use futures::channel::mpsc::unbounded; 19 | use futures::sink::SinkExt; 20 | use futures::StreamExt; 21 | use slog::{debug, Logger}; 22 | use snafu::ResultExt; 23 | use std::time::Duration; 24 | 25 | /// Retrieve the mean value of a Prometheus metric from a list of pod ips. 26 | pub async fn retrieve_aggregate_metric( 27 | logger: Logger, 28 | pod_ips_and_ports: Vec, 29 | metric_name: &str, 30 | ) -> Result, Error> { 31 | let mut metrics_receiver = { 32 | // Create a channel which will receive the metrics as they are pulled in by the various http client threads. 33 | let (metrics_sender, metrics_receiver) = unbounded::, Error>>(); 34 | for pod_ip_and_port in &pod_ips_and_ports { 35 | let pod_ip_and_port: String = pod_ip_and_port.into(); 36 | let metric: String = metric_name.into(); 37 | let mut metrics_sender = metrics_sender.clone(); 38 | let metrics_logger = logger.clone(); 39 | // Spawn a new coroutine to pull metrics from the supplied pod ip. 40 | // All pod ips are interrogated concurrently. 41 | tokio::spawn(async move { 42 | debug!(metrics_logger, "Querying pod metrics"; 43 | "pod_ip_and_port" => pod_ip_and_port.clone(), 44 | "metric_name" => metric.clone()); 45 | metrics_sender 46 | .send(pull_metric_from_pod(&pod_ip_and_port, &metric).await) 47 | .await 48 | .unwrap(); 49 | }); 50 | } 51 | metrics_receiver 52 | }; 53 | 54 | // As the pulled metrics arrive, collect the valid results. 55 | let mut metric_values: Vec = Vec::new(); 56 | while let Some(metric_result) = metrics_receiver.next().await { 57 | if let Ok(Some(metric_value)) = metric_result { 58 | metric_values.push(metric_value); 59 | } 60 | } 61 | 62 | // Compute the mean metric value across all pods. 63 | Ok(if !metric_values.is_empty() { 64 | let n_values = metric_values.len() as f64; 65 | Some(metric_values.into_iter().sum::() / n_values) 66 | } else { 67 | None 68 | }) 69 | } 70 | 71 | /// Retrieve a Prometheus metric value from a pod. 72 | async fn pull_metric_from_pod( 73 | pod_ip_and_port: &str, 74 | metric_name: &str, 75 | ) -> Result, Error> { 76 | // Construct a new client for pulling metrics. 77 | let metric_uri = format!("http://{}/metrics", pod_ip_and_port); 78 | let metrics_client = reqwest::Client::builder() 79 | .timeout(Duration::from_millis(250)) 80 | .build() 81 | .context(HttpClient {})?; 82 | 83 | // Pull the Prometheus metrics from the pod. 84 | let response = metrics_client 85 | .get(&metric_uri) 86 | .send() 87 | .await 88 | .context(HttpClient {})? 89 | .text() 90 | .await 91 | .context(HttpClient {})?; 92 | 93 | // Very primitive prometheus text mode parser. 94 | for record in response.lines() { 95 | let record = String::from(record); 96 | let mut fields = record.split_whitespace(); 97 | if fields.next().unwrap().eq(metric_name) { 98 | let metric_value: f64 = fields.next().unwrap().parse().unwrap(); 99 | return Ok(Some(metric_value)); 100 | } 101 | } 102 | 103 | Ok(None) 104 | } 105 | 106 | #[cfg(test)] 107 | mod tests { 108 | use super::*; 109 | use futures::channel::oneshot; 110 | use futures::{future, FutureExt}; 111 | use hyper::service::{make_service_fn, service_fn}; 112 | use hyper::{Body, Request, Response, Server}; 113 | use slog::{o, Drain}; 114 | use std::net::{SocketAddr, TcpListener}; 115 | use tokio::time::delay_for; 116 | 117 | #[tokio::test] 118 | async fn test_pull_metric() { 119 | let port = get_available_port(8_000).unwrap(); 120 | let shutdown_sender = spawn_server(port, 220.0); 121 | 122 | delay_for(Duration::from_millis(20)).await; 123 | 124 | let response_latency_ms = 125 | pull_metric_from_pod(&format!("localhost:{}", port), "response_latency_ms") 126 | .await 127 | .unwrap(); 128 | shutdown_sender.send(()).unwrap(); 129 | 130 | assert_eq!(response_latency_ms.unwrap() as i32, 220); 131 | } 132 | 133 | #[tokio::test] 134 | async fn test_retrieve_aggregate_metric() { 135 | let port = get_available_port(8_001).unwrap(); 136 | let second_port = get_available_port(port + 1).unwrap(); 137 | 138 | let shutdown_sender = spawn_server(port, 220.0); 139 | let second_shutdown_sender = spawn_server(second_port, 110.0); 140 | 141 | delay_for(Duration::from_millis(20)).await; 142 | 143 | let pod_ips_and_ports = vec![ 144 | format!("localhost:{}", port), 145 | format!("localhost:{}", second_port), 146 | ]; 147 | let response_latency_ms = 148 | retrieve_aggregate_metric(get_logger(), pod_ips_and_ports, "response_latency_ms") 149 | .await 150 | .unwrap(); 151 | 152 | shutdown_sender.send(()).unwrap(); 153 | second_shutdown_sender.send(()).unwrap(); 154 | 155 | assert_eq!(response_latency_ms.unwrap() as i32, 165); 156 | } 157 | 158 | fn spawn_server(port: u16, response_latency_ms: f64) -> oneshot::Sender<()> { 159 | let (shutdown_sender, shutdown_receiver) = oneshot::channel::<()>(); 160 | tokio::spawn(async move { 161 | let addr: SocketAddr = format!("127.0.0.1:{}", port).parse().unwrap(); 162 | Server::bind(&addr) 163 | .serve(make_service_fn(|_| { 164 | let service = service_fn(move |request| { 165 | serve(request, response_latency_ms).map(Ok::<_, hyper::Error>) 166 | }); 167 | future::ok::<_, hyper::Error>(service) 168 | })) 169 | .with_graceful_shutdown(async { 170 | shutdown_receiver.await.ok(); 171 | }) 172 | .await 173 | .unwrap(); 174 | }); 175 | shutdown_sender 176 | } 177 | 178 | async fn serve(request: Request, response_latency_ms: f64) -> Response { 179 | if request.uri().path().eq("/metrics") { 180 | Response::builder() 181 | .status(200) 182 | .header("Content-Type", "text/plain") 183 | .body(Body::from(format!("# HELP response_latency_ms Response latency in milliseconds\n# TYPE response_latency_ms gauge\nresponse_latency_ms {}\n", response_latency_ms))) 184 | .unwrap() 185 | } else { 186 | Response::builder().status(404).body(Body::empty()).unwrap() 187 | } 188 | } 189 | 190 | fn get_available_port(base_port: u16) -> Option { 191 | (base_port..(base_port + 1000u16)).find(|port| port_is_available(*port)) 192 | } 193 | 194 | fn port_is_available(port: u16) -> bool { 195 | TcpListener::bind(("127.0.0.1", port)).is_ok() 196 | } 197 | 198 | fn get_logger() -> Logger { 199 | let plain = slog_term::PlainSyncDecorator::new(std::io::stdout()); 200 | Logger::root(slog_term::FullFormat::new(plain).build().fuse(), o!()) 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | 204 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Damian Peckett 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | use crate::error::*; 18 | use crate::kubernetes::deployment::KubernetesDeploymentResource; 19 | use crate::kubernetes::replicaset::KubernetesReplicaSetResource; 20 | use crate::kubernetes::statefulset::KubernetesStatefulSetResource; 21 | use crate::kubernetes::KubernetesResource; 22 | use crate::kubernetes::KubernetesResourceTrait; 23 | use crate::kubernetes::{KubernetesObject, KubernetesObjectTrait}; 24 | use crate::metrics::retrieve_aggregate_metric; 25 | use crate::resource::{AutoScaler, AutoScalerKubernetesResourceKind, AutoScalerStrategyKind}; 26 | use crate::strategy::bang_bang::BangBangAutoScalerStrategy; 27 | use crate::strategy::AutoScalerStrategy; 28 | use crate::strategy::AutoScalerStrategyTrait; 29 | use chrono::{DateTime, Utc}; 30 | use clap::{ 31 | arg_enum, crate_authors, crate_description, crate_name, crate_version, value_t, App, Arg, 32 | }; 33 | use futures::channel::mpsc::unbounded; 34 | use futures::channel::mpsc::UnboundedSender; 35 | use futures::{SinkExt, StreamExt}; 36 | use kube::api::{Api, Informer, ListParams, WatchEvent}; 37 | use kube::client::APIClient; 38 | use kube::config; 39 | use slog::{crit, debug, error, info, o, warn, Drain, Level, LevelFilter, Logger}; 40 | use snafu::ResultExt; 41 | use std::collections::HashMap; 42 | use std::panic; 43 | use std::process::exit; 44 | use std::sync::{Arc, Mutex as StdMutex}; 45 | use std::time::Duration; 46 | use stream_cancel::TakeUntil; 47 | use stream_cancel::{StreamExt as StreamCancelExt, Tripwire}; 48 | use tokio::sync::Mutex; 49 | use tokio::sync::RwLock; 50 | use tokio::time::interval; 51 | use tokio::time::Interval; 52 | 53 | /// Pangolin error types. 54 | mod error; 55 | /// Kubernetes api abstraction. 56 | #[allow(clippy::type_complexity)] 57 | mod kubernetes; 58 | /// Prometheus metrics related functions. 59 | mod metrics; 60 | /// AutoScaler specification types. 61 | mod resource; 62 | /// AutoScaler control strategies. 63 | mod strategy; 64 | 65 | arg_enum! { 66 | /// Log level command line argument. 67 | #[derive(PartialEq, Debug)] 68 | pub enum LogLevelArgument { 69 | Critical, 70 | Error, 71 | Warning, 72 | Info, 73 | Debug, 74 | Trace, 75 | } 76 | } 77 | 78 | impl From for Level { 79 | fn from(level_arg: LogLevelArgument) -> Level { 80 | match level_arg { 81 | LogLevelArgument::Critical => Level::Critical, 82 | LogLevelArgument::Error => Level::Error, 83 | LogLevelArgument::Warning => Level::Warning, 84 | LogLevelArgument::Info => Level::Info, 85 | LogLevelArgument::Debug => Level::Debug, 86 | LogLevelArgument::Trace => Level::Trace, 87 | } 88 | } 89 | } 90 | 91 | #[tokio::main] 92 | async fn main() -> Result<(), Error> { 93 | let matches = App::new(crate_name!()) 94 | .version(crate_version!()) 95 | .about(crate_description!()) 96 | .author(crate_authors!()) 97 | .arg( 98 | Arg::with_name("LOG_LEVEL") 99 | .long("log-level") 100 | .help("set the application log level") 101 | .takes_value(true) 102 | .possible_values(&LogLevelArgument::variants()) 103 | .case_insensitive(true) 104 | .default_value("Info"), 105 | ) 106 | .get_matches(); 107 | 108 | let log_level: Level = value_t!(matches, "LOG_LEVEL", LogLevelArgument) 109 | .unwrap_or_else(|e| e.exit()) 110 | .into(); 111 | let logger = Logger::root( 112 | StdMutex::new(LevelFilter::new( 113 | slog_json::Json::default(std::io::stdout()), 114 | log_level, 115 | )) 116 | .map(slog::Fuse), 117 | o!("application" => crate_name!(), "version" => crate_version!()), 118 | ); 119 | 120 | info!(logger, "Configured structured logger"; "log_level" => format!("{:?}", log_level)); 121 | 122 | // Replace the panic handler with one that will exit the process on panics (in any thread). 123 | // This lets Kubernetes restart the process if we hit anything unexpected. 124 | let panic_logger = logger.clone(); 125 | let _ = panic::take_hook(); 126 | panic::set_hook(Box::new(move |panic_info| { 127 | crit!(panic_logger, "Thread panicked"; "error" => format!("{}", panic_info)); 128 | exit(1); 129 | })); 130 | 131 | let kube_config = if let Ok(kube_config) = kube::config::incluster_config() { 132 | kube_config 133 | } else { 134 | config::load_kube_config().await.context(Kube {})? 135 | }; 136 | 137 | let kube_client = APIClient::new(kube_config.clone()); 138 | let autoscaler_api: Api = Api::customResource(kube_client.clone(), "autoscalers") 139 | .version("v1alpha1") 140 | .group("pangolinscaler.com"); 141 | 142 | // Handle for managing the lifecycle of subtasks and sending update information 143 | let mut task_handle: HashMap> = HashMap::new(); 144 | 145 | // Retrieve the current list of autoscalers. 146 | let autoscalers = autoscaler_api 147 | .list(&ListParams::default()) 148 | .await 149 | .context(Kube {})?; 150 | for autoscaler in autoscalers { 151 | let task_key = format!( 152 | "{}/{}", 153 | autoscaler.metadata.namespace.as_ref().unwrap(), 154 | autoscaler.metadata.name 155 | ); 156 | task_handle.insert( 157 | task_key, 158 | autoscaler_loop( 159 | logger.new(o!( 160 | "autoscaler_namespace" => autoscaler.metadata.namespace.as_ref().unwrap().clone(), 161 | "autoscaler_name" => autoscaler.metadata.name.clone())), 162 | kube_config.clone(), 163 | autoscaler.clone(), 164 | )?, 165 | ); 166 | } 167 | 168 | // Set up a watcher for autoscaler events. 169 | let informer = Informer::new(autoscaler_api) 170 | .timeout(15) 171 | .init() 172 | .await 173 | .context(Kube {})?; 174 | 175 | // Loop enables us to drop and refresh the kubernetes watcher periodically 176 | // reduces the reliance on long lived connections and provides us a bit more resiliency. 177 | loop { 178 | let mut events = match informer 179 | .poll() 180 | .await 181 | .map(|events| events.boxed()) 182 | .context(Kube {}) 183 | { 184 | Ok(events) => events, 185 | Err(err) => { 186 | error!(logger, "Failed to poll for events"; "error" => format!("{}", err)); 187 | // Rely on kubernetes for retry behavior 188 | return Err(err); 189 | } 190 | }; 191 | 192 | // Loop over all AutoScaler object changes. 193 | while let Some(Ok(event)) = events.next().await { 194 | match event { 195 | WatchEvent::Added(autoscaler) => { 196 | // New AutoScaler has been added. Start a fresh AutoScaler loop. 197 | let autoscaler_namespace = autoscaler.metadata.namespace.as_ref().unwrap(); 198 | info!(logger, "Added autoscaler"; 199 | "autoscaler_namespace" => autoscaler_namespace, 200 | "autoscaler_name" => &autoscaler.metadata.name); 201 | let task_key = format!("{}/{}", autoscaler_namespace, autoscaler.metadata.name); 202 | task_handle.insert( 203 | task_key, 204 | autoscaler_loop( 205 | logger.new(o!( 206 | "autoscaler_namespace" => autoscaler_namespace.clone(), 207 | "autoscaler_name" => autoscaler.metadata.name.clone())), 208 | kube_config.clone(), 209 | autoscaler.clone(), 210 | )?, 211 | ); 212 | } 213 | WatchEvent::Modified(autoscaler) => { 214 | // AutoScaler object has been modified. Send across the updated object. 215 | let autoscaler_namespace = autoscaler.metadata.namespace.as_ref().unwrap(); 216 | info!(logger, "Modified autoscaler"; 217 | "autoscaler_namespace" => autoscaler_namespace, 218 | "autoscaler_name" => &autoscaler.metadata.name); 219 | let task_key = format!("{}/{}", autoscaler_namespace, autoscaler.metadata.name); 220 | if let Some(task_handle) = task_handle.get_mut(&task_key) { 221 | task_handle.send(autoscaler.clone()).await.unwrap(); 222 | } else { 223 | // Somehow we received an update for an object we haven't already created. 224 | // we must have missed the addition of an AutoScaler somehow. Concerning... 225 | warn!(logger, "Adding missing autoscaler"; 226 | "autoscaler_namespace" => autoscaler_namespace, 227 | "autoscaler_name" => &autoscaler.metadata.name); 228 | task_handle.insert( 229 | task_key, 230 | autoscaler_loop( 231 | logger.new(o!( 232 | "autoscaler_namespace" => autoscaler_namespace.clone(), 233 | "autoscaler_name" => autoscaler.metadata.name.clone())), 234 | kube_config.clone(), 235 | autoscaler.clone(), 236 | )?, 237 | ); 238 | } 239 | } 240 | WatchEvent::Deleted(autoscaler) => { 241 | // AutoScaler object has been deleted. Shutdown all subtasks. 242 | let autoscaler_namespace = autoscaler.metadata.namespace.as_ref().unwrap(); 243 | info!(logger, "Deleted autoscaler"; 244 | "autoscaler_namespace" => autoscaler_namespace, 245 | "autoscaler_name" => &autoscaler.metadata.name); 246 | let task_key = format!("{}/{}", autoscaler_namespace, autoscaler.metadata.name); 247 | // Dropping the handle will terminate the task. 248 | drop(task_handle.remove(&task_key)); 249 | } 250 | WatchEvent::Error(err) => { 251 | // AutoScaler object error. 252 | error!(logger, "Autoscaler error"; "error" => format!("{}", err)) 253 | } 254 | } 255 | } 256 | } 257 | } 258 | 259 | fn autoscaler_loop( 260 | logger: Logger, 261 | kube_config: kube::config::Configuration, 262 | autoscaler: AutoScaler, 263 | ) -> Result, Error> { 264 | // Create a channel for receiving updated AutoScaler specifications. 265 | let (update_sender, mut update_receiver) = unbounded::(); 266 | 267 | // Create an interval timer for the reconciliation loop. 268 | let period = autoscaler.spec.interval; 269 | let (timer_cancel, timer_tripwire) = Tripwire::new(); 270 | let mut timer_cancel = Some(timer_cancel); 271 | 272 | // Create an interval timer for the metrics retrieval subtask. 273 | let metric_period = autoscaler.spec.metric.interval; 274 | let (metric_timer_cancel, metric_timer_tripwire) = Tripwire::new(); 275 | let mut metric_timer_cancel = Some(metric_timer_cancel); 276 | 277 | let autoscaler_namespace = String::from(autoscaler.metadata.namespace.as_ref().unwrap()); 278 | let autoscaler = Arc::new(RwLock::new(Some(autoscaler))); 279 | 280 | // A repository for storing a window worth of retrieved metrics. 281 | // This will leak a small amount of memory when matching objects get deleted. 282 | let metric_repository: Arc>>> = 283 | Arc::new(Mutex::new(HashMap::new())); 284 | 285 | // AutoScaler metrics retrieval subtask. 286 | tokio::spawn(metric_retriever_loop( 287 | logger.clone(), 288 | kube_config.clone(), 289 | autoscaler_namespace.clone(), 290 | autoscaler.clone(), 291 | interval(Duration::from_secs(metric_period as u64)).take_until(metric_timer_tripwire), 292 | metric_repository.clone(), 293 | )); 294 | 295 | // AutoScaler reconciliation subtask. 296 | tokio::spawn(reconciliation_loop( 297 | logger.clone(), 298 | kube_config.clone(), 299 | autoscaler_namespace.clone(), 300 | autoscaler.clone(), 301 | interval(Duration::from_secs(period as u64)).take_until(timer_tripwire), 302 | metric_repository.clone(), 303 | )); 304 | 305 | // AutoScaler update receiver subtask. 306 | tokio::spawn(async move { 307 | let initial_period = autoscaler.read().await.as_ref().unwrap().spec.interval; 308 | 309 | debug!(logger, "Starting autoscaler interval source"; 310 | "period" => initial_period); 311 | 312 | // Process any changes to an AutoScalers timing. And update our shared AutoScaler object. 313 | while let Some(updated_autoscaler) = update_receiver.next().await { 314 | debug!(logger, "Received autoscaler update event"); 315 | 316 | drop(timer_cancel.take()); 317 | drop(metric_timer_cancel.take()); 318 | 319 | debug!(logger, "Cancelled existing timer driven tasks"); 320 | 321 | autoscaler.write().await.replace(updated_autoscaler.clone()); 322 | 323 | debug!(logger, "Updated autoscaler spec"); 324 | 325 | // Create new timer sources. 326 | let (updated_timer_cancel, updated_timer_tripwire) = Tripwire::new(); 327 | let (updated_metric_timer_cancel, updated_metric_timer_tripwire) = Tripwire::new(); 328 | timer_cancel.replace(updated_timer_cancel); 329 | metric_timer_cancel.replace(updated_metric_timer_cancel); 330 | 331 | debug!(logger, "Spawning updated timer driven tasks"); 332 | 333 | // Updated AutoScaler metrics retrieval subtask. 334 | tokio::spawn(metric_retriever_loop( 335 | logger.clone(), 336 | kube_config.clone(), 337 | autoscaler_namespace.clone(), 338 | autoscaler.clone(), 339 | interval(Duration::from_secs( 340 | updated_autoscaler.spec.metric.interval as u64, 341 | )) 342 | .take_until(updated_metric_timer_tripwire), 343 | metric_repository.clone(), 344 | )); 345 | 346 | // Updated AutoScaler reconciliation subtask. 347 | tokio::spawn(reconciliation_loop( 348 | logger.clone(), 349 | kube_config.clone(), 350 | autoscaler_namespace.clone(), 351 | autoscaler.clone(), 352 | interval(Duration::from_secs(updated_autoscaler.spec.interval as u64)) 353 | .take_until(updated_timer_tripwire), 354 | metric_repository.clone(), 355 | )); 356 | } 357 | 358 | // Shutdown interval sources, will lead to the completion of subtasks that depend upon them. 359 | // Eg. is used to propagate a full shutdown. 360 | drop(timer_cancel.take()); 361 | drop(metric_timer_cancel.take()); 362 | debug!(logger, "Cancelled timer driven tasks"); 363 | }); 364 | 365 | Ok(update_sender) 366 | } 367 | 368 | async fn reconciliation_loop( 369 | logger: Logger, 370 | kube_config: kube::config::Configuration, 371 | autoscaler_namespace: String, 372 | autoscaler: Arc>>, 373 | mut timer: TakeUntil, 374 | metric_repository: Arc>>>, 375 | ) { 376 | debug!(logger, "Starting autoscaler task"); 377 | 378 | while let Some(_) = timer.next().await { 379 | // Create the strategy fresh each time, to simplify handling autoscaler spec changes. 380 | let strategy = match &autoscaler.read().await.as_ref().unwrap().spec.strategy { 381 | AutoScalerStrategyKind::BangBang => { 382 | if let Some(bang_bang) = &autoscaler.read().await.as_ref().unwrap().spec.bang_bang { 383 | Some(AutoScalerStrategy::BangBang( 384 | BangBangAutoScalerStrategy::new(bang_bang.clone()), 385 | )) 386 | } else { 387 | None 388 | } 389 | } 390 | }; 391 | 392 | if let Some(strategy) = strategy { 393 | // Spawn subtasks to handle reconciliation of each matching object. 394 | spawn_reconciliation_tasks( 395 | logger.clone(), 396 | kube_config.clone(), 397 | autoscaler.clone(), 398 | autoscaler_namespace.clone(), 399 | strategy, 400 | metric_repository.clone(), 401 | ) 402 | .await; 403 | } else { 404 | error!( 405 | logger, 406 | "Autoscaler is missing required autoscaling strategy configuration" 407 | ); 408 | } 409 | } 410 | 411 | debug!(logger, "Stopped autoscaler task"); 412 | } 413 | 414 | /// Task to pull metrics from all objects associated with an AutoScaler. 415 | async fn metric_retriever_loop( 416 | logger: Logger, 417 | kube_config: kube::config::Configuration, 418 | autoscaler_namespace: String, 419 | autoscaler: Arc>>, 420 | mut metric_timer: TakeUntil, 421 | metric_repository: Arc>>>, 422 | ) { 423 | debug!(logger, "Starting autoscaler metric task"); 424 | 425 | while let Some(_) = metric_timer.next().await { 426 | let metric_name = autoscaler 427 | .read() 428 | .await 429 | .as_ref() 430 | .unwrap() 431 | .spec 432 | .metric 433 | .name 434 | .clone(); 435 | 436 | metrics_retriever_task( 437 | logger.clone(), 438 | kube_config.clone(), 439 | autoscaler_namespace.clone(), 440 | autoscaler.clone(), 441 | metric_repository.clone(), 442 | metric_name, 443 | ) 444 | .await; 445 | } 446 | 447 | debug!(logger, "Stopped autoscaler metric task"); 448 | } 449 | 450 | /// For each matching object, spawn a new reconciliation subtask. 451 | async fn spawn_reconciliation_tasks( 452 | logger: Logger, 453 | kube_config: kube::config::Configuration, 454 | autoscaler: Arc>>, 455 | autoscaler_namespace: String, 456 | strategy: AutoScalerStrategy, 457 | metric_repository: Arc>>>, 458 | ) { 459 | // For each matching object run the reconciliation task. 460 | if let Ok(kubernetes_objects) = matching_objects( 461 | logger.clone(), 462 | kube_config, 463 | autoscaler_namespace, 464 | autoscaler.clone(), 465 | ) 466 | .await 467 | { 468 | for kubernetes_object in kubernetes_objects { 469 | // Resolve the name and namespace of the object. 470 | let (object_namespace, object_name) = kubernetes_object.namespace_and_name(); 471 | debug!(logger.clone(), "Autoscaler reconciliation task found matching object"; 472 | "object_namespace" => &object_namespace, 473 | "object_name" => &object_name); 474 | 475 | // Run all the reconciliation tasks in parallel. 476 | tokio::spawn(reconciliation_task( 477 | logger.new(o!( 478 | "object_namespace" => object_namespace.clone(), 479 | "object_name" => object_name.clone())), 480 | autoscaler.clone(), 481 | kubernetes_object, 482 | object_namespace, 483 | object_name, 484 | strategy.clone(), 485 | metric_repository.clone(), 486 | )); 487 | } 488 | } 489 | } 490 | 491 | /// Perform any reconciliation tasks required in this iteration. 492 | #[allow(clippy::cognitive_complexity)] 493 | async fn reconciliation_task( 494 | logger: Logger, 495 | autoscaler: Arc>>, 496 | kubernetes_object: KubernetesObject, 497 | object_namespace: String, 498 | object_name: String, 499 | strategy: AutoScalerStrategy, 500 | metric_repository: Arc>>>, 501 | ) { 502 | // Ensure the object hasn't been recently modified by another pangolin autoscaler. 503 | match kubernetes_object.last_modified().await { 504 | Ok(Some(last_modified)) => { 505 | // Has it been long enough since our last scaling operation? 506 | // We subtract 5 seconds to account for any lag in this processes reconciliation loop. 507 | let utc_now: DateTime = Utc::now(); 508 | if utc_now.signed_duration_since(last_modified).num_seconds() 509 | < autoscaler.read().await.as_ref().unwrap().spec.interval as i64 - 5 510 | { 511 | warn!(logger, "Autoscaler skipping object due to having been recently modified by another process"); 512 | return; 513 | } 514 | } 515 | Ok(None) => (), 516 | Err(err) => { 517 | error!(logger, "Autoscaler skipping object due to error retrieving annotations"; 518 | "error" => format!("{}", err)); 519 | return; 520 | } 521 | } 522 | 523 | // Get the current number of replicas. 524 | let current_replicas = match kubernetes_object.replicas().await { 525 | Ok(current_replicas) => current_replicas, 526 | Err(err) => { 527 | error!(logger, "Autoscaler skipping object due to error retrieving replica count"; 528 | "error" => format!("{}", err)); 529 | return; 530 | } 531 | }; 532 | 533 | // Retrieve the latest window of metrics from the repository 534 | let metric_name = autoscaler 535 | .read() 536 | .await 537 | .as_ref() 538 | .unwrap() 539 | .spec 540 | .metric 541 | .name 542 | .clone(); 543 | let metric_key = format!("{}/{}/{}", object_namespace, object_name, metric_name); 544 | let current_metric_value = { 545 | if let Some(metric_values) = metric_repository.lock().await.remove(&metric_key) { 546 | debug!(logger, "Found collected metrics for object"; 547 | "count" => metric_values.len()); 548 | Some(metric_values.iter().sum::() / metric_values.len() as f64) 549 | } else { 550 | None 551 | } 552 | }; 553 | 554 | // Do we have any metrics for this object? 555 | if let Some(current_metric_value) = current_metric_value { 556 | // Evaluate the autoscaling strategy. 557 | if let Some(delta) = strategy.evaluate(current_replicas, current_metric_value) { 558 | info!(logger, "Scaling object based on autoscaler strategy"; 559 | "aggregate_metric_value" => current_metric_value, 560 | "current_replicas" => current_replicas, 561 | "delta" => delta); 562 | 563 | // Verify the action wouldn't exceed a maximum replicas limit. 564 | if let Some(Some(max_replicas)) = autoscaler 565 | .read() 566 | .await 567 | .as_ref() 568 | .unwrap() 569 | .spec 570 | .limits 571 | .as_ref() 572 | .map(|limit| limit.replicas.as_ref().map(|replicas| replicas.max)) 573 | { 574 | if current_replicas as i32 + delta >= max_replicas as i32 { 575 | warn!(logger, "Autoscaler refusing to scale up due to maximum replicas"; 576 | "max_replicas" => max_replicas, 577 | "current_replicas" => current_replicas, 578 | "delta" => delta); 579 | return; 580 | } 581 | } 582 | 583 | // Verify the action wouldn't fall below the minimum replicas limit. 584 | if let Some(Some(min_replicas)) = autoscaler 585 | .read() 586 | .await 587 | .as_ref() 588 | .unwrap() 589 | .spec 590 | .limits 591 | .as_ref() 592 | .map(|limit| limit.replicas.as_ref().map(|replicas| replicas.min)) 593 | { 594 | if current_replicas as i32 + delta <= min_replicas as i32 { 595 | warn!(logger, "Autoscaler refusing to scale down due to minimum replicas"; 596 | "min_replicas" => min_replicas, 597 | "current_replicas" => current_replicas, 598 | "delta" => delta); 599 | return; 600 | } 601 | } 602 | 603 | // Scale the object. 604 | if let Err(err) = kubernetes_object 605 | .scale((current_replicas as i32 + delta) as u32) 606 | .await 607 | { 608 | error!(logger, "Autoscaler encountered error scaling object"; 609 | "current_replicas" => current_replicas, 610 | "delta" => delta, 611 | "error" => format!("{}", err)); 612 | return; 613 | } 614 | } else { 615 | info!(logger, "Object does not require scaling"; 616 | "aggregate_metric_value" => current_metric_value, 617 | "current_replicas" => current_replicas); 618 | } 619 | } else { 620 | // No metrics are available, there are some innocent causes for this, but most of the time 621 | // it is concerning. 622 | error!(logger, "Skipping scaling object due to no available metrics"; 623 | "aggregate_metric_value" => current_metric_value, 624 | "current_replicas" => current_replicas, 625 | "metric_name" => metric_name); 626 | } 627 | } 628 | 629 | /// Every metrics retrieval interval run task. 630 | async fn metrics_retriever_task( 631 | logger: Logger, 632 | kube_config: kube::config::Configuration, 633 | autoscaler_namespace: String, 634 | autoscaler: Arc>>, 635 | metric_repository: Arc>>>, 636 | metric_name: String, 637 | ) { 638 | if let Ok(kubernetes_objects) = matching_objects( 639 | logger.clone(), 640 | kube_config.clone(), 641 | autoscaler_namespace.clone(), 642 | autoscaler.clone(), 643 | ) 644 | .await 645 | { 646 | for kubernetes_object in kubernetes_objects { 647 | // Resolve the name and namespace of the object. 648 | let (object_namespace, object_name) = kubernetes_object.namespace_and_name(); 649 | debug!(logger.clone(), "Autoscaler metric task found matching object"; 650 | "object_namespace" => &object_namespace, 651 | "object_name" => &object_name); 652 | 653 | // Get the list of pod ips associated with this deployment. 654 | let pod_ips_and_ports = match kubernetes_object.pod_ips().await { 655 | Ok(pod_ips) => pod_ips 656 | .iter() 657 | .map(|pod_ip| format!("{}:9090", pod_ip)) 658 | .collect::>(), 659 | Err(err) => { 660 | warn!(logger, "Autoscaler metric task skipping object due to error retrieving pod ips"; 661 | "error" => format!("{}", err)); 662 | return; 663 | } 664 | }; 665 | 666 | // Pull metrics from all of the pods. 667 | let current_metric_value = match retrieve_aggregate_metric( 668 | logger.clone(), 669 | pod_ips_and_ports.clone(), 670 | &metric_name, 671 | ) 672 | .await 673 | { 674 | Ok(Some(current_metric_value)) => current_metric_value, 675 | Ok(None) => { 676 | warn!( 677 | logger, 678 | "Autoscaler metric task skipping object due to no available metrics" 679 | ); 680 | return; 681 | } 682 | Err(err) => { 683 | error!(logger, "Autoscaler metric task skipping object due to error pulling metrics"; 684 | "error" => format!("{}", err)); 685 | return; 686 | } 687 | }; 688 | 689 | info!(logger, "Successfully pulled autoscaler metric from pods"; 690 | "pod_ips_and_ports" => format!("{:?}", pod_ips_and_ports), 691 | "aggregate_metric_value" => current_metric_value); 692 | 693 | // Add the received metrics into our shared queue for later pickup by the 694 | // reconciliation subtask. 695 | let metric_key = format!("{}/{}/{}", object_namespace, object_name, metric_name); 696 | { 697 | let mut metric_repository_writer = metric_repository.lock().await; 698 | metric_repository_writer 699 | .entry(metric_key) 700 | .or_insert_with(Vec::new) 701 | .push(current_metric_value); 702 | } 703 | } 704 | } 705 | } 706 | 707 | /// Find a list of matching objects for an AutoScaler. 708 | async fn matching_objects( 709 | logger: Logger, 710 | kube_config: kube::config::Configuration, 711 | autoscaler_namespace: String, 712 | autoscaler: Arc>>, 713 | ) -> Result, Error> { 714 | let resource_kind = autoscaler.read().await.as_ref().unwrap().spec.kind.clone(); 715 | let match_labels = autoscaler 716 | .read() 717 | .await 718 | .as_ref() 719 | .unwrap() 720 | .spec 721 | .selector 722 | .match_labels 723 | .clone(); 724 | 725 | // Construct a client for the expected kubernetes resource kind. 726 | let kubernetes_resource = match resource_kind { 727 | AutoScalerKubernetesResourceKind::Deployment => { 728 | KubernetesResource::Deployment(KubernetesDeploymentResource::new( 729 | kube_config.clone(), 730 | &autoscaler_namespace, 731 | &match_labels, 732 | )) 733 | } 734 | AutoScalerKubernetesResourceKind::ReplicaSet => { 735 | KubernetesResource::ReplicaSet(KubernetesReplicaSetResource::new( 736 | kube_config.clone(), 737 | &autoscaler_namespace, 738 | &match_labels, 739 | )) 740 | } 741 | AutoScalerKubernetesResourceKind::StatefulSet => { 742 | KubernetesResource::StatefulSet(KubernetesStatefulSetResource::new( 743 | kube_config.clone(), 744 | &autoscaler_namespace, 745 | &match_labels, 746 | )) 747 | } 748 | }; 749 | 750 | // Get the list of matching kubernetes resources. 751 | match kubernetes_resource.list().await { 752 | Ok(kubernetes_objects) => Ok(kubernetes_objects), 753 | Err(err) => { 754 | warn!(logger, "Autoscaler failed to list objects"; 755 | "error" => format!("{}", err)); 756 | Err(err) 757 | } 758 | } 759 | } 760 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "adler32" 5 | version = "1.0.4" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | 8 | [[package]] 9 | name = "ansi_term" 10 | version = "0.11.0" 11 | source = "registry+https://github.com/rust-lang/crates.io-index" 12 | dependencies = [ 13 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 14 | ] 15 | 16 | [[package]] 17 | name = "anyhow" 18 | version = "1.0.26" 19 | source = "registry+https://github.com/rust-lang/crates.io-index" 20 | 21 | [[package]] 22 | name = "arc-swap" 23 | version = "0.4.4" 24 | source = "registry+https://github.com/rust-lang/crates.io-index" 25 | 26 | [[package]] 27 | name = "arrayref" 28 | version = "0.3.6" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | 31 | [[package]] 32 | name = "arrayvec" 33 | version = "0.5.1" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | 36 | [[package]] 37 | name = "async-compression" 38 | version = "0.2.0" 39 | source = "registry+https://github.com/rust-lang/crates.io-index" 40 | dependencies = [ 41 | "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", 42 | "flate2 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)", 43 | "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 44 | "memchr 2.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 45 | "pin-project-lite 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 46 | ] 47 | 48 | [[package]] 49 | name = "async-trait" 50 | version = "0.1.24" 51 | source = "registry+https://github.com/rust-lang/crates.io-index" 52 | dependencies = [ 53 | "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", 54 | "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 55 | "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", 56 | ] 57 | 58 | [[package]] 59 | name = "atty" 60 | version = "0.2.14" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | dependencies = [ 63 | "hermit-abi 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 64 | "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", 65 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 66 | ] 67 | 68 | [[package]] 69 | name = "autocfg" 70 | version = "0.1.7" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | 73 | [[package]] 74 | name = "autocfg" 75 | version = "1.0.0" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | 78 | [[package]] 79 | name = "base64" 80 | version = "0.11.0" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | 83 | [[package]] 84 | name = "bitflags" 85 | version = "1.2.1" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | 88 | [[package]] 89 | name = "blake2b_simd" 90 | version = "0.5.10" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | dependencies = [ 93 | "arrayref 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 94 | "arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 95 | "constant_time_eq 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 96 | ] 97 | 98 | [[package]] 99 | name = "bumpalo" 100 | version = "3.2.0" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | 103 | [[package]] 104 | name = "bytes" 105 | version = "0.5.4" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | 108 | [[package]] 109 | name = "c2-chacha" 110 | version = "0.2.3" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | dependencies = [ 113 | "ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", 114 | ] 115 | 116 | [[package]] 117 | name = "cc" 118 | version = "1.0.50" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | 121 | [[package]] 122 | name = "cfg-if" 123 | version = "0.1.10" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | 126 | [[package]] 127 | name = "chrono" 128 | version = "0.4.10" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | dependencies = [ 131 | "num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", 132 | "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 133 | "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", 134 | "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", 135 | ] 136 | 137 | [[package]] 138 | name = "clap" 139 | version = "2.33.0" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | dependencies = [ 142 | "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", 143 | "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", 144 | "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 145 | "strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", 146 | "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", 147 | "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 148 | "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", 149 | ] 150 | 151 | [[package]] 152 | name = "constant_time_eq" 153 | version = "0.1.5" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | 156 | [[package]] 157 | name = "core-foundation" 158 | version = "0.6.4" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | dependencies = [ 161 | "core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", 162 | "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", 163 | ] 164 | 165 | [[package]] 166 | name = "core-foundation-sys" 167 | version = "0.6.2" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | 170 | [[package]] 171 | name = "crc32fast" 172 | version = "1.2.0" 173 | source = "registry+https://github.com/rust-lang/crates.io-index" 174 | dependencies = [ 175 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 176 | ] 177 | 178 | [[package]] 179 | name = "crossbeam-utils" 180 | version = "0.7.0" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | dependencies = [ 183 | "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 184 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 185 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 186 | ] 187 | 188 | [[package]] 189 | name = "dirs" 190 | version = "2.0.2" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | dependencies = [ 193 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 194 | "dirs-sys 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 195 | ] 196 | 197 | [[package]] 198 | name = "dirs-sys" 199 | version = "0.3.4" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | dependencies = [ 202 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 203 | "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", 204 | "redox_users 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 205 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 206 | ] 207 | 208 | [[package]] 209 | name = "doc-comment" 210 | version = "0.3.1" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | 213 | [[package]] 214 | name = "dtoa" 215 | version = "0.4.5" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | 218 | [[package]] 219 | name = "either" 220 | version = "1.5.3" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | 223 | [[package]] 224 | name = "encoding_rs" 225 | version = "0.8.22" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | dependencies = [ 228 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 229 | ] 230 | 231 | [[package]] 232 | name = "enum_dispatch" 233 | version = "0.2.1" 234 | source = "registry+https://github.com/rust-lang/crates.io-index" 235 | dependencies = [ 236 | "once_cell 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 237 | "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", 238 | "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 239 | "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", 240 | ] 241 | 242 | [[package]] 243 | name = "flate2" 244 | version = "1.0.13" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | dependencies = [ 247 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 248 | "crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 249 | "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", 250 | "miniz_oxide 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 251 | ] 252 | 253 | [[package]] 254 | name = "fnv" 255 | version = "1.0.6" 256 | source = "registry+https://github.com/rust-lang/crates.io-index" 257 | 258 | [[package]] 259 | name = "foreign-types" 260 | version = "0.3.2" 261 | source = "registry+https://github.com/rust-lang/crates.io-index" 262 | dependencies = [ 263 | "foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 264 | ] 265 | 266 | [[package]] 267 | name = "foreign-types-shared" 268 | version = "0.1.1" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | 271 | [[package]] 272 | name = "fuchsia-zircon" 273 | version = "0.3.3" 274 | source = "registry+https://github.com/rust-lang/crates.io-index" 275 | dependencies = [ 276 | "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 277 | "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 278 | ] 279 | 280 | [[package]] 281 | name = "fuchsia-zircon-sys" 282 | version = "0.3.3" 283 | source = "registry+https://github.com/rust-lang/crates.io-index" 284 | 285 | [[package]] 286 | name = "futures" 287 | version = "0.3.4" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | dependencies = [ 290 | "futures-channel 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 291 | "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 292 | "futures-executor 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 293 | "futures-io 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 294 | "futures-sink 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 295 | "futures-task 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 296 | "futures-util 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 297 | ] 298 | 299 | [[package]] 300 | name = "futures-channel" 301 | version = "0.3.4" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | dependencies = [ 304 | "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 305 | "futures-sink 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 306 | ] 307 | 308 | [[package]] 309 | name = "futures-core" 310 | version = "0.3.4" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | 313 | [[package]] 314 | name = "futures-executor" 315 | version = "0.3.4" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | dependencies = [ 318 | "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 319 | "futures-task 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 320 | "futures-util 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 321 | ] 322 | 323 | [[package]] 324 | name = "futures-io" 325 | version = "0.3.4" 326 | source = "registry+https://github.com/rust-lang/crates.io-index" 327 | 328 | [[package]] 329 | name = "futures-macro" 330 | version = "0.3.4" 331 | source = "registry+https://github.com/rust-lang/crates.io-index" 332 | dependencies = [ 333 | "proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)", 334 | "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", 335 | "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 336 | "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", 337 | ] 338 | 339 | [[package]] 340 | name = "futures-sink" 341 | version = "0.3.4" 342 | source = "registry+https://github.com/rust-lang/crates.io-index" 343 | 344 | [[package]] 345 | name = "futures-task" 346 | version = "0.3.4" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | 349 | [[package]] 350 | name = "futures-timer" 351 | version = "3.0.1" 352 | source = "registry+https://github.com/rust-lang/crates.io-index" 353 | 354 | [[package]] 355 | name = "futures-util" 356 | version = "0.3.4" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | dependencies = [ 359 | "futures-channel 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 360 | "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 361 | "futures-io 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 362 | "futures-macro 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 363 | "futures-sink 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 364 | "futures-task 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 365 | "memchr 2.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 366 | "pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)", 367 | "proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)", 368 | "proc-macro-nested 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 369 | "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 370 | ] 371 | 372 | [[package]] 373 | name = "getrandom" 374 | version = "0.1.14" 375 | source = "registry+https://github.com/rust-lang/crates.io-index" 376 | dependencies = [ 377 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 378 | "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", 379 | "wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)", 380 | ] 381 | 382 | [[package]] 383 | name = "h2" 384 | version = "0.2.1" 385 | source = "registry+https://github.com/rust-lang/crates.io-index" 386 | dependencies = [ 387 | "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", 388 | "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", 389 | "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 390 | "futures-sink 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 391 | "futures-util 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 392 | "http 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 393 | "indexmap 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 394 | "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", 395 | "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 396 | "tokio 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 397 | "tokio-util 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 398 | ] 399 | 400 | [[package]] 401 | name = "heck" 402 | version = "0.3.1" 403 | source = "registry+https://github.com/rust-lang/crates.io-index" 404 | dependencies = [ 405 | "unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)", 406 | ] 407 | 408 | [[package]] 409 | name = "hermit-abi" 410 | version = "0.1.6" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | dependencies = [ 413 | "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", 414 | ] 415 | 416 | [[package]] 417 | name = "http" 418 | version = "0.2.0" 419 | source = "registry+https://github.com/rust-lang/crates.io-index" 420 | dependencies = [ 421 | "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", 422 | "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", 423 | "itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", 424 | ] 425 | 426 | [[package]] 427 | name = "http-body" 428 | version = "0.3.1" 429 | source = "registry+https://github.com/rust-lang/crates.io-index" 430 | dependencies = [ 431 | "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", 432 | "http 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 433 | ] 434 | 435 | [[package]] 436 | name = "httparse" 437 | version = "1.3.4" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | 440 | [[package]] 441 | name = "hyper" 442 | version = "0.13.2" 443 | source = "registry+https://github.com/rust-lang/crates.io-index" 444 | dependencies = [ 445 | "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", 446 | "futures-channel 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 447 | "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 448 | "futures-util 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 449 | "h2 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 450 | "http 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 451 | "http-body 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 452 | "httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 453 | "itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", 454 | "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", 455 | "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", 456 | "pin-project 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", 457 | "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", 458 | "tokio 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 459 | "tower-service 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 460 | "want 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 461 | ] 462 | 463 | [[package]] 464 | name = "hyper-tls" 465 | version = "0.4.1" 466 | source = "registry+https://github.com/rust-lang/crates.io-index" 467 | dependencies = [ 468 | "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", 469 | "hyper 0.13.2 (registry+https://github.com/rust-lang/crates.io-index)", 470 | "native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 471 | "tokio 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 472 | "tokio-tls 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 473 | ] 474 | 475 | [[package]] 476 | name = "idna" 477 | version = "0.2.0" 478 | source = "registry+https://github.com/rust-lang/crates.io-index" 479 | dependencies = [ 480 | "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 481 | "unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 482 | "unicode-normalization 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", 483 | ] 484 | 485 | [[package]] 486 | name = "indexmap" 487 | version = "1.3.2" 488 | source = "registry+https://github.com/rust-lang/crates.io-index" 489 | dependencies = [ 490 | "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 491 | ] 492 | 493 | [[package]] 494 | name = "iovec" 495 | version = "0.1.4" 496 | source = "registry+https://github.com/rust-lang/crates.io-index" 497 | dependencies = [ 498 | "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", 499 | ] 500 | 501 | [[package]] 502 | name = "itoa" 503 | version = "0.4.5" 504 | source = "registry+https://github.com/rust-lang/crates.io-index" 505 | 506 | [[package]] 507 | name = "js-sys" 508 | version = "0.3.35" 509 | source = "registry+https://github.com/rust-lang/crates.io-index" 510 | dependencies = [ 511 | "wasm-bindgen 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 512 | ] 513 | 514 | [[package]] 515 | name = "k8s-openapi" 516 | version = "0.7.1" 517 | source = "registry+https://github.com/rust-lang/crates.io-index" 518 | dependencies = [ 519 | "base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", 520 | "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", 521 | "chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", 522 | "http 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 523 | "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 524 | "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", 525 | "serde-value 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", 526 | "serde_json 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)", 527 | "url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 528 | ] 529 | 530 | [[package]] 531 | name = "kernel32-sys" 532 | version = "0.2.2" 533 | source = "registry+https://github.com/rust-lang/crates.io-index" 534 | dependencies = [ 535 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 536 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 537 | ] 538 | 539 | [[package]] 540 | name = "kube" 541 | version = "0.25.0" 542 | source = "registry+https://github.com/rust-lang/crates.io-index" 543 | dependencies = [ 544 | "base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", 545 | "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", 546 | "chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", 547 | "dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 548 | "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", 549 | "futures 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 550 | "futures-timer 3.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 551 | "futures-util 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 552 | "http 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 553 | "k8s-openapi 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", 554 | "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", 555 | "openssl 0.10.28 (registry+https://github.com/rust-lang/crates.io-index)", 556 | "reqwest 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", 557 | "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", 558 | "serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", 559 | "serde_json 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)", 560 | "serde_yaml 0.8.11 (registry+https://github.com/rust-lang/crates.io-index)", 561 | "thiserror 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)", 562 | "time 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", 563 | "url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 564 | ] 565 | 566 | [[package]] 567 | name = "lazy_static" 568 | version = "1.4.0" 569 | source = "registry+https://github.com/rust-lang/crates.io-index" 570 | 571 | [[package]] 572 | name = "libc" 573 | version = "0.2.66" 574 | source = "registry+https://github.com/rust-lang/crates.io-index" 575 | 576 | [[package]] 577 | name = "linked-hash-map" 578 | version = "0.5.2" 579 | source = "registry+https://github.com/rust-lang/crates.io-index" 580 | 581 | [[package]] 582 | name = "log" 583 | version = "0.4.8" 584 | source = "registry+https://github.com/rust-lang/crates.io-index" 585 | dependencies = [ 586 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 587 | ] 588 | 589 | [[package]] 590 | name = "matches" 591 | version = "0.1.8" 592 | source = "registry+https://github.com/rust-lang/crates.io-index" 593 | 594 | [[package]] 595 | name = "memchr" 596 | version = "2.3.2" 597 | source = "registry+https://github.com/rust-lang/crates.io-index" 598 | 599 | [[package]] 600 | name = "mime" 601 | version = "0.3.16" 602 | source = "registry+https://github.com/rust-lang/crates.io-index" 603 | 604 | [[package]] 605 | name = "mime_guess" 606 | version = "2.0.1" 607 | source = "registry+https://github.com/rust-lang/crates.io-index" 608 | dependencies = [ 609 | "mime 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", 610 | "unicase 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)", 611 | ] 612 | 613 | [[package]] 614 | name = "miniz_oxide" 615 | version = "0.3.6" 616 | source = "registry+https://github.com/rust-lang/crates.io-index" 617 | dependencies = [ 618 | "adler32 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 619 | ] 620 | 621 | [[package]] 622 | name = "mio" 623 | version = "0.6.21" 624 | source = "registry+https://github.com/rust-lang/crates.io-index" 625 | dependencies = [ 626 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 627 | "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 628 | "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 629 | "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 630 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 631 | "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", 632 | "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", 633 | "miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 634 | "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", 635 | "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 636 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 637 | ] 638 | 639 | [[package]] 640 | name = "mio-named-pipes" 641 | version = "0.1.6" 642 | source = "registry+https://github.com/rust-lang/crates.io-index" 643 | dependencies = [ 644 | "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", 645 | "mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)", 646 | "miow 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 647 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 648 | ] 649 | 650 | [[package]] 651 | name = "mio-uds" 652 | version = "0.6.7" 653 | source = "registry+https://github.com/rust-lang/crates.io-index" 654 | dependencies = [ 655 | "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 656 | "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", 657 | "mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)", 658 | ] 659 | 660 | [[package]] 661 | name = "miow" 662 | version = "0.2.1" 663 | source = "registry+https://github.com/rust-lang/crates.io-index" 664 | dependencies = [ 665 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 666 | "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", 667 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 668 | "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 669 | ] 670 | 671 | [[package]] 672 | name = "miow" 673 | version = "0.3.3" 674 | source = "registry+https://github.com/rust-lang/crates.io-index" 675 | dependencies = [ 676 | "socket2 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)", 677 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 678 | ] 679 | 680 | [[package]] 681 | name = "native-tls" 682 | version = "0.2.3" 683 | source = "registry+https://github.com/rust-lang/crates.io-index" 684 | dependencies = [ 685 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 686 | "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", 687 | "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", 688 | "openssl 0.10.28 (registry+https://github.com/rust-lang/crates.io-index)", 689 | "openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 690 | "openssl-sys 0.9.54 (registry+https://github.com/rust-lang/crates.io-index)", 691 | "schannel 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", 692 | "security-framework 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 693 | "security-framework-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 694 | "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 695 | ] 696 | 697 | [[package]] 698 | name = "net2" 699 | version = "0.2.33" 700 | source = "registry+https://github.com/rust-lang/crates.io-index" 701 | dependencies = [ 702 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 703 | "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", 704 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 705 | ] 706 | 707 | [[package]] 708 | name = "nom" 709 | version = "4.2.3" 710 | source = "registry+https://github.com/rust-lang/crates.io-index" 711 | dependencies = [ 712 | "memchr 2.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 713 | "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 714 | ] 715 | 716 | [[package]] 717 | name = "num-integer" 718 | version = "0.1.42" 719 | source = "registry+https://github.com/rust-lang/crates.io-index" 720 | dependencies = [ 721 | "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 722 | "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 723 | ] 724 | 725 | [[package]] 726 | name = "num-traits" 727 | version = "0.2.11" 728 | source = "registry+https://github.com/rust-lang/crates.io-index" 729 | dependencies = [ 730 | "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 731 | ] 732 | 733 | [[package]] 734 | name = "num_cpus" 735 | version = "1.12.0" 736 | source = "registry+https://github.com/rust-lang/crates.io-index" 737 | dependencies = [ 738 | "hermit-abi 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 739 | "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", 740 | ] 741 | 742 | [[package]] 743 | name = "once_cell" 744 | version = "1.3.1" 745 | source = "registry+https://github.com/rust-lang/crates.io-index" 746 | 747 | [[package]] 748 | name = "openssl" 749 | version = "0.10.28" 750 | source = "registry+https://github.com/rust-lang/crates.io-index" 751 | dependencies = [ 752 | "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 753 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 754 | "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 755 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 756 | "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", 757 | "openssl-sys 0.9.54 (registry+https://github.com/rust-lang/crates.io-index)", 758 | ] 759 | 760 | [[package]] 761 | name = "openssl-probe" 762 | version = "0.1.2" 763 | source = "registry+https://github.com/rust-lang/crates.io-index" 764 | 765 | [[package]] 766 | name = "openssl-src" 767 | version = "111.6.1+1.1.1d" 768 | source = "registry+https://github.com/rust-lang/crates.io-index" 769 | dependencies = [ 770 | "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)", 771 | ] 772 | 773 | [[package]] 774 | name = "openssl-sys" 775 | version = "0.9.54" 776 | source = "registry+https://github.com/rust-lang/crates.io-index" 777 | dependencies = [ 778 | "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 779 | "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)", 780 | "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", 781 | "openssl-src 111.6.1+1.1.1d (registry+https://github.com/rust-lang/crates.io-index)", 782 | "pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", 783 | "vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 784 | ] 785 | 786 | [[package]] 787 | name = "ordered-float" 788 | version = "1.0.2" 789 | source = "registry+https://github.com/rust-lang/crates.io-index" 790 | dependencies = [ 791 | "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 792 | ] 793 | 794 | [[package]] 795 | name = "pangolin" 796 | version = "0.1.0" 797 | dependencies = [ 798 | "async-trait 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)", 799 | "chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", 800 | "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", 801 | "enum_dispatch 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 802 | "futures 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 803 | "hyper 0.13.2 (registry+https://github.com/rust-lang/crates.io-index)", 804 | "k8s-openapi 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", 805 | "kube 0.25.0 (registry+https://github.com/rust-lang/crates.io-index)", 806 | "openssl 0.10.28 (registry+https://github.com/rust-lang/crates.io-index)", 807 | "reqwest 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", 808 | "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", 809 | "serde_json 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)", 810 | "slog 2.5.2 (registry+https://github.com/rust-lang/crates.io-index)", 811 | "slog-json 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 812 | "slog-term 2.5.0 (registry+https://github.com/rust-lang/crates.io-index)", 813 | "snafu 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", 814 | "stream-cancel 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", 815 | "tokio 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 816 | ] 817 | 818 | [[package]] 819 | name = "percent-encoding" 820 | version = "2.1.0" 821 | source = "registry+https://github.com/rust-lang/crates.io-index" 822 | 823 | [[package]] 824 | name = "pin-project" 825 | version = "0.4.8" 826 | source = "registry+https://github.com/rust-lang/crates.io-index" 827 | dependencies = [ 828 | "pin-project-internal 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", 829 | ] 830 | 831 | [[package]] 832 | name = "pin-project-internal" 833 | version = "0.4.8" 834 | source = "registry+https://github.com/rust-lang/crates.io-index" 835 | dependencies = [ 836 | "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", 837 | "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 838 | "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", 839 | ] 840 | 841 | [[package]] 842 | name = "pin-project-lite" 843 | version = "0.1.4" 844 | source = "registry+https://github.com/rust-lang/crates.io-index" 845 | 846 | [[package]] 847 | name = "pin-utils" 848 | version = "0.1.0-alpha.4" 849 | source = "registry+https://github.com/rust-lang/crates.io-index" 850 | 851 | [[package]] 852 | name = "pkg-config" 853 | version = "0.3.17" 854 | source = "registry+https://github.com/rust-lang/crates.io-index" 855 | 856 | [[package]] 857 | name = "ppv-lite86" 858 | version = "0.2.6" 859 | source = "registry+https://github.com/rust-lang/crates.io-index" 860 | 861 | [[package]] 862 | name = "proc-macro-hack" 863 | version = "0.5.11" 864 | source = "registry+https://github.com/rust-lang/crates.io-index" 865 | dependencies = [ 866 | "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", 867 | "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 868 | "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", 869 | ] 870 | 871 | [[package]] 872 | name = "proc-macro-nested" 873 | version = "0.1.3" 874 | source = "registry+https://github.com/rust-lang/crates.io-index" 875 | 876 | [[package]] 877 | name = "proc-macro2" 878 | version = "1.0.8" 879 | source = "registry+https://github.com/rust-lang/crates.io-index" 880 | dependencies = [ 881 | "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 882 | ] 883 | 884 | [[package]] 885 | name = "quote" 886 | version = "1.0.2" 887 | source = "registry+https://github.com/rust-lang/crates.io-index" 888 | dependencies = [ 889 | "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", 890 | ] 891 | 892 | [[package]] 893 | name = "rand" 894 | version = "0.7.3" 895 | source = "registry+https://github.com/rust-lang/crates.io-index" 896 | dependencies = [ 897 | "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", 898 | "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", 899 | "rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 900 | "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 901 | "rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 902 | ] 903 | 904 | [[package]] 905 | name = "rand_chacha" 906 | version = "0.2.1" 907 | source = "registry+https://github.com/rust-lang/crates.io-index" 908 | dependencies = [ 909 | "c2-chacha 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 910 | "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 911 | ] 912 | 913 | [[package]] 914 | name = "rand_core" 915 | version = "0.5.1" 916 | source = "registry+https://github.com/rust-lang/crates.io-index" 917 | dependencies = [ 918 | "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", 919 | ] 920 | 921 | [[package]] 922 | name = "rand_hc" 923 | version = "0.2.0" 924 | source = "registry+https://github.com/rust-lang/crates.io-index" 925 | dependencies = [ 926 | "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 927 | ] 928 | 929 | [[package]] 930 | name = "redox_syscall" 931 | version = "0.1.56" 932 | source = "registry+https://github.com/rust-lang/crates.io-index" 933 | 934 | [[package]] 935 | name = "redox_users" 936 | version = "0.3.4" 937 | source = "registry+https://github.com/rust-lang/crates.io-index" 938 | dependencies = [ 939 | "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", 940 | "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", 941 | "rust-argon2 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 942 | ] 943 | 944 | [[package]] 945 | name = "remove_dir_all" 946 | version = "0.5.2" 947 | source = "registry+https://github.com/rust-lang/crates.io-index" 948 | dependencies = [ 949 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 950 | ] 951 | 952 | [[package]] 953 | name = "reqwest" 954 | version = "0.10.1" 955 | source = "registry+https://github.com/rust-lang/crates.io-index" 956 | dependencies = [ 957 | "async-compression 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 958 | "base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", 959 | "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", 960 | "encoding_rs 0.8.22 (registry+https://github.com/rust-lang/crates.io-index)", 961 | "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 962 | "futures-util 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 963 | "http 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 964 | "http-body 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 965 | "hyper 0.13.2 (registry+https://github.com/rust-lang/crates.io-index)", 966 | "hyper-tls 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", 967 | "js-sys 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)", 968 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 969 | "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", 970 | "mime 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", 971 | "mime_guess 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 972 | "native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 973 | "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 974 | "pin-project-lite 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 975 | "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", 976 | "serde_json 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)", 977 | "serde_urlencoded 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", 978 | "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", 979 | "tokio 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 980 | "tokio-tls 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 981 | "url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 982 | "wasm-bindgen 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 983 | "wasm-bindgen-futures 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", 984 | "web-sys 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)", 985 | "winreg 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", 986 | ] 987 | 988 | [[package]] 989 | name = "rust-argon2" 990 | version = "0.7.0" 991 | source = "registry+https://github.com/rust-lang/crates.io-index" 992 | dependencies = [ 993 | "base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", 994 | "blake2b_simd 0.5.10 (registry+https://github.com/rust-lang/crates.io-index)", 995 | "constant_time_eq 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 996 | "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 997 | ] 998 | 999 | [[package]] 1000 | name = "rustversion" 1001 | version = "1.0.2" 1002 | source = "registry+https://github.com/rust-lang/crates.io-index" 1003 | dependencies = [ 1004 | "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", 1005 | "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 1006 | "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", 1007 | ] 1008 | 1009 | [[package]] 1010 | name = "ryu" 1011 | version = "1.0.2" 1012 | source = "registry+https://github.com/rust-lang/crates.io-index" 1013 | 1014 | [[package]] 1015 | name = "schannel" 1016 | version = "0.1.17" 1017 | source = "registry+https://github.com/rust-lang/crates.io-index" 1018 | dependencies = [ 1019 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 1020 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 1021 | ] 1022 | 1023 | [[package]] 1024 | name = "security-framework" 1025 | version = "0.3.4" 1026 | source = "registry+https://github.com/rust-lang/crates.io-index" 1027 | dependencies = [ 1028 | "core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", 1029 | "core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", 1030 | "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", 1031 | "security-framework-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 1032 | ] 1033 | 1034 | [[package]] 1035 | name = "security-framework-sys" 1036 | version = "0.3.3" 1037 | source = "registry+https://github.com/rust-lang/crates.io-index" 1038 | dependencies = [ 1039 | "core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", 1040 | ] 1041 | 1042 | [[package]] 1043 | name = "serde" 1044 | version = "1.0.104" 1045 | source = "registry+https://github.com/rust-lang/crates.io-index" 1046 | dependencies = [ 1047 | "serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", 1048 | ] 1049 | 1050 | [[package]] 1051 | name = "serde-value" 1052 | version = "0.6.0" 1053 | source = "registry+https://github.com/rust-lang/crates.io-index" 1054 | dependencies = [ 1055 | "ordered-float 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 1056 | "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", 1057 | ] 1058 | 1059 | [[package]] 1060 | name = "serde_derive" 1061 | version = "1.0.104" 1062 | source = "registry+https://github.com/rust-lang/crates.io-index" 1063 | dependencies = [ 1064 | "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", 1065 | "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 1066 | "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", 1067 | ] 1068 | 1069 | [[package]] 1070 | name = "serde_json" 1071 | version = "1.0.48" 1072 | source = "registry+https://github.com/rust-lang/crates.io-index" 1073 | dependencies = [ 1074 | "itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", 1075 | "ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 1076 | "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", 1077 | ] 1078 | 1079 | [[package]] 1080 | name = "serde_urlencoded" 1081 | version = "0.6.1" 1082 | source = "registry+https://github.com/rust-lang/crates.io-index" 1083 | dependencies = [ 1084 | "dtoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", 1085 | "itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", 1086 | "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", 1087 | "url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 1088 | ] 1089 | 1090 | [[package]] 1091 | name = "serde_yaml" 1092 | version = "0.8.11" 1093 | source = "registry+https://github.com/rust-lang/crates.io-index" 1094 | dependencies = [ 1095 | "dtoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", 1096 | "linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", 1097 | "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", 1098 | "yaml-rust 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", 1099 | ] 1100 | 1101 | [[package]] 1102 | name = "signal-hook-registry" 1103 | version = "1.2.0" 1104 | source = "registry+https://github.com/rust-lang/crates.io-index" 1105 | dependencies = [ 1106 | "arc-swap 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", 1107 | "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", 1108 | ] 1109 | 1110 | [[package]] 1111 | name = "slab" 1112 | version = "0.4.2" 1113 | source = "registry+https://github.com/rust-lang/crates.io-index" 1114 | 1115 | [[package]] 1116 | name = "slog" 1117 | version = "2.5.2" 1118 | source = "registry+https://github.com/rust-lang/crates.io-index" 1119 | 1120 | [[package]] 1121 | name = "slog-json" 1122 | version = "2.3.0" 1123 | source = "registry+https://github.com/rust-lang/crates.io-index" 1124 | dependencies = [ 1125 | "chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", 1126 | "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", 1127 | "serde_json 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)", 1128 | "slog 2.5.2 (registry+https://github.com/rust-lang/crates.io-index)", 1129 | ] 1130 | 1131 | [[package]] 1132 | name = "slog-term" 1133 | version = "2.5.0" 1134 | source = "registry+https://github.com/rust-lang/crates.io-index" 1135 | dependencies = [ 1136 | "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", 1137 | "chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", 1138 | "slog 2.5.2 (registry+https://github.com/rust-lang/crates.io-index)", 1139 | "term 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", 1140 | "thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 1141 | ] 1142 | 1143 | [[package]] 1144 | name = "smallvec" 1145 | version = "1.2.0" 1146 | source = "registry+https://github.com/rust-lang/crates.io-index" 1147 | 1148 | [[package]] 1149 | name = "snafu" 1150 | version = "0.6.2" 1151 | source = "registry+https://github.com/rust-lang/crates.io-index" 1152 | dependencies = [ 1153 | "doc-comment 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 1154 | "snafu-derive 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", 1155 | ] 1156 | 1157 | [[package]] 1158 | name = "snafu-derive" 1159 | version = "0.6.2" 1160 | source = "registry+https://github.com/rust-lang/crates.io-index" 1161 | dependencies = [ 1162 | "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", 1163 | "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 1164 | "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", 1165 | ] 1166 | 1167 | [[package]] 1168 | name = "socket2" 1169 | version = "0.3.11" 1170 | source = "registry+https://github.com/rust-lang/crates.io-index" 1171 | dependencies = [ 1172 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 1173 | "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", 1174 | "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", 1175 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 1176 | ] 1177 | 1178 | [[package]] 1179 | name = "sourcefile" 1180 | version = "0.1.4" 1181 | source = "registry+https://github.com/rust-lang/crates.io-index" 1182 | 1183 | [[package]] 1184 | name = "stream-cancel" 1185 | version = "0.5.2" 1186 | source = "registry+https://github.com/rust-lang/crates.io-index" 1187 | dependencies = [ 1188 | "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 1189 | "futures-util 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 1190 | "pin-project 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", 1191 | "tokio 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 1192 | ] 1193 | 1194 | [[package]] 1195 | name = "strsim" 1196 | version = "0.8.0" 1197 | source = "registry+https://github.com/rust-lang/crates.io-index" 1198 | 1199 | [[package]] 1200 | name = "syn" 1201 | version = "1.0.14" 1202 | source = "registry+https://github.com/rust-lang/crates.io-index" 1203 | dependencies = [ 1204 | "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", 1205 | "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 1206 | "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 1207 | ] 1208 | 1209 | [[package]] 1210 | name = "tempfile" 1211 | version = "3.1.0" 1212 | source = "registry+https://github.com/rust-lang/crates.io-index" 1213 | dependencies = [ 1214 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 1215 | "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", 1216 | "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", 1217 | "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", 1218 | "remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", 1219 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 1220 | ] 1221 | 1222 | [[package]] 1223 | name = "term" 1224 | version = "0.6.1" 1225 | source = "registry+https://github.com/rust-lang/crates.io-index" 1226 | dependencies = [ 1227 | "dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 1228 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 1229 | ] 1230 | 1231 | [[package]] 1232 | name = "textwrap" 1233 | version = "0.11.0" 1234 | source = "registry+https://github.com/rust-lang/crates.io-index" 1235 | dependencies = [ 1236 | "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 1237 | ] 1238 | 1239 | [[package]] 1240 | name = "thiserror" 1241 | version = "1.0.11" 1242 | source = "registry+https://github.com/rust-lang/crates.io-index" 1243 | dependencies = [ 1244 | "thiserror-impl 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)", 1245 | ] 1246 | 1247 | [[package]] 1248 | name = "thiserror-impl" 1249 | version = "1.0.11" 1250 | source = "registry+https://github.com/rust-lang/crates.io-index" 1251 | dependencies = [ 1252 | "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", 1253 | "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 1254 | "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", 1255 | ] 1256 | 1257 | [[package]] 1258 | name = "thread_local" 1259 | version = "1.0.1" 1260 | source = "registry+https://github.com/rust-lang/crates.io-index" 1261 | dependencies = [ 1262 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 1263 | ] 1264 | 1265 | [[package]] 1266 | name = "time" 1267 | version = "0.1.42" 1268 | source = "registry+https://github.com/rust-lang/crates.io-index" 1269 | dependencies = [ 1270 | "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", 1271 | "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", 1272 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 1273 | ] 1274 | 1275 | [[package]] 1276 | name = "time" 1277 | version = "0.2.6" 1278 | source = "registry+https://github.com/rust-lang/crates.io-index" 1279 | dependencies = [ 1280 | "rustversion 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 1281 | "time-macros 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 1282 | ] 1283 | 1284 | [[package]] 1285 | name = "time-macros" 1286 | version = "0.1.0" 1287 | source = "registry+https://github.com/rust-lang/crates.io-index" 1288 | dependencies = [ 1289 | "proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)", 1290 | "time-macros-impl 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 1291 | ] 1292 | 1293 | [[package]] 1294 | name = "time-macros-impl" 1295 | version = "0.1.0" 1296 | source = "registry+https://github.com/rust-lang/crates.io-index" 1297 | dependencies = [ 1298 | "proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)", 1299 | "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", 1300 | "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 1301 | "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", 1302 | ] 1303 | 1304 | [[package]] 1305 | name = "tokio" 1306 | version = "0.2.11" 1307 | source = "registry+https://github.com/rust-lang/crates.io-index" 1308 | dependencies = [ 1309 | "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", 1310 | "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", 1311 | "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 1312 | "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 1313 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 1314 | "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", 1315 | "memchr 2.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 1316 | "mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)", 1317 | "mio-named-pipes 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 1318 | "mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)", 1319 | "num_cpus 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)", 1320 | "pin-project-lite 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 1321 | "signal-hook-registry 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 1322 | "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 1323 | "tokio-macros 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", 1324 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 1325 | ] 1326 | 1327 | [[package]] 1328 | name = "tokio-macros" 1329 | version = "0.2.4" 1330 | source = "registry+https://github.com/rust-lang/crates.io-index" 1331 | dependencies = [ 1332 | "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", 1333 | "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 1334 | "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", 1335 | ] 1336 | 1337 | [[package]] 1338 | name = "tokio-tls" 1339 | version = "0.3.0" 1340 | source = "registry+https://github.com/rust-lang/crates.io-index" 1341 | dependencies = [ 1342 | "native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 1343 | "tokio 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 1344 | ] 1345 | 1346 | [[package]] 1347 | name = "tokio-util" 1348 | version = "0.2.0" 1349 | source = "registry+https://github.com/rust-lang/crates.io-index" 1350 | dependencies = [ 1351 | "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", 1352 | "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 1353 | "futures-sink 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 1354 | "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", 1355 | "pin-project-lite 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 1356 | "tokio 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 1357 | ] 1358 | 1359 | [[package]] 1360 | name = "tower-service" 1361 | version = "0.3.0" 1362 | source = "registry+https://github.com/rust-lang/crates.io-index" 1363 | 1364 | [[package]] 1365 | name = "try-lock" 1366 | version = "0.2.2" 1367 | source = "registry+https://github.com/rust-lang/crates.io-index" 1368 | 1369 | [[package]] 1370 | name = "unicase" 1371 | version = "2.6.0" 1372 | source = "registry+https://github.com/rust-lang/crates.io-index" 1373 | dependencies = [ 1374 | "version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", 1375 | ] 1376 | 1377 | [[package]] 1378 | name = "unicode-bidi" 1379 | version = "0.3.4" 1380 | source = "registry+https://github.com/rust-lang/crates.io-index" 1381 | dependencies = [ 1382 | "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 1383 | ] 1384 | 1385 | [[package]] 1386 | name = "unicode-normalization" 1387 | version = "0.1.12" 1388 | source = "registry+https://github.com/rust-lang/crates.io-index" 1389 | dependencies = [ 1390 | "smallvec 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 1391 | ] 1392 | 1393 | [[package]] 1394 | name = "unicode-segmentation" 1395 | version = "1.6.0" 1396 | source = "registry+https://github.com/rust-lang/crates.io-index" 1397 | 1398 | [[package]] 1399 | name = "unicode-width" 1400 | version = "0.1.7" 1401 | source = "registry+https://github.com/rust-lang/crates.io-index" 1402 | 1403 | [[package]] 1404 | name = "unicode-xid" 1405 | version = "0.2.0" 1406 | source = "registry+https://github.com/rust-lang/crates.io-index" 1407 | 1408 | [[package]] 1409 | name = "url" 1410 | version = "2.1.1" 1411 | source = "registry+https://github.com/rust-lang/crates.io-index" 1412 | dependencies = [ 1413 | "idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 1414 | "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 1415 | "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 1416 | ] 1417 | 1418 | [[package]] 1419 | name = "vcpkg" 1420 | version = "0.2.8" 1421 | source = "registry+https://github.com/rust-lang/crates.io-index" 1422 | 1423 | [[package]] 1424 | name = "vec_map" 1425 | version = "0.8.1" 1426 | source = "registry+https://github.com/rust-lang/crates.io-index" 1427 | 1428 | [[package]] 1429 | name = "version_check" 1430 | version = "0.1.5" 1431 | source = "registry+https://github.com/rust-lang/crates.io-index" 1432 | 1433 | [[package]] 1434 | name = "version_check" 1435 | version = "0.9.1" 1436 | source = "registry+https://github.com/rust-lang/crates.io-index" 1437 | 1438 | [[package]] 1439 | name = "want" 1440 | version = "0.3.0" 1441 | source = "registry+https://github.com/rust-lang/crates.io-index" 1442 | dependencies = [ 1443 | "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", 1444 | "try-lock 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 1445 | ] 1446 | 1447 | [[package]] 1448 | name = "wasi" 1449 | version = "0.9.0+wasi-snapshot-preview1" 1450 | source = "registry+https://github.com/rust-lang/crates.io-index" 1451 | 1452 | [[package]] 1453 | name = "wasm-bindgen" 1454 | version = "0.2.58" 1455 | source = "registry+https://github.com/rust-lang/crates.io-index" 1456 | dependencies = [ 1457 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 1458 | "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", 1459 | "serde_json 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)", 1460 | "wasm-bindgen-macro 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 1461 | ] 1462 | 1463 | [[package]] 1464 | name = "wasm-bindgen-backend" 1465 | version = "0.2.58" 1466 | source = "registry+https://github.com/rust-lang/crates.io-index" 1467 | dependencies = [ 1468 | "bumpalo 3.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 1469 | "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 1470 | "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", 1471 | "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", 1472 | "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 1473 | "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", 1474 | "wasm-bindgen-shared 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 1475 | ] 1476 | 1477 | [[package]] 1478 | name = "wasm-bindgen-futures" 1479 | version = "0.4.8" 1480 | source = "registry+https://github.com/rust-lang/crates.io-index" 1481 | dependencies = [ 1482 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 1483 | "js-sys 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)", 1484 | "wasm-bindgen 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 1485 | "web-sys 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)", 1486 | ] 1487 | 1488 | [[package]] 1489 | name = "wasm-bindgen-macro" 1490 | version = "0.2.58" 1491 | source = "registry+https://github.com/rust-lang/crates.io-index" 1492 | dependencies = [ 1493 | "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 1494 | "wasm-bindgen-macro-support 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 1495 | ] 1496 | 1497 | [[package]] 1498 | name = "wasm-bindgen-macro-support" 1499 | version = "0.2.58" 1500 | source = "registry+https://github.com/rust-lang/crates.io-index" 1501 | dependencies = [ 1502 | "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", 1503 | "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 1504 | "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", 1505 | "wasm-bindgen-backend 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 1506 | "wasm-bindgen-shared 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 1507 | ] 1508 | 1509 | [[package]] 1510 | name = "wasm-bindgen-shared" 1511 | version = "0.2.58" 1512 | source = "registry+https://github.com/rust-lang/crates.io-index" 1513 | 1514 | [[package]] 1515 | name = "wasm-bindgen-webidl" 1516 | version = "0.2.58" 1517 | source = "registry+https://github.com/rust-lang/crates.io-index" 1518 | dependencies = [ 1519 | "anyhow 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)", 1520 | "heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 1521 | "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", 1522 | "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", 1523 | "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 1524 | "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", 1525 | "wasm-bindgen-backend 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 1526 | "weedle 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", 1527 | ] 1528 | 1529 | [[package]] 1530 | name = "web-sys" 1531 | version = "0.3.35" 1532 | source = "registry+https://github.com/rust-lang/crates.io-index" 1533 | dependencies = [ 1534 | "anyhow 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)", 1535 | "js-sys 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)", 1536 | "sourcefile 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 1537 | "wasm-bindgen 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 1538 | "wasm-bindgen-webidl 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", 1539 | ] 1540 | 1541 | [[package]] 1542 | name = "weedle" 1543 | version = "0.10.0" 1544 | source = "registry+https://github.com/rust-lang/crates.io-index" 1545 | dependencies = [ 1546 | "nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 1547 | ] 1548 | 1549 | [[package]] 1550 | name = "winapi" 1551 | version = "0.2.8" 1552 | source = "registry+https://github.com/rust-lang/crates.io-index" 1553 | 1554 | [[package]] 1555 | name = "winapi" 1556 | version = "0.3.8" 1557 | source = "registry+https://github.com/rust-lang/crates.io-index" 1558 | dependencies = [ 1559 | "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 1560 | "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 1561 | ] 1562 | 1563 | [[package]] 1564 | name = "winapi-build" 1565 | version = "0.1.1" 1566 | source = "registry+https://github.com/rust-lang/crates.io-index" 1567 | 1568 | [[package]] 1569 | name = "winapi-i686-pc-windows-gnu" 1570 | version = "0.4.0" 1571 | source = "registry+https://github.com/rust-lang/crates.io-index" 1572 | 1573 | [[package]] 1574 | name = "winapi-x86_64-pc-windows-gnu" 1575 | version = "0.4.0" 1576 | source = "registry+https://github.com/rust-lang/crates.io-index" 1577 | 1578 | [[package]] 1579 | name = "winreg" 1580 | version = "0.6.2" 1581 | source = "registry+https://github.com/rust-lang/crates.io-index" 1582 | dependencies = [ 1583 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 1584 | ] 1585 | 1586 | [[package]] 1587 | name = "ws2_32-sys" 1588 | version = "0.2.1" 1589 | source = "registry+https://github.com/rust-lang/crates.io-index" 1590 | dependencies = [ 1591 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 1592 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 1593 | ] 1594 | 1595 | [[package]] 1596 | name = "yaml-rust" 1597 | version = "0.4.3" 1598 | source = "registry+https://github.com/rust-lang/crates.io-index" 1599 | dependencies = [ 1600 | "linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", 1601 | ] 1602 | 1603 | [metadata] 1604 | "checksum adler32 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5d2e7343e7fc9de883d1b0341e0b13970f764c14101234857d2ddafa1cb1cac2" 1605 | "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" 1606 | "checksum anyhow 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)" = "7825f6833612eb2414095684fcf6c635becf3ce97fe48cf6421321e93bfbd53c" 1607 | "checksum arc-swap 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d7b8a9123b8027467bce0099fe556c628a53c8d83df0507084c31e9ba2e39aff" 1608 | "checksum arrayref 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" 1609 | "checksum arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" 1610 | "checksum async-compression 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2c5c52622726d68ec35fec88edfb4ccb862d4f3b3bfa4af2f45142e69ef9b220" 1611 | "checksum async-trait 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)" = "750b1c38a1dfadd108da0f01c08f4cdc7ff1bb39b325f9c82cc972361780a6e1" 1612 | "checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 1613 | "checksum autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" 1614 | "checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" 1615 | "checksum base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" 1616 | "checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 1617 | "checksum blake2b_simd 0.5.10 (registry+https://github.com/rust-lang/crates.io-index)" = "d8fb2d74254a3a0b5cac33ac9f8ed0e44aa50378d9dbb2e5d83bd21ed1dc2c8a" 1618 | "checksum bumpalo 3.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1f359dc14ff8911330a51ef78022d376f25ed00248912803b58f00cb1c27f742" 1619 | "checksum bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "130aac562c0dd69c56b3b1cc8ffd2e17be31d0b6c25b61c96b76231aa23e39e1" 1620 | "checksum c2-chacha 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "214238caa1bf3a496ec3392968969cab8549f96ff30652c9e56885329315f6bb" 1621 | "checksum cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)" = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd" 1622 | "checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 1623 | "checksum chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "31850b4a4d6bae316f7a09e691c944c28299298837edc0a03f755618c23cbc01" 1624 | "checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" 1625 | "checksum constant_time_eq 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" 1626 | "checksum core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "25b9e03f145fd4f2bf705e07b900cd41fc636598fe5dc452fd0db1441c3f496d" 1627 | "checksum core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e7ca8a5221364ef15ce201e8ed2f609fc312682a8f4e0e3d4aa5879764e0fa3b" 1628 | "checksum crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" 1629 | "checksum crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ce446db02cdc3165b94ae73111e570793400d0794e46125cc4056c81cbb039f4" 1630 | "checksum dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3" 1631 | "checksum dirs-sys 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "afa0b23de8fd801745c471deffa6e12d248f962c9fd4b4c33787b055599bde7b" 1632 | "checksum doc-comment 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "923dea538cea0aa3025e8685b20d6ee21ef99c4f77e954a30febbaac5ec73a97" 1633 | "checksum dtoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "4358a9e11b9a09cf52383b451b49a169e8d797b68aa02301ff586d70d9661ea3" 1634 | "checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" 1635 | "checksum encoding_rs 0.8.22 (registry+https://github.com/rust-lang/crates.io-index)" = "cd8d03faa7fe0c1431609dfad7bbe827af30f82e1e2ae6f7ee4fca6bd764bc28" 1636 | "checksum enum_dispatch 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "97c676e703d999177c8de93e98dc09d6f4f3f0bb087d4384c3deca69bbe6a99d" 1637 | "checksum flate2 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6bd6d6f4752952feb71363cffc9ebac9411b75b87c6ab6058c40c8900cf43c0f" 1638 | "checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" 1639 | "checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 1640 | "checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 1641 | "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" 1642 | "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" 1643 | "checksum futures 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5c329ae8753502fb44ae4fc2b622fa2a94652c41e795143765ba0927f92ab780" 1644 | "checksum futures-channel 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "f0c77d04ce8edd9cb903932b608268b3fffec4163dc053b3b402bf47eac1f1a8" 1645 | "checksum futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "f25592f769825e89b92358db00d26f965761e094951ac44d3663ef25b7ac464a" 1646 | "checksum futures-executor 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "f674f3e1bcb15b37284a90cedf55afdba482ab061c407a9c0ebbd0f3109741ba" 1647 | "checksum futures-io 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a638959aa96152c7a4cddf50fcb1e3fede0583b27157c26e67d6f99904090dc6" 1648 | "checksum futures-macro 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "9a5081aa3de1f7542a794a397cde100ed903b0630152d0973479018fd85423a7" 1649 | "checksum futures-sink 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3466821b4bc114d95b087b850a724c6f83115e929bc88f1fa98a3304a944c8a6" 1650 | "checksum futures-task 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7b0a34e53cf6cdcd0178aa573aed466b646eb3db769570841fda0c7ede375a27" 1651 | "checksum futures-timer 3.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3de1a2b2a2a33d9e60e17980b60ee061eeaae96a5abe9121db0fdb9af167a1c5" 1652 | "checksum futures-util 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "22766cf25d64306bedf0384da004d05c9974ab104fcc4528f1236181c18004c5" 1653 | "checksum getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" 1654 | "checksum h2 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b9433d71e471c1736fd5a61b671fc0b148d7a2992f666c958d03cd8feb3b88d1" 1655 | "checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" 1656 | "checksum hermit-abi 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "eff2656d88f158ce120947499e971d743c05dbcbed62e5bd2f38f1698bbc3772" 1657 | "checksum http 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b708cc7f06493459026f53b9a61a7a121a5d1ec6238dee58ea4941132b30156b" 1658 | "checksum http-body 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "13d5ff830006f7646652e057693569bfe0d51760c0085a071769d142a205111b" 1659 | "checksum httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" 1660 | "checksum hyper 0.13.2 (registry+https://github.com/rust-lang/crates.io-index)" = "fa1c527bbc634be72aa7ba31e4e4def9bbb020f5416916279b7c705cd838893e" 1661 | "checksum hyper-tls 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3adcd308402b9553630734e9c36b77a7e48b3821251ca2493e8cd596763aafaa" 1662 | "checksum idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" 1663 | "checksum indexmap 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "076f042c5b7b98f31d205f1249267e12a6518c1481e9dae9764af19b707d2292" 1664 | "checksum iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" 1665 | "checksum itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" 1666 | "checksum js-sys 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)" = "7889c7c36282151f6bf465be4700359318aef36baa951462382eae49e9577cf9" 1667 | "checksum k8s-openapi 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "96affb18356fc998baa692c2185d198095ffdf6e6fff3bc9efb4927952f86851" 1668 | "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 1669 | "checksum kube 0.25.0 (registry+https://github.com/rust-lang/crates.io-index)" = "38433a71ab9de436861e0ba3bccd3422bd6d41cd1203cdeaf59240fe54666a6a" 1670 | "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 1671 | "checksum libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)" = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558" 1672 | "checksum linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ae91b68aebc4ddb91978b11a1b02ddd8602a05ec19002801c5666000e05e0f83" 1673 | "checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" 1674 | "checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" 1675 | "checksum memchr 2.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "53445de381a1f436797497c61d851644d0e8e88e6140f22872ad33a704933978" 1676 | "checksum mime 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)" = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" 1677 | "checksum mime_guess 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1a0ed03949aef72dbdf3116a383d7b38b4768e6f960528cd6a6044aa9ed68599" 1678 | "checksum miniz_oxide 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "aa679ff6578b1cddee93d7e82e263b94a575e0bfced07284eb0c037c1d2416a5" 1679 | "checksum mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)" = "302dec22bcf6bae6dfb69c647187f4b4d0fb6f535521f7bc022430ce8e12008f" 1680 | "checksum mio-named-pipes 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "f5e374eff525ce1c5b7687c4cef63943e7686524a387933ad27ca7ec43779cb3" 1681 | "checksum mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)" = "966257a94e196b11bb43aca423754d87429960a768de9414f3691d6957abf125" 1682 | "checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" 1683 | "checksum miow 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "396aa0f2003d7df8395cb93e09871561ccc3e785f0acb369170e8cc74ddf9226" 1684 | "checksum native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4b2df1a4c22fd44a62147fd8f13dd0f95c9d8ca7b2610299b2a2f9cf8964274e" 1685 | "checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" 1686 | "checksum nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6" 1687 | "checksum num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba" 1688 | "checksum num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" 1689 | "checksum num_cpus 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "46203554f085ff89c235cd12f7075f3233af9b11ed7c9e16dfe2560d03313ce6" 1690 | "checksum once_cell 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b1c601810575c99596d4afc46f78a678c80105117c379eb3650cf99b8a21ce5b" 1691 | "checksum openssl 0.10.28 (registry+https://github.com/rust-lang/crates.io-index)" = "973293749822d7dd6370d6da1e523b0d1db19f06c459134c658b2a4261378b52" 1692 | "checksum openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" 1693 | "checksum openssl-src 111.6.1+1.1.1d (registry+https://github.com/rust-lang/crates.io-index)" = "c91b04cb43c1a8a90e934e0cd612e2a5715d976d2d6cff4490278a0cddf35005" 1694 | "checksum openssl-sys 0.9.54 (registry+https://github.com/rust-lang/crates.io-index)" = "1024c0a59774200a555087a6da3f253a9095a5f344e353b212ac4c8b8e450986" 1695 | "checksum ordered-float 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "18869315e81473c951eb56ad5558bbc56978562d3ecfb87abb7a1e944cea4518" 1696 | "checksum percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" 1697 | "checksum pin-project 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7804a463a8d9572f13453c516a5faea534a2403d7ced2f0c7e100eeff072772c" 1698 | "checksum pin-project-internal 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "385322a45f2ecf3410c68d2a549a4a2685e8051d0f278e39743ff4e451cb9b3f" 1699 | "checksum pin-project-lite 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "237844750cfbb86f67afe27eee600dfbbcb6188d734139b534cbfbf4f96792ae" 1700 | "checksum pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587" 1701 | "checksum pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677" 1702 | "checksum ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" 1703 | "checksum proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)" = "ecd45702f76d6d3c75a80564378ae228a85f0b59d2f3ed43c91b4a69eb2ebfc5" 1704 | "checksum proc-macro-nested 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "369a6ed065f249a159e06c45752c780bda2fb53c995718f9e484d08daa9eb42e" 1705 | "checksum proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3acb317c6ff86a4e579dfa00fc5e6cca91ecbb4e7eb2df0468805b674eb88548" 1706 | "checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" 1707 | "checksum rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" 1708 | "checksum rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853" 1709 | "checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" 1710 | "checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" 1711 | "checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" 1712 | "checksum redox_users 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "09b23093265f8d200fa7b4c2c76297f47e681c655f6f1285a8780d6a022f7431" 1713 | "checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" 1714 | "checksum reqwest 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c0e798e19e258bf6c30a304622e3e9ac820e483b06a1857a026e1f109b113fe4" 1715 | "checksum rust-argon2 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2bc8af4bda8e1ff4932523b94d3dd20ee30a87232323eda55903ffd71d2fb017" 1716 | "checksum rustversion 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b3bba175698996010c4f6dce5e7f173b6eb781fce25d2cfc45e27091ce0b79f6" 1717 | "checksum ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8" 1718 | "checksum schannel 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)" = "507a9e6e8ffe0a4e0ebb9a10293e62fdf7657c06f1b8bb07a8fcf697d2abf295" 1719 | "checksum security-framework 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8ef2429d7cefe5fd28bd1d2ed41c944547d4ff84776f5935b456da44593a16df" 1720 | "checksum security-framework-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e31493fc37615debb8c5090a7aeb4a9730bc61e77ab10b9af59f1a202284f895" 1721 | "checksum serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449" 1722 | "checksum serde-value 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5a65a7291a8a568adcae4c10a677ebcedbc6c9cec91c054dee2ce40b0e3290eb" 1723 | "checksum serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64" 1724 | "checksum serde_json 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)" = "9371ade75d4c2d6cb154141b9752cf3781ec9c05e0e5cf35060e1e70ee7b9c25" 1725 | "checksum serde_urlencoded 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9ec5d77e2d4c73717816afac02670d5c4f534ea95ed430442cad02e7a6e32c97" 1726 | "checksum serde_yaml 0.8.11 (registry+https://github.com/rust-lang/crates.io-index)" = "691b17f19fc1ec9d94ec0b5864859290dff279dbd7b03f017afda54eb36c3c35" 1727 | "checksum signal-hook-registry 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94f478ede9f64724c5d173d7bb56099ec3e2d9fc2774aac65d34b8b890405f41" 1728 | "checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" 1729 | "checksum slog 2.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1cc9c640a4adbfbcc11ffb95efe5aa7af7309e002adab54b185507dbf2377b99" 1730 | "checksum slog-json 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ddc0d2aff1f8f325ef660d9a0eb6e6dcd20b30b3f581a5897f58bf42d061c37a" 1731 | "checksum slog-term 2.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "124501187c410b6a46fe8a47a48435ae462fae4e02d03c558d358f40b17308cb" 1732 | "checksum smallvec 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5c2fb2ec9bcd216a5b0d0ccf31ab17b5ed1d627960edff65bbe95d3ce221cefc" 1733 | "checksum snafu 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "546db9181bce2aa22ed883c33d65603b76335b4c2533a98289f54265043de7a1" 1734 | "checksum snafu-derive 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bdc75da2e0323f297402fd9c8fdba709bb04e4c627cbe31d19a2c91fc8d9f0e2" 1735 | "checksum socket2 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)" = "e8b74de517221a2cb01a53349cf54182acdc31a074727d3079068448c0676d85" 1736 | "checksum sourcefile 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4bf77cb82ba8453b42b6ae1d692e4cdc92f9a47beaf89a847c8be83f4e328ad3" 1737 | "checksum stream-cancel 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "15c2f5be039aed0d08b3596461637480d35858ca4360c905b0e802b934fa8e48" 1738 | "checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 1739 | "checksum syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)" = "af6f3550d8dff9ef7dc34d384ac6f107e5d31c8f57d9f28e0081503f547ac8f5" 1740 | "checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" 1741 | "checksum term 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c0863a3345e70f61d613eab32ee046ccd1bcc5f9105fe402c61fcd0c13eeb8b5" 1742 | "checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 1743 | "checksum thiserror 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)" = "ee14bf8e6767ab4c687c9e8bc003879e042a96fd67a3ba5934eadb6536bef4db" 1744 | "checksum thiserror-impl 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)" = "a7b51e1fbc44b5a0840be594fbc0f960be09050f2617e61e6aa43bef97cd3ef4" 1745 | "checksum thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" 1746 | "checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" 1747 | "checksum time 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "bcb8742444475438c500d017736dd7cf3d9c9dd1f0153cfa4af428692484e3ef" 1748 | "checksum time-macros 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9ae9b6e9f095bc105e183e3cd493d72579be3181ad4004fceb01adbe9eecab2d" 1749 | "checksum time-macros-impl 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e987cfe0537f575b5fc99909de6185f6c19c3ad8889e2275e686a873d0869ba1" 1750 | "checksum tokio 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8fdd17989496f49cdc57978c96f0c9fe5e4a58a8bddc6813c449a4624f6a030b" 1751 | "checksum tokio-macros 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "f4b1e7ed7d5d4c2af3d999904b0eebe76544897cdbfb2b9684bed2174ab20f7c" 1752 | "checksum tokio-tls 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7bde02a3a5291395f59b06ec6945a3077602fac2b07eeeaf0dee2122f3619828" 1753 | "checksum tokio-util 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "571da51182ec208780505a32528fc5512a8fe1443ab960b3f2f3ef093cd16930" 1754 | "checksum tower-service 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e987b6bf443f4b5b3b6f38704195592cca41c5bb7aedd3c3693c7081f8289860" 1755 | "checksum try-lock 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e604eb7b43c06650e854be16a2a03155743d3752dd1c943f6829e26b7a36e382" 1756 | "checksum unicase 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" 1757 | "checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" 1758 | "checksum unicode-normalization 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "5479532badd04e128284890390c1e876ef7a993d0570b3597ae43dfa1d59afa4" 1759 | "checksum unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" 1760 | "checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" 1761 | "checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" 1762 | "checksum url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb" 1763 | "checksum vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168" 1764 | "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" 1765 | "checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" 1766 | "checksum version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce" 1767 | "checksum want 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" 1768 | "checksum wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" 1769 | "checksum wasm-bindgen 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)" = "5205e9afdf42282b192e2310a5b463a6d1c1d774e30dc3c791ac37ab42d2616c" 1770 | "checksum wasm-bindgen-backend 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)" = "11cdb95816290b525b32587d76419facd99662a07e59d3cdb560488a819d9a45" 1771 | "checksum wasm-bindgen-futures 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8bbdd49e3e28b40dec6a9ba8d17798245ce32b019513a845369c641b275135d9" 1772 | "checksum wasm-bindgen-macro 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)" = "574094772ce6921576fb6f2e3f7497b8a76273b6db092be18fc48a082de09dc3" 1773 | "checksum wasm-bindgen-macro-support 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)" = "e85031354f25eaebe78bb7db1c3d86140312a911a106b2e29f9cc440ce3e7668" 1774 | "checksum wasm-bindgen-shared 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)" = "f5e7e61fc929f4c0dddb748b102ebf9f632e2b8d739f2016542b4de2965a9601" 1775 | "checksum wasm-bindgen-webidl 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)" = "ef012a0d93fc0432df126a8eaf547b2dce25a8ce9212e1d3cbeef5c11157975d" 1776 | "checksum web-sys 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)" = "aaf97caf6aa8c2b1dac90faf0db529d9d63c93846cca4911856f78a83cebf53b" 1777 | "checksum weedle 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3bb43f70885151e629e2a19ce9e50bd730fd436cfd4b666894c9ce4de9141164" 1778 | "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 1779 | "checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" 1780 | "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 1781 | "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1782 | "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1783 | "checksum winreg 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9" 1784 | "checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" 1785 | "checksum yaml-rust 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "65923dd1784f44da1d2c3dbbc5e822045628c590ba72123e1c73d3c230c4434d" 1786 | --------------------------------------------------------------------------------