diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..16b0cb4
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,35 @@
+name: Release
+on:
+ create:
+ tags:
+ - v*
+
+jobs:
+ docker:
+ runs-on: ubuntu-latest
+ steps:
+ -
+ name: Checkout
+ uses: actions/checkout@v2
+ -
+ name: Set up QEMU
+ uses: docker/setup-qemu-action@v1
+ -
+ name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v1
+ -
+ name: Login to DockerHub
+ uses: docker/login-action@v1
+ with:
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
+ -
+ name: Build and push
+ uses: docker/build-push-action@v2
+ with:
+ context: .
+ push: true
+ build-args: ${{ steps.get_version.outputs.GIT_TAG }}
+ tags: |
+ alcounit/selenosis:latest
+ alcounit/selenosis:${{ steps.get_version.outputs.GIT_TAG }}
diff --git a/Dockerfile b/Dockerfile
index 8f4f6db..c452c0a 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,5 +1,7 @@
FROM golang:alpine AS builder
+ARG BUILD_VERSION
+
RUN apk add --quiet --no-cache build-base git
WORKDIR /src
@@ -13,7 +15,7 @@ RUN go mod download
ADD . .
RUN cd cmd/selenosis && \
- go install -ldflags="-linkmode external -extldflags '-static' -s -w"
+ go install -ldflags="-X main.buildVersion=$BUILD_VERSION -linkmode external -extldflags '-static' -s -w"
FROM scratch
diff --git a/README.md b/README.md
index 1b7312f..793dd9f 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,11 @@
+[![Docker Pulls](https://img.shields.io/docker/pulls/alcounit/selenosis)](https://hub.docker.com/r/alcounit/selenosis/tags?page=1&ordering=last_updated)
# selenosis
Scalable, stateless selenium hub for Kubernetes cluster.
## Overview
### Available flags
```
-[user@host]# ./selenosis --help
+[user@host]$ ./selenosis --help
Scallable, stateless selenium grid for Kubernetes cluster
Usage:
@@ -13,15 +14,17 @@ Usage:
Flags:
--port string port for selenosis (default ":4444")
--proxy-port string proxy continer port (default "4445")
- --browsers-config string browsers config (default "config/browsers.yaml")
+ --browsers-config string browsers config (default "./config/browsers.yaml")
--browser-limit int active sessions max limit (default 10)
- --namespace string kubernetes namespace (default "default")
- --service-name string kubernetes service name for browsers (default "selenosis")
+ --namespace string kubernetes namespace (default "selenosis")
+ --service-name string kubernetes service name for browsers (default "seleniferous")
--browser-wait-timeout duration time in seconds that a browser will be ready (default 30s)
--session-wait-timeout duration time in seconds that a session will be ready (default 1m0s)
- --session-iddle-timeout duration time in seconds that a session will iddle (default 5m0s)
+ --session-idle-timeout duration time in seconds that a session will idle (default 5m0s)
--session-retry-count int session retry count (default 3)
- --graceful-shutdown-timeout duration time in seconds gracefull shutdown timeout (default 5m0s)
+ --graceful-shutdown-timeout duration time in seconds gracefull shutdown timeout (default 30s)
+ --image-pull-secret-name string secret name to private registry
+ --proxy-image string in case you use private registry replace with image from private registry (default "alcounit/seleniferous:latest")
-h, --help help for selenosis
```
@@ -51,10 +54,10 @@ Basic configuration be like (all fields in this example are mandatory):
"path": "/",
"versions": {
"85.0": {
- "image": "selenoid/vnc:chrome:85.0"
+ "image": "selenoid/vnc:chrome_85.0"
},
"86.0": {
- "image": "selenoid/vnc:chrome:86.0"
+ "image": "selenoid/vnc:chrome_86.0"
}
}
},
@@ -92,9 +95,9 @@ chrome:
path: "/"
versions:
'85.0':
- image: selenoid/vnc:chrome:85.0
+ image: selenoid/vnc:chrome_85.0
'86.0':
- image: selenoid/vnc:chrome:86.0
+ image: selenoid/vnc:chrome_86.0
firefox:
defaultVersion: "82.0"
path: "/wd/hub"
@@ -112,8 +115,6 @@ opera:
'71.0':
image: selenoid/vnc:opera_71.0
```
-
-
Browser name and browser version are taken from Selenium desired capabilities.
Each browser can have default spec/annotations/labels, they will merged to all browsers listed in the versions section.
@@ -177,10 +178,10 @@ Each browser can have default spec/annotations/labels, they will merged t
},
"versions": {
"85.0": {
- "image": "selenoid/vnc:chrome:85.0"
+ "image": "selenoid/vnc:chrome_85.0"
},
"86.0": {
- "image": "selenoid/vnc:chrome:86.0"
+ "image": "selenoid/vnc:chrome_86.0"
}
}
}
@@ -225,9 +226,9 @@ chrome:
value: 'true'
versions:
'85.0':
- image: selenoid/vnc:chrome:85.0
+ image: selenoid/vnc:chrome_85.0
'86.0':
- image: selenoid/vnc:chrome:86.0
+ image: selenoid/vnc:chrome_86.0
```
You can override default browser spec/annotation/labels by providing individual spec/annotation/labels to browser version
``` json
@@ -289,7 +290,7 @@ You can override default browser spec/annotation/labels by providing indi
},
"versions": {
"85.0": {
- "image": "selenoid/vnc:chrome:85.0",
+ "image": "selenoid/vnc:chrome_85.0",
"spec": {
"resources": {
"requests": {
@@ -304,7 +305,7 @@ You can override default browser spec/annotation/labels by providing indi
}
},
"86.0": {
- "image": "selenoid/vnc:chrome:86.0",
+ "image": "selenoid/vnc:chrome_86.0",
"spec": {
"hostAliases": [
{
@@ -364,7 +365,7 @@ chrome:
value: 'true'
versions:
'85.0':
- image: selenoid/vnc:chrome:85.0
+ image: selenoid/vnc:chrome_85.0
spec:
resources:
requests:
@@ -374,7 +375,7 @@ chrome:
memory: 1500Gi
cpu: '1'
'86.0':
- image: selenoid/vnc:chrome:86.0
+ image: selenoid/vnc:chrome_86.0
spec:
hostAliases:
- ip: 127.0.0.1
@@ -394,8 +395,6 @@ Files and steps required for selenosis deployment available in [selenosis-deploy
DesiredCapabilities capabilities = new DesiredCapabilities();
capabilities.setBrowserName("chrome");
capabilities.setVersion("85.0");
-capabilities.setCapability("enableVNC", true);
-capabilities.setCapability("enableVideo", false);
RemoteWebDriver driver = new RemoteWebDriver(
URI.create("http://:/wd/hub").toURL(),
@@ -408,8 +407,6 @@ from selenium import webdriver
capabilities = {
"browserName": "chrome",
"version": "85.0",
- "enableVNC": True,
- "enableVideo": False
}
driver = webdriver.Remote(
@@ -417,6 +414,59 @@ driver = webdriver.Remote(
desired_capabilities=capabilities)
```
+ Note: you can omit browser version in your desired capabilities, make sure you set defaultVersion property in the config file.
+
+
+## Browser pods are deleted right after start
+Depends on you cluster version in some cases you can face with issue when some browser pods are deleted right after their start and selenosis log will contains lines like this:
+```log
+time="2020-12-21T10:28:20Z" level=error msg="session failed: Post \"http://selenoid-vnc-chrome-87-0-af3177a0-5052-45be-b4e4-9462146e4633.seleniferous:4445/wd/hub/session\": dial tcp: lookup selenoid-vnc-chrome-87-0-af3177a0-5052-45be-b4e4-9462146e4633.seleniferous on 10.96.0.10:53: no such host" request="POST /wd/hub/session" request_id=fa150040-86c1-4224-9e5c-21416b1d9f5c time_elapsed=5.73s
+```
+To fix this issue do the following:
+```bash
+kubectl edit cm coredns -n kube-system
+```
+add following record to your coredns config
+```config
+ selenosis.svc.cluster.local:53 {
+ errors
+ kubernetes cluster.local {
+ namespaces selenosis
+ }
+ }
+```
+this option will turn off dns caching for selenosis namespace, resulting config update should be as following:
+```yaml
+apiVersion: v1
+data:
+ Corefile: |
+ selenosis.svc.cluster.local:53 {
+ errors
+ kubernetes cluster.local {
+ namespaces selenosis
+ }
+ }
+ .:53 {
+ errors
+ health
+ ready
+ kubernetes cluster.local in-addr.arpa ip6.arpa {
+ pods insecure
+ fallthrough in-addr.arpa ip6.arpa
+ }
+ prometheus :9153
+ forward . /etc/resolv.conf
+ cache 30
+ loop
+ reload
+ loadbalance
+ import custom/*.override
+ }
+ import custom/*.server
+kind: ConfigMap
+...
+```
+
## Features
### Scalability
By default selenosis starts with 2 replica sets. To change it, edit selenosis deployment file: 03-selenosis.yaml
@@ -432,6 +482,12 @@ spec:
selector:
...
```
+
+by using kubectl
+
+```bash
+kubectl scale deployment selenosis -n selenosis --replicas=3
+```
### Stateless
Selenosis doesn't store any session info. All connections to the browsers are automatically assigned via headless service.
@@ -440,4 +496,10 @@ Selenosis doesn't store any session info. All connections to the browsers are au
Selenosis supports hot config reload, to do so update you configMap
```bash
kubectl edit configmap -n selenosis selenosis-config -o yaml
-```
\ No newline at end of file
+```
+
+### UI for debug
+Selenosis itself doesn't have ui. If you need such functionality you can use [selenoid-ui](https://github.com/aerokube/selenoid-ui) with special [adapter container](https://github.com/alcounit/adaptee).
+Deployment steps and minifests you can find in [selenosis-deploy](https://github.com/alcounit/selenosis-deploy) repository.
+
+Currently this project is under development and can be unstable, in case of any bugs or ideas please report
diff --git a/cmd/selenosis/main.go b/cmd/selenosis/main.go
index 6030312..7f84dc5 100644
--- a/cmd/selenosis/main.go
+++ b/cmd/selenosis/main.go
@@ -21,6 +21,8 @@ import (
"golang.org/x/net/websocket"
)
+var buildVersion = "HEAD"
+
//Command ...
func command() *cobra.Command {
@@ -30,11 +32,13 @@ func command() *cobra.Command {
proxyPort string
namespace string
service string
+ imagePullSecretName string
+ proxyImage string
sessionRetryCount int
limit int
browserWaitTimeout time.Duration
sessionWaitTimeout time.Duration
- sessionIddleTimeout time.Duration
+ sessionIdleTimeout time.Duration
shutdownTimeout time.Duration
)
@@ -44,7 +48,7 @@ func command() *cobra.Command {
Run: func(cmd *cobra.Command, args []string) {
logger := logrus.New()
- logger.Info("starting selenosis")
+ logger.Infof("starting selenosis %s", buildVersion)
browsers, err := config.NewBrowsersConfig(cfgFile)
if err != nil {
@@ -58,11 +62,13 @@ func command() *cobra.Command {
logger.Info("config watcher started")
client, err := platform.NewClient(platform.ClientConfig{
- Namespace: namespace,
- Service: service,
- ReadinessTimeout: browserWaitTimeout,
- IddleTimeout: sessionIddleTimeout,
- ServicePort: proxyPort,
+ Namespace: namespace,
+ Service: service,
+ ReadinessTimeout: browserWaitTimeout,
+ IdleTimeout: sessionIdleTimeout,
+ ServicePort: proxyPort,
+ ImagePullSecretName: imagePullSecretName,
+ ProxyImage: proxyImage,
})
if err != nil {
@@ -74,19 +80,20 @@ func command() *cobra.Command {
hostname, _ := os.Hostname()
app := selenosis.New(logger, client, browsers, selenosis.Configuration{
- SelenosisHost: hostname,
- ServiceName: service,
- SidecarPort: proxyPort,
- SessionLimit: limit,
- SessionRetryCount: sessionRetryCount,
- BrowserWaitTimeout: browserWaitTimeout,
- SessionIddleTimeout: sessionIddleTimeout,
+ SelenosisHost: hostname,
+ ServiceName: service,
+ SidecarPort: proxyPort,
+ SessionLimit: limit,
+ SessionRetryCount: sessionRetryCount,
+ BrowserWaitTimeout: browserWaitTimeout,
+ SessionIdleTimeout: sessionIdleTimeout,
+ BuildVersion: buildVersion,
})
router := mux.NewRouter()
- router.HandleFunc("/wd/hub/session", app.HandleSession).Methods(http.MethodPost)
+ router.HandleFunc("/wd/hub/session", app.CheckLimit(app.HandleSession)).Methods(http.MethodPost)
router.PathPrefix("/wd/hub/session/{sessionId}").HandlerFunc(app.HandleProxy)
- router.HandleFunc("/wd/hub/status", app.HadleHubStatus).Methods(http.MethodGet)
+ router.HandleFunc("/wd/hub/status", app.HandleHubStatus).Methods(http.MethodGet)
router.PathPrefix("/vnc/{sessionId}").Handler(websocket.Handler(app.HandleVNC()))
router.PathPrefix("/logs/{sessionId}").Handler(websocket.Handler(app.HandleLogs()))
router.PathPrefix("/devtools/{sessionId}").HandlerFunc(app.HandleReverseProxy)
@@ -130,13 +137,15 @@ func command() *cobra.Command {
cmd.Flags().StringVar(&proxyPort, "proxy-port", "4445", "proxy continer port")
cmd.Flags().StringVar(&cfgFile, "browsers-config", "./config/browsers.yaml", "browsers config")
cmd.Flags().IntVar(&limit, "browser-limit", 10, "active sessions max limit")
- cmd.Flags().StringVar(&namespace, "namespace", "default", "kubernetes namespace")
- cmd.Flags().StringVar(&service, "service-name", "selenosis", "kubernetes service name for browsers")
+ cmd.Flags().StringVar(&namespace, "namespace", "selenosis", "kubernetes namespace")
+ cmd.Flags().StringVar(&service, "service-name", "seleniferous", "kubernetes service name for browsers")
cmd.Flags().DurationVar(&browserWaitTimeout, "browser-wait-timeout", 30*time.Second, "time in seconds that a browser will be ready")
cmd.Flags().DurationVar(&sessionWaitTimeout, "session-wait-timeout", 60*time.Second, "time in seconds that a session will be ready")
- cmd.Flags().DurationVar(&sessionIddleTimeout, "session-iddle-timeout", 5*time.Minute, "time in seconds that a session will iddle")
+ cmd.Flags().DurationVar(&sessionIdleTimeout, "session-idle-timeout", 5*time.Minute, "time in seconds that a session will idle")
cmd.Flags().IntVar(&sessionRetryCount, "session-retry-count", 3, "session retry count")
cmd.Flags().DurationVar(&shutdownTimeout, "graceful-shutdown-timeout", 30*time.Second, "time in seconds gracefull shutdown timeout")
+ cmd.Flags().StringVar(&imagePullSecretName, "image-pull-secret-name", "", "secret name to private registry")
+ cmd.Flags().StringVar(&proxyImage, "proxy-image", "alcounit/seleniferous:latest", "in case you use private registry replace with image from private registry")
cmd.Flags().SortFlags = false
return cmd
diff --git a/go.mod b/go.mod
index 231b7a2..61e98e5 100644
--- a/go.mod
+++ b/go.mod
@@ -15,5 +15,6 @@ require (
k8s.io/api v0.19.3
k8s.io/apimachinery v0.19.3
k8s.io/client-go v0.19.3
+ k8s.io/kubernetes v0.19.3
k8s.io/utils v0.0.0-20201027101359-01387209bb0d
)
diff --git a/go.sum b/go.sum
index 0c60b43..2171596 100644
--- a/go.sum
+++ b/go.sum
@@ -32,7 +32,6 @@ github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdko
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
-github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
@@ -46,9 +45,7 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
-github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
-github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
@@ -64,7 +61,6 @@ github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkg
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
-github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
@@ -89,15 +85,15 @@ github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
-github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7 h1:5ZkaAPbicIKTF2I64qf5Fh8Aa83Q/dnOafMYV0OMwjA=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
-github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -115,6 +111,7 @@ github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g=
@@ -129,15 +126,11 @@ github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
-github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
-github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/googleapis/gnostic v0.4.1 h1:DLJCy1n/vrD4HPjOvYcT8aYQXpPIzoRZONaYwyycI+I=
github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg=
-github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
-github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
@@ -157,6 +150,7 @@ github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
@@ -172,7 +166,6 @@ github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NH
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
-github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
@@ -183,13 +176,13 @@ github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvW
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
-github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
-github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
@@ -224,6 +217,7 @@ github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/9
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -245,8 +239,6 @@ github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
-github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
-github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
@@ -256,8 +248,6 @@ github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasO
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
-github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8=
-github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4=
github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
@@ -265,7 +255,6 @@ github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzu
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
-github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -276,9 +265,7 @@ github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
-github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
-github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
@@ -288,13 +275,11 @@ go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
-golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -318,7 +303,6 @@ golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCc
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
-golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -332,20 +316,15 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
-golang.org/x/net v0.0.0-20200930145003-4acb6c075d10 h1:YfxMZzv3PjGonQYNUaeU2+DhAdqOxerQ30JFB6WgAXo=
-golang.org/x/net v0.0.0-20200930145003-4acb6c075d10/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201029055024-942e2f445f3c h1:rpcgRPA7OvNEOdprt2Wx8/Re2cBTd8NPo/lvo3AyMqk=
golang.org/x/net v0.0.0-20201029055024-942e2f445f3c/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6 h1:pE8b58s1HRDMi8RDc79m0HISf9D4TzseP40cEA6IGfs=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -355,7 +334,6 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -363,33 +341,27 @@ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4 h1:5/PjkGUjvEU5Gl6BxmvKRPpqo2uNMv4rcHBMwzk/st8=
golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -417,6 +389,7 @@ golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
@@ -428,6 +401,7 @@ google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9Ywl
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
+google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
@@ -442,7 +416,6 @@ google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvx
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
-google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
@@ -460,6 +433,7 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
@@ -477,44 +451,30 @@ gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
-k8s.io/api v0.18.1 h1:pnHr0LH69kvL29eHldoepUDKTuiOejNZI2A1gaxve3Q=
-k8s.io/api v0.18.1/go.mod h1:3My4jorQWzSs5a+l7Ge6JBbIxChLnY8HnuT58ZWolss=
k8s.io/api v0.19.3 h1:GN6ntFnv44Vptj/b+OnMW7FmzkpDoIDLZRvKX3XH9aU=
k8s.io/api v0.19.3/go.mod h1:VF+5FT1B74Pw3KxMdKyinLo+zynBaMBiAfGMuldcNDs=
-k8s.io/apimachinery v0.18.1/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA=
-k8s.io/apimachinery v0.19.2 h1:5Gy9vQpAGTKHPVOh5c4plE274X8D/6cuEiTO2zve7tc=
-k8s.io/apimachinery v0.19.2/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA=
k8s.io/apimachinery v0.19.3 h1:bpIQXlKjB4cB/oNpnNnV+BybGPR7iP5oYpsOTEJ4hgc=
k8s.io/apimachinery v0.19.3/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA=
-k8s.io/client-go v0.18.1 h1:2+fnu4LwKJjZVOwijkm1UqZG9aQoFsKEpipOzdfcTD8=
-k8s.io/client-go v0.18.1/go.mod h1:iCikYRiXOj/yRRFE/aWqrpPtDt4P2JVWhtHkmESTcfY=
k8s.io/client-go v0.19.3 h1:ctqR1nQ52NUs6LpI0w+a5U+xjYwflFwA13OJKcicMxg=
k8s.io/client-go v0.19.3/go.mod h1:+eEMktZM+MG0KO+PTkci8xnbCZHvj9TqR6Q1XDUIJOM=
-k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
-k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
-k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
-k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8=
-k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
k8s.io/klog/v2 v2.2.0 h1:XRvcwJozkgZ1UQJmfMGpvRthQHOvihEhYtDfAaxMz/A=
k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
-k8s.io/kube-openapi v0.0.0-20200121204235-bf4fb3bd569c/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E=
k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o=
-k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89 h1:d4vVOjXm687F1iLSP2q3lyPPuyvTUt3aVoBpi2DqRsU=
-k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
+k8s.io/kubernetes v0.19.3 h1:x6Q6M9nNBm9thoKj+PJr3HDPfHlz7FR35Ri61xpNfgg=
+k8s.io/kubernetes v0.19.3/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk=
k8s.io/utils v0.0.0-20200729134348-d5654de09c73/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
k8s.io/utils v0.0.0-20201027101359-01387209bb0d h1:1qqs/6lQQGCeZhCu0tO7La4lAazDXic6BiCmpjWcWUo=
k8s.io/utils v0.0.0-20201027101359-01387209bb0d/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
-sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=
-sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=
sigs.k8s.io/structured-merge-diff/v4 v4.0.1 h1:YXTMot5Qz/X1iBRJhAt+vI+HVttY0WkSqqhKxQ0xVbA=
sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
diff --git a/handlers.go b/handlers.go
index 070dde7..6c68b7c 100644
--- a/handlers.go
+++ b/handlers.go
@@ -10,6 +10,7 @@ import (
"net"
"net/http"
"net/http/httputil"
+ "net/url"
"regexp"
"strings"
"time"
@@ -32,6 +33,26 @@ var (
}
)
+//CheckLimit ...
+func (app *App) CheckLimit(next http.HandlerFunc) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ logger := app.logger.WithFields(logrus.Fields{
+ "request_id": uuid.New(),
+ "request": fmt.Sprintf("%s %s", r.Method, r.URL.Path),
+ })
+
+ total := app.stats.Len()
+
+ if total >= app.sessionLimit {
+ logger.Warnf("active session limit reached: total %d, limit %d", total, app.sessionLimit)
+ tools.JSONError(w, "session limit reached", http.StatusInternalServerError)
+ return
+ }
+
+ next.ServeHTTP(w, r)
+ }
+}
+
//HandleSession ...
func (app *App) HandleSession(w http.ResponseWriter, r *http.Request) {
start := time.Now()
@@ -39,20 +60,6 @@ func (app *App) HandleSession(w http.ResponseWriter, r *http.Request) {
"request_id": uuid.New(),
"request": fmt.Sprintf("%s %s", r.Method, r.URL.Path),
})
-
- l, err := app.client.List()
- if err != nil {
- logger.Errorf("failed to get active session list: %v", err)
- tools.JSONError(w, "Failed to get browsers list", http.StatusInternalServerError)
- return
- }
-
- if len(l) >= app.sessionLimit {
- logger.Warnf("active session limit reached: total %d, limit %d", len(l), app.sessionLimit)
- tools.JSONError(w, "session limit reached", http.StatusInternalServerError)
- return
- }
-
logger.WithField("time_elapsed", tools.TimeElapsed(start)).Info("session")
body, err := ioutil.ReadAll(r.Body)
@@ -197,9 +204,14 @@ func (app *App) HandleSession(w http.ResponseWriter, r *http.Request) {
//HandleProxy ...
func (app *App) HandleProxy(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
- sessionID := vars["sessionId"]
- host := tools.BuildHostPort(sessionID, app.serviceName, app.sidecarPort)
+ sessionID, ok := vars["sessionId"]
+ if !ok {
+ app.logger.Error("session id not found")
+ tools.JSONError(w, "session id not found", http.StatusBadRequest)
+ return
+ }
+ host := tools.BuildHostPort(sessionID, app.serviceName, app.sidecarPort)
logger := app.logger.WithFields(logrus.Fields{
"request_id": uuid.New(),
"session_id": sessionID,
@@ -222,8 +234,8 @@ func (app *App) HandleProxy(w http.ResponseWriter, r *http.Request) {
}
-//HadleHubStatus ...
-func (app *App) HadleHubStatus(w http.ResponseWriter, r *http.Request) {
+//HandleHubStatus ...
+func (app *App) HandleHubStatus(w http.ResponseWriter, r *http.Request) {
logger := app.logger.WithFields(logrus.Fields{
"request_id": uuid.New(),
"request": fmt.Sprintf("%s %s", r.Method, r.URL.Path),
@@ -231,27 +243,30 @@ func (app *App) HadleHubStatus(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
- l, err := app.client.List()
- if err != nil {
- logger.Errorf("hub status: %v", err)
- tools.JSONError(w, "Failed to get browsers list", http.StatusInternalServerError)
- }
+ active, pending := getSessionStats(app.stats.List())
+ total := len(active) + len(pending)
json.NewEncoder(w).Encode(
map[string]interface{}{
"value": map[string]interface{}{
"message": "selenosis up and running",
- "ready": len(l),
+ "ready": total,
},
})
- logger.WithField("active_sessions", len(l)).Infof("hub status")
+ logger.WithField("active_sessions", total).Infof("hub status")
}
//HandleReverseProxy ...
func (app *App) HandleReverseProxy(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
- sessionID := vars["sessionId"]
+ sessionID, ok := vars["sessionId"]
+ if !ok {
+ app.logger.Error("session id not found")
+ tools.JSONError(w, "session id not found", http.StatusBadRequest)
+ return
+ }
+
fragments := strings.Split(r.URL.Path, "/")
logger := app.logger.WithFields(logrus.Fields{
"request_id": uuid.New(),
@@ -279,7 +294,11 @@ func (app *App) HandleVNC() websocket.Handler {
defer wsconn.Close()
vars := mux.Vars(wsconn.Request())
- sessionID := vars["sessionId"]
+ sessionID, ok := vars["sessionId"]
+ if !ok {
+ app.logger.Error("session id not found")
+ return
+ }
logger := app.logger.WithFields(logrus.Fields{
"request_id": uuid.New(),
@@ -287,6 +306,11 @@ func (app *App) HandleVNC() websocket.Handler {
"request": fmt.Sprintf("%s %s", wsconn.Request().Method, wsconn.Request().URL.Path),
})
+ if !statusOk(sessionID, app.serviceName, app.sidecarPort) {
+ logger.Errorf("container host is unreachable")
+ return
+ }
+
host := tools.BuildHostPort(sessionID, app.serviceName, "5900")
logger.Infof("vnc request: %s", host)
@@ -301,10 +325,10 @@ func (app *App) HandleVNC() websocket.Handler {
go func() {
io.Copy(wsconn, conn)
wsconn.Close()
- logger.Errorf("vnc connection closed")
+ logger.Warnf("vnc connection closed")
}()
io.Copy(conn, wsconn)
- logger.Errorf("vnc client disconnected")
+ logger.Infof("vnc client disconnected")
}
}
@@ -314,7 +338,11 @@ func (app *App) HandleLogs() websocket.Handler {
defer wsconn.Close()
vars := mux.Vars(wsconn.Request())
- sessionID := vars["sessionId"]
+ sessionID, ok := vars["sessionId"]
+ if !ok {
+ app.logger.Error("session id not found")
+ return
+ }
logger := app.logger.WithFields(logrus.Fields{
"request_id": uuid.New(),
@@ -322,22 +350,28 @@ func (app *App) HandleLogs() websocket.Handler {
"request": fmt.Sprintf("%s %s", wsconn.Request().Method, wsconn.Request().URL.Path),
})
+ if !statusOk(sessionID, app.serviceName, app.sidecarPort) {
+ logger.Errorf("container host is unreachable")
+ return
+ }
+
logger.Infof("stream logs request: %s", fmt.Sprintf("%s.%s", sessionID, app.serviceName))
- r, err := app.client.Logs(wsconn.Request().Context(), sessionID)
+ conn, err := app.client.Logs(wsconn.Request().Context(), sessionID)
if err != nil {
logger.Errorf("stream logs error: %v", err)
+ return
}
- defer r.Close()
+ defer conn.Close()
wsconn.PayloadType = websocket.BinaryFrame
go func() {
- io.Copy(wsconn, r)
+ io.Copy(wsconn, conn)
wsconn.Close()
- logger.Errorf("stream logs connection closed")
+ logger.Warnf("stream logs connection closed")
}()
- io.Copy(wsconn, r)
- logger.Errorf("stream logs disconnected")
+ io.Copy(wsconn, conn)
+ logger.Infof("stream logs disconnected")
}
}
@@ -347,39 +381,31 @@ func (app *App) HandleStatus(w http.ResponseWriter, r *http.Request) {
type Status struct {
Total int `json:"total"`
+ Active int `json:"active"`
+ Pending int `json:"pending"`
Browsers map[string][]string `json:"config,omitempty"`
- Sessions []*platform.Service `json:"sessions,omitempty"`
+ Sessions []platform.Service `json:"sessions,omitempty"`
}
type Response struct {
Status int `json:"status"`
+ Version string `json:"version"`
Error string `json:"err,omitempty"`
Selenosis Status `json:"selenosis,omitempty"`
}
- sessions, err := app.client.List()
- if err != nil {
- app.logger.Errorf("hub status: %v", err)
- json.NewEncoder(w).Encode(
- Response{
- Status: http.StatusInternalServerError,
- Error: fmt.Sprintf("%v", err),
- Selenosis: Status{
- Total: app.sessionLimit,
- Browsers: app.browsers.GetBrowserVersions(),
- },
- },
- )
- return
- }
+ active, pending := getSessionStats(app.stats.List())
json.NewEncoder(w).Encode(
Response{
- Status: http.StatusOK,
+ Status: http.StatusOK,
+ Version: app.buildVersion,
Selenosis: Status{
Total: app.sessionLimit,
+ Active: len(active),
+ Pending: len(pending),
Browsers: app.browsers.GetBrowserVersions(),
- Sessions: sessions,
+ Sessions: active,
},
},
)
@@ -393,3 +419,33 @@ func parseImage(image string) (container string) {
}
return pref.ReplaceAllString(image, "-")
}
+
+func statusOk(session, service, port string) bool {
+ u := &url.URL{
+ Scheme: "http",
+ Host: tools.BuildHostPort(session, service, port),
+ Path: "/status",
+ }
+
+ resp, err := http.Get(u.String())
+ if err != nil || resp.StatusCode != http.StatusOK {
+ return false
+ }
+
+ return true
+}
+
+func getSessionStats(sessions []platform.Service) (active []platform.Service, pending []platform.Service) {
+ active = make([]platform.Service, 0)
+ pending = make([]platform.Service, 0)
+
+ for _, s := range sessions {
+ switch s.Status {
+ case platform.Running:
+ active = append(active, s)
+ case platform.Pending:
+ pending = append(pending, s)
+ }
+ }
+ return
+}
diff --git a/handlers_test.go b/handlers_test.go
index 14521dc..6d090c2 100644
--- a/handlers_test.go
+++ b/handlers_test.go
@@ -58,11 +58,6 @@ func TestNewSessionRequestErrors(t *testing.T) {
respCode: http.StatusBadRequest,
respBody: `{"code":400,"value":{"message":"unknown browser name amigo"}}`,
},
- "Verify new session call with unknown browser version in request": {
- body: bytes.NewReader([]byte(`{"capabilities":{"firstMatch":[{"browserName":"chrome", "browserVersion":"0.00"}]}}`)),
- respCode: http.StatusBadRequest,
- respBody: `{"code":400,"value":{"message":"unknown browser version 0.00"}}`,
- },
}
for name, test := range tests {
@@ -535,12 +530,12 @@ func initApp(p *PlatformMock) *App {
logger := &logrus.Logger{}
client := NewPlatformMock(p)
conf := Configuration{
- SelenosisHost: "hostname",
- ServiceName: "selenosis",
- SidecarPort: "4445",
- BrowserWaitTimeout: 300 * time.Millisecond,
- SessionIddleTimeout: 600 * time.Millisecond,
- SessionRetryCount: 2,
+ SelenosisHost: "hostname",
+ ServiceName: "selenosis",
+ SidecarPort: "4445",
+ BrowserWaitTimeout: 300 * time.Millisecond,
+ SessionIdleTimeout: 600 * time.Millisecond,
+ SessionRetryCount: 2,
}
browsersConfig, _ := config.NewBrowsersConfig("config/browsers.yaml")
@@ -574,6 +569,11 @@ func (p *PlatformMock) List() ([]*platform.Service, error) {
return nil, nil
}
+func (p *PlatformMock) Watch() <-chan platform.Event {
+ ch := make(chan platform.Event)
+ return ch
+}
+
func (p *PlatformMock) Logs(ctx context.Context, name string) (io.ReadCloser, error) {
return nil, nil
}
diff --git a/platform/kubernetes.go b/platform/kubernetes.go
index a5491f9..9417d19 100644
--- a/platform/kubernetes.go
+++ b/platform/kubernetes.go
@@ -2,6 +2,7 @@ package platform
import (
"context"
+ "errors"
"fmt"
"io"
"net"
@@ -12,11 +13,13 @@ import (
apiv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/util/intstr"
+ "k8s.io/apimachinery/pkg/watch"
+ "k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes"
- v1 "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/client-go/rest"
+ "k8s.io/client-go/tools/cache"
+ "k8s.io/kubernetes/pkg/fields"
"k8s.io/utils/pointer"
)
@@ -44,21 +47,25 @@ var (
//ClientConfig ...
type ClientConfig struct {
- Namespace string
- Service string
- ReadinessTimeout time.Duration
- IddleTimeout time.Duration
- ServicePort string
+ Namespace string
+ Service string
+ ServicePort string
+ ImagePullSecretName string
+ ProxyImage string
+ ReadinessTimeout time.Duration
+ IdleTimeout time.Duration
}
//Client ...
type Client struct {
- ns string
- svc string
- svcPort intstr.IntOrString
- readinessTimeout time.Duration
- iddleTimeout time.Duration
- clientset v1.CoreV1Interface
+ ns string
+ svc string
+ svcPort intstr.IntOrString
+ imagePullSecretName string
+ proxyImage string
+ readinessTimeout time.Duration
+ idleTimeout time.Duration
+ clientset *kubernetes.Clientset
}
//NewClient ...
@@ -75,12 +82,14 @@ func NewClient(c ClientConfig) (Platform, error) {
}
return &Client{
- ns: c.Namespace,
- clientset: clientset.CoreV1(),
- svc: c.Service,
- svcPort: intstr.FromString(c.ServicePort),
- readinessTimeout: c.ReadinessTimeout,
- iddleTimeout: c.IddleTimeout,
+ ns: c.Namespace,
+ clientset: clientset,
+ svc: c.Service,
+ svcPort: intstr.FromString(c.ServicePort),
+ imagePullSecretName: c.ImagePullSecretName,
+ proxyImage: c.ProxyImage,
+ readinessTimeout: c.ReadinessTimeout,
+ idleTimeout: c.IdleTimeout,
}, nil
}
@@ -100,7 +109,7 @@ func NewDefaultClient(namespace string) (Platform, error) {
return &Client{
ns: namespace,
- clientset: clientset.CoreV1(),
+ clientset: clientset,
}, nil
}
@@ -116,18 +125,17 @@ func (cl *Client) Create(layout *ServiceSpec) (*Service, error) {
defaults.session: layout.SessionID,
}
- envVar := func(name, value string) (i int, b bool) {
+ envVar := func(name string) (i int, b bool) {
for i, slice := range layout.Template.Spec.EnvVars {
if slice.Name == name {
- slice.Value = value
return i, true
}
}
return -1, false
}
+ i, b := envVar(defaults.screenResolution)
if layout.RequestedCapabilities.ScreenResolution != "" {
- i, b := envVar(defaults.screenResolution, layout.RequestedCapabilities.ScreenResolution)
if !b {
layout.Template.Spec.EnvVars = append(layout.Template.Spec.EnvVars,
apiv1.EnvVar{Name: defaults.screenResolution,
@@ -136,27 +144,34 @@ func (cl *Client) Create(layout *ServiceSpec) (*Service, error) {
layout.Template.Spec.EnvVars[i] = apiv1.EnvVar{Name: defaults.screenResolution, Value: layout.RequestedCapabilities.ScreenResolution}
}
labels[defaults.screenResolution] = layout.RequestedCapabilities.ScreenResolution
+ } else {
+ if b {
+ labels[defaults.screenResolution] = layout.Template.Spec.EnvVars[i].Value
+ }
}
+ i, b = envVar(defaults.enableVNC)
if layout.RequestedCapabilities.VNC {
vnc := fmt.Sprintf("%v", layout.RequestedCapabilities.VNC)
- i, b := envVar(defaults.enableVNC, vnc)
if !b {
layout.Template.Spec.EnvVars = append(layout.Template.Spec.EnvVars, apiv1.EnvVar{Name: defaults.enableVNC, Value: vnc})
} else {
layout.Template.Spec.EnvVars[i] = apiv1.EnvVar{Name: defaults.enableVNC, Value: vnc}
}
labels[defaults.enableVNC] = vnc
+ } else {
+ if b {
+ labels[defaults.enableVNC] = layout.Template.Spec.EnvVars[i].Value
+ }
}
if layout.RequestedCapabilities.TimeZone != "" {
- i, b := envVar(defaults.timeZone, layout.RequestedCapabilities.TimeZone)
+ i, b := envVar(defaults.timeZone)
if !b {
layout.Template.Spec.EnvVars = append(layout.Template.Spec.EnvVars, apiv1.EnvVar{Name: defaults.timeZone, Value: layout.RequestedCapabilities.TimeZone})
} else {
layout.Template.Spec.EnvVars[i] = apiv1.EnvVar{Name: defaults.timeZone, Value: layout.RequestedCapabilities.TimeZone}
}
- labels[defaults.timeZone] = layout.RequestedCapabilities.TimeZone
}
if layout.Template.Meta.Labels == nil {
@@ -178,10 +193,10 @@ func (cl *Client) Create(layout *ServiceSpec) (*Service, error) {
Subdomain: cl.svc,
Containers: []apiv1.Container{
{
- Name: layout.SessionID,
+ Name: "browser",
Image: layout.Template.Image,
SecurityContext: &apiv1.SecurityContext{
- Privileged: pointer.BoolPtr(false),
+ Privileged: &layout.Template.Privileged,
Capabilities: &apiv1.Capabilities{
Add: []apiv1.Capability{
"SYS_ADMIN",
@@ -197,17 +212,16 @@ func (cl *Client) Create(layout *ServiceSpec) (*Service, error) {
MountPath: "/dev/shm",
},
},
+ ImagePullPolicy: apiv1.PullIfNotPresent,
},
{
Name: "seleniferous",
- Image: "alcounit/seleniferous:latest",
- SecurityContext: &apiv1.SecurityContext{
- Privileged: pointer.BoolPtr(true),
- },
+ Image: cl.proxyImage,
Ports: getSidecarPorts(cl.svcPort),
Command: []string{
- "/seleniferous", "--listhen-port", cl.svcPort.StrVal, "--proxy-default-path", path.Join(layout.Template.Path, "session"), "--iddle-timeout", cl.iddleTimeout.String(), "--namespace", cl.ns,
+ "/seleniferous", "--listhen-port", cl.svcPort.StrVal, "--proxy-default-path", path.Join(layout.Template.Path, "session"), "--idle-timeout", cl.idleTimeout.String(), "--namespace", cl.ns,
},
+ ImagePullPolicy: apiv1.PullIfNotPresent,
},
},
Volumes: []apiv1.Volume{
@@ -220,16 +234,18 @@ func (cl *Client) Create(layout *ServiceSpec) (*Service, error) {
},
},
},
- NodeSelector: layout.Template.Spec.NodeSelector,
- HostAliases: layout.Template.Spec.HostAliases,
- RestartPolicy: apiv1.RestartPolicyNever,
- Affinity: &layout.Template.Spec.Affinity,
- DNSConfig: &layout.Template.Spec.DNSConfig,
+ NodeSelector: layout.Template.Spec.NodeSelector,
+ HostAliases: layout.Template.Spec.HostAliases,
+ RestartPolicy: apiv1.RestartPolicyNever,
+ Affinity: &layout.Template.Spec.Affinity,
+ DNSConfig: &layout.Template.Spec.DNSConfig,
+ Tolerations: layout.Template.Spec.Tolerations,
+ ImagePullSecrets: getImagePullSecretList(cl.imagePullSecretName),
},
}
context := context.Background()
- pod, err := cl.clientset.Pods(cl.ns).Create(context, pod, metav1.CreateOptions{})
+ pod, err := cl.clientset.CoreV1().Pods(cl.ns).Create(context, pod, metav1.CreateOptions{})
if err != nil {
return nil, fmt.Errorf("failed to create pod %v", err)
@@ -240,37 +256,51 @@ func (cl *Client) Create(layout *ServiceSpec) (*Service, error) {
cl.Delete(podName)
}
- var status apiv1.PodStatus
- w, err := cl.clientset.Pods(cl.ns).Watch(context, metav1.ListOptions{
- FieldSelector: fields.OneTermEqualSelector("metadata.name", podName).String(),
+ w, err := cl.clientset.CoreV1().Pods(cl.ns).Watch(context, metav1.ListOptions{
+ FieldSelector: fields.OneTermEqualSelector("metadata.name", podName).String(),
+ TimeoutSeconds: pointer.Int64Ptr(cl.readinessTimeout.Milliseconds()),
})
if err != nil {
- cancel()
- return nil, fmt.Errorf("watch pod: %v", err)
+ return nil, fmt.Errorf("failed to watch pod status: %v", err)
}
- func() {
- for {
- select {
- case events, ok := <-w.ResultChan():
- if !ok {
- return
- }
- pod = events.Object.(*apiv1.Pod)
- status = pod.Status
- if pod.Status.Phase != apiv1.PodPending {
- w.Stop()
- }
- case <-time.After(cl.iddleTimeout):
- w.Stop()
+ statusFn := func() error {
+ defer w.Stop()
+ var watchedPod *apiv1.Pod
+
+ for event := range w.ResultChan() {
+ switch event.Type {
+ case watch.Error:
+ return fmt.Errorf("received error while watching pod: %s",
+ event.Object.GetObjectKind().GroupVersionKind().String())
+ case watch.Deleted, watch.Added, watch.Modified:
+ watchedPod = event.Object.(*apiv1.Pod)
+ default:
+ return fmt.Errorf("received unknown event type %s while watching pod", event.Type)
+ }
+ if event.Type == watch.Deleted {
+ return errors.New("pod was deleted before becoming available")
+ }
+ switch watchedPod.Status.Phase {
+ case apiv1.PodPending:
+ continue
+ case apiv1.PodSucceeded, apiv1.PodFailed:
+ return fmt.Errorf("pod exited early with status %s", watchedPod.Status.Phase)
+ case apiv1.PodRunning:
+ return nil
+ case apiv1.PodUnknown:
+ return errors.New("couldn't obtain pod state")
+ default:
+ return errors.New("pod has unknown status")
}
}
- }()
+ return fmt.Errorf("pod wasn't running")
+ }
- if status.Phase != apiv1.PodRunning {
+ if statusFn() != nil {
cancel()
- return nil, fmt.Errorf("pod status: %v", status.Phase)
+ return nil, fmt.Errorf("failed to create pod: %v", err)
}
host := fmt.Sprintf("%s.%s", podName, cl.svc)
@@ -279,7 +309,7 @@ func (cl *Client) Create(layout *ServiceSpec) (*Service, error) {
Host: net.JoinHostPort(host, browserPorts.selenium.StrVal),
}
- if err := waitForService(u, cl.readinessTimeout); err != nil {
+ if err := waitForService(*u, cl.readinessTimeout); err != nil {
cancel()
return nil, fmt.Errorf("container service is not ready %v", u.String())
}
@@ -292,6 +322,7 @@ func (cl *Client) Create(layout *ServiceSpec) (*Service, error) {
CancelFunc: func() {
cancel()
},
+ Started: pod.CreationTimestamp.Time,
}
return svc, nil
@@ -301,7 +332,7 @@ func (cl *Client) Create(layout *ServiceSpec) (*Service, error) {
func (cl *Client) Delete(name string) error {
context := context.Background()
- return cl.clientset.Pods(cl.ns).Delete(context, name, metav1.DeleteOptions{
+ return cl.clientset.CoreV1().Pods(cl.ns).Delete(context, name, metav1.DeleteOptions{
GracePeriodSeconds: pointer.Int64Ptr(15),
})
}
@@ -309,7 +340,7 @@ func (cl *Client) Delete(name string) error {
//List ...
func (cl *Client) List() ([]*Service, error) {
context := context.Background()
- pods, err := cl.clientset.Pods(cl.ns).List(context, metav1.ListOptions{
+ pods, err := cl.clientset.CoreV1().Pods(cl.ns).List(context, metav1.ListOptions{
LabelSelector: "type=browser",
})
@@ -320,33 +351,111 @@ func (cl *Client) List() ([]*Service, error) {
var services []*Service
for _, pod := range pods.Items {
- if pod.Status.Phase == apiv1.PodRunning {
- podName := pod.GetName()
- host := fmt.Sprintf("%s.%s", podName, cl.svc)
-
- s := &Service{
- SessionID: podName,
- URL: &url.URL{
- Scheme: "http",
- Host: net.JoinHostPort(host, cl.svcPort.StrVal),
- },
- Labels: pod.GetLabels(),
- CancelFunc: func() {
- cl.Delete(podName)
- },
- }
- services = append(services, s)
+ podName := pod.GetName()
+ host := fmt.Sprintf("%s.%s", podName, cl.svc)
+
+ var status ServiceStatus
+ switch pod.Status.Phase {
+ case apiv1.PodRunning:
+ status = Running
+ case apiv1.PodPending:
+ status = Pending
+ default:
+ status = Unknown
+ }
+
+ service := &Service{
+ SessionID: podName,
+ URL: &url.URL{
+ Scheme: "http",
+ Host: net.JoinHostPort(host, cl.svcPort.StrVal),
+ },
+ Labels: pod.GetLabels(),
+ CancelFunc: func() {
+ cl.Delete(podName)
+ },
+ Status: status,
+ Started: pod.CreationTimestamp.Time,
}
+ services = append(services, service)
}
return services, nil
}
+//Watch ...
+func (cl Client) Watch() <-chan Event {
+ ch := make(chan Event)
+
+ convert := func(obj interface{}) *Service {
+ pod := obj.(*apiv1.Pod)
+ podName := pod.GetName()
+ host := fmt.Sprintf("%s.%s", podName, cl.svc)
+
+ var status ServiceStatus
+ switch pod.Status.Phase {
+ case apiv1.PodRunning:
+ status = Running
+ case apiv1.PodPending:
+ status = Pending
+ default:
+ status = Unknown
+ }
+
+ return &Service{
+ SessionID: podName,
+ URL: &url.URL{
+ Scheme: "http",
+ Host: net.JoinHostPort(host, cl.svcPort.StrVal),
+ },
+ Labels: pod.GetLabels(),
+ CancelFunc: func() {
+ cl.Delete(podName)
+ },
+ Status: status,
+ Started: pod.CreationTimestamp.Time,
+ }
+ }
+
+ namespace := informers.WithNamespace(cl.ns)
+ labels := informers.WithTweakListOptions(func(list *metav1.ListOptions) {
+ list.LabelSelector = "type=browser"
+ })
+
+ sharedIformer := informers.NewSharedInformerFactoryWithOptions(cl.clientset, 30*time.Second, namespace, labels)
+ sharedIformer.Core().V1().Pods().Informer().AddEventHandler(
+ cache.ResourceEventHandlerFuncs{
+ AddFunc: func(obj interface{}) {
+ ch <- Event{
+ Type: Added,
+ Service: convert(obj),
+ }
+ },
+ UpdateFunc: func(old interface{}, new interface{}) {
+ ch <- Event{
+ Type: Updated,
+ Service: convert(new),
+ }
+ },
+ DeleteFunc: func(obj interface{}) {
+ ch <- Event{
+ Type: Deleted,
+ Service: convert(obj),
+ }
+ },
+ },
+ )
+
+ var neverStop <-chan struct{} = make(chan struct{})
+ sharedIformer.Start(neverStop)
+ return ch
+}
+
//Logs ...
func (cl *Client) Logs(ctx context.Context, name string) (io.ReadCloser, error) {
- req := cl.clientset.Pods(cl.ns).GetLogs(name, &apiv1.PodLogOptions{
- Container: name,
+ req := cl.clientset.CoreV1().Pods(cl.ns).GetLogs(name, &apiv1.PodLogOptions{
+ Container: "browser",
Follow: true,
Previous: false,
Timestamps: false,
@@ -375,8 +484,18 @@ func getSidecarPorts(p intstr.IntOrString) []apiv1.ContainerPort {
return port
}
-//code credits to https://github.com/aerokube/selenoid/blob/master/service/service.go#L97
-func waitForService(u *url.URL, t time.Duration) error {
+func getImagePullSecretList(secret string) []apiv1.LocalObjectReference {
+ refList := make([]apiv1.LocalObjectReference, 0)
+ if secret != "" {
+ ref := apiv1.LocalObjectReference{
+ Name: secret,
+ }
+ refList = append(refList, ref)
+ }
+ return refList
+}
+
+func waitForService(u url.URL, t time.Duration) error {
up := make(chan struct{})
done := make(chan struct{})
go func() {
@@ -386,6 +505,7 @@ func waitForService(u *url.URL, t time.Duration) error {
return
default:
}
+
req, _ := http.NewRequest(http.MethodHead, u.String(), nil)
req.Close = true
resp, err := http.DefaultClient.Do(req)
@@ -403,7 +523,7 @@ func waitForService(u *url.URL, t time.Duration) error {
select {
case <-time.After(t):
close(done)
- return fmt.Errorf("%s does not respond in %v", u, t)
+ return fmt.Errorf("no responce after %v", t)
case <-up:
}
return nil
diff --git a/platform/platform.go b/platform/platform.go
index acd07ec..1571731 100644
--- a/platform/platform.go
+++ b/platform/platform.go
@@ -4,6 +4,7 @@ import (
"context"
"io"
"net/url"
+ "time"
"github.com/alcounit/selenosis/selenium"
apiv1 "k8s.io/api/core/v1"
@@ -19,10 +20,11 @@ type Meta struct {
type Spec struct {
Resources apiv1.ResourceRequirements `yaml:"resources,omitempty" json:"resources,omitempty"`
HostAliases []apiv1.HostAlias `yaml:"hostAliases,omitempty" json:"hostAliases,omitempty"`
- EnvVars []apiv1.EnvVar `yaml:"envVars,omitempty" json:"envVars,omitempty"`
+ EnvVars []apiv1.EnvVar `yaml:"env,omitempty" json:"env,omitempty"`
NodeSelector map[string]string `yaml:"nodeSelector,omitempty" json:"nodeSelector,omitempty"`
Affinity apiv1.Affinity `yaml:"affinity,omitempty" json:"affinity,omitempty"`
DNSConfig apiv1.PodDNSConfig `yaml:"dnsConfig,omitempty" json:"dnsConfig,omitempty"`
+ Tolerations []apiv1.Toleration `yaml:"tolerations,omitempty" json:"tolerations,omitempty"`
}
//BrowserSpec describes settings for Service
@@ -31,6 +33,7 @@ type BrowserSpec struct {
BrowserVersion string `yaml:"-" json:"-"`
Image string `yaml:"image" json:"image"`
Path string `yaml:"path" json:"path"`
+ Privileged bool `yaml:"privileged" json:"privileged"`
Meta Meta `yaml:"meta" json:"meta"`
Spec Spec `yaml:"spec" json:"spec"`
}
@@ -49,12 +52,38 @@ type Service struct {
Labels map[string]string `json:"labels"`
OnTimeout chan struct{} `json:"-"`
CancelFunc func() `json:"-"`
+ Status ServiceStatus `json:"-"`
+ Started time.Time `json:"started"`
+ Uptime string `json:"uptime"`
}
+//ServiceStatus ...
+type ServiceStatus string
+
+//Event ...
+type Event struct {
+ Type EventType
+ Service *Service
+}
+
+//EventType ...
+type EventType string
+
+const (
+ Added EventType = "Added"
+ Updated EventType = "Updated"
+ Deleted EventType = "Deleted"
+
+ Pending ServiceStatus = "Pending"
+ Running ServiceStatus = "Running"
+ Unknown ServiceStatus = "Unknown"
+)
+
//Platform ...
type Platform interface {
Create(*ServiceSpec) (*Service, error)
Delete(string) error
List() ([]*Service, error)
+ Watch() <-chan Event
Logs(context.Context, string) (io.ReadCloser, error)
}
diff --git a/selenium/selenium.go b/selenium/selenium.go
index 9c5e902..1292db5 100644
--- a/selenium/selenium.go
+++ b/selenium/selenium.go
@@ -28,8 +28,6 @@ type Capabilities struct {
DNSServers []string `json:"dnsServers,omitempty"`
Labels map[string]string `json:"labels,omitempty"`
SessionTimeout string `json:"sessionTimeout,omitempty"`
- S3KeyPattern string `json:"s3KeyPattern,omitempty"`
- ExtensionCapabilities *Capabilities `json:"selenoid:options,omitempty"`
}
//ValidateCapabilities ...
diff --git a/selenosis.go b/selenosis.go
index 57165a0..04ec468 100644
--- a/selenosis.go
+++ b/selenosis.go
@@ -5,46 +5,83 @@ import (
"github.com/alcounit/selenosis/config"
"github.com/alcounit/selenosis/platform"
+ "github.com/alcounit/selenosis/storage"
log "github.com/sirupsen/logrus"
)
//Configuration ....
type Configuration struct {
- SelenosisHost string
- ServiceName string
- SidecarPort string
- SessionLimit int
- SessionRetryCount int
- BrowserWaitTimeout time.Duration
- SessionIddleTimeout time.Duration
+ SelenosisHost string
+ ServiceName string
+ SidecarPort string
+ SessionLimit int
+ SessionRetryCount int
+ BrowserWaitTimeout time.Duration
+ SessionIdleTimeout time.Duration
+ BuildVersion string
}
//App ...
type App struct {
- logger *log.Logger
- client platform.Platform
- browsers *config.BrowsersConfig
- selenosisHost string
- serviceName string
- sidecarPort string
- sessionLimit int
- sessionRetryCount int
- sessionIddleTimeout time.Duration
- browserWaitTimeout time.Duration
+ logger *log.Logger
+ client platform.Platform
+ browsers *config.BrowsersConfig
+ selenosisHost string
+ serviceName string
+ sidecarPort string
+ sessionLimit int
+ sessionRetryCount int
+ sessionIdleTimeout time.Duration
+ browserWaitTimeout time.Duration
+ buildVersion string
+ stats *storage.Storage
}
//New ...
-func New(logger *log.Logger, client platform.Platform, browsers *config.BrowsersConfig, settings Configuration) *App {
+func New(logger *log.Logger, client platform.Platform, browsers *config.BrowsersConfig, cfg Configuration) *App {
+
+ storage := storage.New()
+
+ services, err := client.List()
+ if err != nil {
+ logger.Errorf("failed to get list of active pods: %v", err)
+ }
+
+ for _, service := range services {
+ storage.Put(service.SessionID, service)
+ }
+
+ ch := client.Watch()
+ go func() {
+ for {
+ select {
+ case event := <-ch:
+ switch event.Type {
+ case platform.Added:
+ storage.Put(event.Service.SessionID, event.Service)
+ case platform.Updated:
+ storage.Put(event.Service.SessionID, event.Service)
+ case platform.Deleted:
+ storage.Delete(event.Service.SessionID)
+ }
+ default:
+ break
+ }
+ }
+ }()
+
return &App{
- logger: logger,
- client: client,
- browsers: browsers,
- selenosisHost: settings.SelenosisHost,
- serviceName: settings.ServiceName,
- sidecarPort: settings.SidecarPort,
- sessionLimit: settings.SessionLimit,
- sessionRetryCount: settings.SessionRetryCount,
- browserWaitTimeout: settings.BrowserWaitTimeout,
- sessionIddleTimeout: settings.SessionIddleTimeout,
+ logger: logger,
+ client: client,
+ browsers: browsers,
+ selenosisHost: cfg.SelenosisHost,
+ serviceName: cfg.ServiceName,
+ sidecarPort: cfg.SidecarPort,
+ sessionLimit: cfg.SessionLimit,
+ sessionRetryCount: cfg.SessionRetryCount,
+ browserWaitTimeout: cfg.BrowserWaitTimeout,
+ sessionIdleTimeout: cfg.SessionIdleTimeout,
+ buildVersion: cfg.BuildVersion,
+ stats: storage,
}
}
diff --git a/storage/storage.go b/storage/storage.go
new file mode 100644
index 0000000..db0cae1
--- /dev/null
+++ b/storage/storage.go
@@ -0,0 +1,58 @@
+package storage
+
+import (
+ "sync"
+
+ "github.com/alcounit/selenosis/platform"
+ "github.com/alcounit/selenosis/tools"
+)
+
+//Storage ...
+type Storage struct {
+ sessions map[string]*platform.Service
+ sync.RWMutex
+}
+
+//New ...
+func New() *Storage {
+ return &Storage{
+ sessions: make(map[string]*platform.Service),
+ }
+}
+
+//Put ...
+func (s *Storage) Put(sessionID string, service *platform.Service) {
+ s.Lock()
+ defer s.Unlock()
+ s.sessions[sessionID] = service
+}
+
+//Delete ...
+func (s *Storage) Delete(sessionID string) {
+ s.Lock()
+ defer s.Unlock()
+ delete(s.sessions, sessionID)
+}
+
+//List ...
+func (s *Storage) List() []platform.Service {
+ s.Lock()
+ defer s.Unlock()
+ var l []platform.Service
+
+ for _, p := range s.sessions {
+ c := *p
+ c.Uptime = tools.TimeElapsed(c.Started)
+ l = append(l, c)
+ }
+ return l
+
+}
+
+//Len ...
+func (s *Storage) Len() int {
+ s.Lock()
+ defer s.Unlock()
+
+ return len(s.sessions)
+}