Compare commits
87 Commits
v0.1
...
v0.2-beta-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d8b865848 | ||
|
|
3c87000b71 | ||
|
|
a99f2d08c1 | ||
|
|
f4ebbfc7a3 | ||
|
|
d23ac6679c | ||
|
|
cf30854bb1 | ||
|
|
6b6d71b5b9 | ||
|
|
077263af25 | ||
|
|
6486f65166 | ||
|
|
ac90d3ab13 | ||
|
|
dadd97de8d | ||
|
|
35acb384e1 | ||
|
|
5f9c46a80c | ||
|
|
a48f9866f2 | ||
|
|
c686fdffcb | ||
|
|
4554635194 | ||
|
|
45630ed369 | ||
|
|
4a8d43f736 | ||
|
|
3668b0d6b9 | ||
|
|
7d47eb1409 | ||
|
|
f78a8e9522 | ||
|
|
980cea6a07 | ||
|
|
8c0345edcf | ||
|
|
9eb0cf535f | ||
|
|
61d5440dc3 | ||
|
|
f9d1805ba0 | ||
|
|
909819c69d | ||
|
|
d27616776c | ||
|
|
2ca933fe27 | ||
|
|
b92c7320ca | ||
|
|
4fb11ecd87 | ||
|
|
a21cfff420 | ||
|
|
d690131565 | ||
|
|
2c47b6b573 | ||
|
|
922364ee5f | ||
|
|
87011f266e | ||
|
|
77d0687688 | ||
|
|
0e123c783b | ||
|
|
a4e99c2133 | ||
|
|
e050fea889 | ||
|
|
1ab4aa5d10 | ||
|
|
c002241e76 | ||
|
|
8ba9150da2 | ||
|
|
2b68da36ea | ||
|
|
04e49c5162 | ||
|
|
eaba0f3b60 | ||
|
|
bf32a40668 | ||
|
|
9354966e34 | ||
|
|
8b59becf35 | ||
|
|
43137d09f7 | ||
|
|
a6a6267863 | ||
|
|
1b3d6eae82 | ||
|
|
04db926faa | ||
|
|
4aeb63892e | ||
|
|
75af445a29 | ||
|
|
f047137819 | ||
|
|
b877b56d64 | ||
|
|
6e7931f74d | ||
|
|
83a768bdb5 | ||
|
|
9fec953077 | ||
|
|
8626b012b8 | ||
|
|
fb34a8c990 | ||
|
|
785d223eee | ||
|
|
e588029086 | ||
|
|
b870ac2eaa | ||
|
|
bcc5a33098 | ||
|
|
105c9fdada | ||
|
|
2c1c925c01 | ||
|
|
732ee66d87 | ||
|
|
123863c1ae | ||
|
|
b9b3bafb09 | ||
|
|
eb75dd5784 | ||
|
|
948f72096d | ||
|
|
97ced5e4bf | ||
|
|
e0c9da5d95 | ||
|
|
57996b44c4 | ||
|
|
0c10accf41 | ||
|
|
40128c288b | ||
|
|
932e770c40 | ||
|
|
4224cd737c | ||
|
|
28cd1d082b | ||
|
|
cf62fcc209 | ||
|
|
9e31a727a6 | ||
|
|
c7688a4410 | ||
|
|
ce1cb7f055 | ||
|
|
e35e7396b1 | ||
|
|
9f39a06cd8 |
@@ -11,14 +11,16 @@ jobs:
|
||||
- run:
|
||||
name: Create the credentials file
|
||||
command: sh scripts/createCredentials.sh
|
||||
- run:
|
||||
name: Build and deploy connector
|
||||
command: sh scripts/envCreation.sh
|
||||
- run: |
|
||||
echo 'export AZURE_AUTH_LOCATION=${outputPathCredsfile}' >> $BASH_ENV
|
||||
- run: |
|
||||
echo 'export KUBECONFIG=${outputPathKubeConfigFile}' >> $BASH_ENV
|
||||
- run:
|
||||
name: Dependencies
|
||||
command: go get -v -t -d ./...
|
||||
- run:
|
||||
name: Build
|
||||
command: V=1 make build
|
||||
- run:
|
||||
name: Tests
|
||||
command: go test -v ./...
|
||||
command: V=1 CI=1 make test
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -22,3 +22,6 @@ bin/
|
||||
|
||||
# Test credentials file
|
||||
credentials.json
|
||||
|
||||
# VS Code files
|
||||
.vscode/
|
||||
@@ -5,9 +5,7 @@ accepted.
|
||||
|
||||
## Contributor License Agreements
|
||||
|
||||
**Azure Specific**
|
||||
|
||||
If you are providing provider support for Azure then we have to jump through some legal hurdles first.
|
||||
If you are providing provider support for the Virtual Kubelet then we have to jump through some legal hurdles first.
|
||||
|
||||
The [Microsoft CLA](https://cla.microsoft.com/) must be signed by all
|
||||
contributors. Please fill out either the individual or corporate Contributor
|
||||
@@ -22,7 +20,7 @@ signed the CLA can be accepted into the repository.
|
||||
Each provider is responsible for reviewing PRs. Each provider has a primary and secondary maintainer for the purposes of maintaining their own code.
|
||||
Here's the current list of maintainers.
|
||||
|
||||
Otherwise for the primary Virtual Kubelet code, and overall project maintenance, these are the current maintainers. If you want to become a maintainer for the overall project please email ria.bhatia@microsoft.com.
|
||||
Otherwise for the primary Virtual Kubelet code, and overall project maintenance, these are the current maintainers. If you want to become a maintainer for the overall project, or be a provider for Virtual Kubelet, please email Ria Bhatia at ria.bhatia@microsoft.com.
|
||||
|
||||
### Overall Maintainers
|
||||
|
||||
|
||||
45
Gopkg.lock
generated
45
Gopkg.lock
generated
@@ -1,6 +1,12 @@
|
||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/Azure/go-ansiterm"
|
||||
packages = [".","winterm"]
|
||||
revision = "d6e3b3328b783f23731bc4d058875b0371ff8109"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/Azure/go-autorest"
|
||||
packages = ["autorest/adal","autorest/date"]
|
||||
@@ -37,6 +43,12 @@
|
||||
revision = "f006c2ac4710855cf0f916dd6b77acf6b048dc6e"
|
||||
version = "v1.0.3"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/cenkalti/backoff"
|
||||
packages = ["."]
|
||||
revision = "61153c768f31ee5f130071d08fc82b85208528de"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/cloudfoundry-incubator/candiedyaml"
|
||||
@@ -63,7 +75,7 @@
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/docker/distribution"
|
||||
packages = ["digest","reference"]
|
||||
packages = [".","context","digest","reference","registry/api/errcode","registry/api/v2","registry/client","registry/client/auth/challenge","registry/client/transport","registry/storage/cache","registry/storage/cache/memory","uuid"]
|
||||
revision = "48294d928ced5dd9b378f7fd7c6f5da3ff3f2c89"
|
||||
version = "v2.6.2"
|
||||
|
||||
@@ -181,6 +193,18 @@
|
||||
revision = "ee43cbb60db7bd22502942cccbc39059117352ab"
|
||||
version = "v0.1.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/gorilla/context"
|
||||
packages = ["."]
|
||||
revision = "1ea25387ff6f684839d82767c1733ff4d4d15d0a"
|
||||
version = "v1.1"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/gorilla/mux"
|
||||
packages = ["."]
|
||||
revision = "7f08801859139f86dfafd1c296e2cba9a80d292e"
|
||||
version = "v1.6.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/gregjones/httpcache"
|
||||
@@ -206,9 +230,8 @@
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/hyperhq/hypercli"
|
||||
packages = ["pkg/urlutil"]
|
||||
revision = "860cca29de31268664bf04bd7a87c3ca2c1d675e"
|
||||
version = "v1.10.16"
|
||||
packages = ["cliconfig","daemon/graphdriver","image","image/v1","layer","opts","pkg/archive","pkg/chrootarchive","pkg/fileutils","pkg/homedir","pkg/httputils","pkg/idtools","pkg/ioutils","pkg/jsonlog","pkg/jsonmessage","pkg/longpath","pkg/mflag","pkg/plugins","pkg/pools","pkg/promise","pkg/random","pkg/reexec","pkg/stringid","pkg/system","pkg/tarsum","pkg/term","pkg/term/windows","pkg/urlutil","pkg/version","reference","registry"]
|
||||
revision = "3e4e4fa373cbf8672bded67688b4821cd30b37f9"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/hyperhq/libcompose"
|
||||
@@ -263,6 +286,12 @@
|
||||
packages = ["."]
|
||||
revision = "06020f85339e21b2478f756a78e295255ffa4d6a"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/opencontainers/runc"
|
||||
packages = ["libcontainer/user"]
|
||||
revision = "baf6536d6259209c3edfa2b22237af82942d3dfa"
|
||||
version = "v0.1.1"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/pelletier/go-toml"
|
||||
packages = ["."]
|
||||
@@ -323,6 +352,12 @@
|
||||
revision = "25b30aa063fc18e48662b86996252eabdcf2f0c7"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/vbatts/tar-split"
|
||||
packages = ["archive/tar","tar/asm","tar/storage"]
|
||||
revision = "38ec4ddb06dedbea0a895c4848b248eb38af221b"
|
||||
version = "v0.10.2"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/xeipuuv/gojsonpointer"
|
||||
@@ -403,6 +438,6 @@
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "e476a3cf3fc7c556db93b5f202d0f578cdca9cda551563756ea30261aff2bd9b"
|
||||
inputs-digest = "febdb92c3131da3213797c6f20b297c751e9bddf5938064989494a21c3017333"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
|
||||
@@ -48,3 +48,11 @@
|
||||
[[constraint]]
|
||||
name = "github.com/xeipuuv/gojsonschema"
|
||||
revision = "0c8571ac0ce161a5feb57375a9cdf148c98c0f70"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/gorilla/mux"
|
||||
version = "1.6.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/hyperhq/hypercli"
|
||||
revision = "3e4e4fa373cbf8672bded67688b4821cd30b37f9"
|
||||
|
||||
16
ISSUE_TEMPLATE.md
Normal file
16
ISSUE_TEMPLATE.md
Normal file
@@ -0,0 +1,16 @@
|
||||
|
||||
---
|
||||
|
||||
### Environment summary
|
||||
|
||||
Provider (e.g. Azure, Hyper)
|
||||
|
||||
Version (e.g. 0.1, 0.2-beta)
|
||||
|
||||
K8s Master Info (e.g. AKS, ACS, Bare Metal)
|
||||
|
||||
Install Method (e.g. Helm Chart, )
|
||||
|
||||
### Issue Details
|
||||
|
||||
### Repo Steps
|
||||
73
README.md
73
README.md
@@ -3,7 +3,7 @@
|
||||
Virtual Kubelet is an open source [Kubernetes kubelet](https://kubernetes.io/docs/reference/generated/kubelet/) implementation that masquerades as a kubelet for the purposes of connecting Kubernetes to other APIs. This allows the nodes to be backed by other services like ACI, Hyper.sh, AWS, etc. This connector features a pluggable architecture and direct use of Kubernetes primitives, making it much easier to build on.
|
||||
|
||||
We invite the Kubernetes ecosystem to join us in empowering developers to build
|
||||
upon our base.
|
||||
upon our base. Join our slack channel named, virtual-kubelet, within the [Kubernetes slack group](https://kubernetes.slack.com/).
|
||||
|
||||
Please note this software is experimental and should not be used for anything
|
||||
resembling a production workload.
|
||||
@@ -20,6 +20,7 @@ The best description is "Kubernetes API on top, programmable back."
|
||||
+ [Adding a New Provider via the Provider Interface](#adding-a-new-provider-via-the-provider-interface)
|
||||
* [Testing](#testing)
|
||||
+ [Testing the Azure Provider Client](#testing-the-azure-provider-client)
|
||||
* [Known quirks and workarounds](#known-quirks-and-workarounds)
|
||||
* [Contributing](#contributing)
|
||||
|
||||
## How It Works
|
||||
@@ -41,7 +42,29 @@ Run the binary with your chosen provider:
|
||||
Now that the virtual-kubelet is deployed run `kubectl get nodes` and you should see
|
||||
a `virtual-kubelet` node.
|
||||
|
||||
## Command Line Usage
|
||||
## Current Features
|
||||
|
||||
* Multiple containers per pod
|
||||
* Windows/Linux containers
|
||||
* Restart policies
|
||||
* Volumes
|
||||
* Empty dir
|
||||
* Git hub repo
|
||||
* Azure files
|
||||
* Config maps
|
||||
* Secrets
|
||||
* Pod status
|
||||
* Resource limits (Mem and Cores)
|
||||
* Environment variables
|
||||
* Public IPs
|
||||
* kubectl logs
|
||||
|
||||
## Current Limitations
|
||||
|
||||
* kubectl exec
|
||||
* Metrics
|
||||
|
||||
## Command-Line Usage
|
||||
|
||||
```bash
|
||||
virtual-kubelet implements the Kubelet interface with a pluggable
|
||||
@@ -54,35 +77,21 @@ Usage:
|
||||
|
||||
Available Commands:
|
||||
help Help about any command
|
||||
version A brief description of your command
|
||||
version Show the version of the program
|
||||
|
||||
Flags:
|
||||
-h, --help help for virtual-kubelet
|
||||
--kubeconfig string config file (default is $HOME/.kube/config)
|
||||
--namespace string kuberentes namespace (default is 'all')
|
||||
--namespace string kubernetes namespace (default is 'all')
|
||||
--nodename string kubernetes node name (default "virtual-kubelet")
|
||||
--os string Operating System (Linux/Windows) (default "Linux")
|
||||
--provider string cloud provider
|
||||
--provider-config string cloud provider configuration file
|
||||
--taint string apply taint to node, making scheduling explicit
|
||||
-t, --toggle Help message for toggle
|
||||
|
||||
Use "virtual-kubelet [command] --help" for more information about a command.
|
||||
```
|
||||
|
||||
## Deploy as a Pod by Helm Chart
|
||||
|
||||
Run these commands to deploy the virtual kubeletwhich connects your Kubernetes cluster to Azure Container Instances.
|
||||
If you want to run the connector from the Azure commandline check out this.
|
||||
|
||||
```bash
|
||||
RELEASE_NAME=virtual-kubelet
|
||||
CHART_URL=https://github.com/virtual-kubelet/virtual-kubelet/blob/master/charts/virtual-kubelet-0.1.0.tgz
|
||||
|
||||
helm install "$CHART_URL" --name "$RELEASE_NAME" \
|
||||
--set env.azureClientId=<YOUR-AZURECLIENTID-HERE>,env.azureClientKey=<YOUR-AZURECLIENTKEY-HERE>,env.azureTenantId=<YOUR-AZURETENANTID-HERE>,env.azureSubscriptionId=<YOUR-AZURESUBSCRIPTIONID-HERE>,env.aciResourceGroup=<YOUR-ACIRESOURCEGROUP-HERE>,env.nodeName=<YOUR-NODE-NAME>, env.nodeOsType=<Linux|Windows>,env.nodeTaint=<YOUR-NODE-TAINT>
|
||||
```
|
||||
|
||||
## Providers
|
||||
|
||||
This project features a pluggable provider interface developers can implement
|
||||
@@ -100,16 +109,7 @@ The Azure Container Instances Provider allows you to utilize both
|
||||
typical pods on VMs and Azure Container instances simultaneously in the
|
||||
same Kubernetes cluster.
|
||||
|
||||
```bash
|
||||
./bin/virtual-kubelet --provider azure
|
||||
```
|
||||
|
||||
#### Environment Variables
|
||||
|
||||
`ACI_RESOURCE_GROUP` must be set to the name of a valid Azure resource group where your
|
||||
ACI workload will be run.
|
||||
|
||||
`ACI_REGION` must be set to the name of the region your `ACI_RESOURCE_GROUP` was created.
|
||||
You can find detailed instructions on how to set it up and how to test it in the [Azure Container Instances Provider documentation](./providers/azure/README.md).
|
||||
|
||||
#### Configuration File
|
||||
|
||||
@@ -160,7 +160,7 @@ type Provider interface {
|
||||
Capacity() v1.ResourceList
|
||||
|
||||
// NodeConditions returns a list of conditions (Ready, OutOfDisk, etc), which is polled periodically to update the node status
|
||||
// within Kuberentes.
|
||||
// within Kubernetes.
|
||||
NodeConditions() []v1.NodeCondition
|
||||
|
||||
// OperatingSystem returns the operating system the provider is for.
|
||||
@@ -181,6 +181,19 @@ set to a credentials file.
|
||||
You can generate this file by following the instructions listed in the
|
||||
[README](providers/azure/client/README.md) for that package.
|
||||
|
||||
## Known quirks and workarounds
|
||||
|
||||
### Missing Load Balancer IP addresses for services
|
||||
|
||||
#### When Virtual Kubelet is installed on a cluster, I cannot create external-IPs for a Service
|
||||
|
||||
Kubernetes 1.9 introduces a new flag, `ServiceNodeExclusion`, for the control plane's Controller Manager. Enabling this flag in the Controller Manager's manifest allows Kubernetes to exclude Virtual Kubelet nodes from being added to Load Balancer pools, allowing you to create public facing services with external IPs without issue.
|
||||
|
||||
#### Workaround
|
||||
|
||||
Cluster requirements: Kubernetes 1.9 or above
|
||||
|
||||
Enable the ServiceNodeExclusion flag, by modifying the Controller Manager manifest and adding `--feature-gates=ServiceNodeExclusion=true` to the command line arguments.
|
||||
|
||||
## Contributing
|
||||
|
||||
@@ -194,4 +207,4 @@ provided by the bot. You will only need to do this once across all repos using o
|
||||
|
||||
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
|
||||
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
|
||||
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
||||
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
||||
Binary file not shown.
BIN
charts/virtual-kubelet-for-aks-0.1.3.tgz
Normal file
BIN
charts/virtual-kubelet-for-aks-0.1.3.tgz
Normal file
Binary file not shown.
8
charts/virtual-kubelet-for-aks/Chart.yaml
Normal file
8
charts/virtual-kubelet-for-aks/Chart.yaml
Normal file
@@ -0,0 +1,8 @@
|
||||
name: virtual-kubelet-for-aks
|
||||
version: 0.1.3
|
||||
description: a Helm chart to install virtual kubelet in an AKS or ACS cluster.
|
||||
sources:
|
||||
- https://github.com/virtual-kubelet/virtual-kubelet
|
||||
maintainers:
|
||||
- name: Robbie Zhang
|
||||
email: junjiez@microsoft.com
|
||||
5
charts/virtual-kubelet-for-aks/templates/NOTES.txt
Normal file
5
charts/virtual-kubelet-for-aks/templates/NOTES.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
The virtual kubelet is getting deployed on your cluster.
|
||||
|
||||
To verify that virtual kubelet has started, run:
|
||||
|
||||
kubectl --namespace={{ .Release.Namespace }} get pods -l "app={{ template "fullname" . }}"
|
||||
16
charts/virtual-kubelet-for-aks/templates/_helpers.tpl
Normal file
16
charts/virtual-kubelet-for-aks/templates/_helpers.tpl
Normal file
@@ -0,0 +1,16 @@
|
||||
{{/* vim: set filetype=mustache: */}}
|
||||
{{/*
|
||||
Expand the name of the chart.
|
||||
*/}}
|
||||
{{- define "name" -}}
|
||||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Create a default fully qualified app name.
|
||||
We truncate at 24 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
||||
*/}}
|
||||
{{- define "fullname" -}}
|
||||
{{- $name := default .Chart.Name .Values.nameOverride -}}
|
||||
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
||||
57
charts/virtual-kubelet-for-aks/templates/deployment.yaml
Normal file
57
charts/virtual-kubelet-for-aks/templates/deployment.yaml
Normal file
@@ -0,0 +1,57 @@
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ template "fullname" . }}
|
||||
spec:
|
||||
replicas: 1
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: {{ template "fullname" . }}
|
||||
spec:
|
||||
containers:
|
||||
- name: {{ template "fullname" . }}
|
||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
env:
|
||||
- name: KUBELET_PORT
|
||||
value: "10250"
|
||||
- name: ACS_CREDENTIAL_LOCATION
|
||||
value: /etc/virtual-kubelet/acs.json
|
||||
- name: AZURE_TENANT_ID
|
||||
value: {{ .Values.env.azureTenantId }}
|
||||
- name: AZURE_SUBSCRIPTION_ID
|
||||
value: {{ .Values.env.azureSubscriptionId }}
|
||||
- name: AZURE_CLIENT_ID
|
||||
value: {{ .Values.env.azureClientId }}
|
||||
- name: AZURE_CLIENT_SECRET
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ template "fullname" . }}
|
||||
key: clientSecret
|
||||
- name: ACI_RESOURCE_GROUP
|
||||
value: {{ .Values.env.aciResourceGroup }}
|
||||
- name: ACI_REGION
|
||||
value: {{ default "westus" .Values.env.aciRegion }}
|
||||
- name: APISERVER_CERT_LOCATION
|
||||
value: /etc/virtual-kubelet/cert.pem
|
||||
- name: APISERVER_KEY_LOCATION
|
||||
value: /etc/virtual-kubelet/key.pem
|
||||
- name: VKUBELET_POD_IP
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: status.podIP
|
||||
volumeMounts:
|
||||
- name: credentials
|
||||
mountPath: "/etc/virtual-kubelet"
|
||||
- name: acs-credential
|
||||
mountPath: "/etc/virtual-kubelet/acs.json"
|
||||
command: ["virtual-kubelet"]
|
||||
args: ["--provider", "azure", "--namespace", "default", "--nodename", {{ default "virtual-kubelet" .Values.env.nodeName | quote }} , "--os", {{ default "Linux" .Values.env.nodeOsType | quote }}, "--taint", {{ default "azure.com/aci" .Values.env.nodeTaint | quote }}]
|
||||
volumes:
|
||||
- name: credentials
|
||||
secret:
|
||||
secretName: {{ template "fullname" . }}
|
||||
- name: acs-credential
|
||||
hostPath:
|
||||
path: /etc/kubernetes/azure.json
|
||||
9
charts/virtual-kubelet-for-aks/templates/secrets.yaml
Normal file
9
charts/virtual-kubelet-for-aks/templates/secrets.yaml
Normal file
@@ -0,0 +1,9 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: {{ template "fullname" . }}
|
||||
type: Opaque
|
||||
data:
|
||||
cert.pem: {{ (default "TUlTU0lORw==" .Values.env.apiserverCert) | quote }}
|
||||
key.pem: {{ (default "TUlTU0lORw==" .Values.env.apiserverKey) | quote }}
|
||||
clientSecret: {{ default "" .Values.env.azureClientKey | b64enc | quote }}
|
||||
16
charts/virtual-kubelet-for-aks/values.yaml
Normal file
16
charts/virtual-kubelet-for-aks/values.yaml
Normal file
@@ -0,0 +1,16 @@
|
||||
image:
|
||||
repository: microsoft/virtual-kubelet
|
||||
tag: 0.2-beta-6
|
||||
pullPolicy: Always
|
||||
env:
|
||||
azureClientId:
|
||||
azureClientKey:
|
||||
azureTenantId:
|
||||
azureSubscriptionId:
|
||||
aciResourceGroup:
|
||||
aciRegion:
|
||||
nodeName:
|
||||
nodeTaint:
|
||||
nodeOsType:
|
||||
apiserverCert:
|
||||
apiserverKey:
|
||||
@@ -14,19 +14,28 @@ spec:
|
||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
env:
|
||||
- name: KUBELET_PORT
|
||||
value: "10250"
|
||||
- name: AZURE_AUTH_LOCATION
|
||||
value: /etc/virtual-kubelet/credentials.json
|
||||
- name: ACI_RESOURCE_GROUP
|
||||
value: {{ .Values.env.aciResourceGroup }}
|
||||
- name: ACI_REGION
|
||||
value: {{ default "westus" .Values.env.aciRegion }}
|
||||
- name: APISERVER_CERT_LOCATION
|
||||
value: /etc/virtual-kubelet/cert.pem
|
||||
- name: APISERVER_KEY_LOCATION
|
||||
value: /etc/virtual-kubelet/key.pem
|
||||
- name: VKUBELET_POD_IP
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: status.podIP
|
||||
volumeMounts:
|
||||
- name: credentials
|
||||
mountPath: "/etc/virtual-kubelet"
|
||||
readOnly: true
|
||||
command: ["virtual-kubelet"]
|
||||
args: ["--provider", "azure", "--nodename", {{ default "aci-conn" .Values.env.nodeName | quote }} , "--os", {{ default "Linux" .Values.env.nodeOsType | quote }}, "--taint", {{ default "azure.com/aci" .Values.env.nodeTaint | quote }}]
|
||||
args: ["--provider", "azure", "--namespace", "default", "--nodename", {{ default "virtual-kubelet" .Values.env.nodeName | quote }} , "--os", {{ default "Linux" .Values.env.nodeOsType | quote }}, "--taint", {{ default "azure.com/aci" .Values.env.nodeTaint | quote }}]
|
||||
volumes:
|
||||
- name: credentials
|
||||
secret:
|
||||
secretName: {{ template "fullname" . }}
|
||||
secretName: {{ template "fullname" . }}
|
||||
|
||||
@@ -4,4 +4,6 @@ metadata:
|
||||
name: {{ template "fullname" . }}
|
||||
type: Opaque
|
||||
data:
|
||||
credentials.json: {{ printf "{ \"clientId\": \"%s\", \"clientSecret\": \"%s\", \"subscriptionId\": \"%s\", \"tenantId\": \"%s\", \"activeDirectoryEndpointUrl\": \"https://login.microsoftonline.com/\", \"resourceManagerEndpointUrl\": \"https://management.azure.com/\", \"activeDirectoryGraphResourceId\": \"https://graph.windows.net/\", \"sqlManagementEndpointUrl\": \"database.windows.net\", \"galleryEndpointUrl\": \"https://gallery.azure.com/\", \"managementEndpointUrl\": \"https://management.core.windows.net/\" }" (default "MISSING" .Values.env.azureClientId) (default "MISSING" .Values.env.azureClientKey) (default "MISSING" .Values.env.azureSubscriptionId) (default "MISSING" .Values.env.azureTenantId) | b64enc | quote }}
|
||||
credentials.json: {{ printf "{ \"clientId\": \"%s\", \"clientSecret\": \"%s\", \"subscriptionId\": \"%s\", \"tenantId\": \"%s\", \"activeDirectoryEndpointUrl\": \"https://login.microsoftonline.com/\", \"resourceManagerEndpointUrl\": \"https://management.azure.com/\", \"activeDirectoryGraphResourceId\": \"https://graph.windows.net/\", \"sqlManagementEndpointUrl\": \"database.windows.net\", \"galleryEndpointUrl\": \"https://gallery.azure.com/\", \"managementEndpointUrl\": \"https://management.core.windows.net/\" }" (default "MISSING" .Values.env.azureClientId) (default "MISSING" .Values.env.azureClientKey) (default "MISSING" .Values.env.azureSubscriptionId) (default "MISSING" .Values.env.azureTenantId) | b64enc | quote }}
|
||||
cert.pem: {{ (default "TUlTU0lORw==" .Values.env.apiserverCert) | quote }}
|
||||
key.pem: {{ (default "TUlTU0lORw==" .Values.env.apiserverKey) | quote }}
|
||||
|
||||
@@ -11,4 +11,6 @@ env:
|
||||
aciRegion:
|
||||
nodeName:
|
||||
nodeTaint:
|
||||
nodeOsType:
|
||||
nodeOsType:
|
||||
apiserverCert:
|
||||
apiserverKey:
|
||||
|
||||
@@ -72,7 +72,7 @@ func init() {
|
||||
// will be global for your application.
|
||||
//RootCmd.PersistentFlags().StringVar(&kubeletConfig, "config", "", "config file (default is $HOME/.virtual-kubelet.yaml)")
|
||||
RootCmd.PersistentFlags().StringVar(&kubeConfig, "kubeconfig", "", "config file (default is $HOME/.kube/config)")
|
||||
RootCmd.PersistentFlags().StringVar(&kubeNamespace, "namespace", "", "kuberentes namespace (default is 'all')")
|
||||
RootCmd.PersistentFlags().StringVar(&kubeNamespace, "namespace", "", "kubernetes namespace (default is 'all')")
|
||||
RootCmd.PersistentFlags().StringVar(&nodeName, "nodename", "virtual-kubelet", "kubernetes node name")
|
||||
RootCmd.PersistentFlags().StringVar(&operatingSystem, "os", "Linux", "Operating System (Linux/Windows)")
|
||||
RootCmd.PersistentFlags().StringVar(&provider, "provider", "", "cloud provider")
|
||||
@@ -81,7 +81,7 @@ func init() {
|
||||
|
||||
// Cobra also supports local flags, which will only run
|
||||
// when this action is called directly.
|
||||
RootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
||||
// RootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
||||
}
|
||||
|
||||
// initConfig reads in config file and ENV variables if set.
|
||||
|
||||
@@ -24,13 +24,8 @@ import (
|
||||
// versionCmd represents the version command
|
||||
var versionCmd = &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "A brief description of your command",
|
||||
Long: `A longer description that spans multiple lines and likely contains examples
|
||||
and usage of using your command. For example:
|
||||
|
||||
Cobra is a CLI library for Go that empowers applications.
|
||||
This application is a tool to generate the needed files
|
||||
to quickly create a Cobra application.`,
|
||||
Short: "Show the version of the program",
|
||||
Long: `Show the version of the program`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Printf("Version: %s, Built: %s", version.Version, version.BuildTime)
|
||||
},
|
||||
|
||||
24
examples/iis-pod.yaml
Normal file
24
examples/iis-pod.yaml
Normal file
@@ -0,0 +1,24 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: iis
|
||||
spec:
|
||||
containers:
|
||||
- image: microsoft/iis
|
||||
imagePullPolicy: Always
|
||||
name: iis
|
||||
resources:
|
||||
requests:
|
||||
memory: 4G
|
||||
cpu: 2
|
||||
ports:
|
||||
- containerPort: 80
|
||||
name: http
|
||||
protocol: TCP
|
||||
- containerPort: 443
|
||||
name: https
|
||||
dnsPolicy: Default
|
||||
automountServiceAccountToken: false
|
||||
tolerations:
|
||||
- key: azure.com/aci
|
||||
effect: NoSchedule
|
||||
@@ -19,3 +19,6 @@ spec:
|
||||
name: https
|
||||
dnsPolicy: ClusterFirst
|
||||
nodeName: virtual-kubelet
|
||||
tolerations:
|
||||
- key: azure.com/aci
|
||||
effect: NoSchedule
|
||||
|
||||
@@ -257,11 +257,11 @@ func (rm *ResourceManager) watchSecrets() {
|
||||
func (rm *ResourceManager) incrementRefCounters(p *v1.Pod) {
|
||||
for _, c := range p.Spec.Containers {
|
||||
for _, e := range c.Env {
|
||||
if e.ValueFrom.ConfigMapKeyRef != nil {
|
||||
if e.ValueFrom != nil && e.ValueFrom.ConfigMapKeyRef != nil {
|
||||
rm.configMapRef[e.ValueFrom.ConfigMapKeyRef.Name]++
|
||||
}
|
||||
|
||||
if e.ValueFrom.SecretKeyRef != nil {
|
||||
if e.ValueFrom != nil && e.ValueFrom.SecretKeyRef != nil {
|
||||
rm.secretRef[e.ValueFrom.SecretKeyRef.Name]++
|
||||
}
|
||||
}
|
||||
@@ -277,11 +277,11 @@ func (rm *ResourceManager) incrementRefCounters(p *v1.Pod) {
|
||||
func (rm *ResourceManager) decrementRefCounters(p *v1.Pod) {
|
||||
for _, c := range p.Spec.Containers {
|
||||
for _, e := range c.Env {
|
||||
if e.ValueFrom.ConfigMapKeyRef != nil {
|
||||
if e.ValueFrom != nil && e.ValueFrom.ConfigMapKeyRef != nil {
|
||||
rm.configMapRef[e.ValueFrom.ConfigMapKeyRef.Name]--
|
||||
}
|
||||
|
||||
if e.ValueFrom.SecretKeyRef != nil {
|
||||
if e.ValueFrom != nil && e.ValueFrom.SecretKeyRef != nil {
|
||||
rm.secretRef[e.ValueFrom.SecretKeyRef.Name]--
|
||||
}
|
||||
}
|
||||
|
||||
10
providers/README.md
Normal file
10
providers/README.md
Normal file
@@ -0,0 +1,10 @@
|
||||
Follow these steps to be accepted as a provider within the Virtual Kubelet repo.
|
||||
|
||||
1. Replicate the life-cycle of a pod for example creation and deletion of a pod and how that maps to your service.
|
||||
2. Create a new provider folder with a descriptive name and the necessary code.
|
||||
3. When committing your code add a README.md, helm chart, dockerfile and specify a maintainer of the provider.
|
||||
4. Within the PR itself add a justification for why the provider should be accepted, as well as customer use cases if applicable.
|
||||
|
||||
Some providers are translations of Virtual Kubelet to allow others to adapt their service or applications that are written in other languages.
|
||||
|
||||
|
||||
@@ -1,62 +1,212 @@
|
||||
# Kubernetes ACI connector with AKS
|
||||
# Kubernetes virtual-kubelet with ACI
|
||||
|
||||
Azure Container Instances (ACI) provide a hosted environment for running containers in Azure. When using ACI, there is no need to manage the underlying compute infrastructure, Azure handles this management for you. When running containers in ACI, you are charged by the second for each running container.
|
||||
|
||||
The Azure Container Instances connector for Kubernetes configures an ACI instance as a node in any Kubernetes cluster. When using the ACI connector for Kubernetes, pods can be scheduled on an ACI instance as if the ACI instance is a standard Kubernetes node. This configuration allows you to take advantage of both the capabilities of Kubernetes and the management value and cost benefit of ACI.
|
||||
The Azure Container Instances provider for the Virtual Kubelet configures an ACI instance as a node in any Kubernetes cluster. When using the Virtual Kubelet ACI provider, pods can be scheduled on an ACI instance as if the ACI instance is a standard Kubernetes node. This configuration allows you to take advantage of both the capabilities of Kubernetes and the management value and cost benefit of ACI.
|
||||
|
||||
This document details configuring the ACI connector for Kubernetes on an Azure Container Service (AKS) cluster.
|
||||
This document details configuring the Virtual Kubelet ACI provider.
|
||||
|
||||
## Prerequisite
|
||||
|
||||
The steps detailed in this document assume that you have created an AKS Kubernetes cluster and have established a kubectl connection with the cluster. If you need these items see, the [Azure Container Service (AKS) quickstart][aks-quick-start].
|
||||
This guide assumes that you have a Kubernetes cluster up and running (can be `minikube`) and that `kubectl` is already configured to talk to it.
|
||||
|
||||
You also need the Azure CLI version **2.0.22** or later. Run `az --version` to find the version. If you need to install or upgrade, see [Install Azure CLI](/cli/azure/install-azure-cli).
|
||||
Other pre-requesites are:
|
||||
|
||||
## Installation
|
||||
* A [Microsoft Azure account](https://azure.microsoft.com/en-us/free/).
|
||||
* Install the [Azure CLI](#install-the-azure-cli).
|
||||
* Install the [Kubernetes CLI](#install-the-kubernetes-cli).
|
||||
* Install the [Helm CLI](#install-the-helm-cli).
|
||||
|
||||
To install the ACI connector for an AKS cluster, run the following Azure CLI command. Replace the values for the following arguments:
|
||||
### Install the Azure CLI
|
||||
|
||||
- resource-group - the resource group of the AKS cluster.
|
||||
- name - the name of the AKS cluster.
|
||||
- connector-name - the name given to the ACI connector.
|
||||
Install `az` by following the instructions for your operating system.
|
||||
See the [full installation instructions](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest) if yours isn't listed below.
|
||||
|
||||
```azurecli-interactive
|
||||
az aks install-connector --resource-group myResourceGroup --name myAKSCluster --connector-name myaciconnector
|
||||
#### MacOS
|
||||
|
||||
```console
|
||||
brew install azure-cli
|
||||
```
|
||||
|
||||
#### Windows
|
||||
|
||||
Download and run the [Azure CLI Installer (MSI)](https://aka.ms/InstallAzureCliWindows).
|
||||
|
||||
#### Ubuntu 64-bit
|
||||
|
||||
1. Add the azure-cli repo to your sources:
|
||||
```console
|
||||
echo "deb [arch=amd64] https://packages.microsoft.com/repos/azure-cli/ wheezy main" | \
|
||||
sudo tee /etc/apt/sources.list.d/azure-cli.list
|
||||
```
|
||||
1. Run the following commands to install the Azure CLI and its dependencies:
|
||||
```console
|
||||
sudo apt-key adv --keyserver packages.microsoft.com --recv-keys 52E16F86FEE04B979B07E28DB02C46DF417A0893
|
||||
sudo apt-get install apt-transport-https
|
||||
sudo apt-get update && sudo apt-get install azure-cli
|
||||
```
|
||||
|
||||
### Install the Kubernetes CLI
|
||||
|
||||
Install `kubectl` by running the following command:
|
||||
|
||||
```console
|
||||
az aks install-cli
|
||||
```
|
||||
|
||||
### Install the Helm CLI
|
||||
|
||||
[Helm](https://github.com/kubernetes/helm) is a tool for installing pre-configured applications on Kubernetes.
|
||||
Install `helm` by running the following command:
|
||||
|
||||
#### MacOS
|
||||
|
||||
```console
|
||||
brew install kubernetes-helm
|
||||
```
|
||||
|
||||
#### Windows
|
||||
|
||||
1. Download the latest [Helm release](https://storage.googleapis.com/kubernetes-helm/helm-v2.7.2-windows-amd64.tar.gz).
|
||||
1. Decompress the tar file.
|
||||
1. Copy **helm.exe** to a directory on your PATH.
|
||||
|
||||
#### Linux
|
||||
|
||||
```console
|
||||
curl https://raw.githubusercontent.com/kubernetes/helm/master/scripts/get | bash
|
||||
```
|
||||
---
|
||||
|
||||
## Cluster and Azure Account Setup
|
||||
|
||||
Now that we have all the tools, we will set up your Azure account to work with ACI.
|
||||
|
||||
### Configure your Azure account
|
||||
|
||||
First let's identify your Azure subscription and save it for use later on in the quickstart.
|
||||
|
||||
1. Run `az login` and follow the instructions in the command output to authorize `az` to use your account
|
||||
1. List your Azure subscriptions:
|
||||
```console
|
||||
az account list -o table
|
||||
```
|
||||
1. Copy your subscription ID and save it in an environment variable:
|
||||
|
||||
**Bash**
|
||||
```console
|
||||
export AZURE_SUBSCRIPTION_ID="<SubscriptionId>"
|
||||
```
|
||||
|
||||
**PowerShell**
|
||||
```console
|
||||
$env:AZURE_SUBSCRIPTION_ID = "<SubscriptionId>"
|
||||
```
|
||||
|
||||
### Create a Resource Group for ACI
|
||||
|
||||
To use Azure Container Instances, you must provide a resource group. Create one with the az cli using the following command.
|
||||
|
||||
```console
|
||||
export ACI_REGION=eastus
|
||||
az group create --name aci-group --location "$ACI_REGION"
|
||||
export AZURE_RG=aci-group
|
||||
```
|
||||
|
||||
### Create a service principal
|
||||
|
||||
This creates an identity for the Virtual Kubelet ACI provider to use when provisioning
|
||||
resources on your account on behalf of Kubernetes.
|
||||
|
||||
1. Create a service principal with RBAC enabled for the quickstart:
|
||||
```console
|
||||
az ad sp create-for-rbac --name virtual-kubelet-quickstart -o table
|
||||
```
|
||||
1. Save the values from the command output in environment variables:
|
||||
|
||||
**Bash**
|
||||
```console
|
||||
export AZURE_TENANT_ID=<Tenant>
|
||||
export AZURE_CLIENT_ID=<AppId>
|
||||
export AZURE_CLIENT_SECRET=<Password>
|
||||
```
|
||||
|
||||
**PowerShell**
|
||||
```console
|
||||
$env:AZURE_TENANT_ID = "<Tenant>"
|
||||
$env:AZURE_CLIENT_ID = "<AppId>"
|
||||
$env:AZURE_CLIENT_SECRET = "<Password>"
|
||||
```
|
||||
|
||||
### Setting up your Azure account to use ACI
|
||||
|
||||
You will need to enable ACI in your subscription:
|
||||
|
||||
```console
|
||||
az provider register -n Microsoft.ContainerInstance
|
||||
```
|
||||
|
||||
## Deployment of the ACI provider in your cluster
|
||||
|
||||
Run these commands to deploy the virtual kubelet which connects your Kubernetes cluster to Azure Container Instances.
|
||||
|
||||
If your cluster is an AKS cluster:
|
||||
|
||||
```console
|
||||
export VK_RELEASE=virtual-kubelet-for-aks-0.1.3
|
||||
````
|
||||
|
||||
For any other type of Kubernetes cluster:
|
||||
|
||||
```console
|
||||
export VK_RELEASE=virtual-kubelet-0.1.0
|
||||
```
|
||||
|
||||
```console
|
||||
RELEASE_NAME=virtual-kubelet
|
||||
NODE_NAME=virtual-kubelet
|
||||
CHART_URL=https://github.com/virtual-kubelet/virtual-kubelet/raw/master/charts/$VK_RELEASE.tgz
|
||||
|
||||
curl https://raw.githubusercontent.com/virtual-kubelet/virtual-kubelet/master/scripts/createCertAndKey.sh > createCertAndKey.sh
|
||||
. createCertAndKey.sh
|
||||
|
||||
helm install "$CHART_URL" --name "$RELEASE_NAME" \
|
||||
--set env.azureClientId="$AZURE_CLIENT_ID",env.azureClientKey="$AZURE_CLIENT_SECRET",env.azureTenantId="$AZURE_TENANT_ID",env.azureSubscriptionId="$AZURE_SUBSCRIPTION_ID",env.aciResourceGroup="$AZURE_RG",env.nodeName="$NODE_NAME",env.nodeOsType=<Linux|Windows>,env.apiserverCert=$cert,env.apiserverKey=$key
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```
|
||||
NAME: myaciconnector
|
||||
LAST DEPLOYED: Tue Dec 5 21:12:33 2017
|
||||
```console
|
||||
NAME: virtual-kubelet
|
||||
LAST DEPLOYED: Thu Feb 15 13:17:01 2018
|
||||
NAMESPACE: default
|
||||
STATUS: DEPLOYED
|
||||
|
||||
RESOURCES:
|
||||
==> v1/Secret
|
||||
NAME TYPE DATA AGE
|
||||
myaciconnector-aci-connector Opaque 4 1s
|
||||
NAME TYPE DATA AGE
|
||||
virtual-kubelet-virtual-kubelet Opaque 3 1s
|
||||
|
||||
==> v1beta1/Deployment
|
||||
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
|
||||
myaciconnector-aci-connector 1 1 1 0 1s
|
||||
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
|
||||
virtual-kubelet-virtual-kubelet 1 1 1 0 1s
|
||||
|
||||
==> v1/Pod(related)
|
||||
NAME READY STATUS RESTARTS AGE
|
||||
myaciconnector-aci-connector-1218204046-cn81d 0/1 ContainerCreating 0 1s
|
||||
NAME READY STATUS RESTARTS AGE
|
||||
virtual-kubelet-virtual-kubelet-7bcf5dc749-6mvgp 0/1 ContainerCreating 0 1s
|
||||
|
||||
|
||||
NOTES:
|
||||
The aci-connector is getting deployed on your cluster.
|
||||
The virtual kubelet is getting deployed on your cluster.
|
||||
|
||||
To verify that aci-connector has started, run:
|
||||
To verify that virtual kubelet has started, run:
|
||||
|
||||
kubectl --namespace=default get pods -l "app=myaciconnector-aci-connector"
|
||||
kubectl --namespace=default get pods -l "app=virtual-kubelet-virtual-kubelet"
|
||||
```
|
||||
|
||||
## Validate the ACI connector
|
||||
## Validate the Virtual Kubelet ACI provider
|
||||
|
||||
To validate that the ACI connector has been installed, return a list of Kubernetes nodes using the [kubectl get nodes][kubectl-get] command. You should see a node that matches the name given to the ACI connector.
|
||||
To validate that the Virtual Kubelet has been installed, return a list of Kubernetes nodes using the [kubectl get nodes][kubectl-get] command. You should see a node that matches the name given to the ACI connector.
|
||||
|
||||
```azurecli-interactive
|
||||
kubectl get nodes
|
||||
@@ -65,59 +215,64 @@ kubectl get nodes
|
||||
Output:
|
||||
|
||||
```console
|
||||
NAME STATUS ROLES AGE VERSION
|
||||
aci-connector Ready <none> 2m v1.6.6
|
||||
aks-nodepool1-39289454-0 Ready agent 22h v1.7.7
|
||||
aks-nodepool1-39289454-1 Ready agent 22h v1.7.7
|
||||
aks-nodepool1-39289454-2 Ready agent 22h v1.7.7
|
||||
NAME STATUS ROLES AGE VERSION
|
||||
virtual-kubelet Ready <none> 2m v1.8.3
|
||||
aks-nodepool1-39289454-0 Ready agent 22h v1.7.7
|
||||
aks-nodepool1-39289454-1 Ready agent 22h v1.7.7
|
||||
aks-nodepool1-39289454-2 Ready agent 22h v1.7.7
|
||||
```
|
||||
|
||||
## Schedule a pod in ACI
|
||||
|
||||
Create a file named `aci-connector-test.yaml` and copy in the following YAML. Replace the `nodeName` value with the name given to the ACI connector.
|
||||
Create a file named `virtual-kubelet-test.yaml` and copy in the following YAML. Replace the `nodeName` value with the name given to the virtual kubelet node.
|
||||
|
||||
```yaml
|
||||
apiVersion: apps/v1beta1
|
||||
kind: Deployment
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: aci-helloworld
|
||||
name: helloworld
|
||||
spec:
|
||||
replicas: 1
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: aci-helloworld
|
||||
spec:
|
||||
containers:
|
||||
- name: aci-helloworld
|
||||
image: microsoft/aci-helloworld
|
||||
ports:
|
||||
- containerPort: 80
|
||||
nodeName: aci-connector
|
||||
containers:
|
||||
- image: microsoft/aci-helloworld
|
||||
imagePullPolicy: Always
|
||||
name: helloworld
|
||||
resources:
|
||||
requests:
|
||||
memory: 1G
|
||||
cpu: 1
|
||||
ports:
|
||||
- containerPort: 80
|
||||
name: http
|
||||
protocol: TCP
|
||||
- containerPort: 443
|
||||
name: https
|
||||
dnsPolicy: ClusterFirst
|
||||
nodeName: virtual-kubelet
|
||||
```
|
||||
|
||||
Run the application with the [kubectl create][kubectl-create] command.
|
||||
|
||||
```azurecli-interactive
|
||||
kubectl create -f aci-connector-test.yml
|
||||
```console
|
||||
kubectl create -f virtual-kubelet-test.yml
|
||||
```
|
||||
|
||||
Use the [kubectl get pods][kubectl-get] command with the `-o wide` argument to output a list of pods with the scheduled node.
|
||||
|
||||
```azurecli-interactive
|
||||
```console
|
||||
kubectl get pods -o wide
|
||||
```
|
||||
|
||||
Notice that the `kube-aci-demo` pod is running on the `myACIConnector` node.
|
||||
Notice that the `helloworld` pod is running on the `virtual-kubelet` node.
|
||||
|
||||
```console
|
||||
NAME READY STATUS RESTARTS AGE IP NODE
|
||||
aci-helloworld-2559879000-8vmjw 1/1 Running 0 39s 52.179.3.180 aci-connector
|
||||
aci-helloworld-2559879000-8vmjw 1/1 Running 0 39s 52.179.3.180 virtual-kubelet
|
||||
|
||||
```
|
||||
|
||||
To validate that the container is running in an Azure Container Instance, use the [az container list][az-container-list] Azure CLI command.
|
||||
|
||||
```azurecli-interactive
|
||||
```cli
|
||||
az container list -o table
|
||||
```
|
||||
|
||||
@@ -126,19 +281,18 @@ Output:
|
||||
```console
|
||||
Name ResourceGroup ProvisioningState Image IP:ports CPU/Memory OsType Location
|
||||
------------------------------- --------------- ------------------- ------------------------ --------------- --------------- -------- ----------
|
||||
aci-helloworld-2559879000-8vmjw myAKSCluster2 Succeeded microsoft/aci-helloworld 52.179.3.180:80 1.0 core/1.5 gb Linux eastus
|
||||
helloworld-2559879000-8vmjw myResourceGroup Succeeded microsoft/aci-helloworld 52.179.3.180:80 1.0 core/1.5 gb Linux eastus
|
||||
```
|
||||
|
||||
## Remove the ACI connector
|
||||
## Remove the Virtual Kubelet
|
||||
|
||||
To remove the ACI connector, run the following command. Replace the argument values with the name of the connector, AKS cluster, and the AKS cluster resource group.
|
||||
You can remove your Virtual Kubelet node, you can delete the Helm deployment, by running the following command:
|
||||
|
||||
```azurecli-interactive
|
||||
az aks remove-connector --resource-group myResourceGroup --name myAKSCluster --connector-name myaciconnector
|
||||
```
|
||||
helm delete virtual-kubelet --purge
|
||||
```
|
||||
|
||||
<!-- LINKS -->
|
||||
[aks-quick-start]: https://docs.microsoft.com/en-us/azure/aks/kubernetes-walkthrough
|
||||
[kubectl-create]: https://kubernetes.io/docs/user-guide/kubectl/v1.6/#create
|
||||
[kubectl-get]: https://kubernetes.io/docs/user-guide/kubectl/v1.8/#get
|
||||
[az-container-list]: https://docs.microsoft.com/en-us/cli/azure/aks?view=azure-cli-latest#az_aks_list
|
||||
[az-container-list]: https://docs.microsoft.com/en-us/cli/azure/aks?view=azure-cli-latest#az_aks_list
|
||||
|
||||
@@ -7,11 +7,14 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/virtual-kubelet/virtual-kubelet/manager"
|
||||
client "github.com/virtual-kubelet/virtual-kubelet/providers/azure/client"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/providers/azure/client/aci"
|
||||
"k8s.io/api/core/v1"
|
||||
k8serr "k8s.io/apimachinery/pkg/api/errors"
|
||||
@@ -20,17 +23,22 @@ import (
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
)
|
||||
|
||||
// The service account secret mount path.
|
||||
const serviceAccountSecretMountPath = "/var/run/secrets/kubernetes.io/serviceaccount"
|
||||
|
||||
// ACIProvider implements the virtual-kubelet provider interface and communicates with Azure's ACI APIs.
|
||||
type ACIProvider struct {
|
||||
aciClient *aci.Client
|
||||
resourceManager *manager.ResourceManager
|
||||
resourceGroup string
|
||||
region string
|
||||
nodeName string
|
||||
operatingSystem string
|
||||
cpu string
|
||||
memory string
|
||||
pods string
|
||||
aciClient *aci.Client
|
||||
resourceManager *manager.ResourceManager
|
||||
resourceGroup string
|
||||
region string
|
||||
nodeName string
|
||||
operatingSystem string
|
||||
cpu string
|
||||
memory string
|
||||
pods string
|
||||
internalIP string
|
||||
daemonEndpointPort int32
|
||||
}
|
||||
|
||||
// AuthConfig is the secret returned from an ImageRegistryCredential
|
||||
@@ -44,18 +52,35 @@ type AuthConfig struct {
|
||||
RegistryToken string `json:"registrytoken,omitempty"`
|
||||
}
|
||||
|
||||
// See https://docs.microsoft.com/en-us/azure/container-instances/container-instances-quotas for valid regions.
|
||||
var validAciRegions = []string{
|
||||
"westeurope",
|
||||
"westus",
|
||||
"eastus",
|
||||
"southeastasia",
|
||||
}
|
||||
|
||||
// isValidACIRegion checks to make sure we're using a valid ACI region
|
||||
func isValidACIRegion(region string) bool {
|
||||
regionLower := strings.ToLower(region)
|
||||
regionTrimmed := strings.Replace(regionLower, " ", "", -1)
|
||||
|
||||
for _, validRegion := range validAciRegions {
|
||||
if regionTrimmed == validRegion {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// NewACIProvider creates a new ACIProvider.
|
||||
func NewACIProvider(config string, rm *manager.ResourceManager, nodeName, operatingSystem string) (*ACIProvider, error) {
|
||||
func NewACIProvider(config string, rm *manager.ResourceManager, nodeName, operatingSystem string, internalIP string, daemonEndpointPort int32) (*ACIProvider, error) {
|
||||
var p ACIProvider
|
||||
var err error
|
||||
|
||||
p.resourceManager = rm
|
||||
|
||||
p.aciClient, err = aci.NewClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if config != "" {
|
||||
f, err := os.Open(config)
|
||||
if err != nil {
|
||||
@@ -68,6 +93,61 @@ func NewACIProvider(config string, rm *manager.ResourceManager, nodeName, operat
|
||||
}
|
||||
}
|
||||
|
||||
var azAuth *client.Authentication
|
||||
|
||||
if authFilepath := os.Getenv("AZURE_AUTH_LOCATION"); authFilepath != "" {
|
||||
auth, err := client.NewAuthenticationFromFile(authFilepath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
azAuth = auth
|
||||
}
|
||||
|
||||
if acsFilepath := os.Getenv("ACS_CREDENTIAL_LOCATION"); acsFilepath != "" {
|
||||
acsCredential, err := NewAcsCredential(acsFilepath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if acsCredential != nil {
|
||||
if acsCredential.Cloud != client.PublicCloud.Name {
|
||||
return nil, fmt.Errorf("ACI only supports Public Azure. '%v' is not supported", acsCredential.Cloud)
|
||||
}
|
||||
|
||||
azAuth = client.NewAuthentication(
|
||||
acsCredential.Cloud,
|
||||
acsCredential.ClientID,
|
||||
acsCredential.ClientSecret,
|
||||
acsCredential.SubscriptionID,
|
||||
acsCredential.TenantID)
|
||||
|
||||
p.resourceGroup = acsCredential.ResourceGroup
|
||||
p.region = acsCredential.Region
|
||||
}
|
||||
}
|
||||
|
||||
if clientID := os.Getenv("AZURE_CLIENT_ID"); clientID != "" {
|
||||
azAuth.ClientID = clientID
|
||||
}
|
||||
|
||||
if clientSecret := os.Getenv("AZURE_CLIENT_SECRET"); clientSecret != "" {
|
||||
azAuth.ClientSecret = clientSecret
|
||||
}
|
||||
|
||||
if tenantID := os.Getenv("AZURE_TENANT_ID"); tenantID != "" {
|
||||
azAuth.TenantID = tenantID
|
||||
}
|
||||
|
||||
if subscriptionID := os.Getenv("AZURE_SUBSCRIPTION_ID"); subscriptionID != "" {
|
||||
azAuth.SubscriptionID = subscriptionID
|
||||
}
|
||||
|
||||
p.aciClient, err = aci.NewClient(azAuth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if rg := os.Getenv("ACI_RESOURCE_GROUP"); rg != "" {
|
||||
p.resourceGroup = rg
|
||||
}
|
||||
@@ -81,6 +161,12 @@ func NewACIProvider(config string, rm *manager.ResourceManager, nodeName, operat
|
||||
if p.region == "" {
|
||||
return nil, errors.New("Region can not be empty please set ACI_REGION")
|
||||
}
|
||||
if r := p.region; !isValidACIRegion(r) {
|
||||
unsupportedRegionMessage := fmt.Sprintf("Region %s is invalid. Current supported regions are: %s",
|
||||
r, strings.Join(validAciRegions, ", "))
|
||||
|
||||
return nil, errors.New(unsupportedRegionMessage)
|
||||
}
|
||||
|
||||
// Set sane defaults for Capacity in case config is not supplied
|
||||
p.cpu = "20"
|
||||
@@ -89,6 +175,8 @@ func NewACIProvider(config string, rm *manager.ResourceManager, nodeName, operat
|
||||
|
||||
p.operatingSystem = operatingSystem
|
||||
p.nodeName = nodeName
|
||||
p.internalIP = internalIP
|
||||
p.daemonEndpointPort = daemonEndpointPort
|
||||
|
||||
return &p, err
|
||||
}
|
||||
@@ -121,6 +209,8 @@ func (p *ACIProvider) CreatePod(pod *v1.Pod) error {
|
||||
containerGroup.ContainerGroupProperties.Volumes = volumes
|
||||
containerGroup.ContainerGroupProperties.ImageRegistryCredentials = creds
|
||||
|
||||
filterServiceAccountSecretVolume(p.operatingSystem, &containerGroup)
|
||||
|
||||
// create ipaddress if containerPort is used
|
||||
count := 0
|
||||
for _, container := range containers {
|
||||
@@ -177,10 +267,9 @@ func (p *ACIProvider) DeletePod(pod *v1.Pod) error {
|
||||
// GetPod returns a pod by name that is running inside ACI
|
||||
// returns nil if a pod by that name is not found.
|
||||
func (p *ACIProvider) GetPod(namespace, name string) (*v1.Pod, error) {
|
||||
cg, err := p.aciClient.GetContainerGroup(p.resourceGroup, fmt.Sprintf("%s-%s", namespace, name))
|
||||
cg, err, status := p.aciClient.GetContainerGroup(p.resourceGroup, fmt.Sprintf("%s-%s", namespace, name))
|
||||
if err != nil {
|
||||
// Trap error for 404 and return gracefully
|
||||
if strings.Contains(err.Error(), "ResourceNotFound") {
|
||||
if *status == http.StatusNotFound {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
@@ -193,6 +282,33 @@ func (p *ACIProvider) GetPod(namespace, name string) (*v1.Pod, error) {
|
||||
return containerGroupToPod(cg)
|
||||
}
|
||||
|
||||
// GetContainerLogs returns the logs of a pod by name that is running inside ACI.
|
||||
func (p *ACIProvider) GetContainerLogs(namespace, podName, containerName string, tail int) (string, error) {
|
||||
logContent := ""
|
||||
cg, err, _ := p.aciClient.GetContainerGroup(p.resourceGroup, fmt.Sprintf("%s-%s", namespace, podName))
|
||||
if err != nil {
|
||||
return logContent, err
|
||||
}
|
||||
|
||||
if cg.Tags["NodeName"] != p.nodeName {
|
||||
return logContent, nil
|
||||
}
|
||||
// get logs from cg
|
||||
retry := 10
|
||||
for i := 0; i < retry; i++ {
|
||||
cLogs, err := p.aciClient.GetContainerLogs(p.resourceGroup, cg.Name, containerName, tail)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
time.Sleep(5000 * time.Millisecond)
|
||||
} else {
|
||||
logContent = cLogs.Content
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return logContent, err
|
||||
}
|
||||
|
||||
// GetPodStatus returns the status of a pod by name that is running inside ACI
|
||||
// returns nil if a pod by that name is not found.
|
||||
func (p *ACIProvider) GetPodStatus(namespace, name string) (*v1.PodStatus, error) {
|
||||
@@ -243,7 +359,7 @@ func (p *ACIProvider) Capacity() v1.ResourceList {
|
||||
}
|
||||
|
||||
// NodeConditions returns a list of conditions (Ready, OutOfDisk, etc), for updates to the node status
|
||||
// within Kuberentes.
|
||||
// within Kubernetes.
|
||||
func (p *ACIProvider) NodeConditions() []v1.NodeCondition {
|
||||
// TODO: Make these dynamic and augment with custom ACI specific conditions of interest
|
||||
return []v1.NodeCondition{
|
||||
@@ -290,6 +406,28 @@ func (p *ACIProvider) NodeConditions() []v1.NodeCondition {
|
||||
}
|
||||
}
|
||||
|
||||
// NodeAddresses returns a list of addresses for the node status
|
||||
// within Kubernetes.
|
||||
func (p *ACIProvider) NodeAddresses() []v1.NodeAddress {
|
||||
// TODO: Make these dynamic and augment with custom ACI specific conditions of interest
|
||||
return []v1.NodeAddress{
|
||||
{
|
||||
Type: "InternalIP",
|
||||
Address: p.internalIP,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NodeDaemonEndpoints returns NodeDaemonEndpoints for the node status
|
||||
// within Kubernetes.
|
||||
func (p *ACIProvider) NodeDaemonEndpoints() *v1.NodeDaemonEndpoints {
|
||||
return &v1.NodeDaemonEndpoints{
|
||||
KubeletEndpoint: v1.DaemonEndpoint{
|
||||
Port: p.daemonEndpointPort,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// OperatingSystem returns the operating system that was provided by the config.
|
||||
func (p *ACIProvider) OperatingSystem() string {
|
||||
return p.operatingSystem
|
||||
@@ -308,26 +446,24 @@ func (p *ACIProvider) getImagePullSecrets(pod *v1.Pod) ([]aci.ImageRegistryCrede
|
||||
// TODO: Check if secret type is v1.SecretTypeDockercfg and use DockerConfigKey instead of hardcoded value
|
||||
// TODO: Check if secret type is v1.SecretTypeDockerConfigJson and use DockerConfigJsonKey to determine if it's in json format
|
||||
// TODO: Return error if it's not one of these two types
|
||||
repoDataB64, ok := secret.Data[".dockercfg"]
|
||||
repoData, ok := secret.Data[".dockercfg"]
|
||||
if !ok {
|
||||
return ips, fmt.Errorf("no dockercfg present in secret")
|
||||
}
|
||||
repoData, err := base64.StdEncoding.DecodeString(string(repoDataB64))
|
||||
|
||||
var authConfigs map[string]AuthConfig
|
||||
err = json.Unmarshal(repoData, &authConfigs)
|
||||
if err != nil {
|
||||
return ips, err
|
||||
}
|
||||
|
||||
var ac AuthConfig
|
||||
err = json.Unmarshal(repoData, &ac)
|
||||
if err != nil {
|
||||
return ips, err
|
||||
for server, authConfig := range authConfigs {
|
||||
ips = append(ips, aci.ImageRegistryCredential{
|
||||
Password: authConfig.Password,
|
||||
Server: server,
|
||||
Username: authConfig.Username,
|
||||
})
|
||||
}
|
||||
|
||||
ips = append(ips, aci.ImageRegistryCredential{
|
||||
Password: ac.Password,
|
||||
Server: ac.ServerAddress,
|
||||
Username: ac.Username,
|
||||
})
|
||||
}
|
||||
return ips, nil
|
||||
}
|
||||
@@ -339,7 +475,7 @@ func (p *ACIProvider) getContainers(pod *v1.Pod) ([]aci.Container, error) {
|
||||
Name: container.Name,
|
||||
ContainerProperties: aci.ContainerProperties{
|
||||
Image: container.Image,
|
||||
Command: container.Command,
|
||||
Command: append(container.Command, container.Args...),
|
||||
Ports: make([]aci.ContainerPort, 0, len(container.Ports)),
|
||||
},
|
||||
}
|
||||
@@ -400,7 +536,7 @@ func (p *ACIProvider) getVolumes(pod *v1.Pod) ([]aci.Volume, error) {
|
||||
}
|
||||
|
||||
if secret == nil {
|
||||
return nil, fmt.Errorf("Getting secret for AzureFile volume returned an empty secret.")
|
||||
return nil, fmt.Errorf("Getting secret for AzureFile volume returned an empty secret")
|
||||
}
|
||||
|
||||
volumes = append(volumes, aci.Volume{
|
||||
@@ -418,11 +554,8 @@ func (p *ACIProvider) getVolumes(pod *v1.Pod) ([]aci.Volume, error) {
|
||||
// Handle the case for the EmptyDir.
|
||||
if v.EmptyDir != nil {
|
||||
volumes = append(volumes, aci.Volume{
|
||||
Name: v.Name,
|
||||
EmptyDir: map[string]interface{}{
|
||||
"medium": v.EmptyDir.Medium,
|
||||
"sizeLimit": v.EmptyDir.SizeLimit,
|
||||
},
|
||||
Name: v.Name,
|
||||
EmptyDir: map[string]interface{}{},
|
||||
})
|
||||
continue
|
||||
}
|
||||
@@ -472,7 +605,7 @@ func (p *ACIProvider) getVolumes(pod *v1.Pod) ([]aci.Volume, error) {
|
||||
if v.ConfigMap != nil {
|
||||
paths := make(map[string]string)
|
||||
configMap, err := p.resourceManager.GetConfigMap(v.ConfigMap.Name, pod.Namespace)
|
||||
if v.Secret.Optional != nil && !*v.Secret.Optional && k8serr.IsNotFound(err) {
|
||||
if v.ConfigMap.Optional != nil && !*v.ConfigMap.Optional && k8serr.IsNotFound(err) {
|
||||
return nil, fmt.Errorf("ConfigMap %s is required by Pod %s and does not exist", v.ConfigMap.Name, pod.Name)
|
||||
}
|
||||
if configMap == nil {
|
||||
@@ -661,3 +794,39 @@ func aciContainerStateToContainerState(cs aci.ContainerState) v1.ContainerState
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Filters service account secret volume for Windows.
|
||||
// Service account secret volume gets automatically turned on if not specified otherwise.
|
||||
// ACI doesn't support secret volume for Windows, so we need to filter it.
|
||||
func filterServiceAccountSecretVolume(osType string, containerGroup *aci.ContainerGroup) {
|
||||
if strings.EqualFold(osType, "Windows") {
|
||||
serviceAccountSecretVolumeName := make(map[string]bool)
|
||||
|
||||
for index, container := range containerGroup.ContainerGroupProperties.Containers {
|
||||
volumeMounts := make([]aci.VolumeMount, 0, len(container.VolumeMounts))
|
||||
for _, volumeMount := range container.VolumeMounts {
|
||||
if !strings.EqualFold(serviceAccountSecretMountPath, volumeMount.MountPath) {
|
||||
volumeMounts = append(volumeMounts, volumeMount)
|
||||
} else {
|
||||
serviceAccountSecretVolumeName[volumeMount.Name] = true
|
||||
}
|
||||
}
|
||||
containerGroup.ContainerGroupProperties.Containers[index].VolumeMounts = volumeMounts
|
||||
}
|
||||
|
||||
if len(serviceAccountSecretVolumeName) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("Ignoring service account secret volumes '%v' for Windows", reflect.ValueOf(serviceAccountSecretVolumeName).MapKeys())
|
||||
|
||||
volumes := make([]aci.Volume, 0, len(containerGroup.ContainerGroupProperties.Volumes))
|
||||
for _, volume := range containerGroup.ContainerGroupProperties.Volumes {
|
||||
if _, ok := serviceAccountSecretVolumeName[volume.Name]; !ok {
|
||||
volumes = append(volumes, volume)
|
||||
}
|
||||
}
|
||||
|
||||
containerGroup.ContainerGroupProperties.Volumes = volumes
|
||||
}
|
||||
}
|
||||
|
||||
38
providers/azure/acsCredential.go
Normal file
38
providers/azure/acsCredential.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package azure
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
)
|
||||
|
||||
// AcsCredential represents the credential file for ACS
|
||||
type AcsCredential struct {
|
||||
Cloud string `json:"cloud"`
|
||||
TenantID string `json:"tenantId"`
|
||||
SubscriptionID string `json:"subscriptionId"`
|
||||
ClientID string `json:"aadClientId"`
|
||||
ClientSecret string `json:"aadClientSecret"`
|
||||
ResourceGroup string `json:"resourceGroup"`
|
||||
Region string `json:"location"`
|
||||
}
|
||||
|
||||
// NewAcsCredential returns an AcsCredential struct from file path
|
||||
func NewAcsCredential(filepath string) (*AcsCredential, error) {
|
||||
log.Printf("Reading ACS credential file %q", filepath)
|
||||
|
||||
b, err := ioutil.ReadFile(filepath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Reading ACS credential file %q failed: %v", filepath, err)
|
||||
}
|
||||
|
||||
// Unmarshal the authentication file.
|
||||
var cred AcsCredential
|
||||
if err := json.Unmarshal(b, &cred); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Printf("Load ACS credential file %q successfully", filepath)
|
||||
return &cred, nil
|
||||
}
|
||||
129
providers/azure/acsCredential_test.go
Normal file
129
providers/azure/acsCredential_test.go
Normal file
@@ -0,0 +1,129 @@
|
||||
package azure
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const cred = `
|
||||
{
|
||||
"cloud":"AzurePublicCloud",
|
||||
"tenantId": "72f988bf-86f1-41af-91ab-2d7cd011db47",
|
||||
"subscriptionId": "11122233-4444-5555-6666-777888999000",
|
||||
"aadClientId": "123",
|
||||
"aadClientSecret": "456",
|
||||
"resourceGroup": "vk-test-rg",
|
||||
"location": "westcentralus",
|
||||
"subnetName": "k8s-subnet",
|
||||
"securityGroupName": "k8s-master-nsg",
|
||||
"vnetName": "k8s-vnet",
|
||||
"routeTableName": "k8s-master-routetable",
|
||||
"primaryAvailabilitySetName": "agentpool1-availabilitySet",
|
||||
"cloudProviderBackoff": true,
|
||||
"cloudProviderBackoffRetries": 6,
|
||||
"cloudProviderBackoffExponent": 1.5,
|
||||
"cloudProviderBackoffDuration": 5,
|
||||
"cloudProviderBackoffJitter": 1,
|
||||
"cloudProviderRatelimit": true,
|
||||
"cloudProviderRateLimitQPS": 3,
|
||||
"cloudProviderRateLimitBucket": 10
|
||||
}`
|
||||
|
||||
func TestAcsCred(t *testing.T) {
|
||||
file, err := ioutil.TempFile("", "acs_test")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
defer os.Remove(file.Name())
|
||||
|
||||
if _, err := file.Write([]byte(cred)); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
cred, err := NewAcsCredential(file.Name())
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
wanted := "AzurePublicCloud"
|
||||
if cred.Cloud != wanted {
|
||||
t.Errorf("Wanted %s, got %s.", wanted, cred.Cloud)
|
||||
}
|
||||
|
||||
wanted = "72f988bf-86f1-41af-91ab-2d7cd011db47"
|
||||
if cred.TenantID != wanted {
|
||||
t.Errorf("Wanted %s, got %s.", wanted, cred.TenantID)
|
||||
}
|
||||
|
||||
wanted = "11122233-4444-5555-6666-777888999000"
|
||||
if cred.SubscriptionID != wanted {
|
||||
t.Errorf("Wanted %s, got %s.", wanted, cred.SubscriptionID)
|
||||
}
|
||||
wanted = "123"
|
||||
if cred.ClientID != wanted {
|
||||
t.Errorf("Wanted %s, got %s.", wanted, cred.ClientID)
|
||||
}
|
||||
|
||||
wanted = "456"
|
||||
if cred.ClientSecret != wanted {
|
||||
t.Errorf("Wanted %s, got %s.", wanted, cred.ClientSecret)
|
||||
}
|
||||
|
||||
wanted = "vk-test-rg"
|
||||
if cred.ResourceGroup != wanted {
|
||||
t.Errorf("Wanted %s, got %s.", wanted, cred.ResourceGroup)
|
||||
}
|
||||
|
||||
wanted = "westcentralus"
|
||||
if cred.Region != wanted {
|
||||
t.Errorf("Wanted %s, got %s.", wanted, cred.Region)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAcsCredFileNotFound(t *testing.T) {
|
||||
file, err := ioutil.TempFile("", "acs_test")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
fileName := file.Name()
|
||||
|
||||
if err := file.Close(); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
os.Remove(fileName)
|
||||
|
||||
if _, err := NewAcsCredential(fileName); err == nil {
|
||||
t.Fatal("expected to fail with bad json")
|
||||
}
|
||||
}
|
||||
|
||||
const credBad = `
|
||||
{
|
||||
"cloud":"AzurePublicCloud",
|
||||
"tenantId": "72f988bf-86f1-41af-91ab-2d7cd011db47",
|
||||
"subscriptionId": "11122233-4444-5555-6666-777888999000",
|
||||
"aadClientId": "123",
|
||||
"aadClientSecret": "456",
|
||||
"resourceGroup": "vk-test-rg",
|
||||
"location": "westcentralus",
|
||||
"subnetName": "k8s-subnet",`
|
||||
|
||||
func TestAcsCredBadJson(t *testing.T) {
|
||||
file, err := ioutil.TempFile("", "acs_test")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
defer os.Remove(file.Name())
|
||||
|
||||
if _, err := file.Write([]byte(credBad)); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if _, err := NewAcsCredential(file.Name()); err == nil {
|
||||
t.Fatal("expected to fail with bad json")
|
||||
}
|
||||
}
|
||||
@@ -29,16 +29,14 @@ type Client struct {
|
||||
}
|
||||
|
||||
// NewClient creates a new Azure Container Instances client.
|
||||
func NewClient() (*Client, error) {
|
||||
// Get authentication credentials from file.
|
||||
auth, err := azure.NewAuthenticationFromFile()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Creating azure authentication from file failed: %v", err)
|
||||
func NewClient(auth *azure.Authentication) (*Client, error) {
|
||||
if auth == nil {
|
||||
return nil, fmt.Errorf("Authentication is not supplied for the Azure client")
|
||||
}
|
||||
|
||||
client, err := azure.NewClient(auth, BaseURI, userAgent)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Creating azure client failed: %v", err)
|
||||
return nil, fmt.Errorf("Creating Azure client failed: %v", err)
|
||||
}
|
||||
|
||||
return &Client{hc: client.HTTPClient, auth: auth}, nil
|
||||
|
||||
@@ -3,11 +3,10 @@ package aci
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
azure "github.com/virtual-kubelet/virtual-kubelet/providers/azure/client"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/providers/azure/client/resourcegroups"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
@@ -20,23 +19,6 @@ var (
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Check if the AZURE_AUTH_LOCATION variable is already set.
|
||||
// If it is not set, set it to the root of this project in a credentials.json file.
|
||||
if os.Getenv("AZURE_AUTH_LOCATION") == "" {
|
||||
// Check if the credentials.json file exists in the root of this project.
|
||||
_, filename, _, _ := runtime.Caller(0)
|
||||
dir := filepath.Dir(filename)
|
||||
file := filepath.Join(dir, "../../../../credentials.json")
|
||||
|
||||
// Check if the file exists.
|
||||
if _, err := os.Stat(file); os.IsNotExist(err) {
|
||||
log.Fatalf("Either set AZURE_AUTH_LOCATION or add a credentials.json file to the root of this project.")
|
||||
}
|
||||
|
||||
// Set the environment variable for the authentication file.
|
||||
os.Setenv("AZURE_AUTH_LOCATION", file)
|
||||
}
|
||||
|
||||
// Create a resource group name with uuid.
|
||||
uid := uuid.New()
|
||||
resourceGroup += "-" + uid.String()[0:6]
|
||||
@@ -45,8 +27,13 @@ func init() {
|
||||
// The TestMain function creates a resource group for testing
|
||||
// and deletes in when it's done.
|
||||
func TestMain(m *testing.M) {
|
||||
auth, err := azure.NewAuthenticationFromFile("../../../../credentials.json")
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to load Azure authentication file: %v", err)
|
||||
}
|
||||
|
||||
// Check if the resource group exists and create it if not.
|
||||
rgCli, err := resourcegroups.NewClient()
|
||||
rgCli, err := resourcegroups.NewClient(auth)
|
||||
if err != nil {
|
||||
log.Fatalf("creating new resourcegroups client failed: %v", err)
|
||||
}
|
||||
@@ -84,11 +71,17 @@ func TestMain(m *testing.M) {
|
||||
}
|
||||
|
||||
func TestNewClient(t *testing.T) {
|
||||
var err error
|
||||
client, err = NewClient()
|
||||
auth, err := azure.NewAuthenticationFromFile("../../../../credentials.json")
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to load Azure authentication file: %v", err)
|
||||
}
|
||||
|
||||
c, err := NewClient(auth)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
client = c
|
||||
}
|
||||
|
||||
func TestCreateContainerGroupFails(t *testing.T) {
|
||||
@@ -163,7 +156,7 @@ func TestCreateContainerGroup(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetContainerGroup(t *testing.T) {
|
||||
cg, err := client.GetContainerGroup(resourceGroup, containerGroup)
|
||||
cg, err, _ := client.GetContainerGroup(resourceGroup, containerGroup)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
// GetContainerGroup gets an Azure Container Instance in the provided
|
||||
// resource group with the given container group name.
|
||||
// From: https://docs.microsoft.com/en-us/rest/api/container-instances/containergroups/get
|
||||
func (c *Client) GetContainerGroup(resourceGroup, containerGroupName string) (*ContainerGroup, error) {
|
||||
func (c *Client) GetContainerGroup(resourceGroup, containerGroupName string) (*ContainerGroup, error, *int) {
|
||||
urlParams := url.Values{
|
||||
"api-version": []string{apiVersion},
|
||||
}
|
||||
@@ -25,7 +25,7 @@ func (c *Client) GetContainerGroup(resourceGroup, containerGroupName string) (*C
|
||||
// Create the request.
|
||||
req, err := http.NewRequest("GET", uri, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Creating get container group uri request failed: %v", err)
|
||||
return nil, fmt.Errorf("Creating get container group uri request failed: %v", err), nil
|
||||
}
|
||||
|
||||
// Add the parameters to the url.
|
||||
@@ -34,29 +34,29 @@ func (c *Client) GetContainerGroup(resourceGroup, containerGroupName string) (*C
|
||||
"resourceGroup": resourceGroup,
|
||||
"containerGroupName": containerGroupName,
|
||||
}); err != nil {
|
||||
return nil, fmt.Errorf("Expanding URL with parameters failed: %v", err)
|
||||
return nil, fmt.Errorf("Expanding URL with parameters failed: %v", err), nil
|
||||
}
|
||||
|
||||
// Send the request.
|
||||
resp, err := c.hc.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Sending get container group request failed: %v", err)
|
||||
return nil, fmt.Errorf("Sending get container group request failed: %v", err), &resp.StatusCode
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 200 (OK) is a success response.
|
||||
if err := api.CheckResponse(resp); err != nil {
|
||||
return nil, err
|
||||
return nil, err, &resp.StatusCode
|
||||
}
|
||||
|
||||
// Decode the body from the response.
|
||||
if resp.Body == nil {
|
||||
return nil, errors.New("Create container group returned an empty body in the response")
|
||||
return nil, errors.New("Create container group returned an empty body in the response"), &resp.StatusCode
|
||||
}
|
||||
var cg ContainerGroup
|
||||
if err := json.NewDecoder(resp.Body).Decode(&cg); err != nil {
|
||||
return nil, fmt.Errorf("Decoding get container group response body failed: %v", err)
|
||||
return nil, fmt.Errorf("Decoding get container group response body failed: %v", err), &resp.StatusCode
|
||||
}
|
||||
|
||||
return &cg, nil
|
||||
return &cg, nil, &resp.StatusCode
|
||||
}
|
||||
|
||||
@@ -263,7 +263,7 @@ type UsageListResult struct {
|
||||
type Volume struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
AzureFile *AzureFileVolume `json:"azureFile,omitempty"`
|
||||
EmptyDir map[string]interface{} `json:"emptyDir,omitempty"`
|
||||
EmptyDir map[string]interface{} `json:"emptyDir"`
|
||||
Secret map[string]string `json:"secret,omitempty"`
|
||||
GitRepo *GitRepoVolume `json:"gitRepo,omitempty"`
|
||||
}
|
||||
|
||||
@@ -6,19 +6,11 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"unicode/utf16"
|
||||
|
||||
"github.com/dimchansky/utfbom"
|
||||
)
|
||||
|
||||
const (
|
||||
// AuthenticationFilepathName defines the name of the environment variable
|
||||
// containing the path to the file to be used to populate Authentication
|
||||
// for Azure.
|
||||
AuthenticationFilepathName = "AZURE_AUTH_LOCATION"
|
||||
)
|
||||
|
||||
// Authentication represents the authentication file for Azure.
|
||||
type Authentication struct {
|
||||
ClientID string `json:"clientId,omitempty"`
|
||||
@@ -35,32 +27,49 @@ type Authentication struct {
|
||||
|
||||
// NewAuthentication returns an authentication struct from user provided
|
||||
// credentials.
|
||||
func NewAuthentication(clientID, clientSecret, subscriptionID, tenantID string) *Authentication {
|
||||
func NewAuthentication(azureCloud, clientID, clientSecret, subscriptionID, tenantID string) *Authentication {
|
||||
environment := PublicCloud
|
||||
|
||||
switch azureCloud {
|
||||
case PublicCloud.Name:
|
||||
environment = PublicCloud
|
||||
break;
|
||||
case USGovernmentCloud.Name:
|
||||
environment = USGovernmentCloud
|
||||
break;
|
||||
case ChinaCloud.Name:
|
||||
environment = ChinaCloud
|
||||
break;
|
||||
case GermanCloud.Name:
|
||||
environment = GermanCloud
|
||||
break;
|
||||
}
|
||||
|
||||
return &Authentication{
|
||||
ClientID: clientID,
|
||||
ClientSecret: clientSecret,
|
||||
SubscriptionID: subscriptionID,
|
||||
TenantID: tenantID,
|
||||
ClientID: clientID,
|
||||
ClientSecret: clientSecret,
|
||||
SubscriptionID: subscriptionID,
|
||||
TenantID: tenantID,
|
||||
ActiveDirectoryEndpoint: environment.ActiveDirectoryEndpoint,
|
||||
ResourceManagerEndpoint: environment.ResourceManagerEndpoint,
|
||||
GraphResourceID: environment.GraphEndpoint,
|
||||
SQLManagementEndpoint: environment.SQLDatabaseDNSSuffix,
|
||||
GalleryEndpoint: environment.GalleryEndpoint,
|
||||
ManagementEndpoint: environment.ServiceManagementEndpoint,
|
||||
}
|
||||
}
|
||||
|
||||
// NewAuthenticationFromFile returns an authentication struct from file located
|
||||
// at AZURE_AUTH_LOCATION.
|
||||
func NewAuthenticationFromFile() (*Authentication, error) {
|
||||
file := os.Getenv(AuthenticationFilepathName)
|
||||
if file == "" {
|
||||
return nil, fmt.Errorf("Authentication file not found, environment variable %s is not set", AuthenticationFilepathName)
|
||||
}
|
||||
|
||||
b, err := ioutil.ReadFile(file)
|
||||
// NewAuthenticationFromFile returns an authentication struct from file path
|
||||
func NewAuthenticationFromFile(filepath string) (*Authentication, error) {
|
||||
b, err := ioutil.ReadFile(filepath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Reading authentication file %q failed: %v", file, err)
|
||||
return nil, fmt.Errorf("Reading authentication file %q failed: %v", filepath, err)
|
||||
}
|
||||
|
||||
// Authentication file might be encoded.
|
||||
decoded, err := decode(b)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Decoding authentication file %q failed: %v", file, err)
|
||||
return nil, fmt.Errorf("Decoding authentication file %q failed: %v", filepath, err)
|
||||
}
|
||||
|
||||
// Unmarshal the authentication file.
|
||||
|
||||
@@ -26,16 +26,14 @@ type Client struct {
|
||||
}
|
||||
|
||||
// NewClient creates a new Azure resource groups client.
|
||||
func NewClient() (*Client, error) {
|
||||
// Get authentication credentials from file.
|
||||
auth, err := azure.NewAuthenticationFromFile()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Creating azure authentication from file failed: %v", err)
|
||||
func NewClient(auth *azure.Authentication) (*Client, error) {
|
||||
if auth == nil {
|
||||
return nil, fmt.Errorf("Authentication is not supplied for the Azure client")
|
||||
}
|
||||
|
||||
client, err := azure.NewClient(auth, BaseURI, userAgent)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Creating azure client failed: %v", err)
|
||||
return nil, fmt.Errorf("Creating Azure client failed: %v", err)
|
||||
}
|
||||
|
||||
return &Client{hc: client.HTTPClient, auth: auth}, nil
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
package resourcegroups
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
azure "github.com/virtual-kubelet/virtual-kubelet/providers/azure/client"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -17,34 +14,23 @@ var (
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Check if the AZURE_AUTH_LOCATION variable is already set.
|
||||
// If it is not set, set it to the root of this project in a credentials.json file.
|
||||
if os.Getenv("AZURE_AUTH_LOCATION") == "" {
|
||||
// Check if the credentials.json file exists in the root of this project.
|
||||
_, filename, _, _ := runtime.Caller(0)
|
||||
dir := filepath.Dir(filename)
|
||||
file := filepath.Join(dir, "../../../../credentials.json")
|
||||
|
||||
// Check if the file exists.
|
||||
if _, err := os.Stat(file); os.IsNotExist(err) {
|
||||
log.Fatalf("Either set AZURE_AUTH_LOCATION or add a credentials.json file to the root of this project.")
|
||||
}
|
||||
|
||||
// Set the environment variable for the authentication file.
|
||||
os.Setenv("AZURE_AUTH_LOCATION", file)
|
||||
}
|
||||
|
||||
// Create a resource group name with uuid.
|
||||
uid := uuid.New()
|
||||
resourceGroup += "-" + uid.String()[0:6]
|
||||
}
|
||||
|
||||
func TestNewClient(t *testing.T) {
|
||||
var err error
|
||||
client, err = NewClient()
|
||||
auth, err := azure.NewAuthenticationFromFile("../../../../credentials.json")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to load Azure authentication file: %v", err)
|
||||
}
|
||||
|
||||
c, err := NewClient(auth)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
client = c
|
||||
}
|
||||
|
||||
func TestResourceGroupDoesNotExist(t *testing.T) {
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
Region = "westus"
|
||||
ResourceGroup = "virtual-kubeletrg"
|
||||
OperatingSystem = "Linux"
|
||||
CPU = 100
|
||||
Memory = 100Gi
|
||||
Pods = 50
|
||||
CPU = "100"
|
||||
Memory = "100Gi"
|
||||
Pods = "50"
|
||||
|
||||
124
providers/hypersh/README.md
Normal file
124
providers/hypersh/README.md
Normal file
@@ -0,0 +1,124 @@
|
||||
hyper.sh provider for virtual-kubelet
|
||||
=====================================
|
||||
|
||||
# Configure for hyper.sh
|
||||
|
||||
## Use environment variable
|
||||
|
||||
- necessary
|
||||
- HYPER_ACCESS_KEY
|
||||
- HYPER_SECRET_KEY
|
||||
- optional
|
||||
- HYPER_INSTANCE_TYPE: default s4
|
||||
- HYPER_DEFAULT_REGION: default us-west-1
|
||||
- HYPER_HOST: tcp://${HYPER_DEFAULT_REGION}.hyper.sh:443
|
||||
|
||||
> You can use You can use either HYPER_HOST or HYPER_DEFAULT_REGION
|
||||
|
||||
|
||||
## Use config file
|
||||
|
||||
> default config file for hyper.sh is ~/.hyper/config.json
|
||||
|
||||
```
|
||||
//example configuration file for Hyper.sh
|
||||
{
|
||||
"auths": {
|
||||
"https://index.docker.io/v1/": {
|
||||
"auth": "xxxxxx",
|
||||
"email": "xxxxxx"
|
||||
},
|
||||
},
|
||||
"clouds": {
|
||||
"tcp://*.hyper.sh:443": {
|
||||
"accesskey": "xxxxxx",
|
||||
"secretkey": "xxxxxx",
|
||||
"region": "us-west-1"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
# Usage of virtual-kubelet cli
|
||||
|
||||
```
|
||||
// example 1 : use environment variable
|
||||
export HYPER_ACCESS_KEY=xxxxxx
|
||||
export HYPER_SECRET_KEY=xxxxxx
|
||||
export HYPER_DEFAULT_REGION=eu-central-1
|
||||
export HYPER_INSTANCE_TYPE=s4
|
||||
./virtual-kubelet --provider=hyper
|
||||
|
||||
|
||||
// example 2 : use default config file(~/.hyper/config.json)
|
||||
unset HYPER_ACCESS_KEY
|
||||
unset HYPER_SECRET_KEY
|
||||
export HYPER_DEFAULT_REGION=eu-central-1
|
||||
./virtual-kubelet --provider=hyper
|
||||
|
||||
|
||||
// example 3 : use custom config file, eg: ~/.hyper2/config.json
|
||||
$ ./virtual-kubelet --provider=hyper --provider-config=$HOME/.hyper2
|
||||
```
|
||||
|
||||
|
||||
# Quick Start
|
||||
|
||||
## create pod yaml
|
||||
|
||||
```
|
||||
$ cat pod-nginx
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: nginx
|
||||
spec:
|
||||
nodeName: virtual-kubelet
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:latest
|
||||
ports:
|
||||
- containerPort: 80
|
||||
```
|
||||
|
||||
## create pod
|
||||
|
||||
```
|
||||
$ kubectl create -f pod-nginx
|
||||
```
|
||||
|
||||
## list container on hyper.sh
|
||||
|
||||
```
|
||||
$ hyper ps
|
||||
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES PUBLIC IP
|
||||
a0ae3d4112d5 nginx:latest "nginx -g 'daemon off" 9 seconds ago Up 4 seconds 0.0.0.0:80->80/tcp pod-nginx-nginx
|
||||
```
|
||||
|
||||
## server log
|
||||
|
||||
```
|
||||
$ export HYPER_DEFAULT_REGION=eu-central-1
|
||||
$ ./virtual-kubelet --provider=hyper --provider-config=$HOME/.hyper3
|
||||
/home/demo/.kube/config
|
||||
2017/12/20 17:30:30 config file under "/home/demo/.hyper3" was loaded
|
||||
2017/12/20 17:30:30
|
||||
Host: tcp://eu-central-1.hyper.sh:443
|
||||
AccessKey: K**********
|
||||
SecretKey: 4**********
|
||||
InstanceType: s4
|
||||
2017/12/20 17:30:31 Node 'virtual-kubelet' with OS type 'Linux' registered
|
||||
2017/12/20 17:30:31 receive GetPods
|
||||
2017/12/20 17:30:32 found 0 pods
|
||||
2017/12/20 17:30:37 receive GetPods
|
||||
2017/12/20 17:30:37 found 0 pods
|
||||
2017/12/20 17:30:38 Error retrieving pod 'nginx' from provider: Error: No such container: pod-nginx-nginx
|
||||
2017/12/20 17:30:38 receive CreatePod "nginx"
|
||||
2017/12/20 17:30:38 container "a0ae3d4112d53023b5972906f2f15c0d34360c132b3c273b286473afad613b63" for pod "nginx" was created
|
||||
2017/12/20 17:30:43 container "a0ae3d4112d53023b5972906f2f15c0d34360c132b3c273b286473afad613b63" for pod "nginx" was started
|
||||
2017/12/20 17:30:43 Pod 'nginx' created.
|
||||
2017/12/20 17:30:43 receive GetPods
|
||||
2017/12/20 17:30:43 found 1 pods
|
||||
2017/12/20 17:30:47 receive GetPods
|
||||
2017/12/20 17:30:47 found 1 pods
|
||||
```
|
||||
@@ -1,58 +0,0 @@
|
||||
package hypersh
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/virtual-kubelet/virtual-kubelet/providers"
|
||||
"github.com/BurntSushi/toml"
|
||||
)
|
||||
|
||||
type providerConfig struct {
|
||||
Region string
|
||||
AccessKey string
|
||||
SecretKey string
|
||||
OperatingSystem string
|
||||
CPU string
|
||||
Memory string
|
||||
Pods string
|
||||
}
|
||||
|
||||
func (p *HyperProvider) loadConfig(r io.Reader) error {
|
||||
var config providerConfig
|
||||
if _, err := toml.DecodeReader(r, &config); err != nil {
|
||||
return err
|
||||
}
|
||||
p.region = config.Region
|
||||
p.accessKey = config.AccessKey
|
||||
p.secretKey = config.SecretKey
|
||||
|
||||
// Default to 20 mcpu
|
||||
p.cpu = "20"
|
||||
if config.CPU != "" {
|
||||
p.cpu = config.CPU
|
||||
}
|
||||
// Default to 100Gi
|
||||
p.memory = "100Gi"
|
||||
if config.Memory != "" {
|
||||
p.memory = config.Memory
|
||||
}
|
||||
// Default to 20 pods
|
||||
p.pods = "20"
|
||||
if config.Pods != "" {
|
||||
p.pods = config.Pods
|
||||
}
|
||||
|
||||
// Default to Linux if the operating system was not defined in the config.
|
||||
if config.OperatingSystem == "" {
|
||||
config.OperatingSystem = providers.OperatingSystemLinux
|
||||
}
|
||||
|
||||
// Validate operating system from config.
|
||||
if config.OperatingSystem != providers.OperatingSystemLinux {
|
||||
return fmt.Errorf("%q is not a valid operating system, only %s is valid", config.OperatingSystem, providers.OperatingSystemLinux)
|
||||
}
|
||||
|
||||
p.operatingSystem = config.OperatingSystem
|
||||
return nil
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
# example configuration file for Hyper.sh virtual-kubelet
|
||||
|
||||
AccessKey = ""
|
||||
SecretKey = ""
|
||||
Region = "us-west-1"
|
||||
CPU = 100
|
||||
Memory = 100Gi
|
||||
Pods = 50
|
||||
359
providers/hypersh/hypersh.go
Normal file → Executable file
359
providers/hypersh/hypersh.go
Normal file → Executable file
@@ -6,120 +6,246 @@ import (
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"github.com/docker/go-connections/nat"
|
||||
hyper "github.com/hyperhq/hyper-api/client"
|
||||
"github.com/hyperhq/hyper-api/types"
|
||||
"github.com/hyperhq/hyper-api/types/container"
|
||||
"github.com/hyperhq/hyper-api/types/filters"
|
||||
"github.com/hyperhq/hyper-api/types/network"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/manager"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/providers"
|
||||
|
||||
"github.com/docker/go-connections/sockets"
|
||||
"github.com/docker/go-connections/tlsconfig"
|
||||
hyper "github.com/hyperhq/hyper-api/client"
|
||||
"github.com/hyperhq/hyper-api/types"
|
||||
"github.com/hyperhq/hyper-api/types/filters"
|
||||
"github.com/hyperhq/hyper-api/types/network"
|
||||
"github.com/hyperhq/hypercli/cliconfig"
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
var host = "tcp://*.hyper.sh:443"
|
||||
|
||||
const (
|
||||
host = "https://us-west-1.hyper.sh"
|
||||
verStr = "v1.23"
|
||||
containerLabel = "hyper-virtual-kubelet"
|
||||
nodeLabel = containerLabel + "-node"
|
||||
verStr = "v1.23"
|
||||
containerLabel = "hyper-virtual-kubelet"
|
||||
nodeLabel = containerLabel + "-node"
|
||||
instanceTypeLabel = "sh_hyper_instancetype"
|
||||
)
|
||||
|
||||
// HyperProvider implements the virtual-kubelet provider interface and communicates with hyper.sh APIs.
|
||||
type HyperProvider struct {
|
||||
hyperClient *hyper.Client
|
||||
configFile *cliconfig.ConfigFile
|
||||
resourceManager *manager.ResourceManager
|
||||
nodeName string
|
||||
operatingSystem string
|
||||
region string
|
||||
host string
|
||||
accessKey string
|
||||
secretKey string
|
||||
cpu string
|
||||
memory string
|
||||
instanceType string
|
||||
pods string
|
||||
}
|
||||
|
||||
// NewHyperProvider creates a new HyperProvider
|
||||
func NewHyperProvider(config string, rm *manager.ResourceManager, nodeName, operatingSystem string) (*HyperProvider, error) {
|
||||
var p HyperProvider
|
||||
var err error
|
||||
var (
|
||||
p HyperProvider
|
||||
err error
|
||||
host string
|
||||
dft bool
|
||||
tlsOptions = &tlsconfig.Options{InsecureSkipVerify: false}
|
||||
)
|
||||
|
||||
p.resourceManager = rm
|
||||
|
||||
if config != "" {
|
||||
f, err := os.Open(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
if err := p.loadConfig(f); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Get config from environment variable
|
||||
if h := os.Getenv("HYPER_HOST"); h != "" {
|
||||
p.host = h
|
||||
}
|
||||
|
||||
if ak := os.Getenv("HYPERSH_ACCESS_KEY"); ak != "" {
|
||||
if ak := os.Getenv("HYPER_ACCESS_KEY"); ak != "" {
|
||||
p.accessKey = ak
|
||||
}
|
||||
|
||||
if sk := os.Getenv("HYPERSH_SECRET_KEY"); sk != "" {
|
||||
if sk := os.Getenv("HYPER_SECRET_KEY"); sk != "" {
|
||||
p.secretKey = sk
|
||||
}
|
||||
|
||||
if r := os.Getenv("HYPERSH_REGION"); r != "" {
|
||||
p.region = r
|
||||
if p.host == "" {
|
||||
// ignore HYPER_DEFAULT_REGION when HYPER_HOST was specified
|
||||
if r := os.Getenv("HYPER_DEFAULT_REGION"); r != "" {
|
||||
p.region = r
|
||||
}
|
||||
}
|
||||
if it := os.Getenv("HYPER_INSTANCE_TYPE"); it != "" {
|
||||
p.instanceType = it
|
||||
} else {
|
||||
p.instanceType = "s4"
|
||||
}
|
||||
|
||||
if p.accessKey != "" || p.secretKey != "" {
|
||||
//use environment variable
|
||||
if p.accessKey == "" || p.secretKey == "" {
|
||||
return nil, fmt.Errorf("WARNING: Need to specify HYPER_ACCESS_KEY and HYPER_SECRET_KEY at the same time.")
|
||||
}
|
||||
log.Printf("Use AccessKey and SecretKey from HYPER_ACCESS_KEY and HYPER_SECRET_KEY")
|
||||
if p.region == "" {
|
||||
p.region = cliconfig.DefaultHyperRegion
|
||||
}
|
||||
if p.host == "" {
|
||||
host, _, err = p.getServerHost(p.region, tlsOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.host = host
|
||||
}
|
||||
} else {
|
||||
// use config file, default path is ~/.hyper
|
||||
if config == "" {
|
||||
config = cliconfig.ConfigDir()
|
||||
}
|
||||
configFile, err := cliconfig.Load(config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("WARNING: Error loading config file %q: %v\n", config, err)
|
||||
}
|
||||
p.configFile = configFile
|
||||
log.Printf("config file under %q was loaded\n", config)
|
||||
|
||||
if p.host == "" {
|
||||
host, dft, err = p.getServerHost(p.region, tlsOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.host = host
|
||||
}
|
||||
// Get Region, AccessKey and SecretKey from config file
|
||||
cc, ok := configFile.CloudConfig[p.host]
|
||||
if !ok {
|
||||
cc, ok = configFile.CloudConfig[cliconfig.DefaultHyperFormat]
|
||||
}
|
||||
if ok {
|
||||
p.accessKey = cc.AccessKey
|
||||
p.secretKey = cc.SecretKey
|
||||
|
||||
if p.region == "" && dft {
|
||||
if p.region = cc.Region; p.region == "" {
|
||||
p.region = p.getDefaultRegion()
|
||||
}
|
||||
}
|
||||
if !dft {
|
||||
if p.region = cc.Region; p.region == "" {
|
||||
p.region = cliconfig.DefaultHyperRegion
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("WARNING: can not find entrypoint %q in config file", cliconfig.DefaultHyperFormat)
|
||||
}
|
||||
if p.accessKey == "" || p.secretKey == "" {
|
||||
return nil, fmt.Errorf("WARNING: AccessKey or SecretKey is empty in config %q", config)
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("\n Host: %s\n AccessKey: %s**********\n SecretKey: %s**********\n InstanceType: %s\n", p.host, p.accessKey[0:1], p.secretKey[0:1], p.instanceType)
|
||||
httpClient, err := newHTTPClient(p.host, tlsOptions)
|
||||
|
||||
customHeaders := map[string]string{}
|
||||
ver := "0.1"
|
||||
customHeaders["User-Agent"] = fmt.Sprintf("Virtual-Kubelet-Client/%s (%s)", ver, runtime.GOOS)
|
||||
|
||||
p.operatingSystem = operatingSystem
|
||||
p.nodeName = nodeName
|
||||
|
||||
p.hyperClient, err = hyper.NewClient(host, verStr, http.DefaultClient, nil, p.accessKey, p.secretKey, p.region)
|
||||
p.hyperClient, err = hyper.NewClient(p.host, verStr, httpClient, customHeaders, p.accessKey, p.secretKey, p.region)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
//test connect to hyper.sh
|
||||
_, err = p.hyperClient.Info(context.Background())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &p, nil
|
||||
}
|
||||
|
||||
func newHTTPClient(host string, tlsOptions *tlsconfig.Options) (*http.Client, error) {
|
||||
if tlsOptions == nil {
|
||||
// let the api client configure the default transport.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
config, err := tlsconfig.Client(*tlsOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: config,
|
||||
}
|
||||
proto, addr, _, err := hyper.ParseHost(host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &p, nil
|
||||
sockets.ConfigureTransport(tr, proto, addr)
|
||||
|
||||
return &http.Client{
|
||||
Transport: tr,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CreatePod accepts a Pod definition and creates
|
||||
// a hyper.sh deployment
|
||||
func (p *HyperProvider) CreatePod(pod *v1.Pod) error {
|
||||
log.Printf("receive CreatePod %q\n", pod.Name)
|
||||
|
||||
// get containers
|
||||
containers, hostConfigs, err := getContainers(pod)
|
||||
//Ignore daemonSet Pod
|
||||
if pod != nil && pod.OwnerReferences != nil && len(pod.OwnerReferences) != 0 && pod.OwnerReferences[0].Kind == "DaemonSet" {
|
||||
log.Printf("Skip to create DaemonSet pod %q\n", pod.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get containers
|
||||
containers, hostConfigs, err := p.getContainers(pod)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// TODO: get registry creds
|
||||
|
||||
// TODO: get volumes
|
||||
|
||||
// Iterate over the containers to create and start them.
|
||||
for k, ctr := range containers {
|
||||
//one container in a Pod in hyper.sh currently
|
||||
containerName := fmt.Sprintf("pod-%s-%s", pod.Name, pod.Spec.Containers[k].Name)
|
||||
|
||||
if err = p.ensureImage(ctr.Image); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add labels to the pod containers.
|
||||
ctr.Labels = map[string]string{
|
||||
containerLabel: pod.Name,
|
||||
nodeLabel: p.nodeName,
|
||||
containerLabel: pod.Name,
|
||||
nodeLabel: p.nodeName,
|
||||
instanceTypeLabel: p.instanceType,
|
||||
}
|
||||
hostConfigs[k].NetworkMode = "bridge"
|
||||
|
||||
// Create the container.
|
||||
resp, err := p.hyperClient.ContainerCreate(context.Background(), &ctr, &hostConfigs[k], &network.NetworkingConfig{}, containerName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Creating container %q failed in pod %q: %v", containerName, pod.Name, err)
|
||||
return err
|
||||
}
|
||||
log.Printf("container %q for pod %q was created\n", resp.ID, pod.Name)
|
||||
|
||||
// Iterate throught the warnings.
|
||||
for _, warning := range resp.Warnings {
|
||||
log.Printf("Warning while creating container %q for pod %q: %s", containerName, pod.Name, warning)
|
||||
log.Printf("warning while creating container %q for pod %q: %s", containerName, pod.Name, warning)
|
||||
}
|
||||
|
||||
// Start the container.
|
||||
if err := p.hyperClient.ContainerStart(context.Background(), resp.ID, ""); err != nil {
|
||||
return fmt.Errorf("Starting container %q failed in pod %q: %v", containerName, pod.Name, err)
|
||||
return err
|
||||
}
|
||||
log.Printf("container %q for pod %q was started\n", resp.ID, pod.Name)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -129,40 +255,106 @@ func (p *HyperProvider) UpdatePod(pod *v1.Pod) error {
|
||||
}
|
||||
|
||||
// DeletePod deletes the specified pod out of hyper.sh.
|
||||
func (p *HyperProvider) DeletePod(pod *v1.Pod) error {
|
||||
func (p *HyperProvider) DeletePod(pod *v1.Pod) (err error) {
|
||||
log.Printf("receive DeletePod %q\n", pod.Name)
|
||||
var (
|
||||
containerName = fmt.Sprintf("pod-%s-%s", pod.Name, pod.Name)
|
||||
container types.ContainerJSON
|
||||
)
|
||||
// Inspect hyper container
|
||||
container, err = p.hyperClient.ContainerInspect(context.Background(), containerName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Check container label
|
||||
if v, ok := container.Config.Labels[containerLabel]; ok {
|
||||
// Check value of label
|
||||
if v != pod.Name {
|
||||
return fmt.Errorf("the label %q of hyper container %q should be %q, but it's %q currently", containerLabel, container.Name, pod.Name, v)
|
||||
}
|
||||
rmOptions := types.ContainerRemoveOptions{
|
||||
RemoveVolumes: true,
|
||||
Force: true,
|
||||
}
|
||||
// Delete hyper container
|
||||
resp, err := p.hyperClient.ContainerRemove(context.Background(), container.ID, rmOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Iterate throught the warnings.
|
||||
for _, warning := range resp {
|
||||
log.Printf("warning while deleting container %q for pod %q: %s", container.ID, pod.Name, warning)
|
||||
}
|
||||
log.Printf("container %q for pod %q was deleted\n", container.ID, pod.Name)
|
||||
} else {
|
||||
return fmt.Errorf("hyper container %q has no label %q", container.Name, containerLabel)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetPod returns a pod by name that is running inside hyper.sh
|
||||
// returns nil if a pod by that name is not found.
|
||||
func (p *HyperProvider) GetPod(namespace, name string) (*v1.Pod, error) {
|
||||
return nil, nil
|
||||
func (p *HyperProvider) GetPod(namespace, name string) (pod *v1.Pod, err error) {
|
||||
var (
|
||||
containerName = fmt.Sprintf("pod-%s-%s", name, name)
|
||||
container types.ContainerJSON
|
||||
)
|
||||
// Inspect hyper container
|
||||
container, err = p.hyperClient.ContainerInspect(context.Background(), containerName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Convert hyper container into Pod
|
||||
pod, err = p.containerJSONToPod(&container)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return pod, nil
|
||||
}
|
||||
}
|
||||
|
||||
// GetContainerLogs retrieves the logs of a container by name from the provider.
|
||||
func (p *HyperProvider) GetContainerLogs(namespace, podName, containerName string, tail int) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// GetPodStatus returns the status of a pod by name that is running inside hyper.sh
|
||||
// returns nil if a pod by that name is not found.
|
||||
func (p *HyperProvider) GetPodStatus(namespace, name string) (*v1.PodStatus, error) {
|
||||
return nil, nil
|
||||
pod, err := p.GetPod(namespace, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pod.Status, nil
|
||||
}
|
||||
|
||||
// GetPods returns a list of all pods known to be running within hyper.sh.
|
||||
func (p *HyperProvider) GetPods() ([]*v1.Pod, error) {
|
||||
filter, err := filters.FromParam(nodeLabel + "=" + p.nodeName)
|
||||
log.Printf("receive GetPods\n")
|
||||
filter, err := filters.FromParam(fmt.Sprintf("{\"label\":{\"%s=%s\":true}}", nodeLabel, p.nodeName))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Creating filter to get containers by node name failed: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
// Filter by label.
|
||||
_, err = p.hyperClient.ContainerList(context.Background(), types.ContainerListOptions{
|
||||
containers, err := p.hyperClient.ContainerList(context.Background(), types.ContainerListOptions{
|
||||
Filter: filter,
|
||||
All: true,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Listing containers failed: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
log.Printf("found %d pods\n", len(containers))
|
||||
|
||||
// TODO: convert containers into pods
|
||||
|
||||
return nil, nil
|
||||
var pods = []*v1.Pod{}
|
||||
for _, container := range containers {
|
||||
pod, err := p.containerToPod(&container)
|
||||
if err != nil {
|
||||
log.Printf("WARNING: convert container %q to pod error: %v\n", container.ID, err)
|
||||
continue
|
||||
}
|
||||
pods = append(pods, pod)
|
||||
}
|
||||
return pods, nil
|
||||
}
|
||||
|
||||
// Capacity returns a resource list containing the capacity limits set for hyper.sh.
|
||||
@@ -176,7 +368,7 @@ func (p *HyperProvider) Capacity() v1.ResourceList {
|
||||
}
|
||||
|
||||
// NodeConditions returns a list of conditions (Ready, OutOfDisk, etc), for updates to the node status
|
||||
// within Kuberentes.
|
||||
// within Kubernetes.
|
||||
func (p *HyperProvider) NodeConditions() []v1.NodeCondition {
|
||||
// TODO: Make these dynamic and augment with custom hyper.sh specific conditions of interest
|
||||
return []v1.NodeCondition{
|
||||
@@ -224,59 +416,20 @@ func (p *HyperProvider) NodeConditions() []v1.NodeCondition {
|
||||
|
||||
}
|
||||
|
||||
// NodeAddresses returns a list of addresses for the node status
|
||||
// within Kubernetes.
|
||||
func (p *HyperProvider) NodeAddresses() []v1.NodeAddress {
|
||||
return nil
|
||||
}
|
||||
|
||||
// NodeDaemonEndpoints returns NodeDaemonEndpoints for the node status
|
||||
// within Kubernetes.
|
||||
func (p *HyperProvider) NodeDaemonEndpoints() *v1.NodeDaemonEndpoints {
|
||||
return nil
|
||||
}
|
||||
|
||||
// OperatingSystem returns the operating system for this provider.
|
||||
// This is a noop to default to Linux for now.
|
||||
func (p *HyperProvider) OperatingSystem() string {
|
||||
return providers.OperatingSystemLinux
|
||||
}
|
||||
|
||||
func getContainers(pod *v1.Pod) ([]container.Config, []container.HostConfig, error) {
|
||||
containers := make([]container.Config, len(pod.Spec.Containers))
|
||||
hostConfigs := make([]container.HostConfig, len(pod.Spec.Containers))
|
||||
for x, ctr := range pod.Spec.Containers {
|
||||
// Do container.Config
|
||||
var c container.Config
|
||||
c.Image = ctr.Image
|
||||
c.Cmd = ctr.Command
|
||||
ports := map[nat.Port]struct{}{}
|
||||
portBindings := nat.PortMap{}
|
||||
for _, p := range ctr.Ports {
|
||||
port, err := nat.NewPort(string(p.Protocol), fmt.Sprintf("%d", p.HostPort))
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("creating new port in container conversion failed: %v", err)
|
||||
}
|
||||
ports[port] = struct{}{}
|
||||
|
||||
portBindings[port] = []nat.PortBinding{
|
||||
{
|
||||
HostPort: fmt.Sprintf("%d", p.HostPort),
|
||||
},
|
||||
}
|
||||
}
|
||||
c.ExposedPorts = ports
|
||||
|
||||
// TODO: do volumes
|
||||
|
||||
envs := make([]string, len(ctr.Env))
|
||||
for z, e := range ctr.Env {
|
||||
envs[z] = fmt.Sprintf("%s=%s", e.Name, e.Value)
|
||||
}
|
||||
c.Env = envs
|
||||
|
||||
// Do container.HostConfig
|
||||
var hc container.HostConfig
|
||||
cpuLimit := ctr.Resources.Limits.Cpu().Value()
|
||||
memoryLimit := ctr.Resources.Limits.Memory().Value()
|
||||
|
||||
hc.Resources = container.Resources{
|
||||
CPUShares: cpuLimit,
|
||||
Memory: memoryLimit,
|
||||
}
|
||||
|
||||
hc.PortBindings = portBindings
|
||||
|
||||
containers[x] = c
|
||||
hostConfigs[x] = hc
|
||||
}
|
||||
return containers, hostConfigs, nil
|
||||
}
|
||||
|
||||
419
providers/hypersh/util.go
Normal file
419
providers/hypersh/util.go
Normal file
@@ -0,0 +1,419 @@
|
||||
package hypersh
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/go-connections/nat"
|
||||
"github.com/docker/go-connections/tlsconfig"
|
||||
"github.com/hyperhq/hyper-api/types"
|
||||
"github.com/hyperhq/hyper-api/types/container"
|
||||
registrytypes "github.com/hyperhq/hyper-api/types/registry"
|
||||
"github.com/hyperhq/hypercli/cliconfig"
|
||||
"github.com/hyperhq/hypercli/opts"
|
||||
"github.com/hyperhq/hypercli/pkg/jsonmessage"
|
||||
"github.com/hyperhq/hypercli/pkg/term"
|
||||
"github.com/hyperhq/hypercli/reference"
|
||||
"github.com/hyperhq/hypercli/registry"
|
||||
"k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func (p *HyperProvider) getContainers(pod *v1.Pod) ([]container.Config, []container.HostConfig, error) {
|
||||
containers := make([]container.Config, len(pod.Spec.Containers))
|
||||
hostConfigs := make([]container.HostConfig, len(pod.Spec.Containers))
|
||||
for x, ctr := range pod.Spec.Containers {
|
||||
// Do container.Config
|
||||
var c container.Config
|
||||
c.Image = ctr.Image
|
||||
c.Cmd = ctr.Command
|
||||
ports := map[nat.Port]struct{}{}
|
||||
portBindings := nat.PortMap{}
|
||||
for _, p := range ctr.Ports {
|
||||
//TODO: p.HostPort is 0 by default, but it's invalid in hyper.sh
|
||||
if p.HostPort == 0 {
|
||||
p.HostPort = p.ContainerPort
|
||||
}
|
||||
port, err := nat.NewPort(strings.ToLower(string(p.Protocol)), fmt.Sprintf("%d", p.HostPort))
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("creating new port in container conversion failed: %v", err)
|
||||
}
|
||||
ports[port] = struct{}{}
|
||||
|
||||
portBindings[port] = []nat.PortBinding{
|
||||
{
|
||||
HostIP: "0.0.0.0",
|
||||
HostPort: fmt.Sprintf("%d", p.HostPort),
|
||||
},
|
||||
}
|
||||
}
|
||||
c.ExposedPorts = ports
|
||||
|
||||
// TODO: do volumes
|
||||
|
||||
envs := make([]string, len(ctr.Env))
|
||||
for z, e := range ctr.Env {
|
||||
envs[z] = fmt.Sprintf("%s=%s", e.Name, e.Value)
|
||||
}
|
||||
c.Env = envs
|
||||
|
||||
// Do container.HostConfig
|
||||
var hc container.HostConfig
|
||||
cpuLimit := ctr.Resources.Limits.Cpu().Value()
|
||||
memoryLimit := ctr.Resources.Limits.Memory().Value()
|
||||
|
||||
hc.Resources = container.Resources{
|
||||
CPUShares: cpuLimit,
|
||||
Memory: memoryLimit,
|
||||
}
|
||||
|
||||
hc.PortBindings = portBindings
|
||||
|
||||
containers[x] = c
|
||||
hostConfigs[x] = hc
|
||||
}
|
||||
return containers, hostConfigs, nil
|
||||
}
|
||||
|
||||
func (p *HyperProvider) containerJSONToPod(container *types.ContainerJSON) (*v1.Pod, error) {
|
||||
podName, found := container.Config.Labels[containerLabel]
|
||||
if !found {
|
||||
return nil, fmt.Errorf("can not found podName: key %q not found in container label", containerLabel)
|
||||
}
|
||||
|
||||
nodeName, found := container.Config.Labels[nodeLabel]
|
||||
if !found {
|
||||
return nil, fmt.Errorf("can not found nodeName: key %q not found in container label", containerLabel)
|
||||
}
|
||||
|
||||
created, err := time.Parse(time.RFC3339, container.Created)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse Created time failed:%v", container.Created)
|
||||
}
|
||||
startedAt, err := time.Parse(time.RFC3339, container.State.StartedAt)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse StartedAt time failed:%v", container.State.StartedAt)
|
||||
}
|
||||
finishedAt, err := time.Parse(time.RFC3339, container.State.FinishedAt)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse FinishedAt time failed:%v", container.State.FinishedAt)
|
||||
}
|
||||
|
||||
var (
|
||||
podCondition v1.PodCondition
|
||||
containerState v1.ContainerState
|
||||
)
|
||||
switch p.hyperStateToPodPhase(container.State.Status) {
|
||||
case v1.PodPending:
|
||||
podCondition = v1.PodCondition{
|
||||
Type: v1.PodInitialized,
|
||||
Status: v1.ConditionFalse,
|
||||
}
|
||||
containerState = v1.ContainerState{
|
||||
Waiting: &v1.ContainerStateWaiting{},
|
||||
}
|
||||
case v1.PodRunning: // running
|
||||
podCondition = v1.PodCondition{
|
||||
Type: v1.PodReady,
|
||||
Status: v1.ConditionTrue,
|
||||
}
|
||||
containerState = v1.ContainerState{
|
||||
Running: &v1.ContainerStateRunning{
|
||||
StartedAt: metav1.NewTime(startedAt),
|
||||
},
|
||||
}
|
||||
case v1.PodSucceeded: // normal exit
|
||||
podCondition = v1.PodCondition{
|
||||
Type: v1.PodReasonUnschedulable,
|
||||
Status: v1.ConditionFalse,
|
||||
}
|
||||
containerState = v1.ContainerState{
|
||||
Terminated: &v1.ContainerStateTerminated{
|
||||
ExitCode: int32(container.State.ExitCode),
|
||||
FinishedAt: metav1.NewTime(finishedAt),
|
||||
},
|
||||
}
|
||||
case v1.PodFailed: // exit with error
|
||||
podCondition = v1.PodCondition{
|
||||
Type: v1.PodReasonUnschedulable,
|
||||
Status: v1.ConditionFalse,
|
||||
}
|
||||
containerState = v1.ContainerState{
|
||||
Terminated: &v1.ContainerStateTerminated{
|
||||
ExitCode: int32(container.State.ExitCode),
|
||||
FinishedAt: metav1.NewTime(finishedAt),
|
||||
Reason: container.State.Error,
|
||||
},
|
||||
}
|
||||
default: //unkown
|
||||
podCondition = v1.PodCondition{
|
||||
Type: v1.PodReasonUnschedulable,
|
||||
Status: v1.ConditionUnknown,
|
||||
}
|
||||
containerState = v1.ContainerState{}
|
||||
}
|
||||
|
||||
pod := v1.Pod{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Pod",
|
||||
APIVersion: "v1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: podName,
|
||||
Namespace: "default",
|
||||
CreationTimestamp: metav1.NewTime(created),
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
NodeName: nodeName,
|
||||
Volumes: []v1.Volume{},
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
Name: podName,
|
||||
Image: container.Config.Image,
|
||||
Command: container.Config.Cmd,
|
||||
},
|
||||
},
|
||||
},
|
||||
Status: v1.PodStatus{
|
||||
Phase: p.hyperStateToPodPhase(container.State.Status),
|
||||
Conditions: []v1.PodCondition{podCondition},
|
||||
Message: "",
|
||||
Reason: "",
|
||||
HostIP: "",
|
||||
PodIP: container.NetworkSettings.IPAddress,
|
||||
ContainerStatuses: []v1.ContainerStatus{
|
||||
{
|
||||
Name: podName,
|
||||
RestartCount: int32(container.RestartCount),
|
||||
Image: container.Config.Image,
|
||||
ImageID: container.Image,
|
||||
ContainerID: container.ID,
|
||||
Ready: container.State.Running,
|
||||
State: containerState,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return &pod, nil
|
||||
}
|
||||
|
||||
func (p *HyperProvider) containerToPod(container *types.Container) (*v1.Pod, error) {
|
||||
// TODO: convert containers into pods
|
||||
podName, found := container.Labels[containerLabel]
|
||||
if !found {
|
||||
return nil, fmt.Errorf("can not found podName: key %q not found in container label", containerLabel)
|
||||
}
|
||||
|
||||
nodeName, found := container.Labels[nodeLabel]
|
||||
if !found {
|
||||
return nil, fmt.Errorf("can not found nodeName: key %q not found in container label", containerLabel)
|
||||
}
|
||||
|
||||
var (
|
||||
podCondition v1.PodCondition
|
||||
isReady bool = true
|
||||
)
|
||||
if strings.ToLower(string(container.State)) == strings.ToLower(string(v1.PodRunning)) {
|
||||
podCondition = v1.PodCondition{
|
||||
Type: v1.PodReady,
|
||||
Status: v1.ConditionTrue,
|
||||
}
|
||||
} else {
|
||||
podCondition = v1.PodCondition{
|
||||
Type: v1.PodReasonUnschedulable,
|
||||
Status: v1.ConditionFalse,
|
||||
}
|
||||
isReady = false
|
||||
}
|
||||
|
||||
pod := v1.Pod{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Pod",
|
||||
APIVersion: "v1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: podName,
|
||||
Namespace: "default",
|
||||
ClusterName: "",
|
||||
UID: "",
|
||||
CreationTimestamp: metav1.NewTime(time.Unix(container.Created, 0)),
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
NodeName: nodeName,
|
||||
Volumes: []v1.Volume{},
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
Name: podName,
|
||||
Image: container.Image,
|
||||
Command: strings.Split(container.Command, " "),
|
||||
Resources: v1.ResourceRequirements{},
|
||||
},
|
||||
},
|
||||
},
|
||||
Status: v1.PodStatus{
|
||||
//Phase: "",
|
||||
Conditions: []v1.PodCondition{podCondition},
|
||||
Message: "",
|
||||
Reason: "",
|
||||
HostIP: "",
|
||||
PodIP: "",
|
||||
ContainerStatuses: []v1.ContainerStatus{
|
||||
{
|
||||
Name: container.Names[0],
|
||||
Image: container.Image,
|
||||
ImageID: container.ImageID,
|
||||
ContainerID: container.ID,
|
||||
Ready: isReady,
|
||||
State: v1.ContainerState{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return &pod, nil
|
||||
}
|
||||
|
||||
func (p *HyperProvider) hyperStateToPodPhase(state string) v1.PodPhase {
|
||||
switch strings.ToLower(state) {
|
||||
case "created":
|
||||
return v1.PodPending
|
||||
case "restarting":
|
||||
return v1.PodPending
|
||||
case "running":
|
||||
return v1.PodRunning
|
||||
case "exited":
|
||||
return v1.PodSucceeded
|
||||
case "paused":
|
||||
return v1.PodSucceeded
|
||||
case "dead":
|
||||
return v1.PodFailed
|
||||
}
|
||||
return v1.PodUnknown
|
||||
}
|
||||
|
||||
func (p *HyperProvider) getServerHost(region string, tlsOptions *tlsconfig.Options) (host string, dft bool, err error) {
|
||||
dft = false
|
||||
host = region
|
||||
if host == "" {
|
||||
host = os.Getenv("HYPER_DEFAULT_REGION")
|
||||
region = p.getDefaultRegion()
|
||||
}
|
||||
if _, err := url.ParseRequestURI(host); err != nil {
|
||||
host = "tcp://" + region + "." + cliconfig.DefaultHyperEndpoint
|
||||
dft = true
|
||||
}
|
||||
host, err = opts.ParseHost(tlsOptions != nil, host)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *HyperProvider) getDefaultRegion() string {
|
||||
cc, ok := p.configFile.CloudConfig[cliconfig.DefaultHyperFormat]
|
||||
if ok && cc.Region != "" {
|
||||
return cc.Region
|
||||
}
|
||||
return cliconfig.DefaultHyperRegion
|
||||
}
|
||||
|
||||
func (p *HyperProvider) ensureImage(image string) error {
|
||||
distributionRef, err := reference.ParseNamed(image)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if reference.IsNameOnly(distributionRef) {
|
||||
distributionRef = reference.WithDefaultTag(distributionRef)
|
||||
log.Printf("Using default tag: %s", reference.DefaultTag)
|
||||
}
|
||||
|
||||
// Resolve the Repository name from fqn to RepositoryInfo
|
||||
repoInfo, err := registry.ParseRepositoryInfo(distributionRef)
|
||||
var authConfig types.AuthConfig
|
||||
if p.configFile != nil {
|
||||
authConfig = p.resolveAuthConfig(p.configFile.AuthConfigs, repoInfo.Index)
|
||||
}
|
||||
encodedAuth, err := p.encodeAuthToBase64(authConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
options := types.ImagePullOptions{
|
||||
RegistryAuth: encodedAuth,
|
||||
All: false,
|
||||
}
|
||||
responseBody, err := p.hyperClient.ImagePull(context.Background(), distributionRef.String(), options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer responseBody.Close()
|
||||
var (
|
||||
outFd uintptr
|
||||
isTerminalOut bool
|
||||
)
|
||||
_, stdout, _ := term.StdStreams()
|
||||
if stdout != nil {
|
||||
outFd, isTerminalOut = term.GetFdInfo(stdout)
|
||||
}
|
||||
jsonmessage.DisplayJSONMessagesStream(responseBody, stdout, outFd, isTerminalOut, nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *HyperProvider) resolveAuthConfig(authConfigs map[string]types.AuthConfig, index *registrytypes.IndexInfo) types.AuthConfig {
|
||||
configKey := index.Name
|
||||
if index.Official {
|
||||
configKey = p.electAuthServer()
|
||||
}
|
||||
|
||||
// First try the happy case
|
||||
if c, found := authConfigs[configKey]; found || index.Official {
|
||||
return c
|
||||
}
|
||||
|
||||
convertToHostname := func(url string) string {
|
||||
stripped := url
|
||||
if strings.HasPrefix(url, "http://") {
|
||||
stripped = strings.Replace(url, "http://", "", 1)
|
||||
} else if strings.HasPrefix(url, "https://") {
|
||||
stripped = strings.Replace(url, "https://", "", 1)
|
||||
}
|
||||
|
||||
nameParts := strings.SplitN(stripped, "/", 2)
|
||||
|
||||
return nameParts[0]
|
||||
}
|
||||
|
||||
// Maybe they have a legacy config file, we will iterate the keys converting
|
||||
// them to the new format and testing
|
||||
for registry, ac := range authConfigs {
|
||||
if configKey == convertToHostname(registry) {
|
||||
return ac
|
||||
}
|
||||
}
|
||||
|
||||
// When all else fails, return an empty auth config
|
||||
return types.AuthConfig{}
|
||||
}
|
||||
|
||||
func (p *HyperProvider) electAuthServer() string {
|
||||
serverAddress := registry.IndexServer
|
||||
if info, err := p.hyperClient.Info(context.Background()); err != nil {
|
||||
log.Printf("Warning: failed to get default registry endpoint from daemon (%v). Using system default: %s", err, serverAddress)
|
||||
} else {
|
||||
serverAddress = info.IndexServerAddress
|
||||
}
|
||||
return serverAddress
|
||||
}
|
||||
|
||||
// encodeAuthToBase64 serializes the auth configuration as JSON base64 payload
|
||||
func (p *HyperProvider) encodeAuthToBase64(authConfig types.AuthConfig) (string, error) {
|
||||
buf, err := json.Marshal(authConfig)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return base64.URLEncoding.EncodeToString(buf), nil
|
||||
}
|
||||
260
providers/mock/mock.go
Normal file
260
providers/mock/mock.go
Normal file
@@ -0,0 +1,260 @@
|
||||
package mock
|
||||
|
||||
import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"fmt"
|
||||
"github.com/virtual-kubelet/virtual-kubelet/providers"
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// MockProvider implements the virtual-kubelet provider interface and stores pods in memory.
|
||||
type MockProvider struct {
|
||||
nodeName string
|
||||
operatingSystem string
|
||||
internalIP string
|
||||
daemonEndpointPort int32
|
||||
pods map[string]*v1.Pod
|
||||
}
|
||||
|
||||
// NewMockProvider creates a new MockProvider
|
||||
func NewMockProvider(nodeName, operatingSystem string, internalIP string, daemonEndpointPort int32) (*MockProvider, error) {
|
||||
provider := MockProvider{
|
||||
nodeName: nodeName,
|
||||
operatingSystem: operatingSystem,
|
||||
internalIP: internalIP,
|
||||
daemonEndpointPort: daemonEndpointPort,
|
||||
pods: make(map[string]*v1.Pod),
|
||||
}
|
||||
|
||||
return &provider, nil
|
||||
}
|
||||
|
||||
// CreatePod accepts a Pod definition and stores it in memory.
|
||||
func (p *MockProvider) CreatePod(pod *v1.Pod) error {
|
||||
log.Printf("receive CreatePod %q\n", pod.Name)
|
||||
|
||||
key, err := buildKey(pod)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.pods[key] = pod
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdatePod accepts a Pod definition and updates its reference.
|
||||
func (p *MockProvider) UpdatePod(pod *v1.Pod) error {
|
||||
log.Printf("receive UpdatePod %q\n", pod.Name)
|
||||
|
||||
key, err := buildKey(pod)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.pods[key] = pod
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeletePod deletes the specified pod out of memory.
|
||||
func (p *MockProvider) DeletePod(pod *v1.Pod) (err error) {
|
||||
log.Printf("receive DeletePod %q\n", pod.Name)
|
||||
|
||||
key, err := buildKey(pod)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
delete(p.pods, key)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetPod returns a pod by name that is stored in memory.
|
||||
func (p *MockProvider) GetPod(namespace, name string) (pod *v1.Pod, err error) {
|
||||
log.Printf("receive GetPod %q\n", pod.Name)
|
||||
|
||||
key, err := buildKey(pod)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if pod, ok := p.pods[key]; ok {
|
||||
return pod, nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// GetContainerLogs retrieves the logs of a container by name from the provider.
|
||||
func (p *MockProvider) GetContainerLogs(namespace, podName, containerName string, tail int) (string, error) {
|
||||
log.Printf("receive GetContainerLogs %q\n", podName)
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// GetPodStatus returns the status of a pod by name that is "running".
|
||||
// returns nil if a pod by that name is not found.
|
||||
func (p *MockProvider) GetPodStatus(namespace, name string) (*v1.PodStatus, error) {
|
||||
log.Printf("receive GetPodStatus %q\n", name)
|
||||
|
||||
now := metav1.NewTime(time.Now())
|
||||
|
||||
status := &v1.PodStatus{
|
||||
Phase: v1.PodRunning,
|
||||
HostIP: "1.2.3.4",
|
||||
PodIP: "5.6.7.8",
|
||||
StartTime: &now,
|
||||
Conditions: []v1.PodCondition{
|
||||
{
|
||||
Type: v1.PodInitialized,
|
||||
Status: v1.ConditionTrue,
|
||||
},
|
||||
{
|
||||
Type: v1.PodReady,
|
||||
Status: v1.ConditionTrue,
|
||||
},
|
||||
{
|
||||
Type: v1.PodScheduled,
|
||||
Status: v1.ConditionTrue,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
pod, err := p.GetPod(namespace, name)
|
||||
if err != nil {
|
||||
return status, err
|
||||
}
|
||||
|
||||
for _, container := range pod.Spec.Containers {
|
||||
status.ContainerStatuses = append(status.ContainerStatuses, v1.ContainerStatus{
|
||||
Name: container.Name,
|
||||
Image: container.Image,
|
||||
Ready: true,
|
||||
RestartCount: 0,
|
||||
State: v1.ContainerState{
|
||||
Running: &v1.ContainerStateRunning{
|
||||
StartedAt: now,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return status, nil
|
||||
}
|
||||
|
||||
// GetPods returns a list of all pods known to be "running".
|
||||
func (p *MockProvider) GetPods() ([]*v1.Pod, error) {
|
||||
log.Printf("receive GetPods\n")
|
||||
|
||||
var pods []*v1.Pod
|
||||
|
||||
for _, pod := range p.pods {
|
||||
pods = append(pods, pod)
|
||||
}
|
||||
|
||||
return pods, nil
|
||||
}
|
||||
|
||||
// Capacity returns a resource list containing the capacity limits.
|
||||
func (p *MockProvider) Capacity() v1.ResourceList {
|
||||
// TODO: These should be configurable
|
||||
return v1.ResourceList{
|
||||
"cpu": resource.MustParse("20"),
|
||||
"memory": resource.MustParse("100Gi"),
|
||||
"pods": resource.MustParse("20"),
|
||||
}
|
||||
}
|
||||
|
||||
// NodeConditions returns a list of conditions (Ready, OutOfDisk, etc), for updates to the node status
|
||||
// within Kubernetes.
|
||||
func (p *MockProvider) NodeConditions() []v1.NodeCondition {
|
||||
// TODO: Make this configurable
|
||||
return []v1.NodeCondition{
|
||||
{
|
||||
Type: "Ready",
|
||||
Status: v1.ConditionTrue,
|
||||
LastHeartbeatTime: metav1.Now(),
|
||||
LastTransitionTime: metav1.Now(),
|
||||
Reason: "KubeletReady",
|
||||
Message: "kubelet is ready.",
|
||||
},
|
||||
{
|
||||
Type: "OutOfDisk",
|
||||
Status: v1.ConditionFalse,
|
||||
LastHeartbeatTime: metav1.Now(),
|
||||
LastTransitionTime: metav1.Now(),
|
||||
Reason: "KubeletHasSufficientDisk",
|
||||
Message: "kubelet has sufficient disk space available",
|
||||
},
|
||||
{
|
||||
Type: "MemoryPressure",
|
||||
Status: v1.ConditionFalse,
|
||||
LastHeartbeatTime: metav1.Now(),
|
||||
LastTransitionTime: metav1.Now(),
|
||||
Reason: "KubeletHasSufficientMemory",
|
||||
Message: "kubelet has sufficient memory available",
|
||||
},
|
||||
{
|
||||
Type: "DiskPressure",
|
||||
Status: v1.ConditionFalse,
|
||||
LastHeartbeatTime: metav1.Now(),
|
||||
LastTransitionTime: metav1.Now(),
|
||||
Reason: "KubeletHasNoDiskPressure",
|
||||
Message: "kubelet has no disk pressure",
|
||||
},
|
||||
{
|
||||
Type: "NetworkUnavailable",
|
||||
Status: v1.ConditionFalse,
|
||||
LastHeartbeatTime: metav1.Now(),
|
||||
LastTransitionTime: metav1.Now(),
|
||||
Reason: "RouteCreated",
|
||||
Message: "RouteController created a route",
|
||||
},
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// NodeAddresses returns a list of addresses for the node status
|
||||
// within Kubernetes.
|
||||
func (p *MockProvider) NodeAddresses() []v1.NodeAddress {
|
||||
return []v1.NodeAddress{
|
||||
{
|
||||
Type: "InternalIP",
|
||||
Address: p.internalIP,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NodeDaemonEndpoints returns NodeDaemonEndpoints for the node status
|
||||
// within Kubernetes.
|
||||
func (p *MockProvider) NodeDaemonEndpoints() *v1.NodeDaemonEndpoints {
|
||||
return &v1.NodeDaemonEndpoints{
|
||||
KubeletEndpoint: v1.DaemonEndpoint{
|
||||
Port: p.daemonEndpointPort,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// OperatingSystem returns the operating system for this provider.
|
||||
// This is a noop to default to Linux for now.
|
||||
func (p *MockProvider) OperatingSystem() string {
|
||||
return providers.OperatingSystemLinux
|
||||
}
|
||||
|
||||
// buildKey is a helper for building the "key" for the providers pod store.
|
||||
func buildKey(pod *v1.Pod) (string, error) {
|
||||
if pod.ObjectMeta.Namespace == "" {
|
||||
return "", fmt.Errorf("pod namespace not found")
|
||||
}
|
||||
|
||||
if pod.ObjectMeta.Name == "" {
|
||||
return "", fmt.Errorf("pod name not found")
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s-%s", pod.ObjectMeta.Namespace, pod.ObjectMeta.Name), nil
|
||||
}
|
||||
277
providers/web/broker.go
Normal file
277
providers/web/broker.go
Normal file
@@ -0,0 +1,277 @@
|
||||
// Package web provides an implementation of the virtual kubelet provider interface
|
||||
// by forwarding all calls to a web endpoint. The web endpoint to which requests
|
||||
// must be forwarded must be specified through an environment variable called
|
||||
// `WEB_ENDPOINT_URL`. This endpoint must implement the following HTTP APIs:
|
||||
// - POST /createPod
|
||||
// - PUT /updatePod
|
||||
// - DELETE /deletePod
|
||||
// - GET /getPod?namespace=[namespace]&name=[pod name]
|
||||
// - GE /getContainerLogs?namespace=[namespace]&podName=[pod name]&containerName=[container name]&tail=[tail value]
|
||||
// - GET /getPodStatus?namespace=[namespace]&name=[pod name]
|
||||
// - GET /getPods
|
||||
// - GET /capacity
|
||||
// - GET /nodeConditions
|
||||
// - GET /nodeAddresses
|
||||
package web
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/cenkalti/backoff"
|
||||
"k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
// BrokerProvider implements the virtual-kubelet provider interface by forwarding kubelet calls to a web endpoint.
|
||||
type BrokerProvider struct {
|
||||
nodeName string
|
||||
operatingSystem string
|
||||
endpoint *url.URL
|
||||
client *http.Client
|
||||
daemonEndpointPort int32
|
||||
}
|
||||
|
||||
// NewBrokerProvider creates a new BrokerProvider
|
||||
func NewBrokerProvider(nodeName, operatingSystem string, daemonEndpointPort int32) (*BrokerProvider, error) {
|
||||
var provider BrokerProvider
|
||||
|
||||
provider.nodeName = nodeName
|
||||
provider.operatingSystem = operatingSystem
|
||||
provider.client = &http.Client{}
|
||||
provider.daemonEndpointPort = daemonEndpointPort
|
||||
|
||||
if ep := os.Getenv("WEB_ENDPOINT_URL"); ep != "" {
|
||||
epurl, err := url.Parse(ep)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
provider.endpoint = epurl
|
||||
}
|
||||
|
||||
return &provider, nil
|
||||
}
|
||||
|
||||
// CreatePod accepts a Pod definition and forwards the call to the web endpoint
|
||||
func (p *BrokerProvider) CreatePod(pod *v1.Pod) error {
|
||||
return p.createUpdatePod(pod, "POST", "/createPod")
|
||||
}
|
||||
|
||||
// UpdatePod accepts a Pod definition and forwards the call to the web endpoint
|
||||
func (p *BrokerProvider) UpdatePod(pod *v1.Pod) error {
|
||||
return p.createUpdatePod(pod, "PUT", "/updatePod")
|
||||
}
|
||||
|
||||
// DeletePod accepts a Pod definition and forwards the call to the web endpoint
|
||||
func (p *BrokerProvider) DeletePod(pod *v1.Pod) error {
|
||||
urlPath, err := url.Parse("/deletePod")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// encode pod definition as JSON and post request
|
||||
podJSON, err := json.Marshal(pod)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = p.doRequest("DELETE", urlPath, podJSON, false)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetPod returns a pod by name that is being managed by the web server
|
||||
func (p *BrokerProvider) GetPod(namespace, name string) (*v1.Pod, error) {
|
||||
urlPathStr := fmt.Sprintf(
|
||||
"/getPod?namespace=%s&name=%s",
|
||||
url.QueryEscape(namespace),
|
||||
url.QueryEscape(name))
|
||||
|
||||
var pod v1.Pod
|
||||
err := p.doGetRequest(urlPathStr, &pod)
|
||||
|
||||
// if we get a "404 Not Found" then we return nil to indicate that no pod
|
||||
// with this name was found
|
||||
if err != nil && err.Error() == "404 Not Found" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return &pod, err
|
||||
}
|
||||
|
||||
// GetContainerLogs returns the logs of a container running in a pod by name.
|
||||
func (p *BrokerProvider) GetContainerLogs(namespace, podName, containerName string, tail int) (string, error) {
|
||||
urlPathStr := fmt.Sprintf(
|
||||
"/getContainerLogs?namespace=%s&podName=%s&containerName=%s&tail=%d",
|
||||
url.QueryEscape(namespace),
|
||||
url.QueryEscape(podName),
|
||||
url.QueryEscape(containerName),
|
||||
tail)
|
||||
|
||||
response, err := p.doGetRequestBytes(urlPathStr)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(response), nil
|
||||
}
|
||||
|
||||
// GetPodStatus retrieves the status of a given pod by name.
|
||||
func (p *BrokerProvider) GetPodStatus(namespace, name string) (*v1.PodStatus, error) {
|
||||
urlPathStr := fmt.Sprintf(
|
||||
"/getPodStatus?namespace=%s&name=%s",
|
||||
url.QueryEscape(namespace),
|
||||
url.QueryEscape(name))
|
||||
|
||||
var podStatus v1.PodStatus
|
||||
err := p.doGetRequest(urlPathStr, &podStatus)
|
||||
|
||||
// if we get a "404 Not Found" then we return nil to indicate that no pod
|
||||
// with this name was found
|
||||
if err != nil && err.Error() == "404 Not Found" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return &podStatus, err
|
||||
}
|
||||
|
||||
// GetPods retrieves a list of all pods scheduled to run.
|
||||
func (p *BrokerProvider) GetPods() ([]*v1.Pod, error) {
|
||||
var pods []*v1.Pod
|
||||
err := p.doGetRequest("/getPods", &pods)
|
||||
|
||||
return pods, err
|
||||
}
|
||||
|
||||
// Capacity returns a resource list containing the capacity limits
|
||||
func (p *BrokerProvider) Capacity() v1.ResourceList {
|
||||
var resourceList v1.ResourceList
|
||||
err := p.doGetRequest("/capacity", &resourceList)
|
||||
|
||||
// TODO: This API should support reporting an error.
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return resourceList
|
||||
}
|
||||
|
||||
// NodeConditions returns a list of conditions (Ready, OutOfDisk, etc), for updates to the node status
|
||||
func (p *BrokerProvider) NodeConditions() []v1.NodeCondition {
|
||||
var nodeConditions []v1.NodeCondition
|
||||
err := p.doGetRequest("/nodeConditions", &nodeConditions)
|
||||
|
||||
// TODO: This API should support reporting an error.
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return nodeConditions
|
||||
}
|
||||
|
||||
// NodeAddresses returns a list of addresses for the node status
|
||||
// within Kubernetes.
|
||||
func (p *BrokerProvider) NodeAddresses() []v1.NodeAddress {
|
||||
var nodeAddresses []v1.NodeAddress
|
||||
err := p.doGetRequest("/nodeAddresses", &nodeAddresses)
|
||||
|
||||
// TODO: This API should support reporting an error.
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return nodeAddresses
|
||||
}
|
||||
|
||||
// NodeDaemonEndpoints returns NodeDaemonEndpoints for the node status
|
||||
// within Kubernetes.
|
||||
func (p *BrokerProvider) NodeDaemonEndpoints() *v1.NodeDaemonEndpoints {
|
||||
return &v1.NodeDaemonEndpoints{
|
||||
KubeletEndpoint: v1.DaemonEndpoint{
|
||||
Port: p.daemonEndpointPort,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// OperatingSystem returns the operating system for this provider.
|
||||
func (p *BrokerProvider) OperatingSystem() string {
|
||||
return p.operatingSystem
|
||||
}
|
||||
|
||||
func (p *BrokerProvider) doGetRequest(urlPathStr string, v interface{}) error {
|
||||
response, err := p.doGetRequestBytes(urlPathStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return json.Unmarshal(response, &v)
|
||||
}
|
||||
|
||||
func (p *BrokerProvider) doGetRequestBytes(urlPathStr string) ([]byte, error) {
|
||||
urlPath, err := url.Parse(urlPathStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return p.doRequest("GET", urlPath, nil, true)
|
||||
}
|
||||
|
||||
func (p *BrokerProvider) createUpdatePod(pod *v1.Pod, method, postPath string) error {
|
||||
// build the post url
|
||||
postPathURL, err := url.Parse(postPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// encode pod definition as JSON and post request
|
||||
podJSON, err := json.Marshal(pod)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = p.doRequest(method, postPathURL, podJSON, false)
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *BrokerProvider) doRequest(method string, urlPath *url.URL, body []byte, readResponse bool) ([]byte, error) {
|
||||
// build full URL
|
||||
requestURL := p.endpoint.ResolveReference(urlPath)
|
||||
|
||||
// build the request
|
||||
var bodyReader io.Reader
|
||||
if body != nil {
|
||||
bodyReader = bytes.NewReader(body)
|
||||
}
|
||||
request, err := http.NewRequest(method, requestURL.String(), bodyReader)
|
||||
request.Header.Add("Content-Type", "application/json")
|
||||
|
||||
// issue request
|
||||
retry := backoff.NewExponentialBackOff()
|
||||
retry.MaxElapsedTime = 5 * time.Minute
|
||||
|
||||
var response *http.Response
|
||||
err = backoff.Retry(func() error {
|
||||
response, err = p.client.Do(request)
|
||||
return err
|
||||
}, retry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer response.Body.Close()
|
||||
if response.StatusCode < 200 || response.StatusCode > 299 {
|
||||
return nil, errors.New(response.Status)
|
||||
}
|
||||
|
||||
// read response body if asked to
|
||||
if readResponse {
|
||||
return ioutil.ReadAll(response.Body)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
21
providers/web/charts/virtual-kubelet-web/.helmignore
Normal file
21
providers/web/charts/virtual-kubelet-web/.helmignore
Normal file
@@ -0,0 +1,21 @@
|
||||
# Patterns to ignore when building packages.
|
||||
# This supports shell glob matching, relative path matching, and
|
||||
# negation (prefixed with !). Only one pattern per line.
|
||||
.DS_Store
|
||||
# Common VCS dirs
|
||||
.git/
|
||||
.gitignore
|
||||
.bzr/
|
||||
.bzrignore
|
||||
.hg/
|
||||
.hgignore
|
||||
.svn/
|
||||
# Common backup files
|
||||
*.swp
|
||||
*.bak
|
||||
*.tmp
|
||||
*~
|
||||
# Various IDEs
|
||||
.project
|
||||
.idea/
|
||||
*.tmproj
|
||||
4
providers/web/charts/virtual-kubelet-web/Chart.yaml
Normal file
4
providers/web/charts/virtual-kubelet-web/Chart.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
name: virtual-kubelet-web
|
||||
version: 0.1.0
|
||||
description: a Helm chart to install virtual kubelet in Kubernetes setup with a web provider
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
The virtual kubelet is getting deployed on your cluster.
|
||||
|
||||
To verify that virtual kubelet has started, run:
|
||||
|
||||
kubectl --namespace={{ .Release.Namespace }} get pods -l "app={{ template "virtual-kubelet-web.fullname" . }}"
|
||||
@@ -0,0 +1,16 @@
|
||||
{{/* vim: set filetype=mustache: */}}
|
||||
{{/*
|
||||
Expand the name of the chart.
|
||||
*/}}
|
||||
{{- define "virtual-kubelet-web.name" -}}
|
||||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Create a default fully qualified app name.
|
||||
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
||||
*/}}
|
||||
{{- define "virtual-kubelet-web.fullname" -}}
|
||||
{{- $name := default .Chart.Name .Values.nameOverride -}}
|
||||
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
||||
@@ -0,0 +1,42 @@
|
||||
apiVersion: extensions/v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ template "virtual-kubelet-web.fullname" . }}
|
||||
labels:
|
||||
app: {{ template "virtual-kubelet-web.name" . }}
|
||||
chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
|
||||
release: {{ .Release.Name }}
|
||||
heritage: {{ .Release.Service }}
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: {{ template "virtual-kubelet-web.name" . }}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: {{ template "virtual-kubelet-web.name" . }}
|
||||
release: {{ .Release.Name }}
|
||||
spec:
|
||||
containers:
|
||||
- name: rustwebprovider
|
||||
image: "{{ .Values.rustwebimage.repository }}:{{ .Values.rustwebimage.tag }}"
|
||||
imagePullPolicy: {{ .Values.rustwebimage.pullPolicy }}
|
||||
ports:
|
||||
- containerPort: {{ .Values.rustwebimage.port }}
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: {{ .Values.rustwebimage.port }}
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: {{ .Values.rustwebimage.port }}
|
||||
- name: virtualkubelet
|
||||
image: "{{ .Values.vkimage.repository }}:{{ .Values.vkimage.tag }}"
|
||||
imagePullPolicy: {{ .Values.vkimage.pullPolicy }}
|
||||
env:
|
||||
- name: WEB_ENDPOINT_URL
|
||||
value: http://localhost:{{ .Values.rustwebimage.port }}
|
||||
command: ["virtual-kubelet"]
|
||||
args: ["--provider", "web", "--nodename", {{ default "web-provider" .Values.env.nodeName | quote }}]
|
||||
11
providers/web/charts/virtual-kubelet-web/values.yaml
Normal file
11
providers/web/charts/virtual-kubelet-web/values.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
rustwebimage:
|
||||
repository: avranju/rust-web-provider
|
||||
tag: latest
|
||||
pullPolicy: Always
|
||||
port: 3000
|
||||
vkimage:
|
||||
repository: avranju/virtual-kubelet
|
||||
tag: latest
|
||||
pullPolicy: Always
|
||||
env:
|
||||
nodeName: virtual-kubelet-web
|
||||
4
providers/web/rust-web-provider/.gitignore
vendored
Normal file
4
providers/web/rust-web-provider/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
/target/
|
||||
**/*.rs.bk
|
||||
.idea/
|
||||
.vscode/
|
||||
988
providers/web/rust-web-provider/Cargo.lock
generated
Normal file
988
providers/web/rust-web-provider/Cargo.lock
generated
Normal file
@@ -0,0 +1,988 @@
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "bodyparser"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"iron 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"persistent 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"plugin 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"iovec 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "dtoa"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fuchsia-zircon"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"fuchsia-zircon-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fuchsia-zircon"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"fuchsia-zircon-sys 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fuchsia-zircon-sys"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fuchsia-zircon-sys"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.1.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "futures-cpupool"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"futures 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httparse"
|
||||
version = "1.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "0.10.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"base64 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"httparse 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"mime 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"time 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"traitobject 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"url 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "0.11.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"base64 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bytes 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"futures 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"futures-cpupool 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"httparse 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"mime 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"relay 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"time 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tokio-core 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tokio-io 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tokio-proto 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tokio-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicase 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-normalization 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iovec"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iron"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"hyper 0.10.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"mime_guess 1.8.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"modifier 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"plugin 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"typemap 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"url 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "kernel32-sys"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kube_rust"
|
||||
version = "1.0.0"
|
||||
source = "git+https://github.com/avranju/kube-rust?rev=058de6366d0d75cb60b2d0fd5ba1abd2e7d83fff#058de6366d0d75cb60b2d0fd5ba1abd2e7d83fff"
|
||||
dependencies = [
|
||||
"base64 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"futures 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"hyper 0.11.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_yaml 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"url 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "language-tags"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "lazycell"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "linked-hash-map"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matches"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mime"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mime"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"unicase 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mime_guess"
|
||||
version = "1.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"mime 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"phf 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"phf_codegen 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "0.6.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"fuchsia-zircon 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"fuchsia-zircon-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"iovec 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazycell 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"net2 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miow"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"net2 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "modifier"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "net2"
|
||||
version = "0.2.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num"
|
||||
version = "0.1.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"num-bigint 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-complex 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-integer 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-iter 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-rational 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-traits 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint"
|
||||
version = "0.1.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"num-integer 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-traits 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-complex"
|
||||
version = "0.1.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"num-traits 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"num-traits 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-iter"
|
||||
version = "0.1.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"num-integer 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-traits 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-rational"
|
||||
version = "0.1.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"num-bigint 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-integer 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-traits 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.1.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "persistent"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"iron 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"plugin 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf"
|
||||
version = "0.7.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"phf_shared 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_codegen"
|
||||
version = "0.7.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"phf_generator 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"phf_shared 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_generator"
|
||||
version = "0.7.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"phf_shared 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_shared"
|
||||
version = "0.7.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"siphasher 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "plugin"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"typemap 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "0.3.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.3.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"fuchsia-zircon 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.1.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex-syntax 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "relay"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"futures 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "route-recognizer"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "router"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"iron 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"route-recognizer 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"url 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-web-provider"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"env_logger 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"kube_rust 1.0.0 (git+https://github.com/avranju/kube-rust?rev=058de6366d0d75cb60b2d0fd5ba1abd2e7d83fff)",
|
||||
"log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"time 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"virtual-kubelet-adapter 0.1.0 (git+https://github.com/avranju/rust-virtual-kubelet-adapter?rev=4250103d31e2864725e47bdd23295e79ee12b6d0)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-serialize"
|
||||
version = "0.3.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "safemem"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "scoped-tls"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive_internals 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive_internals"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"itoa 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-traits 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_yaml"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"linked-hash-map 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-traits 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"yaml-rust 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "0.11.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "synom"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "take"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.1.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"redox_syscall 0.1.33 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-core"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bytes 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"futures 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"iovec 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"mio 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"scoped-tls 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"slab 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tokio-io 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-io"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bytes 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"futures 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-proto"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"futures 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"net2 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"smallvec 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"take 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tokio-core 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tokio-io 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tokio-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-service"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"futures 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "traitobject"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "typeable"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "typemap"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"unsafe-any 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicase"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"version_check 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicase"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"version_check 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-normalization"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "unreachable"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unsafe-any"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"traitobject 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"idna 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf8-ranges"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "virtual-kubelet-adapter"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/avranju/rust-virtual-kubelet-adapter?rev=4250103d31e2864725e47bdd23295e79ee12b6d0#4250103d31e2864725e47bdd23295e79ee12b6d0"
|
||||
dependencies = [
|
||||
"bodyparser 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"iron 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"kube_rust 1.0.0 (git+https://github.com/avranju/kube-rust?rev=058de6366d0d75cb60b2d0fd5ba1abd2e7d83fff)",
|
||||
"num 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"persistent 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"router 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"url 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "void"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-build"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "ws2_32-sys"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yaml-rust"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"linked-hash-map 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[metadata]
|
||||
"checksum aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d6531d44de723825aa81398a6415283229725a00fa30713812ab9323faa82fc4"
|
||||
"checksum base64 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "96434f987501f0ed4eb336a411e0631ecd1afa11574fe148587adc4ff96143c9"
|
||||
"checksum base64 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5032d51da2741729bfdaeb2664d9b8c6d9fd1e2b90715c660b6def36628499c2"
|
||||
"checksum base64 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "229d032f1a99302697f10b27167ae6d03d49d032e6a8e2550e8d3fc13356d2b4"
|
||||
"checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d"
|
||||
"checksum bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b3c30d3802dfb7281680d6285f2ccdaa8c2d8fee41f93805dba5c4cf50dc23cf"
|
||||
"checksum bodyparser 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f023abfa58aad6f6bc4ae0630799e24d5ee0ab8bb2e49f651d9b1f9aa4f52f30"
|
||||
"checksum byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "652805b7e73fada9d85e9a6682a4abd490cb52d96aeecc12e33a0de34dfd0d23"
|
||||
"checksum bytes 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d828f97b58cc5de3e40c421d0cf2132d6b2da4ee0e11b8632fa838f0f9333ad6"
|
||||
"checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de"
|
||||
"checksum dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "09c3753c3db574d215cba4ea76018483895d7bff25a31b49ba45db21c48e50ab"
|
||||
"checksum env_logger 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3ddf21e73e016298f5cb37d6ef8e8da8e39f91f9ec8b0df44b7deb16a9f8cd5b"
|
||||
"checksum fuchsia-zircon 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f6c0581a4e363262e52b87f59ee2afe3415361c6ec35e665924eb08afe8ff159"
|
||||
"checksum fuchsia-zircon 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bd510087c325af53ba24f3be8f1c081b0982319adcb8b03cad764512923ccc19"
|
||||
"checksum fuchsia-zircon-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "43f3795b4bae048dc6123a6b972cadde2e676f9ded08aef6bb77f5f157684a82"
|
||||
"checksum fuchsia-zircon-sys 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "08b3a6f13ad6b96572b53ce7af74543132f1a7055ccceb6d073dd36c54481859"
|
||||
"checksum futures 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)" = "118b49cac82e04121117cbd3121ede3147e885627d82c4546b87c702debb90c1"
|
||||
"checksum futures-cpupool 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "e86f49cc0d92fe1b97a5980ec32d56208272cbb00f15044ea9e2799dde766fdf"
|
||||
"checksum httparse 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "af2f2dd97457e8fb1ae7c5a420db346af389926e36f43768b96f101546b04a07"
|
||||
"checksum hyper 0.10.13 (registry+https://github.com/rust-lang/crates.io-index)" = "368cb56b2740ebf4230520e2b90ebb0461e69034d85d1945febd9b3971426db2"
|
||||
"checksum hyper 0.11.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4844b207be8393981c5fcb61c9372d7c96432fcc8f5c3431a255a9d19b5c298b"
|
||||
"checksum idna 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "014b298351066f1512874135335d62a789ffe78a9974f94b43ed5621951eaf7d"
|
||||
"checksum iovec 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b6e8b9c2247fcf6c6a1151f1156932be5606c9fd6f55a2d7f9fc1cb29386b2f7"
|
||||
"checksum iron 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d8e17268922834707e1c29e8badbf9c712c9c43378e1b6a3388946baff10be2"
|
||||
"checksum itoa 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8324a32baf01e2ae060e9de58ed0bc2320c9a2833491ee36cd3b4c414de4db8c"
|
||||
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
|
||||
"checksum kube_rust 1.0.0 (git+https://github.com/avranju/kube-rust?rev=058de6366d0d75cb60b2d0fd5ba1abd2e7d83fff)" = "<none>"
|
||||
"checksum language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a"
|
||||
"checksum lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c8f31047daa365f19be14b47c29df4f7c3b581832407daabe6ae77397619237d"
|
||||
"checksum lazycell 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3b585b7a6811fb03aa10e74b278a0f00f8dd9b45dc681f148bb29fa5cb61859b"
|
||||
"checksum libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)" = "36fbc8a8929c632868295d0178dd8f63fc423fd7537ad0738372bd010b3ac9b0"
|
||||
"checksum linked-hash-map 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2d2aab0478615bb586559b0114d94dd8eca4fdbb73b443adcb0d00b61692b4bf"
|
||||
"checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b"
|
||||
"checksum log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "89f010e843f2b1a31dbd316b3b8d443758bc634bed37aabade59c686d644e0a2"
|
||||
"checksum matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "100aabe6b8ff4e4a7e32c1c13523379802df0772b82466207ac25b013f193376"
|
||||
"checksum memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "796fba70e76612589ed2ce7f45282f5af869e0fdd7cc6199fa1aa1f1d591ba9d"
|
||||
"checksum mime 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ba626b8a6de5da682e1caa06bdb42a335aee5a84db8e5046a3e8ab17ba0a3ae0"
|
||||
"checksum mime 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e2e00e17be181010a91dbfefb01660b17311059dc8c7f48b9017677721e732bd"
|
||||
"checksum mime_guess 1.8.3 (registry+https://github.com/rust-lang/crates.io-index)" = "dc7e82a15629bb4ecd9e72365bf33d1382be91e030f820edb8e2a21c02430da8"
|
||||
"checksum mio 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)" = "0e8411968194c7b139e9105bc4ae7db0bae232af087147e72f0616ebf5fdb9cb"
|
||||
"checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919"
|
||||
"checksum modifier 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "41f5c9112cb662acd3b204077e0de5bc66305fa8df65c8019d5adb10e9ab6e58"
|
||||
"checksum net2 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)" = "3a80f842784ef6c9a958b68b7516bc7e35883c614004dd94959a4dca1b716c09"
|
||||
"checksum num 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "cc4083e14b542ea3eb9b5f33ff48bd373a92d78687e74f4cc0a30caeb754f0ca"
|
||||
"checksum num-bigint 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "bdc1494b5912f088f260b775799468d9b9209ac60885d8186a547a0476289e23"
|
||||
"checksum num-complex 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "58de7b4bf7cf5dbecb635a5797d489864eadd03b107930cbccf9e0fd7428b47c"
|
||||
"checksum num-integer 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)" = "d1452e8b06e448a07f0e6ebb0bb1d92b8890eea63288c0b627331d53514d0fba"
|
||||
"checksum num-iter 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)" = "7485fcc84f85b4ecd0ea527b14189281cf27d60e583ae65ebc9c088b13dffe01"
|
||||
"checksum num-rational 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "0c7cb72a95250d8a370105c828f388932373e0e94414919891a0f945222310fe"
|
||||
"checksum num-traits 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "cacfcab5eb48250ee7d0c7896b51a2c5eec99c1feea5f32025635f5ae4b00070"
|
||||
"checksum num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c51a3322e4bca9d212ad9a158a02abc6934d005490c054a2778df73a70aa0a30"
|
||||
"checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831"
|
||||
"checksum persistent 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8e8fa0009c4f3d350281309909c618abddf10bb7e3145f28410782f6a5ec74c5"
|
||||
"checksum phf 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)" = "cb325642290f28ee14d8c6201159949a872f220c62af6e110a56ea914fbe42fc"
|
||||
"checksum phf_codegen 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)" = "d62594c0bb54c464f633175d502038177e90309daf2e0158be42ed5f023ce88f"
|
||||
"checksum phf_generator 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)" = "6b07ffcc532ccc85e3afc45865469bf5d9e4ef5bfcf9622e3cfe80c2d275ec03"
|
||||
"checksum phf_shared 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)" = "07e24b0ca9643bdecd0632f2b3da6b1b89bbb0030e0b992afc1113b23a7bc2f2"
|
||||
"checksum plugin 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "1a6a0dc3910bc8db877ffed8e457763b317cf880df4ae19109b9f77d277cf6e0"
|
||||
"checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a"
|
||||
"checksum rand 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)" = "9e7944d95d25ace8f377da3ac7068ce517e4c646754c43a1b1849177bbf72e59"
|
||||
"checksum redox_syscall 0.1.33 (registry+https://github.com/rust-lang/crates.io-index)" = "07b8f011e3254d5a9b318fde596d409a0001c9ae4c6e7907520c2eaa4d988c99"
|
||||
"checksum regex 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "744554e01ccbd98fff8c457c3b092cd67af62a555a43bfe97ae8a0451f7799fa"
|
||||
"checksum regex-syntax 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8e931c58b93d86f080c734bfd2bce7dd0079ae2331235818133c8be7f422e20e"
|
||||
"checksum relay 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f301bafeb60867c85170031bdb2fcf24c8041f33aee09e7b116a58d4e9f781c5"
|
||||
"checksum route-recognizer 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3255338088df8146ba63d60a9b8e3556f1146ce2973bc05a75181a42ce2256"
|
||||
"checksum router 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "dc63b6f3b8895b0d04e816b2b1aa58fdba2d5acca3cbb8f0ab8e017347d57397"
|
||||
"checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda"
|
||||
"checksum safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e27a8b19b835f7aea908818e871f5cc3a5a186550c30773be987e155e8163d8f"
|
||||
"checksum scoped-tls 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f417c22df063e9450888a7561788e9bd46d3bb3c1466435b4eccb903807f147d"
|
||||
"checksum serde 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)" = "db99f3919e20faa51bb2996057f5031d8685019b5a06139b1ce761da671b8526"
|
||||
"checksum serde_derive 1.0.27 (registry+https://github.com/rust-lang/crates.io-index)" = "f4ba7591cfe93755e89eeecdbcc668885624829b020050e6aec99c2a03bd3fd0"
|
||||
"checksum serde_derive_internals 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6e03f1c9530c3fb0a0a5c9b826bdd9246a5921ae995d75f512ac917fc4dd55b5"
|
||||
"checksum serde_json 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c9db7266c7d63a4c4b7fe8719656ccdd51acf1bed6124b174f933b009fb10bcb"
|
||||
"checksum serde_yaml 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e0f868d400d9d13d00988da49f7f02aeac6ef00f11901a8c535bd59d777b9e19"
|
||||
"checksum siphasher 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0df90a788073e8d0235a67e50441d47db7c8ad9debd91cbf43736a2a92d36537"
|
||||
"checksum slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "17b4fcaed89ab08ef143da37bc52adbcc04d4a69014f4c1208d6b51f0c47bc23"
|
||||
"checksum slab 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fdeff4cd9ecff59ec7e3744cbca73dfe5ac35c2aedb2cfba8a1c715a18912e9d"
|
||||
"checksum smallvec 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4c8cbcd6df1e117c2210e13ab5109635ad68a929fcbb8964dc965b76cb5ee013"
|
||||
"checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad"
|
||||
"checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6"
|
||||
"checksum take 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b157868d8ac1f56b64604539990685fa7611d8fa9e5476cf0c02cf34d32917c5"
|
||||
"checksum thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "279ef31c19ededf577bfd12dfae728040a21f635b06a24cd670ff510edd38963"
|
||||
"checksum time 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)" = "d5d788d3aa77bc0ef3e9621256885555368b47bd495c13dd2e7413c89f845520"
|
||||
"checksum tokio-core 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "c87c27560184212c9dc45cd8f38623f37918248aad5b58fb65303b5d07a98c6e"
|
||||
"checksum tokio-io 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "514aae203178929dbf03318ad7c683126672d4d96eccb77b29603d33c9e25743"
|
||||
"checksum tokio-proto 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8fbb47ae81353c63c487030659494b295f6cb6576242f907f203473b191b0389"
|
||||
"checksum tokio-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "24da22d077e0f15f55162bdbdc661228c1581892f52074fb242678d015b45162"
|
||||
"checksum traitobject 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079"
|
||||
"checksum typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1410f6f91f21d1612654e7cc69193b0334f909dcf2c790c4826254fbb86f8887"
|
||||
"checksum typemap 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "653be63c80a3296da5551e1bfd2cca35227e13cdd08c6668903ae2f4f77aa1f6"
|
||||
"checksum unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7f4765f83163b74f957c797ad9253caf97f103fb064d3999aea9568d09fc8a33"
|
||||
"checksum unicase 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "284b6d3db520d67fbe88fd778c21510d1b0ba4a551e5d0fbb023d33405f6de8a"
|
||||
"checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5"
|
||||
"checksum unicode-normalization 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "51ccda9ef9efa3f7ef5d91e8f9b83bbe6955f9bf86aec89d5cce2c874625920f"
|
||||
"checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc"
|
||||
"checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56"
|
||||
"checksum unsafe-any 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f30360d7979f5e9c6e6cea48af192ea8fab4afb3cf72597154b8f08935bc9c7f"
|
||||
"checksum url 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fa35e768d4daf1d85733418a49fb42e10d7f633e394fccab4ab7aba897053fe2"
|
||||
"checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122"
|
||||
"checksum version_check 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6b772017e347561807c1aa192438c5fd74242a670a6cffacc40f2defd1dc069d"
|
||||
"checksum virtual-kubelet-adapter 0.1.0 (git+https://github.com/avranju/rust-virtual-kubelet-adapter?rev=4250103d31e2864725e47bdd23295e79ee12b6d0)" = "<none>"
|
||||
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
|
||||
"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
|
||||
"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
|
||||
"checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e"
|
||||
"checksum yaml-rust 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "57ab38ee1a4a266ed033496cf9af1828d8d6e6c1cfa5f643a2809effcae4d628"
|
||||
11
providers/web/rust-web-provider/Cargo.toml
Normal file
11
providers/web/rust-web-provider/Cargo.toml
Normal file
@@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "rust-web-provider"
|
||||
version = "0.1.0"
|
||||
authors = ["Rajasekharan Vengalil <rajave@microsoft.com>"]
|
||||
|
||||
[dependencies]
|
||||
kube_rust = { git = "https://github.com/avranju/kube-rust", rev = "058de6366d0d75cb60b2d0fd5ba1abd2e7d83fff" }
|
||||
virtual-kubelet-adapter = { git = "https://github.com/avranju/rust-virtual-kubelet-adapter", rev = "4250103d31e2864725e47bdd23295e79ee12b6d0"}
|
||||
log = "0.4"
|
||||
env_logger = "0.4"
|
||||
time = "0.1"
|
||||
9
providers/web/rust-web-provider/Dockerfile
Normal file
9
providers/web/rust-web-provider/Dockerfile
Normal file
@@ -0,0 +1,9 @@
|
||||
FROM debian:stretch-slim
|
||||
|
||||
WORKDIR /app
|
||||
ADD ./rust-web-provider /app/rust-web-provider
|
||||
|
||||
ENV RUST_LOG=info
|
||||
EXPOSE 3000
|
||||
|
||||
ENTRYPOINT [ "/app/rust-web-provider" ]
|
||||
75
providers/web/rust-web-provider/scripts/buildDocker.sh
Executable file
75
providers/web/rust-web-provider/scripts/buildDocker.sh
Executable file
@@ -0,0 +1,75 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_NAME=$(basename "$0")
|
||||
DIR=$(cd "$(dirname "$0")" && pwd)
|
||||
ROOT_FOLDER="$DIR/.."
|
||||
PUBLISH_DIR=$ROOT_FOLDER/target/publish
|
||||
TARGET_NAME=rust-web-provider
|
||||
IMAGE_NAME=rust-web-provider
|
||||
IMAGE_VERSION=latest
|
||||
BUILD_RELEASE=true
|
||||
SOURCE_RELEASE_DIR=$ROOT_FOLDER/target/release
|
||||
SOURCE_DEBUG_DIR=$ROOT_FOLDER/target/debug
|
||||
SOURCE_DIR=$SOURCE_RELEASE_DIR
|
||||
|
||||
usage()
|
||||
{
|
||||
echo "$SCRIPT_NAME [options]"
|
||||
echo "Note: You might have to run this as root or sudo."
|
||||
echo ""
|
||||
echo "options"
|
||||
echo " -i, --image-name Image name (default: rust-web-provider)"
|
||||
echo " -v, --image-version Docker Image Version (default: latest)"
|
||||
echo " -r, --build-release Build release configuration - true|false (default: true)"
|
||||
exit 1;
|
||||
}
|
||||
|
||||
process_args()
|
||||
{
|
||||
save_next_arg=0
|
||||
for arg in "$@"
|
||||
do
|
||||
if [ $save_next_arg -eq 1 ]; then
|
||||
IMAGE_NAME="$arg"
|
||||
save_next_arg=0
|
||||
elif [ $save_next_arg -eq 2 ]; then
|
||||
IMAGE_VERSION="$arg"
|
||||
save_next_arg=0
|
||||
elif [ $save_next_arg -eq 3 ]; then
|
||||
BUILD_RELEASE="$arg"
|
||||
save_next_arg=0
|
||||
else
|
||||
case "$arg" in
|
||||
"-h" | "--help" ) usage;;
|
||||
"-i" | "--image-name" ) save_next_arg=1;;
|
||||
"-v" | "--image-version" ) save_next_arg=2;;
|
||||
"-r" | "--build-release" ) save_next_arg=3;;
|
||||
* ) usage;;
|
||||
esac
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# process command line args
|
||||
process_args "$@"
|
||||
|
||||
# build bits
|
||||
if [ "$BUILD_RELEASE" == "true" ]; then
|
||||
cargo build --release
|
||||
else
|
||||
SOURCE_DIR=$SOURCE_DEBUG_DIR
|
||||
cargo build
|
||||
fi
|
||||
|
||||
# copy release binary & Dockerfile to a "publish" folder
|
||||
rm -rf "$PUBLISH_DIR"
|
||||
mkdir -p "$PUBLISH_DIR"
|
||||
cp "$ROOT_FOLDER/Dockerfile" "$PUBLISH_DIR"
|
||||
cp "$SOURCE_DIR/$TARGET_NAME" "$PUBLISH_DIR"
|
||||
|
||||
# build the Docker image
|
||||
pushd "$PUBLISH_DIR"
|
||||
docker build -t "$IMAGE_NAME":"$IMAGE_VERSION" .
|
||||
popd
|
||||
20
providers/web/rust-web-provider/src/main.rs
Normal file
20
providers/web/rust-web-provider/src/main.rs
Normal file
@@ -0,0 +1,20 @@
|
||||
extern crate env_logger;
|
||||
extern crate kube_rust;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
extern crate time;
|
||||
extern crate virtual_kubelet_adapter;
|
||||
|
||||
mod utils;
|
||||
mod unit_provider;
|
||||
|
||||
use virtual_kubelet_adapter::start_server;
|
||||
use unit_provider::UnitProvider;
|
||||
|
||||
fn main() {
|
||||
// initialize logger
|
||||
env_logger::init().unwrap();
|
||||
|
||||
let provider = Box::new(UnitProvider::new());
|
||||
start_server(provider).unwrap();
|
||||
}
|
||||
156
providers/web/rust-web-provider/src/unit_provider.rs
Normal file
156
providers/web/rust-web-provider/src/unit_provider.rs
Normal file
@@ -0,0 +1,156 @@
|
||||
use kube_rust::models::{V1NodeAddress, V1NodeCondition, V1NodeDaemonEndpoints, V1Pod, V1PodStatus};
|
||||
use virtual_kubelet_adapter::{Error, Provider, Result};
|
||||
use std::collections::BTreeMap;
|
||||
use utils::Filter;
|
||||
use time;
|
||||
|
||||
pub struct UnitProvider {
|
||||
pods_map: BTreeMap<String, V1Pod>,
|
||||
}
|
||||
|
||||
impl UnitProvider {
|
||||
pub fn new() -> UnitProvider {
|
||||
UnitProvider {
|
||||
pods_map: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn make_pod_id(&self, pod: &V1Pod) -> String {
|
||||
let empty = String::from("");
|
||||
pod.metadata()
|
||||
.map(|m| m.name().unwrap_or(&empty))
|
||||
.unwrap_or(&empty)
|
||||
.clone()
|
||||
}
|
||||
|
||||
fn pod_name(&mut self, pod: &V1Pod) -> String {
|
||||
let empty = "".to_owned();
|
||||
format!(
|
||||
"{}",
|
||||
pod.metadata()
|
||||
.map(|m| m.name())
|
||||
.and_then(|s| s)
|
||||
.unwrap_or(&empty)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Provider for UnitProvider {
|
||||
fn create_pod(&mut self, pod: &V1Pod) -> Result<()> {
|
||||
info!("Creating pod: {}", self.pod_name(pod));
|
||||
|
||||
let id = self.make_pod_id(pod);
|
||||
let mut new_pod = pod.clone();
|
||||
new_pod.set_status(
|
||||
pod.status()
|
||||
.map(|s| s.clone())
|
||||
.unwrap_or_else(|| V1PodStatus::new())
|
||||
.with_phase(String::from("Running")),
|
||||
);
|
||||
self.pods_map.insert(id, new_pod);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_pod(&mut self, pod: &V1Pod) -> Result<()> {
|
||||
info!("Updating pod: {}", self.pod_name(pod));
|
||||
|
||||
// update the pod definition if it exists
|
||||
let id = self.make_pod_id(pod);
|
||||
if self.pods_map.contains_key(&id) {
|
||||
self.pods_map.insert(id, pod.clone());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn delete_pod(&mut self, pod: &V1Pod) -> Result<()> {
|
||||
info!("Deleting pod: {}", self.pod_name(pod));
|
||||
|
||||
let id = self.make_pod_id(pod);
|
||||
self.pods_map.remove(&id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_pod(&self, namespace: &str, name: &str) -> Result<V1Pod> {
|
||||
info!("Getting pod: {}", name);
|
||||
self.pods_map
|
||||
.get(name)
|
||||
.filter(|pod| {
|
||||
let empty = String::from("");
|
||||
let ns = pod.metadata()
|
||||
.map(|m| m.namespace())
|
||||
.and_then(|n| n)
|
||||
.unwrap_or(&empty);
|
||||
namespace.len() == 0 || namespace == ns
|
||||
})
|
||||
.map(|pod| pod.clone())
|
||||
.ok_or_else(|| {
|
||||
info!("Could not find pod: {}", name);
|
||||
Error::not_found("Pod not found")
|
||||
})
|
||||
}
|
||||
|
||||
fn get_container_logs(
|
||||
&self,
|
||||
namespace: &str,
|
||||
pod_name: &str,
|
||||
container_name: &str,
|
||||
tail: i32,
|
||||
) -> Result<String> {
|
||||
Ok(format!(
|
||||
"get_container_logs() - ns: {}, pod_name: {}, container_name: {}, tail: {}",
|
||||
namespace, pod_name, container_name, tail
|
||||
))
|
||||
}
|
||||
|
||||
fn get_pod_status(&self, _: &str, name: &str) -> Result<V1PodStatus> {
|
||||
info!("Getting pod status: {}", name);
|
||||
self.pods_map
|
||||
.get(name)
|
||||
.map(|pod| pod.status())
|
||||
.and_then(|pod_status| pod_status)
|
||||
.map(|pod_status| pod_status.clone())
|
||||
.ok_or_else(|| {
|
||||
info!("Could not find pod/status: {}", name);
|
||||
Error::not_found("Pod/status not found")
|
||||
})
|
||||
}
|
||||
|
||||
fn get_pods(&self) -> Result<Vec<V1Pod>> {
|
||||
info!("Getting pods");
|
||||
Ok(self.pods_map.values().cloned().collect())
|
||||
}
|
||||
|
||||
fn capacity(&self) -> Result<BTreeMap<String, String>> {
|
||||
info!("Getting capacity");
|
||||
let values = [("cpu", "20"), ("memory", "100Gi"), ("pods", "20")];
|
||||
let mut map = BTreeMap::new();
|
||||
for v in values.iter() {
|
||||
map.insert(v.0.to_string(), v.1.to_string());
|
||||
}
|
||||
|
||||
Ok(map)
|
||||
}
|
||||
|
||||
fn node_conditions(&self) -> Result<Vec<V1NodeCondition>> {
|
||||
info!("Getting node_condition");
|
||||
Ok(vec![
|
||||
V1NodeCondition::new(String::from("True"), String::from("Ready"))
|
||||
.with_reason(String::from("KubeletReady"))
|
||||
.with_message(String::from("Rusty times."))
|
||||
.with_last_heartbeat_time(format!("{}", time::now_utc().rfc3339()))
|
||||
.with_last_transition_time(format!("{}", time::now_utc().rfc3339())),
|
||||
])
|
||||
}
|
||||
|
||||
fn node_addresses(&self) -> Result<Vec<V1NodeAddress>> {
|
||||
Ok(vec![])
|
||||
}
|
||||
|
||||
fn node_daemon_endpoints(&self) -> Result<V1NodeDaemonEndpoints> {
|
||||
Err(Error::new("Not implemented"))
|
||||
}
|
||||
|
||||
fn operating_system(&self) -> String {
|
||||
String::from("linux")
|
||||
}
|
||||
}
|
||||
18
providers/web/rust-web-provider/src/utils.rs
Normal file
18
providers/web/rust-web-provider/src/utils.rs
Normal file
@@ -0,0 +1,18 @@
|
||||
pub trait Filter<T> {
|
||||
fn filter<P: FnOnce(&T) -> bool>(self, predicate: P) -> Self;
|
||||
}
|
||||
|
||||
impl<T> Filter<T> for Option<T> {
|
||||
fn filter<P: FnOnce(&T) -> bool>(self, predicate: P) -> Self {
|
||||
match self {
|
||||
Some(x) => {
|
||||
if predicate(&x) {
|
||||
Some(x)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
5
scripts/createCertAndKey.sh
Normal file
5
scripts/createCertAndKey.sh
Normal file
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
# Generate cert and key for chart
|
||||
openssl req -newkey rsa:4096 -new -nodes -x509 -days 3650 -keyout key.pem -out cert.pem -subj "/C=US/ST=CA/L=virtualkubelet/O=virtualkubelet/OU=virtualkubelet/CN=virtualkubelet"
|
||||
cert=$(base64 cert.pem)
|
||||
key=$(base64 key.pem)
|
||||
@@ -13,4 +13,27 @@ cat <<EOF > ${outputPathCredsfile}
|
||||
"galleryEndpointUrl": "$galleryEndpointUrl",
|
||||
"managementEndpointUrl": "$managementEndpointUrl"
|
||||
}
|
||||
EOF
|
||||
|
||||
# This will build the kubeConfig during the CI
|
||||
cat <<EOF > ${outputPathKubeConfigFile}
|
||||
---
|
||||
apiVersion: v1
|
||||
clusters:
|
||||
- cluster:
|
||||
certificate-authority-data: "$kubeConfigCertificateAuthorityData"
|
||||
server: $kubeConfigServer
|
||||
name: "aci-connector-k8s"
|
||||
contexts:
|
||||
- context:
|
||||
cluster: "aci-connector-k8s"
|
||||
user: "aci-connector-k8s-admin"
|
||||
name: "aci-connector-k8s"
|
||||
current-context: "aci-connector-k8s"
|
||||
kind: Config
|
||||
users:
|
||||
- name: "aci-connector-k8s-admin"
|
||||
user:
|
||||
client-certificate-data: "$kubeConfigClientCertificateData"
|
||||
client-key-data: "$kubeConfigClientKeyData"
|
||||
EOF
|
||||
21
vendor/github.com/Azure/go-ansiterm/LICENSE
generated
vendored
Normal file
21
vendor/github.com/Azure/go-ansiterm/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Microsoft Corporation
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
12
vendor/github.com/Azure/go-ansiterm/README.md
generated
vendored
Normal file
12
vendor/github.com/Azure/go-ansiterm/README.md
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
# go-ansiterm
|
||||
|
||||
This is a cross platform Ansi Terminal Emulation library. It reads a stream of Ansi characters and produces the appropriate function calls. The results of the function calls are platform dependent.
|
||||
|
||||
For example the parser might receive "ESC, [, A" as a stream of three characters. This is the code for Cursor Up (http://www.vt100.net/docs/vt510-rm/CUU). The parser then calls the cursor up function (CUU()) on an event handler. The event handler determines what platform specific work must be done to cause the cursor to move up one position.
|
||||
|
||||
The parser (parser.go) is a partial implementation of this state machine (http://vt100.net/emu/vt500_parser.png). There are also two event handler implementations, one for tests (test_event_handler.go) to validate that the expected events are being produced and called, the other is a Windows implementation (winterm/win_event_handler.go).
|
||||
|
||||
See parser_test.go for examples exercising the state machine and generating appropriate function calls.
|
||||
|
||||
-----
|
||||
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
||||
188
vendor/github.com/Azure/go-ansiterm/constants.go
generated
vendored
Normal file
188
vendor/github.com/Azure/go-ansiterm/constants.go
generated
vendored
Normal file
@@ -0,0 +1,188 @@
|
||||
package ansiterm
|
||||
|
||||
const LogEnv = "DEBUG_TERMINAL"
|
||||
|
||||
// ANSI constants
|
||||
// References:
|
||||
// -- http://www.ecma-international.org/publications/standards/Ecma-048.htm
|
||||
// -- http://man7.org/linux/man-pages/man4/console_codes.4.html
|
||||
// -- http://manpages.ubuntu.com/manpages/intrepid/man4/console_codes.4.html
|
||||
// -- http://en.wikipedia.org/wiki/ANSI_escape_code
|
||||
// -- http://vt100.net/emu/dec_ansi_parser
|
||||
// -- http://vt100.net/emu/vt500_parser.svg
|
||||
// -- http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
|
||||
// -- http://www.inwap.com/pdp10/ansicode.txt
|
||||
const (
|
||||
// ECMA-48 Set Graphics Rendition
|
||||
// Note:
|
||||
// -- Constants leading with an underscore (e.g., _ANSI_xxx) are unsupported or reserved
|
||||
// -- Fonts could possibly be supported via SetCurrentConsoleFontEx
|
||||
// -- Windows does not expose the per-window cursor (i.e., caret) blink times
|
||||
ANSI_SGR_RESET = 0
|
||||
ANSI_SGR_BOLD = 1
|
||||
ANSI_SGR_DIM = 2
|
||||
_ANSI_SGR_ITALIC = 3
|
||||
ANSI_SGR_UNDERLINE = 4
|
||||
_ANSI_SGR_BLINKSLOW = 5
|
||||
_ANSI_SGR_BLINKFAST = 6
|
||||
ANSI_SGR_REVERSE = 7
|
||||
_ANSI_SGR_INVISIBLE = 8
|
||||
_ANSI_SGR_LINETHROUGH = 9
|
||||
_ANSI_SGR_FONT_00 = 10
|
||||
_ANSI_SGR_FONT_01 = 11
|
||||
_ANSI_SGR_FONT_02 = 12
|
||||
_ANSI_SGR_FONT_03 = 13
|
||||
_ANSI_SGR_FONT_04 = 14
|
||||
_ANSI_SGR_FONT_05 = 15
|
||||
_ANSI_SGR_FONT_06 = 16
|
||||
_ANSI_SGR_FONT_07 = 17
|
||||
_ANSI_SGR_FONT_08 = 18
|
||||
_ANSI_SGR_FONT_09 = 19
|
||||
_ANSI_SGR_FONT_10 = 20
|
||||
_ANSI_SGR_DOUBLEUNDERLINE = 21
|
||||
ANSI_SGR_BOLD_DIM_OFF = 22
|
||||
_ANSI_SGR_ITALIC_OFF = 23
|
||||
ANSI_SGR_UNDERLINE_OFF = 24
|
||||
_ANSI_SGR_BLINK_OFF = 25
|
||||
_ANSI_SGR_RESERVED_00 = 26
|
||||
ANSI_SGR_REVERSE_OFF = 27
|
||||
_ANSI_SGR_INVISIBLE_OFF = 28
|
||||
_ANSI_SGR_LINETHROUGH_OFF = 29
|
||||
ANSI_SGR_FOREGROUND_BLACK = 30
|
||||
ANSI_SGR_FOREGROUND_RED = 31
|
||||
ANSI_SGR_FOREGROUND_GREEN = 32
|
||||
ANSI_SGR_FOREGROUND_YELLOW = 33
|
||||
ANSI_SGR_FOREGROUND_BLUE = 34
|
||||
ANSI_SGR_FOREGROUND_MAGENTA = 35
|
||||
ANSI_SGR_FOREGROUND_CYAN = 36
|
||||
ANSI_SGR_FOREGROUND_WHITE = 37
|
||||
_ANSI_SGR_RESERVED_01 = 38
|
||||
ANSI_SGR_FOREGROUND_DEFAULT = 39
|
||||
ANSI_SGR_BACKGROUND_BLACK = 40
|
||||
ANSI_SGR_BACKGROUND_RED = 41
|
||||
ANSI_SGR_BACKGROUND_GREEN = 42
|
||||
ANSI_SGR_BACKGROUND_YELLOW = 43
|
||||
ANSI_SGR_BACKGROUND_BLUE = 44
|
||||
ANSI_SGR_BACKGROUND_MAGENTA = 45
|
||||
ANSI_SGR_BACKGROUND_CYAN = 46
|
||||
ANSI_SGR_BACKGROUND_WHITE = 47
|
||||
_ANSI_SGR_RESERVED_02 = 48
|
||||
ANSI_SGR_BACKGROUND_DEFAULT = 49
|
||||
// 50 - 65: Unsupported
|
||||
|
||||
ANSI_MAX_CMD_LENGTH = 4096
|
||||
|
||||
MAX_INPUT_EVENTS = 128
|
||||
DEFAULT_WIDTH = 80
|
||||
DEFAULT_HEIGHT = 24
|
||||
|
||||
ANSI_BEL = 0x07
|
||||
ANSI_BACKSPACE = 0x08
|
||||
ANSI_TAB = 0x09
|
||||
ANSI_LINE_FEED = 0x0A
|
||||
ANSI_VERTICAL_TAB = 0x0B
|
||||
ANSI_FORM_FEED = 0x0C
|
||||
ANSI_CARRIAGE_RETURN = 0x0D
|
||||
ANSI_ESCAPE_PRIMARY = 0x1B
|
||||
ANSI_ESCAPE_SECONDARY = 0x5B
|
||||
ANSI_OSC_STRING_ENTRY = 0x5D
|
||||
ANSI_COMMAND_FIRST = 0x40
|
||||
ANSI_COMMAND_LAST = 0x7E
|
||||
DCS_ENTRY = 0x90
|
||||
CSI_ENTRY = 0x9B
|
||||
OSC_STRING = 0x9D
|
||||
ANSI_PARAMETER_SEP = ";"
|
||||
ANSI_CMD_G0 = '('
|
||||
ANSI_CMD_G1 = ')'
|
||||
ANSI_CMD_G2 = '*'
|
||||
ANSI_CMD_G3 = '+'
|
||||
ANSI_CMD_DECPNM = '>'
|
||||
ANSI_CMD_DECPAM = '='
|
||||
ANSI_CMD_OSC = ']'
|
||||
ANSI_CMD_STR_TERM = '\\'
|
||||
|
||||
KEY_CONTROL_PARAM_2 = ";2"
|
||||
KEY_CONTROL_PARAM_3 = ";3"
|
||||
KEY_CONTROL_PARAM_4 = ";4"
|
||||
KEY_CONTROL_PARAM_5 = ";5"
|
||||
KEY_CONTROL_PARAM_6 = ";6"
|
||||
KEY_CONTROL_PARAM_7 = ";7"
|
||||
KEY_CONTROL_PARAM_8 = ";8"
|
||||
KEY_ESC_CSI = "\x1B["
|
||||
KEY_ESC_N = "\x1BN"
|
||||
KEY_ESC_O = "\x1BO"
|
||||
|
||||
FILL_CHARACTER = ' '
|
||||
)
|
||||
|
||||
func getByteRange(start byte, end byte) []byte {
|
||||
bytes := make([]byte, 0, 32)
|
||||
for i := start; i <= end; i++ {
|
||||
bytes = append(bytes, byte(i))
|
||||
}
|
||||
|
||||
return bytes
|
||||
}
|
||||
|
||||
var toGroundBytes = getToGroundBytes()
|
||||
var executors = getExecuteBytes()
|
||||
|
||||
// SPACE 20+A0 hex Always and everywhere a blank space
|
||||
// Intermediate 20-2F hex !"#$%&'()*+,-./
|
||||
var intermeds = getByteRange(0x20, 0x2F)
|
||||
|
||||
// Parameters 30-3F hex 0123456789:;<=>?
|
||||
// CSI Parameters 30-39, 3B hex 0123456789;
|
||||
var csiParams = getByteRange(0x30, 0x3F)
|
||||
|
||||
var csiCollectables = append(getByteRange(0x30, 0x39), getByteRange(0x3B, 0x3F)...)
|
||||
|
||||
// Uppercase 40-5F hex @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_
|
||||
var upperCase = getByteRange(0x40, 0x5F)
|
||||
|
||||
// Lowercase 60-7E hex `abcdefghijlkmnopqrstuvwxyz{|}~
|
||||
var lowerCase = getByteRange(0x60, 0x7E)
|
||||
|
||||
// Alphabetics 40-7E hex (all of upper and lower case)
|
||||
var alphabetics = append(upperCase, lowerCase...)
|
||||
|
||||
var printables = getByteRange(0x20, 0x7F)
|
||||
|
||||
var escapeIntermediateToGroundBytes = getByteRange(0x30, 0x7E)
|
||||
var escapeToGroundBytes = getEscapeToGroundBytes()
|
||||
|
||||
// See http://www.vt100.net/emu/vt500_parser.png for description of the complex
|
||||
// byte ranges below
|
||||
|
||||
func getEscapeToGroundBytes() []byte {
|
||||
escapeToGroundBytes := getByteRange(0x30, 0x4F)
|
||||
escapeToGroundBytes = append(escapeToGroundBytes, getByteRange(0x51, 0x57)...)
|
||||
escapeToGroundBytes = append(escapeToGroundBytes, 0x59)
|
||||
escapeToGroundBytes = append(escapeToGroundBytes, 0x5A)
|
||||
escapeToGroundBytes = append(escapeToGroundBytes, 0x5C)
|
||||
escapeToGroundBytes = append(escapeToGroundBytes, getByteRange(0x60, 0x7E)...)
|
||||
return escapeToGroundBytes
|
||||
}
|
||||
|
||||
func getExecuteBytes() []byte {
|
||||
executeBytes := getByteRange(0x00, 0x17)
|
||||
executeBytes = append(executeBytes, 0x19)
|
||||
executeBytes = append(executeBytes, getByteRange(0x1C, 0x1F)...)
|
||||
return executeBytes
|
||||
}
|
||||
|
||||
func getToGroundBytes() []byte {
|
||||
groundBytes := []byte{0x18}
|
||||
groundBytes = append(groundBytes, 0x1A)
|
||||
groundBytes = append(groundBytes, getByteRange(0x80, 0x8F)...)
|
||||
groundBytes = append(groundBytes, getByteRange(0x91, 0x97)...)
|
||||
groundBytes = append(groundBytes, 0x99)
|
||||
groundBytes = append(groundBytes, 0x9A)
|
||||
groundBytes = append(groundBytes, 0x9C)
|
||||
return groundBytes
|
||||
}
|
||||
|
||||
// Delete 7F hex Always and everywhere ignored
|
||||
// C1 Control 80-9F hex 32 additional control characters
|
||||
// G1 Displayable A1-FE hex 94 additional displayable characters
|
||||
// Special A0+FF hex Same as SPACE and DELETE
|
||||
7
vendor/github.com/Azure/go-ansiterm/context.go
generated
vendored
Normal file
7
vendor/github.com/Azure/go-ansiterm/context.go
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
package ansiterm
|
||||
|
||||
type ansiContext struct {
|
||||
currentChar byte
|
||||
paramBuffer []byte
|
||||
interBuffer []byte
|
||||
}
|
||||
49
vendor/github.com/Azure/go-ansiterm/csi_entry_state.go
generated
vendored
Normal file
49
vendor/github.com/Azure/go-ansiterm/csi_entry_state.go
generated
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
package ansiterm
|
||||
|
||||
type csiEntryState struct {
|
||||
baseState
|
||||
}
|
||||
|
||||
func (csiState csiEntryState) Handle(b byte) (s state, e error) {
|
||||
csiState.parser.logf("CsiEntry::Handle %#x", b)
|
||||
|
||||
nextState, err := csiState.baseState.Handle(b)
|
||||
if nextState != nil || err != nil {
|
||||
return nextState, err
|
||||
}
|
||||
|
||||
switch {
|
||||
case sliceContains(alphabetics, b):
|
||||
return csiState.parser.ground, nil
|
||||
case sliceContains(csiCollectables, b):
|
||||
return csiState.parser.csiParam, nil
|
||||
case sliceContains(executors, b):
|
||||
return csiState, csiState.parser.execute()
|
||||
}
|
||||
|
||||
return csiState, nil
|
||||
}
|
||||
|
||||
func (csiState csiEntryState) Transition(s state) error {
|
||||
csiState.parser.logf("CsiEntry::Transition %s --> %s", csiState.Name(), s.Name())
|
||||
csiState.baseState.Transition(s)
|
||||
|
||||
switch s {
|
||||
case csiState.parser.ground:
|
||||
return csiState.parser.csiDispatch()
|
||||
case csiState.parser.csiParam:
|
||||
switch {
|
||||
case sliceContains(csiParams, csiState.parser.context.currentChar):
|
||||
csiState.parser.collectParam()
|
||||
case sliceContains(intermeds, csiState.parser.context.currentChar):
|
||||
csiState.parser.collectInter()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (csiState csiEntryState) Enter() error {
|
||||
csiState.parser.clear()
|
||||
return nil
|
||||
}
|
||||
38
vendor/github.com/Azure/go-ansiterm/csi_param_state.go
generated
vendored
Normal file
38
vendor/github.com/Azure/go-ansiterm/csi_param_state.go
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
package ansiterm
|
||||
|
||||
type csiParamState struct {
|
||||
baseState
|
||||
}
|
||||
|
||||
func (csiState csiParamState) Handle(b byte) (s state, e error) {
|
||||
csiState.parser.logf("CsiParam::Handle %#x", b)
|
||||
|
||||
nextState, err := csiState.baseState.Handle(b)
|
||||
if nextState != nil || err != nil {
|
||||
return nextState, err
|
||||
}
|
||||
|
||||
switch {
|
||||
case sliceContains(alphabetics, b):
|
||||
return csiState.parser.ground, nil
|
||||
case sliceContains(csiCollectables, b):
|
||||
csiState.parser.collectParam()
|
||||
return csiState, nil
|
||||
case sliceContains(executors, b):
|
||||
return csiState, csiState.parser.execute()
|
||||
}
|
||||
|
||||
return csiState, nil
|
||||
}
|
||||
|
||||
func (csiState csiParamState) Transition(s state) error {
|
||||
csiState.parser.logf("CsiParam::Transition %s --> %s", csiState.Name(), s.Name())
|
||||
csiState.baseState.Transition(s)
|
||||
|
||||
switch s {
|
||||
case csiState.parser.ground:
|
||||
return csiState.parser.csiDispatch()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
36
vendor/github.com/Azure/go-ansiterm/escape_intermediate_state.go
generated
vendored
Normal file
36
vendor/github.com/Azure/go-ansiterm/escape_intermediate_state.go
generated
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
package ansiterm
|
||||
|
||||
type escapeIntermediateState struct {
|
||||
baseState
|
||||
}
|
||||
|
||||
func (escState escapeIntermediateState) Handle(b byte) (s state, e error) {
|
||||
escState.parser.logf("escapeIntermediateState::Handle %#x", b)
|
||||
nextState, err := escState.baseState.Handle(b)
|
||||
if nextState != nil || err != nil {
|
||||
return nextState, err
|
||||
}
|
||||
|
||||
switch {
|
||||
case sliceContains(intermeds, b):
|
||||
return escState, escState.parser.collectInter()
|
||||
case sliceContains(executors, b):
|
||||
return escState, escState.parser.execute()
|
||||
case sliceContains(escapeIntermediateToGroundBytes, b):
|
||||
return escState.parser.ground, nil
|
||||
}
|
||||
|
||||
return escState, nil
|
||||
}
|
||||
|
||||
func (escState escapeIntermediateState) Transition(s state) error {
|
||||
escState.parser.logf("escapeIntermediateState::Transition %s --> %s", escState.Name(), s.Name())
|
||||
escState.baseState.Transition(s)
|
||||
|
||||
switch s {
|
||||
case escState.parser.ground:
|
||||
return escState.parser.escDispatch()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
47
vendor/github.com/Azure/go-ansiterm/escape_state.go
generated
vendored
Normal file
47
vendor/github.com/Azure/go-ansiterm/escape_state.go
generated
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
package ansiterm
|
||||
|
||||
type escapeState struct {
|
||||
baseState
|
||||
}
|
||||
|
||||
func (escState escapeState) Handle(b byte) (s state, e error) {
|
||||
escState.parser.logf("escapeState::Handle %#x", b)
|
||||
nextState, err := escState.baseState.Handle(b)
|
||||
if nextState != nil || err != nil {
|
||||
return nextState, err
|
||||
}
|
||||
|
||||
switch {
|
||||
case b == ANSI_ESCAPE_SECONDARY:
|
||||
return escState.parser.csiEntry, nil
|
||||
case b == ANSI_OSC_STRING_ENTRY:
|
||||
return escState.parser.oscString, nil
|
||||
case sliceContains(executors, b):
|
||||
return escState, escState.parser.execute()
|
||||
case sliceContains(escapeToGroundBytes, b):
|
||||
return escState.parser.ground, nil
|
||||
case sliceContains(intermeds, b):
|
||||
return escState.parser.escapeIntermediate, nil
|
||||
}
|
||||
|
||||
return escState, nil
|
||||
}
|
||||
|
||||
func (escState escapeState) Transition(s state) error {
|
||||
escState.parser.logf("Escape::Transition %s --> %s", escState.Name(), s.Name())
|
||||
escState.baseState.Transition(s)
|
||||
|
||||
switch s {
|
||||
case escState.parser.ground:
|
||||
return escState.parser.escDispatch()
|
||||
case escState.parser.escapeIntermediate:
|
||||
return escState.parser.collectInter()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (escState escapeState) Enter() error {
|
||||
escState.parser.clear()
|
||||
return nil
|
||||
}
|
||||
90
vendor/github.com/Azure/go-ansiterm/event_handler.go
generated
vendored
Normal file
90
vendor/github.com/Azure/go-ansiterm/event_handler.go
generated
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
package ansiterm
|
||||
|
||||
type AnsiEventHandler interface {
|
||||
// Print
|
||||
Print(b byte) error
|
||||
|
||||
// Execute C0 commands
|
||||
Execute(b byte) error
|
||||
|
||||
// CUrsor Up
|
||||
CUU(int) error
|
||||
|
||||
// CUrsor Down
|
||||
CUD(int) error
|
||||
|
||||
// CUrsor Forward
|
||||
CUF(int) error
|
||||
|
||||
// CUrsor Backward
|
||||
CUB(int) error
|
||||
|
||||
// Cursor to Next Line
|
||||
CNL(int) error
|
||||
|
||||
// Cursor to Previous Line
|
||||
CPL(int) error
|
||||
|
||||
// Cursor Horizontal position Absolute
|
||||
CHA(int) error
|
||||
|
||||
// Vertical line Position Absolute
|
||||
VPA(int) error
|
||||
|
||||
// CUrsor Position
|
||||
CUP(int, int) error
|
||||
|
||||
// Horizontal and Vertical Position (depends on PUM)
|
||||
HVP(int, int) error
|
||||
|
||||
// Text Cursor Enable Mode
|
||||
DECTCEM(bool) error
|
||||
|
||||
// Origin Mode
|
||||
DECOM(bool) error
|
||||
|
||||
// 132 Column Mode
|
||||
DECCOLM(bool) error
|
||||
|
||||
// Erase in Display
|
||||
ED(int) error
|
||||
|
||||
// Erase in Line
|
||||
EL(int) error
|
||||
|
||||
// Insert Line
|
||||
IL(int) error
|
||||
|
||||
// Delete Line
|
||||
DL(int) error
|
||||
|
||||
// Insert Character
|
||||
ICH(int) error
|
||||
|
||||
// Delete Character
|
||||
DCH(int) error
|
||||
|
||||
// Set Graphics Rendition
|
||||
SGR([]int) error
|
||||
|
||||
// Pan Down
|
||||
SU(int) error
|
||||
|
||||
// Pan Up
|
||||
SD(int) error
|
||||
|
||||
// Device Attributes
|
||||
DA([]string) error
|
||||
|
||||
// Set Top and Bottom Margins
|
||||
DECSTBM(int, int) error
|
||||
|
||||
// Index
|
||||
IND() error
|
||||
|
||||
// Reverse Index
|
||||
RI() error
|
||||
|
||||
// Flush updates from previous commands
|
||||
Flush() error
|
||||
}
|
||||
24
vendor/github.com/Azure/go-ansiterm/ground_state.go
generated
vendored
Normal file
24
vendor/github.com/Azure/go-ansiterm/ground_state.go
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
package ansiterm
|
||||
|
||||
type groundState struct {
|
||||
baseState
|
||||
}
|
||||
|
||||
func (gs groundState) Handle(b byte) (s state, e error) {
|
||||
gs.parser.context.currentChar = b
|
||||
|
||||
nextState, err := gs.baseState.Handle(b)
|
||||
if nextState != nil || err != nil {
|
||||
return nextState, err
|
||||
}
|
||||
|
||||
switch {
|
||||
case sliceContains(printables, b):
|
||||
return gs, gs.parser.print()
|
||||
|
||||
case sliceContains(executors, b):
|
||||
return gs, gs.parser.execute()
|
||||
}
|
||||
|
||||
return gs, nil
|
||||
}
|
||||
31
vendor/github.com/Azure/go-ansiterm/osc_string_state.go
generated
vendored
Normal file
31
vendor/github.com/Azure/go-ansiterm/osc_string_state.go
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
package ansiterm
|
||||
|
||||
type oscStringState struct {
|
||||
baseState
|
||||
}
|
||||
|
||||
func (oscState oscStringState) Handle(b byte) (s state, e error) {
|
||||
oscState.parser.logf("OscString::Handle %#x", b)
|
||||
nextState, err := oscState.baseState.Handle(b)
|
||||
if nextState != nil || err != nil {
|
||||
return nextState, err
|
||||
}
|
||||
|
||||
switch {
|
||||
case isOscStringTerminator(b):
|
||||
return oscState.parser.ground, nil
|
||||
}
|
||||
|
||||
return oscState, nil
|
||||
}
|
||||
|
||||
// See below for OSC string terminators for linux
|
||||
// http://man7.org/linux/man-pages/man4/console_codes.4.html
|
||||
func isOscStringTerminator(b byte) bool {
|
||||
|
||||
if b == ANSI_BEL || b == 0x5C {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
151
vendor/github.com/Azure/go-ansiterm/parser.go
generated
vendored
Normal file
151
vendor/github.com/Azure/go-ansiterm/parser.go
generated
vendored
Normal file
@@ -0,0 +1,151 @@
|
||||
package ansiterm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
type AnsiParser struct {
|
||||
currState state
|
||||
eventHandler AnsiEventHandler
|
||||
context *ansiContext
|
||||
csiEntry state
|
||||
csiParam state
|
||||
dcsEntry state
|
||||
escape state
|
||||
escapeIntermediate state
|
||||
error state
|
||||
ground state
|
||||
oscString state
|
||||
stateMap []state
|
||||
|
||||
logf func(string, ...interface{})
|
||||
}
|
||||
|
||||
type Option func(*AnsiParser)
|
||||
|
||||
func WithLogf(f func(string, ...interface{})) Option {
|
||||
return func(ap *AnsiParser) {
|
||||
ap.logf = f
|
||||
}
|
||||
}
|
||||
|
||||
func CreateParser(initialState string, evtHandler AnsiEventHandler, opts ...Option) *AnsiParser {
|
||||
ap := &AnsiParser{
|
||||
eventHandler: evtHandler,
|
||||
context: &ansiContext{},
|
||||
}
|
||||
for _, o := range opts {
|
||||
o(ap)
|
||||
}
|
||||
|
||||
if isDebugEnv := os.Getenv(LogEnv); isDebugEnv == "1" {
|
||||
logFile, _ := os.Create("ansiParser.log")
|
||||
logger := log.New(logFile, "", log.LstdFlags)
|
||||
if ap.logf != nil {
|
||||
l := ap.logf
|
||||
ap.logf = func(s string, v ...interface{}) {
|
||||
l(s, v...)
|
||||
logger.Printf(s, v...)
|
||||
}
|
||||
} else {
|
||||
ap.logf = logger.Printf
|
||||
}
|
||||
}
|
||||
|
||||
if ap.logf == nil {
|
||||
ap.logf = func(string, ...interface{}) {}
|
||||
}
|
||||
|
||||
ap.csiEntry = csiEntryState{baseState{name: "CsiEntry", parser: ap}}
|
||||
ap.csiParam = csiParamState{baseState{name: "CsiParam", parser: ap}}
|
||||
ap.dcsEntry = dcsEntryState{baseState{name: "DcsEntry", parser: ap}}
|
||||
ap.escape = escapeState{baseState{name: "Escape", parser: ap}}
|
||||
ap.escapeIntermediate = escapeIntermediateState{baseState{name: "EscapeIntermediate", parser: ap}}
|
||||
ap.error = errorState{baseState{name: "Error", parser: ap}}
|
||||
ap.ground = groundState{baseState{name: "Ground", parser: ap}}
|
||||
ap.oscString = oscStringState{baseState{name: "OscString", parser: ap}}
|
||||
|
||||
ap.stateMap = []state{
|
||||
ap.csiEntry,
|
||||
ap.csiParam,
|
||||
ap.dcsEntry,
|
||||
ap.escape,
|
||||
ap.escapeIntermediate,
|
||||
ap.error,
|
||||
ap.ground,
|
||||
ap.oscString,
|
||||
}
|
||||
|
||||
ap.currState = getState(initialState, ap.stateMap)
|
||||
|
||||
ap.logf("CreateParser: parser %p", ap)
|
||||
return ap
|
||||
}
|
||||
|
||||
func getState(name string, states []state) state {
|
||||
for _, el := range states {
|
||||
if el.Name() == name {
|
||||
return el
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ap *AnsiParser) Parse(bytes []byte) (int, error) {
|
||||
for i, b := range bytes {
|
||||
if err := ap.handle(b); err != nil {
|
||||
return i, err
|
||||
}
|
||||
}
|
||||
|
||||
return len(bytes), ap.eventHandler.Flush()
|
||||
}
|
||||
|
||||
func (ap *AnsiParser) handle(b byte) error {
|
||||
ap.context.currentChar = b
|
||||
newState, err := ap.currState.Handle(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if newState == nil {
|
||||
ap.logf("WARNING: newState is nil")
|
||||
return errors.New("New state of 'nil' is invalid.")
|
||||
}
|
||||
|
||||
if newState != ap.currState {
|
||||
if err := ap.changeState(newState); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ap *AnsiParser) changeState(newState state) error {
|
||||
ap.logf("ChangeState %s --> %s", ap.currState.Name(), newState.Name())
|
||||
|
||||
// Exit old state
|
||||
if err := ap.currState.Exit(); err != nil {
|
||||
ap.logf("Exit state '%s' failed with : '%v'", ap.currState.Name(), err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Perform transition action
|
||||
if err := ap.currState.Transition(newState); err != nil {
|
||||
ap.logf("Transition from '%s' to '%s' failed with: '%v'", ap.currState.Name(), newState.Name, err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Enter new state
|
||||
if err := newState.Enter(); err != nil {
|
||||
ap.logf("Enter state '%s' failed with: '%v'", newState.Name(), err)
|
||||
return err
|
||||
}
|
||||
|
||||
ap.currState = newState
|
||||
return nil
|
||||
}
|
||||
99
vendor/github.com/Azure/go-ansiterm/parser_action_helpers.go
generated
vendored
Normal file
99
vendor/github.com/Azure/go-ansiterm/parser_action_helpers.go
generated
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
package ansiterm
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func parseParams(bytes []byte) ([]string, error) {
|
||||
paramBuff := make([]byte, 0, 0)
|
||||
params := []string{}
|
||||
|
||||
for _, v := range bytes {
|
||||
if v == ';' {
|
||||
if len(paramBuff) > 0 {
|
||||
// Completed parameter, append it to the list
|
||||
s := string(paramBuff)
|
||||
params = append(params, s)
|
||||
paramBuff = make([]byte, 0, 0)
|
||||
}
|
||||
} else {
|
||||
paramBuff = append(paramBuff, v)
|
||||
}
|
||||
}
|
||||
|
||||
// Last parameter may not be terminated with ';'
|
||||
if len(paramBuff) > 0 {
|
||||
s := string(paramBuff)
|
||||
params = append(params, s)
|
||||
}
|
||||
|
||||
return params, nil
|
||||
}
|
||||
|
||||
func parseCmd(context ansiContext) (string, error) {
|
||||
return string(context.currentChar), nil
|
||||
}
|
||||
|
||||
func getInt(params []string, dflt int) int {
|
||||
i := getInts(params, 1, dflt)[0]
|
||||
return i
|
||||
}
|
||||
|
||||
func getInts(params []string, minCount int, dflt int) []int {
|
||||
ints := []int{}
|
||||
|
||||
for _, v := range params {
|
||||
i, _ := strconv.Atoi(v)
|
||||
// Zero is mapped to the default value in VT100.
|
||||
if i == 0 {
|
||||
i = dflt
|
||||
}
|
||||
ints = append(ints, i)
|
||||
}
|
||||
|
||||
if len(ints) < minCount {
|
||||
remaining := minCount - len(ints)
|
||||
for i := 0; i < remaining; i++ {
|
||||
ints = append(ints, dflt)
|
||||
}
|
||||
}
|
||||
|
||||
return ints
|
||||
}
|
||||
|
||||
func (ap *AnsiParser) modeDispatch(param string, set bool) error {
|
||||
switch param {
|
||||
case "?3":
|
||||
return ap.eventHandler.DECCOLM(set)
|
||||
case "?6":
|
||||
return ap.eventHandler.DECOM(set)
|
||||
case "?25":
|
||||
return ap.eventHandler.DECTCEM(set)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ap *AnsiParser) hDispatch(params []string) error {
|
||||
if len(params) == 1 {
|
||||
return ap.modeDispatch(params[0], true)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ap *AnsiParser) lDispatch(params []string) error {
|
||||
if len(params) == 1 {
|
||||
return ap.modeDispatch(params[0], false)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getEraseParam(params []string) int {
|
||||
param := getInt(params, 0)
|
||||
if param < 0 || 3 < param {
|
||||
param = 0
|
||||
}
|
||||
|
||||
return param
|
||||
}
|
||||
119
vendor/github.com/Azure/go-ansiterm/parser_actions.go
generated
vendored
Normal file
119
vendor/github.com/Azure/go-ansiterm/parser_actions.go
generated
vendored
Normal file
@@ -0,0 +1,119 @@
|
||||
package ansiterm
|
||||
|
||||
func (ap *AnsiParser) collectParam() error {
|
||||
currChar := ap.context.currentChar
|
||||
ap.logf("collectParam %#x", currChar)
|
||||
ap.context.paramBuffer = append(ap.context.paramBuffer, currChar)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ap *AnsiParser) collectInter() error {
|
||||
currChar := ap.context.currentChar
|
||||
ap.logf("collectInter %#x", currChar)
|
||||
ap.context.paramBuffer = append(ap.context.interBuffer, currChar)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ap *AnsiParser) escDispatch() error {
|
||||
cmd, _ := parseCmd(*ap.context)
|
||||
intermeds := ap.context.interBuffer
|
||||
ap.logf("escDispatch currentChar: %#x", ap.context.currentChar)
|
||||
ap.logf("escDispatch: %v(%v)", cmd, intermeds)
|
||||
|
||||
switch cmd {
|
||||
case "D": // IND
|
||||
return ap.eventHandler.IND()
|
||||
case "E": // NEL, equivalent to CRLF
|
||||
err := ap.eventHandler.Execute(ANSI_CARRIAGE_RETURN)
|
||||
if err == nil {
|
||||
err = ap.eventHandler.Execute(ANSI_LINE_FEED)
|
||||
}
|
||||
return err
|
||||
case "M": // RI
|
||||
return ap.eventHandler.RI()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ap *AnsiParser) csiDispatch() error {
|
||||
cmd, _ := parseCmd(*ap.context)
|
||||
params, _ := parseParams(ap.context.paramBuffer)
|
||||
ap.logf("Parsed params: %v with length: %d", params, len(params))
|
||||
|
||||
ap.logf("csiDispatch: %v(%v)", cmd, params)
|
||||
|
||||
switch cmd {
|
||||
case "@":
|
||||
return ap.eventHandler.ICH(getInt(params, 1))
|
||||
case "A":
|
||||
return ap.eventHandler.CUU(getInt(params, 1))
|
||||
case "B":
|
||||
return ap.eventHandler.CUD(getInt(params, 1))
|
||||
case "C":
|
||||
return ap.eventHandler.CUF(getInt(params, 1))
|
||||
case "D":
|
||||
return ap.eventHandler.CUB(getInt(params, 1))
|
||||
case "E":
|
||||
return ap.eventHandler.CNL(getInt(params, 1))
|
||||
case "F":
|
||||
return ap.eventHandler.CPL(getInt(params, 1))
|
||||
case "G":
|
||||
return ap.eventHandler.CHA(getInt(params, 1))
|
||||
case "H":
|
||||
ints := getInts(params, 2, 1)
|
||||
x, y := ints[0], ints[1]
|
||||
return ap.eventHandler.CUP(x, y)
|
||||
case "J":
|
||||
param := getEraseParam(params)
|
||||
return ap.eventHandler.ED(param)
|
||||
case "K":
|
||||
param := getEraseParam(params)
|
||||
return ap.eventHandler.EL(param)
|
||||
case "L":
|
||||
return ap.eventHandler.IL(getInt(params, 1))
|
||||
case "M":
|
||||
return ap.eventHandler.DL(getInt(params, 1))
|
||||
case "P":
|
||||
return ap.eventHandler.DCH(getInt(params, 1))
|
||||
case "S":
|
||||
return ap.eventHandler.SU(getInt(params, 1))
|
||||
case "T":
|
||||
return ap.eventHandler.SD(getInt(params, 1))
|
||||
case "c":
|
||||
return ap.eventHandler.DA(params)
|
||||
case "d":
|
||||
return ap.eventHandler.VPA(getInt(params, 1))
|
||||
case "f":
|
||||
ints := getInts(params, 2, 1)
|
||||
x, y := ints[0], ints[1]
|
||||
return ap.eventHandler.HVP(x, y)
|
||||
case "h":
|
||||
return ap.hDispatch(params)
|
||||
case "l":
|
||||
return ap.lDispatch(params)
|
||||
case "m":
|
||||
return ap.eventHandler.SGR(getInts(params, 1, 0))
|
||||
case "r":
|
||||
ints := getInts(params, 2, 1)
|
||||
top, bottom := ints[0], ints[1]
|
||||
return ap.eventHandler.DECSTBM(top, bottom)
|
||||
default:
|
||||
ap.logf("ERROR: Unsupported CSI command: '%s', with full context: %v", cmd, ap.context)
|
||||
return nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (ap *AnsiParser) print() error {
|
||||
return ap.eventHandler.Print(ap.context.currentChar)
|
||||
}
|
||||
|
||||
func (ap *AnsiParser) clear() error {
|
||||
ap.context = &ansiContext{}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ap *AnsiParser) execute() error {
|
||||
return ap.eventHandler.Execute(ap.context.currentChar)
|
||||
}
|
||||
141
vendor/github.com/Azure/go-ansiterm/parser_test.go
generated
vendored
Normal file
141
vendor/github.com/Azure/go-ansiterm/parser_test.go
generated
vendored
Normal file
@@ -0,0 +1,141 @@
|
||||
package ansiterm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestStateTransitions(t *testing.T) {
|
||||
stateTransitionHelper(t, "CsiEntry", "Ground", alphabetics)
|
||||
stateTransitionHelper(t, "CsiEntry", "CsiParam", csiCollectables)
|
||||
stateTransitionHelper(t, "Escape", "CsiEntry", []byte{ANSI_ESCAPE_SECONDARY})
|
||||
stateTransitionHelper(t, "Escape", "OscString", []byte{0x5D})
|
||||
stateTransitionHelper(t, "Escape", "Ground", escapeToGroundBytes)
|
||||
stateTransitionHelper(t, "Escape", "EscapeIntermediate", intermeds)
|
||||
stateTransitionHelper(t, "EscapeIntermediate", "EscapeIntermediate", intermeds)
|
||||
stateTransitionHelper(t, "EscapeIntermediate", "EscapeIntermediate", executors)
|
||||
stateTransitionHelper(t, "EscapeIntermediate", "Ground", escapeIntermediateToGroundBytes)
|
||||
stateTransitionHelper(t, "OscString", "Ground", []byte{ANSI_BEL})
|
||||
stateTransitionHelper(t, "OscString", "Ground", []byte{0x5C})
|
||||
stateTransitionHelper(t, "Ground", "Ground", executors)
|
||||
}
|
||||
|
||||
func TestAnyToX(t *testing.T) {
|
||||
anyToXHelper(t, []byte{ANSI_ESCAPE_PRIMARY}, "Escape")
|
||||
anyToXHelper(t, []byte{DCS_ENTRY}, "DcsEntry")
|
||||
anyToXHelper(t, []byte{OSC_STRING}, "OscString")
|
||||
anyToXHelper(t, []byte{CSI_ENTRY}, "CsiEntry")
|
||||
anyToXHelper(t, toGroundBytes, "Ground")
|
||||
}
|
||||
|
||||
func TestCollectCsiParams(t *testing.T) {
|
||||
parser, _ := createTestParser("CsiEntry")
|
||||
parser.Parse(csiCollectables)
|
||||
|
||||
buffer := parser.context.paramBuffer
|
||||
bufferCount := len(buffer)
|
||||
|
||||
if bufferCount != len(csiCollectables) {
|
||||
t.Errorf("Buffer: %v", buffer)
|
||||
t.Errorf("CsiParams: %v", csiCollectables)
|
||||
t.Errorf("Buffer count failure: %d != %d", bufferCount, len(csiParams))
|
||||
return
|
||||
}
|
||||
|
||||
for i, v := range csiCollectables {
|
||||
if v != buffer[i] {
|
||||
t.Errorf("Buffer: %v", buffer)
|
||||
t.Errorf("CsiParams: %v", csiParams)
|
||||
t.Errorf("Mismatch at buffer[%d] = %d", i, buffer[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseParams(t *testing.T) {
|
||||
parseParamsHelper(t, []byte{}, []string{})
|
||||
parseParamsHelper(t, []byte{';'}, []string{})
|
||||
parseParamsHelper(t, []byte{';', ';'}, []string{})
|
||||
parseParamsHelper(t, []byte{'7'}, []string{"7"})
|
||||
parseParamsHelper(t, []byte{'7', ';'}, []string{"7"})
|
||||
parseParamsHelper(t, []byte{'7', ';', ';'}, []string{"7"})
|
||||
parseParamsHelper(t, []byte{'7', ';', ';', '8'}, []string{"7", "8"})
|
||||
parseParamsHelper(t, []byte{'7', ';', '8', ';'}, []string{"7", "8"})
|
||||
parseParamsHelper(t, []byte{'7', ';', ';', '8', ';', ';'}, []string{"7", "8"})
|
||||
parseParamsHelper(t, []byte{'7', '8'}, []string{"78"})
|
||||
parseParamsHelper(t, []byte{'7', '8', ';'}, []string{"78"})
|
||||
parseParamsHelper(t, []byte{'7', '8', ';', '9', '0'}, []string{"78", "90"})
|
||||
parseParamsHelper(t, []byte{'7', '8', ';', ';', '9', '0'}, []string{"78", "90"})
|
||||
parseParamsHelper(t, []byte{'7', '8', ';', '9', '0', ';'}, []string{"78", "90"})
|
||||
parseParamsHelper(t, []byte{'7', '8', ';', '9', '0', ';', ';'}, []string{"78", "90"})
|
||||
}
|
||||
|
||||
func TestCursor(t *testing.T) {
|
||||
cursorSingleParamHelper(t, 'A', "CUU")
|
||||
cursorSingleParamHelper(t, 'B', "CUD")
|
||||
cursorSingleParamHelper(t, 'C', "CUF")
|
||||
cursorSingleParamHelper(t, 'D', "CUB")
|
||||
cursorSingleParamHelper(t, 'E', "CNL")
|
||||
cursorSingleParamHelper(t, 'F', "CPL")
|
||||
cursorSingleParamHelper(t, 'G', "CHA")
|
||||
cursorTwoParamHelper(t, 'H', "CUP")
|
||||
cursorTwoParamHelper(t, 'f', "HVP")
|
||||
funcCallParamHelper(t, []byte{'?', '2', '5', 'h'}, "CsiEntry", "Ground", []string{"DECTCEM([true])"})
|
||||
funcCallParamHelper(t, []byte{'?', '2', '5', 'l'}, "CsiEntry", "Ground", []string{"DECTCEM([false])"})
|
||||
}
|
||||
|
||||
func TestErase(t *testing.T) {
|
||||
// Erase in Display
|
||||
eraseHelper(t, 'J', "ED")
|
||||
|
||||
// Erase in Line
|
||||
eraseHelper(t, 'K', "EL")
|
||||
}
|
||||
|
||||
func TestSelectGraphicRendition(t *testing.T) {
|
||||
funcCallParamHelper(t, []byte{'m'}, "CsiEntry", "Ground", []string{"SGR([0])"})
|
||||
funcCallParamHelper(t, []byte{'0', 'm'}, "CsiEntry", "Ground", []string{"SGR([0])"})
|
||||
funcCallParamHelper(t, []byte{'0', ';', '1', 'm'}, "CsiEntry", "Ground", []string{"SGR([0 1])"})
|
||||
funcCallParamHelper(t, []byte{'0', ';', '1', ';', '2', 'm'}, "CsiEntry", "Ground", []string{"SGR([0 1 2])"})
|
||||
}
|
||||
|
||||
func TestScroll(t *testing.T) {
|
||||
scrollHelper(t, 'S', "SU")
|
||||
scrollHelper(t, 'T', "SD")
|
||||
}
|
||||
|
||||
func TestPrint(t *testing.T) {
|
||||
parser, evtHandler := createTestParser("Ground")
|
||||
parser.Parse(printables)
|
||||
validateState(t, parser.currState, "Ground")
|
||||
|
||||
for i, v := range printables {
|
||||
expectedCall := fmt.Sprintf("Print([%s])", string(v))
|
||||
actualCall := evtHandler.FunctionCalls[i]
|
||||
if actualCall != expectedCall {
|
||||
t.Errorf("Actual != Expected: %v != %v at %d", actualCall, expectedCall, i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClear(t *testing.T) {
|
||||
p, _ := createTestParser("Ground")
|
||||
fillContext(p.context)
|
||||
p.clear()
|
||||
validateEmptyContext(t, p.context)
|
||||
}
|
||||
|
||||
func TestClearOnStateChange(t *testing.T) {
|
||||
clearOnStateChangeHelper(t, "Ground", "Escape", []byte{ANSI_ESCAPE_PRIMARY})
|
||||
clearOnStateChangeHelper(t, "Ground", "CsiEntry", []byte{CSI_ENTRY})
|
||||
}
|
||||
|
||||
func TestC0(t *testing.T) {
|
||||
expectedCall := "Execute([" + string(ANSI_LINE_FEED) + "])"
|
||||
c0Helper(t, []byte{ANSI_LINE_FEED}, "Ground", []string{expectedCall})
|
||||
expectedCall = "Execute([" + string(ANSI_CARRIAGE_RETURN) + "])"
|
||||
c0Helper(t, []byte{ANSI_CARRIAGE_RETURN}, "Ground", []string{expectedCall})
|
||||
}
|
||||
|
||||
func TestEscDispatch(t *testing.T) {
|
||||
funcCallParamHelper(t, []byte{'M'}, "Escape", "Ground", []string{"RI([])"})
|
||||
}
|
||||
114
vendor/github.com/Azure/go-ansiterm/parser_test_helpers_test.go
generated
vendored
Normal file
114
vendor/github.com/Azure/go-ansiterm/parser_test_helpers_test.go
generated
vendored
Normal file
@@ -0,0 +1,114 @@
|
||||
package ansiterm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func getStateNames() []string {
|
||||
parser, _ := createTestParser("Ground")
|
||||
|
||||
stateNames := []string{}
|
||||
for _, state := range parser.stateMap {
|
||||
stateNames = append(stateNames, state.Name())
|
||||
}
|
||||
|
||||
return stateNames
|
||||
}
|
||||
|
||||
func stateTransitionHelper(t *testing.T, start string, end string, bytes []byte) {
|
||||
for _, b := range bytes {
|
||||
bytes := []byte{byte(b)}
|
||||
parser, _ := createTestParser(start)
|
||||
parser.Parse(bytes)
|
||||
validateState(t, parser.currState, end)
|
||||
}
|
||||
}
|
||||
|
||||
func anyToXHelper(t *testing.T, bytes []byte, expectedState string) {
|
||||
for _, s := range getStateNames() {
|
||||
stateTransitionHelper(t, s, expectedState, bytes)
|
||||
}
|
||||
}
|
||||
|
||||
func funcCallParamHelper(t *testing.T, bytes []byte, start string, expected string, expectedCalls []string) {
|
||||
parser, evtHandler := createTestParser(start)
|
||||
parser.Parse(bytes)
|
||||
validateState(t, parser.currState, expected)
|
||||
validateFuncCalls(t, evtHandler.FunctionCalls, expectedCalls)
|
||||
}
|
||||
|
||||
func parseParamsHelper(t *testing.T, bytes []byte, expectedParams []string) {
|
||||
params, err := parseParams(bytes)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Parameter parse error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(params) != len(expectedParams) {
|
||||
t.Errorf("Parsed parameters: %v", params)
|
||||
t.Errorf("Expected parameters: %v", expectedParams)
|
||||
t.Errorf("Parameter length failure: %d != %d", len(params), len(expectedParams))
|
||||
return
|
||||
}
|
||||
|
||||
for i, v := range expectedParams {
|
||||
if v != params[i] {
|
||||
t.Errorf("Parsed parameters: %v", params)
|
||||
t.Errorf("Expected parameters: %v", expectedParams)
|
||||
t.Errorf("Parameter parse failure: %s != %s at position %d", v, params[i], i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func cursorSingleParamHelper(t *testing.T, command byte, funcName string) {
|
||||
funcCallParamHelper(t, []byte{command}, "CsiEntry", "Ground", []string{fmt.Sprintf("%s([1])", funcName)})
|
||||
funcCallParamHelper(t, []byte{'0', command}, "CsiEntry", "Ground", []string{fmt.Sprintf("%s([1])", funcName)})
|
||||
funcCallParamHelper(t, []byte{'2', command}, "CsiEntry", "Ground", []string{fmt.Sprintf("%s([2])", funcName)})
|
||||
funcCallParamHelper(t, []byte{'2', '3', command}, "CsiEntry", "Ground", []string{fmt.Sprintf("%s([23])", funcName)})
|
||||
funcCallParamHelper(t, []byte{'2', ';', '3', command}, "CsiEntry", "Ground", []string{fmt.Sprintf("%s([2])", funcName)})
|
||||
funcCallParamHelper(t, []byte{'2', ';', '3', ';', '4', command}, "CsiEntry", "Ground", []string{fmt.Sprintf("%s([2])", funcName)})
|
||||
}
|
||||
|
||||
func cursorTwoParamHelper(t *testing.T, command byte, funcName string) {
|
||||
funcCallParamHelper(t, []byte{command}, "CsiEntry", "Ground", []string{fmt.Sprintf("%s([1 1])", funcName)})
|
||||
funcCallParamHelper(t, []byte{'0', command}, "CsiEntry", "Ground", []string{fmt.Sprintf("%s([1 1])", funcName)})
|
||||
funcCallParamHelper(t, []byte{'2', command}, "CsiEntry", "Ground", []string{fmt.Sprintf("%s([2 1])", funcName)})
|
||||
funcCallParamHelper(t, []byte{'2', '3', command}, "CsiEntry", "Ground", []string{fmt.Sprintf("%s([23 1])", funcName)})
|
||||
funcCallParamHelper(t, []byte{'2', ';', '3', command}, "CsiEntry", "Ground", []string{fmt.Sprintf("%s([2 3])", funcName)})
|
||||
funcCallParamHelper(t, []byte{'2', ';', '3', ';', '4', command}, "CsiEntry", "Ground", []string{fmt.Sprintf("%s([2 3])", funcName)})
|
||||
}
|
||||
|
||||
func eraseHelper(t *testing.T, command byte, funcName string) {
|
||||
funcCallParamHelper(t, []byte{command}, "CsiEntry", "Ground", []string{fmt.Sprintf("%s([0])", funcName)})
|
||||
funcCallParamHelper(t, []byte{'0', command}, "CsiEntry", "Ground", []string{fmt.Sprintf("%s([0])", funcName)})
|
||||
funcCallParamHelper(t, []byte{'1', command}, "CsiEntry", "Ground", []string{fmt.Sprintf("%s([1])", funcName)})
|
||||
funcCallParamHelper(t, []byte{'2', command}, "CsiEntry", "Ground", []string{fmt.Sprintf("%s([2])", funcName)})
|
||||
funcCallParamHelper(t, []byte{'3', command}, "CsiEntry", "Ground", []string{fmt.Sprintf("%s([3])", funcName)})
|
||||
funcCallParamHelper(t, []byte{'4', command}, "CsiEntry", "Ground", []string{fmt.Sprintf("%s([0])", funcName)})
|
||||
funcCallParamHelper(t, []byte{'1', ';', '2', command}, "CsiEntry", "Ground", []string{fmt.Sprintf("%s([1])", funcName)})
|
||||
}
|
||||
|
||||
func scrollHelper(t *testing.T, command byte, funcName string) {
|
||||
funcCallParamHelper(t, []byte{command}, "CsiEntry", "Ground", []string{fmt.Sprintf("%s([1])", funcName)})
|
||||
funcCallParamHelper(t, []byte{'0', command}, "CsiEntry", "Ground", []string{fmt.Sprintf("%s([1])", funcName)})
|
||||
funcCallParamHelper(t, []byte{'1', command}, "CsiEntry", "Ground", []string{fmt.Sprintf("%s([1])", funcName)})
|
||||
funcCallParamHelper(t, []byte{'5', command}, "CsiEntry", "Ground", []string{fmt.Sprintf("%s([5])", funcName)})
|
||||
funcCallParamHelper(t, []byte{'4', ';', '6', command}, "CsiEntry", "Ground", []string{fmt.Sprintf("%s([4])", funcName)})
|
||||
}
|
||||
|
||||
func clearOnStateChangeHelper(t *testing.T, start string, end string, bytes []byte) {
|
||||
p, _ := createTestParser(start)
|
||||
fillContext(p.context)
|
||||
p.Parse(bytes)
|
||||
validateState(t, p.currState, end)
|
||||
validateEmptyContext(t, p.context)
|
||||
}
|
||||
|
||||
func c0Helper(t *testing.T, bytes []byte, expectedState string, expectedCalls []string) {
|
||||
parser, evtHandler := createTestParser("Ground")
|
||||
parser.Parse(bytes)
|
||||
validateState(t, parser.currState, expectedState)
|
||||
validateFuncCalls(t, evtHandler.FunctionCalls, expectedCalls)
|
||||
}
|
||||
66
vendor/github.com/Azure/go-ansiterm/parser_test_utilities_test.go
generated
vendored
Normal file
66
vendor/github.com/Azure/go-ansiterm/parser_test_utilities_test.go
generated
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
package ansiterm
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func createTestParser(s string) (*AnsiParser, *TestAnsiEventHandler) {
|
||||
evtHandler := CreateTestAnsiEventHandler()
|
||||
parser := CreateParser(s, evtHandler)
|
||||
|
||||
return parser, evtHandler
|
||||
}
|
||||
|
||||
func validateState(t *testing.T, actualState state, expectedStateName string) {
|
||||
actualName := "Nil"
|
||||
|
||||
if actualState != nil {
|
||||
actualName = actualState.Name()
|
||||
}
|
||||
|
||||
if actualName != expectedStateName {
|
||||
t.Errorf("Invalid state: '%s' != '%s'", actualName, expectedStateName)
|
||||
}
|
||||
}
|
||||
|
||||
func validateFuncCalls(t *testing.T, actualCalls []string, expectedCalls []string) {
|
||||
actualCount := len(actualCalls)
|
||||
expectedCount := len(expectedCalls)
|
||||
|
||||
if actualCount != expectedCount {
|
||||
t.Errorf("Actual calls: %v", actualCalls)
|
||||
t.Errorf("Expected calls: %v", expectedCalls)
|
||||
t.Errorf("Call count error: %d != %d", actualCount, expectedCount)
|
||||
return
|
||||
}
|
||||
|
||||
for i, v := range actualCalls {
|
||||
if v != expectedCalls[i] {
|
||||
t.Errorf("Actual calls: %v", actualCalls)
|
||||
t.Errorf("Expected calls: %v", expectedCalls)
|
||||
t.Errorf("Mismatched calls: %s != %s with lengths %d and %d", v, expectedCalls[i], len(v), len(expectedCalls[i]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func fillContext(context *ansiContext) {
|
||||
context.currentChar = 'A'
|
||||
context.paramBuffer = []byte{'C', 'D', 'E'}
|
||||
context.interBuffer = []byte{'F', 'G', 'H'}
|
||||
}
|
||||
|
||||
func validateEmptyContext(t *testing.T, context *ansiContext) {
|
||||
var expectedCurrChar byte = 0x0
|
||||
if context.currentChar != expectedCurrChar {
|
||||
t.Errorf("Currentchar mismatch '%#x' != '%#x'", context.currentChar, expectedCurrChar)
|
||||
}
|
||||
|
||||
if len(context.paramBuffer) != 0 {
|
||||
t.Errorf("Non-empty parameter buffer: %v", context.paramBuffer)
|
||||
}
|
||||
|
||||
if len(context.paramBuffer) != 0 {
|
||||
t.Errorf("Non-empty intermediate buffer: %v", context.interBuffer)
|
||||
}
|
||||
|
||||
}
|
||||
71
vendor/github.com/Azure/go-ansiterm/states.go
generated
vendored
Normal file
71
vendor/github.com/Azure/go-ansiterm/states.go
generated
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
package ansiterm
|
||||
|
||||
type stateID int
|
||||
|
||||
type state interface {
|
||||
Enter() error
|
||||
Exit() error
|
||||
Handle(byte) (state, error)
|
||||
Name() string
|
||||
Transition(state) error
|
||||
}
|
||||
|
||||
type baseState struct {
|
||||
name string
|
||||
parser *AnsiParser
|
||||
}
|
||||
|
||||
func (base baseState) Enter() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (base baseState) Exit() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (base baseState) Handle(b byte) (s state, e error) {
|
||||
|
||||
switch {
|
||||
case b == CSI_ENTRY:
|
||||
return base.parser.csiEntry, nil
|
||||
case b == DCS_ENTRY:
|
||||
return base.parser.dcsEntry, nil
|
||||
case b == ANSI_ESCAPE_PRIMARY:
|
||||
return base.parser.escape, nil
|
||||
case b == OSC_STRING:
|
||||
return base.parser.oscString, nil
|
||||
case sliceContains(toGroundBytes, b):
|
||||
return base.parser.ground, nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (base baseState) Name() string {
|
||||
return base.name
|
||||
}
|
||||
|
||||
func (base baseState) Transition(s state) error {
|
||||
if s == base.parser.ground {
|
||||
execBytes := []byte{0x18}
|
||||
execBytes = append(execBytes, 0x1A)
|
||||
execBytes = append(execBytes, getByteRange(0x80, 0x8F)...)
|
||||
execBytes = append(execBytes, getByteRange(0x91, 0x97)...)
|
||||
execBytes = append(execBytes, 0x99)
|
||||
execBytes = append(execBytes, 0x9A)
|
||||
|
||||
if sliceContains(execBytes, base.parser.context.currentChar) {
|
||||
return base.parser.execute()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type dcsEntryState struct {
|
||||
baseState
|
||||
}
|
||||
|
||||
type errorState struct {
|
||||
baseState
|
||||
}
|
||||
173
vendor/github.com/Azure/go-ansiterm/test_event_handler_test.go
generated
vendored
Normal file
173
vendor/github.com/Azure/go-ansiterm/test_event_handler_test.go
generated
vendored
Normal file
@@ -0,0 +1,173 @@
|
||||
package ansiterm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type TestAnsiEventHandler struct {
|
||||
FunctionCalls []string
|
||||
}
|
||||
|
||||
func CreateTestAnsiEventHandler() *TestAnsiEventHandler {
|
||||
evtHandler := TestAnsiEventHandler{}
|
||||
evtHandler.FunctionCalls = make([]string, 0)
|
||||
return &evtHandler
|
||||
}
|
||||
|
||||
func (h *TestAnsiEventHandler) recordCall(call string, params []string) {
|
||||
s := fmt.Sprintf("%s(%v)", call, params)
|
||||
h.FunctionCalls = append(h.FunctionCalls, s)
|
||||
}
|
||||
|
||||
func (h *TestAnsiEventHandler) Print(b byte) error {
|
||||
h.recordCall("Print", []string{string(b)})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *TestAnsiEventHandler) Execute(b byte) error {
|
||||
h.recordCall("Execute", []string{string(b)})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *TestAnsiEventHandler) CUU(param int) error {
|
||||
h.recordCall("CUU", []string{strconv.Itoa(param)})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *TestAnsiEventHandler) CUD(param int) error {
|
||||
h.recordCall("CUD", []string{strconv.Itoa(param)})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *TestAnsiEventHandler) CUF(param int) error {
|
||||
h.recordCall("CUF", []string{strconv.Itoa(param)})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *TestAnsiEventHandler) CUB(param int) error {
|
||||
h.recordCall("CUB", []string{strconv.Itoa(param)})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *TestAnsiEventHandler) CNL(param int) error {
|
||||
h.recordCall("CNL", []string{strconv.Itoa(param)})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *TestAnsiEventHandler) CPL(param int) error {
|
||||
h.recordCall("CPL", []string{strconv.Itoa(param)})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *TestAnsiEventHandler) CHA(param int) error {
|
||||
h.recordCall("CHA", []string{strconv.Itoa(param)})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *TestAnsiEventHandler) VPA(param int) error {
|
||||
h.recordCall("VPA", []string{strconv.Itoa(param)})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *TestAnsiEventHandler) CUP(x int, y int) error {
|
||||
xS, yS := strconv.Itoa(x), strconv.Itoa(y)
|
||||
h.recordCall("CUP", []string{xS, yS})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *TestAnsiEventHandler) HVP(x int, y int) error {
|
||||
xS, yS := strconv.Itoa(x), strconv.Itoa(y)
|
||||
h.recordCall("HVP", []string{xS, yS})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *TestAnsiEventHandler) DECTCEM(visible bool) error {
|
||||
h.recordCall("DECTCEM", []string{strconv.FormatBool(visible)})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *TestAnsiEventHandler) DECOM(visible bool) error {
|
||||
h.recordCall("DECOM", []string{strconv.FormatBool(visible)})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *TestAnsiEventHandler) DECCOLM(use132 bool) error {
|
||||
h.recordCall("DECOLM", []string{strconv.FormatBool(use132)})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *TestAnsiEventHandler) ED(param int) error {
|
||||
h.recordCall("ED", []string{strconv.Itoa(param)})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *TestAnsiEventHandler) EL(param int) error {
|
||||
h.recordCall("EL", []string{strconv.Itoa(param)})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *TestAnsiEventHandler) IL(param int) error {
|
||||
h.recordCall("IL", []string{strconv.Itoa(param)})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *TestAnsiEventHandler) DL(param int) error {
|
||||
h.recordCall("DL", []string{strconv.Itoa(param)})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *TestAnsiEventHandler) ICH(param int) error {
|
||||
h.recordCall("ICH", []string{strconv.Itoa(param)})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *TestAnsiEventHandler) DCH(param int) error {
|
||||
h.recordCall("DCH", []string{strconv.Itoa(param)})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *TestAnsiEventHandler) SGR(params []int) error {
|
||||
strings := []string{}
|
||||
for _, v := range params {
|
||||
strings = append(strings, strconv.Itoa(v))
|
||||
}
|
||||
|
||||
h.recordCall("SGR", strings)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *TestAnsiEventHandler) SU(param int) error {
|
||||
h.recordCall("SU", []string{strconv.Itoa(param)})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *TestAnsiEventHandler) SD(param int) error {
|
||||
h.recordCall("SD", []string{strconv.Itoa(param)})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *TestAnsiEventHandler) DA(params []string) error {
|
||||
h.recordCall("DA", params)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *TestAnsiEventHandler) DECSTBM(top int, bottom int) error {
|
||||
topS, bottomS := strconv.Itoa(top), strconv.Itoa(bottom)
|
||||
h.recordCall("DECSTBM", []string{topS, bottomS})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *TestAnsiEventHandler) RI() error {
|
||||
h.recordCall("RI", nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *TestAnsiEventHandler) IND() error {
|
||||
h.recordCall("IND", nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *TestAnsiEventHandler) Flush() error {
|
||||
return nil
|
||||
}
|
||||
21
vendor/github.com/Azure/go-ansiterm/utilities.go
generated
vendored
Normal file
21
vendor/github.com/Azure/go-ansiterm/utilities.go
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
package ansiterm
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func sliceContains(bytes []byte, b byte) bool {
|
||||
for _, v := range bytes {
|
||||
if v == b {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func convertBytesToInteger(bytes []byte) int {
|
||||
s := string(bytes)
|
||||
i, _ := strconv.Atoi(s)
|
||||
return i
|
||||
}
|
||||
182
vendor/github.com/Azure/go-ansiterm/winterm/ansi.go
generated
vendored
Normal file
182
vendor/github.com/Azure/go-ansiterm/winterm/ansi.go
generated
vendored
Normal file
@@ -0,0 +1,182 @@
|
||||
// +build windows
|
||||
|
||||
package winterm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/Azure/go-ansiterm"
|
||||
)
|
||||
|
||||
// Windows keyboard constants
|
||||
// See https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx.
|
||||
const (
|
||||
VK_PRIOR = 0x21 // PAGE UP key
|
||||
VK_NEXT = 0x22 // PAGE DOWN key
|
||||
VK_END = 0x23 // END key
|
||||
VK_HOME = 0x24 // HOME key
|
||||
VK_LEFT = 0x25 // LEFT ARROW key
|
||||
VK_UP = 0x26 // UP ARROW key
|
||||
VK_RIGHT = 0x27 // RIGHT ARROW key
|
||||
VK_DOWN = 0x28 // DOWN ARROW key
|
||||
VK_SELECT = 0x29 // SELECT key
|
||||
VK_PRINT = 0x2A // PRINT key
|
||||
VK_EXECUTE = 0x2B // EXECUTE key
|
||||
VK_SNAPSHOT = 0x2C // PRINT SCREEN key
|
||||
VK_INSERT = 0x2D // INS key
|
||||
VK_DELETE = 0x2E // DEL key
|
||||
VK_HELP = 0x2F // HELP key
|
||||
VK_F1 = 0x70 // F1 key
|
||||
VK_F2 = 0x71 // F2 key
|
||||
VK_F3 = 0x72 // F3 key
|
||||
VK_F4 = 0x73 // F4 key
|
||||
VK_F5 = 0x74 // F5 key
|
||||
VK_F6 = 0x75 // F6 key
|
||||
VK_F7 = 0x76 // F7 key
|
||||
VK_F8 = 0x77 // F8 key
|
||||
VK_F9 = 0x78 // F9 key
|
||||
VK_F10 = 0x79 // F10 key
|
||||
VK_F11 = 0x7A // F11 key
|
||||
VK_F12 = 0x7B // F12 key
|
||||
|
||||
RIGHT_ALT_PRESSED = 0x0001
|
||||
LEFT_ALT_PRESSED = 0x0002
|
||||
RIGHT_CTRL_PRESSED = 0x0004
|
||||
LEFT_CTRL_PRESSED = 0x0008
|
||||
SHIFT_PRESSED = 0x0010
|
||||
NUMLOCK_ON = 0x0020
|
||||
SCROLLLOCK_ON = 0x0040
|
||||
CAPSLOCK_ON = 0x0080
|
||||
ENHANCED_KEY = 0x0100
|
||||
)
|
||||
|
||||
type ansiCommand struct {
|
||||
CommandBytes []byte
|
||||
Command string
|
||||
Parameters []string
|
||||
IsSpecial bool
|
||||
}
|
||||
|
||||
func newAnsiCommand(command []byte) *ansiCommand {
|
||||
|
||||
if isCharacterSelectionCmdChar(command[1]) {
|
||||
// Is Character Set Selection commands
|
||||
return &ansiCommand{
|
||||
CommandBytes: command,
|
||||
Command: string(command),
|
||||
IsSpecial: true,
|
||||
}
|
||||
}
|
||||
|
||||
// last char is command character
|
||||
lastCharIndex := len(command) - 1
|
||||
|
||||
ac := &ansiCommand{
|
||||
CommandBytes: command,
|
||||
Command: string(command[lastCharIndex]),
|
||||
IsSpecial: false,
|
||||
}
|
||||
|
||||
// more than a single escape
|
||||
if lastCharIndex != 0 {
|
||||
start := 1
|
||||
// skip if double char escape sequence
|
||||
if command[0] == ansiterm.ANSI_ESCAPE_PRIMARY && command[1] == ansiterm.ANSI_ESCAPE_SECONDARY {
|
||||
start++
|
||||
}
|
||||
// convert this to GetNextParam method
|
||||
ac.Parameters = strings.Split(string(command[start:lastCharIndex]), ansiterm.ANSI_PARAMETER_SEP)
|
||||
}
|
||||
|
||||
return ac
|
||||
}
|
||||
|
||||
func (ac *ansiCommand) paramAsSHORT(index int, defaultValue int16) int16 {
|
||||
if index < 0 || index >= len(ac.Parameters) {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
param, err := strconv.ParseInt(ac.Parameters[index], 10, 16)
|
||||
if err != nil {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
return int16(param)
|
||||
}
|
||||
|
||||
func (ac *ansiCommand) String() string {
|
||||
return fmt.Sprintf("0x%v \"%v\" (\"%v\")",
|
||||
bytesToHex(ac.CommandBytes),
|
||||
ac.Command,
|
||||
strings.Join(ac.Parameters, "\",\""))
|
||||
}
|
||||
|
||||
// isAnsiCommandChar returns true if the passed byte falls within the range of ANSI commands.
|
||||
// See http://manpages.ubuntu.com/manpages/intrepid/man4/console_codes.4.html.
|
||||
func isAnsiCommandChar(b byte) bool {
|
||||
switch {
|
||||
case ansiterm.ANSI_COMMAND_FIRST <= b && b <= ansiterm.ANSI_COMMAND_LAST && b != ansiterm.ANSI_ESCAPE_SECONDARY:
|
||||
return true
|
||||
case b == ansiterm.ANSI_CMD_G1 || b == ansiterm.ANSI_CMD_OSC || b == ansiterm.ANSI_CMD_DECPAM || b == ansiterm.ANSI_CMD_DECPNM:
|
||||
// non-CSI escape sequence terminator
|
||||
return true
|
||||
case b == ansiterm.ANSI_CMD_STR_TERM || b == ansiterm.ANSI_BEL:
|
||||
// String escape sequence terminator
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isXtermOscSequence(command []byte, current byte) bool {
|
||||
return (len(command) >= 2 && command[0] == ansiterm.ANSI_ESCAPE_PRIMARY && command[1] == ansiterm.ANSI_CMD_OSC && current != ansiterm.ANSI_BEL)
|
||||
}
|
||||
|
||||
func isCharacterSelectionCmdChar(b byte) bool {
|
||||
return (b == ansiterm.ANSI_CMD_G0 || b == ansiterm.ANSI_CMD_G1 || b == ansiterm.ANSI_CMD_G2 || b == ansiterm.ANSI_CMD_G3)
|
||||
}
|
||||
|
||||
// bytesToHex converts a slice of bytes to a human-readable string.
|
||||
func bytesToHex(b []byte) string {
|
||||
hex := make([]string, len(b))
|
||||
for i, ch := range b {
|
||||
hex[i] = fmt.Sprintf("%X", ch)
|
||||
}
|
||||
return strings.Join(hex, "")
|
||||
}
|
||||
|
||||
// ensureInRange adjusts the passed value, if necessary, to ensure it is within
|
||||
// the passed min / max range.
|
||||
func ensureInRange(n int16, min int16, max int16) int16 {
|
||||
if n < min {
|
||||
return min
|
||||
} else if n > max {
|
||||
return max
|
||||
} else {
|
||||
return n
|
||||
}
|
||||
}
|
||||
|
||||
func GetStdFile(nFile int) (*os.File, uintptr) {
|
||||
var file *os.File
|
||||
switch nFile {
|
||||
case syscall.STD_INPUT_HANDLE:
|
||||
file = os.Stdin
|
||||
case syscall.STD_OUTPUT_HANDLE:
|
||||
file = os.Stdout
|
||||
case syscall.STD_ERROR_HANDLE:
|
||||
file = os.Stderr
|
||||
default:
|
||||
panic(fmt.Errorf("Invalid standard handle identifier: %v", nFile))
|
||||
}
|
||||
|
||||
fd, err := syscall.GetStdHandle(nFile)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("Invalid standard handle identifier: %v -- %v", nFile, err))
|
||||
}
|
||||
|
||||
return file, uintptr(fd)
|
||||
}
|
||||
327
vendor/github.com/Azure/go-ansiterm/winterm/api.go
generated
vendored
Normal file
327
vendor/github.com/Azure/go-ansiterm/winterm/api.go
generated
vendored
Normal file
@@ -0,0 +1,327 @@
|
||||
// +build windows
|
||||
|
||||
package winterm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
//===========================================================================================================
|
||||
// IMPORTANT NOTE:
|
||||
//
|
||||
// The methods below make extensive use of the "unsafe" package to obtain the required pointers.
|
||||
// Beginning in Go 1.3, the garbage collector may release local variables (e.g., incoming arguments, stack
|
||||
// variables) the pointers reference *before* the API completes.
|
||||
//
|
||||
// As a result, in those cases, the code must hint that the variables remain in active by invoking the
|
||||
// dummy method "use" (see below). Newer versions of Go are planned to change the mechanism to no longer
|
||||
// require unsafe pointers.
|
||||
//
|
||||
// If you add or modify methods, ENSURE protection of local variables through the "use" builtin to inform
|
||||
// the garbage collector the variables remain in use if:
|
||||
//
|
||||
// -- The value is not a pointer (e.g., int32, struct)
|
||||
// -- The value is not referenced by the method after passing the pointer to Windows
|
||||
//
|
||||
// See http://golang.org/doc/go1.3.
|
||||
//===========================================================================================================
|
||||
|
||||
var (
|
||||
kernel32DLL = syscall.NewLazyDLL("kernel32.dll")
|
||||
|
||||
getConsoleCursorInfoProc = kernel32DLL.NewProc("GetConsoleCursorInfo")
|
||||
setConsoleCursorInfoProc = kernel32DLL.NewProc("SetConsoleCursorInfo")
|
||||
setConsoleCursorPositionProc = kernel32DLL.NewProc("SetConsoleCursorPosition")
|
||||
setConsoleModeProc = kernel32DLL.NewProc("SetConsoleMode")
|
||||
getConsoleScreenBufferInfoProc = kernel32DLL.NewProc("GetConsoleScreenBufferInfo")
|
||||
setConsoleScreenBufferSizeProc = kernel32DLL.NewProc("SetConsoleScreenBufferSize")
|
||||
scrollConsoleScreenBufferProc = kernel32DLL.NewProc("ScrollConsoleScreenBufferA")
|
||||
setConsoleTextAttributeProc = kernel32DLL.NewProc("SetConsoleTextAttribute")
|
||||
setConsoleWindowInfoProc = kernel32DLL.NewProc("SetConsoleWindowInfo")
|
||||
writeConsoleOutputProc = kernel32DLL.NewProc("WriteConsoleOutputW")
|
||||
readConsoleInputProc = kernel32DLL.NewProc("ReadConsoleInputW")
|
||||
waitForSingleObjectProc = kernel32DLL.NewProc("WaitForSingleObject")
|
||||
)
|
||||
|
||||
// Windows Console constants
|
||||
const (
|
||||
// Console modes
|
||||
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx.
|
||||
ENABLE_PROCESSED_INPUT = 0x0001
|
||||
ENABLE_LINE_INPUT = 0x0002
|
||||
ENABLE_ECHO_INPUT = 0x0004
|
||||
ENABLE_WINDOW_INPUT = 0x0008
|
||||
ENABLE_MOUSE_INPUT = 0x0010
|
||||
ENABLE_INSERT_MODE = 0x0020
|
||||
ENABLE_QUICK_EDIT_MODE = 0x0040
|
||||
ENABLE_EXTENDED_FLAGS = 0x0080
|
||||
ENABLE_AUTO_POSITION = 0x0100
|
||||
ENABLE_VIRTUAL_TERMINAL_INPUT = 0x0200
|
||||
|
||||
ENABLE_PROCESSED_OUTPUT = 0x0001
|
||||
ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002
|
||||
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
|
||||
DISABLE_NEWLINE_AUTO_RETURN = 0x0008
|
||||
ENABLE_LVB_GRID_WORLDWIDE = 0x0010
|
||||
|
||||
// Character attributes
|
||||
// Note:
|
||||
// -- The attributes are combined to produce various colors (e.g., Blue + Green will create Cyan).
|
||||
// Clearing all foreground or background colors results in black; setting all creates white.
|
||||
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms682088(v=vs.85).aspx#_win32_character_attributes.
|
||||
FOREGROUND_BLUE uint16 = 0x0001
|
||||
FOREGROUND_GREEN uint16 = 0x0002
|
||||
FOREGROUND_RED uint16 = 0x0004
|
||||
FOREGROUND_INTENSITY uint16 = 0x0008
|
||||
FOREGROUND_MASK uint16 = 0x000F
|
||||
|
||||
BACKGROUND_BLUE uint16 = 0x0010
|
||||
BACKGROUND_GREEN uint16 = 0x0020
|
||||
BACKGROUND_RED uint16 = 0x0040
|
||||
BACKGROUND_INTENSITY uint16 = 0x0080
|
||||
BACKGROUND_MASK uint16 = 0x00F0
|
||||
|
||||
COMMON_LVB_MASK uint16 = 0xFF00
|
||||
COMMON_LVB_REVERSE_VIDEO uint16 = 0x4000
|
||||
COMMON_LVB_UNDERSCORE uint16 = 0x8000
|
||||
|
||||
// Input event types
|
||||
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683499(v=vs.85).aspx.
|
||||
KEY_EVENT = 0x0001
|
||||
MOUSE_EVENT = 0x0002
|
||||
WINDOW_BUFFER_SIZE_EVENT = 0x0004
|
||||
MENU_EVENT = 0x0008
|
||||
FOCUS_EVENT = 0x0010
|
||||
|
||||
// WaitForSingleObject return codes
|
||||
WAIT_ABANDONED = 0x00000080
|
||||
WAIT_FAILED = 0xFFFFFFFF
|
||||
WAIT_SIGNALED = 0x0000000
|
||||
WAIT_TIMEOUT = 0x00000102
|
||||
|
||||
// WaitForSingleObject wait duration
|
||||
WAIT_INFINITE = 0xFFFFFFFF
|
||||
WAIT_ONE_SECOND = 1000
|
||||
WAIT_HALF_SECOND = 500
|
||||
WAIT_QUARTER_SECOND = 250
|
||||
)
|
||||
|
||||
// Windows API Console types
|
||||
// -- See https://msdn.microsoft.com/en-us/library/windows/desktop/ms682101(v=vs.85).aspx for Console specific types (e.g., COORD)
|
||||
// -- See https://msdn.microsoft.com/en-us/library/aa296569(v=vs.60).aspx for comments on alignment
|
||||
type (
|
||||
CHAR_INFO struct {
|
||||
UnicodeChar uint16
|
||||
Attributes uint16
|
||||
}
|
||||
|
||||
CONSOLE_CURSOR_INFO struct {
|
||||
Size uint32
|
||||
Visible int32
|
||||
}
|
||||
|
||||
CONSOLE_SCREEN_BUFFER_INFO struct {
|
||||
Size COORD
|
||||
CursorPosition COORD
|
||||
Attributes uint16
|
||||
Window SMALL_RECT
|
||||
MaximumWindowSize COORD
|
||||
}
|
||||
|
||||
COORD struct {
|
||||
X int16
|
||||
Y int16
|
||||
}
|
||||
|
||||
SMALL_RECT struct {
|
||||
Left int16
|
||||
Top int16
|
||||
Right int16
|
||||
Bottom int16
|
||||
}
|
||||
|
||||
// INPUT_RECORD is a C/C++ union of which KEY_EVENT_RECORD is one case, it is also the largest
|
||||
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683499(v=vs.85).aspx.
|
||||
INPUT_RECORD struct {
|
||||
EventType uint16
|
||||
KeyEvent KEY_EVENT_RECORD
|
||||
}
|
||||
|
||||
KEY_EVENT_RECORD struct {
|
||||
KeyDown int32
|
||||
RepeatCount uint16
|
||||
VirtualKeyCode uint16
|
||||
VirtualScanCode uint16
|
||||
UnicodeChar uint16
|
||||
ControlKeyState uint32
|
||||
}
|
||||
|
||||
WINDOW_BUFFER_SIZE struct {
|
||||
Size COORD
|
||||
}
|
||||
)
|
||||
|
||||
// boolToBOOL converts a Go bool into a Windows int32.
|
||||
func boolToBOOL(f bool) int32 {
|
||||
if f {
|
||||
return int32(1)
|
||||
} else {
|
||||
return int32(0)
|
||||
}
|
||||
}
|
||||
|
||||
// GetConsoleCursorInfo retrieves information about the size and visiblity of the console cursor.
|
||||
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683163(v=vs.85).aspx.
|
||||
func GetConsoleCursorInfo(handle uintptr, cursorInfo *CONSOLE_CURSOR_INFO) error {
|
||||
r1, r2, err := getConsoleCursorInfoProc.Call(handle, uintptr(unsafe.Pointer(cursorInfo)), 0)
|
||||
return checkError(r1, r2, err)
|
||||
}
|
||||
|
||||
// SetConsoleCursorInfo sets the size and visiblity of the console cursor.
|
||||
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686019(v=vs.85).aspx.
|
||||
func SetConsoleCursorInfo(handle uintptr, cursorInfo *CONSOLE_CURSOR_INFO) error {
|
||||
r1, r2, err := setConsoleCursorInfoProc.Call(handle, uintptr(unsafe.Pointer(cursorInfo)), 0)
|
||||
return checkError(r1, r2, err)
|
||||
}
|
||||
|
||||
// SetConsoleCursorPosition location of the console cursor.
|
||||
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686025(v=vs.85).aspx.
|
||||
func SetConsoleCursorPosition(handle uintptr, coord COORD) error {
|
||||
r1, r2, err := setConsoleCursorPositionProc.Call(handle, coordToPointer(coord))
|
||||
use(coord)
|
||||
return checkError(r1, r2, err)
|
||||
}
|
||||
|
||||
// GetConsoleMode gets the console mode for given file descriptor
|
||||
// See http://msdn.microsoft.com/en-us/library/windows/desktop/ms683167(v=vs.85).aspx.
|
||||
func GetConsoleMode(handle uintptr) (mode uint32, err error) {
|
||||
err = syscall.GetConsoleMode(syscall.Handle(handle), &mode)
|
||||
return mode, err
|
||||
}
|
||||
|
||||
// SetConsoleMode sets the console mode for given file descriptor
|
||||
// See http://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx.
|
||||
func SetConsoleMode(handle uintptr, mode uint32) error {
|
||||
r1, r2, err := setConsoleModeProc.Call(handle, uintptr(mode), 0)
|
||||
use(mode)
|
||||
return checkError(r1, r2, err)
|
||||
}
|
||||
|
||||
// GetConsoleScreenBufferInfo retrieves information about the specified console screen buffer.
|
||||
// See http://msdn.microsoft.com/en-us/library/windows/desktop/ms683171(v=vs.85).aspx.
|
||||
func GetConsoleScreenBufferInfo(handle uintptr) (*CONSOLE_SCREEN_BUFFER_INFO, error) {
|
||||
info := CONSOLE_SCREEN_BUFFER_INFO{}
|
||||
err := checkError(getConsoleScreenBufferInfoProc.Call(handle, uintptr(unsafe.Pointer(&info)), 0))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &info, nil
|
||||
}
|
||||
|
||||
func ScrollConsoleScreenBuffer(handle uintptr, scrollRect SMALL_RECT, clipRect SMALL_RECT, destOrigin COORD, char CHAR_INFO) error {
|
||||
r1, r2, err := scrollConsoleScreenBufferProc.Call(handle, uintptr(unsafe.Pointer(&scrollRect)), uintptr(unsafe.Pointer(&clipRect)), coordToPointer(destOrigin), uintptr(unsafe.Pointer(&char)))
|
||||
use(scrollRect)
|
||||
use(clipRect)
|
||||
use(destOrigin)
|
||||
use(char)
|
||||
return checkError(r1, r2, err)
|
||||
}
|
||||
|
||||
// SetConsoleScreenBufferSize sets the size of the console screen buffer.
|
||||
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686044(v=vs.85).aspx.
|
||||
func SetConsoleScreenBufferSize(handle uintptr, coord COORD) error {
|
||||
r1, r2, err := setConsoleScreenBufferSizeProc.Call(handle, coordToPointer(coord))
|
||||
use(coord)
|
||||
return checkError(r1, r2, err)
|
||||
}
|
||||
|
||||
// SetConsoleTextAttribute sets the attributes of characters written to the
|
||||
// console screen buffer by the WriteFile or WriteConsole function.
|
||||
// See http://msdn.microsoft.com/en-us/library/windows/desktop/ms686047(v=vs.85).aspx.
|
||||
func SetConsoleTextAttribute(handle uintptr, attribute uint16) error {
|
||||
r1, r2, err := setConsoleTextAttributeProc.Call(handle, uintptr(attribute), 0)
|
||||
use(attribute)
|
||||
return checkError(r1, r2, err)
|
||||
}
|
||||
|
||||
// SetConsoleWindowInfo sets the size and position of the console screen buffer's window.
|
||||
// Note that the size and location must be within and no larger than the backing console screen buffer.
|
||||
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686125(v=vs.85).aspx.
|
||||
func SetConsoleWindowInfo(handle uintptr, isAbsolute bool, rect SMALL_RECT) error {
|
||||
r1, r2, err := setConsoleWindowInfoProc.Call(handle, uintptr(boolToBOOL(isAbsolute)), uintptr(unsafe.Pointer(&rect)))
|
||||
use(isAbsolute)
|
||||
use(rect)
|
||||
return checkError(r1, r2, err)
|
||||
}
|
||||
|
||||
// WriteConsoleOutput writes the CHAR_INFOs from the provided buffer to the active console buffer.
|
||||
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms687404(v=vs.85).aspx.
|
||||
func WriteConsoleOutput(handle uintptr, buffer []CHAR_INFO, bufferSize COORD, bufferCoord COORD, writeRegion *SMALL_RECT) error {
|
||||
r1, r2, err := writeConsoleOutputProc.Call(handle, uintptr(unsafe.Pointer(&buffer[0])), coordToPointer(bufferSize), coordToPointer(bufferCoord), uintptr(unsafe.Pointer(writeRegion)))
|
||||
use(buffer)
|
||||
use(bufferSize)
|
||||
use(bufferCoord)
|
||||
return checkError(r1, r2, err)
|
||||
}
|
||||
|
||||
// ReadConsoleInput reads (and removes) data from the console input buffer.
|
||||
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms684961(v=vs.85).aspx.
|
||||
func ReadConsoleInput(handle uintptr, buffer []INPUT_RECORD, count *uint32) error {
|
||||
r1, r2, err := readConsoleInputProc.Call(handle, uintptr(unsafe.Pointer(&buffer[0])), uintptr(len(buffer)), uintptr(unsafe.Pointer(count)))
|
||||
use(buffer)
|
||||
return checkError(r1, r2, err)
|
||||
}
|
||||
|
||||
// WaitForSingleObject waits for the passed handle to be signaled.
|
||||
// It returns true if the handle was signaled; false otherwise.
|
||||
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms687032(v=vs.85).aspx.
|
||||
func WaitForSingleObject(handle uintptr, msWait uint32) (bool, error) {
|
||||
r1, _, err := waitForSingleObjectProc.Call(handle, uintptr(uint32(msWait)))
|
||||
switch r1 {
|
||||
case WAIT_ABANDONED, WAIT_TIMEOUT:
|
||||
return false, nil
|
||||
case WAIT_SIGNALED:
|
||||
return true, nil
|
||||
}
|
||||
use(msWait)
|
||||
return false, err
|
||||
}
|
||||
|
||||
// String helpers
|
||||
func (info CONSOLE_SCREEN_BUFFER_INFO) String() string {
|
||||
return fmt.Sprintf("Size(%v) Cursor(%v) Window(%v) Max(%v)", info.Size, info.CursorPosition, info.Window, info.MaximumWindowSize)
|
||||
}
|
||||
|
||||
func (coord COORD) String() string {
|
||||
return fmt.Sprintf("%v,%v", coord.X, coord.Y)
|
||||
}
|
||||
|
||||
func (rect SMALL_RECT) String() string {
|
||||
return fmt.Sprintf("(%v,%v),(%v,%v)", rect.Left, rect.Top, rect.Right, rect.Bottom)
|
||||
}
|
||||
|
||||
// checkError evaluates the results of a Windows API call and returns the error if it failed.
|
||||
func checkError(r1, r2 uintptr, err error) error {
|
||||
// Windows APIs return non-zero to indicate success
|
||||
if r1 != 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Return the error if provided, otherwise default to EINVAL
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return syscall.EINVAL
|
||||
}
|
||||
|
||||
// coordToPointer converts a COORD into a uintptr (by fooling the type system).
|
||||
func coordToPointer(c COORD) uintptr {
|
||||
// Note: This code assumes the two SHORTs are correctly laid out; the "cast" to uint32 is just to get a pointer to pass.
|
||||
return uintptr(*((*uint32)(unsafe.Pointer(&c))))
|
||||
}
|
||||
|
||||
// use is a no-op, but the compiler cannot see that it is.
|
||||
// Calling use(p) ensures that p is kept live until that point.
|
||||
func use(p interface{}) {}
|
||||
100
vendor/github.com/Azure/go-ansiterm/winterm/attr_translation.go
generated
vendored
Normal file
100
vendor/github.com/Azure/go-ansiterm/winterm/attr_translation.go
generated
vendored
Normal file
@@ -0,0 +1,100 @@
|
||||
// +build windows
|
||||
|
||||
package winterm
|
||||
|
||||
import "github.com/Azure/go-ansiterm"
|
||||
|
||||
const (
|
||||
FOREGROUND_COLOR_MASK = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE
|
||||
BACKGROUND_COLOR_MASK = BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE
|
||||
)
|
||||
|
||||
// collectAnsiIntoWindowsAttributes modifies the passed Windows text mode flags to reflect the
|
||||
// request represented by the passed ANSI mode.
|
||||
func collectAnsiIntoWindowsAttributes(windowsMode uint16, inverted bool, baseMode uint16, ansiMode int16) (uint16, bool) {
|
||||
switch ansiMode {
|
||||
|
||||
// Mode styles
|
||||
case ansiterm.ANSI_SGR_BOLD:
|
||||
windowsMode = windowsMode | FOREGROUND_INTENSITY
|
||||
|
||||
case ansiterm.ANSI_SGR_DIM, ansiterm.ANSI_SGR_BOLD_DIM_OFF:
|
||||
windowsMode &^= FOREGROUND_INTENSITY
|
||||
|
||||
case ansiterm.ANSI_SGR_UNDERLINE:
|
||||
windowsMode = windowsMode | COMMON_LVB_UNDERSCORE
|
||||
|
||||
case ansiterm.ANSI_SGR_REVERSE:
|
||||
inverted = true
|
||||
|
||||
case ansiterm.ANSI_SGR_REVERSE_OFF:
|
||||
inverted = false
|
||||
|
||||
case ansiterm.ANSI_SGR_UNDERLINE_OFF:
|
||||
windowsMode &^= COMMON_LVB_UNDERSCORE
|
||||
|
||||
// Foreground colors
|
||||
case ansiterm.ANSI_SGR_FOREGROUND_DEFAULT:
|
||||
windowsMode = (windowsMode &^ FOREGROUND_MASK) | (baseMode & FOREGROUND_MASK)
|
||||
|
||||
case ansiterm.ANSI_SGR_FOREGROUND_BLACK:
|
||||
windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK)
|
||||
|
||||
case ansiterm.ANSI_SGR_FOREGROUND_RED:
|
||||
windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_RED
|
||||
|
||||
case ansiterm.ANSI_SGR_FOREGROUND_GREEN:
|
||||
windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_GREEN
|
||||
|
||||
case ansiterm.ANSI_SGR_FOREGROUND_YELLOW:
|
||||
windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_RED | FOREGROUND_GREEN
|
||||
|
||||
case ansiterm.ANSI_SGR_FOREGROUND_BLUE:
|
||||
windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_BLUE
|
||||
|
||||
case ansiterm.ANSI_SGR_FOREGROUND_MAGENTA:
|
||||
windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_RED | FOREGROUND_BLUE
|
||||
|
||||
case ansiterm.ANSI_SGR_FOREGROUND_CYAN:
|
||||
windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_GREEN | FOREGROUND_BLUE
|
||||
|
||||
case ansiterm.ANSI_SGR_FOREGROUND_WHITE:
|
||||
windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE
|
||||
|
||||
// Background colors
|
||||
case ansiterm.ANSI_SGR_BACKGROUND_DEFAULT:
|
||||
// Black with no intensity
|
||||
windowsMode = (windowsMode &^ BACKGROUND_MASK) | (baseMode & BACKGROUND_MASK)
|
||||
|
||||
case ansiterm.ANSI_SGR_BACKGROUND_BLACK:
|
||||
windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK)
|
||||
|
||||
case ansiterm.ANSI_SGR_BACKGROUND_RED:
|
||||
windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_RED
|
||||
|
||||
case ansiterm.ANSI_SGR_BACKGROUND_GREEN:
|
||||
windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_GREEN
|
||||
|
||||
case ansiterm.ANSI_SGR_BACKGROUND_YELLOW:
|
||||
windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_RED | BACKGROUND_GREEN
|
||||
|
||||
case ansiterm.ANSI_SGR_BACKGROUND_BLUE:
|
||||
windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_BLUE
|
||||
|
||||
case ansiterm.ANSI_SGR_BACKGROUND_MAGENTA:
|
||||
windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_RED | BACKGROUND_BLUE
|
||||
|
||||
case ansiterm.ANSI_SGR_BACKGROUND_CYAN:
|
||||
windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_GREEN | BACKGROUND_BLUE
|
||||
|
||||
case ansiterm.ANSI_SGR_BACKGROUND_WHITE:
|
||||
windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE
|
||||
}
|
||||
|
||||
return windowsMode, inverted
|
||||
}
|
||||
|
||||
// invertAttributes inverts the foreground and background colors of a Windows attributes value
|
||||
func invertAttributes(windowsMode uint16) uint16 {
|
||||
return (COMMON_LVB_MASK & windowsMode) | ((FOREGROUND_MASK & windowsMode) << 4) | ((BACKGROUND_MASK & windowsMode) >> 4)
|
||||
}
|
||||
101
vendor/github.com/Azure/go-ansiterm/winterm/cursor_helpers.go
generated
vendored
Normal file
101
vendor/github.com/Azure/go-ansiterm/winterm/cursor_helpers.go
generated
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
// +build windows
|
||||
|
||||
package winterm
|
||||
|
||||
const (
|
||||
horizontal = iota
|
||||
vertical
|
||||
)
|
||||
|
||||
func (h *windowsAnsiEventHandler) getCursorWindow(info *CONSOLE_SCREEN_BUFFER_INFO) SMALL_RECT {
|
||||
if h.originMode {
|
||||
sr := h.effectiveSr(info.Window)
|
||||
return SMALL_RECT{
|
||||
Top: sr.top,
|
||||
Bottom: sr.bottom,
|
||||
Left: 0,
|
||||
Right: info.Size.X - 1,
|
||||
}
|
||||
} else {
|
||||
return SMALL_RECT{
|
||||
Top: info.Window.Top,
|
||||
Bottom: info.Window.Bottom,
|
||||
Left: 0,
|
||||
Right: info.Size.X - 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// setCursorPosition sets the cursor to the specified position, bounded to the screen size
|
||||
func (h *windowsAnsiEventHandler) setCursorPosition(position COORD, window SMALL_RECT) error {
|
||||
position.X = ensureInRange(position.X, window.Left, window.Right)
|
||||
position.Y = ensureInRange(position.Y, window.Top, window.Bottom)
|
||||
err := SetConsoleCursorPosition(h.fd, position)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h.logf("Cursor position set: (%d, %d)", position.X, position.Y)
|
||||
return err
|
||||
}
|
||||
|
||||
func (h *windowsAnsiEventHandler) moveCursorVertical(param int) error {
|
||||
return h.moveCursor(vertical, param)
|
||||
}
|
||||
|
||||
func (h *windowsAnsiEventHandler) moveCursorHorizontal(param int) error {
|
||||
return h.moveCursor(horizontal, param)
|
||||
}
|
||||
|
||||
func (h *windowsAnsiEventHandler) moveCursor(moveMode int, param int) error {
|
||||
info, err := GetConsoleScreenBufferInfo(h.fd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
position := info.CursorPosition
|
||||
switch moveMode {
|
||||
case horizontal:
|
||||
position.X += int16(param)
|
||||
case vertical:
|
||||
position.Y += int16(param)
|
||||
}
|
||||
|
||||
if err = h.setCursorPosition(position, h.getCursorWindow(info)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *windowsAnsiEventHandler) moveCursorLine(param int) error {
|
||||
info, err := GetConsoleScreenBufferInfo(h.fd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
position := info.CursorPosition
|
||||
position.X = 0
|
||||
position.Y += int16(param)
|
||||
|
||||
if err = h.setCursorPosition(position, h.getCursorWindow(info)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *windowsAnsiEventHandler) moveCursorColumn(param int) error {
|
||||
info, err := GetConsoleScreenBufferInfo(h.fd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
position := info.CursorPosition
|
||||
position.X = int16(param) - 1
|
||||
|
||||
if err = h.setCursorPosition(position, h.getCursorWindow(info)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
84
vendor/github.com/Azure/go-ansiterm/winterm/erase_helpers.go
generated
vendored
Normal file
84
vendor/github.com/Azure/go-ansiterm/winterm/erase_helpers.go
generated
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
// +build windows
|
||||
|
||||
package winterm
|
||||
|
||||
import "github.com/Azure/go-ansiterm"
|
||||
|
||||
func (h *windowsAnsiEventHandler) clearRange(attributes uint16, fromCoord COORD, toCoord COORD) error {
|
||||
// Ignore an invalid (negative area) request
|
||||
if toCoord.Y < fromCoord.Y {
|
||||
return nil
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
var coordStart = COORD{}
|
||||
var coordEnd = COORD{}
|
||||
|
||||
xCurrent, yCurrent := fromCoord.X, fromCoord.Y
|
||||
xEnd, yEnd := toCoord.X, toCoord.Y
|
||||
|
||||
// Clear any partial initial line
|
||||
if xCurrent > 0 {
|
||||
coordStart.X, coordStart.Y = xCurrent, yCurrent
|
||||
coordEnd.X, coordEnd.Y = xEnd, yCurrent
|
||||
|
||||
err = h.clearRect(attributes, coordStart, coordEnd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
xCurrent = 0
|
||||
yCurrent += 1
|
||||
}
|
||||
|
||||
// Clear intervening rectangular section
|
||||
if yCurrent < yEnd {
|
||||
coordStart.X, coordStart.Y = xCurrent, yCurrent
|
||||
coordEnd.X, coordEnd.Y = xEnd, yEnd-1
|
||||
|
||||
err = h.clearRect(attributes, coordStart, coordEnd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
xCurrent = 0
|
||||
yCurrent = yEnd
|
||||
}
|
||||
|
||||
// Clear remaining partial ending line
|
||||
coordStart.X, coordStart.Y = xCurrent, yCurrent
|
||||
coordEnd.X, coordEnd.Y = xEnd, yEnd
|
||||
|
||||
err = h.clearRect(attributes, coordStart, coordEnd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *windowsAnsiEventHandler) clearRect(attributes uint16, fromCoord COORD, toCoord COORD) error {
|
||||
region := SMALL_RECT{Top: fromCoord.Y, Left: fromCoord.X, Bottom: toCoord.Y, Right: toCoord.X}
|
||||
width := toCoord.X - fromCoord.X + 1
|
||||
height := toCoord.Y - fromCoord.Y + 1
|
||||
size := uint32(width) * uint32(height)
|
||||
|
||||
if size <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
buffer := make([]CHAR_INFO, size)
|
||||
|
||||
char := CHAR_INFO{ansiterm.FILL_CHARACTER, attributes}
|
||||
for i := 0; i < int(size); i++ {
|
||||
buffer[i] = char
|
||||
}
|
||||
|
||||
err := WriteConsoleOutput(h.fd, buffer, COORD{X: width, Y: height}, COORD{X: 0, Y: 0}, ®ion)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
118
vendor/github.com/Azure/go-ansiterm/winterm/scroll_helper.go
generated
vendored
Normal file
118
vendor/github.com/Azure/go-ansiterm/winterm/scroll_helper.go
generated
vendored
Normal file
@@ -0,0 +1,118 @@
|
||||
// +build windows
|
||||
|
||||
package winterm
|
||||
|
||||
// effectiveSr gets the current effective scroll region in buffer coordinates
|
||||
func (h *windowsAnsiEventHandler) effectiveSr(window SMALL_RECT) scrollRegion {
|
||||
top := addInRange(window.Top, h.sr.top, window.Top, window.Bottom)
|
||||
bottom := addInRange(window.Top, h.sr.bottom, window.Top, window.Bottom)
|
||||
if top >= bottom {
|
||||
top = window.Top
|
||||
bottom = window.Bottom
|
||||
}
|
||||
return scrollRegion{top: top, bottom: bottom}
|
||||
}
|
||||
|
||||
func (h *windowsAnsiEventHandler) scrollUp(param int) error {
|
||||
info, err := GetConsoleScreenBufferInfo(h.fd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sr := h.effectiveSr(info.Window)
|
||||
return h.scroll(param, sr, info)
|
||||
}
|
||||
|
||||
func (h *windowsAnsiEventHandler) scrollDown(param int) error {
|
||||
return h.scrollUp(-param)
|
||||
}
|
||||
|
||||
func (h *windowsAnsiEventHandler) deleteLines(param int) error {
|
||||
info, err := GetConsoleScreenBufferInfo(h.fd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
start := info.CursorPosition.Y
|
||||
sr := h.effectiveSr(info.Window)
|
||||
// Lines cannot be inserted or deleted outside the scrolling region.
|
||||
if start >= sr.top && start <= sr.bottom {
|
||||
sr.top = start
|
||||
return h.scroll(param, sr, info)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (h *windowsAnsiEventHandler) insertLines(param int) error {
|
||||
return h.deleteLines(-param)
|
||||
}
|
||||
|
||||
// scroll scrolls the provided scroll region by param lines. The scroll region is in buffer coordinates.
|
||||
func (h *windowsAnsiEventHandler) scroll(param int, sr scrollRegion, info *CONSOLE_SCREEN_BUFFER_INFO) error {
|
||||
h.logf("scroll: scrollTop: %d, scrollBottom: %d", sr.top, sr.bottom)
|
||||
h.logf("scroll: windowTop: %d, windowBottom: %d", info.Window.Top, info.Window.Bottom)
|
||||
|
||||
// Copy from and clip to the scroll region (full buffer width)
|
||||
scrollRect := SMALL_RECT{
|
||||
Top: sr.top,
|
||||
Bottom: sr.bottom,
|
||||
Left: 0,
|
||||
Right: info.Size.X - 1,
|
||||
}
|
||||
|
||||
// Origin to which area should be copied
|
||||
destOrigin := COORD{
|
||||
X: 0,
|
||||
Y: sr.top - int16(param),
|
||||
}
|
||||
|
||||
char := CHAR_INFO{
|
||||
UnicodeChar: ' ',
|
||||
Attributes: h.attributes,
|
||||
}
|
||||
|
||||
if err := ScrollConsoleScreenBuffer(h.fd, scrollRect, scrollRect, destOrigin, char); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *windowsAnsiEventHandler) deleteCharacters(param int) error {
|
||||
info, err := GetConsoleScreenBufferInfo(h.fd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return h.scrollLine(param, info.CursorPosition, info)
|
||||
}
|
||||
|
||||
func (h *windowsAnsiEventHandler) insertCharacters(param int) error {
|
||||
return h.deleteCharacters(-param)
|
||||
}
|
||||
|
||||
// scrollLine scrolls a line horizontally starting at the provided position by a number of columns.
|
||||
func (h *windowsAnsiEventHandler) scrollLine(columns int, position COORD, info *CONSOLE_SCREEN_BUFFER_INFO) error {
|
||||
// Copy from and clip to the scroll region (full buffer width)
|
||||
scrollRect := SMALL_RECT{
|
||||
Top: position.Y,
|
||||
Bottom: position.Y,
|
||||
Left: position.X,
|
||||
Right: info.Size.X - 1,
|
||||
}
|
||||
|
||||
// Origin to which area should be copied
|
||||
destOrigin := COORD{
|
||||
X: position.X - int16(columns),
|
||||
Y: position.Y,
|
||||
}
|
||||
|
||||
char := CHAR_INFO{
|
||||
UnicodeChar: ' ',
|
||||
Attributes: h.attributes,
|
||||
}
|
||||
|
||||
if err := ScrollConsoleScreenBuffer(h.fd, scrollRect, scrollRect, destOrigin, char); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
9
vendor/github.com/Azure/go-ansiterm/winterm/utilities.go
generated
vendored
Normal file
9
vendor/github.com/Azure/go-ansiterm/winterm/utilities.go
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
// +build windows
|
||||
|
||||
package winterm
|
||||
|
||||
// AddInRange increments a value by the passed quantity while ensuring the values
|
||||
// always remain within the supplied min / max range.
|
||||
func addInRange(n int16, increment int16, min int16, max int16) int16 {
|
||||
return ensureInRange(n+increment, min, max)
|
||||
}
|
||||
743
vendor/github.com/Azure/go-ansiterm/winterm/win_event_handler.go
generated
vendored
Normal file
743
vendor/github.com/Azure/go-ansiterm/winterm/win_event_handler.go
generated
vendored
Normal file
@@ -0,0 +1,743 @@
|
||||
// +build windows
|
||||
|
||||
package winterm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/Azure/go-ansiterm"
|
||||
)
|
||||
|
||||
type windowsAnsiEventHandler struct {
|
||||
fd uintptr
|
||||
file *os.File
|
||||
infoReset *CONSOLE_SCREEN_BUFFER_INFO
|
||||
sr scrollRegion
|
||||
buffer bytes.Buffer
|
||||
attributes uint16
|
||||
inverted bool
|
||||
wrapNext bool
|
||||
drewMarginByte bool
|
||||
originMode bool
|
||||
marginByte byte
|
||||
curInfo *CONSOLE_SCREEN_BUFFER_INFO
|
||||
curPos COORD
|
||||
logf func(string, ...interface{})
|
||||
}
|
||||
|
||||
type Option func(*windowsAnsiEventHandler)
|
||||
|
||||
func WithLogf(f func(string, ...interface{})) Option {
|
||||
return func(w *windowsAnsiEventHandler) {
|
||||
w.logf = f
|
||||
}
|
||||
}
|
||||
|
||||
func CreateWinEventHandler(fd uintptr, file *os.File, opts ...Option) ansiterm.AnsiEventHandler {
|
||||
infoReset, err := GetConsoleScreenBufferInfo(fd)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
h := &windowsAnsiEventHandler{
|
||||
fd: fd,
|
||||
file: file,
|
||||
infoReset: infoReset,
|
||||
attributes: infoReset.Attributes,
|
||||
}
|
||||
for _, o := range opts {
|
||||
o(h)
|
||||
}
|
||||
|
||||
if isDebugEnv := os.Getenv(ansiterm.LogEnv); isDebugEnv == "1" {
|
||||
logFile, _ := os.Create("winEventHandler.log")
|
||||
logger := log.New(logFile, "", log.LstdFlags)
|
||||
if h.logf != nil {
|
||||
l := h.logf
|
||||
h.logf = func(s string, v ...interface{}) {
|
||||
l(s, v...)
|
||||
logger.Printf(s, v...)
|
||||
}
|
||||
} else {
|
||||
h.logf = logger.Printf
|
||||
}
|
||||
}
|
||||
|
||||
if h.logf == nil {
|
||||
h.logf = func(string, ...interface{}) {}
|
||||
}
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
type scrollRegion struct {
|
||||
top int16
|
||||
bottom int16
|
||||
}
|
||||
|
||||
// simulateLF simulates a LF or CR+LF by scrolling if necessary to handle the
|
||||
// current cursor position and scroll region settings, in which case it returns
|
||||
// true. If no special handling is necessary, then it does nothing and returns
|
||||
// false.
|
||||
//
|
||||
// In the false case, the caller should ensure that a carriage return
|
||||
// and line feed are inserted or that the text is otherwise wrapped.
|
||||
func (h *windowsAnsiEventHandler) simulateLF(includeCR bool) (bool, error) {
|
||||
if h.wrapNext {
|
||||
if err := h.Flush(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
h.clearWrap()
|
||||
}
|
||||
pos, info, err := h.getCurrentInfo()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
sr := h.effectiveSr(info.Window)
|
||||
if pos.Y == sr.bottom {
|
||||
// Scrolling is necessary. Let Windows automatically scroll if the scrolling region
|
||||
// is the full window.
|
||||
if sr.top == info.Window.Top && sr.bottom == info.Window.Bottom {
|
||||
if includeCR {
|
||||
pos.X = 0
|
||||
h.updatePos(pos)
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// A custom scroll region is active. Scroll the window manually to simulate
|
||||
// the LF.
|
||||
if err := h.Flush(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
h.logf("Simulating LF inside scroll region")
|
||||
if err := h.scrollUp(1); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if includeCR {
|
||||
pos.X = 0
|
||||
if err := SetConsoleCursorPosition(h.fd, pos); err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
|
||||
} else if pos.Y < info.Window.Bottom {
|
||||
// Let Windows handle the LF.
|
||||
pos.Y++
|
||||
if includeCR {
|
||||
pos.X = 0
|
||||
}
|
||||
h.updatePos(pos)
|
||||
return false, nil
|
||||
} else {
|
||||
// The cursor is at the bottom of the screen but outside the scroll
|
||||
// region. Skip the LF.
|
||||
h.logf("Simulating LF outside scroll region")
|
||||
if includeCR {
|
||||
if err := h.Flush(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
pos.X = 0
|
||||
if err := SetConsoleCursorPosition(h.fd, pos); err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
// executeLF executes a LF without a CR.
|
||||
func (h *windowsAnsiEventHandler) executeLF() error {
|
||||
handled, err := h.simulateLF(false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !handled {
|
||||
// Windows LF will reset the cursor column position. Write the LF
|
||||
// and restore the cursor position.
|
||||
pos, _, err := h.getCurrentInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h.buffer.WriteByte(ansiterm.ANSI_LINE_FEED)
|
||||
if pos.X != 0 {
|
||||
if err := h.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
h.logf("Resetting cursor position for LF without CR")
|
||||
if err := SetConsoleCursorPosition(h.fd, pos); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *windowsAnsiEventHandler) Print(b byte) error {
|
||||
if h.wrapNext {
|
||||
h.buffer.WriteByte(h.marginByte)
|
||||
h.clearWrap()
|
||||
if _, err := h.simulateLF(true); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
pos, info, err := h.getCurrentInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if pos.X == info.Size.X-1 {
|
||||
h.wrapNext = true
|
||||
h.marginByte = b
|
||||
} else {
|
||||
pos.X++
|
||||
h.updatePos(pos)
|
||||
h.buffer.WriteByte(b)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *windowsAnsiEventHandler) Execute(b byte) error {
|
||||
switch b {
|
||||
case ansiterm.ANSI_TAB:
|
||||
h.logf("Execute(TAB)")
|
||||
// Move to the next tab stop, but preserve auto-wrap if already set.
|
||||
if !h.wrapNext {
|
||||
pos, info, err := h.getCurrentInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pos.X = (pos.X + 8) - pos.X%8
|
||||
if pos.X >= info.Size.X {
|
||||
pos.X = info.Size.X - 1
|
||||
}
|
||||
if err := h.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := SetConsoleCursorPosition(h.fd, pos); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case ansiterm.ANSI_BEL:
|
||||
h.buffer.WriteByte(ansiterm.ANSI_BEL)
|
||||
return nil
|
||||
|
||||
case ansiterm.ANSI_BACKSPACE:
|
||||
if h.wrapNext {
|
||||
if err := h.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
h.clearWrap()
|
||||
}
|
||||
pos, _, err := h.getCurrentInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if pos.X > 0 {
|
||||
pos.X--
|
||||
h.updatePos(pos)
|
||||
h.buffer.WriteByte(ansiterm.ANSI_BACKSPACE)
|
||||
}
|
||||
return nil
|
||||
|
||||
case ansiterm.ANSI_VERTICAL_TAB, ansiterm.ANSI_FORM_FEED:
|
||||
// Treat as true LF.
|
||||
return h.executeLF()
|
||||
|
||||
case ansiterm.ANSI_LINE_FEED:
|
||||
// Simulate a CR and LF for now since there is no way in go-ansiterm
|
||||
// to tell if the LF should include CR (and more things break when it's
|
||||
// missing than when it's incorrectly added).
|
||||
handled, err := h.simulateLF(true)
|
||||
if handled || err != nil {
|
||||
return err
|
||||
}
|
||||
return h.buffer.WriteByte(ansiterm.ANSI_LINE_FEED)
|
||||
|
||||
case ansiterm.ANSI_CARRIAGE_RETURN:
|
||||
if h.wrapNext {
|
||||
if err := h.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
h.clearWrap()
|
||||
}
|
||||
pos, _, err := h.getCurrentInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if pos.X != 0 {
|
||||
pos.X = 0
|
||||
h.updatePos(pos)
|
||||
h.buffer.WriteByte(ansiterm.ANSI_CARRIAGE_RETURN)
|
||||
}
|
||||
return nil
|
||||
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (h *windowsAnsiEventHandler) CUU(param int) error {
|
||||
if err := h.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
h.logf("CUU: [%v]", []string{strconv.Itoa(param)})
|
||||
h.clearWrap()
|
||||
return h.moveCursorVertical(-param)
|
||||
}
|
||||
|
||||
func (h *windowsAnsiEventHandler) CUD(param int) error {
|
||||
if err := h.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
h.logf("CUD: [%v]", []string{strconv.Itoa(param)})
|
||||
h.clearWrap()
|
||||
return h.moveCursorVertical(param)
|
||||
}
|
||||
|
||||
func (h *windowsAnsiEventHandler) CUF(param int) error {
|
||||
if err := h.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
h.logf("CUF: [%v]", []string{strconv.Itoa(param)})
|
||||
h.clearWrap()
|
||||
return h.moveCursorHorizontal(param)
|
||||
}
|
||||
|
||||
func (h *windowsAnsiEventHandler) CUB(param int) error {
|
||||
if err := h.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
h.logf("CUB: [%v]", []string{strconv.Itoa(param)})
|
||||
h.clearWrap()
|
||||
return h.moveCursorHorizontal(-param)
|
||||
}
|
||||
|
||||
func (h *windowsAnsiEventHandler) CNL(param int) error {
|
||||
if err := h.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
h.logf("CNL: [%v]", []string{strconv.Itoa(param)})
|
||||
h.clearWrap()
|
||||
return h.moveCursorLine(param)
|
||||
}
|
||||
|
||||
func (h *windowsAnsiEventHandler) CPL(param int) error {
|
||||
if err := h.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
h.logf("CPL: [%v]", []string{strconv.Itoa(param)})
|
||||
h.clearWrap()
|
||||
return h.moveCursorLine(-param)
|
||||
}
|
||||
|
||||
func (h *windowsAnsiEventHandler) CHA(param int) error {
|
||||
if err := h.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
h.logf("CHA: [%v]", []string{strconv.Itoa(param)})
|
||||
h.clearWrap()
|
||||
return h.moveCursorColumn(param)
|
||||
}
|
||||
|
||||
func (h *windowsAnsiEventHandler) VPA(param int) error {
|
||||
if err := h.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
h.logf("VPA: [[%d]]", param)
|
||||
h.clearWrap()
|
||||
info, err := GetConsoleScreenBufferInfo(h.fd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
window := h.getCursorWindow(info)
|
||||
position := info.CursorPosition
|
||||
position.Y = window.Top + int16(param) - 1
|
||||
return h.setCursorPosition(position, window)
|
||||
}
|
||||
|
||||
func (h *windowsAnsiEventHandler) CUP(row int, col int) error {
|
||||
if err := h.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
h.logf("CUP: [[%d %d]]", row, col)
|
||||
h.clearWrap()
|
||||
info, err := GetConsoleScreenBufferInfo(h.fd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
window := h.getCursorWindow(info)
|
||||
position := COORD{window.Left + int16(col) - 1, window.Top + int16(row) - 1}
|
||||
return h.setCursorPosition(position, window)
|
||||
}
|
||||
|
||||
func (h *windowsAnsiEventHandler) HVP(row int, col int) error {
|
||||
if err := h.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
h.logf("HVP: [[%d %d]]", row, col)
|
||||
h.clearWrap()
|
||||
return h.CUP(row, col)
|
||||
}
|
||||
|
||||
func (h *windowsAnsiEventHandler) DECTCEM(visible bool) error {
|
||||
if err := h.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
h.logf("DECTCEM: [%v]", []string{strconv.FormatBool(visible)})
|
||||
h.clearWrap()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *windowsAnsiEventHandler) DECOM(enable bool) error {
|
||||
if err := h.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
h.logf("DECOM: [%v]", []string{strconv.FormatBool(enable)})
|
||||
h.clearWrap()
|
||||
h.originMode = enable
|
||||
return h.CUP(1, 1)
|
||||
}
|
||||
|
||||
func (h *windowsAnsiEventHandler) DECCOLM(use132 bool) error {
|
||||
if err := h.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
h.logf("DECCOLM: [%v]", []string{strconv.FormatBool(use132)})
|
||||
h.clearWrap()
|
||||
if err := h.ED(2); err != nil {
|
||||
return err
|
||||
}
|
||||
info, err := GetConsoleScreenBufferInfo(h.fd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
targetWidth := int16(80)
|
||||
if use132 {
|
||||
targetWidth = 132
|
||||
}
|
||||
if info.Size.X < targetWidth {
|
||||
if err := SetConsoleScreenBufferSize(h.fd, COORD{targetWidth, info.Size.Y}); err != nil {
|
||||
h.logf("set buffer failed: %v", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
window := info.Window
|
||||
window.Left = 0
|
||||
window.Right = targetWidth - 1
|
||||
if err := SetConsoleWindowInfo(h.fd, true, window); err != nil {
|
||||
h.logf("set window failed: %v", err)
|
||||
return err
|
||||
}
|
||||
if info.Size.X > targetWidth {
|
||||
if err := SetConsoleScreenBufferSize(h.fd, COORD{targetWidth, info.Size.Y}); err != nil {
|
||||
h.logf("set buffer failed: %v", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return SetConsoleCursorPosition(h.fd, COORD{0, 0})
|
||||
}
|
||||
|
||||
func (h *windowsAnsiEventHandler) ED(param int) error {
|
||||
if err := h.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
h.logf("ED: [%v]", []string{strconv.Itoa(param)})
|
||||
h.clearWrap()
|
||||
|
||||
// [J -- Erases from the cursor to the end of the screen, including the cursor position.
|
||||
// [1J -- Erases from the beginning of the screen to the cursor, including the cursor position.
|
||||
// [2J -- Erases the complete display. The cursor does not move.
|
||||
// Notes:
|
||||
// -- Clearing the entire buffer, versus just the Window, works best for Windows Consoles
|
||||
|
||||
info, err := GetConsoleScreenBufferInfo(h.fd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var start COORD
|
||||
var end COORD
|
||||
|
||||
switch param {
|
||||
case 0:
|
||||
start = info.CursorPosition
|
||||
end = COORD{info.Size.X - 1, info.Size.Y - 1}
|
||||
|
||||
case 1:
|
||||
start = COORD{0, 0}
|
||||
end = info.CursorPosition
|
||||
|
||||
case 2:
|
||||
start = COORD{0, 0}
|
||||
end = COORD{info.Size.X - 1, info.Size.Y - 1}
|
||||
}
|
||||
|
||||
err = h.clearRange(h.attributes, start, end)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If the whole buffer was cleared, move the window to the top while preserving
|
||||
// the window-relative cursor position.
|
||||
if param == 2 {
|
||||
pos := info.CursorPosition
|
||||
window := info.Window
|
||||
pos.Y -= window.Top
|
||||
window.Bottom -= window.Top
|
||||
window.Top = 0
|
||||
if err := SetConsoleCursorPosition(h.fd, pos); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := SetConsoleWindowInfo(h.fd, true, window); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *windowsAnsiEventHandler) EL(param int) error {
|
||||
if err := h.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
h.logf("EL: [%v]", strconv.Itoa(param))
|
||||
h.clearWrap()
|
||||
|
||||
// [K -- Erases from the cursor to the end of the line, including the cursor position.
|
||||
// [1K -- Erases from the beginning of the line to the cursor, including the cursor position.
|
||||
// [2K -- Erases the complete line.
|
||||
|
||||
info, err := GetConsoleScreenBufferInfo(h.fd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var start COORD
|
||||
var end COORD
|
||||
|
||||
switch param {
|
||||
case 0:
|
||||
start = info.CursorPosition
|
||||
end = COORD{info.Size.X, info.CursorPosition.Y}
|
||||
|
||||
case 1:
|
||||
start = COORD{0, info.CursorPosition.Y}
|
||||
end = info.CursorPosition
|
||||
|
||||
case 2:
|
||||
start = COORD{0, info.CursorPosition.Y}
|
||||
end = COORD{info.Size.X, info.CursorPosition.Y}
|
||||
}
|
||||
|
||||
err = h.clearRange(h.attributes, start, end)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *windowsAnsiEventHandler) IL(param int) error {
|
||||
if err := h.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
h.logf("IL: [%v]", strconv.Itoa(param))
|
||||
h.clearWrap()
|
||||
return h.insertLines(param)
|
||||
}
|
||||
|
||||
func (h *windowsAnsiEventHandler) DL(param int) error {
|
||||
if err := h.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
h.logf("DL: [%v]", strconv.Itoa(param))
|
||||
h.clearWrap()
|
||||
return h.deleteLines(param)
|
||||
}
|
||||
|
||||
func (h *windowsAnsiEventHandler) ICH(param int) error {
|
||||
if err := h.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
h.logf("ICH: [%v]", strconv.Itoa(param))
|
||||
h.clearWrap()
|
||||
return h.insertCharacters(param)
|
||||
}
|
||||
|
||||
func (h *windowsAnsiEventHandler) DCH(param int) error {
|
||||
if err := h.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
h.logf("DCH: [%v]", strconv.Itoa(param))
|
||||
h.clearWrap()
|
||||
return h.deleteCharacters(param)
|
||||
}
|
||||
|
||||
func (h *windowsAnsiEventHandler) SGR(params []int) error {
|
||||
if err := h.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
strings := []string{}
|
||||
for _, v := range params {
|
||||
strings = append(strings, strconv.Itoa(v))
|
||||
}
|
||||
|
||||
h.logf("SGR: [%v]", strings)
|
||||
|
||||
if len(params) <= 0 {
|
||||
h.attributes = h.infoReset.Attributes
|
||||
h.inverted = false
|
||||
} else {
|
||||
for _, attr := range params {
|
||||
|
||||
if attr == ansiterm.ANSI_SGR_RESET {
|
||||
h.attributes = h.infoReset.Attributes
|
||||
h.inverted = false
|
||||
continue
|
||||
}
|
||||
|
||||
h.attributes, h.inverted = collectAnsiIntoWindowsAttributes(h.attributes, h.inverted, h.infoReset.Attributes, int16(attr))
|
||||
}
|
||||
}
|
||||
|
||||
attributes := h.attributes
|
||||
if h.inverted {
|
||||
attributes = invertAttributes(attributes)
|
||||
}
|
||||
err := SetConsoleTextAttribute(h.fd, attributes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *windowsAnsiEventHandler) SU(param int) error {
|
||||
if err := h.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
h.logf("SU: [%v]", []string{strconv.Itoa(param)})
|
||||
h.clearWrap()
|
||||
return h.scrollUp(param)
|
||||
}
|
||||
|
||||
func (h *windowsAnsiEventHandler) SD(param int) error {
|
||||
if err := h.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
h.logf("SD: [%v]", []string{strconv.Itoa(param)})
|
||||
h.clearWrap()
|
||||
return h.scrollDown(param)
|
||||
}
|
||||
|
||||
func (h *windowsAnsiEventHandler) DA(params []string) error {
|
||||
h.logf("DA: [%v]", params)
|
||||
// DA cannot be implemented because it must send data on the VT100 input stream,
|
||||
// which is not available to go-ansiterm.
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *windowsAnsiEventHandler) DECSTBM(top int, bottom int) error {
|
||||
if err := h.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
h.logf("DECSTBM: [%d, %d]", top, bottom)
|
||||
|
||||
// Windows is 0 indexed, Linux is 1 indexed
|
||||
h.sr.top = int16(top - 1)
|
||||
h.sr.bottom = int16(bottom - 1)
|
||||
|
||||
// This command also moves the cursor to the origin.
|
||||
h.clearWrap()
|
||||
return h.CUP(1, 1)
|
||||
}
|
||||
|
||||
func (h *windowsAnsiEventHandler) RI() error {
|
||||
if err := h.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
h.logf("RI: []")
|
||||
h.clearWrap()
|
||||
|
||||
info, err := GetConsoleScreenBufferInfo(h.fd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sr := h.effectiveSr(info.Window)
|
||||
if info.CursorPosition.Y == sr.top {
|
||||
return h.scrollDown(1)
|
||||
}
|
||||
|
||||
return h.moveCursorVertical(-1)
|
||||
}
|
||||
|
||||
func (h *windowsAnsiEventHandler) IND() error {
|
||||
h.logf("IND: []")
|
||||
return h.executeLF()
|
||||
}
|
||||
|
||||
func (h *windowsAnsiEventHandler) Flush() error {
|
||||
h.curInfo = nil
|
||||
if h.buffer.Len() > 0 {
|
||||
h.logf("Flush: [%s]", h.buffer.Bytes())
|
||||
if _, err := h.buffer.WriteTo(h.file); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if h.wrapNext && !h.drewMarginByte {
|
||||
h.logf("Flush: drawing margin byte '%c'", h.marginByte)
|
||||
|
||||
info, err := GetConsoleScreenBufferInfo(h.fd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
charInfo := []CHAR_INFO{{UnicodeChar: uint16(h.marginByte), Attributes: info.Attributes}}
|
||||
size := COORD{1, 1}
|
||||
position := COORD{0, 0}
|
||||
region := SMALL_RECT{Left: info.CursorPosition.X, Top: info.CursorPosition.Y, Right: info.CursorPosition.X, Bottom: info.CursorPosition.Y}
|
||||
if err := WriteConsoleOutput(h.fd, charInfo, size, position, ®ion); err != nil {
|
||||
return err
|
||||
}
|
||||
h.drewMarginByte = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// cacheConsoleInfo ensures that the current console screen information has been queried
|
||||
// since the last call to Flush(). It must be called before accessing h.curInfo or h.curPos.
|
||||
func (h *windowsAnsiEventHandler) getCurrentInfo() (COORD, *CONSOLE_SCREEN_BUFFER_INFO, error) {
|
||||
if h.curInfo == nil {
|
||||
info, err := GetConsoleScreenBufferInfo(h.fd)
|
||||
if err != nil {
|
||||
return COORD{}, nil, err
|
||||
}
|
||||
h.curInfo = info
|
||||
h.curPos = info.CursorPosition
|
||||
}
|
||||
return h.curPos, h.curInfo, nil
|
||||
}
|
||||
|
||||
func (h *windowsAnsiEventHandler) updatePos(pos COORD) {
|
||||
if h.curInfo == nil {
|
||||
panic("failed to call getCurrentInfo before calling updatePos")
|
||||
}
|
||||
h.curPos = pos
|
||||
}
|
||||
|
||||
// clearWrap clears the state where the cursor is in the margin
|
||||
// waiting for the next character before wrapping the line. This must
|
||||
// be done before most operations that act on the cursor.
|
||||
func (h *windowsAnsiEventHandler) clearWrap() {
|
||||
h.wrapNext = false
|
||||
h.drewMarginByte = false
|
||||
}
|
||||
22
vendor/github.com/cenkalti/backoff/.gitignore
generated
vendored
Normal file
22
vendor/github.com/cenkalti/backoff/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
9
vendor/github.com/cenkalti/backoff/.travis.yml
generated
vendored
Normal file
9
vendor/github.com/cenkalti/backoff/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
language: go
|
||||
go:
|
||||
- 1.3.3
|
||||
- tip
|
||||
before_install:
|
||||
- go get github.com/mattn/goveralls
|
||||
- go get golang.org/x/tools/cmd/cover
|
||||
script:
|
||||
- $HOME/gopath/bin/goveralls -service=travis-ci
|
||||
20
vendor/github.com/cenkalti/backoff/LICENSE
generated
vendored
Normal file
20
vendor/github.com/cenkalti/backoff/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Cenk Altı
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
30
vendor/github.com/cenkalti/backoff/README.md
generated
vendored
Normal file
30
vendor/github.com/cenkalti/backoff/README.md
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
# Exponential Backoff [![GoDoc][godoc image]][godoc] [![Build Status][travis image]][travis] [![Coverage Status][coveralls image]][coveralls]
|
||||
|
||||
This is a Go port of the exponential backoff algorithm from [Google's HTTP Client Library for Java][google-http-java-client].
|
||||
|
||||
[Exponential backoff][exponential backoff wiki]
|
||||
is an algorithm that uses feedback to multiplicatively decrease the rate of some process,
|
||||
in order to gradually find an acceptable rate.
|
||||
The retries exponentially increase and stop increasing when a certain threshold is met.
|
||||
|
||||
## Usage
|
||||
|
||||
See https://godoc.org/github.com/cenkalti/backoff#pkg-examples
|
||||
|
||||
## Contributing
|
||||
|
||||
* I would like to keep this library as small as possible.
|
||||
* Please don't send a PR without opening an issue and discussing it first.
|
||||
* If proposed change is not a common use case, I will probably not accept it.
|
||||
|
||||
[godoc]: https://godoc.org/github.com/cenkalti/backoff
|
||||
[godoc image]: https://godoc.org/github.com/cenkalti/backoff?status.png
|
||||
[travis]: https://travis-ci.org/cenkalti/backoff
|
||||
[travis image]: https://travis-ci.org/cenkalti/backoff.png?branch=master
|
||||
[coveralls]: https://coveralls.io/github/cenkalti/backoff?branch=master
|
||||
[coveralls image]: https://coveralls.io/repos/github/cenkalti/backoff/badge.svg?branch=master
|
||||
|
||||
[google-http-java-client]: https://github.com/google/google-http-java-client
|
||||
[exponential backoff wiki]: http://en.wikipedia.org/wiki/Exponential_backoff
|
||||
|
||||
[advanced example]: https://godoc.org/github.com/cenkalti/backoff#example_
|
||||
66
vendor/github.com/cenkalti/backoff/backoff.go
generated
vendored
Normal file
66
vendor/github.com/cenkalti/backoff/backoff.go
generated
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
// Package backoff implements backoff algorithms for retrying operations.
|
||||
//
|
||||
// Use Retry function for retrying operations that may fail.
|
||||
// If Retry does not meet your needs,
|
||||
// copy/paste the function into your project and modify as you wish.
|
||||
//
|
||||
// There is also Ticker type similar to time.Ticker.
|
||||
// You can use it if you need to work with channels.
|
||||
//
|
||||
// See Examples section below for usage examples.
|
||||
package backoff
|
||||
|
||||
import "time"
|
||||
|
||||
// BackOff is a backoff policy for retrying an operation.
|
||||
type BackOff interface {
|
||||
// NextBackOff returns the duration to wait before retrying the operation,
|
||||
// or backoff.Stop to indicate that no more retries should be made.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// duration := backoff.NextBackOff();
|
||||
// if (duration == backoff.Stop) {
|
||||
// // Do not retry operation.
|
||||
// } else {
|
||||
// // Sleep for duration and retry operation.
|
||||
// }
|
||||
//
|
||||
NextBackOff() time.Duration
|
||||
|
||||
// Reset to initial state.
|
||||
Reset()
|
||||
}
|
||||
|
||||
// Stop indicates that no more retries should be made for use in NextBackOff().
|
||||
const Stop time.Duration = -1
|
||||
|
||||
// ZeroBackOff is a fixed backoff policy whose backoff time is always zero,
|
||||
// meaning that the operation is retried immediately without waiting, indefinitely.
|
||||
type ZeroBackOff struct{}
|
||||
|
||||
func (b *ZeroBackOff) Reset() {}
|
||||
|
||||
func (b *ZeroBackOff) NextBackOff() time.Duration { return 0 }
|
||||
|
||||
// StopBackOff is a fixed backoff policy that always returns backoff.Stop for
|
||||
// NextBackOff(), meaning that the operation should never be retried.
|
||||
type StopBackOff struct{}
|
||||
|
||||
func (b *StopBackOff) Reset() {}
|
||||
|
||||
func (b *StopBackOff) NextBackOff() time.Duration { return Stop }
|
||||
|
||||
// ConstantBackOff is a backoff policy that always returns the same backoff delay.
|
||||
// This is in contrast to an exponential backoff policy,
|
||||
// which returns a delay that grows longer as you call NextBackOff() over and over again.
|
||||
type ConstantBackOff struct {
|
||||
Interval time.Duration
|
||||
}
|
||||
|
||||
func (b *ConstantBackOff) Reset() {}
|
||||
func (b *ConstantBackOff) NextBackOff() time.Duration { return b.Interval }
|
||||
|
||||
func NewConstantBackOff(d time.Duration) *ConstantBackOff {
|
||||
return &ConstantBackOff{Interval: d}
|
||||
}
|
||||
27
vendor/github.com/cenkalti/backoff/backoff_test.go
generated
vendored
Normal file
27
vendor/github.com/cenkalti/backoff/backoff_test.go
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
package backoff
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestNextBackOffMillis(t *testing.T) {
|
||||
subtestNextBackOff(t, 0, new(ZeroBackOff))
|
||||
subtestNextBackOff(t, Stop, new(StopBackOff))
|
||||
}
|
||||
|
||||
func subtestNextBackOff(t *testing.T, expectedValue time.Duration, backOffPolicy BackOff) {
|
||||
for i := 0; i < 10; i++ {
|
||||
next := backOffPolicy.NextBackOff()
|
||||
if next != expectedValue {
|
||||
t.Errorf("got: %d expected: %d", next, expectedValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestConstantBackOff(t *testing.T) {
|
||||
backoff := NewConstantBackOff(time.Second)
|
||||
if backoff.NextBackOff() != time.Second {
|
||||
t.Error("invalid interval")
|
||||
}
|
||||
}
|
||||
60
vendor/github.com/cenkalti/backoff/context.go
generated
vendored
Normal file
60
vendor/github.com/cenkalti/backoff/context.go
generated
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
package backoff
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// BackOffContext is a backoff policy that stops retrying after the context
|
||||
// is canceled.
|
||||
type BackOffContext interface {
|
||||
BackOff
|
||||
Context() context.Context
|
||||
}
|
||||
|
||||
type backOffContext struct {
|
||||
BackOff
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
// WithContext returns a BackOffContext with context ctx
|
||||
//
|
||||
// ctx must not be nil
|
||||
func WithContext(b BackOff, ctx context.Context) BackOffContext {
|
||||
if ctx == nil {
|
||||
panic("nil context")
|
||||
}
|
||||
|
||||
if b, ok := b.(*backOffContext); ok {
|
||||
return &backOffContext{
|
||||
BackOff: b.BackOff,
|
||||
ctx: ctx,
|
||||
}
|
||||
}
|
||||
|
||||
return &backOffContext{
|
||||
BackOff: b,
|
||||
ctx: ctx,
|
||||
}
|
||||
}
|
||||
|
||||
func ensureContext(b BackOff) BackOffContext {
|
||||
if cb, ok := b.(BackOffContext); ok {
|
||||
return cb
|
||||
}
|
||||
return WithContext(b, context.Background())
|
||||
}
|
||||
|
||||
func (b *backOffContext) Context() context.Context {
|
||||
return b.ctx
|
||||
}
|
||||
|
||||
func (b *backOffContext) NextBackOff() time.Duration {
|
||||
select {
|
||||
case <-b.Context().Done():
|
||||
return Stop
|
||||
default:
|
||||
return b.BackOff.NextBackOff()
|
||||
}
|
||||
}
|
||||
26
vendor/github.com/cenkalti/backoff/context_test.go
generated
vendored
Normal file
26
vendor/github.com/cenkalti/backoff/context_test.go
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
package backoff
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func TestContext(t *testing.T) {
|
||||
b := NewConstantBackOff(time.Millisecond)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
cb := WithContext(b, ctx)
|
||||
|
||||
if cb.Context() != ctx {
|
||||
t.Error("invalid context")
|
||||
}
|
||||
|
||||
cancel()
|
||||
|
||||
if cb.NextBackOff() != Stop {
|
||||
t.Error("invalid next back off")
|
||||
}
|
||||
}
|
||||
73
vendor/github.com/cenkalti/backoff/example_test.go
generated
vendored
Normal file
73
vendor/github.com/cenkalti/backoff/example_test.go
generated
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
package backoff
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func ExampleRetry() {
|
||||
// An operation that may fail.
|
||||
operation := func() error {
|
||||
return nil // or an error
|
||||
}
|
||||
|
||||
err := Retry(operation, NewExponentialBackOff())
|
||||
if err != nil {
|
||||
// Handle error.
|
||||
return
|
||||
}
|
||||
|
||||
// Operation is successful.
|
||||
}
|
||||
|
||||
func ExampleRetryContext() {
|
||||
// A context
|
||||
ctx := context.Background()
|
||||
|
||||
// An operation that may fail.
|
||||
operation := func() error {
|
||||
return nil // or an error
|
||||
}
|
||||
|
||||
b := WithContext(NewExponentialBackOff(), ctx)
|
||||
|
||||
err := Retry(operation, b)
|
||||
if err != nil {
|
||||
// Handle error.
|
||||
return
|
||||
}
|
||||
|
||||
// Operation is successful.
|
||||
}
|
||||
|
||||
func ExampleTicker() {
|
||||
// An operation that may fail.
|
||||
operation := func() error {
|
||||
return nil // or an error
|
||||
}
|
||||
|
||||
ticker := NewTicker(NewExponentialBackOff())
|
||||
|
||||
var err error
|
||||
|
||||
// Ticks will continue to arrive when the previous operation is still running,
|
||||
// so operations that take a while to fail could run in quick succession.
|
||||
for _ = range ticker.C {
|
||||
if err = operation(); err != nil {
|
||||
log.Println(err, "will retry...")
|
||||
continue
|
||||
}
|
||||
|
||||
ticker.Stop()
|
||||
break
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
// Operation has failed.
|
||||
return
|
||||
}
|
||||
|
||||
// Operation is successful.
|
||||
return
|
||||
}
|
||||
156
vendor/github.com/cenkalti/backoff/exponential.go
generated
vendored
Normal file
156
vendor/github.com/cenkalti/backoff/exponential.go
generated
vendored
Normal file
@@ -0,0 +1,156 @@
|
||||
package backoff
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
/*
|
||||
ExponentialBackOff is a backoff implementation that increases the backoff
|
||||
period for each retry attempt using a randomization function that grows exponentially.
|
||||
|
||||
NextBackOff() is calculated using the following formula:
|
||||
|
||||
randomized interval =
|
||||
RetryInterval * (random value in range [1 - RandomizationFactor, 1 + RandomizationFactor])
|
||||
|
||||
In other words NextBackOff() will range between the randomization factor
|
||||
percentage below and above the retry interval.
|
||||
|
||||
For example, given the following parameters:
|
||||
|
||||
RetryInterval = 2
|
||||
RandomizationFactor = 0.5
|
||||
Multiplier = 2
|
||||
|
||||
the actual backoff period used in the next retry attempt will range between 1 and 3 seconds,
|
||||
multiplied by the exponential, that is, between 2 and 6 seconds.
|
||||
|
||||
Note: MaxInterval caps the RetryInterval and not the randomized interval.
|
||||
|
||||
If the time elapsed since an ExponentialBackOff instance is created goes past the
|
||||
MaxElapsedTime, then the method NextBackOff() starts returning backoff.Stop.
|
||||
|
||||
The elapsed time can be reset by calling Reset().
|
||||
|
||||
Example: Given the following default arguments, for 10 tries the sequence will be,
|
||||
and assuming we go over the MaxElapsedTime on the 10th try:
|
||||
|
||||
Request # RetryInterval (seconds) Randomized Interval (seconds)
|
||||
|
||||
1 0.5 [0.25, 0.75]
|
||||
2 0.75 [0.375, 1.125]
|
||||
3 1.125 [0.562, 1.687]
|
||||
4 1.687 [0.8435, 2.53]
|
||||
5 2.53 [1.265, 3.795]
|
||||
6 3.795 [1.897, 5.692]
|
||||
7 5.692 [2.846, 8.538]
|
||||
8 8.538 [4.269, 12.807]
|
||||
9 12.807 [6.403, 19.210]
|
||||
10 19.210 backoff.Stop
|
||||
|
||||
Note: Implementation is not thread-safe.
|
||||
*/
|
||||
type ExponentialBackOff struct {
|
||||
InitialInterval time.Duration
|
||||
RandomizationFactor float64
|
||||
Multiplier float64
|
||||
MaxInterval time.Duration
|
||||
// After MaxElapsedTime the ExponentialBackOff stops.
|
||||
// It never stops if MaxElapsedTime == 0.
|
||||
MaxElapsedTime time.Duration
|
||||
Clock Clock
|
||||
|
||||
currentInterval time.Duration
|
||||
startTime time.Time
|
||||
random *rand.Rand
|
||||
}
|
||||
|
||||
// Clock is an interface that returns current time for BackOff.
|
||||
type Clock interface {
|
||||
Now() time.Time
|
||||
}
|
||||
|
||||
// Default values for ExponentialBackOff.
|
||||
const (
|
||||
DefaultInitialInterval = 500 * time.Millisecond
|
||||
DefaultRandomizationFactor = 0.5
|
||||
DefaultMultiplier = 1.5
|
||||
DefaultMaxInterval = 60 * time.Second
|
||||
DefaultMaxElapsedTime = 15 * time.Minute
|
||||
)
|
||||
|
||||
// NewExponentialBackOff creates an instance of ExponentialBackOff using default values.
|
||||
func NewExponentialBackOff() *ExponentialBackOff {
|
||||
b := &ExponentialBackOff{
|
||||
InitialInterval: DefaultInitialInterval,
|
||||
RandomizationFactor: DefaultRandomizationFactor,
|
||||
Multiplier: DefaultMultiplier,
|
||||
MaxInterval: DefaultMaxInterval,
|
||||
MaxElapsedTime: DefaultMaxElapsedTime,
|
||||
Clock: SystemClock,
|
||||
random: rand.New(rand.NewSource(time.Now().UnixNano())),
|
||||
}
|
||||
b.Reset()
|
||||
return b
|
||||
}
|
||||
|
||||
type systemClock struct{}
|
||||
|
||||
func (t systemClock) Now() time.Time {
|
||||
return time.Now()
|
||||
}
|
||||
|
||||
// SystemClock implements Clock interface that uses time.Now().
|
||||
var SystemClock = systemClock{}
|
||||
|
||||
// Reset the interval back to the initial retry interval and restarts the timer.
|
||||
func (b *ExponentialBackOff) Reset() {
|
||||
b.currentInterval = b.InitialInterval
|
||||
b.startTime = b.Clock.Now()
|
||||
}
|
||||
|
||||
// NextBackOff calculates the next backoff interval using the formula:
|
||||
// Randomized interval = RetryInterval +/- (RandomizationFactor * RetryInterval)
|
||||
func (b *ExponentialBackOff) NextBackOff() time.Duration {
|
||||
// Make sure we have not gone over the maximum elapsed time.
|
||||
if b.MaxElapsedTime != 0 && b.GetElapsedTime() > b.MaxElapsedTime {
|
||||
return Stop
|
||||
}
|
||||
defer b.incrementCurrentInterval()
|
||||
if b.random == nil {
|
||||
b.random = rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
}
|
||||
return getRandomValueFromInterval(b.RandomizationFactor, b.random.Float64(), b.currentInterval)
|
||||
}
|
||||
|
||||
// GetElapsedTime returns the elapsed time since an ExponentialBackOff instance
|
||||
// is created and is reset when Reset() is called.
|
||||
//
|
||||
// The elapsed time is computed using time.Now().UnixNano().
|
||||
func (b *ExponentialBackOff) GetElapsedTime() time.Duration {
|
||||
return b.Clock.Now().Sub(b.startTime)
|
||||
}
|
||||
|
||||
// Increments the current interval by multiplying it with the multiplier.
|
||||
func (b *ExponentialBackOff) incrementCurrentInterval() {
|
||||
// Check for overflow, if overflow is detected set the current interval to the max interval.
|
||||
if float64(b.currentInterval) >= float64(b.MaxInterval)/b.Multiplier {
|
||||
b.currentInterval = b.MaxInterval
|
||||
} else {
|
||||
b.currentInterval = time.Duration(float64(b.currentInterval) * b.Multiplier)
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a random value from the following interval:
|
||||
// [randomizationFactor * currentInterval, randomizationFactor * currentInterval].
|
||||
func getRandomValueFromInterval(randomizationFactor, random float64, currentInterval time.Duration) time.Duration {
|
||||
var delta = randomizationFactor * float64(currentInterval)
|
||||
var minInterval = float64(currentInterval) - delta
|
||||
var maxInterval = float64(currentInterval) + delta
|
||||
|
||||
// Get a random value from the range [minInterval, maxInterval].
|
||||
// The formula used below has a +1 because if the minInterval is 1 and the maxInterval is 3 then
|
||||
// we want a 33% chance for selecting either 1, 2 or 3.
|
||||
return time.Duration(minInterval + (random * (maxInterval - minInterval + 1)))
|
||||
}
|
||||
108
vendor/github.com/cenkalti/backoff/exponential_test.go
generated
vendored
Normal file
108
vendor/github.com/cenkalti/backoff/exponential_test.go
generated
vendored
Normal file
@@ -0,0 +1,108 @@
|
||||
package backoff
|
||||
|
||||
import (
|
||||
"math"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestBackOff(t *testing.T) {
|
||||
var (
|
||||
testInitialInterval = 500 * time.Millisecond
|
||||
testRandomizationFactor = 0.1
|
||||
testMultiplier = 2.0
|
||||
testMaxInterval = 5 * time.Second
|
||||
testMaxElapsedTime = 15 * time.Minute
|
||||
)
|
||||
|
||||
exp := NewExponentialBackOff()
|
||||
exp.InitialInterval = testInitialInterval
|
||||
exp.RandomizationFactor = testRandomizationFactor
|
||||
exp.Multiplier = testMultiplier
|
||||
exp.MaxInterval = testMaxInterval
|
||||
exp.MaxElapsedTime = testMaxElapsedTime
|
||||
exp.Reset()
|
||||
|
||||
var expectedResults = []time.Duration{500, 1000, 2000, 4000, 5000, 5000, 5000, 5000, 5000, 5000}
|
||||
for i, d := range expectedResults {
|
||||
expectedResults[i] = d * time.Millisecond
|
||||
}
|
||||
|
||||
for _, expected := range expectedResults {
|
||||
assertEquals(t, expected, exp.currentInterval)
|
||||
// Assert that the next backoff falls in the expected range.
|
||||
var minInterval = expected - time.Duration(testRandomizationFactor*float64(expected))
|
||||
var maxInterval = expected + time.Duration(testRandomizationFactor*float64(expected))
|
||||
var actualInterval = exp.NextBackOff()
|
||||
if !(minInterval <= actualInterval && actualInterval <= maxInterval) {
|
||||
t.Error("error")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetRandomizedInterval(t *testing.T) {
|
||||
// 33% chance of being 1.
|
||||
assertEquals(t, 1, getRandomValueFromInterval(0.5, 0, 2))
|
||||
assertEquals(t, 1, getRandomValueFromInterval(0.5, 0.33, 2))
|
||||
// 33% chance of being 2.
|
||||
assertEquals(t, 2, getRandomValueFromInterval(0.5, 0.34, 2))
|
||||
assertEquals(t, 2, getRandomValueFromInterval(0.5, 0.66, 2))
|
||||
// 33% chance of being 3.
|
||||
assertEquals(t, 3, getRandomValueFromInterval(0.5, 0.67, 2))
|
||||
assertEquals(t, 3, getRandomValueFromInterval(0.5, 0.99, 2))
|
||||
}
|
||||
|
||||
type TestClock struct {
|
||||
i time.Duration
|
||||
start time.Time
|
||||
}
|
||||
|
||||
func (c *TestClock) Now() time.Time {
|
||||
t := c.start.Add(c.i)
|
||||
c.i += time.Second
|
||||
return t
|
||||
}
|
||||
|
||||
func TestGetElapsedTime(t *testing.T) {
|
||||
var exp = NewExponentialBackOff()
|
||||
exp.Clock = &TestClock{}
|
||||
exp.Reset()
|
||||
|
||||
var elapsedTime = exp.GetElapsedTime()
|
||||
if elapsedTime != time.Second {
|
||||
t.Errorf("elapsedTime=%d", elapsedTime)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMaxElapsedTime(t *testing.T) {
|
||||
var exp = NewExponentialBackOff()
|
||||
exp.Clock = &TestClock{start: time.Time{}.Add(10000 * time.Second)}
|
||||
// Change the currentElapsedTime to be 0 ensuring that the elapsed time will be greater
|
||||
// than the max elapsed time.
|
||||
exp.startTime = time.Time{}
|
||||
assertEquals(t, Stop, exp.NextBackOff())
|
||||
}
|
||||
|
||||
func TestBackOffOverflow(t *testing.T) {
|
||||
var (
|
||||
testInitialInterval time.Duration = math.MaxInt64 / 2
|
||||
testMaxInterval time.Duration = math.MaxInt64
|
||||
testMultiplier = 2.1
|
||||
)
|
||||
|
||||
exp := NewExponentialBackOff()
|
||||
exp.InitialInterval = testInitialInterval
|
||||
exp.Multiplier = testMultiplier
|
||||
exp.MaxInterval = testMaxInterval
|
||||
exp.Reset()
|
||||
|
||||
exp.NextBackOff()
|
||||
// Assert that when an overflow is possible the current varerval time.Duration is set to the max varerval time.Duration .
|
||||
assertEquals(t, testMaxInterval, exp.currentInterval)
|
||||
}
|
||||
|
||||
func assertEquals(t *testing.T, expected, value time.Duration) {
|
||||
if expected != value {
|
||||
t.Errorf("got: %d, expected: %d", value, expected)
|
||||
}
|
||||
}
|
||||
78
vendor/github.com/cenkalti/backoff/retry.go
generated
vendored
Normal file
78
vendor/github.com/cenkalti/backoff/retry.go
generated
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
package backoff
|
||||
|
||||
import "time"
|
||||
|
||||
// An Operation is executing by Retry() or RetryNotify().
|
||||
// The operation will be retried using a backoff policy if it returns an error.
|
||||
type Operation func() error
|
||||
|
||||
// Notify is a notify-on-error function. It receives an operation error and
|
||||
// backoff delay if the operation failed (with an error).
|
||||
//
|
||||
// NOTE that if the backoff policy stated to stop retrying,
|
||||
// the notify function isn't called.
|
||||
type Notify func(error, time.Duration)
|
||||
|
||||
// Retry the operation o until it does not return error or BackOff stops.
|
||||
// o is guaranteed to be run at least once.
|
||||
// It is the caller's responsibility to reset b after Retry returns.
|
||||
//
|
||||
// If o returns a *PermanentError, the operation is not retried, and the
|
||||
// wrapped error is returned.
|
||||
//
|
||||
// Retry sleeps the goroutine for the duration returned by BackOff after a
|
||||
// failed operation returns.
|
||||
func Retry(o Operation, b BackOff) error { return RetryNotify(o, b, nil) }
|
||||
|
||||
// RetryNotify calls notify function with the error and wait duration
|
||||
// for each failed attempt before sleep.
|
||||
func RetryNotify(operation Operation, b BackOff, notify Notify) error {
|
||||
var err error
|
||||
var next time.Duration
|
||||
|
||||
cb := ensureContext(b)
|
||||
|
||||
b.Reset()
|
||||
for {
|
||||
if err = operation(); err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if permanent, ok := err.(*PermanentError); ok {
|
||||
return permanent.Err
|
||||
}
|
||||
|
||||
if next = b.NextBackOff(); next == Stop {
|
||||
return err
|
||||
}
|
||||
|
||||
if notify != nil {
|
||||
notify(err, next)
|
||||
}
|
||||
|
||||
t := time.NewTimer(next)
|
||||
|
||||
select {
|
||||
case <-cb.Context().Done():
|
||||
t.Stop()
|
||||
return err
|
||||
case <-t.C:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PermanentError signals that the operation should not be retried.
|
||||
type PermanentError struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *PermanentError) Error() string {
|
||||
return e.Err.Error()
|
||||
}
|
||||
|
||||
// Permanent wraps the given err in a *PermanentError.
|
||||
func Permanent(err error) *PermanentError {
|
||||
return &PermanentError{
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
99
vendor/github.com/cenkalti/backoff/retry_test.go
generated
vendored
Normal file
99
vendor/github.com/cenkalti/backoff/retry_test.go
generated
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
package backoff
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func TestRetry(t *testing.T) {
|
||||
const successOn = 3
|
||||
var i = 0
|
||||
|
||||
// This function is successful on "successOn" calls.
|
||||
f := func() error {
|
||||
i++
|
||||
log.Printf("function is called %d. time\n", i)
|
||||
|
||||
if i == successOn {
|
||||
log.Println("OK")
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Println("error")
|
||||
return errors.New("error")
|
||||
}
|
||||
|
||||
err := Retry(f, NewExponentialBackOff())
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %s", err.Error())
|
||||
}
|
||||
if i != successOn {
|
||||
t.Errorf("invalid number of retries: %d", i)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRetryContext(t *testing.T) {
|
||||
var cancelOn = 3
|
||||
var i = 0
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
// This function cancels context on "cancelOn" calls.
|
||||
f := func() error {
|
||||
i++
|
||||
log.Printf("function is called %d. time\n", i)
|
||||
|
||||
// cancelling the context in the operation function is not a typical
|
||||
// use-case, however it allows to get predictable test results.
|
||||
if i == cancelOn {
|
||||
cancel()
|
||||
}
|
||||
|
||||
log.Println("error")
|
||||
return fmt.Errorf("error (%d)", i)
|
||||
}
|
||||
|
||||
err := Retry(f, WithContext(NewConstantBackOff(time.Millisecond), ctx))
|
||||
if err == nil {
|
||||
t.Errorf("error is unexpectedly nil")
|
||||
}
|
||||
if err.Error() != "error (3)" {
|
||||
t.Errorf("unexpected error: %s", err.Error())
|
||||
}
|
||||
if i != cancelOn {
|
||||
t.Errorf("invalid number of retries: %d", i)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRetryPermenent(t *testing.T) {
|
||||
const permanentOn = 3
|
||||
var i = 0
|
||||
|
||||
// This function fails permanently after permanentOn tries
|
||||
f := func() error {
|
||||
i++
|
||||
log.Printf("function is called %d. time\n", i)
|
||||
|
||||
if i == permanentOn {
|
||||
log.Println("permanent error")
|
||||
return Permanent(errors.New("permanent error"))
|
||||
}
|
||||
|
||||
log.Println("error")
|
||||
return errors.New("error")
|
||||
}
|
||||
|
||||
err := Retry(f, NewExponentialBackOff())
|
||||
if err == nil || err.Error() != "permanent error" {
|
||||
t.Errorf("unexpected error: %s", err)
|
||||
}
|
||||
if i != permanentOn {
|
||||
t.Errorf("invalid number of retries: %d", i)
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user