+
diff --git a/src/frontend/packages/suse-extensions/src/suse-login/suse-login.component.scss b/src/frontend/packages/suse-extensions/src/suse-login/suse-login.component.scss
index 9fa728ccc6..095d223561 100644
--- a/src/frontend/packages/suse-extensions/src/suse-login/suse-login.component.scss
+++ b/src/frontend/packages/suse-extensions/src/suse-login/suse-login.component.scss
@@ -172,6 +172,10 @@ app-suse-login {
padding: 0 60px;
}
+ .hidden {
+ visibility: hidden;
+ }
+
}
// If SSO Login change the presentation
@@ -186,8 +190,9 @@ app-suse-login {
.suse-login__sso-button.mat-button.suse-login__submit {
align-self: flex-start;
}
-
-
+ .suse-login__message {
+ padding-top: 0;
+ }
}
@media only screen and (max-width: 1120px) {
diff --git a/src/jetstream/cnsi.go b/src/jetstream/cnsi.go
index daf0ea3446..685e5fef03 100644
--- a/src/jetstream/cnsi.go
+++ b/src/jetstream/cnsi.go
@@ -633,5 +633,12 @@ func (p *portalProxy) updateEndpoint(c echo.Context) error {
}
}
+ // Notify plugins if they support the notification interface
+ for _, plugin := range p.Plugins {
+ if notifier, ok := plugin.(interfaces.EndpointNotificationPlugin); ok {
+ notifier.OnEndpointNotification(interfaces.EndpointUpdateAction, &endpoint)
+ }
+ }
+
return nil
}
diff --git a/src/jetstream/config.dev b/src/jetstream/config.dev
index d6f6b930b3..27ec166bf3 100644
--- a/src/jetstream/config.dev
+++ b/src/jetstream/config.dev
@@ -55,4 +55,7 @@ LOCAL_USER_PASSWORD=admin
LOCAL_USER_SCOPE=stratos.admin
# Enable/disable API key-based access to Stratos API (disabled, admin_only, all_users). Default is admin_only
-#API_KEYS_ENABLED=admin_only
\ No newline at end of file
+#API_KEYS_ENABLED=admin_only
+
+# Cache folder for Helm Charts
+HELM_CACHE_FOLDER=./.helm-cache
diff --git a/src/jetstream/config.example b/src/jetstream/config.example
index 417f5f8144..3723583c27 100644
--- a/src/jetstream/config.example
+++ b/src/jetstream/config.example
@@ -68,15 +68,11 @@ INVITE_USER_CLIENT_SECRET=
# DB_DATABASE_NAME=stratosdb
# DB_SSL_MODE=disable
-# Helm Monocular configuration
-#FDB_URL="ongodb://127.0.0.1:27016"
-#SYNC_SERVER_URL=http://127.0.0.1:8080"
-
-# Simplify development with FDB (value is port of FDB server: 27016 for FDB, 27017 for MongoDB)
-#FDB_LOCAL_DEV=27017
-
# Analysis services API
#ANALYSIS_SERVICES_API=
# Download link when installing the Kubernetes Dashboard in a targetted Kube Endpoint
# STRATOS_KUBERNETES_DASHBOARD_IMAGE=
+
+# Cache folder for Helm Charts
+# HELM_CACHE_FOLDER=./helm-cache
\ No newline at end of file
diff --git a/src/jetstream/go.mod b/src/jetstream/go.mod
index 567daf2f2e..4ca6a15bdc 100644
--- a/src/jetstream/go.mod
+++ b/src/jetstream/go.mod
@@ -34,8 +34,9 @@ require (
github.com/elazarl/goproxy v0.0.0-20200315184450-1f3cb6622dad // indirect
github.com/elazarl/goproxy/ext v0.0.0-20200315184450-1f3cb6622dad // indirect
github.com/fatih/color v1.7.0 // indirect
- github.com/go-sql-driver/mysql v1.4.1
- github.com/golang/mock v1.4.4
+ github.com/go-sql-driver/mysql v1.5.0
+ github.com/golang/mock v1.2.0
+ github.com/golang/snappy v0.0.1 // indirect
github.com/google/go-querystring v1.0.0 // indirect
github.com/google/martian v2.1.0+incompatible
github.com/gopherjs/gopherjs v0.0.0-20190411002643-bd77b112433e // indirect
@@ -75,11 +76,12 @@ require (
github.com/valyala/fasttemplate v1.1.0 // indirect
github.com/vito/go-interact v0.0.0-20171111012221-fa338ed9e9ec // indirect
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
- golang.org/x/crypto v0.0.0-20191205161847-0a08dada0ff9
+ golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a
gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0
gopkg.in/cheggaaa/pb.v1 v1.0.27 // indirect
- gopkg.in/yaml.v2 v2.2.8
+ gopkg.in/yaml.v2 v2.3.0
k8s.io/apiextensions-apiserver v0.0.0 // indirect
+ k8s.io/helm v2.16.10+incompatible // indirect
k8s.io/kubectl v0.0.0 // indirect
)
@@ -91,8 +93,6 @@ replace (
github.com/SermoDigital/jose => github.com/SermoDigital/jose v0.9.2-0.20180104203859-803625baeddc
github.com/Sirupsen/logrus => github.com/sirupsen/logrus v1.4.1
github.com/docker/docker => github.com/moby/moby v0.7.3-0.20190826074503-38ab9da00309
- github.com/helm/monocular/chartrepo => ./plugins/monocular/chart-repo
- github.com/helm/monocular/chartsvc => ./plugins/monocular/chartsvc
github.com/kubernetes-sigs/aws-iam-authenticator => github.com/kubernetes-sigs/aws-iam-authenticator v0.3.1-0.20190111160901-390d9087a4bc
github.com/russross/blackfriday v2.0.0+incompatible => github.com/russross/blackfriday v1.5.2
github.com/sergi/go-diff => github.com/sergi/go-diff v1.0.0
diff --git a/src/jetstream/go.sum b/src/jetstream/go.sum
index 81cfff4a3c..32e2f9c513 100644
--- a/src/jetstream/go.sum
+++ b/src/jetstream/go.sum
@@ -34,11 +34,14 @@ github.com/MakeNowJust/heredoc v0.0.0-20171113091838-e9091a26100e h1:eb0Pzkt15Bm
github.com/MakeNowJust/heredoc v0.0.0-20171113091838-e9091a26100e/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E=
github.com/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RPBiVg=
github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
+github.com/Masterminds/semver v1.4.2 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITgsTc=
github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/Masterminds/semver/v3 v3.0.1 h1:2kKm5lb7dKVrt5TYUiAavE6oFc1cFT0057UVGT+JqLk=
github.com/Masterminds/semver/v3 v3.0.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
+github.com/Masterminds/semver/v3 v3.1.0 h1:Y2lUDsFKVRSYGojLJ1yLxSXdMmMYTYls0rCvoqmMUQk=
+github.com/Masterminds/semver/v3 v3.1.0/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/Masterminds/sprig v2.18.0+incompatible h1:QoGhlbC6pter1jxKnjMFxT8EqsLuDE6FEcNbWEpw+lI=
github.com/Masterminds/sprig v2.18.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
github.com/Masterminds/sprig/v3 v3.0.0 h1:KSQz7Nb08/3VU9E4ns29dDxcczhOD1q7O1UfM4G3t3g=
@@ -65,6 +68,7 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRF
github.com/antonlindstrom/pgstore v0.0.0-20170604072116-a407030ba6d0 h1:e6PEaXbztY0ViaKotCICNnBQDUeNEJgrQ5UAHWlloh4=
github.com/antonlindstrom/pgstore v0.0.0-20170604072116-a407030ba6d0/go.mod h1:2Ti6VUHVxpC0VSmTZzEvpzysnaGAfGBOoMIz5ykPyyw=
github.com/apoydence/eachers v0.0.0-20181020210610-23942921fe77/go.mod h1:bXvGk6IkT1Agy7qzJ+DjIw/SJ1AaB3AvAuMDVV+Vkoo=
+github.com/arschles/assert v1.0.0 h1:NofQbRhtxcLgP+XoKunA7J6UMJNTqX7xR/19tej8UsA=
github.com/arschles/assert v1.0.0/go.mod h1:m/u69zW43x0h8dTHcv3JJZljINyEYgBuf5fYJP6WikI=
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA=
@@ -292,6 +296,7 @@ github.com/gopherjs/gopherjs v0.0.0-20190411002643-bd77b112433e/go.mod h1:wJfORR
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/handlers v1.4.0/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
+github.com/gorilla/mux v1.7.0 h1:tOSd0UKHQd6urX6ApfOn4XdBMY6Sh1MfxV3kmaazO+U=
github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
@@ -319,6 +324,7 @@ github.com/hashicorp/golang-lru v0.5.3 h1:YPkqC67at8FYaadspW/6uE0COsBxS2656RLEr8
github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/helm/monocular v1.4.0 h1:g0sOpuMe+9u+aPfd9ZO8mWV+c8W0dfGyBG9Wl23nwec=
github.com/helm/monocular v1.4.0/go.mod h1:PpkCN0v4zVVigsIHnsQdJytKFmaUkwfhxB7z33a9/gE=
+github.com/helm/monocular v1.10.0 h1:/tkbVH0+7GR2C4W2ODJGiVGXyHmYCa3vaBb5S+pz6bE=
github.com/heptio/authenticator v0.3.0/go.mod h1:Q86X8hc61JXhE5XxYLKmrSRWby/Oe8IIYZIBgmGVkTA=
github.com/heptiolabs/healthcheck v0.0.0-20180807145615-6ff867650f40 h1:GT4RsKmHh1uZyhmTkWJTDALRjSHYQp6FRKrotf0zhAs=
github.com/heptiolabs/healthcheck v0.0.0-20180807145615-6ff867650f40/go.mod h1:NtmN9h8vrTveVQRLHcX2HQ5wIPBDCsZ351TGbZWgg38=
@@ -353,10 +359,12 @@ github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQL
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/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
+github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
+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/kubeapps/common v0.0.0-20190508164739-10b110436c1a h1:VqeX/fehAB6FtBox0TVYcjOMXGE56INQIfbXegditX4=
github.com/kubeapps/common v0.0.0-20190508164739-10b110436c1a/go.mod h1:TsgmjeDpbftqhwPKInJ3v+l+xbHs4goiB6DFb2WqY9c=
@@ -568,6 +576,8 @@ golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20191028145041-f83a4685e152/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20191205161847-0a08dada0ff9 h1:abxekknhS/Drh3uoQDk5Hc7BgeiyI39Crb7vhf/1j5s=
golang.org/x/crypto v0.0.0-20191205161847-0a08dada0ff9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM=
+golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -608,6 +618,7 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -693,6 +704,8 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
+gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
helm.sh/helm/v3 v3.0.0 h1:or/9cs1GgfcTQeEnR2CVJNw893/rmqIG1KsNHmUiSFw=
helm.sh/helm/v3 v3.0.0/go.mod h1:sI7B9yfvMgxtTPMWdk1jSKJ2aa59UyP9qhPydqW6mgo=
@@ -704,6 +717,8 @@ k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8
k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
k8s.io/helm v2.16.1+incompatible h1:L+k810plJlaGWEw1EszeT4deK8XVaKxac1oGcuB+WDc=
k8s.io/helm v2.16.1+incompatible/go.mod h1:LZzlS4LQBHfciFOurYBFkCMTaZ0D1l+p0teMg7TSULI=
+k8s.io/helm v2.16.10+incompatible h1:eFksERw3joHEL62TrcDX8I5fgEQJvit4qxxPXAkYTyQ=
+k8s.io/helm v2.16.10+incompatible/go.mod h1:LZzlS4LQBHfciFOurYBFkCMTaZ0D1l+p0teMg7TSULI=
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 v0.4.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
diff --git a/src/jetstream/plugins/kubernetes/go.mod b/src/jetstream/plugins/kubernetes/go.mod
index ebeac9e5a1..e5d44dd87d 100644
--- a/src/jetstream/plugins/kubernetes/go.mod
+++ b/src/jetstream/plugins/kubernetes/go.mod
@@ -13,7 +13,6 @@ require (
github.com/ghodss/yaml v1.0.0
github.com/gorilla/websocket v1.4.0
github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc // indirect
- github.com/helm/monocular/chartsvc v0.0.0-00010101000000-000000000000
github.com/heptio/authenticator v0.3.0 // indirect
github.com/kubernetes-sigs/aws-iam-authenticator v0.3.0
github.com/labstack/echo v3.3.10+incompatible
@@ -35,7 +34,6 @@ replace (
github.com/SermoDigital/jose => github.com/SermoDigital/jose v0.9.2-0.20180104203859-803625baeddc
github.com/cloudfoundry-incubator/stratos/src/jetstream => ../..
github.com/docker/docker => github.com/moby/moby v0.7.3-0.20190826074503-38ab9da00309
- github.com/helm/monocular/chartsvc => ../monocular/chartsvc
github.com/kubernetes-sigs/aws-iam-authenticator => github.com/kubernetes-sigs/aws-iam-authenticator v0.3.1-0.20190111160901-390d9087a4bc
github.com/russross/blackfriday v2.0.0+incompatible => github.com/russross/blackfriday v1.5.2
github.com/sergi/go-diff => github.com/sergi/go-diff v1.0.0
diff --git a/src/jetstream/plugins/kubernetes/install_release.go b/src/jetstream/plugins/kubernetes/install_release.go
index 575c0b7065..7998300c8b 100644
--- a/src/jetstream/plugins/kubernetes/install_release.go
+++ b/src/jetstream/plugins/kubernetes/install_release.go
@@ -3,7 +3,6 @@ package kubernetes
import (
"bytes"
"encoding/json"
- "errors"
"fmt"
"github.com/labstack/echo"
@@ -28,6 +27,7 @@ type installRequest struct {
Name string `json:"releaseName"`
Namespace string `json:"releaseNamespace"`
Values string `json:"values"`
+ ChartURL string `json:"chartUrl"`
Chart struct {
Name string `json:"chartName"`
Repository string `json:"repo"`
@@ -38,6 +38,7 @@ type installRequest struct {
type upgradeRequest struct {
MonocularEndpoint string `json:"monocularEndpoint"`
Values string `json:"values"`
+ ChartURL string `json:"chartUrl"`
Chart struct {
Name string `json:"name"`
Repository string `json:"repo"`
@@ -46,11 +47,6 @@ type upgradeRequest struct {
RestartPods bool `json:"restartPods"`
}
-// Monocular is a plugin for Monocular
-type Monocular interface {
- GetChartDownloadUrl(monocularEndpoint, chartID, chartVersion string) (string, error)
-}
-
// InstallRelease will install a Helm 3 release
func (c *KubernetesSpecification) InstallRelease(ec echo.Context) error {
bodyReader := ec.Request().Body
@@ -63,7 +59,12 @@ func (c *KubernetesSpecification) InstallRelease(ec echo.Context) error {
return interfaces.NewJetstreamErrorf("Could not get Create Release Parameters: %v+", err)
}
- chart, err := c.loadChart(params.MonocularEndpoint, params.Chart.Repository, params.Chart.Name, params.Chart.Version)
+ // Client must give us the download URL for the chart
+ if len(params.ChartURL) == 0 {
+ return interfaces.NewJetstreamErrorf("Client did not supply Chart download URL")
+ }
+
+ chart, err := c.loadChart(params.ChartURL)
if err != nil {
return interfaces.NewJetstreamErrorf("Could not load chart: %v+", err)
}
@@ -112,29 +113,8 @@ func (c *KubernetesSpecification) InstallRelease(ec echo.Context) error {
return ec.JSON(200, release)
}
-func (c *KubernetesSpecification) getChart(monocularEndpoint, chartID, version string) (string, error) {
- helm := c.portalProxy.GetPlugin("monocular")
- if helm == nil {
- return "", errors.New("Could not find monocular plugin")
- }
-
- monocular, ok := helm.(Monocular)
- if !ok {
- return "", errors.New("Could not find monocular plugin interface")
- }
-
- return monocular.GetChartDownloadUrl(monocularEndpoint, chartID, version)
-}
-
// Load the Helm chart for the given repository, name and version
-func (c *KubernetesSpecification) loadChart(monocularEndpoint, repo, name, version string) (*chart.Chart, error) {
-
- chartID := fmt.Sprintf("%s/%s", repo, name)
- downloadURL, err := c.getChart(monocularEndpoint, chartID, version)
- if err != nil {
- return nil, fmt.Errorf("Could not get the Download URL for the Helm Chart: %+v", err)
- }
-
+func (c *KubernetesSpecification) loadChart(downloadURL string) (*chart.Chart, error) {
log.Debugf("Helm Chart Download URL: %s", downloadURL)
// NWM: Should we look up Helm Repository endpoint and use the value from that
@@ -218,6 +198,16 @@ func (c *KubernetesSpecification) UpgradeRelease(ec echo.Context) error {
return interfaces.NewJetstreamErrorf("Could not get Upgrade Release Parameters: %+v", err)
}
+ // Client must give us the download URL for the chart
+ if len(params.ChartURL) == 0 {
+ return interfaces.NewJetstreamErrorf("Client did not supply Chart download URL")
+ }
+
+ chart, err := c.loadChart(params.ChartURL)
+ if err != nil {
+ return interfaces.NewJetstreamErrorf("Could not load chart for upgrade: %+v", err)
+ }
+
config, hc, err := c.GetHelmConfiguration(endpointGUID, userGUID, namespace)
if err != nil {
return interfaces.NewJetstreamErrorf("Could not get Helm Configuration for endpoint: %+v", err)
@@ -225,11 +215,6 @@ func (c *KubernetesSpecification) UpgradeRelease(ec echo.Context) error {
defer hc.Cleanup()
- chart, err := c.loadChart(params.MonocularEndpoint, params.Chart.Repository, params.Chart.Name, params.Chart.Version)
- if err != nil {
- return interfaces.NewJetstreamErrorf("Could not load chart for upgrade: %+v", err)
- }
-
userSuppliedValues := map[string]interface{}{}
if err := yaml.Unmarshal([]byte(params.Values), &userSuppliedValues); err != nil {
// Could not parse the user's values
diff --git a/src/jetstream/plugins/monocular/20200819184800_ChartStore.go b/src/jetstream/plugins/monocular/20200819184800_ChartStore.go
new file mode 100644
index 0000000000..41ef1ebebf
--- /dev/null
+++ b/src/jetstream/plugins/monocular/20200819184800_ChartStore.go
@@ -0,0 +1,49 @@
+package monocular
+
+import (
+ "database/sql"
+
+ "bitbucket.org/liamstask/goose/lib/goose"
+
+ "github.com/cloudfoundry-incubator/stratos/src/jetstream/datastore"
+)
+
+func init() {
+ datastore.RegisterMigration(20200819184800, "HelmChartStore", func(txn *sql.Tx, conf *goose.DBConf) error {
+
+ createChartsTable := "CREATE TABLE IF NOT EXISTS helm_charts ("
+ createChartsTable += "endpoint VARCHAR(64) NOT NULL,"
+ createChartsTable += "name VARCHAR(255) NOT NULL,"
+ createChartsTable += "repo_name VARCHAR(255) NOT NULL,"
+ createChartsTable += "version VARCHAR(64) NOT NULL,"
+ createChartsTable += "created TIMESTAMP NOT NULL,"
+ createChartsTable += "app_version VARCHAR(64) NOT NULL,"
+ createChartsTable += "description VARCHAR(255) NOT NULL,"
+ createChartsTable += "icon_url VARCHAR(255) NOT NULL,"
+ createChartsTable += "chart_url VARCHAR(255) NOT NULL,"
+ createChartsTable += "source_url VARCHAR(255) NOT NULL,"
+ createChartsTable += "digest VARCHAR(64) NOT NULL,"
+ createChartsTable += "is_latest BOOLEAN NOT NULL DEFAULT FALSE,"
+ createChartsTable += "update_batch VARCHAR(64) NOT NULL,"
+ createChartsTable += "PRIMARY KEY (endpoint, name, version) );"
+
+ _, err := txn.Exec(createChartsTable)
+ if err != nil {
+ return err
+ }
+
+ createIndex := "CREATE INDEX helm_charts_endpoint ON helm_charts (endpoint, name, version);"
+ _, err = txn.Exec(createIndex)
+ if err != nil {
+ return err
+ }
+
+ createRepoIndex := "CREATE INDEX helm_charts_repository ON helm_charts (name, repo_name, version);"
+ _, err = txn.Exec(createRepoIndex)
+ if err != nil {
+ return err
+ }
+
+ return nil
+ })
+}
diff --git a/src/jetstream/plugins/monocular/README.md b/src/jetstream/plugins/monocular/README.md
deleted file mode 100644
index 956b10490c..0000000000
--- a/src/jetstream/plugins/monocular/README.md
+++ /dev/null
@@ -1,82 +0,0 @@
-# Syncing this subtree with the Monocular repo
-
-## Common Prerequisites
-
-**NOTE:**
-
-The scripts do their best to fail gracefully. To avoid problems however, make sure the following pre-requisities are done before attempting. A CTRL-C in the middle of a run will likely require some cleanup.
-
-
-1. Ensure you have the monocular fork as a remote. e.g.
-
-```
-git remote add -f -t master stratos-monocular-fork git@github.com:cf-stratos/monocular.git
-```
-
-2. With a second remote it's useful to store remote refs separately in case of branch/tag collisions:
-
-```
-git config --add remote.stratos-monocular-fork.fetch 'refs/tags/*:refs/rtags/stratos-monocular-fork/*
-```
-
-## Before running the PULL script
-Follow these carefully.
-
-1. Ensure you are on the branch you want to merge into.
-2. Ensure you have no unstaged or uncommitted changes
-3. `git status` should be clean
-
-## Before running the PUSH script
-1. Ensure that at least one pull haa been made already
-2. Ensure the monocular target branch exists and has been pushed to its monocular remote
-3. Ensure you have no unstaged or uncommitted changes.
-4. `git status` should be clean.
-
-## Running the scripts..
-
-Both scripts, when run either without arguments or with the `-h` flag, will show usage instructions.
-
-### PULL script:
-
-This pulls the subtree from monocular repo under `/cmd` into the stratos repo under `/src/jetstream/plugins/monocular`
-
-Usage:
-
-```
-git-pull-downstream.sh -f [ -b | -r ]
--f MONOCULAR_FORK The upstream (monocular) repo"
--b MONOCULAR_BRANCH The upstream branch to pull from" >&1
--r MONOCULAR_REF The upsream ref to pull from (e.g. tag or SHA1)
-```
-
-e.g.
-
-```
-./git-pull-downstream.sh -f stratos-monocular-fork -b master
-```
-
-#### To pull a tag:
-The tag should be specufied as : `refs/rtags/stratos-monocular-fork/`
-
-e.g.
-
-```
-./git-pull-downstream.sh -f stratos-monocular-fork -r refs/rtags/stratos-monocular-fork/v1.4.0
-```
-
-
-### PUSH script:
-Usage:
-
-```
-git-push-upstream.sh -f -t -s ]
--f MONOCULAR_FORK The upstream (monocular) repo to push to"
--t MONOCULAR_BRANCH The target (monocular) upstream branch to push to
--s STRATOS_SOURCE_BRANCH The source stratos branch containing the changes
-```
-
-e.g.
-
-```
-./git-push-upstream.sh -f stratos-monocular-fork -t feature-branch -s dummy-master
-```
\ No newline at end of file
diff --git a/src/jetstream/plugins/monocular/README.txt b/src/jetstream/plugins/monocular/README.txt
deleted file mode 100644
index 149af612ae..0000000000
--- a/src/jetstream/plugins/monocular/README.txt
+++ /dev/null
@@ -1,13 +0,0 @@
-### Run chart repo in serve mode WITH TLS ###
-### Modify the bind mounts according to location of certs on your system
-
-docker run -p 8080:8080 --rm -e -ti --network=host -v ~/dev/git/fdb-doclayer-fork/packaging/docker/testcerts:/etc/tlscerts kreinecke/suse-fdb-chart-repo:latest /chartrepo serve --doclayer-url=mongodb://localhost:27016 stable https://kubernetes-charts.storage.googleapis.com --cafile /etc/tlscerts/ca.crt --certfile /etc/tlscerts/client.crt --keyfile /etc/tlscerts/client.key --debug
-
-To test sync and status fetch:
-curl -X PUT -H "Content-Type: application/json" -d '{"repoURL":"https://kubernetes-charts.storage.googleapis.com"}' http://localhost:8080/v1/sync/stable
-
-curl -X GET http://localhost:8080/v1/status/stable
-
-
-### TLS Currently not optional, but for reference, use command below if making optional in future ###
-docker run -p 8080:8080 --rm -e -ti --network=host kreinecke/suse-fdb-chart-repo:latest /chartrepo serve --doclayer-url=mongodb://192.168.57.1:27016 stable https://kubernetes-charts.storage.googleapis.com
diff --git a/src/jetstream/plugins/monocular/cache.go b/src/jetstream/plugins/monocular/cache.go
new file mode 100644
index 0000000000..6e38df02ec
--- /dev/null
+++ b/src/jetstream/plugins/monocular/cache.go
@@ -0,0 +1,341 @@
+package monocular
+
+import (
+ "archive/tar"
+ "compress/gzip"
+ "crypto/sha256"
+ "encoding/hex"
+ "errors"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "net/http"
+ "net/url"
+ "os"
+ "path"
+ "path/filepath"
+ "strings"
+
+ "github.com/cloudfoundry-incubator/stratos/src/jetstream/plugins/monocular/store"
+ log "github.com/sirupsen/logrus"
+ yaml "gopkg.in/yaml.v2"
+)
+
+// Local Helm Chart Cache
+
+// A local file cache stores chart files
+// We only download the files for the latest version - other versions are downloaded on demand
+// Within the cache there is a folder for each endpoint (helm repository) and under each of those
+// a folder for each chart with chart files in ana a digest tag file
+
+// If there are multiple Stratos backends, then each maintains its own cache
+
+// The Chart index is stored in the database
+
+const digestFilename = "digest"
+
+// deleteCacheForEndpoint will delete all cached files for the given endpoint
+func (m *Monocular) deleteCacheForEndpoint(endpointID string) error {
+ endpointCacheFolder := path.Join(m.CacheFolder, endpointID)
+ return os.RemoveAll(endpointCacheFolder)
+}
+
+// cacheCharts will cache charts in the local folder cache
+func (m *Monocular) cacheCharts(charts []store.ChartStoreRecord) error {
+
+ var errorCount = 0
+ log.Debug("Cacheing charts")
+ for _, chart := range charts {
+ log.Debugf("Processing: %s", chart.Name)
+ if err := m.cacheChart(chart); err != nil {
+ errorCount++
+ log.Warnf("Error cacheing chart: %s - %+v", chart.Name, err)
+ }
+ if _, err := m.cacheChartIcon(chart); err != nil {
+ errorCount++
+ log.Warnf("Error cacheing chart icon: %s - %+v", chart.Name, err)
+ }
+
+ }
+ if errorCount > 0 {
+ return errors.New("Error(s) occurred caching charts")
+ }
+
+ return nil
+}
+
+// Get the cache folder path for a chart
+func (m *Monocular) getChartCacheFolder(chart store.ChartStoreRecord) string {
+ filename := fmt.Sprintf("%s_%s", chart.Name, chart.Version)
+ return path.Join(m.CacheFolder, chart.EndpointID, filename)
+}
+
+// cleanCacheFiles will Clean all files in the folder for an endpoint that are not referenced by any of the charts we have for that endpoint
+func (m *Monocular) cleanCacheFiles(endpointID string, allCharts []store.ChartStoreRecord) error {
+
+ // Build map of the valid chart folder names
+ validFiles := make(map[string]bool)
+ for _, chart := range allCharts {
+ validFiles[m.getChartCacheFolder(chart)] = true
+ }
+
+ endpointCacheFolder := path.Join(m.CacheFolder, endpointID)
+ // Don't delete the top-level cache folder for the endpoint
+ validFiles[endpointCacheFolder] = true
+ errorCount := 0
+ filepath.Walk(endpointCacheFolder, func(path string, info os.FileInfo, err error) error {
+ if err == nil && info.IsDir() {
+ if _, ok := validFiles[path]; !ok {
+ // Filename does not exist in the map of valid file names
+ log.Debugf("Need to delete unused cache folder: %s", path)
+ if err := os.RemoveAll(path); err != nil {
+ log.Errorf("Could not delete folder %s - %+v", path, err)
+ errorCount++
+ }
+ }
+ }
+ return nil
+ })
+
+ if errorCount > 0 {
+ return fmt.Errorf("Error(s) occurred cleaning unused folders from the cache folder for endpoint %s", endpointID)
+ }
+
+ return nil
+}
+
+// Is there a chart digest in the given folder with the given value?
+func hasDigestFile(chartCachePath, digest string) bool {
+ data, err := ioutil.ReadFile(path.Join(chartCachePath, digestFilename))
+ if err == nil {
+ chk := strings.TrimSpace(string(data))
+ return chk == digest
+ }
+
+ return false
+}
+
+// write the chart digest to a file
+func writeDigestFile(chartCachePath, digest string) error {
+ return ioutil.WriteFile(path.Join(chartCachePath, digestFilename), []byte(digest), 0644)
+}
+
+func (m *Monocular) getChartYaml(chart store.ChartStoreRecord) *ChartMetadata {
+
+ // Cache the Chart if we don't have it already
+ m.cacheChart(chart)
+
+ chartCacheYamlPath := path.Join(m.getChartCacheFolder(chart), "Chart.yaml")
+ if _, err := os.Stat(chartCacheYamlPath); os.IsNotExist(err) {
+ return nil
+ }
+
+ // Check we can unmarshall the request
+ data, err := ioutil.ReadFile(chartCacheYamlPath)
+ if err != nil {
+ return nil
+ }
+
+ // Parse as yaml
+ var chartYaml ChartMetadata
+ err = yaml.Unmarshal(data, &chartYaml)
+ if err != nil {
+ return nil
+ }
+ return &chartYaml
+}
+
+// Get the cache file path for the chart icon
+func (m *Monocular) getIconCacheFile(chart store.ChartStoreRecord) string {
+ ext := ""
+ u, err := url.Parse(chart.IconURL)
+ if err == nil {
+ parts := strings.Split(u.Path, "/")
+ filename := parts[len(parts)-1]
+ index := strings.LastIndex(filename, ".")
+ if index != -1 {
+ ext = filename[index:]
+ }
+ }
+
+ filename := fmt.Sprintf("icon%s", ext)
+ return path.Join(m.getChartCacheFolder(chart), filename)
+}
+
+func (m *Monocular) ensureFolder(path string) error {
+ if _, err := os.Stat(path); os.IsNotExist(err) {
+ return os.MkdirAll(path, os.ModePerm)
+ }
+
+ return nil
+}
+
+// Cache chart
+// Check to see if we already have files for the given chart - if not, download the archive
+// and extract the files we need:
+// Chart.yaml, README.md, values.yaml, values.schema.json
+// Download the icon as well
+func (m *Monocular) cacheChart(chart store.ChartStoreRecord) error {
+ log.Debugf("Cacheing chart: %s, %s", chart.Name, chart.Version)
+
+ chartCachePath := m.getChartCacheFolder(chart)
+ if err := m.ensureFolder(chartCachePath); err != nil {
+ log.Warnf("Could not create folder for chart downloads: %+v", err)
+ return err
+ }
+
+ // Check to see if we have the same digest
+ if ok := hasDigestFile(chartCachePath, chart.Digest); ok {
+ log.Debug("Skipping download - already have archive with the same digest")
+ return nil
+ }
+
+ archiveFile := path.Join(chartCachePath, "chart.tgz")
+ if err := m.downloadFile(archiveFile, chart.ChartURL); err != nil {
+ return fmt.Errorf("Could not download chart from: %s - %+v", chart.ChartURL, err)
+ }
+
+ sum, err := getFileChecksum(archiveFile)
+ if err != nil {
+ return fmt.Errorf("Could not calculate checksum for chart archive: %s - %+v", archiveFile, err)
+ }
+
+ if err := writeDigestFile(chartCachePath, sum); err != nil {
+ return fmt.Errorf("Could not write chart digest file in: %s - %+v", chartCachePath, err)
+ }
+
+ // Now extract the files we need
+ filenames := []string{"Chart.yaml", "README.md", "values.schema.json", "values.yaml"}
+ if err := extractArchiveFiles(archiveFile, chart.Name, chartCachePath, filenames); err != nil {
+ return fmt.Errorf("Could not extract files from chart archive: %s - %+v", archiveFile, err)
+ }
+
+ // We can delete the Chart archive - don't need it anymore
+ os.Remove(archiveFile)
+
+ return nil
+}
+
+// Cache a chart icon
+func (m *Monocular) cacheChartIcon(chart store.ChartStoreRecord) (string, error) {
+ log.Debugf("Cacheing chart icon: %s, %s", chart.Name, chart.Version)
+ if len(chart.IconURL) > 0 {
+ log.Debugf("Downloading chart icon: %s", chart.IconURL)
+ // If icon file already exists then don't download again
+ iconFilePath := m.getIconCacheFile(chart)
+ if _, err := os.Stat(iconFilePath); os.IsNotExist(err) {
+ if err := m.ensureFolder(path.Dir(iconFilePath)); err != nil {
+ log.Error(err)
+ } else if err := m.downloadFile(iconFilePath, chart.IconURL); err != nil {
+ log.Errorf("Could not download chart icon: %+v", err)
+ return "", fmt.Errorf("Could not download Chart icon: %+v", err)
+ }
+ }
+ return iconFilePath, nil
+ }
+
+ return "", nil
+}
+
+// download a file form the given url sand save to the file path
+func (m *Monocular) downloadFile(filepath string, url string) error {
+ // Get the data
+ httpClient := m.portalProxy.GetHttpClient(false)
+ resp, err := httpClient.Get(url)
+ if err != nil {
+ return err
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode != http.StatusOK {
+ return fmt.Errorf("Error downloading icon: %s - %d:%s", url, resp.StatusCode, resp.Status)
+ }
+
+ // Create the file
+ out, err := os.Create(filepath)
+ if err != nil {
+ return err
+ }
+ defer out.Close()
+
+ // Write the body to file
+ _, err = io.Copy(out, resp.Body)
+ return err
+}
+
+func extractArchiveFiles(archivePath, chartName, downloadFolder string, filenames []string) error {
+ // Map the filenames array into a map of path to destination file
+ requiredFiles := make(map[string]string)
+ requiredCount := len(filenames)
+ for _, name := range filenames {
+ requiredFiles[fmt.Sprintf("%s/%s", chartName, name)] = path.Join(downloadFolder, name)
+ }
+
+ f, err := os.Open(archivePath)
+ if err != nil {
+ log.Error("Helm: Archive extract file: Could not open file %s - %+v", archivePath, err)
+ return err
+ }
+ defer f.Close()
+
+ gzf, err := gzip.NewReader(f)
+ if err != nil {
+ log.Error("Helm: Archive extract file: Could not open zip file %s - %+v", archivePath, err)
+ return err
+ }
+
+ tarReader := tar.NewReader(gzf)
+ for {
+ header, err := tarReader.Next()
+ if err == io.EOF {
+ break
+ }
+
+ if err != nil {
+ log.Error("Helm: Archive extract file: Could not process archive file %s - %+v", archivePath, err)
+ return err
+ }
+
+ name := header.Name
+ switch header.Typeflag {
+ case tar.TypeDir:
+ continue
+ case tar.TypeReg:
+ // Is this a file we are looking for?
+ if downloadPath, ok := requiredFiles[name]; ok {
+ // Create the file
+ out, err := os.Create(downloadPath)
+ if err != nil {
+ return err
+ }
+ defer out.Close()
+
+ io.Copy(out, tarReader)
+
+ // If we have extracted all of the files we are looking for, then return early, rather than
+ // going through the rest of the files
+ requiredCount--
+ if requiredCount == 0 {
+ return nil
+ }
+ }
+ }
+ }
+
+ return nil
+}
+
+// get the SHA256 checksum for a file
+func getFileChecksum(file string) (string, error) {
+ f, err := os.Open(file)
+ if err != nil {
+ return "", err
+ }
+ defer f.Close()
+ hasher := sha256.New()
+ if _, err := io.Copy(hasher, f); err != nil {
+ return "", err
+ }
+
+ return hex.EncodeToString(hasher.Sum(nil)), nil
+}
diff --git a/src/jetstream/plugins/monocular/chart-repo/.gitignore b/src/jetstream/plugins/monocular/chart-repo/.gitignore
deleted file mode 100644
index 253eef939d..0000000000
--- a/src/jetstream/plugins/monocular/chart-repo/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-chart-repo-sync
diff --git a/src/jetstream/plugins/monocular/chart-repo/Dockerfile b/src/jetstream/plugins/monocular/chart-repo/Dockerfile
deleted file mode 100644
index ac5d67a440..0000000000
--- a/src/jetstream/plugins/monocular/chart-repo/Dockerfile
+++ /dev/null
@@ -1,12 +0,0 @@
-FROM splatform/stratos-bk-build-base:leap15_1 as builder
-
-COPY --chown=stratos:users . /go/src/github.com/helm/monocular
-WORKDIR /go/src/github.com/helm/monocular
-ARG VERSION
-RUN GO111MODULE=on GOPROXY=https://gocenter.io CGO_ENABLED=0 go build -a -installsuffix cgo -ldflags "-X main.version=$VERSION" .
-
-FROM splatform/stratos-bk-base:leap15_1
-#COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
-COPY --from=builder /go/src/github.com/helm/monocular/chartrepo /chartrepo
-USER 1001
-CMD ["/chartrepo"]
diff --git a/src/jetstream/plugins/monocular/chart-repo/Makefile b/src/jetstream/plugins/monocular/chart-repo/Makefile
deleted file mode 100644
index 42140b79a8..0000000000
--- a/src/jetstream/plugins/monocular/chart-repo/Makefile
+++ /dev/null
@@ -1,8 +0,0 @@
-IMAGE_REPO ?= docker.io/kreinecke/suse-fdb-chart-repo-tls
-IMAGE_TAG ?= latest
-# Version of the binary to be produced
-VERSION ?= $$(git rev-parse HEAD)
-
-docker-build:
- # We use the context of the root dir
- docker build --pull --rm -t ${IMAGE_REPO}:${IMAGE_TAG} --build-arg "VERSION=${VERSION}" -f Dockerfile .
diff --git a/src/jetstream/plugins/monocular/chart-repo/api_endpoint.go b/src/jetstream/plugins/monocular/chart-repo/api_endpoint.go
deleted file mode 100644
index 8040e204b2..0000000000
--- a/src/jetstream/plugins/monocular/chart-repo/api_endpoint.go
+++ /dev/null
@@ -1,323 +0,0 @@
-/*
-Copyright (c) 2017 The Helm Authors
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package main
-
-import (
- "context"
- "crypto/tls"
- "crypto/x509"
- "encoding/json"
- "fmt"
- "io/ioutil"
- "net/http"
- "os"
- "time"
-
- "github.com/google/uuid"
- "github.com/gorilla/mux"
- "github.com/heptiolabs/healthcheck"
- log "github.com/sirupsen/logrus"
- "github.com/urfave/negroni"
- "go.mongodb.org/mongo-driver/mongo/options"
-
- "github.com/helm/monocular/chartrepo/common"
- fdb "github.com/helm/monocular/chartrepo/foundationdb"
-)
-
-const pathPrefix = "/v1"
-
-var fdbClient fdb.Client
-var fDBName string
-var authorizationHeader string
-
-// Params a key-value map of path params
-type Params map[string]string
-
-// WithParams can be used to wrap handlers to take an extra arg for path params
-type WithParams func(http.ResponseWriter, *http.Request, Params)
-
-func (h WithParams) ServeHTTP(w http.ResponseWriter, req *http.Request) {
- vars := mux.Vars(req)
- h(w, req, vars)
-}
-
-func setupRoutes() http.Handler {
- r := mux.NewRouter()
-
- // Healthcheck
- health := healthcheck.NewHandler()
- r.Handle("/live", health)
- r.Handle("/ready", health)
-
- // Routes
- apiv1 := r.PathPrefix(pathPrefix).Subrouter()
- apiv1.Methods("PUT").Path("/sync/{repo}").Handler(WithParams(OnDemandSync))
- apiv1.Methods("PUT").Path("/delete/{repo}").Handler(WithParams(OnDemandDelete))
- apiv1.Methods("GET").Path("/status/{repo}").Handler(WithParams(RepoSyncStatus))
-
- n := negroni.Classic()
- n.UseHandler(r)
- return n
-}
-
-func OnDemandSync(w http.ResponseWriter, req *http.Request, params Params) {
-
- //Running in serve mode, we dont want to close the db client connection after a request
- var clientKeepAlive = true
-
- type syncParams struct {
- RepoURL string `json:"repoURL"`
- }
-
- repoName := params["repo"]
- if repoName == "" {
- err := fmt.Errorf("No Repository name provided in request for Sync action.")
- log.Error(err.Error())
- w.Header().Set("Server", "ChartRepo (On-Demand)")
- http.Error(w, err.Error(), http.StatusBadRequest)
- return
- }
-
- var status string
- //If this repo is already syncing, don't start another sync, but return in progress response
- //Ignore error return value when fetching status - we don't care if the repo does not exist yet
- currentRepoStatus, _ := fdb.GetRepoSyncStatus(repoName)
- activeSyncJob := currentRepoStatus.Status == common.SyncStatusInProgress || currentRepoStatus.Status == common.SyncStatusInProgress
- if activeSyncJob {
- status = common.SyncStatusInProgress
- } else {
-
- dec := json.NewDecoder(req.Body)
- var url syncParams
- if err := dec.Decode(&url); err != nil {
- log.Error(err.Error())
- w.Header().Set("Server", "ChartRepo (On-Demand)")
- http.Error(w, "Error decoding sync request repository URL: "+err.Error(), http.StatusBadRequest)
- return
- }
-
- repoURL := url.RepoURL
-
- if repoURL == "" {
- err := fmt.Errorf("No Repository URL provided in request for Sync action.")
- log.Error(err.Error())
- w.Header().Set("Server", "ChartRepo (On-Demand)")
- http.Error(w, err.Error(), http.StatusBadRequest)
- return
- }
-
- go fdb.SyncRepo(fdbClient, fDBName, repoName, repoURL, authorizationHeader, clientKeepAlive)
-
- status = common.SyncStatusInProgress
- }
-
- requestUUID, err := uuid.NewUUID()
-
- //Return sync status in response
- response := common.SyncJobStatusResponse{requestUUID.String(), status}
- js, err := json.Marshal(response)
- if err != nil {
- log.Error(err.Error())
- w.Header().Set("Server", "ChartRepo (On-Demand)")
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
-
- w.Header().Set("Content-Type", "application/json")
- w.Header().Set("Server", "ChartRepo (On-Demand)")
- w.Write(js)
-}
-
-func OnDemandDelete(w http.ResponseWriter, req *http.Request, params Params) {
- repoName := params["repo"]
- requestUUID, err := uuid.NewUUID()
- if repoName == "" {
- err := fmt.Errorf("No Repository name provided in request for Delete action.")
- log.Error(err.Error())
- w.Header().Set("Server", "ChartRepo (On-Demand)")
- http.Error(w, err.Error(), http.StatusBadRequest)
- }
-
- var status string
- currentRepoStatus, err := fdb.GetRepoSyncStatus(repoName)
- if err != nil {
- log.Errorf("Request: requestUUID, %v", err.Error())
- w.Header().Set("Server", "ChartRepo (On-Demand)")
- http.Error(w, err.Error(), http.StatusNotFound)
- return
- }
- //If this repo is already being deleted, don't start another delete, but return in progress response
- activeDeleteJob := currentRepoStatus.Status == common.DeleteStatusInProgress || currentRepoStatus.Status == common.DeleteStatusInProgress
- if activeDeleteJob {
- status = common.DeleteStatusInProgress
- } else {
-
- //Running in serve mode, we dont want to close the db client connection after a request
- var clientKeepAlive = true
-
- go fdb.DeleteRepo(fdbClient, fDBName, repoName, clientKeepAlive)
- status = common.DeleteStatusInProgress
- }
-
- //Return delete status in response
-
- response := common.DeleteJobStatusResponse{requestUUID.String(), status}
- js, err := json.Marshal(response)
- if err != nil {
- log.Error(err.Error())
- w.Header().Set("Server", "ChartRepo (On-Demand)")
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
-
- w.Header().Set("Content-Type", "application/json")
- w.Header().Set("Server", "ChartRepo (On-Demand)")
- w.Write(js)
-}
-
-func RepoSyncStatus(w http.ResponseWriter, req *http.Request, params Params) {
- repoName := params["repo"]
- if repoName == "" {
- log.Fatal("No Repository name provided in request for sync status.")
- }
-
- requestUUID, err := uuid.NewUUID()
- status, err := fdb.GetRepoSyncStatus(repoName)
- if err != nil {
- log.Errorf("Request: requestUUID, %v, %v", requestUUID, err.Error())
- w.Header().Set("Server", "ChartRepo (On-Demand)")
- http.Error(w, err.Error(), http.StatusNotFound)
- return
- }
-
- response := common.SyncJobStatusResponse{
- UUID: requestUUID.String(),
- Status: status.Status,
- }
- js, err := json.Marshal(response)
- if err != nil {
- log.Errorf("Request: requestUUID, %v, %v", requestUUID, err.Error())
- w.Header().Set("Server", "ChartRepo (On-Demand)")
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
-
- w.Header().Set("Content-Type", "application/json")
- w.Header().Set("Server", "ChartRepo (On-Demand)")
- w.Write(js)
-}
-
-func RepoDeleteStatus(w http.ResponseWriter, req *http.Request, params Params) {
- repoName := params["repo"]
- if repoName == "" {
- log.Fatal("No Repository name provided in request for delete status.")
- }
-
- requestUUID, err := uuid.NewUUID()
- status, err := fdb.GetRepoDeleteStatus(repoName)
- if err != nil {
- log.Errorf("Request: requestUUID, %v", err.Error())
- w.Header().Set("Server", "ChartRepo (On-Demand)")
- http.Error(w, err.Error(), http.StatusNotFound)
- return
- }
-
- response := common.DeleteJobStatusResponse{requestUUID.String(), status.Status}
- js, err := json.Marshal(response)
- if err != nil {
- log.Errorf("Request: requestUUID, %v", err.Error())
- w.Header().Set("Server", "ChartRepo (On-Demand)")
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
-
- w.Header().Set("Content-Type", "application/json")
- w.Header().Set("Server", "ChartRepo (On-Demand)")
- w.Write(js)
-}
-
-func initOnDemandEndpoint(fdbURL string, fdbName string, tlsEnabled bool, caCertFile string, certFile string, keyFile string, authHeader string, debug bool) {
-
- authorizationHeader = authHeader
- fDBName = fdbName
-
- if debug {
- log.SetLevel(log.DebugLevel)
- }
-
- InitFDBDocLayerConnection(&fdbURL, &fdbName, &tlsEnabled, &caCertFile, &certFile, &keyFile, &debug)
-
- n := setupRoutes()
-
- port := os.Getenv("PORT")
- if port == "" {
- port = "8080"
- }
- addr := ":" + port
- log.Infof("On-Demand endpoint listening on: %v", addr)
- if tlsEnabled {
- log.Infof("TLS is enabled.")
- } else {
- log.Infof("TLS is disabled.")
- }
- http.ListenAndServe(addr, n)
-}
-
-func InitFDBDocLayerConnection(fdbURL *string, fDB *string, tlsEnabled *bool, CAFile *string, certFile *string, keyFile *string, debug *bool) {
-
- log.Debugf("Attempting to connect to FDB: %v, %v, debug: %v", *fdbURL, *fDB, *debug)
-
- var tlsConfig *tls.Config
-
- if *tlsEnabled {
- //Load CA Cert from file here
- CA, err := ioutil.ReadFile(*CAFile) // just pass the file name
- if err != nil {
- log.Fatalf("Cannot load CA certificate from file: %v.", err)
- return
- }
- CACert := x509.NewCertPool()
- ok := CACert.AppendCertsFromPEM([]byte(CA))
- if !ok {
- log.Fatalf("Cannot append CA certificate to certificate pool.")
- return
- }
- //Now load the key pair and create tls options struct
- clientKeyPair, err := tls.LoadX509KeyPair(*certFile, *keyFile)
- if err != nil {
- log.Fatalf("Cannot load server keypair: %v", err)
- return
- }
-
- tlsConfig = &tls.Config{RootCAs: CACert, Certificates: []tls.Certificate{clientKeyPair}}
- }
-
- //Init client options and open connection
- clientOptions := options.Client().ApplyURI(*fdbURL).SetMinPoolSize(10).SetMaxPoolSize(100)
- if *tlsEnabled {
- clientOptions.SetTLSConfig(tlsConfig)
- }
- ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
- defer cancel()
- client, err := fdb.NewDocLayerClient(ctx, clientOptions)
- fdbClient = client
- if err != nil {
- log.Fatalf("Can't create client for FoundationDB document layer: %v. URL provided was: %v", err, *fdbURL)
- return
- }
- log.Debugf("FDB Document Layer client created.")
-}
diff --git a/src/jetstream/plugins/monocular/chart-repo/chart_repo.go b/src/jetstream/plugins/monocular/chart-repo/chart_repo.go
deleted file mode 100644
index ec917468d8..0000000000
--- a/src/jetstream/plugins/monocular/chart-repo/chart_repo.go
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
-Copyright (c) 2018 The Helm Authors
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package main
-
-import (
- "os"
-
- "github.com/helm/monocular/chartrepo/utils"
-
- "github.com/spf13/cobra"
-)
-
-var rootCmd = &cobra.Command{
- Use: "chart-repo",
- Short: "Chart Repository utility",
- Run: func(cmd *cobra.Command, args []string) {
- cmd.Help()
- },
-}
-
-func main() {
- cmd := rootCmd
- if err := cmd.Execute(); err != nil {
- os.Exit(1)
- }
-}
-
-func init() {
-
- cmds := []*cobra.Command{SyncCmd, DeleteCmd, ServeCmd}
-
- for _, cmd := range cmds {
- rootCmd.AddCommand(cmd)
-
- //Flags for optional FoundationDB + Document Layer backend
- cmd.Flags().String("doclayer-url", "mongodb://dev-fdbdoclayer/27016", "FoundationDB Document Layer URL")
- cmd.Flags().String("doclayer-database", "monocular-plugin", "FoundationDB Document-Layer database")
-
- //Flags for Serve-Mode TLS
- cmd.Flags().Bool("tls", false, "Enable Mutual TLS")
- cmd.Flags().String("cafile", "", "Path to CA certificate to use for client verification.")
- cmd.Flags().String("certfile", "", "Path to TLS certificate.")
- cmd.Flags().String("keyfile", "", "Path to TLS key.")
-
- // see version.go
- cmd.Flags().StringVarP(&utils.UserAgentComment, "user-agent-comment", "", "", "UserAgent comment used during outbound requests")
- cmd.Flags().Bool("debug", false, "verbose logging")
- }
- rootCmd.AddCommand(utils.VersionCmd)
-}
diff --git a/src/jetstream/plugins/monocular/chart-repo/common/chart_utils.go b/src/jetstream/plugins/monocular/chart-repo/common/chart_utils.go
deleted file mode 100644
index 0a7298c0e2..0000000000
--- a/src/jetstream/plugins/monocular/chart-repo/common/chart_utils.go
+++ /dev/null
@@ -1,205 +0,0 @@
-/*
-Copyright (c) 2019 The Helm Authors
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package common
-
-import (
- "archive/tar"
- "bytes"
- "crypto/sha256"
- "crypto/tls"
- "crypto/x509"
- "errors"
- "fmt"
- "io"
- "io/ioutil"
- "net/http"
- "net/url"
- "os"
- "path"
- "strings"
- "time"
-
- "github.com/helm/monocular/chartrepo/utils"
-
- "github.com/ghodss/yaml"
- "github.com/jinzhu/copier"
- log "github.com/sirupsen/logrus"
- helmrepo "k8s.io/helm/pkg/repo"
-)
-
-//ParseRepoURL parses a repo URL string into a URL
-func ParseRepoURL(repoURL string) (*url.URL, error) {
- repoURL = strings.TrimSpace(repoURL)
- return url.ParseRequestURI(repoURL)
-}
-
-//FetchRepoIndex fetches the index.yaml for a repository
-func FetchRepoIndex(r Repo, netClient HTTPClient) ([]byte, error) {
- indexURL, err := ParseRepoURL(r.URL)
- if err != nil {
- log.WithFields(log.Fields{"url": r.URL}).WithError(err).Error("failed to parse URL")
- return nil, err
- }
- indexURL.Path = path.Join(indexURL.Path, "index.yaml")
- req, err := http.NewRequest("GET", indexURL.String(), nil)
- if err != nil {
- log.WithFields(log.Fields{"url": req.URL.String()}).WithError(err).Error("could not build repo index request")
- return nil, err
- }
-
- req.Header.Set("User-Agent", utils.UserAgent())
- if len(r.AuthorizationHeader) > 0 {
- req.Header.Set("Authorization", r.AuthorizationHeader)
- }
-
- res, err := netClient.Do(req)
- if res != nil {
- defer res.Body.Close()
- }
- if err != nil {
- log.WithFields(log.Fields{"url": req.URL.String()}).WithError(err).Error("error requesting repo index")
- return nil, err
- }
-
- if res.StatusCode != http.StatusOK {
- log.WithFields(log.Fields{"url": req.URL.String(), "status": res.StatusCode}).Error("error requesting repo index, are you sure this is a chart repository?")
- return nil, errors.New("repo index request failed")
- }
-
- body, err := ioutil.ReadAll(res.Body)
- if err != nil {
- return nil, err
- }
- return body, nil
-}
-
-//ParseRepoIndex parses a repository index into an IndexFile
-func ParseRepoIndex(body []byte) (*helmrepo.IndexFile, error) {
- var index helmrepo.IndexFile
- err := yaml.Unmarshal(body, &index)
- if err != nil {
- return nil, err
- }
- index.SortEntries()
- return &index, nil
-}
-
-//ChartsFromIndex creates an array of Charts from a Repository IndexFile
-//Deprecated charts are skipped
-func ChartsFromIndex(index *helmrepo.IndexFile, r Repo) []Chart {
- var charts []Chart
- for _, entry := range index.Entries {
- if entry[0].GetDeprecated() {
- log.WithFields(log.Fields{"name": entry[0].GetName()}).Info("skipping deprecated chart")
- continue
- }
- charts = append(charts, NewChart(entry, r))
- }
- return charts
-}
-
-// NewChart Takes an entry from the index and constructs a database representation of the
-// object.
-func NewChart(entry helmrepo.ChartVersions, r Repo) Chart {
- var c Chart
- copier.Copy(&c, entry[0])
- copier.Copy(&c.ChartVersions, entry)
- c.Repo = r
- c.ID = fmt.Sprintf("%s/%s", r.Name, c.Name)
- return c
-}
-
-//ExtractFilesFromTarball extracts the specified files from a tarball into a map
-func ExtractFilesFromTarball(filenames []string, tarf *tar.Reader) (map[string]string, error) {
- ret := make(map[string]string)
- for {
- header, err := tarf.Next()
- if err == io.EOF {
- break
- }
- if err != nil {
- return ret, err
- }
-
- for _, f := range filenames {
- if strings.EqualFold(header.Name, f) {
- var b bytes.Buffer
- io.Copy(&b, tarf)
- ret[f] = string(b.Bytes())
- break
- }
- }
- }
- return ret, nil
-}
-
-//ChartTarballURL returns the URL for a given chart version
-func ChartTarballURL(r Repo, cv ChartVersion) string {
- source := cv.URLs[0]
- if _, err := ParseRepoURL(source); err != nil {
- // If the chart URL is not absolute, join with repo URL. It's fine if the
- // URL we build here is invalid as we can catch this error when actually
- // making the request
- u, _ := url.Parse(r.URL)
- u.Path = path.Join(u.Path, source)
- return u.String()
- }
- return source
-}
-
-// InitNetClient configures an HTTP client for making requests to repositories
-func InitNetClient(additionalCA string, timeoutSeconds time.Duration) (*http.Client, error) {
- // Get the SystemCertPool, continue with an empty pool on error
- caCertPool, _ := x509.SystemCertPool()
- if caCertPool == nil {
- caCertPool = x509.NewCertPool()
- }
-
- // If additionalCA exists, load it
- if _, err := os.Stat(additionalCA); !os.IsNotExist(err) {
- certs, err := ioutil.ReadFile(additionalCA)
- if err != nil {
- return nil, fmt.Errorf("Failed to append %s to RootCAs: %v", additionalCA, err)
- }
-
- // Append our cert to the system pool
- if ok := caCertPool.AppendCertsFromPEM(certs); !ok {
- return nil, fmt.Errorf("Failed to append %s to RootCAs", additionalCA)
- }
- }
-
- // Return Transport for testing purposes
- return &http.Client{
- Timeout: time.Second * timeoutSeconds,
- Transport: &http.Transport{
- TLSClientConfig: &tls.Config{
- RootCAs: caCertPool,
- },
- Proxy: http.ProxyFromEnvironment,
- },
- }, nil
-}
-
-//GetSha256 generates a SHA 256 hash for a given byte array
-func GetSha256(src []byte) (string, error) {
- f := bytes.NewReader(src)
- h := sha256.New()
- if _, err := io.Copy(h, f); err != nil {
- return "", err
- }
- return fmt.Sprintf("%x", h.Sum(nil)), nil
-}
diff --git a/src/jetstream/plugins/monocular/chart-repo/common/types.go b/src/jetstream/plugins/monocular/chart-repo/common/types.go
deleted file mode 100644
index a1fa3a4076..0000000000
--- a/src/jetstream/plugins/monocular/chart-repo/common/types.go
+++ /dev/null
@@ -1,162 +0,0 @@
-/*
-Copyright (c) 2019 The Helm Authors
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package common
-
-import (
- "net/http"
- "sync"
- "time"
-)
-
-//Repo holds information to identify a repository
-type Repo struct {
- Name string
- URL string
- AuthorizationHeader string `bson:"-"`
-}
-
-//Maintainer describes the maintainer of a Chart
-type Maintainer struct {
- Name string
- Email string
-}
-
-//Chart holds full descriptor of a Helm chart
-type Chart struct {
- ID string `bson:"_id"`
- Name string
- Repo Repo
- Description string
- Home string
- Keywords []string
- Maintainers []Maintainer
- Sources []string
- Icon string
- ChartVersions []ChartVersion
-}
-
-//ChartVersion holds version information on a Chart
-type ChartVersion struct {
- Version string
- AppVersion string
- Created time.Time
- Digest string
- URLs []string
-}
-
-//ChartFiles describes the chart values, readme, schema and digest components of a chart
-type ChartFiles struct {
- ID string `bson:"_id"`
- Readme string
- Values string
- Schema string
- Repo Repo
- Digest string
-}
-
-//RepoCheck describes the state of a repository in terms its current checksum and last update time.
-//It is used to determine whether or not to re-sync a respository.
-type RepoCheck struct {
- ID string `bson:"_id"`
- LastUpdate time.Time `bson:"last_update"`
- Checksum string `bson:"checksum"`
-}
-
-//ImportChartFilesJob contains the information needed by an
-//ImportWorker when import a chart from a repository
-type ImportChartFilesJob struct {
- Name string
- Repo Repo
- ChartVersion ChartVersion
-}
-
-//HTTPClient defines a behaviour for making HTTP requests
-type HTTPClient interface {
- Do(req *http.Request) (*http.Response, error)
-}
-
-type RepoSyncStatus struct {
- Repo string `json:"repo"`
- URL string `json:"url"`
- Status string `json:"status"`
-}
-
-type SyncStatusMap struct {
- mut sync.Mutex
- statusMap map[string]RepoSyncStatus
-}
-
-func NewSyncStatusMap() *SyncStatusMap {
- return &SyncStatusMap{sync.Mutex{}, make(map[string]RepoSyncStatus)}
-}
-
-func (m *SyncStatusMap) Set(repo string, status RepoSyncStatus) {
- m.mut.Lock()
- defer m.mut.Unlock()
- m.statusMap[repo] = status
-}
-
-func (m *SyncStatusMap) Get(repo string) RepoSyncStatus {
- m.mut.Lock()
- defer m.mut.Unlock()
- return m.statusMap[repo]
-}
-
-const SyncStatusFailed = "Failed"
-const SyncStatusSynced = "Synchronized"
-const SyncStatusInProgress = "Synchronizing"
-
-type RepoDeleteStatus struct {
- Repo string `json:"repo"`
- URL string `json:"url"`
- Status string `json:"status"`
-}
-
-type DeleteStatusMap struct {
- mut sync.Mutex
- statusMap map[string]RepoDeleteStatus
-}
-
-func NewDeleteStatusMap() *DeleteStatusMap {
- return &DeleteStatusMap{sync.Mutex{}, make(map[string]RepoDeleteStatus)}
-}
-
-func (m *DeleteStatusMap) Set(repo string, status RepoDeleteStatus) {
- m.mut.Lock()
- defer m.mut.Unlock()
- m.statusMap[repo] = status
-}
-
-func (m *DeleteStatusMap) Get(repo string) RepoDeleteStatus {
- m.mut.Lock()
- defer m.mut.Unlock()
- return m.statusMap[repo]
-}
-
-const DeleteStatusFailed = "Failed"
-const DeleteStatusDeleted = "Deleted"
-const DeleteStatusInProgress = "Deleting"
-
-type SyncJobStatusResponse struct {
- UUID string `json:"uuid"`
- Status string `json:"status"`
-}
-
-type DeleteJobStatusResponse struct {
- UUID string `json:"uuid"`
- Status string `json:"status"`
-}
diff --git a/src/jetstream/plugins/monocular/chart-repo/delete.go b/src/jetstream/plugins/monocular/chart-repo/delete.go
deleted file mode 100644
index 17b1e2dc43..0000000000
--- a/src/jetstream/plugins/monocular/chart-repo/delete.go
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
-Copyright (c) 2018 The Helm Authors
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package main
-
-import (
- "github.com/helm/monocular/chartrepo/foundationdb"
-
- log "github.com/sirupsen/logrus"
- "github.com/spf13/cobra"
-)
-
-//DeleteCmd Delete a chart repository from Monocular
-var DeleteCmd = &cobra.Command{
- Use: "delete [REPO NAME]",
- Short: "delete a chart repository",
- Run: func(cmd *cobra.Command, args []string) {
- if len(args) != 1 {
- log.Info("Need exactly one argument: [REPO NAME]")
- cmd.Help()
- return
- }
-
- foundationdb.Delete(cmd, args)
- },
-}
diff --git a/src/jetstream/plugins/monocular/chart-repo/foundationdb/datastore.go b/src/jetstream/plugins/monocular/chart-repo/foundationdb/datastore.go
deleted file mode 100644
index b5eaf598c0..0000000000
--- a/src/jetstream/plugins/monocular/chart-repo/foundationdb/datastore.go
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
-Copyright (c) 2019 The Helm Authors
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package foundationdb
-
-import (
- "context"
- "fmt"
- "time"
-
- log "github.com/sirupsen/logrus"
- "go.mongodb.org/mongo-driver/mongo"
- "go.mongodb.org/mongo-driver/mongo/options"
-)
-
-const defaultTimeout = 30 * time.Second
-
-// Config configures the database connection
-type Config struct {
- URL string
- Database string
- Timeout time.Duration
-}
-
-// Client is an interface for a MongoDB client
-type Client interface {
- Database(name string) (Database, func())
-}
-
-// Database is an interface for accessing a MongoDB database
-type Database interface {
- Collection(name string) Collection
-}
-
-// Collection is an interface for accessing a MongoDB collection
-type Collection interface {
- BulkWrite(ctxt context.Context, operations []mongo.WriteModel, options *options.BulkWriteOptions) (*mongo.BulkWriteResult, error)
- DeleteMany(ctxt context.Context, filter interface{}, options *options.DeleteOptions) (*mongo.DeleteResult, error)
- FindOne(ctxt context.Context, filter interface{}, result interface{}, options *options.FindOneOptions) error
- InsertOne(ctxt context.Context, document interface{}, options *options.InsertOneOptions) (*mongo.InsertOneResult, error)
- UpdateOne(ctxt context.Context, filter interface{}, update interface{}, options *options.UpdateOptions) (*mongo.UpdateResult, error)
-}
-
-// mongoDatabase wraps an mongo.Database and implements Database
-type mongoDatabase struct {
- Database *mongo.Database
-}
-
-// mongoClient wraps an mongo.Database and implements Database
-type mongoClient struct {
- Client *mongo.Client
-}
-
-//NewDocLayerClient creates a mongoDB client using the given options
-func NewDocLayerClient(ctx context.Context, options *options.ClientOptions) (Client, error) {
- client, err := mongo.Connect(ctx, options)
- return &mongoClient{client}, err
-}
-
-//Database Creates a new interface for accessing the specified FDB Document-Layer database
-func (c *mongoClient) Database(dbName string) (Database, func()) {
-
- db := &mongoDatabase{c.Client.Database(dbName)}
-
- return db, func() {
- err := c.Client.Disconnect(context.Background())
-
- if err != nil {
- log.Fatal(err)
- }
- fmt.Println("Connection to MongoDB closed.")
- }
-}
-
-//Collection returns a reference to a given collection in the FDB Document-layer
-func (d *mongoDatabase) Collection(name string) Collection {
- return &mongoCollection{d.Database.Collection(name)}
-}
-
-// mongoCollection wraps a mongo.Collection and implements Collection
-type mongoCollection struct {
- Collection *mongo.Collection
-}
-
-func (c *mongoCollection) BulkWrite(ctxt context.Context, operations []mongo.WriteModel, options *options.BulkWriteOptions) (*mongo.BulkWriteResult, error) {
- res, err := c.Collection.BulkWrite(ctxt, operations, options)
- return res, err
-}
-
-func (c *mongoCollection) DeleteMany(ctxt context.Context, filter interface{}, options *options.DeleteOptions) (*mongo.DeleteResult, error) {
- res, err := c.Collection.DeleteMany(ctxt, filter, options)
- return res, err
-}
-
-func (c *mongoCollection) FindOne(ctxt context.Context, filter interface{}, result interface{}, options *options.FindOneOptions) error {
- res := c.Collection.FindOne(ctxt, filter, options)
- return res.Decode(result)
-}
-
-func (c *mongoCollection) InsertOne(ctxt context.Context, document interface{}, options *options.InsertOneOptions) (*mongo.InsertOneResult, error) {
- res, err := c.Collection.InsertOne(ctxt, document, options)
- return res, err
-}
-
-func (c *mongoCollection) UpdateOne(ctxt context.Context, filter interface{}, document interface{}, options *options.UpdateOptions) (*mongo.UpdateResult, error) {
- res, err := c.Collection.UpdateOne(ctxt, filter, document, options)
- return res, err
-}
diff --git a/src/jetstream/plugins/monocular/chart-repo/foundationdb/delete.go b/src/jetstream/plugins/monocular/chart-repo/foundationdb/delete.go
deleted file mode 100644
index d5f29445ab..0000000000
--- a/src/jetstream/plugins/monocular/chart-repo/foundationdb/delete.go
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
-Copyright (c) 2019 The Helm Authors
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package foundationdb
-
-import (
- "context"
- "time"
-
- log "github.com/sirupsen/logrus"
- "github.com/spf13/cobra"
- "go.mongodb.org/mongo-driver/mongo/options"
-)
-
-//Delete Deletes a chart repository from FoundationDB Document-Layer
-func Delete(cmd *cobra.Command, args []string) {
-
- fdbURL, err := cmd.Flags().GetString("doclayer-url")
- if err != nil {
- log.Fatal(err)
- }
- fDB, err := cmd.Flags().GetString("doclayer-database")
- if err != nil {
- log.Fatal(err)
- }
- debug, err := cmd.Flags().GetBool("debug")
- if err != nil {
- log.Fatal(err)
- }
- if debug {
- log.SetLevel(log.DebugLevel)
- }
-
- log.Debugf("Creating client for FDB: %v, %v, %v", fdbURL, fDB, debug)
- clientOptions := options.Client().ApplyURI(fdbURL).SetMinPoolSize(10).SetMaxPoolSize(100)
- ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
- defer cancel()
- client, err := NewDocLayerClient(ctx, clientOptions)
- if err != nil {
- log.Fatalf("Can't create client for FoundationDB document layer: %v URL provided was: %v", err, fdbURL)
- return
- }
-
- log.Debugf("Client created.")
-
- //Close db client after serving request
- var clientKeepAlive = false
- if err = DeleteRepo(client, fDB, args[0], clientKeepAlive); err != nil {
- log.Fatalf("Can't delete chart repository %s from database: %v", args[0], err, clientKeepAlive)
- }
-
- log.Infof("Successfully deleted the chart repository %s from database", args[0])
-}
diff --git a/src/jetstream/plugins/monocular/chart-repo/foundationdb/mockstore.go b/src/jetstream/plugins/monocular/chart-repo/foundationdb/mockstore.go
deleted file mode 100644
index eaac71db68..0000000000
--- a/src/jetstream/plugins/monocular/chart-repo/foundationdb/mockstore.go
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
-Copyright (c) 2019 The Helm Authors
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package foundationdb
-
-import (
- "context"
-
- "github.com/stretchr/testify/mock"
- "go.mongodb.org/mongo-driver/mongo"
- "go.mongodb.org/mongo-driver/mongo/options"
-)
-
-//mockDatabase acts as a mock datastore.Database
-type mockDatabase struct {
- *mock.Mock
-}
-
-type mockClient struct {
- *mock.Mock
-}
-
-//NewMockClient returns a mocked Document-Layer client
-func NewMockClient(m *mock.Mock) Client {
- return mockClient{m}
-}
-
-//Database returns a mocked datastore.Database and empty closer function
-func (c mockClient) Database(dbName string) (Database, func()) {
-
- db := mockDatabase{c.Mock}
-
- return db, func() {
- }
-}
-
-func (d mockDatabase) Collection(name string) Collection {
- return mockCollection{d.Mock}
-}
-
-// mockCollection acts as a mock datastore.Collection
-type mockCollection struct {
- *mock.Mock
-}
-
-func (c mockCollection) BulkWrite(ctxt context.Context, operations []mongo.WriteModel, options *options.BulkWriteOptions) (*mongo.BulkWriteResult, error) {
- args := c.Called(ctxt, operations, options)
- return args.Get(0).(*mongo.BulkWriteResult), args.Error(1)
-}
-
-func (c mockCollection) DeleteMany(ctxt context.Context, filter interface{}, options *options.DeleteOptions) (*mongo.DeleteResult, error) {
- args := c.Called(ctxt, filter, options)
- return args.Get(0).(*mongo.DeleteResult), args.Error(1)
-}
-
-func (c mockCollection) FindOne(ctxt context.Context, filter interface{}, result interface{}, options *options.FindOneOptions) error {
- args := c.Called(ctxt, filter, result, options)
- return args.Error(0)
-}
-
-func (c mockCollection) InsertOne(ctxt context.Context, document interface{}, options *options.InsertOneOptions) (*mongo.InsertOneResult, error) {
- args := c.Called(ctxt, document, options)
- return args.Get(0).(*mongo.InsertOneResult), args.Error(1)
-}
-
-func (c mockCollection) UpdateOne(ctxt context.Context, filter interface{}, document interface{}, options *options.UpdateOptions) (*mongo.UpdateResult, error) {
- args := c.Called(ctxt, filter, document, options)
- return args.Get(0).(*mongo.UpdateResult), args.Error(1)
-}
diff --git a/src/jetstream/plugins/monocular/chart-repo/foundationdb/sync.go b/src/jetstream/plugins/monocular/chart-repo/foundationdb/sync.go
deleted file mode 100644
index 6db32d6b64..0000000000
--- a/src/jetstream/plugins/monocular/chart-repo/foundationdb/sync.go
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
-Copyright (c) 2018 The Helm Authors
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package foundationdb
-
-import (
- "context"
- "os"
- "time"
-
- log "github.com/sirupsen/logrus"
- "github.com/spf13/cobra"
- "go.mongodb.org/mongo-driver/mongo/options"
-)
-
-//Sync Add a new chart repository to FoundationDB Document-Layer and periodically sync it
-func Sync(cmd *cobra.Command, args []string) {
-
- fdbURL, err := cmd.Flags().GetString("doclayer-url")
- if err != nil {
- log.Fatal(err)
- }
- fDB, err := cmd.Flags().GetString("doclayer-database")
- if err != nil {
- log.Fatal(err)
- }
- debug, err := cmd.Flags().GetBool("debug")
- if err != nil {
- log.Fatal(err)
- }
- if debug {
- log.SetLevel(log.DebugLevel)
- }
-
- log.Debugf("Creating client for FDB: %v, %v, %v", fdbURL, fDB, debug)
- clientOptions := options.Client().ApplyURI(fdbURL).SetMinPoolSize(10).SetMaxPoolSize(100)
- ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
- defer cancel()
- client, err := NewDocLayerClient(ctx, clientOptions)
- if err != nil {
- log.Fatalf("Can't create client for FoundationDB document layer: %v. URL provided was: %v", err, fdbURL)
- return
- }
-
- log.Debugf("Client created.")
-
- startTime := time.Now()
- //Close db client after serving request
- var clientKeepAlive = false
- authorizationHeader := os.Getenv("AUTHORIZATION_HEADER")
- if err = SyncRepo(client, fDB, args[0], args[1], authorizationHeader, clientKeepAlive); err != nil {
- log.Fatalf("Can't add chart repository to database: %v", err)
- return
- }
- timeTaken := time.Since(startTime).Seconds()
- log.Infof("Successfully added the chart repository %s to database in %v seconds", args[0], timeTaken)
-}
diff --git a/src/jetstream/plugins/monocular/chart-repo/foundationdb/utils.go b/src/jetstream/plugins/monocular/chart-repo/foundationdb/utils.go
deleted file mode 100644
index 926ed920ad..0000000000
--- a/src/jetstream/plugins/monocular/chart-repo/foundationdb/utils.go
+++ /dev/null
@@ -1,531 +0,0 @@
-/*
-Copyright (c) 2019 The Helm Authors
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package foundationdb
-
-import (
- "archive/tar"
- "bytes"
- "compress/gzip"
- "context"
- "errors"
- "fmt"
- "io/ioutil"
- "net/http"
- "strings"
- "sync"
- "time"
-
- "github.com/helm/monocular/chartrepo/common"
- "github.com/helm/monocular/chartrepo/utils"
-
- "github.com/disintegration/imaging"
- log "github.com/sirupsen/logrus"
-
- "go.mongodb.org/mongo-driver/bson"
- "go.mongodb.org/mongo-driver/mongo"
- "go.mongodb.org/mongo-driver/mongo/options"
-)
-
-const (
- chartCollection = "charts"
- repositoryCollection = "repos"
- chartFilesCollection = "files"
- defaultTimeoutSeconds = 10
- additionalCAFile = "/usr/local/share/ca-certificates/ca.crt"
-)
-
-var netClient common.HTTPClient = &http.Client{}
-var repoSyncStatus *common.SyncStatusMap = common.NewSyncStatusMap()
-var repoDeleteStatus *common.DeleteStatusMap = common.NewDeleteStatusMap()
-
-func init() {
- var err error
- netClient, err = common.InitNetClient(additionalCAFile, defaultTimeoutSeconds)
- if err != nil {
- log.Fatal(err)
- }
-}
-
-func createStatus(repoName, repoUrl, status string) common.RepoSyncStatus {
- syncStatus := common.RepoSyncStatus{}
- syncStatus.Repo = repoName
- syncStatus.URL = repoUrl
- syncStatus.Status = status
- return syncStatus
-}
-
-// SyncRepo Syncing is performed in the following steps:
-// 1. Update database to match chart metadata from index
-// 2. Concurrently process icons for charts (concurrently)
-// 3. Concurrently process the README and values.yaml for the latest chart version of each chart
-// 4. Concurrently process READMEs and values.yaml for historic chart versions
-//
-// These steps are processed in this way to ensure relevant chart data is
-// imported into the database as fast as possible. E.g. we want all icons for
-// charts before fetching readmes for each chart and version pair.
-func SyncRepo(dbClient Client, dbName, repoName, repoURL string, authorizationHeader string, clientKeepAlive bool) error {
-
- repoSyncStatus.Set(repoName, createStatus(repoName, repoURL, common.SyncStatusInProgress))
-
- db, closer := dbClient.Database(dbName)
-
- log.Infof("Keeping client alive: %v", clientKeepAlive)
- if !clientKeepAlive {
- defer closer()
- }
-
- url, err := common.ParseRepoURL(repoURL)
- if err != nil {
- log.WithFields(log.Fields{"url": repoURL}).WithError(err).Error("Failed to parse Repo URL")
- repoSyncStatus.Set(repoName, common.RepoSyncStatus{"repoName", repoURL, common.SyncStatusFailed})
- return err
- }
-
- log.Debugf("Checking database connection and readiness...")
- collection := db.Collection("numbers")
- ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
- res, err := collection.InsertOne(ctx, bson.M{"name": "pi", "value": 3.14159}, options.InsertOne())
- if err != nil {
- log.Errorf("Database readiness/connection test failed: %v", err)
- cancel()
- repoSyncStatus.Set(repoName, createStatus(repoName, repoURL, common.SyncStatusFailed))
- return err
- }
- id := res.InsertedID
- collection.DeleteMany(ctx, bson.M{"_id": id}, options.Delete())
- cancel()
- log.Debugf("Database connection test successful.")
- log.Debugf("Inserted a test document to test collection with ID: %v", id)
-
- r := common.Repo{Name: repoName, URL: url.String(), AuthorizationHeader: authorizationHeader}
- repoBytes, err := common.FetchRepoIndex(r, netClient)
- if err != nil {
- log.Errorf("Failed to fetch repo index: %v", err)
-
- repoSyncStatus.Set(repoName, createStatus(repoName, repoURL, common.SyncStatusFailed))
- return err
- }
-
- repoChecksum, err := common.GetSha256(repoBytes)
- if err != nil {
- log.Errorf("Failed to generate repo checksum: %v", err)
- repoSyncStatus.Set(repoName, createStatus(repoName, repoURL, common.SyncStatusFailed))
- return err
- }
-
- // Check if the repo has been already processed
- if repoAlreadyProcessed(db, repoName, repoChecksum) {
- log.WithFields(log.Fields{"url": repoURL}).Info("Skipping repository since there are no updates")
- repoSyncStatus.Set(repoName, createStatus(repoName, repoURL, common.SyncStatusSynced))
- return nil
- }
-
- index, err := common.ParseRepoIndex(repoBytes)
- if err != nil {
- log.Errorf("Error parsing repo index: %v", err)
- repoSyncStatus.Set(repoName, createStatus(repoName, repoURL, common.SyncStatusFailed))
- return err
- }
-
- charts := common.ChartsFromIndex(index, r)
- log.Debugf("%v Charts in index of repo: %v", len(charts), repoURL)
- if len(charts) == 0 {
- repoSyncStatus.Set(repoName, createStatus(repoName, repoURL, common.SyncStatusSynced))
- return errors.New("no charts in repository index")
- }
-
- err = importCharts(db, dbName, charts)
- if err != nil {
- log.Errorf("Error importing charts: %v", err)
- repoSyncStatus.Set(repoName, createStatus(repoName, repoURL, common.SyncStatusFailed))
- return err
- }
-
- // Process 10 charts at a time
- numWorkers := 10
- iconJobs := make(chan common.Chart, numWorkers)
- chartFilesJobs := make(chan common.ImportChartFilesJob, numWorkers)
- var wg sync.WaitGroup
-
- log.Debugf("starting %d workers", numWorkers)
- for i := 0; i < numWorkers; i++ {
- wg.Add(1)
- go importWorker(db, &wg, iconJobs, chartFilesJobs)
- }
-
- // Enqueue jobs to process chart icons
- for _, c := range charts {
- iconJobs <- c
- }
- // Close the iconJobs channel to signal the worker pools to move on to the
- // chart files jobs
- close(iconJobs)
-
- // Iterate through the list of charts and enqueue the latest chart version to
- // be processed. Append the rest of the chart versions to a list to be
- // enqueued later
- var toEnqueue []common.ImportChartFilesJob
- for _, c := range charts {
- chartFilesJobs <- common.ImportChartFilesJob{Name: c.Name, Repo: c.Repo, ChartVersion: c.ChartVersions[0]}
- for _, cv := range c.ChartVersions[1:] {
- toEnqueue = append(toEnqueue, common.ImportChartFilesJob{Name: c.Name, Repo: c.Repo, ChartVersion: cv})
- }
- }
-
- // Enqueue all the remaining chart versions
- for _, cfj := range toEnqueue {
- chartFilesJobs <- cfj
- }
- // Close the chartFilesJobs channel to signal the worker pools that there are
- // no more jobs to process
- close(chartFilesJobs)
-
- // Wait for the worker pools to finish processing
- wg.Wait()
-
- // Update cache in the database
- if err = updateLastCheck(db, repoName, repoChecksum, time.Now()); err != nil {
- log.Errorf("Error updating last repo check timestamp: %v", err)
- repoSyncStatus.Set(repoName, common.RepoSyncStatus{"repoName", repoURL, common.SyncStatusFailed})
- return err
- }
- log.WithFields(log.Fields{"Repository": repoName, "URL": repoURL}).Info("Synchronized successfully: ")
- repoSyncStatus.Set(repoName, common.RepoSyncStatus{"repoName", repoURL, common.SyncStatusSynced})
- return nil
-}
-
-func repoAlreadyProcessed(db Database, repoName string, checksum string) bool {
- lastCheck := &common.RepoCheck{}
- filter := bson.M{"_id": repoName}
- err := db.Collection(repositoryCollection).FindOne(context.Background(), filter, lastCheck, options.FindOne())
- return err == nil && checksum == lastCheck.Checksum
-}
-
-func updateLastCheck(db Database, repoName string, checksum string, now time.Time) error {
- selector := bson.M{"_id": repoName}
- update := bson.M{"$set": bson.M{"last_update": now, "checksum": checksum}}
- _, err := db.Collection(repositoryCollection).UpdateOne(context.Background(), selector, update, options.Update())
- return err
-}
-
-func DeleteRepo(dbClient Client, dbName, repoName string, clientKeepAlive bool) error {
- db, closer := dbClient.Database(dbName)
-
- log.Infof("Keeping client alive: %v", clientKeepAlive)
- if !clientKeepAlive {
- defer closer()
- }
-
- log.Debugf("Checking database connection and readiness...")
- collection := db.Collection("numbers")
- ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
- res, err := collection.InsertOne(ctx, bson.M{"name": "pi", "value": 3.14159}, options.InsertOne())
- if err != nil {
- log.Fatalf("Database readiness/connection test failed: %v", err)
- cancel()
- return err
- }
- id := res.InsertedID
- collection.DeleteMany(ctx, bson.M{"_id": id}, options.Delete())
- cancel()
- log.Debugf("Database connection test successful.")
- log.Debugf("Inserted a test document to test collection with ID: %v", id)
-
- collection = db.Collection(chartCollection)
- filter := bson.M{
- "repo.name": repoName,
- }
- deleteResult, err := collection.DeleteMany(context.Background(), filter, options.Delete())
- if err != nil {
- log.Debugf("Error occurred during delete repo (deleting charts from index). Err: %v, Result: %v", err, deleteResult)
- return err
- }
- log.Debugf("Repo delete (delete charts from index) result: %v charts deleted", deleteResult.DeletedCount)
-
- collection = db.Collection(chartFilesCollection)
- deleteResult, err = collection.DeleteMany(context.Background(), filter, options.Delete())
- if err != nil {
- log.Debugf("Error occurred during delete repo (deleting chart files from index). Err: %v, Result: %v", err, deleteResult)
- return err
- }
- log.Debugf("Repo delete (delete chart files from index) result: %v chart files deleted.", deleteResult.DeletedCount)
- collection = db.Collection(repositoryCollection)
- deleteResult, err = collection.DeleteMany(context.Background(), filter, options.Delete())
- if err != nil {
- log.Debugf("Error occurred during delete repo (deleting repositories from index). Err: %v, Result: %v", err, deleteResult)
- return err
- }
- log.Debugf("Repo delete (delete chart files from index) result: %v repositories deleted.", deleteResult.DeletedCount)
-
- return err
-}
-
-func importCharts(db Database, dbName string, charts []common.Chart) error {
- var operations []mongo.WriteModel
- var chartIDs []string
- for _, c := range charts {
- operation := mongo.NewUpdateOneModel()
- chartIDs = append(chartIDs, c.ID)
- // charts to upsert - pair of filter, chart
- operation.SetFilter(bson.M{
- "_id": c.ID,
- })
-
- chartBSON, err := bson.Marshal(&c)
- var doc bson.M
- bson.Unmarshal(chartBSON, &doc)
- delete(doc, "_id")
-
- if err != nil {
- log.Debugf("Error marshalling chart to BSON: %v. Skipping this chart.", err)
- } else {
- update := doc
- operation.SetUpdate(update)
- operation.SetUpsert(true)
- operations = append(operations, operation)
- }
- log.Debugf("Adding chart insert operation for chart: %v", c.ID)
- }
-
- //Must use bulk write for array of filters
- collection := db.Collection(chartCollection)
- updateResult, err := collection.BulkWrite(
- context.Background(),
- operations,
- options.BulkWrite(),
- )
-
- //Set upsert flag and upsert the pairs here
- //Updates our index for charts that we already have and inserts charts that are new
- if err != nil {
- log.Debugf("Error occurred during chart import (upsert many). Err: %v", err)
- return err
- }
- log.Debugf("Upsert chart index success. %v documents inserted, %v documents upserted, %v documents modified", updateResult.InsertedCount, updateResult.UpsertedCount, updateResult.ModifiedCount)
-
- //Remove from our index, any charts that no longer exist
- filter := bson.M{
- "_id": bson.M{
- "$nin": chartIDs,
- },
- "repo.name": charts[0].Repo.Name,
- }
- deleteResult, err := collection.DeleteMany(context.Background(), filter, options.Delete())
- if err != nil {
- log.Debugf("Error occurred during chart import (delete many). Err: %v", err)
- return err
- }
- log.Debugf("Delete stale charts from index success. %v documents deleted.", deleteResult.DeletedCount)
-
- return err
-}
-
-func importWorker(db Database, wg *sync.WaitGroup, icons <-chan common.Chart, chartFiles <-chan common.ImportChartFilesJob) {
- defer wg.Done()
- for c := range icons {
- log.WithFields(log.Fields{"name": c.Name}).Debug("importing icon")
- if err := fetchAndImportIcon(db, c); err != nil {
- log.WithFields(log.Fields{"name": c.Name}).WithError(err).Warn("failed to import icon")
- }
- }
- for j := range chartFiles {
- log.WithFields(log.Fields{"name": j.Name, "version": j.ChartVersion.Version}).Debug("importing readme and values")
- if err := fetchAndImportFiles(db, j.Name, j.Repo, j.ChartVersion); err != nil {
- log.WithFields(log.Fields{"name": j.Name, "version": j.ChartVersion.Version}).WithError(err).Warn("failed to import files")
- }
- }
-}
-
-func fetchAndImportIcon(db Database, c common.Chart) error {
- if c.Icon == "" {
- log.WithFields(log.Fields{"name": c.Name}).Debug("icon not found")
- return nil
- }
-
- req, err := http.NewRequest("GET", c.Icon, nil)
- if err != nil {
- return err
- }
- req.Header.Set("User-Agent", utils.UserAgent())
- if len(c.Repo.AuthorizationHeader) > 0 {
- req.Header.Set("Authorization", c.Repo.AuthorizationHeader)
- }
-
- res, err := netClient.Do(req)
- if res != nil {
- defer res.Body.Close()
- }
- if err != nil {
- return err
- }
-
- if res.StatusCode != http.StatusOK {
- return fmt.Errorf("%d %s", res.StatusCode, c.Icon)
- }
-
- b := []byte{}
- contentType := ""
- if strings.Contains(res.Header.Get("Content-Type"), "image/svg") {
- // if the icon is a SVG file simply read it
- b, err = ioutil.ReadAll(res.Body)
- if err != nil {
- return err
- }
- contentType = res.Header.Get("Content-Type")
- } else {
- // if the icon is in any other format try to convert it to PNG
- orig, err := imaging.Decode(res.Body)
- if err != nil {
- log.WithFields(log.Fields{"name": c.Name}).WithError(err).Error("failed to decode icon")
- return err
- }
-
- // TODO: make this configurable?
- icon := imaging.Fit(orig, 160, 160, imaging.Lanczos)
-
- var buf bytes.Buffer
- imaging.Encode(&buf, icon, imaging.PNG)
- b = buf.Bytes()
- contentType = "image/png"
- }
-
- collection := db.Collection(chartCollection)
- //Update single icon
- update := bson.M{"$set": bson.M{"raw_icon": b, "icon_content_type": contentType}}
- filter := bson.M{"_id": c.ID}
- updateResult, err := collection.UpdateOne(context.Background(), filter, update, options.Update())
- if err != nil {
- log.Debugf("Error occurred during chart icon import (update one). Err: %v, Result: %v", err, updateResult)
- return err
- }
- return err
-}
-
-func fetchAndImportFiles(db Database, name string, r common.Repo, cv common.ChartVersion) error {
-
- chartFilesID := fmt.Sprintf("%s/%s-%s", r.Name, name, cv.Version)
- //Check if we already have indexed files for this chart version and digest
- collection := db.Collection(chartFilesCollection)
- filter := bson.M{"_id": chartFilesID, "digest": cv.Digest}
- findResult := collection.FindOne(context.Background(), filter, &common.ChartFiles{}, options.FindOne())
- if findResult != mongo.ErrNoDocuments {
- log.WithFields(log.Fields{"name": name, "version": cv.Version}).Debug("skipping existing files")
- return nil
- }
- log.WithFields(log.Fields{"name": name, "version": cv.Version}).Debug("fetching files")
-
- url := common.ChartTarballURL(r, cv)
- req, err := http.NewRequest("GET", url, nil)
- if err != nil {
- return err
- }
- req.Header.Set("User-Agent", utils.UserAgent())
- if len(r.AuthorizationHeader) > 0 {
- req.Header.Set("Authorization", r.AuthorizationHeader)
- }
-
- res, err := netClient.Do(req)
- if err != nil {
- return err
- }
- defer res.Body.Close()
-
- // We read the whole chart into memory, this should be okay since the chart
- // tarball needs to be small enough to fit into a GRPC call (Tiller
- // requirement)
- gzf, err := gzip.NewReader(res.Body)
- if err != nil {
- return err
- }
- defer gzf.Close()
-
- tarf := tar.NewReader(gzf)
-
- readmeFileName := name + "/README.md"
- valuesFileName := name + "/values.yaml"
- schemaFileName := name + "/values.schema.json"
- filenames := []string{valuesFileName, readmeFileName, schemaFileName}
-
- files, err := common.ExtractFilesFromTarball(filenames, tarf)
- if err != nil {
- return err
- }
-
- chartFiles := common.ChartFiles{ID: chartFilesID, Repo: r, Digest: cv.Digest}
- if v, ok := files[readmeFileName]; ok {
- chartFiles.Readme = v
- } else {
- log.WithFields(log.Fields{"name": name, "version": cv.Version}).Warn("README.md not found")
- }
- if v, ok := files[valuesFileName]; ok {
- chartFiles.Values = v
- } else {
- log.WithFields(log.Fields{"name": name, "version": cv.Version}).Warn("values.yaml not found")
- }
- if v, ok := files[schemaFileName]; ok {
- chartFiles.Schema = v
- } else {
- log.WithFields(log.Fields{"name": name, "version": cv.Version}).Warn("values.schema.json not found")
- }
-
- // inserts the chart files if not already indexed, or updates the existing
- // entry if digest has changed
- log.Debugf("Inserting chart files %v to collection: %v....", chartFilesID, chartFilesCollection)
- collection = db.Collection(chartFilesCollection)
- filter = bson.M{"_id": chartFilesID}
- chartBSON, err := bson.Marshal(&chartFiles)
- var doc bson.M
- bson.Unmarshal(chartBSON, &doc)
- delete(doc, "_id")
- update := bson.M{"$set": doc}
- updateResult, err := collection.UpdateOne(context.Background(), filter, update, options.Update().SetUpsert(true))
- if err != nil {
- log.Debugf("Error occurred during chart files import (update one). Chart files : %v doc: %v Err: %v", chartFiles, doc, err)
- return err
- }
- log.Debugf("Chart files import success. (update one) Upserted: %v Updated: %v", updateResult.UpsertedCount, updateResult.ModifiedCount)
- return nil
-}
-
-func GetRepoSyncStatus(repo string) (common.RepoSyncStatus, error) {
- status := repoSyncStatus.Get(repo)
- if status == (common.RepoSyncStatus{}) {
- return status, errors.New("Repo does not exist in database.")
- }
- return status, nil
-}
-
-func GetRepoDeleteStatus(repo string) (common.RepoDeleteStatus, error) {
- status := repoDeleteStatus.Get(repo)
- if status == (common.RepoDeleteStatus{}) {
- return status, errors.New("Repo does not exist in database.")
- }
- return status, nil
-}
-
-func database(client *mongo.Client, dbName string) (*mongo.Database, func()) {
-
- db := client.Database(dbName)
- return db, func() {
- err := client.Disconnect(context.Background())
-
- if err != nil {
- log.Fatal(err)
- }
- fmt.Println("Connection to MongoDB closed.")
- }
-}
diff --git a/src/jetstream/plugins/monocular/chart-repo/foundationdb/utils_test.go b/src/jetstream/plugins/monocular/chart-repo/foundationdb/utils_test.go
deleted file mode 100644
index e435012458..0000000000
--- a/src/jetstream/plugins/monocular/chart-repo/foundationdb/utils_test.go
+++ /dev/null
@@ -1,696 +0,0 @@
-/*
-Copyright (c) 2018 The Helm Authors
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package foundationdb
-
-import (
- "archive/tar"
- "bytes"
- "compress/gzip"
- "crypto/rand"
- "fmt"
- "image"
- "image/color"
- "io"
- "io/ioutil"
- "net/http"
- "net/http/httptest"
- "os"
- "path"
- "strings"
- "testing"
- "time"
-
- "github.com/helm/monocular/chartrepo/common"
- "github.com/helm/monocular/chartrepo/utils"
-
- "github.com/arschles/assert"
- "github.com/disintegration/imaging"
- log "github.com/sirupsen/logrus"
- "github.com/stretchr/testify/mock"
- "go.mongodb.org/mongo-driver/bson"
- "go.mongodb.org/mongo-driver/mongo"
-)
-
-var validRepoIndexYAMLBytes, _ = ioutil.ReadFile("../testdata/valid-index.yaml")
-var validRepoIndexYAML = string(validRepoIndexYAMLBytes)
-
-var invalidRepoIndexYAML = "invalid"
-
-type badHTTPClient struct{}
-
-func (h *badHTTPClient) Do(req *http.Request) (*http.Response, error) {
- w := httptest.NewRecorder()
- w.WriteHeader(500)
- return w.Result(), nil
-}
-
-type goodHTTPClient struct{}
-
-func (h *goodHTTPClient) Do(req *http.Request) (*http.Response, error) {
- w := httptest.NewRecorder()
- // Don't accept trailing slashes
- if strings.HasPrefix(req.URL.Path, "//") {
- w.WriteHeader(500)
- }
- // If subpath repo URL test, check that index.yaml is correctly added to the
- // subpath
- if req.URL.Host == "subpath.test" && req.URL.Path != "/subpath/index.yaml" {
- w.WriteHeader(500)
- }
-
- w.Write([]byte(validRepoIndexYAML))
- return w.Result(), nil
-}
-
-type badIconClient struct{}
-
-func (h *badIconClient) Do(req *http.Request) (*http.Response, error) {
- w := httptest.NewRecorder()
- w.Write([]byte("not-an-image"))
- return w.Result(), nil
-}
-
-type goodIconClient struct{}
-
-func iconBytes() []byte {
- var b bytes.Buffer
- img := imaging.New(1, 1, color.White)
- imaging.Encode(&b, img, imaging.PNG)
- return b.Bytes()
-}
-
-func (h *goodIconClient) Do(req *http.Request) (*http.Response, error) {
- w := httptest.NewRecorder()
- w.Write(iconBytes())
- return w.Result(), nil
-}
-
-type svgIconClient struct{}
-
-func (h *svgIconClient) Do(req *http.Request) (*http.Response, error) {
- w := httptest.NewRecorder()
- w.Write([]byte("foo"))
- res := w.Result()
- res.Header.Set("Content-Type", "image/svg")
- return res, nil
-}
-
-type goodTarballClient struct {
- c common.Chart
- skipReadme bool
- skipValues bool
- skipSchema bool
-}
-
-var testChartReadme = "# readme for chart\n\nBest chart in town"
-var testChartValues = "image: test"
-var testChartSchema = `{"properties": {}}`
-
-func (h *goodTarballClient) Do(req *http.Request) (*http.Response, error) {
- w := httptest.NewRecorder()
- gzw := gzip.NewWriter(w)
- files := []tarballFile{{h.c.Name + "/Chart.yaml", "should be a Chart.yaml here..."}}
- if !h.skipValues {
- files = append(files, tarballFile{h.c.Name + "/values.yaml", testChartValues})
- }
- if !h.skipReadme {
- files = append(files, tarballFile{h.c.Name + "/README.md", testChartReadme})
- }
- if !h.skipSchema {
- files = append(files, tarballFile{h.c.Name + "/values.schema.json", testChartSchema})
- }
- createTestTarball(gzw, files)
- gzw.Flush()
- return w.Result(), nil
-}
-
-type authenticatedTarballClient struct {
- c common.Chart
-}
-
-func (h *authenticatedTarballClient) Do(req *http.Request) (*http.Response, error) {
- w := httptest.NewRecorder()
-
- // Ensure we're sending the right Authorization header
- if !strings.Contains(req.Header.Get("Authorization"), "Bearer ThisSecretAccessTokenAuthenticatesTheClient") {
- w.WriteHeader(500)
- } else {
- gzw := gzip.NewWriter(w)
- files := []tarballFile{{h.c.Name + "/Chart.yaml", "should be a Chart.yaml here..."}}
- files = append(files, tarballFile{h.c.Name + "/values.yaml", testChartValues})
- files = append(files, tarballFile{h.c.Name + "/README.md", testChartReadme})
- files = append(files, tarballFile{h.c.Name + "/values.schema.json", testChartSchema})
- createTestTarball(gzw, files)
- gzw.Flush()
- }
- return w.Result(), nil
-}
-
-func Test_syncURLInvalidity(t *testing.T) {
- tests := []struct {
- name string
- repoURL string
- }{
- {"invalid URL", "not-a-url"},
- {"invalid URL", "https//google.com"},
- }
- m := mock.Mock{}
- dbClient := NewMockClient(&m)
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- err := SyncRepo(dbClient, "test", "test", tt.repoURL, "")
- assert.ExistsErr(t, err, tt.name)
- })
- }
-}
-
-func Test_fetchRepoIndex(t *testing.T) {
- tests := []struct {
- name string
- r common.Repo
- }{
- {"valid HTTP URL", common.Repo{URL: "http://my.examplerepo.com"}},
- {"valid HTTPS URL", common.Repo{URL: "https://my.examplerepo.com"}},
- {"valid trailing URL", common.Repo{URL: "https://my.examplerepo.com/"}},
- {"valid subpath URL", common.Repo{URL: "https://subpath.test/subpath/"}},
- {"valid URL with trailing spaces", common.Repo{URL: "https://subpath.test/subpath/ "}},
- {"valid URL with leading spaces", common.Repo{URL: " https://subpath.test/subpath/"}},
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- netClient = &goodHTTPClient{}
- _, err := common.FetchRepoIndex(tt.r, netClient)
- assert.NoErr(t, err)
- })
- }
-
- t.Run("failed request", func(t *testing.T) {
- netClient = &badHTTPClient{}
- _, err := common.FetchRepoIndex(common.Repo{URL: "https://my.examplerepo.com"}, netClient)
- assert.ExistsErr(t, err, "failed request")
- })
-}
-
-func Test_fetchRepoIndexUserAgent(t *testing.T) {
- tests := []struct {
- name string
- version string
- userAgentComment string
- expectedUserAgent string
- }{
- {"default user agent", "", "", "chart-repo/devel"},
- {"custom version no app", "1.0", "", "chart-repo/1.0"},
- {"custom version and app", "1.0", "monocular/1.2", "chart-repo/1.0 (monocular/1.2)"},
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- // Override global variables used to generate the userAgent
- if tt.version != "" {
- utils.Version = tt.version
- }
-
- if tt.userAgentComment != "" {
- utils.UserAgentComment = tt.userAgentComment
- }
-
- server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
- assert.Equal(t, tt.expectedUserAgent, req.Header.Get("User-Agent"), "expected user agent")
- rw.Write([]byte(validRepoIndexYAML))
- }))
- // Close the server when test finishes
- defer server.Close()
-
- netClient = server.Client()
-
- _, err := common.FetchRepoIndex(common.Repo{URL: server.URL}, netClient)
- assert.NoErr(t, err)
- })
- }
-}
-
-func Test_parseRepoIndex(t *testing.T) {
- tests := []struct {
- name string
- repoYAML string
- }{
- {"invalid", "invalid"},
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- _, err := common.ParseRepoIndex([]byte(tt.repoYAML))
- assert.ExistsErr(t, err, tt.name)
- })
- }
-
- t.Run("valid", func(t *testing.T) {
- index, err := common.ParseRepoIndex([]byte(validRepoIndexYAML))
- assert.NoErr(t, err)
- assert.Equal(t, len(index.Entries), 2, "number of charts")
- assert.Equal(t, index.Entries["acs-engine-autoscaler"][0].GetName(), "acs-engine-autoscaler", "chart version populated")
- })
-}
-
-func Test_chartsFromIndex(t *testing.T) {
- r := common.Repo{Name: "test", URL: "http://testrepo.com"}
- index, _ := common.ParseRepoIndex([]byte(validRepoIndexYAML))
- charts := common.ChartsFromIndex(index, r)
- assert.Equal(t, len(charts), 2, "number of charts")
-
- indexWithDeprecated := validRepoIndexYAML + `
- deprecated-chart:
- - name: deprecated-chart
- deprecated: true`
- index2, err := common.ParseRepoIndex([]byte(indexWithDeprecated))
- assert.NoErr(t, err)
- charts = common.ChartsFromIndex(index2, r)
- assert.Equal(t, len(charts), 2, "number of charts")
-}
-
-func Test_newChart(t *testing.T) {
- r := common.Repo{Name: "test", URL: "http://testrepo.com"}
- index, _ := common.ParseRepoIndex([]byte(validRepoIndexYAML))
- c := common.NewChart(index.Entries["wordpress"], r)
- assert.Equal(t, c.Name, "wordpress", "correctly built")
- assert.Equal(t, len(c.ChartVersions), 2, "correctly built")
- assert.Equal(t, c.Description, "new description!", "takes chart fields from latest entry")
- assert.Equal(t, c.Repo, r, "repo set")
- assert.Equal(t, c.ID, "test/wordpress", "id set")
-}
-
-func Test_importCharts(t *testing.T) {
- m := &mock.Mock{}
- // Ensure Upsert func is called with some arguments
- m.On("BulkWrite", mock.Anything, mock.Anything, mock.Anything).Return(&mongo.BulkWriteResult{}, nil)
- m.On("DeleteMany", mock.Anything, mock.Anything, mock.Anything).Return(&mongo.DeleteResult{}, nil)
- dbClient := NewMockClient(m)
- db, _ := dbClient.Database("test")
- index, _ := common.ParseRepoIndex([]byte(validRepoIndexYAML))
- charts := common.ChartsFromIndex(index, common.Repo{Name: "test", URL: "http://testrepo.com"})
- importCharts(db, "test", charts)
-
- m.AssertExpectations(t)
- // The BulkWrite method takes an array of WriteModels.
- // For x charts to upsert, there should be x elements.
- // Each element has a selector (filter) and an "update" - the update document to apply
- args := m.Calls[0].Arguments.Get(1).([]mongo.WriteModel)
- assert.Equal(t, len(args), len(charts), "number of charts to upsert")
- for i := 0; i < len(args); i++ {
- updateModel := args[i].(*mongo.UpdateOneModel)
- assert.Equal(t, updateModel.Filter, bson.M{"_id": "test/" + updateModel.Update.(bson.M)["name"].(string)}, "selector")
- }
-}
-
-func Test_DeleteRepo(t *testing.T) {
- m := &mock.Mock{}
- //Expect a few calls to test the DB readiness
- m.On("InsertOne", mock.Anything, mock.Anything, mock.Anything).Return(&mongo.InsertOneResult{}, nil)
- m.On("DeleteMany", mock.Anything, mock.Anything, mock.Anything).Return(&mongo.DeleteResult{}, nil)
-
- m.On("DeleteMany", mock.Anything, bson.M{
- "repo.name": "test",
- }, mock.Anything).Return(&mongo.DeleteResult{}, nil)
- dbClient := NewMockClient(m)
-
- err := DeleteRepo(dbClient, "test", "test")
- if err != nil {
- t.Errorf("failed to delete chart repo test: %v", err)
- }
- m.AssertExpectations(t)
-}
-
-func Test_fetchAndImportIcon(t *testing.T) {
- t.Run("no icon", func(t *testing.T) {
- m := mock.Mock{}
- dbClient := NewMockClient(&m)
- db, _ := dbClient.Database("test")
- c := common.Chart{ID: "test/acs-engine-autoscaler"}
- assert.NoErr(t, fetchAndImportIcon(db, c))
- })
-
- index, _ := common.ParseRepoIndex([]byte(validRepoIndexYAML))
- charts := common.ChartsFromIndex(index, common.Repo{Name: "test", URL: "http://testrepo.com"})
-
- t.Run("failed download", func(t *testing.T) {
- netClient = &badHTTPClient{}
- c := charts[0]
- m := mock.Mock{}
- dbClient := NewMockClient(&m)
- db, _ := dbClient.Database("test")
- assert.Err(t, fmt.Errorf("500 %s", c.Icon), fetchAndImportIcon(db, c))
- })
-
- t.Run("bad icon", func(t *testing.T) {
- netClient = &badIconClient{}
- c := charts[0]
- m := mock.Mock{}
- dbClient := NewMockClient(&m)
- db, _ := dbClient.Database("test")
- assert.Err(t, image.ErrFormat, fetchAndImportIcon(db, c))
- })
-
- t.Run("valid icon", func(t *testing.T) {
- netClient = &goodIconClient{}
- c := charts[0]
- m := mock.Mock{}
- dbClient := NewMockClient(&m)
- db, _ := dbClient.Database("test")
- m.On("UpdateOne", mock.Anything, bson.M{"_id": c.ID}, bson.M{"$set": bson.M{"raw_icon": iconBytes(), "icon_content_type": "image/png"}}, mock.Anything).Return(&mongo.UpdateResult{}, nil)
- assert.NoErr(t, fetchAndImportIcon(db, c))
- m.AssertExpectations(t)
- })
-
- t.Run("valid SVG icon", func(t *testing.T) {
- netClient = &svgIconClient{}
- c := common.Chart{
- ID: "foo",
- Icon: "https://foo/bar/logo.svg",
- Repo: common.Repo{},
- }
- m := mock.Mock{}
- dbClient := NewMockClient(&m)
- db, _ := dbClient.Database("test")
- m.On("UpdateOne", mock.Anything, bson.M{"_id": c.ID}, bson.M{"$set": bson.M{"raw_icon": []byte("foo"), "icon_content_type": "image/svg"}}, mock.Anything).Return(&mongo.UpdateResult{}, nil)
- assert.NoErr(t, fetchAndImportIcon(db, c))
- m.AssertExpectations(t)
- })
-}
-
-func Test_fetchAndImportFiles(t *testing.T) {
- index, _ := common.ParseRepoIndex([]byte(validRepoIndexYAML))
- charts := common.ChartsFromIndex(index, common.Repo{Name: "test", URL: "http://testrepo.com", AuthorizationHeader: "Bearer ThisSecretAccessTokenAuthenticatesTheClient1s"})
- cv := charts[0].ChartVersions[0]
-
- t.Run("http error", func(t *testing.T) {
- m := mock.Mock{}
- m.On("FindOne", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(mongo.ErrNoDocuments)
- dbClient := NewMockClient(&m)
- db, _ := dbClient.Database("test")
- netClient = &badHTTPClient{}
- assert.Err(t, io.EOF, fetchAndImportFiles(db, charts[0].Name, charts[0].Repo, cv))
- })
-
- t.Run("file not found", func(t *testing.T) {
- netClient = &goodTarballClient{c: charts[0], skipValues: true, skipReadme: true, skipSchema: true}
- m := mock.Mock{}
- m.On("FindOne", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(mongo.ErrNoDocuments)
- chartFilesID := fmt.Sprintf("%s/%s-%s", charts[0].Repo.Name, charts[0].Name, cv.Version)
- chartFiles := common.ChartFiles{ID: chartFilesID, Readme: "", Values: "", Schema: "", Repo: charts[0].Repo, Digest: cv.Digest}
- chartBSON, _ := bson.Marshal(&chartFiles)
- var doc bson.M
- bson.Unmarshal(chartBSON, &doc)
- delete(doc, "_id")
- update := bson.M{"$set": doc}
- m.On("UpdateOne", mock.Anything, bson.M{"_id": chartFilesID}, update, mock.Anything).Return(&mongo.UpdateResult{}, nil)
- dbClient := NewMockClient(&m)
- db, _ := dbClient.Database("test")
- err := fetchAndImportFiles(db, charts[0].Name, charts[0].Repo, cv)
- assert.NoErr(t, err)
- m.AssertExpectations(t)
- })
-
- t.Run("authenticated request", func(t *testing.T) {
- netClient = &authenticatedTarballClient{c: charts[0]}
- m := mock.Mock{}
- m.On("FindOne", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(mongo.ErrNoDocuments)
- chartFilesID := fmt.Sprintf("%s/%s-%s", charts[0].Repo.Name, charts[0].Name, cv.Version)
- chartFiles := common.ChartFiles{ID: chartFilesID, Readme: testChartReadme, Values: testChartValues, Schema: testChartSchema, Repo: charts[0].Repo, Digest: cv.Digest}
- chartBSON, _ := bson.Marshal(&chartFiles)
- var doc bson.M
- bson.Unmarshal(chartBSON, &doc)
- delete(doc, "_id")
- update := bson.M{"$set": doc}
- m.On("UpdateOne", mock.Anything, bson.M{"_id": chartFilesID}, update, mock.Anything).Return(&mongo.UpdateResult{}, nil)
- dbClient := NewMockClient(&m)
- db, _ := dbClient.Database("test")
- err := fetchAndImportFiles(db, charts[0].Name, charts[0].Repo, cv)
- assert.NoErr(t, err)
- m.AssertExpectations(t)
- })
-
- t.Run("valid tarball", func(t *testing.T) {
- netClient = &goodTarballClient{c: charts[0]}
- m := mock.Mock{}
- m.On("FindOne", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(mongo.ErrNoDocuments)
- chartFilesID := fmt.Sprintf("%s/%s-%s", charts[0].Repo.Name, charts[0].Name, cv.Version)
- chartFiles := common.ChartFiles{ID: chartFilesID, Readme: testChartReadme, Values: testChartValues, Schema: testChartSchema, Repo: charts[0].Repo, Digest: cv.Digest}
- chartBSON, _ := bson.Marshal(&chartFiles)
- var doc bson.M
- bson.Unmarshal(chartBSON, &doc)
- delete(doc, "_id")
- update := bson.M{"$set": doc}
- m.On("UpdateOne", mock.Anything, bson.M{"_id": chartFilesID}, update, mock.Anything).Return(&mongo.UpdateResult{}, nil)
- dbClient := NewMockClient(&m)
- db, _ := dbClient.Database("test")
- err := fetchAndImportFiles(db, charts[0].Name, charts[0].Repo, cv)
- assert.NoErr(t, err)
- m.AssertExpectations(t)
- })
-
- t.Run("file exists", func(t *testing.T) {
- m := mock.Mock{}
- // don't return an error when checking if files already exists
- m.On("FindOne", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
- dbClient := NewMockClient(&m)
- db, _ := dbClient.Database("test")
- err := fetchAndImportFiles(db, charts[0].Name, charts[0].Repo, cv)
- assert.NoErr(t, err)
- m.AssertNotCalled(t, "UpdateOne", mock.Anything, mock.Anything, mock.Anything)
- })
-}
-
-func Test_chartTarballURL(t *testing.T) {
- r := common.Repo{Name: "test", URL: "http://testrepo.com"}
- tests := []struct {
- name string
- cv common.ChartVersion
- wanted string
- }{
- {"absolute url", common.ChartVersion{URLs: []string{"http://testrepo.com/wordpress-0.1.0.tgz"}}, "http://testrepo.com/wordpress-0.1.0.tgz"},
- {"relative url", common.ChartVersion{URLs: []string{"wordpress-0.1.0.tgz"}}, "http://testrepo.com/wordpress-0.1.0.tgz"},
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- assert.Equal(t, common.ChartTarballURL(r, tt.cv), tt.wanted, "url")
- })
- }
-}
-
-func Test_extractFilesFromTarball(t *testing.T) {
- tests := []struct {
- name string
- files []tarballFile
- filename string
- want string
- }{
- {"file", []tarballFile{{"file.txt", "best file ever"}}, "file.txt", "best file ever"},
- {"multiple file tarball", []tarballFile{{"file.txt", "best file ever"}, {"file2.txt", "worst file ever"}}, "file2.txt", "worst file ever"},
- {"file in dir", []tarballFile{{"file.txt", "best file ever"}, {"test/file2.txt", "worst file ever"}}, "test/file2.txt", "worst file ever"},
- {"filename ignore case", []tarballFile{{"Readme.md", "# readme for chart"}, {"values.yaml", "key: value"}}, "README.md", "# readme for chart"},
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- var b bytes.Buffer
- createTestTarball(&b, tt.files)
- r := bytes.NewReader(b.Bytes())
- tarf := tar.NewReader(r)
- files, err := common.ExtractFilesFromTarball([]string{tt.filename}, tarf)
- assert.NoErr(t, err)
- assert.Equal(t, files[tt.filename], tt.want, "file body")
- })
- }
-
- t.Run("extract multiple files", func(t *testing.T) {
- var b bytes.Buffer
- tFiles := []tarballFile{{"file.txt", "best file ever"}, {"file2.txt", "worst file ever"}}
- createTestTarball(&b, tFiles)
- r := bytes.NewReader(b.Bytes())
- tarf := tar.NewReader(r)
- files, err := common.ExtractFilesFromTarball([]string{tFiles[0].Name, tFiles[1].Name}, tarf)
- assert.NoErr(t, err)
- assert.Equal(t, len(files), 2, "matches")
- for _, f := range tFiles {
- assert.Equal(t, files[f.Name], f.Body, "file body")
- }
- })
-
- t.Run("file not found", func(t *testing.T) {
- var b bytes.Buffer
- createTestTarball(&b, []tarballFile{{"file.txt", "best file ever"}})
- r := bytes.NewReader(b.Bytes())
- tarf := tar.NewReader(r)
- name := "file2.txt"
- files, err := common.ExtractFilesFromTarball([]string{name}, tarf)
- assert.NoErr(t, err)
- assert.Equal(t, files[name], "", "file body")
- })
-
- t.Run("not a tarball", func(t *testing.T) {
- b := make([]byte, 4)
- rand.Read(b)
- r := bytes.NewReader(b)
- tarf := tar.NewReader(r)
- files, err := common.ExtractFilesFromTarball([]string{"file2.txt"}, tarf)
- assert.Err(t, io.ErrUnexpectedEOF, err)
- assert.Equal(t, len(files), 0, "file body")
- })
-}
-
-type tarballFile struct {
- Name, Body string
-}
-
-func createTestTarball(w io.Writer, files []tarballFile) {
- // Create a new tar archive.
- tarw := tar.NewWriter(w)
-
- // Add files to the archive.
- for _, file := range files {
- hdr := &tar.Header{
- Name: file.Name,
- Mode: 0600,
- Size: int64(len(file.Body)),
- }
- if err := tarw.WriteHeader(hdr); err != nil {
- log.Fatalln(err)
- }
- if _, err := tarw.Write([]byte(file.Body)); err != nil {
- log.Fatalln(err)
- }
- }
- // Make sure to check the error on Close.
- if err := tarw.Close(); err != nil {
- log.Fatal(err)
- }
-}
-
-func Test_initNetClient(t *testing.T) {
- // Test env
- otherDir, err := ioutil.TempDir("", "ca-registry")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(otherDir)
-
- // Create cert
- caCert := `-----BEGIN CERTIFICATE-----
-MIIC6jCCAdKgAwIBAgIUKVfzA7lfBgSYP8enCVhlm0ql5YwwDQYJKoZIhvcNAQEL
-BQAwDTELMAkGA1UEAxMCQ0EwHhcNMTgxMjEyMTQxNzAwWhcNMjMxMjExMTQxNzAw
-WjANMQswCQYDVQQDEwJDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
-ALZU3fsAgvoUuLSHr24apslaYyuX84wGoZQmtFtQ+A3DF9KL/2nn3yZ6qJPkH0TF
-sbObEQRNi+P6vQ3nI/dSNMX5PzMBP2CB6L7zEXzZQEHtAK0Bzva5CKEBGX7OfIKl
-aBvs+dzKVJBdb+Oh0maacMwa4QbcD6ejzF90jUbaO65lpQpcL7KQdppKOGNclRaA
-hQTV2VsxrV4hH7K9btaTTxso+8W6p8v6X9vf40Ywx72p+SKnGh+FCrOp1gYLBLwo
-4SM0OUQHRvqUlj0XhZk5pW0dMRwHcoz1S2GmE5bj4edr4j+zGzGxa2wRGKvM0OCn
-Do84AVszTFPmUf+mCl4pJNECAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1Ud
-EwEB/wQFMAMBAf8wHQYDVR0OBBYEFI5l5k+MEhrbOQ29dOW1qJhI0yKaMA0GCSqG
-SIb3DQEBCwUAA4IBAQByDebUOKzn6jfmXlW62vm09V+ipqId01wm21G9XMtMEqhc
-xtun6YwQeTuGPtdepWG+NXuSsiX/HNAHeaumJaaljHhdKDisnMQ0CTnNsu8NPkAl
-9iMEB3iXLWkb7+HgfPJAHZVGcMqMxNEMZYHB1Fh0G2Ne376X94+GYJ08qR2C8rUP
-BShhMSktB578h4GtPIWSjPhDUWg1fGe7sewR+GPyuL9859hOD0wGm9tUixBKloCu
-b90fhqZZ3FqZD7W1qJGKvz/8geqi0noip+uq/dokK1jarRkOVEJP+EvXkHo0tIuc
-h251U/Daz6NiQBM9AxyAw6EHm8XAZBvCuebfzyrT
------END CERTIFICATE-----`
- otherCA := path.Join(otherDir, "ca.crt")
- err = ioutil.WriteFile(otherCA, []byte(caCert), 0644)
- if err != nil {
- t.Error(err)
- }
-
- _, err = common.InitNetClient(otherCA, defaultTimeoutSeconds)
- if err != nil {
- t.Error(err)
- }
-}
-
-var emptyRepoIndexYAMLBytes, _ = ioutil.ReadFile("testdata/empty-repo-index.yaml")
-var emptyRepoIndexYAML = string(emptyRepoIndexYAMLBytes)
-
-type emptyChartRepoHTTPClient struct{}
-
-func (h *emptyChartRepoHTTPClient) Do(req *http.Request) (*http.Response, error) {
- w := httptest.NewRecorder()
- w.Write([]byte(emptyRepoIndexYAML))
- return w.Result(), nil
-}
-
-func Test_emptyChartRepo(t *testing.T) {
- netClient = &emptyChartRepoHTTPClient{}
- m := mock.Mock{}
- dbClient := NewMockClient(&m)
- //Expect a call to test the DB readiness
- m.On("InsertOne", mock.Anything, mock.Anything, mock.Anything).Return(&mongo.InsertOneResult{}, nil)
- m.On("DeleteMany", mock.Anything, mock.Anything, mock.Anything).Return(&mongo.DeleteResult{}, nil)
-
- m.On("FindOne", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
- err := SyncRepo(dbClient, "test", "testRepo", "https://my.examplerepo.com", "")
- assert.ExistsErr(t, err, "Failed Request")
-}
-
-func Test_getSha256(t *testing.T) {
- sha, err := common.GetSha256([]byte("this is a test"))
- assert.Equal(t, err, nil, "Unable to get sha")
- assert.Equal(t, sha, "2e99758548972a8e8822ad47fa1017ff72f06f3ff6a016851f45c398732bc50c", "Unable to get sha")
-}
-
-func Test_repoAlreadyProcessed(t *testing.T) {
- tests := []struct {
- name string
- checksum string
- mockedLastCheck common.RepoCheck
- processed bool
- }{
- {"not processed yet", "bar", common.RepoCheck{}, false},
- {"already processed", "bar", common.RepoCheck{Checksum: "bar"}, true},
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- m := mock.Mock{}
- repo := &common.RepoCheck{}
- m.On("FindOne", mock.Anything, mock.Anything, repo, mock.Anything).Run(func(args mock.Arguments) {
- *args.Get(2).(*common.RepoCheck) = tt.mockedLastCheck
- }).Return(nil)
- dbClient := NewMockClient(&m)
- db, _ := dbClient.Database("test")
- res := repoAlreadyProcessed(db, "", tt.checksum)
- if res != tt.processed {
- t.Errorf("Expected alreadyProcessed to be %v got %v", tt.processed, res)
- }
- })
- }
-}
-
-func Test_updateLastCheck(t *testing.T) {
- m := mock.Mock{}
- repoName := "foo"
- checksum := "bar"
- now := time.Now()
- selector := bson.M{"_id": repoName}
- m.On("UpdateOne", mock.Anything, selector, bson.M{"$set": bson.M{"last_update": now, "checksum": checksum}}, mock.Anything).Return(&mongo.UpdateResult{}, nil)
- dbClient := NewMockClient(&m)
- db, _ := dbClient.Database("test")
- err := updateLastCheck(db, repoName, checksum, now)
- if err != nil {
- t.Errorf("Unexpected error %v", err)
- }
- if len(m.Calls) != 1 {
- t.Errorf("Expected one call got %d", len(m.Calls))
- }
-}
diff --git a/src/jetstream/plugins/monocular/chart-repo/go.mod b/src/jetstream/plugins/monocular/chart-repo/go.mod
deleted file mode 100644
index 8ba1b1a9dc..0000000000
--- a/src/jetstream/plugins/monocular/chart-repo/go.mod
+++ /dev/null
@@ -1,37 +0,0 @@
-module github.com/helm/monocular/chartrepo
-
-go 1.12
-
-require (
- github.com/Masterminds/semver v1.5.0 // indirect
- github.com/arschles/assert v1.0.0
- github.com/cyphar/filepath-securejoin v0.2.2 // indirect
- github.com/disintegration/imaging v1.6.2
- github.com/ghodss/yaml v1.0.0
- github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 // indirect
- github.com/gobwas/glob v0.2.3 // indirect
- github.com/golang/snappy v0.0.1 // indirect
- github.com/google/uuid v1.1.1
- github.com/gorilla/mux v1.7.3
- github.com/heptiolabs/healthcheck v0.0.0-20180807145615-6ff867650f40
- github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a
- github.com/kubeapps/common v0.0.0-20190508164739-10b110436c1a
- github.com/prometheus/client_golang v1.2.1 // indirect
- github.com/sirupsen/logrus v1.4.2
- github.com/spf13/cobra v0.0.5
- github.com/stretchr/testify v1.4.0
- github.com/urfave/negroni v1.0.0
- github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c // indirect
- github.com/xdg/stringprep v1.0.0 // indirect
- go.mongodb.org/mongo-driver v1.1.3
- golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect
- k8s.io/apimachinery v0.0.0-20191203211716-adc6f4cd9e7d // indirect
- k8s.io/client-go v11.0.0+incompatible // indirect
- k8s.io/helm v2.16.1+incompatible
-)
-
-replace (
- github.com/helm/monocular/chartrepo/common => ./common
- github.com/helm/monocular/chartrepo/foundationdb => ./foundationdb
- github.com/helm/monocular/chartrepo/utils => ./utils
-)
diff --git a/src/jetstream/plugins/monocular/chart-repo/go.sum b/src/jetstream/plugins/monocular/chart-repo/go.sum
deleted file mode 100644
index b34c274aa1..0000000000
--- a/src/jetstream/plugins/monocular/chart-repo/go.sum
+++ /dev/null
@@ -1,230 +0,0 @@
-github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
-github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
-github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
-github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
-github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
-github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
-github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
-github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
-github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
-github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
-github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
-github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
-github.com/arschles/assert v1.0.0 h1:NofQbRhtxcLgP+XoKunA7J6UMJNTqX7xR/19tej8UsA=
-github.com/arschles/assert v1.0.0/go.mod h1:m/u69zW43x0h8dTHcv3JJZljINyEYgBuf5fYJP6WikI=
-github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
-github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
-github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
-github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
-github.com/cespare/xxhash/v2 v2.1.0 h1:yTUvW7Vhb89inJ+8irsUqiWjh8iT6sQPZiQzI6ReGkA=
-github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM=
-github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
-github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
-github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
-github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
-github.com/cyphar/filepath-securejoin v0.2.2 h1:jCwT2GTP+PY5nBz3c/YL5PAIbusElVrPujOBSCj8xRg=
-github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4=
-github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
-github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
-github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
-github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
-github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
-github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
-github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
-github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
-github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
-github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
-github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
-github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 h1:DujepqpGd1hyOd7aW59XpK7Qymp8iy83xq74fLr21is=
-github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
-github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
-github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
-github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
-github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
-github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
-github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0=
-github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg=
-github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=
-github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
-github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
-github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
-github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
-github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
-github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
-github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
-github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-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 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
-github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
-github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
-github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
-github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
-github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
-github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
-github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
-github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
-github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
-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/helm/monocular v1.9.0 h1:jmCzg4mwHLLyL75/5Ekj3aqJs0Hr63fX3+n/hInwZ3U=
-github.com/heptiolabs/healthcheck v0.0.0-20180807145615-6ff867650f40 h1:GT4RsKmHh1uZyhmTkWJTDALRjSHYQp6FRKrotf0zhAs=
-github.com/heptiolabs/healthcheck v0.0.0-20180807145615-6ff867650f40/go.mod h1:NtmN9h8vrTveVQRLHcX2HQ5wIPBDCsZ351TGbZWgg38=
-github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
-github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
-github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a h1:zPPuIq2jAWWPTrGt70eK/BSch+gFAGrNzecsoENgu2o=
-github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a/go.mod h1:yL958EeXv8Ylng6IfnvG4oflryUi3vgA3xPs9hmII1s=
-github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
-github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
-github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
-github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
-github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
-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/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/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
-github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
-github.com/kubeapps/common v0.0.0-20190508164739-10b110436c1a h1:VqeX/fehAB6FtBox0TVYcjOMXGE56INQIfbXegditX4=
-github.com/kubeapps/common v0.0.0-20190508164739-10b110436c1a/go.mod h1:TsgmjeDpbftqhwPKInJ3v+l+xbHs4goiB6DFb2WqY9c=
-github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
-github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
-github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
-github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
-github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
-github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
-github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
-github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
-github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
-github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
-github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
-github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
-github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
-github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
-github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
-github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
-github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
-github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
-github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
-github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
-github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
-github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
-github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
-github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
-github.com/prometheus/client_golang v1.2.1 h1:JnMpQc6ppsNgw9QPAGF6Dod479itz7lvlsMzzNayLOI=
-github.com/prometheus/client_golang v1.2.1/go.mod h1:XMU6Z2MjaRKVu/dC1qupJI9SiNkDYzz3xecMgSW/F+U=
-github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
-github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM=
-github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
-github.com/prometheus/common v0.7.0 h1:L+1lyG48J1zAQXA3RBX/nG/B3gjlHq0zTt2tlbJLyCY=
-github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
-github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
-github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
-github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQlL8=
-github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
-github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
-github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
-github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
-github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
-github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
-github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
-github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
-github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
-github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
-github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
-github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
-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.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
-github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
-github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
-github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
-github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
-github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
-github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
-github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
-github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc=
-github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
-github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c h1:u40Z8hqBAAQyv+vATcGgV0YCnDjqSL7/q/JyPhhJSPk=
-github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I=
-github.com/xdg/stringprep v1.0.0 h1:d9X0esnoa3dFsV0FG35rAT0RIhYFlPq7MiP+DW89La0=
-github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
-github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
-go.mongodb.org/mongo-driver v1.1.3 h1:++7u8r9adKhGR+I79NfEtYrk2ktjenErXM99PSufIoI=
-go.mongodb.org/mongo-driver v1.1.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
-golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
-golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0=
-golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U=
-golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
-golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/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/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
-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-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/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-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
-golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456 h1:ng0gs1AKnRRuEMZoTLLlbOd+C17zUDepwGQBb/n+JVg=
-golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY=
-golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
-golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
-golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
-golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-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/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
-gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
-gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
-gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
-gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
-gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-k8s.io/apimachinery v0.0.0-20191203211716-adc6f4cd9e7d h1:q+OZmYewHJeMCzwpHkXlNTtk5bvaUMPCikKvf77RBlo=
-k8s.io/apimachinery v0.0.0-20191203211716-adc6f4cd9e7d/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg=
-k8s.io/client-go v11.0.0+incompatible h1:LBbX2+lOwY9flffWlJM7f1Ct8V2SRNiMRDFeiwnJo9o=
-k8s.io/client-go v11.0.0+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s=
-k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
-k8s.io/helm v2.16.1+incompatible h1:L+k810plJlaGWEw1EszeT4deK8XVaKxac1oGcuB+WDc=
-k8s.io/helm v2.16.1+incompatible/go.mod h1:LZzlS4LQBHfciFOurYBFkCMTaZ0D1l+p0teMg7TSULI=
-k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
-k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
-k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
-sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
-sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
diff --git a/src/jetstream/plugins/monocular/chart-repo/serve.go b/src/jetstream/plugins/monocular/chart-repo/serve.go
deleted file mode 100644
index 22589b0524..0000000000
--- a/src/jetstream/plugins/monocular/chart-repo/serve.go
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
-Copyright (c) 2018 The Helm Authors
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package main
-
-import (
- "os"
-
- log "github.com/sirupsen/logrus"
- "github.com/spf13/cobra"
-)
-
-//ServeCmd Start an HTTP server to allow on-demand trigger of sync or delete
-var ServeCmd = &cobra.Command{
- Use: "serve",
- Short: "Start an HTTP server for on-demand trigger of sync or delete",
- Run: func(cmd *cobra.Command, args []string) {
- fdbURL, err := cmd.Flags().GetString("doclayer-url")
- if err != nil {
- log.Fatal(err)
- }
- fDB, err := cmd.Flags().GetString("doclayer-database")
- if err != nil {
- log.Fatal(err)
- }
- cACert, err := cmd.Flags().GetString("cafile")
- if err != nil {
- log.Fatal(err)
- }
- key, err := cmd.Flags().GetString("keyfile")
- if err != nil {
- log.Fatal(err)
- }
- cert, err := cmd.Flags().GetString("certfile")
- if err != nil {
- log.Fatal(err)
- }
-
- //TLS options must either be all set to enabled TLS, or none set to disable TLS
- var tlsEnabled = cACert != "" && key != "" && cert != ""
- if !(tlsEnabled || (cACert == "" && key == "" && cert == "")) {
- cmd.Help()
- log.Fatal("To enable TLS, all 3 TLS cert paths must be set.")
- }
-
- debug, err := cmd.Flags().GetBool("debug")
- if err != nil {
- log.Fatal(err)
- }
- authorizationHeader := os.Getenv("AUTHORIZATION_HEADER")
-
- initOnDemandEndpoint(fdbURL, fDB, tlsEnabled, cACert, cert, key, authorizationHeader, debug)
- },
-}
diff --git a/src/jetstream/plugins/monocular/chart-repo/sync.go b/src/jetstream/plugins/monocular/chart-repo/sync.go
deleted file mode 100644
index 5230e78ead..0000000000
--- a/src/jetstream/plugins/monocular/chart-repo/sync.go
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
-Copyright (c) 2018 The Helm Authors
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package main
-
-import (
- "github.com/helm/monocular/chartrepo/foundationdb"
-
- log "github.com/sirupsen/logrus"
- "github.com/spf13/cobra"
-)
-
-//SyncCmd Sync a chart repository with Monocular
-var SyncCmd = &cobra.Command{
- Use: "sync [REPO NAME] [REPO URL]",
- Short: "add a new chart repository, and resync its charts periodically",
- Run: func(cmd *cobra.Command, args []string) {
-
- if len(args) != 2 {
- log.Info("Need exactly two arguments: [REPO NAME] [REPO URL]")
- cmd.Help()
- return
- }
- foundationdb.Sync(cmd, args)
- },
-}
diff --git a/src/jetstream/plugins/monocular/chart-repo/testdata/empty-repo-index.yaml b/src/jetstream/plugins/monocular/chart-repo/testdata/empty-repo-index.yaml
deleted file mode 100644
index 9e608aa034..0000000000
--- a/src/jetstream/plugins/monocular/chart-repo/testdata/empty-repo-index.yaml
+++ /dev/null
@@ -1 +0,0 @@
-entries:
\ No newline at end of file
diff --git a/src/jetstream/plugins/monocular/chart-repo/testdata/valid-index.yaml b/src/jetstream/plugins/monocular/chart-repo/testdata/valid-index.yaml
deleted file mode 100644
index d5a93537c1..0000000000
--- a/src/jetstream/plugins/monocular/chart-repo/testdata/valid-index.yaml
+++ /dev/null
@@ -1,69 +0,0 @@
-entries:
- acs-engine-autoscaler:
- - apiVersion: v1
- appVersion: 2.1.1
- created: 2017-12-06T18:48:59.568323124Z
- description: Scales worker nodes within agent pools
- digest: 39e66eb53c310529bd9dd19776f8ba662e063a4ebd51fc5ec9f2267e2e073e3e
- icon: https://github.com/kubernetes/kubernetes/blob/master/logo/logo.png
- maintainers:
- - email: ritazh@microsoft.com
- name: ritazh
- - email: wibuch@microsoft.com
- name: wbuchwalter
- name: acs-engine-autoscaler
- sources:
- - https://github.com/wbuchwalter/Kubernetes-acs-engine-autoscaler
- urls:
- - https://kubernetes-charts.storage.googleapis.com/acs-engine-autoscaler-2.1.1.tgz
- version: 2.1.1
- wordpress:
- - appVersion: 4.9.1
- created: 2017-12-06T18:48:59.644981487Z
- description: new description!
- digest: 74889e60a35dcffa4686f88bb23de863fed2b6e63a69b1f4858dde37c301885c
- engine: gotpl
- home: http://www.wordpress.com/
- icon: https://bitnami.com/assets/stacks/wordpress/img/wordpress-stack-220x234.png
- keywords:
- - wordpress
- - cms
- - blog
- - http
- - web
- - application
- - php
- maintainers:
- - email: containers@bitnami.com
- name: bitnami-bot
- name: wordpress
- sources:
- - https://github.com/bitnami/bitnami-docker-wordpress
- urls:
- - https://kubernetes-charts.storage.googleapis.com/wordpress-0.7.5.tgz
- version: 0.7.5
- - appVersion: 4.9.0
- created: 2017-12-01T11:49:00.136950565Z
- description: Web publishing platform for building blogs and websites.
- digest: a69139ef3008eeb11ca60261ec2ded61e84ce7db32bb3626056e84bcff7ec270
- engine: gotpl
- home: http://www.wordpress.com/
- icon: https://bitnami.com/assets/stacks/wordpress/img/wordpress-stack-220x234.png
- keywords:
- - wordpress
- - cms
- - cms
- - blog
- - http
- - web
- - application
- - php
- maintainers:
- - email: containers@bitnami.com
- name: bitnami-bot
- name: wordpress
- sources:
- - https://github.com/bitnami/bitnami-docker-wordpress
- urls:
- - https://kubernetes-charts.storage.googleapis.com/wordpress-0.7.4.tgz
- version: 0.7.4
diff --git a/src/jetstream/plugins/monocular/chart-repo/utils/version.go b/src/jetstream/plugins/monocular/chart-repo/utils/version.go
deleted file mode 100644
index e30f658e8c..0000000000
--- a/src/jetstream/plugins/monocular/chart-repo/utils/version.go
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
-Copyright (c) 2018 The Helm Authors
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package utils
-
-import (
- "fmt"
-
- "github.com/spf13/cobra"
-)
-
-var (
- Version = "devel"
- UserAgentComment string
-)
-
-// UserAgent returns the user agent to be used during calls to the chart repositories
-// Examples:
-// chart-repo/devel
-// chart-repo/1.0
-// chart-repo/1.0 (monocular v1.0-beta4)
-// More info here https://github.com/kubeapps/kubeapps/issues/767#issuecomment-436835938
-func UserAgent() string {
- ua := "chart-repo/" + Version
- if UserAgentComment != "" {
- ua = fmt.Sprintf("%s (%s)", ua, UserAgentComment)
- }
- return ua
-}
-
-//VersionCmd returns Monocular version information
-var VersionCmd = &cobra.Command{
- Use: "version",
- Short: "returns version information",
- Run: func(cmd *cobra.Command, args []string) {
- fmt.Println(Version)
- },
-}
diff --git a/src/jetstream/plugins/monocular/chartsvc/models/chart.go b/src/jetstream/plugins/monocular/chart.go
similarity index 57%
rename from src/jetstream/plugins/monocular/chartsvc/models/chart.go
rename to src/jetstream/plugins/monocular/chart.go
index 7501f85c36..70e9c4a763 100644
--- a/src/jetstream/plugins/monocular/chartsvc/models/chart.go
+++ b/src/jetstream/plugins/monocular/chart.go
@@ -14,12 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-package models
+package monocular
import (
"time"
-
- "k8s.io/helm/pkg/proto/hapi/chart"
)
// Repo holds the App repository details
@@ -30,18 +28,18 @@ type Repo struct {
// Chart is a higher-level representation of a chart package
type Chart struct {
- ID string `json:"-" bson:"_id"`
- Name string `json:"name"`
- Repo Repo `json:"repo"`
- Description string `json:"description"`
- Home string `json:"home"`
- Keywords []string `json:"keywords"`
- Maintainers []chart.Maintainer `json:"maintainers"`
- Sources []string `json:"sources"`
- Icon string `json:"icon"`
- RawIcon []byte `json:"-" bson:"raw_icon"`
- IconContentType string `json:"-" bson:"icon_content_type,omitempty"`
- ChartVersions []ChartVersion `json:"-"`
+ ID string `json:"-"`
+ Name string `json:"name"`
+ Repo Repo `json:"repo"`
+ Description string `json:"description"`
+ Home string `json:"home"`
+ Keywords []string `json:"keywords"`
+ Maintainers []ChartMaintainer `json:"maintainers"`
+ Sources []string `json:"sources"`
+ Icon string `json:"icon"`
+ RawIcon []byte `json:"-"`
+ IconContentType string `json:"-"`
+ ChartVersions []ChartVersion `json:"-"`
}
// ChartVersion is a representation of a specific version of a chart
@@ -51,17 +49,9 @@ type ChartVersion struct {
Created time.Time `json:"created"`
Digest string `json:"digest"`
URLs []string `json:"urls"`
- Readme string `json:"readme" bson:"-"`
- Values string `json:"values" bson:"-"`
- Schema string `json:"schema" bson:"-"`
-}
-
-// ChartFiles holds the README and values for a given chart version
-type ChartFiles struct {
- ID string `bson:"_id"`
- Readme string
- Values string
- Schema string
+ Readme string `json:"readme,omitempty"`
+ Values string `json:"values,omitempty"`
+ Schema string `json:"schema,omitempty"`
}
//RepoCheck describes the state of a repository in terms its current checksum and last update time.
diff --git a/src/jetstream/plugins/monocular/chart_svc.go b/src/jetstream/plugins/monocular/chart_svc.go
new file mode 100644
index 0000000000..2913536a52
--- /dev/null
+++ b/src/jetstream/plugins/monocular/chart_svc.go
@@ -0,0 +1,285 @@
+package monocular
+
+import (
+ "errors"
+ "fmt"
+ "net/http"
+ "os"
+ "path"
+
+ "github.com/cloudfoundry-incubator/stratos/src/jetstream/plugins/monocular/store"
+ "github.com/labstack/echo"
+ log "github.com/sirupsen/logrus"
+)
+
+// Functions to provide a Monocular compatible API with our chart store
+
+// List all helm Charts - gets the latest version for each Chart
+func (m *Monocular) listCharts(c echo.Context) error {
+ log.Debug("List Charts called")
+
+ // Check if this is a request for an external Monocular
+ if handled, err := m.processMonocularRequest(c); handled {
+ return err
+ }
+
+ charts, err := m.ChartStore.GetLatestCharts()
+ if err != nil {
+ return err
+ }
+
+ // Translate the list into an array of Charts
+ var list APIListResponse
+ for _, chart := range charts {
+ list = append(list, m.translateToChartAPIResponse(chart, nil))
+ }
+
+ meta := Meta{
+ TotalPages: 1,
+ }
+
+ body := BodyAPIListResponse{
+ Data: &list,
+ Meta: meta,
+ }
+
+ return c.JSON(200, body)
+}
+
+// Get the latest version of a given chart
+func (m *Monocular) getChart(c echo.Context) error {
+ log.Debug("Get Chart called")
+
+ // Check if this is a request for an external Monocular
+ if handled, err := m.processMonocularRequest(c); handled {
+ return err
+ }
+
+ repo := c.Param("repo")
+ chartName := c.Param("name")
+
+ chart, err := m.ChartStore.GetChart(repo, chartName, "")
+ if err != nil {
+ return err
+ }
+
+ chartYaml := m.getChartYaml(*chart)
+ body := BodyAPIResponse{
+ Data: *m.translateToChartAPIResponse(chart, chartYaml),
+ }
+ return c.JSON(200, body)
+}
+
+func (m *Monocular) getIcon(c echo.Context) error {
+ log.Debug("Get Icon called")
+
+ // Check if this is a request for an external Monocular
+ if handled, err := m.processMonocularRequest(c); handled {
+ return err
+ }
+
+ repo := c.Param("repo")
+ chartName := c.Param("chartName")
+ version := c.Param("version")
+
+ if len(version) == 0 {
+ log.Debugf("Get icon for %s/%s", repo, chartName)
+ } else {
+ log.Debugf("Get icon for %s/%s-%s", repo, chartName, version)
+ }
+
+ chart, err := m.ChartStore.GetChart(repo, chartName, version)
+ if err != nil {
+ log.Error("Can not find chart")
+ return errors.New("Error")
+ }
+
+ // This will download and cache the icon if it is not already cached - it returns the local file path to the icon file
+ // or an empty string if no icon is available or could not be downloaded
+ iconFilePath, _ := m.cacheChartIcon(*chart)
+ if len(iconFilePath) == 0 {
+ // No icon or error downloading
+ http.Redirect(c.Response().Writer, c.Request(), "/core/assets/custom/placeholder.png", http.StatusTemporaryRedirect)
+ return nil
+ }
+
+ c.File(iconFilePath)
+ return nil
+}
+
+// /chartsvc/v1/charts/:repo/:name/versions/:version
+// Get specific chart version
+func (m *Monocular) getChartVersion(c echo.Context) error {
+ log.Debug("getChartAndVersion called")
+
+ // Check if this is a request for an external Monocular
+ if handled, err := m.processMonocularRequest(c); handled {
+ return err
+ }
+
+ repo := c.Param("repo")
+ chartName := c.Param("name")
+ version := c.Param("version")
+
+ chart, err := m.ChartStore.GetChart(repo, chartName, version)
+ if err != nil {
+ return err
+ }
+
+ chartYaml := m.getChartYaml(*chart)
+ if chartYaml == nil {
+ // Error - could not get chart yaml
+ return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("Can not find Chart.yaml for %s/%s-%s", repo, chartName, version))
+ }
+
+ body := BodyAPIResponse{
+ Data: *m.translateToChartVersionAPIResponse(chart, chartYaml),
+ }
+ return c.JSON(200, body)
+}
+
+// /chartsvc/v1/charts/:repo/:name/versions
+// Get all chart versions for a given chart
+func (m *Monocular) getChartVersions(c echo.Context) error {
+
+ // Check if this is a request for an external Monocular
+ if handled, err := m.processMonocularRequest(c); handled {
+ return err
+ }
+
+ repo := c.Param("repo")
+ chartName := c.Param("name")
+
+ // Get all versions for a given chart
+ charts, err := m.ChartStore.GetChartVersions(repo, chartName)
+ if err != nil {
+ return err
+ }
+
+ // Translate the list into an array of Charts
+ var list APIListResponse
+ for _, chart := range charts {
+ list = append(list, m.translateToChartVersionAPIResponse(chart, nil))
+ }
+
+ body := BodyAPIListResponse{
+ Data: &list,
+ }
+
+ return c.JSON(200, body)
+}
+
+// Get a file such as the README or valyes for a given chart version
+func (m *Monocular) getChartAndVersionFile(c echo.Context) error {
+ log.Debug("Get Chart file called")
+
+ // Check if this is a request for an external Monocular
+ if handled, err := m.processMonocularRequest(c); handled {
+ return err
+ }
+
+ repo := c.Param("repo")
+ chartName := c.Param("name")
+ version := c.Param("version")
+ filename := c.Param("filename")
+
+ log.Debugf("Get chart file: %s", filename)
+
+ chart, err := m.ChartStore.GetChart(repo, chartName, version)
+ if err != nil {
+ return err
+ }
+
+ if m.cacheChart(*chart) == nil {
+ return c.File(path.Join(m.getChartCacheFolder(*chart), filename))
+ }
+
+ return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("Can not find file %s for the specified chart", filename))
+}
+
+// This is the simpler version that returns just enough data needed for the Charts list view
+// This is a slight cheat - the response is not as complete as the Monocular API, but includes
+// enough for the UI and saves us having to pull out all of the Chart.yaml files
+func (m *Monocular) translateToChartAPIResponse(record *store.ChartStoreRecord, chartYaml *ChartMetadata) *APIResponse {
+ response := &APIResponse{
+ ID: fmt.Sprintf("%s/%s", record.Repository, record.Name),
+ Type: "chart",
+ Relationships: make(map[string]Rel),
+ Attributes: m.translateToChart(record, chartYaml),
+ }
+
+ response.Relationships["latestChartVersion"] = Rel{
+ Data: m.translateToChartVersion(record, chartYaml),
+ }
+ return response
+}
+
+func (m *Monocular) translateToChart(record *store.ChartStoreRecord, chartYaml *ChartMetadata) Chart {
+ chart := Chart{
+ Name: record.Name,
+ Description: record.Description,
+ Repo: Repo{},
+ Icon: fmt.Sprintf("/v1/assets/%s/%s/%s/logo", record.Repository, record.Name, record.Version),
+ Sources: record.Sources,
+ }
+
+ chart.Repo.Name = record.Repository
+
+ if chartYaml != nil {
+ chart.Keywords = chartYaml.Keywords
+ // Prefer the Chart Yaml description if we have it (db one might be truncated)
+ chart.Description = chartYaml.Description
+ chart.Maintainers = make([]ChartMaintainer, len(chartYaml.Maintainers))
+ for index, maintainer := range chartYaml.Maintainers {
+ chart.Maintainers[index] = *maintainer
+ }
+
+ chart.Home = chartYaml.Home
+ }
+
+ return chart
+}
+
+func (m *Monocular) translateToChartVersion(record *store.ChartStoreRecord, chartYaml *ChartMetadata) ChartVersion {
+ chartVersion := ChartVersion{
+ Version: record.Version,
+ AppVersion: record.AppVersion,
+ Digest: record.Digest,
+ Created: record.Created,
+ URLs: make([]string, 1),
+ }
+ chartVersion.URLs[0] = record.ChartURL
+ if chartYaml != nil {
+ // If we have the Chart yaml, then we already have the chart
+ // Add in the files that are available
+ cacheFolder := m.getChartCacheFolder(*record)
+ chartVersion.Readme = getFileAssetURL(record.Repository, record.Name, record.Version, cacheFolder, "README.md")
+ chartVersion.Schema = getFileAssetURL(record.Repository, record.Name, record.Version, cacheFolder, "values.schema.json")
+ chartVersion.Values = getFileAssetURL(record.Repository, record.Name, record.Version, cacheFolder, "values.yaml")
+ }
+
+ return chartVersion
+}
+
+func (m *Monocular) translateToChartVersionAPIResponse(record *store.ChartStoreRecord, chartYaml *ChartMetadata) *APIResponse {
+ response := &APIResponse{
+ ID: fmt.Sprintf("%s/%s-%s", record.Repository, record.Name, record.Version),
+ Type: "chartVersion",
+ Relationships: make(map[string]Rel),
+ Attributes: m.translateToChartVersion(record, chartYaml),
+ }
+
+ response.Relationships["chart"] = Rel{
+ Data: m.translateToChart(record, chartYaml),
+ }
+ return response
+}
+
+func getFileAssetURL(repo, name, version, folder, filename string) string {
+ cachePath := path.Join(folder, filename)
+ if _, err := os.Stat(cachePath); os.IsNotExist(err) {
+ return ""
+ }
+
+ return fmt.Sprintf("/v1/assets/%s/%s/versions/%s/%s", repo, name, version, filename)
+}
diff --git a/src/jetstream/plugins/monocular/chartsvc/Dockerfile b/src/jetstream/plugins/monocular/chartsvc/Dockerfile
deleted file mode 100644
index 9f4ce2c0bc..0000000000
--- a/src/jetstream/plugins/monocular/chartsvc/Dockerfile
+++ /dev/null
@@ -1,9 +0,0 @@
-FROM golang:1.12 as builder
-COPY . /go/src/github.com/helm/monocular/chartsvc
-WORKDIR /go/src/github.com/helm/monocular/chartsvc
-RUN GO111MODULE=on GOPROXY=https://gocenter.io CGO_ENABLED=0 go build -a -installsuffix cgo .
-FROM scratch
-COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
-COPY --from=builder /go/src/github.com/helm/monocular/chartsvc /chartsvc
-EXPOSE 8080
-CMD ["/chartsvc"]
diff --git a/src/jetstream/plugins/monocular/chartsvc/Makefile b/src/jetstream/plugins/monocular/chartsvc/Makefile
deleted file mode 100644
index ef0b800ae2..0000000000
--- a/src/jetstream/plugins/monocular/chartsvc/Makefile
+++ /dev/null
@@ -1,6 +0,0 @@
-IMAGE_REPO ?= docker.io/kreinecke/suse-fdb-chartsvc
-IMAGE_TAG ?= latest
-
-docker-build:
- # We use the context of the root dir
- docker build --pull --rm -t ${IMAGE_REPO}:${IMAGE_TAG} -f Dockerfile .
diff --git a/src/jetstream/plugins/monocular/chartsvc/README.md b/src/jetstream/plugins/monocular/chartsvc/README.md
deleted file mode 100644
index 2cc0afc9d3..0000000000
--- a/src/jetstream/plugins/monocular/chartsvc/README.md
+++ /dev/null
@@ -1,6 +0,0 @@
-# Chartsvc
-
-Chartsvc is a service for Monocular that reads chart metadata from the database
-and presents it in a RESTful API. It should be used with the
-[chart-repo](https://github.com/helm/monocular/tree/master/cmd/chart-repo) to
-populate chart metadata in the database.
diff --git a/src/jetstream/plugins/monocular/chartsvc/datastore.go b/src/jetstream/plugins/monocular/chartsvc/datastore.go
deleted file mode 100644
index 0e789c277c..0000000000
--- a/src/jetstream/plugins/monocular/chartsvc/datastore.go
+++ /dev/null
@@ -1,41 +0,0 @@
-package chartsvc
-
-import (
- "context"
- "fmt"
-
- "github.com/helm/monocular/chartsvc/foundationdb"
- "github.com/helm/monocular/chartsvc/foundationdb/datastore"
- "github.com/helm/monocular/chartsvc/models"
- log "github.com/sirupsen/logrus"
- "go.mongodb.org/mongo-driver/bson"
- "go.mongodb.org/mongo-driver/mongo"
- "go.mongodb.org/mongo-driver/mongo/options"
-)
-
-const chartCollection = "charts"
-
-type ChartSvcDatastore struct {
- dbClient datastore.Client
- db datastore.Database
- dbCloser func()
-}
-
-func (m *ChartSvcDatastore) ListRepositories() ([]string, error) {
- return foundationdb.ListRepositories()
-}
-
-// GetChart returns the chart with the given ID
-func (m *ChartSvcDatastore) GetChart(chartID string) (models.Chart, error) {
- var chart models.Chart
-
- chartCollection := m.db.Collection(chartCollection)
- filter := bson.M{"_id": chartID}
- findResult := chartCollection.FindOne(context.Background(), filter, &chart, options.FindOne())
- if findResult == mongo.ErrNoDocuments {
- log.WithError(findResult).Errorf("could not find chart with id %s", chartID)
- return chart, fmt.Errorf("could not find chart with id %s", chartID)
- }
-
- return chart, nil
-}
diff --git a/src/jetstream/plugins/monocular/chartsvc/foundationdb/datastore/datastore.go b/src/jetstream/plugins/monocular/chartsvc/foundationdb/datastore/datastore.go
deleted file mode 100644
index 37ac06d008..0000000000
--- a/src/jetstream/plugins/monocular/chartsvc/foundationdb/datastore/datastore.go
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
-Copyright (c) 2019
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-// Package datastore implements an interface on top of the mgo mongo client
-package datastore
-
-import (
- "context"
- "fmt"
- "reflect"
- "time"
-
- log "github.com/sirupsen/logrus"
- "go.mongodb.org/mongo-driver/mongo"
- "go.mongodb.org/mongo-driver/mongo/options"
-)
-
-const defaultTimeout = 30 * time.Second
-
-// Config configures the database connection
-type Config struct {
- URL string
- Database string
- Timeout time.Duration
-}
-
-// Client is an interface for a MongoDB client
-type Client interface {
- Database(name string) (Database, func())
-}
-
-// Database is an interface for accessing a MongoDB database
-type Database interface {
- Collection(name string) Collection
-}
-
-// Collection is an interface for accessing a MongoDB collection
-type Collection interface {
- BulkWrite(ctxt context.Context, operations []mongo.WriteModel, options *options.BulkWriteOptions) (*mongo.BulkWriteResult, error)
- DeleteMany(ctxt context.Context, filter interface{}, options *options.DeleteOptions) (*mongo.DeleteResult, error)
- FindOne(ctxt context.Context, filter interface{}, result interface{}, options *options.FindOneOptions) error
- InsertOne(ctxt context.Context, document interface{}, options *options.InsertOneOptions) (*mongo.InsertOneResult, error)
- UpdateOne(ctxt context.Context, filter interface{}, update interface{}, options *options.UpdateOptions) (*mongo.UpdateResult, error)
- Find(ctxt context.Context, filter interface{}, result interface{}, options *options.FindOptions) error
-}
-
-// mongoDatabase wraps a mongo.Database and implements Collection
-type mongoDatabase struct {
- Database *mongo.Database
-}
-
-// mongoClient wraps a mongo.Client and implements Database
-type mongoClient struct {
- Client *mongo.Client
-}
-
-// NewDocLayerClient creates a new MongoDB client for the FoundationDB document layer.
-func NewDocLayerClient(ctx context.Context, options *options.ClientOptions) (Client, error) {
- client, err := mongo.Connect(ctx, options)
- return &mongoClient{client}, err
-}
-
-func (c *mongoClient) Database(dbName string) (Database, func()) {
-
- db := &mongoDatabase{c.Client.Database(dbName)}
-
- return db, func() {
- log.Infof("Closing db connection")
- err := c.Client.Disconnect(context.Background())
-
- if err != nil {
- log.Fatal(err)
- }
- fmt.Println("Connection to MongoDB closed.")
- }
-}
-
-func (d *mongoDatabase) Collection(name string) Collection {
- return &mongoCollection{d.Database.Collection(name)}
-}
-
-// mgoCollection wraps an mgo.Collection and implements Collection
-type mongoCollection struct {
- Collection *mongo.Collection
-}
-
-func (c *mongoCollection) BulkWrite(ctxt context.Context, operations []mongo.WriteModel, options *options.BulkWriteOptions) (*mongo.BulkWriteResult, error) {
- res, err := c.Collection.BulkWrite(ctxt, operations, options)
- return res, err
-}
-
-func (c *mongoCollection) DeleteMany(ctxt context.Context, filter interface{}, options *options.DeleteOptions) (*mongo.DeleteResult, error) {
- res, err := c.Collection.DeleteMany(ctxt, filter, options)
- return res, err
-}
-
-func (c *mongoCollection) FindOne(ctxt context.Context, filter interface{}, result interface{}, options *options.FindOneOptions) error {
- res := c.Collection.FindOne(ctxt, filter, options)
- return res.Decode(result)
-}
-
-func (c *mongoCollection) InsertOne(ctxt context.Context, document interface{}, options *options.InsertOneOptions) (*mongo.InsertOneResult, error) {
- res, err := c.Collection.InsertOne(ctxt, document, options)
- return res, err
-}
-
-func (c *mongoCollection) UpdateOne(ctxt context.Context, filter interface{}, document interface{}, options *options.UpdateOptions) (*mongo.UpdateResult, error) {
- res, err := c.Collection.UpdateOne(ctxt, filter, document, options)
- return res, err
-}
-
-func (c *mongoCollection) Find(ctxt context.Context, filter interface{}, result interface{}, options *options.FindOptions) error {
- resCursor, err := c.Collection.Find(ctxt, filter, options)
- if err != nil {
- log.WithError(err).Errorf(
- "Error fetching query result.")
- } else {
- resultType := reflect.ValueOf(result)
- if resultType.Kind() != reflect.Ptr {
- return fmt.Errorf("Result arg must be a pointer type. Got: %v", resultType.Kind())
- }
- resultSliceType := resultType.Elem()
- if resultSliceType.Kind() != reflect.Slice {
- return fmt.Errorf("Result arg must be a slice value. Got: %v", resultSliceType.Kind())
- }
- err = resCursor.All(context.Background(), result)
- if err != nil {
- log.WithError(err).Errorf(
- "Error decoding query result.")
- }
- }
- return err
-}
diff --git a/src/jetstream/plugins/monocular/chartsvc/foundationdb/datastore/mockstore.go b/src/jetstream/plugins/monocular/chartsvc/foundationdb/datastore/mockstore.go
deleted file mode 100644
index 053ffea382..0000000000
--- a/src/jetstream/plugins/monocular/chartsvc/foundationdb/datastore/mockstore.go
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
-Copyright (c) 2019
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package datastore
-
-import (
- "context"
-
- "github.com/stretchr/testify/mock"
- "go.mongodb.org/mongo-driver/mongo"
- "go.mongodb.org/mongo-driver/mongo/options"
-)
-
-// mockDatabase acts as a mock datastore.Database
-type mockDatabase struct {
- *mock.Mock
-}
-
-type mockClient struct {
- *mock.Mock
-}
-
-//NewMockClient returns a mocked FDB Document-Layer client
-func NewMockClient(m *mock.Mock) Client {
- return mockClient{m}
-}
-
-// DB returns a mocked datastore.Database and empty closer function
-func (c mockClient) Database(dbName string) (Database, func()) {
-
- db := mockDatabase{c.Mock}
-
- return db, func() {
- }
-}
-
-func (d mockDatabase) Collection(name string) Collection {
- return mockCollection{d.Mock}
-}
-
-// mockCollection acts as a mock datastore.Collection
-type mockCollection struct {
- *mock.Mock
-}
-
-func (c mockCollection) BulkWrite(ctxt context.Context, operations []mongo.WriteModel, options *options.BulkWriteOptions) (*mongo.BulkWriteResult, error) {
- args := c.Called(ctxt, operations, options)
- return args.Get(0).(*mongo.BulkWriteResult), args.Error(1)
-}
-
-func (c mockCollection) DeleteMany(ctxt context.Context, filter interface{}, options *options.DeleteOptions) (*mongo.DeleteResult, error) {
- args := c.Called(ctxt, filter, options)
- return args.Get(0).(*mongo.DeleteResult), args.Error(1)
-}
-
-func (c mockCollection) FindOne(ctxt context.Context, filter interface{}, result interface{}, options *options.FindOneOptions) error {
- args := c.Called(ctxt, filter, result, options)
- return args.Error(0)
-}
-
-func (c mockCollection) InsertOne(ctxt context.Context, document interface{}, options *options.InsertOneOptions) (*mongo.InsertOneResult, error) {
- args := c.Called(ctxt, document, options)
- return args.Get(0).(*mongo.InsertOneResult), args.Error(1)
-}
-
-func (c mockCollection) UpdateOne(ctxt context.Context, filter interface{}, document interface{}, options *options.UpdateOptions) (*mongo.UpdateResult, error) {
- args := c.Called(ctxt, filter, document, options)
- return args.Get(0).(*mongo.UpdateResult), args.Error(1)
-}
-
-func (c mockCollection) Find(ctxt context.Context, filter interface{}, result interface{}, options *options.FindOptions) error {
- args := c.Called(ctxt, filter, result, options)
- return args.Error(0)
-}
diff --git a/src/jetstream/plugins/monocular/chartsvc/foundationdb/handler.go b/src/jetstream/plugins/monocular/chartsvc/foundationdb/handler.go
deleted file mode 100644
index d937f43c06..0000000000
--- a/src/jetstream/plugins/monocular/chartsvc/foundationdb/handler.go
+++ /dev/null
@@ -1,541 +0,0 @@
-/*
-Copyright (c) 2019
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package foundationdb
-
-import (
- "context"
- "fmt"
- "math"
- "net/http"
- "sort"
- "strconv"
- "strings"
-
- "github.com/helm/monocular/chartsvc/foundationdb/datastore"
- "github.com/helm/monocular/chartsvc/models"
- "github.com/helm/monocular/chartsvc/utils"
-
- "github.com/gorilla/mux"
- "github.com/kubeapps/common/response"
- log "github.com/sirupsen/logrus"
- "go.mongodb.org/mongo-driver/bson"
- "go.mongodb.org/mongo-driver/mongo"
- "go.mongodb.org/mongo-driver/mongo/options"
-)
-
-// Params a key-value map of path params
-type Params map[string]string
-
-// WithParams can be used to wrap handlers to take an extra arg for path params
-type WithParams func(http.ResponseWriter, *http.Request, Params)
-
-func (h WithParams) ServeHTTP(w http.ResponseWriter, req *http.Request) {
- vars := mux.Vars(req)
- h(w, req, vars)
-}
-
-const chartCollection = "charts"
-const filesCollection = "files"
-const repoCollection = "repos"
-
-// count is used to parse the result of a $count operation in the database
-type count struct {
- Count int
-}
-
-var dbClient datastore.Client
-var db datastore.Database
-var dbCloser func()
-
-//var db mongo.Database
-var dbName string
-var pathPrefix string
-
-//SetPathPrefix sets the URL prefix for the ChartSVC API endpoint
-func SetPathPrefix(prefix string) {
- pathPrefix = prefix
-}
-
-//InitDBConfig sets FDB Document-Layer client and DB config for the ChartSVC API handler
-func InitDBConfig(client datastore.Client, name string) {
- dbClient = client
- db, dbCloser = dbClient.Database(name)
- dbName = name
-}
-
-// getPageNumberAndSize extracts the page number and size of a request. Default (1, 0) if not set
-func getPageNumberAndSize(req *http.Request) (int, int) {
- page := req.FormValue("page")
- size := req.FormValue("size")
- pageInt, err := strconv.ParseUint(page, 10, 64)
- if err != nil {
- pageInt = 1
- }
- // ParseUint will return 0 if size is a not positive integer
- sizeInt, _ := strconv.ParseUint(size, 10, 64)
- return int(pageInt), int(sizeInt)
-}
-
-// showDuplicates returns if a request wants to retrieve charts. Default false
-func showDuplicates(req *http.Request) bool {
- return len(req.FormValue("showDuplicates")) > 0
-}
-
-// min returns the minimum of two integers.
-// We are not using math.Min since that compares float64
-// and it's unnecessarily complex.
-func min(a, b int) int {
- if a < b {
- return a
- }
- return b
-}
-
-func uniqChartList(charts []*models.Chart) []*models.Chart {
- // We will keep track of unique digest:chart to avoid duplicates
- chartDigests := map[string]bool{}
- res := []*models.Chart{}
- for _, c := range charts {
- digest := c.ChartVersions[0].Digest
- // Filter out the chart if we've seen the same digest before
- if _, ok := chartDigests[digest]; !ok {
- chartDigests[digest] = true
- res = append(res, c)
- }
- }
- return res
-}
-
-func getPaginatedChartList(repo string, pageNumber, pageSize int, showDuplicates bool) (utils.ApiListResponse, interface{}, error) {
- log.Debugf("Request for paginated chart list for repo %v", repo)
-
- //Find all charts for repo name and sort by chart name
- collection := db.Collection(chartCollection)
- filter := bson.M{}
- if repo != "" {
- filter = bson.M{"repo.name": repo}
- }
- var charts []*models.Chart
- err := collection.Find(context.Background(), filter, &charts, options.Find())
- if err != nil {
- log.WithError(err).Errorf(
- "Error fetching charts from DB for pagination %s",
- repo,
- )
- return newChartListResponse([]*models.Chart{}), utils.Meta{TotalPages: 0}, err
- }
- var tempChartMap map[string]*models.Chart = make(map[string]*models.Chart)
-
- chartsToSort := make([]*models.Chart, 0, len(tempChartMap))
-
- if !showDuplicates {
- for _, chart := range charts {
- log.Debugf("Chart digest: %v.", chart.ChartVersions[0].Digest)
- tempChartMap[chart.ChartVersions[0].Digest] = chart
- }
- log.Debugf("Charts in map: %v", len(tempChartMap))
- //Now just get all the values from our map
- for _, v := range tempChartMap {
- log.Debugf("Adding chart: %v to unique chart list.", *v)
- chartsToSort = append(chartsToSort, v)
- }
- } else {
- chartsToSort = charts
- }
-
- //Sort the list of paginated charts by name
- sort.Slice(chartsToSort, func(i, j int) bool {
- return chartsToSort[i].Name < chartsToSort[j].Name
- })
-
- sortedCharts := chartsToSort
- log.Debugf("Charts in sorted list: %v", len(sortedCharts))
- log.Debugf("Page size requested: %v", pageSize)
- var paginatedCharts = sortedCharts
- totalPages := 1
- if pageSize != 0 {
- // If a pageSize is given, returns only the the specified number of charts and
- // the number of pages
- cc := count{}
- cc.Count = len(sortedCharts)
- totalPages = int(math.Ceil(float64(cc.Count) / float64(pageSize)))
-
- // If the page number is out of range, return the last one
- if pageNumber > totalPages {
- pageNumber = totalPages
- }
- paginatedCharts = sortedCharts[(pageNumber-1)*pageSize : pageNumber*pageSize]
- }
-
- log.Debugf("Returning %v charts, Done.", len(paginatedCharts))
- return newChartListResponse(paginatedCharts), utils.Meta{TotalPages: totalPages}, nil
-}
-
-//ListRepositories returns a list of names of all stored repositories
-func ListRepositories() ([]string, error) {
- var repoNames []string
- //Find all repo names
- collection := db.Collection(repoCollection)
- filter := bson.M{}
- var lastChecks []*models.RepoCheck
- err := collection.Find(context.Background(), filter, &lastChecks, options.Find())
- if err != nil {
- log.WithError(err).Error("could not fetch repositories")
- return repoNames, err
- }
- for i := range lastChecks {
- repoNames = append(repoNames, lastChecks[i].ID)
- }
- return repoNames, nil
-}
-
-// ListCharts returns a list of charts
-func ListCharts(w http.ResponseWriter, req *http.Request) {
- log.Debug("Request for charts..")
- pageNumber, pageSize := getPageNumberAndSize(req)
- cl, meta, err := getPaginatedChartList("", pageNumber, pageSize, showDuplicates(req))
- if err != nil {
- log.WithError(err).Error("could not fetch charts")
- response.NewErrorResponse(http.StatusInternalServerError, "could not fetch all charts").Write(w)
- return
- }
- response.NewDataResponseWithMeta(cl, meta).Write(w)
- log.Debug("Done.")
-}
-
-// ListRepoCharts returns a list of charts in the given repo
-func ListRepoCharts(w http.ResponseWriter, req *http.Request, params Params) {
- log.Debug("Request for charts..")
- pageNumber, pageSize := getPageNumberAndSize(req)
- cl, meta, err := getPaginatedChartList(params["repo"], pageNumber, pageSize, showDuplicates(req))
- if err != nil {
- log.WithError(err).Error("could not fetch charts")
- response.NewErrorResponse(http.StatusInternalServerError, "could not fetch all charts").Write(w)
- return
- }
- response.NewDataResponseWithMeta(cl, meta).Write(w)
- log.Debug("Done.")
-}
-
-// GetChart returns the chart from the given repo
-func GetChart(w http.ResponseWriter, req *http.Request, params Params) {
- var chart models.Chart
- chartID := fmt.Sprintf("%s/%s", params["repo"], params["chartName"])
-
- chartCollection := db.Collection(chartCollection)
- filter := bson.M{"_id": chartID}
- findResult := chartCollection.FindOne(context.Background(), filter, &chart, options.FindOne())
- if findResult == mongo.ErrNoDocuments {
- log.WithError(findResult).Errorf("could not find chart with id %s", chartID)
- response.NewErrorResponse(http.StatusNotFound, "could not find chart").Write(w)
- return
- }
-
- cr := newChartResponse(&chart)
- response.NewDataResponse(cr).Write(w)
-}
-
-// ListChartVersions returns a list of chart versions for the given chart
-func ListChartVersions(w http.ResponseWriter, req *http.Request, params Params) {
- var chart models.Chart
- chartID := fmt.Sprintf("%s/%s", params["repo"], params["chartName"])
-
- chartCollection := db.Collection(chartCollection)
- filter := bson.M{"_id": chartID}
- findResult := chartCollection.FindOne(context.Background(), filter, &chart, options.FindOne())
- if findResult == mongo.ErrNoDocuments {
- log.WithError(findResult).Errorf("could not find chart with id %s", chartID)
- response.NewErrorResponse(http.StatusNotFound, "could not find chart").Write(w)
- return
- }
-
- cvl := newChartVersionListResponse(&chart)
- response.NewDataResponse(cvl).Write(w)
-}
-
-// GetChartVersion returns the given chart version
-func GetChartVersion(w http.ResponseWriter, req *http.Request, params Params) {
- var chart models.Chart
- chartID := fmt.Sprintf("%s/%s", params["repo"], params["chartName"])
-
- chartCollection := db.Collection(chartCollection)
- filter := bson.M{
- "_id": chartID,
- "chartversions": bson.M{"$elemMatch": bson.M{"version": params["version"]}},
- }
- projection := bson.M{
- "name": 1, "repo": 1, "description": 1, "home": 1, "keywords": 1, "maintainers": 1, "sources": 1,
- "chartversions": 1,
- }
- findResult := chartCollection.FindOne(context.Background(), filter, &chart, options.FindOne().SetProjection(projection))
- if findResult == mongo.ErrNoDocuments {
- log.WithError(findResult).Errorf("could not find chart with id %s", chartID)
- response.NewErrorResponse(http.StatusNotFound, "could not find chart").Write(w)
- return
- }
-
- for i := range chart.ChartVersions {
- if chart.ChartVersions[i].Version == params["version"] {
- chart.ChartVersions = chart.ChartVersions[i : i+1]
- break
- }
- }
- // Cut the versions slice down to just one element
- cvr := newChartVersionResponse(&chart, chart.ChartVersions[0])
- response.NewDataResponse(cvr).Write(w)
-}
-
-// GetChartIcon returns the icon for a given chart
-func GetChartIcon(w http.ResponseWriter, req *http.Request, params Params) {
- var chart models.Chart
- chartID := fmt.Sprintf("%s/%s", params["repo"], params["chartName"])
-
- chartCollection := db.Collection(chartCollection)
- filter := bson.M{"_id": chartID}
- findResult := chartCollection.FindOne(context.Background(), filter, &chart, options.FindOne())
- if findResult == mongo.ErrNoDocuments {
- log.WithError(findResult).Errorf("could not find chart with id %s", chartID)
- http.NotFound(w, req)
- return
- }
- if chart.RawIcon == nil {
- http.NotFound(w, req)
- return
- }
-
- if chart.IconContentType != "" {
- // Force the Content-Type header because the autogenerated type does not work for
- // image/svg+xml. It is detected as plain text
- w.Header().Set("Content-Type", chart.IconContentType)
- }
-
- w.Write(chart.RawIcon)
-}
-
-// GetChartVersionReadme returns the README for a given chart
-func GetChartVersionReadme(w http.ResponseWriter, req *http.Request, params Params) {
-
- var files models.ChartFiles
- fileID := fmt.Sprintf("%s/%s-%s", params["repo"], params["chartName"], params["version"])
-
- filesCollection := db.Collection(filesCollection)
- filter := bson.M{"_id": fileID}
- findResult := filesCollection.FindOne(context.Background(), filter, &files, options.FindOne())
- if findResult == mongo.ErrNoDocuments {
- log.WithError(findResult).Errorf("could not find files with id %s", fileID)
- http.NotFound(w, req)
- return
- }
- readme := []byte(files.Readme)
- if len(readme) == 0 {
- log.Errorf("could not find a README for id %s", fileID)
- http.NotFound(w, req)
- return
- }
- w.Write(readme)
-}
-
-// GetChartVersionValues returns the values.yaml for a given chart
-func GetChartVersionValues(w http.ResponseWriter, req *http.Request, params Params) {
- var files models.ChartFiles
-
- fileID := fmt.Sprintf("%s/%s-%s", params["repo"], params["chartName"], params["version"])
- filesCollection := db.Collection(filesCollection)
- filter := bson.M{"_id": fileID}
- findResult := filesCollection.FindOne(context.Background(), filter, &files, options.FindOne())
- if findResult == mongo.ErrNoDocuments {
- log.WithError(findResult).Errorf("could not find values.yaml with id %s", fileID)
- http.NotFound(w, req)
- return
- }
-
- w.Write([]byte(files.Values))
-}
-
-// GetChartVersionSchema returns the values.schema.json for a given chart
-func GetChartVersionSchema(w http.ResponseWriter, req *http.Request, params Params) {
-
- var files models.ChartFiles
-
- fileID := fmt.Sprintf("%s/%s-%s", params["repo"], params["chartName"], params["version"])
- filter := bson.M{"_id": fileID}
- filesCollection := db.Collection(filesCollection)
- findResult := filesCollection.FindOne(context.Background(), filter, &files, options.FindOne())
- if findResult == mongo.ErrNoDocuments {
- log.WithError(findResult).Errorf("could not find values.schema.json with id %s", fileID)
- http.NotFound(w, req)
- return
- }
-
- w.Write([]byte(files.Schema))
-}
-
-// ListChartsWithFilters returns the list of repos that contains the given chart and the latest version found
-func ListChartsWithFilters(w http.ResponseWriter, req *http.Request, params Params) {
-
- var charts []*models.Chart
-
- chartCollection := db.Collection(chartCollection)
- filter := bson.M{
- "name": params["chartName"],
- "chartversions": bson.M{
- "$elemMatch": bson.M{"version": req.FormValue("version"), "appversion": req.FormValue("appversion")},
- }}
- projection := bson.M{
- "name": 1, "repo": 1,
- "chartversions": bson.M{"$slice": 1},
- }
- err := chartCollection.Find(context.Background(), filter, &charts, options.Find().SetProjection(projection))
- if err != nil {
- log.WithError(err).Errorf(
- "Error finding charts with the given name %s, version %s and appversion %s",
- params["chartName"], req.FormValue("version"), req.FormValue("appversion"),
- )
- // continue to return empty list
- }
-
- chartResponse := charts
- if !showDuplicates(req) {
- chartResponse = uniqChartList(charts)
- }
-
- cl := newChartListResponse(chartResponse)
- response.NewDataResponse(cl).Write(w)
-}
-
-// SearchCharts returns the list of charts that matches the query param in any of these fields:
-// - name
-// - description
-// - repository name
-// - any keyword
-// - any source
-// - any maintainer name
-func SearchCharts(w http.ResponseWriter, req *http.Request, params Params) {
-
- query := req.FormValue("q")
- var charts []*models.Chart
-
- chartCollection := db.Collection(chartCollection)
- filter := bson.M{
- "$or": []bson.M{
- {"name": bson.M{"$regex": query}},
- {"description": bson.M{"$regex": query}},
- {"repo.name": bson.M{"$regex": query}},
- {"keywords": bson.M{"$elemMatch": bson.M{"$regex": query}}},
- {"sources": bson.M{"$elemMatch": bson.M{"$regex": query}}},
- {"maintainers": bson.M{"$elemMatch": bson.M{"name": bson.M{"$regex": query}}}},
- },
- }
- if params["repo"] != "" {
- filter["repo.name"] = params["repo"]
- }
- err := chartCollection.Find(context.Background(), filter, &charts, options.Find())
- if err != nil {
- log.WithError(err).Errorf(
- "Error finding charts with the given name %s, version %s and appversion %s",
- params["chartName"], req.FormValue("version"), req.FormValue("appversion"),
- )
- // continue to return empty list
- }
-
- chartResponse := charts
- if !showDuplicates(req) {
- chartResponse = uniqChartList(charts)
- }
-
- cl := newChartListResponse(uniqChartList(chartResponse))
- response.NewDataResponse(cl).Write(w)
-}
-
-func newChartResponse(c *models.Chart) *utils.ApiResponse {
-
- // NWM: This was: latestCV := c.ChartVersions[0] - but this includes development charts
- // FIX: This ignores development versions
- latestCV := findFirstNonDevelopmentVersion(c)
-
- return &utils.ApiResponse{
- Type: "chart",
- ID: c.ID,
- Attributes: chartAttributes(*c),
- Links: utils.SelfLink{Self: pathPrefix + "/charts/" + c.ID},
- Relationships: utils.RelMap{
- "latestChartVersion": utils.Rel{
- Data: chartVersionAttributes(c.ID, latestCV),
- Links: utils.SelfLink{Self: pathPrefix + "/charts/" + c.ID + "/versions/" + latestCV.Version},
- },
- },
- }
-}
-
-func findFirstNonDevelopmentVersion(c *models.Chart) models.ChartVersion {
- for _, chartVersion := range c.ChartVersions {
- if strings.Index(chartVersion.Version, "-") == -1 {
- return chartVersion
- }
- }
- return c.ChartVersions[0]
-}
-
-func newChartListResponse(charts []*models.Chart) utils.ApiListResponse {
- cl := utils.ApiListResponse{}
- for _, c := range charts {
- cl = append(cl, newChartResponse(c))
- }
- return cl
-}
-
-func chartVersionAttributes(cid string, cv models.ChartVersion) models.ChartVersion {
- cv.Readme = pathPrefix + "/assets/" + cid + "/versions/" + cv.Version + "/README.md"
- cv.Values = pathPrefix + "/assets/" + cid + "/versions/" + cv.Version + "/values.yaml"
- cv.Schema = pathPrefix + "/assets/" + cid + "/versions/" + cv.Version + "/values.schema.json"
-
- return cv
-}
-
-func chartAttributes(c models.Chart) models.Chart {
- if c.RawIcon != nil {
- c.Icon = pathPrefix + "/assets/" + c.ID + "/logo"
- } else {
- // If the icon wasn't processed, it is either not set or invalid
- c.Icon = ""
- }
- return c
-}
-
-func newChartVersionResponse(c *models.Chart, cv models.ChartVersion) *utils.ApiResponse {
- return &utils.ApiResponse{
- Type: "chartVersion",
- ID: fmt.Sprintf("%s-%s", c.ID, cv.Version),
- Attributes: chartVersionAttributes(c.ID, cv),
- Links: utils.SelfLink{Self: pathPrefix + "/charts/" + c.ID + "/versions/" + cv.Version},
- Relationships: utils.RelMap{
- "chart": utils.Rel{
- Data: chartAttributes(*c),
- Links: utils.SelfLink{Self: pathPrefix + "/charts/" + c.ID},
- },
- },
- }
-}
-
-func newChartVersionListResponse(c *models.Chart) utils.ApiListResponse {
- var cvl utils.ApiListResponse
- for _, cv := range c.ChartVersions {
- cvl = append(cvl, newChartVersionResponse(c, cv))
- }
-
- return cvl
-}
diff --git a/src/jetstream/plugins/monocular/chartsvc/foundationdb/handler_test.go b/src/jetstream/plugins/monocular/chartsvc/foundationdb/handler_test.go
deleted file mode 100644
index 2496a16069..0000000000
--- a/src/jetstream/plugins/monocular/chartsvc/foundationdb/handler_test.go
+++ /dev/null
@@ -1,867 +0,0 @@
-/*
-Copyright (c) 2019
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package foundationdb
-
-import (
- "bytes"
- "encoding/json"
- "image/color"
- "net/http"
- "net/http/httptest"
- "strconv"
- "strings"
- "testing"
-
- "github.com/helm/monocular/chartsvc/foundationdb/datastore"
- "github.com/helm/monocular/chartsvc/models"
- "github.com/helm/monocular/chartsvc/utils"
-
- "github.com/disintegration/imaging"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/mock"
- "go.mongodb.org/mongo-driver/bson"
- "go.mongodb.org/mongo-driver/mongo"
-)
-
-var cc count
-
-const testChartReadme = "# Quickstart\n\n```bash\nhelm install my-repo/my-chart\n```"
-const testChartValues = "image:\n registry: docker.io\n repository: my-repo/my-chart\n tag: 0.1.0"
-
-const fdbURL = "mongodb://fdb-service/27016"
-const fDB = "charts"
-
-func iconBytes() []byte {
- var b bytes.Buffer
- img := imaging.New(1, 1, color.White)
- imaging.Encode(&b, img, imaging.PNG)
- return b.Bytes()
-}
-
-func Test_chartAttributes(t *testing.T) {
- tests := []struct {
- name string
- chart models.Chart
- }{
- {"chart has no icon", models.Chart{
- ID: "stable/wordpress",
- }},
- {"chart has a icon", models.Chart{
- ID: "repo/mychart", RawIcon: iconBytes(), IconContentType: "image/svg",
- }},
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- c := chartAttributes(tt.chart)
- assert.Equal(t, tt.chart.ID, c.ID)
- assert.Equal(t, tt.chart.RawIcon, c.RawIcon)
- if len(tt.chart.RawIcon) == 0 {
- assert.Equal(t, len(c.Icon), 0, "icon url should be undefined")
- } else {
- assert.Equal(t, c.Icon, pathPrefix+"/assets/"+tt.chart.ID+"/logo", "the icon url should be the same")
- assert.Equal(t, c.IconContentType, tt.chart.IconContentType, "the icon content type should be the same")
- }
- })
- }
-}
-
-func Test_chartVersionAttributes(t *testing.T) {
- tests := []struct {
- name string
- chart models.Chart
- }{
- {"my-chart", models.Chart{
- ID: "my-repo/my-chart", ChartVersions: []models.ChartVersion{{Version: "0.1.0"}},
- }},
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- cv := chartVersionAttributes(tt.chart.ID, tt.chart.ChartVersions[0])
- assert.Equal(t, cv.Version, tt.chart.ChartVersions[0].Version, "version string should be the same")
- assert.Equal(t, cv.Readme, pathPrefix+"/assets/"+tt.chart.ID+"/versions/"+tt.chart.ChartVersions[0].Version+"/README.md", "README.md resource path should be the same")
- assert.Equal(t, cv.Values, pathPrefix+"/assets/"+tt.chart.ID+"/versions/"+tt.chart.ChartVersions[0].Version+"/values.yaml", "values.yaml resource path should be the same")
- })
- }
-}
-
-func Test_newChartResponse(t *testing.T) {
- tests := []struct {
- name string
- chart models.Chart
- }{
- {"chart has only one version", models.Chart{
- ID: "my-repo/my-chart", ChartVersions: []models.ChartVersion{{Version: "1.2.3"}}},
- },
- {"chart has many versions", models.Chart{
- ID: "my-repo/my-chart", ChartVersions: []models.ChartVersion{{Version: "0.1.2"}, {Version: "0.1.0"}},
- }},
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- cResponse := newChartResponse(&tt.chart)
- assert.Equal(t, cResponse.Type, "chart", "response type is chart")
- assert.Equal(t, cResponse.ID, tt.chart.ID, "chart ID should be the same")
- assert.Equal(t, cResponse.Relationships["latestChartVersion"].Data.(models.ChartVersion).Version, tt.chart.ChartVersions[0].Version, "latestChartVersion should match version at index 0")
- assert.Equal(t, cResponse.Links.(utils.SelfLink).Self, pathPrefix+"/charts/"+tt.chart.ID, "self link should be the same")
- assert.Equal(t, len(cResponse.Attributes.(models.Chart).ChartVersions), len(tt.chart.ChartVersions), "number of chart versions in the response should be the same")
- })
- }
-}
-
-func Test_newChartListResponse(t *testing.T) {
- tests := []struct {
- name string
- input []*models.Chart
- result []*models.Chart
- }{
- {"no charts", []*models.Chart{}, []*models.Chart{}},
- {"has one chart", []*models.Chart{
- {ID: "my-repo/my-chart", ChartVersions: []models.ChartVersion{{Version: "0.0.1", Digest: "123"}}},
- }, []*models.Chart{
- {ID: "my-repo/my-chart", ChartVersions: []models.ChartVersion{{Version: "0.0.1", Digest: "123"}}},
- }},
- {"has two charts", []*models.Chart{
- {ID: "my-repo/my-chart", ChartVersions: []models.ChartVersion{{Version: "0.0.1", Digest: "123"}}},
- {ID: "stable/wordpress", ChartVersions: []models.ChartVersion{{Version: "1.2.3", Digest: "1234"}, {Version: "1.2.2", Digest: "12345"}}},
- }, []*models.Chart{
- {ID: "my-repo/my-chart", ChartVersions: []models.ChartVersion{{Version: "0.0.1", Digest: "123"}}},
- {ID: "stable/wordpress", ChartVersions: []models.ChartVersion{{Version: "1.2.3", Digest: "1234"}, {Version: "1.2.2", Digest: "12345"}}},
- }},
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- clResponse := newChartListResponse(tt.input)
- assert.Equal(t, len(clResponse), len(tt.result), "number of charts in response should be the same")
- for i := range tt.result {
- assert.Equal(t, clResponse[i].Type, "chart", "response type is chart")
- assert.Equal(t, clResponse[i].ID, tt.result[i].ID, "chart ID should be the same")
- assert.Equal(t, clResponse[i].Relationships["latestChartVersion"].Data.(models.ChartVersion).Version, tt.result[i].ChartVersions[0].Version, "latestChartVersion should match version at index 0")
- assert.Equal(t, clResponse[i].Links.(utils.SelfLink).Self, pathPrefix+"/charts/"+tt.result[i].ID, "self link should be the same")
- assert.Equal(t, len(clResponse[i].Attributes.(models.Chart).ChartVersions), len(tt.result[i].ChartVersions), "number of chart versions in the response should be the same")
- }
- })
- }
-}
-
-func Test_newChartVersionResponse(t *testing.T) {
- tests := []struct {
- name string
- chart models.Chart
- }{
- {"my-chart", models.Chart{
- ID: "my-repo/my-chart", ChartVersions: []models.ChartVersion{{Version: "0.1.0"}, {Version: "0.2.3"}},
- }},
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- for i := range tt.chart.ChartVersions {
- cvResponse := newChartVersionResponse(&tt.chart, tt.chart.ChartVersions[i])
- assert.Equal(t, cvResponse.Type, "chartVersion", "response type is chartVersion")
- assert.Equal(t, cvResponse.ID, tt.chart.ID+"-"+tt.chart.ChartVersions[i].Version, "reponse id should have chart version suffix")
- assert.Equal(t, cvResponse.Links.(interface{}).(utils.SelfLink).Self, pathPrefix+"/charts/"+tt.chart.ID+"/versions/"+tt.chart.ChartVersions[i].Version, "self link should be the same")
- assert.Equal(t, cvResponse.Attributes.(models.ChartVersion).Version, tt.chart.ChartVersions[i].Version, "chart version in the response should be the same")
- assert.Equal(t, cvResponse.Relationships["chart"].Data.(interface{}).(models.Chart), tt.chart, "chart in relatioship matches")
- }
- })
- }
-}
-
-func Test_newChartVersionListResponse(t *testing.T) {
- tests := []struct {
- name string
- chart models.Chart
- }{
- {"chart has no versions", models.Chart{
- ID: "my-repo/my-chart", ChartVersions: []models.ChartVersion{},
- }},
- {"chart has one version", models.Chart{
- ID: "my-repo/my-chart", ChartVersions: []models.ChartVersion{{Version: "0.0.1"}},
- }},
- {"chart has many versions", models.Chart{
- ID: "my-repo/my-chart", ChartVersions: []models.ChartVersion{{Version: "0.0.1"}, {Version: "0.0.2"}},
- }},
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- cvListResponse := newChartVersionListResponse(&tt.chart)
- assert.Equal(t, len(cvListResponse), len(tt.chart.ChartVersions), "number of chart versions in response should be the same")
- for i := range tt.chart.ChartVersions {
- assert.Equal(t, cvListResponse[i].Type, "chartVersion", "response type is chartVersion")
- assert.Equal(t, cvListResponse[i].ID, tt.chart.ID+"-"+tt.chart.ChartVersions[i].Version, "reponse id should have chart version suffix")
- assert.Equal(t, cvListResponse[i].Links.(interface{}).(utils.SelfLink).Self, pathPrefix+"/charts/"+tt.chart.ID+"/versions/"+tt.chart.ChartVersions[i].Version, "self link should be the same")
- assert.Equal(t, cvListResponse[i].Attributes.(models.ChartVersion).Version, tt.chart.ChartVersions[i].Version, "chart version in the response should be the same")
- }
- })
- }
-}
-
-func Test_listCharts(t *testing.T) {
- pageSize := 2
- tests := []struct {
- name string
- query string
- dbQueryResult []*models.Chart
- chartListResult []*models.Chart
- meta utils.Meta
- }{
- {"no charts", "", []*models.Chart{}, []*models.Chart{}, utils.Meta{TotalPages: 1}},
- {"one chart", "", []*models.Chart{
- {ID: "my-repo/my-chart", Name: "my-chart", ChartVersions: []models.ChartVersion{{Version: "0.0.1", Digest: "123"}}},
- }, []*models.Chart{
- {ID: "my-repo/my-chart", Name: "my-chart", ChartVersions: []models.ChartVersion{{Version: "0.0.1", Digest: "123"}}},
- }, utils.Meta{TotalPages: 1}},
- {"two charts", "", []*models.Chart{
- {ID: "my-repo/my-chart", Name: "my-chart", ChartVersions: []models.ChartVersion{{Version: "0.0.1", Digest: "123"}}},
- {ID: "stable/dokuwiki", Name: "dokuwiki", ChartVersions: []models.ChartVersion{{Version: "1.2.3", Digest: "1234"}, {Version: "1.2.2", Digest: "12345"}}},
- }, []*models.Chart{
- {ID: "stable/dokuwiki", Name: "dokuwiki", ChartVersions: []models.ChartVersion{{Version: "1.2.3", Digest: "1234"}, {Version: "1.2.2", Digest: "12345"}}},
- {ID: "my-repo/my-chart", Name: "my-chart", ChartVersions: []models.ChartVersion{{Version: "0.0.1", Digest: "123"}}},
- }, utils.Meta{TotalPages: 1}},
- // Pagination tests
- {"four charts with pagination", "?size=" + strconv.Itoa(pageSize), []*models.Chart{
- {ID: "my-repo/my-chart", Name: "my-chart", ChartVersions: []models.ChartVersion{{Version: "0.0.1", Digest: "123"}}},
- {ID: "stable/dokuwiki", Name: "dokuwiki", ChartVersions: []models.ChartVersion{{Version: "1.2.3", Digest: "1234"}}},
- {ID: "stable/drupal", Name: "drupal", ChartVersions: []models.ChartVersion{{Version: "1.2.3", Digest: "12345"}}},
- {ID: "stable/wordpress", Name: "wordpress", ChartVersions: []models.ChartVersion{{Version: "1.2.3", Digest: "123456"}}},
- }, []*models.Chart{
- {ID: "stable/dokuwiki", Name: "dokuwiki", ChartVersions: []models.ChartVersion{{Version: "1.2.3", Digest: "1234"}}},
- {ID: "stable/drupal", Name: "drupal", ChartVersions: []models.ChartVersion{{Version: "1.2.3", Digest: "12345"}}},
- }, utils.Meta{TotalPages: 2}},
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- var m mock.Mock
- dbClient = datastore.NewMockClient(&m)
- db, _ = dbClient.Database("test")
- var chartsList []*models.Chart
-
- m.On("Find", mock.Anything, mock.Anything, &chartsList, mock.Anything).Return(nil).Run(func(args mock.Arguments) {
- *args.Get(2).(*[]*models.Chart) = tt.dbQueryResult
- })
-
- w := httptest.NewRecorder()
- req := httptest.NewRequest("GET", "/charts"+tt.query, nil)
- ListCharts(w, req)
-
- m.AssertExpectations(t)
- assert.Equal(t, http.StatusOK, w.Code)
-
- var b utils.BodyAPIListResponse
- json.NewDecoder(w.Body).Decode(&b)
- if b.Data == nil {
- t.Fatal("chart list shouldn't be null")
- }
- data := *b.Data
-
- assert.Len(t, data, len(tt.chartListResult))
-
- for i, resp := range data {
- assert.Equal(t, resp.ID, tt.chartListResult[i].ID, "chart id in the response should be the same")
- assert.Equal(t, resp.Type, "chart", "response type is chart")
- assert.Equal(t, resp.Links.(map[string]interface{})["self"], pathPrefix+"/charts/"+tt.chartListResult[i].ID, "self link should be the same")
- assert.Equal(t, resp.Relationships["latestChartVersion"].Data.(map[string]interface{})["version"], tt.chartListResult[i].ChartVersions[0].Version, "latestChartVersion should match version at index 0")
- }
- assert.Equal(t, b.Meta, tt.meta, "response meta should be the same")
- })
- }
-}
-
-func Test_listRepoCharts(t *testing.T) {
- pageSize := 2
- tests := []struct {
- name string
- repo string
- query string
- dbQueryResult []*models.Chart
- chartListResult []*models.Chart
- meta utils.Meta
- }{
- {"repo has no charts", "my-repo", "", []*models.Chart{}, []*models.Chart{}, utils.Meta{TotalPages: 1}},
- {"repo has one chart", "my-repo", "", []*models.Chart{
- {ID: "my-repo/my-chart", Name: "my-chart", ChartVersions: []models.ChartVersion{{Version: "0.0.1", Digest: "123"}}},
- }, []*models.Chart{
- {ID: "my-repo/my-chart", Name: "my-chart", ChartVersions: []models.ChartVersion{{Version: "0.0.1", Digest: "123"}}},
- }, utils.Meta{TotalPages: 1}},
- {"repo has many charts", "my-repo", "", []*models.Chart{
- {ID: "my-repo/my-chart", Name: "my-chart", ChartVersions: []models.ChartVersion{{Version: "0.0.1", Digest: "123"}}},
- {ID: "my-repo/dokuwiki", Name: "dokuwiki", ChartVersions: []models.ChartVersion{{Version: "1.2.3", Digest: "1234"}, {Version: "1.2.2", Digest: "12345"}}},
- }, []*models.Chart{
- {ID: "my-repo/dokuwiki", Name: "dokuwiki", ChartVersions: []models.ChartVersion{{Version: "1.2.3", Digest: "1234"}, {Version: "1.2.2", Digest: "12345"}}},
- {ID: "my-repo/my-chart", Name: "my-chart", ChartVersions: []models.ChartVersion{{Version: "0.0.1", Digest: "123"}}},
- }, utils.Meta{TotalPages: 1}},
- {"repo has many charts with pagination", "my-repo", "?size=" + strconv.Itoa(pageSize), []*models.Chart{
- {ID: "my-repo/my-chart3", Name: "my-chart3", ChartVersions: []models.ChartVersion{{Version: "0.0.1", Digest: "123"}}},
- {ID: "my-repo/my-chart1", Name: "my-chart1", ChartVersions: []models.ChartVersion{{Version: "0.0.1", Digest: "1234"}}},
- {ID: "my-repo/my-chart2", Name: "my-chart2", ChartVersions: []models.ChartVersion{{Version: "0.0.1", Digest: "12345"}}},
- }, []*models.Chart{
- {ID: "my-repo/my-chart1", Name: "my-chart1", ChartVersions: []models.ChartVersion{{Version: "0.0.1", Digest: "1234"}}},
- {ID: "my-repo/my-chart2", Name: "my-chart2", ChartVersions: []models.ChartVersion{{Version: "0.0.1", Digest: "12345"}}},
- }, utils.Meta{TotalPages: 2}},
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- var m mock.Mock
- dbClient = datastore.NewMockClient(&m)
- db, _ = dbClient.Database("test")
- var chartsList []*models.Chart
- m.On("Find", mock.Anything, bson.M{"repo.name": "my-repo"}, &chartsList, mock.Anything).Return(nil).Run(func(args mock.Arguments) {
- *args.Get(2).(*[]*models.Chart) = tt.dbQueryResult
- })
-
- w := httptest.NewRecorder()
- req := httptest.NewRequest("GET", "/charts/"+tt.repo+tt.query, nil)
- params := Params{
- "repo": "my-repo",
- }
-
- ListRepoCharts(w, req, params)
-
- m.AssertExpectations(t)
- assert.Equal(t, http.StatusOK, w.Code)
-
- var b utils.BodyAPIListResponse
- json.NewDecoder(w.Body).Decode(&b)
- data := *b.Data
- assert.Len(t, data, len(tt.chartListResult))
- for i, resp := range data {
- assert.Equal(t, resp.ID, tt.chartListResult[i].ID, "chart id in the response should be the same")
- assert.Equal(t, resp.Type, "chart", "response type is chart")
- assert.Equal(t, resp.Relationships["latestChartVersion"].Data.(map[string]interface{})["version"], tt.chartListResult[i].ChartVersions[0].Version, "latestChartVersion should match version at index 0")
- }
- assert.Equal(t, b.Meta, tt.meta, "response meta should be the same")
- })
- }
-}
-
-func Test_getChart(t *testing.T) {
- tests := []struct {
- name string
- err error
- chart models.Chart
- wantCode int
- }{
- {
- "chart does not exist",
- mongo.ErrNoDocuments,
- models.Chart{ID: "my-repo/my-chart"},
- http.StatusNotFound,
- },
- {
- "chart exists",
- nil,
- models.Chart{ID: "my-repo/my-chart", ChartVersions: []models.ChartVersion{{Version: "0.1.0"}}},
- http.StatusOK,
- },
- {
- "chart has multiple versions",
- nil,
- models.Chart{ID: "my-repo/my-chart", ChartVersions: []models.ChartVersion{{Version: "0.1.0"}, {Version: "0.0.1"}}},
- http.StatusOK,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- var m mock.Mock
- dbClient = datastore.NewMockClient(&m)
- db, _ = dbClient.Database("test")
-
- if tt.err != nil {
- m.On("FindOne", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tt.err)
- } else {
- m.On("FindOne", mock.Anything, mock.Anything, &models.Chart{}, mock.Anything).Return(nil).Run(func(args mock.Arguments) {
- *args.Get(2).(*models.Chart) = tt.chart
- })
- }
-
- w := httptest.NewRecorder()
- req := httptest.NewRequest("GET", "/charts/"+tt.chart.ID, nil)
- parts := strings.Split(tt.chart.ID, "/")
- params := Params{
- "repo": parts[0],
- "chartName": parts[1],
- }
-
- GetChart(w, req, params)
-
- m.AssertExpectations(t)
- assert.Equal(t, tt.wantCode, w.Code)
- if tt.wantCode == http.StatusOK {
- var b utils.BodyAPIResponse
- json.NewDecoder(w.Body).Decode(&b)
- assert.Equal(t, b.Data.ID, tt.chart.ID, "chart id in the response should be the same")
- assert.Equal(t, b.Data.Type, "chart", "response type is chart")
- assert.Equal(t, b.Data.Links.(map[string]interface{})["self"], pathPrefix+"/charts/"+tt.chart.ID, "self link should be the same")
- assert.Equal(t, b.Data.Relationships["latestChartVersion"].Data.(map[string]interface{})["version"], tt.chart.ChartVersions[0].Version, "latestChartVersion should match version at index 0")
- }
- })
- }
-}
-
-func Test_listChartVersions(t *testing.T) {
- tests := []struct {
- name string
- err error
- chart models.Chart
- wantCode int
- }{
- {
- "chart does not exist",
- mongo.ErrNoDocuments,
- models.Chart{ID: "my-repo/my-chart"},
- http.StatusNotFound,
- },
- {
- "chart exists",
- nil,
- models.Chart{ID: "my-repo/my-chart", ChartVersions: []models.ChartVersion{{Version: "0.1.0"}}},
- http.StatusOK,
- },
- {
- "chart has multiple versions",
- nil,
- models.Chart{ID: "my-repo/my-chart", ChartVersions: []models.ChartVersion{{Version: "0.1.0"}, {Version: "0.0.1"}}},
- http.StatusOK,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- var m mock.Mock
- dbClient = datastore.NewMockClient(&m)
- db, _ = dbClient.Database("test")
-
- if tt.err != nil {
- m.On("FindOne", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tt.err)
- } else {
- m.On("FindOne", mock.Anything, mock.Anything, &models.Chart{}, mock.Anything).Return(nil).Run(func(args mock.Arguments) {
- *args.Get(2).(*models.Chart) = tt.chart
- })
- }
-
- w := httptest.NewRecorder()
- req := httptest.NewRequest("GET", "/charts/"+tt.chart.ID+"/versions", nil)
- parts := strings.Split(tt.chart.ID, "/")
- params := Params{
- "repo": parts[0],
- "chartName": parts[1],
- }
-
- ListChartVersions(w, req, params)
-
- m.AssertExpectations(t)
- assert.Equal(t, tt.wantCode, w.Code)
- if tt.wantCode == http.StatusOK {
- var b utils.BodyAPIListResponse
- json.NewDecoder(w.Body).Decode(&b)
- data := *b.Data
- for i, resp := range data {
- assert.Equal(t, resp.ID, tt.chart.ID+"-"+tt.chart.ChartVersions[i].Version, "chart id in the response should be the same")
- assert.Equal(t, resp.Type, "chartVersion", "response type is chartVersion")
- assert.Equal(t, resp.Attributes.(map[string]interface{})["version"], tt.chart.ChartVersions[i].Version, "chart version should match")
- }
- }
- })
- }
-}
-
-func Test_getChartVersion(t *testing.T) {
- tests := []struct {
- name string
- err error
- chart models.Chart
- wantCode int
- }{
- {
- "chart does not exist",
- mongo.ErrNoDocuments,
- models.Chart{ID: "my-repo/my-chart", ChartVersions: []models.ChartVersion{{Version: "0.1.0"}}},
- http.StatusNotFound,
- },
- {
- "chart exists",
- nil,
- models.Chart{ID: "my-repo/my-chart", ChartVersions: []models.ChartVersion{{Version: "0.1.0"}}},
- http.StatusOK,
- },
- {
- "chart has multiple versions",
- nil,
- models.Chart{ID: "my-repo/my-chart", ChartVersions: []models.ChartVersion{{Version: "0.1.0"}, {Version: "0.0.1"}}},
- http.StatusOK,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- var m mock.Mock
- dbClient = datastore.NewMockClient(&m)
- db, _ = dbClient.Database("test")
-
- if tt.err != nil {
- m.On("FindOne", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tt.err)
- } else {
- m.On("FindOne", mock.Anything, mock.Anything, &models.Chart{}, mock.Anything).Return(nil).Run(func(args mock.Arguments) {
- *args.Get(2).(*models.Chart) = tt.chart
- })
- }
-
- w := httptest.NewRecorder()
- req := httptest.NewRequest("GET", "/charts/"+tt.chart.ID+"/versions/"+tt.chart.ChartVersions[0].Version, nil)
- parts := strings.Split(tt.chart.ID, "/")
- params := Params{
- "repo": parts[0],
- "chartName": parts[1],
- "version": tt.chart.ChartVersions[0].Version,
- }
-
- GetChartVersion(w, req, params)
-
- m.AssertExpectations(t)
- assert.Equal(t, tt.wantCode, w.Code)
- if tt.wantCode == http.StatusOK {
- var b utils.BodyAPIResponse
- json.NewDecoder(w.Body).Decode(&b)
- assert.Equal(t, b.Data.ID, tt.chart.ID+"-"+tt.chart.ChartVersions[0].Version, "chart id in the response should be the same")
- assert.Equal(t, b.Data.Type, "chartVersion", "response type is chartVersion")
- assert.Equal(t, b.Data.Attributes.(map[string]interface{})["version"], tt.chart.ChartVersions[0].Version, "chart version should match")
- }
- })
- }
-}
-
-func Test_getChartIcon(t *testing.T) {
- tests := []struct {
- name string
- err error
- chart models.Chart
- wantCode int
- }{
- {
- "chart does not exist",
- mongo.ErrNoDocuments,
- models.Chart{ID: "my-repo/my-chart"},
- http.StatusNotFound,
- },
- {
- "chart has icon",
- nil,
- models.Chart{ID: "my-repo/my-chart", RawIcon: iconBytes(), IconContentType: "image/png"},
- http.StatusOK,
- },
- {
- "chart does not have a icon",
- nil,
- models.Chart{ID: "my-repo/my-chart"},
- http.StatusNotFound,
- },
- {
- "chart has icon with custom type",
- nil,
- models.Chart{ID: "my-repo/my-chart", RawIcon: iconBytes(), IconContentType: "image/svg"},
- http.StatusOK,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- var m mock.Mock
- dbClient = datastore.NewMockClient(&m)
- db, _ = dbClient.Database("test")
-
- if tt.err != nil {
- m.On("FindOne", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tt.err)
- } else {
- m.On("FindOne", mock.Anything, mock.Anything, &models.Chart{}, mock.Anything).Return(nil).Run(func(args mock.Arguments) {
- *args.Get(2).(*models.Chart) = tt.chart
- })
- }
-
- w := httptest.NewRecorder()
- req := httptest.NewRequest("GET", "/assets/"+tt.chart.ID+"/logo", nil)
- parts := strings.Split(tt.chart.ID, "/")
- params := Params{
- "repo": parts[0],
- "chartName": parts[1],
- }
-
- GetChartIcon(w, req, params)
-
- m.AssertExpectations(t)
- assert.Equal(t, tt.wantCode, w.Code, "http status code should match")
- if tt.wantCode == http.StatusOK {
- assert.Equal(t, w.Body.Bytes(), tt.chart.RawIcon, "raw icon data should match")
- assert.Equal(t, w.Header().Get("Content-Type"), tt.chart.IconContentType, "icon content type should match")
- }
- })
- }
-}
-
-func Test_getChartVersionReadme(t *testing.T) {
- tests := []struct {
- name string
- version string
- err error
- files models.ChartFiles
- wantCode int
- }{
- {
- "chart does not exist",
- "0.1.0",
- mongo.ErrNoDocuments,
- models.ChartFiles{ID: "my-repo/my-chart"},
- http.StatusNotFound,
- },
- {
- "chart exists",
- "1.2.3",
- nil,
- models.ChartFiles{ID: "my-repo/my-chart", Readme: testChartReadme},
- http.StatusOK,
- },
- {
- "chart does not have a readme",
- "1.1.1",
- nil,
- models.ChartFiles{ID: "my-repo/my-chart"},
- http.StatusNotFound,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- var m mock.Mock
- dbClient = datastore.NewMockClient(&m)
- db, _ = dbClient.Database("test")
-
- if tt.err != nil {
- m.On("FindOne", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tt.err)
- } else {
- m.On("FindOne", mock.Anything, mock.Anything, &models.ChartFiles{}, mock.Anything).Return(nil).Run(func(args mock.Arguments) {
- *args.Get(2).(*models.ChartFiles) = tt.files
- })
- }
-
- w := httptest.NewRecorder()
- req := httptest.NewRequest("GET", "/assets/"+tt.files.ID+"/versions/"+tt.version+"/README.md", nil)
- parts := strings.Split(tt.files.ID, "/")
- params := Params{
- "repo": parts[0],
- "chartName": parts[1],
- "version": "0.1.0",
- }
-
- GetChartVersionReadme(w, req, params)
-
- m.AssertExpectations(t)
- assert.Equal(t, tt.wantCode, w.Code, "http status code should match")
- if tt.wantCode == http.StatusOK {
- assert.Equal(t, string(w.Body.Bytes()), tt.files.Readme, "content of the readme should match")
- }
- })
- }
-}
-
-func Test_getChartVersionValues(t *testing.T) {
- tests := []struct {
- name string
- version string
- err error
- files models.ChartFiles
- wantCode int
- }{
- {
- "chart does not exist",
- "0.1.0",
- mongo.ErrNoDocuments,
- models.ChartFiles{ID: "my-repo/my-chart"},
- http.StatusNotFound,
- },
- {
- "chart exists",
- "3.2.1",
- nil,
- models.ChartFiles{ID: "my-repo/my-chart", Values: testChartValues},
- http.StatusOK,
- },
- {
- "chart does not have values.yaml",
- "2.2.2",
- nil,
- models.ChartFiles{ID: "my-repo/my-chart"},
- http.StatusOK,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- var m mock.Mock
- dbClient = datastore.NewMockClient(&m)
- db, _ = dbClient.Database("test")
-
- if tt.err != nil {
- m.On("FindOne", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tt.err)
- } else {
- m.On("FindOne", mock.Anything, mock.Anything, &models.ChartFiles{}, mock.Anything).Return(nil).Run(func(args mock.Arguments) {
- *args.Get(2).(*models.ChartFiles) = tt.files
- })
- }
-
- w := httptest.NewRecorder()
- req := httptest.NewRequest("GET", "/assets/"+tt.files.ID+"/versions/"+tt.version+"/values.yaml", nil)
- parts := strings.Split(tt.files.ID, "/")
- params := Params{
- "repo": parts[0],
- "chartName": parts[1],
- "version": "0.1.0",
- }
-
- GetChartVersionValues(w, req, params)
-
- m.AssertExpectations(t)
- assert.Equal(t, tt.wantCode, w.Code, "http status code should match")
- if tt.wantCode == http.StatusOK {
- assert.Equal(t, string(w.Body.Bytes()), tt.files.Values, "content of values.yaml should match")
- }
- })
- }
-}
-
-func Test_findLatestChart(t *testing.T) {
- t.Run("returns mocked chart", func(t *testing.T) {
- chart := &models.Chart{
- Name: "foo",
- ID: "foo",
- Repo: models.Repo{Name: "bar"},
- ChartVersions: []models.ChartVersion{
- models.ChartVersion{Version: "1.0.0", AppVersion: "0.1.0"},
- models.ChartVersion{Version: "0.0.1", AppVersion: "0.1.0"},
- },
- }
- charts := []*models.Chart{chart}
- reqVersion := "1.0.0"
- reqAppVersion := "0.1.0"
-
- var m mock.Mock
- dbClient = datastore.NewMockClient(&m)
- db, _ = dbClient.Database("test")
- var chartsList []*models.Chart
- m.On("Find", mock.Anything, mock.Anything, &chartsList, mock.Anything).Run(func(args mock.Arguments) {
- *args.Get(2).(*[]*models.Chart) = charts
- }).Return(nil)
-
- w := httptest.NewRecorder()
- req := httptest.NewRequest("GET", "/charts?name="+chart.Name+"&version="+reqVersion+"&appversion="+reqAppVersion, nil)
- params := Params{
- "name": chart.Name,
- "version": reqVersion,
- "appversion": reqAppVersion,
- }
-
- ListChartsWithFilters(w, req, params)
-
- var b utils.BodyAPIListResponse
- json.NewDecoder(w.Body).Decode(&b)
- if b.Data == nil {
- t.Fatal("chart list shouldn't be null")
- }
- data := *b.Data
-
- if data[0].ID != chart.ID {
- t.Errorf("Expecting %v, received %v", chart, data[0].ID)
- }
- })
- t.Run("ignores duplicated chart", func(t *testing.T) {
- charts := []*models.Chart{
- {Name: "foo", ID: "stable/foo", Repo: models.Repo{Name: "bar"}, ChartVersions: []models.ChartVersion{models.ChartVersion{Version: "1.0.0", AppVersion: "0.1.0", Digest: "123"}}},
- {Name: "foo", ID: "bitnami/foo", Repo: models.Repo{Name: "bar"}, ChartVersions: []models.ChartVersion{models.ChartVersion{Version: "1.0.0", AppVersion: "0.1.0", Digest: "123"}}},
- }
- reqVersion := "1.0.0"
- reqAppVersion := "0.1.0"
-
- var m mock.Mock
- dbClient = datastore.NewMockClient(&m)
- db, _ = dbClient.Database("test")
- var chartsList []*models.Chart
- m.On("Find", mock.Anything, mock.Anything, &chartsList, mock.Anything).Run(func(args mock.Arguments) {
- *args.Get(2).(*[]*models.Chart) = charts
- }).Return(nil)
-
- w := httptest.NewRecorder()
- req := httptest.NewRequest("GET", "/charts?name="+charts[0].Name+"&version="+reqVersion+"&appversion="+reqAppVersion, nil)
- params := Params{
- "name": charts[0].Name,
- "version": reqVersion,
- "appversion": reqAppVersion,
- }
-
- ListChartsWithFilters(w, req, params)
-
- var b utils.BodyAPIListResponse
- json.NewDecoder(w.Body).Decode(&b)
- if b.Data == nil {
- t.Fatal("chart list shouldn't be null")
- }
- data := *b.Data
-
- assert.Equal(t, len(data), 1, "it should return a single chart")
- if data[0].ID != charts[0].ID {
- t.Errorf("Expecting %v, received %v", charts[0], data[0].ID)
- }
- })
- t.Run("includes duplicated charts when showDuplicates param set", func(t *testing.T) {
- charts := []*models.Chart{
- {Name: "foo", ID: "stable/foo", Repo: models.Repo{Name: "bar"}, ChartVersions: []models.ChartVersion{models.ChartVersion{Version: "1.0.0", AppVersion: "0.1.0", Digest: "123"}}},
- {Name: "foo", ID: "bitnami/foo", Repo: models.Repo{Name: "bar"}, ChartVersions: []models.ChartVersion{models.ChartVersion{Version: "1.0.0", AppVersion: "0.1.0", Digest: "123"}}},
- }
- reqVersion := "1.0.0"
- reqAppVersion := "0.1.0"
-
- var m mock.Mock
- dbClient = datastore.NewMockClient(&m)
- db, _ = dbClient.Database("test")
- var chartsList []*models.Chart
- m.On("Find", mock.Anything, mock.Anything, &chartsList, mock.Anything).Run(func(args mock.Arguments) {
- *args.Get(2).(*[]*models.Chart) = charts
- }).Return(nil)
-
- w := httptest.NewRecorder()
- req := httptest.NewRequest("GET", "/charts?showDuplicates=true&name="+charts[0].Name+"&version="+reqVersion+"&appversion="+reqAppVersion, nil)
- params := Params{
- "name": charts[0].Name,
- "version": reqVersion,
- "appversion": reqAppVersion,
- }
-
- ListChartsWithFilters(w, req, params)
-
- var b utils.BodyAPIListResponse
- json.NewDecoder(w.Body).Decode(&b)
- if b.Data == nil {
- t.Fatal("chart list shouldn't be null")
- }
- data := *b.Data
-
- assert.Equal(t, 2, len(data), "it should return both charts")
- })
-}
diff --git a/src/jetstream/plugins/monocular/chartsvc/go.mod b/src/jetstream/plugins/monocular/chartsvc/go.mod
deleted file mode 100644
index 3fea97e947..0000000000
--- a/src/jetstream/plugins/monocular/chartsvc/go.mod
+++ /dev/null
@@ -1,31 +0,0 @@
-module github.com/helm/monocular/chartsvc
-
-go 1.12
-
-require (
- github.com/disintegration/imaging v1.6.2
- github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8
- github.com/golang/snappy v0.0.1 // indirect
- github.com/gorilla/mux v1.7.3
- github.com/heptiolabs/healthcheck v0.0.0-20180807145615-6ff867650f40
- github.com/kubeapps/common v0.0.0-20190508164739-10b110436c1a
- github.com/labstack/echo v3.3.10+incompatible // indirect
- github.com/labstack/gommon v0.3.0 // indirect
- github.com/prometheus/client_golang v1.2.1 // indirect
- github.com/sirupsen/logrus v1.4.2
- github.com/stretchr/testify v1.4.0
- github.com/unrolled/render v1.0.1 // indirect
- github.com/urfave/negroni v1.0.0
- github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c // indirect
- github.com/xdg/stringprep v1.0.0 // indirect
- go.mongodb.org/mongo-driver v1.1.3
- golang.org/x/crypto v0.0.0-20191205161847-0a08dada0ff9 // indirect
- golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect
- k8s.io/helm v2.16.1+incompatible
-)
-
-replace (
- github.com/helm/monocular/chartsvc/foundationdb => ./foundationdb
- github.com/helm/monocular/chartsvc/models => ./models
- github.com/helm/monocular/chartsvc/utils => ./utils
-)
diff --git a/src/jetstream/plugins/monocular/chartsvc/go.sum b/src/jetstream/plugins/monocular/chartsvc/go.sum
deleted file mode 100644
index dbf10aba67..0000000000
--- a/src/jetstream/plugins/monocular/chartsvc/go.sum
+++ /dev/null
@@ -1,139 +0,0 @@
-github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
-github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
-github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
-github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
-github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
-github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
-github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
-github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
-github.com/cespare/xxhash/v2 v2.1.0 h1:yTUvW7Vhb89inJ+8irsUqiWjh8iT6sQPZiQzI6ReGkA=
-github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM=
-github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
-github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
-github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
-github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM=
-github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 h1:DujepqpGd1hyOd7aW59XpK7Qymp8iy83xq74fLr21is=
-github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
-github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
-github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
-github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
-github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
-github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
-github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
-github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
-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 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
-github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
-github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
-github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
-github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
-github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
-github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
-github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
-github.com/heptiolabs/healthcheck v0.0.0-20180807145615-6ff867650f40 h1:GT4RsKmHh1uZyhmTkWJTDALRjSHYQp6FRKrotf0zhAs=
-github.com/heptiolabs/healthcheck v0.0.0-20180807145615-6ff867650f40/go.mod h1:NtmN9h8vrTveVQRLHcX2HQ5wIPBDCsZ351TGbZWgg38=
-github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
-github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
-github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
-github.com/konsorten/go-windows-terminal-sequences v1.0.1/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/kubeapps/common v0.0.0-20190508164739-10b110436c1a h1:VqeX/fehAB6FtBox0TVYcjOMXGE56INQIfbXegditX4=
-github.com/kubeapps/common v0.0.0-20190508164739-10b110436c1a/go.mod h1:TsgmjeDpbftqhwPKInJ3v+l+xbHs4goiB6DFb2WqY9c=
-github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg=
-github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s=
-github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0=
-github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
-github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
-github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
-github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
-github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg=
-github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
-github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
-github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
-github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
-github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
-github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
-github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
-github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
-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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
-github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
-github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
-github.com/prometheus/client_golang v1.2.1 h1:JnMpQc6ppsNgw9QPAGF6Dod479itz7lvlsMzzNayLOI=
-github.com/prometheus/client_golang v1.2.1/go.mod h1:XMU6Z2MjaRKVu/dC1qupJI9SiNkDYzz3xecMgSW/F+U=
-github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
-github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM=
-github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
-github.com/prometheus/common v0.7.0 h1:L+1lyG48J1zAQXA3RBX/nG/B3gjlHq0zTt2tlbJLyCY=
-github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
-github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
-github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
-github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQlL8=
-github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
-github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
-github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
-github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
-github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
-github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
-github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
-github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
-github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
-github.com/unrolled/render v1.0.1 h1:VDDnQQVfBMsOsp3VaCJszSO0nkBIVEYoPWeRThk9spY=
-github.com/unrolled/render v1.0.1/go.mod h1:gN9T0NhL4Bfbwu8ann7Ry/TGHYfosul+J0obPf6NBdM=
-github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc=
-github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
-github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
-github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
-github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8=
-github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
-github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c h1:u40Z8hqBAAQyv+vATcGgV0YCnDjqSL7/q/JyPhhJSPk=
-github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I=
-github.com/xdg/stringprep v1.0.0 h1:d9X0esnoa3dFsV0FG35rAT0RIhYFlPq7MiP+DW89La0=
-github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
-go.mongodb.org/mongo-driver v1.1.3 h1:++7u8r9adKhGR+I79NfEtYrk2ktjenErXM99PSufIoI=
-go.mongodb.org/mongo-driver v1.1.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
-go.mongodb.org/mongo-driver v1.2.1 h1:ANAlYXXM5XmOdW/Nc38jOr+wS5nlk7YihT24U1imiWM=
-golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20191205161847-0a08dada0ff9 h1:abxekknhS/Drh3uoQDk5Hc7BgeiyI39Crb7vhf/1j5s=
-golang.org/x/crypto v0.0.0-20191205161847-0a08dada0ff9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U=
-golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
-golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190613194153-d28f0bde5980 h1:dfGZHvZk057jK2MCeWus/TowKpJ8y4AmooUzdBSR9GU=
-golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
-golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
-golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY=
-golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
-golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
-gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-k8s.io/helm v2.16.1+incompatible h1:L+k810plJlaGWEw1EszeT4deK8XVaKxac1oGcuB+WDc=
-k8s.io/helm v2.16.1+incompatible/go.mod h1:LZzlS4LQBHfciFOurYBFkCMTaZ0D1l+p0teMg7TSULI=
diff --git a/src/jetstream/plugins/monocular/chartsvc/main.go b/src/jetstream/plugins/monocular/chartsvc/main.go
deleted file mode 100644
index 05226e6bc5..0000000000
--- a/src/jetstream/plugins/monocular/chartsvc/main.go
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
-Copyright (c) 2017 The Helm Authors
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package chartsvc
-
-import (
- "context"
- "crypto/tls"
- "crypto/x509"
- "flag"
- "io/ioutil"
- "net/http"
- "os"
-
- "github.com/gorilla/mux"
- "github.com/heptiolabs/healthcheck"
- mongoDatastore "github.com/kubeapps/common/datastore"
- log "github.com/sirupsen/logrus"
- "github.com/urfave/negroni"
- "go.mongodb.org/mongo-driver/mongo"
- "go.mongodb.org/mongo-driver/mongo/options"
-
- fdb "github.com/helm/monocular/chartsvc/foundationdb"
- fdbDatastore "github.com/helm/monocular/chartsvc/foundationdb/datastore"
-)
-
-const pathPrefix = "/v1"
-
-var client *mongo.Client
-var dbSession mongoDatastore.Session
-
-func SetupRoutes() http.Handler {
- r := mux.NewRouter()
-
- // Healthcheck
- health := healthcheck.NewHandler()
- r.Handle("/live", health)
- r.Handle("/ready", health)
-
- // Routes
- apiv1 := r.PathPrefix(pathPrefix).Subrouter()
- apiv1.Methods("GET").Path("/charts").Queries("name", "{chartName}", "version", "{version}", "appversion", "{appversion}").Handler(fdb.WithParams(fdb.ListChartsWithFilters))
- apiv1.Methods("GET").Path("/charts").Queries("name", "{chartName}", "version", "{version}", "appversion", "{appversion}", "showDuplicates", "{showDuplicates}").Handler(fdb.WithParams(fdb.ListChartsWithFilters))
- apiv1.Methods("GET").Path("/charts").HandlerFunc(fdb.ListCharts)
- apiv1.Methods("GET").Path("/charts").Queries("showDuplicates", "{showDuplicates}").HandlerFunc(fdb.ListCharts)
- apiv1.Methods("GET").Path("/charts/search").Queries("q", "{query}").Handler(fdb.WithParams(fdb.SearchCharts))
- apiv1.Methods("GET").Path("/charts/search").Queries("q", "{query}", "showDuplicates", "{showDuplicates}").Handler(fdb.WithParams(fdb.SearchCharts))
- apiv1.Methods("GET").Path("/charts/{repo}").Handler(fdb.WithParams(fdb.ListRepoCharts))
- apiv1.Methods("GET").Path("/charts/{repo}/search").Queries("q", "{query}").Handler(fdb.WithParams(fdb.SearchCharts))
- apiv1.Methods("GET").Path("/charts/{repo}/search").Queries("q", "{query}", "showDuplicates", "{showDuplicates}").Handler(fdb.WithParams(fdb.SearchCharts))
- apiv1.Methods("GET").Path("/charts/{repo}/{chartName}").Handler(fdb.WithParams(fdb.GetChart))
- apiv1.Methods("GET").Path("/charts/{repo}/{chartName}/versions").Handler(fdb.WithParams(fdb.ListChartVersions))
- apiv1.Methods("GET").Path("/charts/{repo}/{chartName}/versions/{version}").Handler(fdb.WithParams(fdb.GetChartVersion))
- apiv1.Methods("GET").Path("/assets/{repo}/{chartName}/logo").Handler(fdb.WithParams(fdb.GetChartIcon))
- // Maintain the logo-160x160-fit.png endpoint for backward compatibility /assets/{repo}/{chartName}/logo should be used instead
- apiv1.Methods("GET").Path("/assets/{repo}/{chartName}/logo-160x160-fit.png").Handler(fdb.WithParams(fdb.GetChartIcon))
- apiv1.Methods("GET").Path("/assets/{repo}/{chartName}/versions/{version}/README.md").Handler(fdb.WithParams(fdb.GetChartVersionReadme))
- apiv1.Methods("GET").Path("/assets/{repo}/{chartName}/versions/{version}/values.yaml").Handler(fdb.WithParams(fdb.GetChartVersionValues))
- apiv1.Methods("GET").Path("/assets/{repo}/{chartName}/versions/{version}/values.schema.json").Handler(fdb.WithParams(fdb.GetChartVersionSchema))
-
- n := negroni.Classic()
- n.UseHandler(r)
- return n
-}
-
-func main() {
-
- debug := flag.Bool("debug", false, "Debug Logging")
-
- //Flags for optional FoundationDB + Document Layer backend
- fdbURL := flag.String("doclayer-url", "mongodb://fdb-service/27016", "FoundationDB Document Layer URL")
- fDB := flag.String("doclayer-database", "monocular-plugin", "FoundationDB Document-Layer database")
-
- //Flags for Serve-Mode TLS
- cACertFile := flag.String("cafile", "", "Path to CA certificate to use for client verification.")
- certFile := flag.String("certfile", "", "Path to TLS certificate.")
- keyFile := flag.String("keyfile", "", "Path to TLS key.")
-
- //TLS options must either be all set to enabled TLS, or none set to disable TLS
- var tlsEnabled = *cACertFile != "" && *keyFile != "" && *certFile != ""
- if !(tlsEnabled || (*cACertFile == "" && *keyFile == "" && *certFile == "")) {
- log.Fatal("To enable TLS, all 3 TLS cert paths must be set.")
- }
-
- flag.Parse()
-
- if *debug {
- log.SetLevel(log.DebugLevel)
- }
-
- InitFDBDocLayerConnection(fdbURL, fDB, &tlsEnabled, *cACertFile, *certFile, *keyFile, debug)
-
- n := SetupRoutes()
-
- port := os.Getenv("PORT")
- if port == "" {
- port = "8080"
- }
- addr := ":" + port
- log.WithFields(log.Fields{"addr": addr}).Info("Started chartsvc")
- http.ListenAndServe(addr, n)
-}
-
-func InitFDBDocLayerConnection(fdbURL *string, fDB *string, tlsEnabled *bool, CAFile string, certFile string, keyFile string, debug *bool) *ChartSvcDatastore {
-
- log.Debugf("Attempting to connect to FDB: %v, %v, debug: %v", *fdbURL, *fDB, *debug)
-
- var tlsConfig *tls.Config
-
- if *tlsEnabled {
- //Load CA Cert from file here
- CA, err := ioutil.ReadFile(CAFile) // just pass the file name
- if err != nil {
- log.Fatalf("Cannot load CA certificate from file: %v.", err)
- return nil
- }
- CACert := x509.NewCertPool()
- ok := CACert.AppendCertsFromPEM([]byte(CA))
- if !ok {
- log.Fatalf("Cannot append CA certificate to certificate pool.")
- return nil
- }
- //Now load the key pair and create tls options struct
- clientKeyPair, err := tls.LoadX509KeyPair(certFile, keyFile)
- if err != nil {
- log.Fatalf("Cannot load server keypair: %v", err)
- return nil
- }
-
- tlsConfig = &tls.Config{RootCAs: CACert, Certificates: []tls.Certificate{clientKeyPair}}
- }
-
- clientOptions := options.Client().ApplyURI(*fdbURL)
- if *tlsEnabled {
- clientOptions.SetTLSConfig(tlsConfig)
- }
- client, err := fdbDatastore.NewDocLayerClient(context.Background(), clientOptions)
- if err != nil {
- log.Fatalf("Can't create client for FoundationDB document layer: %v. URL provided was: %v.", err, *fdbURL)
- return nil
- }
- log.Debugf("FDB Document Layer client created.")
-
- fdb.InitDBConfig(client, *fDB)
- fdb.SetPathPrefix(pathPrefix)
-
- db, dbCloser := client.Database(*fDB)
- datastore := &ChartSvcDatastore{
- dbClient: client,
- db: db,
- dbCloser: dbCloser,
- }
-
- return datastore
-}
diff --git a/src/jetstream/plugins/monocular/chartsvc/main_test.go b/src/jetstream/plugins/monocular/chartsvc/main_test.go
deleted file mode 100644
index 8dbc7b3e11..0000000000
--- a/src/jetstream/plugins/monocular/chartsvc/main_test.go
+++ /dev/null
@@ -1,544 +0,0 @@
-/*
-Copyright (c) 2017 The Helm Authors
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package chartsvc
-
-import (
- "encoding/json"
- "errors"
- "net/http"
- "net/http/httptest"
- "testing"
-
- fdb "github.com/helm/monocular/chartsvc/foundationdb"
- datastore "github.com/helm/monocular/chartsvc/foundationdb/datastore"
- "github.com/helm/monocular/chartsvc/models"
- "github.com/helm/monocular/chartsvc/utils"
- "go.mongodb.org/mongo-driver/mongo"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/mock"
-)
-
-var dbType string = "mongodb"
-
-const testChartSchema = `{"properties": {"type": "object"}}`
-
-// tests the GET /live endpoint
-func Test_GetLive(t *testing.T) {
- var m mock.Mock
- dbClient := datastore.NewMockClient(&m)
- fdb.InitDBConfig(dbClient, "test")
-
- ts := httptest.NewServer(SetupRoutes())
- defer ts.Close()
-
- res, err := http.Get(ts.URL + "/live")
- assert.NoError(t, err, "should not return an error")
- defer res.Body.Close()
- assert.Equal(t, res.StatusCode, http.StatusOK, "http status code should match")
-}
-
-// tests the GET /ready endpoint
-func Test_GetReady(t *testing.T) {
- var m mock.Mock
- dbClient := datastore.NewMockClient(&m)
- fdb.InitDBConfig(dbClient, "test")
-
- ts := httptest.NewServer(SetupRoutes())
- defer ts.Close()
-
- res, err := http.Get(ts.URL + "/ready")
- assert.NoError(t, err, "should not return an error")
- defer res.Body.Close()
- assert.Equal(t, res.StatusCode, http.StatusOK, "http status code should match")
-}
-
-// tests the GET /{apiVersion}/charts endpoint
-func Test_GetCharts(t *testing.T) {
- ts := httptest.NewServer(SetupRoutes())
- defer ts.Close()
-
- tests := []struct {
- name string
- charts []*models.Chart
- }{
- {"no charts", []*models.Chart{}},
- {"one chart", []*models.Chart{
- {ID: "my-repo/my-chart", ChartVersions: []models.ChartVersion{{Version: "0.0.1"}}}},
- },
- {"two charts", []*models.Chart{
- {ID: "my-repo/my-chart", ChartVersions: []models.ChartVersion{{Version: "0.0.1", Digest: "123"}}},
- {ID: "my-repo/dokuwiki", ChartVersions: []models.ChartVersion{{Version: "1.2.3", Digest: "1234"}, {Version: "1.2.2", Digest: "12345"}}},
- }},
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- var m mock.Mock
- dbClient := datastore.NewMockClient(&m)
- fdb.InitDBConfig(dbClient, "test")
-
- m.On("Find", mock.Anything, mock.Anything, &utils.ChartsList, mock.Anything).Return(nil).Run(func(args mock.Arguments) {
- *args.Get(2).(*[]*models.Chart) = tt.charts
- })
-
- res, err := http.Get(ts.URL + pathPrefix + "/charts")
- assert.NoError(t, err)
- defer res.Body.Close()
-
- m.AssertExpectations(t)
- assert.Equal(t, res.StatusCode, http.StatusOK, "http status code should match")
-
- var b utils.BodyAPIListResponse
- json.NewDecoder(res.Body).Decode(&b)
- assert.Len(t, *b.Data, len(tt.charts))
- })
- }
-}
-
-// tests the GET /{apiVersion}/charts/{repo} endpoint
-func Test_GetChartsInRepo(t *testing.T) {
- ts := httptest.NewServer(SetupRoutes())
- defer ts.Close()
-
- tests := []struct {
- name string
- repo string
- charts []*models.Chart
- }{
- {"repo has no charts", "my-repo", []*models.Chart{}},
- {"repo has one chart", "my-repo", []*models.Chart{
- {ID: "my-repo/my-chart", ChartVersions: []models.ChartVersion{{Version: "0.0.1", Digest: "123"}}},
- }},
- {"repo has many charts", "my-repo", []*models.Chart{
- {ID: "my-repo/my-chart", ChartVersions: []models.ChartVersion{{Version: "0.0.1", Digest: "123"}}},
- {ID: "my-repo/dokuwiki", ChartVersions: []models.ChartVersion{{Version: "1.2.3", Digest: "1234"}, {Version: "1.2.2", Digest: "12345"}}},
- }},
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- var m mock.Mock
- dbClient := datastore.NewMockClient(&m)
- fdb.InitDBConfig(dbClient, "test")
-
- m.On("Find", mock.Anything, mock.Anything, &utils.ChartsList, mock.Anything).Return(nil).Run(func(args mock.Arguments) {
- *args.Get(2).(*[]*models.Chart) = tt.charts
- })
- res, err := http.Get(ts.URL + pathPrefix + "/charts/" + tt.repo)
- assert.NoError(t, err)
- defer res.Body.Close()
-
- m.AssertExpectations(t)
- assert.Equal(t, res.StatusCode, http.StatusOK, "http status code should match")
-
- var b utils.BodyAPIListResponse
- json.NewDecoder(res.Body).Decode(&b)
- assert.Len(t, *b.Data, len(tt.charts))
- })
- }
-}
-
-// tests the GET /{apiVersion}/charts/{repo}/{chartName} endpoint
-func Test_GetChartInRepo(t *testing.T) {
- ts := httptest.NewServer(SetupRoutes())
- defer ts.Close()
-
- tests := []struct {
- name string
- err error
- chart models.Chart
- wantCode int
- }{
- {
- "chart does not exist",
- errors.New("return an error when checking if chart exists"),
- models.Chart{ID: "my-repo/my-chart"},
- http.StatusNotFound,
- },
- {
- "chart exists",
- nil,
- models.Chart{ID: "my-repo/my-chart", ChartVersions: []models.ChartVersion{{Version: "0.1.0"}}},
- http.StatusOK,
- },
- {
- "chart has multiple versions",
- nil,
- models.Chart{ID: "my-repo/my-chart", ChartVersions: []models.ChartVersion{{Version: "0.1.0"}, {Version: "0.0.1"}}},
- http.StatusOK,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- var m mock.Mock
- dbClient := datastore.NewMockClient(&m)
- fdb.InitDBConfig(dbClient, "test")
- if tt.err != nil {
- m.On("FindOne", mock.Anything, mock.Anything, &models.Chart{}, mock.Anything).Return(mongo.ErrNoDocuments)
- } else {
- m.On("FindOne", mock.Anything, mock.Anything, &models.Chart{}, mock.Anything).Return(nil).Run(func(args mock.Arguments) {
- *args.Get(2).(*models.Chart) = tt.chart
- })
- }
-
- res, err := http.Get(ts.URL + pathPrefix + "/charts/" + tt.chart.ID)
- assert.NoError(t, err)
- defer res.Body.Close()
-
- m.AssertExpectations(t)
- assert.Equal(t, tt.wantCode, res.StatusCode, "http status code should match")
- })
- }
-}
-
-// tests the GET /{apiVersion}/charts/{repo}/{chartName}/versions endpoint
-func Test_ListChartVersions(t *testing.T) {
- ts := httptest.NewServer(SetupRoutes())
- defer ts.Close()
-
- tests := []struct {
- name string
- err error
- chart models.Chart
- wantCode int
- }{
- {
- "chart does not exist",
- errors.New("return an error when checking if chart exists"),
- models.Chart{ID: "my-repo/my-chart"},
- http.StatusNotFound,
- },
- {
- "chart exists",
- nil,
- models.Chart{ID: "my-repo/my-chart", ChartVersions: []models.ChartVersion{{Version: "0.1.0"}}},
- http.StatusOK,
- },
- {
- "chart has multiple versions",
- nil,
- models.Chart{ID: "my-repo/my-chart", ChartVersions: []models.ChartVersion{{Version: "0.1.0"}, {Version: "0.0.1"}}},
- http.StatusOK,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- var m mock.Mock
- dbClient := datastore.NewMockClient(&m)
- fdb.InitDBConfig(dbClient, "test")
- if tt.err != nil {
- m.On("FindOne", mock.Anything, mock.Anything, &models.Chart{}, mock.Anything).Return(mongo.ErrNoDocuments)
- } else {
- m.On("FindOne", mock.Anything, mock.Anything, &models.Chart{}, mock.Anything).Return(nil).Run(func(args mock.Arguments) {
- *args.Get(2).(*models.Chart) = tt.chart
- })
- }
-
- res, err := http.Get(ts.URL + pathPrefix + "/charts/" + tt.chart.ID + "/versions")
- assert.NoError(t, err)
- defer res.Body.Close()
-
- m.AssertExpectations(t)
- assert.Equal(t, tt.wantCode, res.StatusCode, "http status code should match")
- })
- }
-}
-
-// tests the GET /{apiVersion}/charts/{repo}/{chartName}/versions/{:version} endpoint
-func Test_GetChartVersion(t *testing.T) {
- ts := httptest.NewServer(SetupRoutes())
- defer ts.Close()
-
- tests := []struct {
- name string
- err error
- chart models.Chart
- wantCode int
- }{
- {
- "chart does not exist",
- errors.New("return an error when checking if chart exists"),
- models.Chart{ID: "my-repo/my-chart", ChartVersions: []models.ChartVersion{{Version: "0.1.0"}}},
- http.StatusNotFound,
- },
- {
- "chart exists",
- nil,
- models.Chart{ID: "my-repo/my-chart", ChartVersions: []models.ChartVersion{{Version: "0.1.0"}}},
- http.StatusOK,
- },
- {
- "chart has multiple versions",
- nil,
- models.Chart{ID: "my-repo/my-chart", ChartVersions: []models.ChartVersion{{Version: "0.1.0"}, {Version: "0.0.1"}}},
- http.StatusOK,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- var m mock.Mock
- dbClient := datastore.NewMockClient(&m)
- fdb.InitDBConfig(dbClient, "test")
- if tt.err != nil {
- m.On("FindOne", mock.Anything, mock.Anything, &models.Chart{}, mock.Anything).Return(mongo.ErrNoDocuments)
- } else {
- m.On("FindOne", mock.Anything, mock.Anything, &models.Chart{}, mock.Anything).Return(nil).Run(func(args mock.Arguments) {
- *args.Get(2).(*models.Chart) = tt.chart
- })
- }
-
- res, err := http.Get(ts.URL + pathPrefix + "/charts/" + tt.chart.ID + "/versions/" + tt.chart.ChartVersions[0].Version)
- assert.NoError(t, err)
- defer res.Body.Close()
-
- m.AssertExpectations(t)
- assert.Equal(t, res.StatusCode, tt.wantCode, "http status code should match")
- })
- }
-}
-
-// tests the GET /{apiVersion}/assets/{repo}/{chartName}/logo-160x160-fit.png endpoint
-func Test_GetChartIcon(t *testing.T) {
- ts := httptest.NewServer(SetupRoutes())
- defer ts.Close()
-
- tests := []struct {
- name string
- err error
- chart models.Chart
- wantCode int
- }{
- {
- "chart does not exist",
- errors.New("return an error when checking if chart exists"),
- models.Chart{ID: "my-repo/my-chart"},
- http.StatusNotFound,
- },
- {
- "chart has icon",
- nil,
- models.Chart{ID: "my-repo/my-chart", RawIcon: utils.IconBytes()},
- http.StatusOK,
- },
- {
- "chart does not have a icon",
- nil,
- models.Chart{ID: "my-repo/my-chart"},
- http.StatusNotFound,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- var m mock.Mock
- dbClient := datastore.NewMockClient(&m)
- fdb.InitDBConfig(dbClient, "test")
- if tt.err != nil {
- m.On("FindOne", mock.Anything, mock.Anything, &models.Chart{}, mock.Anything).Return(mongo.ErrNoDocuments)
- } else {
- m.On("FindOne", mock.Anything, mock.Anything, &models.Chart{}, mock.Anything).Return(nil).Run(func(args mock.Arguments) {
- *args.Get(2).(*models.Chart) = tt.chart
- })
- }
-
- res, err := http.Get(ts.URL + pathPrefix + "/assets/" + tt.chart.ID + "/logo")
- assert.NoError(t, err)
- defer res.Body.Close()
-
- m.AssertExpectations(t)
- assert.Equal(t, tt.wantCode, res.StatusCode, "http status code should match")
- })
- }
-}
-
-// tests the GET /{apiVersion}/assets/{repo}/{chartName}/versions/{version}/README.md endpoint
-func Test_GetChartReadme(t *testing.T) {
- ts := httptest.NewServer(SetupRoutes())
- defer ts.Close()
-
- tests := []struct {
- name string
- version string
- err error
- files models.ChartFiles
- wantCode int
- }{
- {
- "chart does not exist",
- "0.1.0",
- errors.New("return an error when checking if chart exists"),
- models.ChartFiles{ID: "my-repo/my-chart"},
- http.StatusNotFound,
- },
- {
- "chart exists",
- "1.2.3",
- nil,
- models.ChartFiles{ID: "my-repo/my-chart", Readme: utils.TestChartReadme},
- http.StatusOK,
- },
- {
- "chart does not have a readme",
- "1.1.1",
- nil,
- models.ChartFiles{ID: "my-repo/my-chart"},
- http.StatusNotFound,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- var m mock.Mock
- dbClient := datastore.NewMockClient(&m)
- fdb.InitDBConfig(dbClient, "test")
- if tt.err != nil {
- m.On("FindOne", mock.Anything, mock.Anything, &models.ChartFiles{}, mock.Anything).Return(mongo.ErrNoDocuments)
- } else {
- m.On("FindOne", mock.Anything, mock.Anything, &models.ChartFiles{}, mock.Anything).Return(nil).Run(func(args mock.Arguments) {
- *args.Get(2).(*models.ChartFiles) = tt.files
- })
- }
-
- res, err := http.Get(ts.URL + pathPrefix + "/assets/" + tt.files.ID + "/versions/" + tt.version + "/README.md")
- assert.NoError(t, err)
- defer res.Body.Close()
-
- m.AssertExpectations(t)
- assert.Equal(t, tt.wantCode, res.StatusCode, "http status code should match")
- })
- }
-}
-
-// tests the GET /{apiVersion}/assets/{repo}/{chartName}/versions/{version}/values.yaml endpoint
-func Test_GetChartValues(t *testing.T) {
- ts := httptest.NewServer(SetupRoutes())
- defer ts.Close()
-
- tests := []struct {
- name string
- version string
- err error
- files models.ChartFiles
- wantCode int
- }{
- {
- "chart does not exist",
- "0.1.0",
- errors.New("return an error when checking if chart exists"),
- models.ChartFiles{ID: "my-repo/my-chart"},
- http.StatusNotFound,
- },
- {
- "chart exists",
- "3.2.1",
- nil,
- models.ChartFiles{ID: "my-repo/my-chart", Values: utils.TestChartValues},
- http.StatusOK,
- },
- {
- "chart does not have values.yaml",
- "2.2.2",
- nil,
- models.ChartFiles{ID: "my-repo/my-chart"},
- http.StatusOK,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- var m mock.Mock
- dbClient := datastore.NewMockClient(&m)
- fdb.InitDBConfig(dbClient, "test")
- if tt.err != nil {
- m.On("FindOne", mock.Anything, mock.Anything, &models.ChartFiles{}, mock.Anything).Return(mongo.ErrNoDocuments)
- } else {
- m.On("FindOne", mock.Anything, mock.Anything, &models.ChartFiles{}, mock.Anything).Return(nil).Run(func(args mock.Arguments) {
- *args.Get(2).(*models.ChartFiles) = tt.files
- })
- }
-
- res, err := http.Get(ts.URL + pathPrefix + "/assets/" + tt.files.ID + "/versions/" + tt.version + "/values.yaml")
- assert.NoError(t, err)
- defer res.Body.Close()
-
- m.AssertExpectations(t)
- assert.Equal(t, res.StatusCode, tt.wantCode, "http status code should match")
- })
- }
-}
-
-// tests the GET /{apiVersion}/assets/{repo}/{chartName}/versions/{version}/values/schema.json endpoint
-func Test_GetChartSchema(t *testing.T) {
- ts := httptest.NewServer(SetupRoutes())
- defer ts.Close()
-
- tests := []struct {
- name string
- version string
- err error
- files models.ChartFiles
- wantCode int
- }{
- {
- "chart does not exist",
- "0.1.0",
- errors.New("return an error when checking if chart exists"),
- models.ChartFiles{ID: "my-repo/my-chart"},
- http.StatusNotFound,
- },
- {
- "chart exists",
- "3.2.1",
- nil,
- models.ChartFiles{ID: "my-repo/my-chart", Schema: testChartSchema},
- http.StatusOK,
- },
- {
- "chart does not have values.schema.json",
- "2.2.2",
- nil,
- models.ChartFiles{ID: "my-repo/my-chart"},
- http.StatusOK,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- var m mock.Mock
- dbClient := datastore.NewMockClient(&m)
- fdb.InitDBConfig(dbClient, "test")
- if tt.err != nil {
- m.On("FindOne", mock.Anything, mock.Anything, &models.ChartFiles{}, mock.Anything).Return(mongo.ErrNoDocuments)
- } else {
- m.On("FindOne", mock.Anything, mock.Anything, &models.ChartFiles{}, mock.Anything).Return(nil).Run(func(args mock.Arguments) {
- *args.Get(2).(*models.ChartFiles) = tt.files
- })
- }
-
- res, err := http.Get(ts.URL + pathPrefix + "/assets/" + tt.files.ID + "/versions/" + tt.version + "/values.schema.json")
- assert.NoError(t, err)
- defer res.Body.Close()
-
- m.AssertExpectations(t)
- assert.Equal(t, res.StatusCode, tt.wantCode, "http status code should match")
- })
- }
-}
diff --git a/src/jetstream/plugins/monocular/chartsvc/utils/testutils.go b/src/jetstream/plugins/monocular/chartsvc/utils/testutils.go
deleted file mode 100644
index 4014887a8f..0000000000
--- a/src/jetstream/plugins/monocular/chartsvc/utils/testutils.go
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
-Copyright (c) 2019 The Helm Authors
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package utils
-
-import (
- "bytes"
- "image/color"
-
- "github.com/helm/monocular/chartsvc/models"
-
- "github.com/disintegration/imaging"
-)
-
-//ChartsList a list of charts used in unit tests
-var ChartsList []*models.Chart
-
-//IconBytes the bytes of a chart icon image used in unit tests
-func IconBytes() []byte {
- var b bytes.Buffer
- img := imaging.New(1, 1, color.White)
- imaging.Encode(&b, img, imaging.PNG)
- return b.Bytes()
-}
-
-const TestChartReadme = "# Quickstart\n\n```bash\nhelm install my-repo/my-chart\n```"
-const TestChartValues = "image:\n registry: docker.io\n repository: my-repo/my-chart\n tag: 0.1.0"
-const TestChartSchema = `{"properties": {"type": "object"}}`
diff --git a/src/jetstream/plugins/monocular/chartsvc_main.txt b/src/jetstream/plugins/monocular/chartsvc_main.txt
deleted file mode 100644
index 33c73651be..0000000000
--- a/src/jetstream/plugins/monocular/chartsvc_main.txt
+++ /dev/null
@@ -1,15 +0,0 @@
-
-// Stratos addition to chartsvc main.go
-
-// SetMongoConfig allosws Stratos plugin to configure MongoDB
-func SetMongoConfig(dbURL, dbName, dbUsername *string, dbPassword string) (datastore.Session, error) {
- var err error
- mongoConfig := datastore.Config{URL: *dbURL, Database: *dbName, Username: *dbUsername, Password: dbPassword}
- dbSession, err = datastore.NewSession(mongoConfig)
- return dbSession, err
-}
-
-// GetRoutes exposes the HTTP routes for the Chart Service API
-func GetRoutes() http.Handler {
- return setupRoutes()
-}
diff --git a/src/jetstream/plugins/monocular/git-merge-subpath.sh b/src/jetstream/plugins/monocular/git-merge-subpath.sh
deleted file mode 100755
index da5ecf1b8b..0000000000
--- a/src/jetstream/plugins/monocular/git-merge-subpath.sh
+++ /dev/null
@@ -1,65 +0,0 @@
-#! /bin/bash
-
-#================================================
-# Attribution:
-# https://stackoverflow.com/questions/23937436/add-subdirectory-of-remote-repo-with-git-subtree/30386041#30386041
-#================================================================
-
-git-merge-subpath() {
- local SQUASH
- if [[ $1 == "--squash" ]]; then
- SQUASH=1
- shift
- fi
- if (( $# != 3 )); then
- local PARAMS="[--squash] SOURCE_COMMIT SOURCE_PREFIX DEST_PREFIX"
- echo "USAGE: ${FUNCNAME[0]} $PARAMS"
- return 1
- fi
-
- # Friendly parameter names; strip any trailing slashes from prefixes.
- local SOURCE_COMMIT="$1" SOURCE_PREFIX="${2%/}" DEST_PREFIX="${3%/}"
-
- local SOURCE_SHA1
- SOURCE_SHA1=$(git rev-parse --verify "$SOURCE_COMMIT^{commit}")
-
- local OLD_SHA1
- local GIT_ROOT=$(git rev-parse --show-toplevel)
- if [[ -n "$(ls -A "$GIT_ROOT/$DEST_PREFIX" 2> /dev/null)" ]]; then
- # OLD_SHA1 will remain empty if there is no match.
- local RE="^${FUNCNAME[0]}: [0-9a-f]{40} $SOURCE_PREFIX $DEST_PREFIX\$"
- OLD_SHA1=$(git log -1 --format=%b -E --grep="$RE" \
- | grep --color=never -E "$RE" | tail -1 | awk '{print $2}')
- fi
-
- local OLD_TREEISH
- if [[ -n $OLD_SHA1 ]]; then
- OLD_TREEISH="$OLD_SHA1:$SOURCE_PREFIX"
- else
- # This is the first time git-merge-subpath is run, so diff against the
- # empty commit instead of the last commit created by git-merge-subpath.
- OLD_TREEISH=$(git hash-object -t tree /dev/null)
- fi &&
-
- if [[ -z $SQUASH ]]; then
- git merge -s ours --no-commit "$SOURCE_COMMIT" || return 1
- fi &&
-
- printf "Calculating diff of changes from source and applying to destination..."
- git diff --color=never "$OLD_TREEISH" "$SOURCE_COMMIT:$SOURCE_PREFIX" \
- | git apply -3 --directory="$DEST_PREFIX" || git mergetool
- echo "done."
- if (( $? == 1 )); then
- echo "Uh-oh! Try cleaning up with |git reset --merge|."
- echo "You may be able to finish the merge manually then run the following (do not modify the commit message):"
- echo "git commit -em \"Merge $SOURCE_COMMIT:$SOURCE_PREFIX/ to $DEST_PREFIX/ ${FUNCNAME[0]}: $SOURCE_SHA1 $SOURCE_PREFIX $DEST_PREFIX\""
- return 1
- else
- git commit -em "Merge $SOURCE_COMMIT:$SOURCE_PREFIX/ to $DEST_PREFIX/
-
-# Feel free to edit the title and body above, but make sure to keep the
-# ${FUNCNAME[0]}: line below intact, so ${FUNCNAME[0]} can find it
-# again when grepping git log.
-${FUNCNAME[0]}: $SOURCE_SHA1 $SOURCE_PREFIX $DEST_PREFIX"
- fi
-}
diff --git a/src/jetstream/plugins/monocular/git-pull-downstream.sh b/src/jetstream/plugins/monocular/git-pull-downstream.sh
deleted file mode 100755
index 462b3bdc0b..0000000000
--- a/src/jetstream/plugins/monocular/git-pull-downstream.sh
+++ /dev/null
@@ -1,77 +0,0 @@
-#!/bin/bash
-
-echo "=== Pull Monocular Code to Stratos Repo ===" >&1
-echo "Pulls the subtree under cmd from monocular repo into the src/jetstream/plugins/monocular subtree in stratos" >&1
-usage() {
- echo "Usage: git-pull-downstream.sh -f [ -b | -r ]" >&1
- echo " options:" >&1
- printf " -f MONOCULAR_FORK The upstream fork/repo to pull from\n" >&1
- printf " ONE OF EITHER -b or -r must be set\n" >&1
- echo " -b MONOCULAR_BRANCH The upstream branch to pull from" >&1
- echo " -r MONOCULAR_REF The upsream ref to pull from (e.g. tag or SHA1)" >&1
- echo " -h usage" >&1
- exit 0
-}
-
-while getopts f:b:r:h arg
-do
- case ${arg} in
- f) MONOCULAR_FORK=${OPTARG};;
- b) MONOCULAR_BRANCH=${OPTARG};;
- r) MONOCULAR_REF=${OPTARG};;
- h) usage;;
- \?) usage;;
- esac
-done
-
-if [ -z "$MONOCULAR_FORK" ]; then
- echo "Must specify a monocular fork to pull from" >&2
- usage
-fi
-
-if [ -n "$MONOCULAR_BRANCH" ] && [ -n "$MONOCULAR_REF" ] || [ -z "$MONOCULAR_BRANCH" ] && [ -z "$MONOCULAR_REF" ]; then
- echo "Must specify either a monocular branch or ref to pull from" >&2
- usage
-fi
-
-echo "Monocular fork: $MONOCULAR_FORK" >&1
-echo "Monocular branch: $MONOCULAR_BRANCH" >&1
-echo "Monocular ref: $MONOCULAR_REF" >&1
-
-DIRPATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
-echo "${DIRPATH}"
-
-pushd "${DIRPATH}" >& /dev/null || exit
-
-source ./git-merge-subpath.sh
-
-echo "Fetching $MONOCULAR_FORK"
-
-# Fetch and merge the monocular subtree into the Stratos repo and attempt squash commit
-git fetch "$MONOCULAR_FORK"
-
-# Set our pull ref to either "fork/branch" or [tag|commit]
-if [ -n "$MONOCULAR_REF" ]; then
- PULL_REF="$MONOCULAR_REF"
-else
- PULL_REF="$MONOCULAR_FORK/$MONOCULAR_BRANCH"
-fi
-
-echo "Reading subtree index from $MONOCULAR_FORK:cmd. Merging in changes from $PULL_REF into src/jetstream/plugins/monocular" >&1
-# Move to root of repo
-repo_root=$(git rev-parse --show-toplevel)
-echo "Moving to repo top level: $repo_root" >&1
-pushd "$repo_root" >&/dev/null || exit
-
-# Read in tree
-# Git diff apply from last commit (or none) to the specified commit between the given subtrees
-git-merge-subpath --squash "$PULL_REF" cmd src/jetstream/plugins/monocular
-merge_retval=$?
-
-popd >& /dev/null || exit
-popd >& /dev/null || exit
-
-if [ $merge_retval -eq 0 ]; then
- echo "Success" >&1
-fi
-
diff --git a/src/jetstream/plugins/monocular/git-push-upstream.sh b/src/jetstream/plugins/monocular/git-push-upstream.sh
deleted file mode 100755
index c4e54aaed7..0000000000
--- a/src/jetstream/plugins/monocular/git-push-upstream.sh
+++ /dev/null
@@ -1,82 +0,0 @@
-#!/bin/bash
-
-echo "== Push Monocular Changes Upstream ==" >&1
-
-usage() {
- echo "Usage: git-push-upstream.sh -f -t -s ]" >&1
- echo " options:" >&1
- echo " -f MONOCULAR_FORK The upstream fork/repo to push to" >&1
- echo " -t MONOCULAR_BRANCH The target upstream branch to push to" >&1
- echo " -s STRATOS_SOURCE_BRANCH The Stratos source branch containing the changes" >&1
- echo " -h usage"
-}
-
-while getopts f:t:s:h arg; do
- case ${arg} in
- f) MONOCULAR_FORK=${OPTARG};;
- t) MONOCULAR_BRANCH=${OPTARG};;
- s) STRATOS_BRANCH=${OPTARG};;
- h) usage;;
- \?) usage;;
- esac
-done
-
-
-if [[ -z "$MONOCULAR_FORK" || -z "$MONOCULAR_BRANCH" || -z "$STRATOS_BRANCH" ]]; then
- usage
- exit 1
-fi
-
-echo "Monocular fork: $MONOCULAR_FORK" >&1
-echo "Monocular branch: $MONOCULAR_BRANCH" >&1
-echo "Stratos source branch: $STRATOS_BRANCH" >&1
-
-DIRPATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
-echo "${DIRPATH}"
-
-pushd "${DIRPATH}" >& /dev/null || exit
-
-source ./git-merge-subpath.sh
-
-echo "Checking out monocular target branch: $MONOCULAR_BRANCH from $MONOCULAR_FORK into temporary merge branch: temp-merge-branch" >&1
-
-##Change to top level of repo
-repo_root=$(git rev-parse --show-toplevel)
-echo "Moving to repo top level: $repo_root" >&1
-pushd "$repo_root" >&/dev/null || exit
-
-##Checkout the monocular feature branch onto a temporary merge branch
-git fetch "$MONOCULAR_FORK"
-git checkout -b temp-merge-branch "$MONOCULAR_FORK"/"$MONOCULAR_BRANCH" || exit
-
-##Merge changes from our monocular subtree in Stratos v2-master to our temp-merge-branch
-
-echo "Merging Stratos branch $STRATOS_BRANCH at src/jetstream/plugins/monocular into temp-merge-branch" >&1
-
-git-merge-subpath --squash origin/"$STRATOS_BRANCH" src/jetstream/plugins/monocular cmd
-if [ $? -ne 0 ]; then
- echo "Unsuccessful." >&2
- #Move back to repo root - important in case of bail-out here.
- popd >& /dev/null || exit
- exit 1
-else
-
- echo "Pushing changes to target branch: $MONOCULAR_BRANCH on $MONOCULAR_FORK" >&1
-
- ##Push the temporary merge branch back to the monocular feature branch
- git push "$MONOCULAR_FORK" HEAD:"$MONOCULAR_BRANCH"
- if [ $? -ne 0 ]; then
- echo "Failed to push to branch: $MONOCULAR_BRANCH on $MONOCULAR_FORK." >&2
- else
- echo "Cleaning up temporary branches" >&1
- ##Cleanup: remove our temporary merge branch
- git checkout "$STRATOS_BRANCH" && git branch -d temp-merge-branch
- fi
-fi
-
-popd >& /dev/null || exit
-popd >& /dev/null || exit
-
-if [ $? -eq 0 ]; then
- echo "Success" >&1
-fi
diff --git a/src/jetstream/plugins/monocular/go.mod b/src/jetstream/plugins/monocular/go.mod
index d099ac958b..a1f6697738 100644
--- a/src/jetstream/plugins/monocular/go.mod
+++ b/src/jetstream/plugins/monocular/go.mod
@@ -4,22 +4,19 @@ go 1.12
require (
bitbucket.org/liamstask/goose v0.0.0-20150115234039-8488cc47d90c
+ github.com/Masterminds/semver/v3 v3.1.0 // indirect
github.com/go-sql-driver/mysql v1.4.1 // indirect
- github.com/helm/monocular v1.4.0
- github.com/helm/monocular/chartrepo v0.0.0-00010101000000-000000000000
- github.com/helm/monocular/chartsvc v0.0.0-00010101000000-000000000000
github.com/kubeapps/common v0.0.0-20190508164739-10b110436c1a
github.com/kylelemons/go-gypsy v0.0.0-20160905020020-08cad365cd28 // indirect
github.com/labstack/echo v3.3.10+incompatible
+ github.com/labstack/gommon v0.3.0 // indirect
github.com/lib/pq v1.0.0 // indirect
github.com/mattn/go-sqlite3 v1.10.0 // indirect
github.com/satori/go.uuid v1.2.0
github.com/sirupsen/logrus v1.4.2
github.com/ziutek/mymysql v1.5.4 // indirect
+ golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a // indirect
+ gopkg.in/yaml.v2 v2.3.0 // indirect
)
-replace (
- github.com/cloudfoundry-incubator/stratos/src/jetstream => ../..
- github.com/helm/monocular/chartrepo => ./chart-repo
- github.com/helm/monocular/chartsvc => ./chartsvc
-)
+replace github.com/cloudfoundry-incubator/stratos/src/jetstream => ../..
diff --git a/src/jetstream/plugins/monocular/go.sum b/src/jetstream/plugins/monocular/go.sum
index 1dc960fbfd..c4d22047ed 100644
--- a/src/jetstream/plugins/monocular/go.sum
+++ b/src/jetstream/plugins/monocular/go.sum
@@ -6,6 +6,8 @@ github.com/Masterminds/semver v1.4.2 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITg
github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
+github.com/Masterminds/semver/v3 v3.1.0 h1:Y2lUDsFKVRSYGojLJ1yLxSXdMmMYTYls0rCvoqmMUQk=
+github.com/Masterminds/semver/v3 v3.1.0/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
@@ -254,6 +256,8 @@ golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnf
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191205161847-0a08dada0ff9 h1:abxekknhS/Drh3uoQDk5Hc7BgeiyI39Crb7vhf/1j5s=
golang.org/x/crypto v0.0.0-20191205161847-0a08dada0ff9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM=
+golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81 h1:00VmoueYNlNz/aHIilyyQz/MHSqGoWJzpFv/HW8xpzI=
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U=
@@ -262,6 +266,7 @@ golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9 h1:rjwSpXsdiK0dV8/Naq3kAw9ymfAeJIyd0upUIElB+lI=
@@ -280,12 +285,15 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY=
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
@@ -303,6 +311,8 @@ gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
+gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
k8s.io/apimachinery v0.0.0-20190221093215-450d01ad5771 h1:XMECjVNpkFVT9uY40z07scw8Xtn2mUnkxx8BC3gjwoE=
k8s.io/apimachinery v0.0.0-20190221093215-450d01ad5771/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0=
k8s.io/apimachinery v0.0.0-20191203211716-adc6f4cd9e7d h1:q+OZmYewHJeMCzwpHkXlNTtk5bvaUMPCikKvf77RBlo=
diff --git a/src/jetstream/plugins/monocular/main.go b/src/jetstream/plugins/monocular/main.go
index 01f12dc905..b5c46f2e58 100644
--- a/src/jetstream/plugins/monocular/main.go
+++ b/src/jetstream/plugins/monocular/main.go
@@ -1,26 +1,18 @@
package monocular
import (
- "encoding/json"
"errors"
- "fmt"
"io/ioutil"
- "math/rand"
"net/http"
"os"
- "os/exec"
"path/filepath"
"strings"
"time"
+ "github.com/cloudfoundry-incubator/stratos/src/jetstream/plugins/monocular/store"
"github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces"
"github.com/labstack/echo"
log "github.com/sirupsen/logrus"
-
- "github.com/helm/monocular/chartsvc"
- "github.com/helm/monocular/chartsvc/foundationdb"
- "github.com/helm/monocular/chartsvc/models"
- "github.com/helm/monocular/chartsvc/utils"
)
const (
@@ -28,30 +20,25 @@ const (
helmHubEndpointType = "hub"
helmRepoEndpointType = "repo"
stratosPrefix = "/pp/v1/"
- prefix = "/pp/v1/chartsvc/"
kubeReleaseNameEnvVar = "STRATOS_HELM_RELEASE"
- foundationDBURLEnvVar = "FDB_URL"
- syncServerURLEnvVar = "SYNC_SERVER_URL"
- caCertEnvVar = "MONOCULAR_CA_CRT_PATH"
- tlsKeyEnvVar = "MONOCULAR_KEY_PATH"
- tLSCertEnvVar = "MONOCULAR_CRT_PATH"
- localDevEnvVar = "FDB_LOCAL_DEV"
- chartSyncBasePort = 45000
+ cacheFolderEnvVar = "HELM_CACHE_FOLDER"
+ defaultCacheFolder = "./.helm-cache"
)
// Monocular is a plugin for Monocular
type Monocular struct {
portalProxy interfaces.PortalProxy
chartSvcRoutes http.Handler
- RepoQueryStore *chartsvc.ChartSvcDatastore
+ ChartStore store.ChartStore
FoundationDBURL string
SyncServiceURL string
devSyncPID int
+ CacheFolder string
}
type HelmHubChart struct {
- utils.ApiResponse
- Attributes *models.ChartVersion `json:"attributes"`
+ APIResponse
+ Attributes *ChartVersion `json:"attributes"`
}
type HelmHubChartResponse struct {
@@ -60,96 +47,51 @@ type HelmHubChartResponse struct {
// Init creates a new Monocular
func Init(portalProxy interfaces.PortalProxy) (interfaces.StratosPlugin, error) {
+ store.InitRepositoryProvider(portalProxy.GetConfig().DatabaseProviderName)
return &Monocular{portalProxy: portalProxy}, nil
}
// Init performs plugin initialization
func (m *Monocular) Init() error {
log.Debug("Monocular init .... ")
- if err := m.configure(); err != nil {
+
+ m.CacheFolder = m.portalProxy.Env().String(cacheFolderEnvVar, defaultCacheFolder)
+ folder, err := filepath.Abs(m.CacheFolder)
+ if err != nil {
return err
}
-
- fdbURL := m.FoundationDBURL
- fDB := "monocular-plugin"
- debug := false
- caCertPath, _ := m.portalProxy.Env().Lookup(caCertEnvVar)
- TLSCertPath, _ := m.portalProxy.Env().Lookup(tLSCertEnvVar)
- tlsKeyPath, _ := m.portalProxy.Env().Lookup(tlsKeyEnvVar)
- m.ConfigureChartSVC(&fdbURL, &fDB, caCertPath, TLSCertPath, tlsKeyPath, &debug)
- m.chartSvcRoutes = chartsvc.SetupRoutes()
- m.InitSync()
- m.syncOnStartup()
- return nil
-}
-
-// Destroy does any cleanup for the plugin on exit
-func (m *Monocular) Destroy() {
- log.Debug("Monocular plugin .. destroy")
- if m.devSyncPID != 0 {
- log.Info("... Stopping chart sync tool")
- if p, err := os.FindProcess(m.devSyncPID); err == nil {
- p.Kill()
- } else {
- log.Error("Could not find process for the chart sync tool")
+ m.CacheFolder = folder
+ log.Infof("Using Cache folder: %s", m.CacheFolder)
+
+ // Check that the folder exists - try to make it, if not
+ if _, err := os.Stat(m.CacheFolder); os.IsNotExist(err) {
+ log.Info("Helm Cache folder does not exist - creating")
+ if err := os.MkdirAll(m.CacheFolder, os.ModePerm); err != nil {
+ log.Warn("Could not create folder for Helm Cache")
+ return err
}
}
-}
-
-func (m *Monocular) configure() error {
-
- // Env var lookup for Monocular services
- m.FoundationDBURL = m.portalProxy.Env().String(foundationDBURLEnvVar, "")
- m.SyncServiceURL = m.portalProxy.Env().String(syncServerURLEnvVar, "")
-
- if fdbPort, isLocal := m.portalProxy.Env().Lookup(localDevEnvVar); isLocal {
- // Create a random port to use for the chart sync service
- devSyncPort := chartSyncBasePort + rand.Intn(5000)
- m.FoundationDBURL = fmt.Sprintf("mongodb://127.0.0.1:%s", fdbPort)
- m.SyncServiceURL = fmt.Sprintf("http://127.0.0.1:%d", devSyncPort)
- // Run the chartrepo tool
- dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
- if err != nil {
- log.Error("Can not get folder of current process")
- }
- chartSyncTool := filepath.Join(dir, "plugins", "monocular", "chart-repo", "chartrepo")
- cmd := exec.Command(chartSyncTool, "serve", fmt.Sprintf("--doclayer-url=%s", m.FoundationDBURL))
- cmd.Env = make([]string, 1)
- cmd.Stdout = os.Stdout
- cmd.Stderr = os.Stderr
- cmd.Env[0] = fmt.Sprintf("PORT=%d", devSyncPort)
- if err = cmd.Start(); err != nil {
- log.Fatalf("Error starting chart sync tool: %+v", err)
- } else {
- m.devSyncPID = cmd.Process.Pid
- }
+ store, err := store.NewHelmChartDBStore(m.portalProxy.GetDatabaseConnection())
+ if err != nil {
+ log.Errorf("Can not get Helm Chart store: %s", err)
+ return err
}
- log.Debugf("Foundation DB : %s", m.FoundationDBURL)
- log.Debugf("Sync Server : %s", m.SyncServiceURL)
-
- if len(m.FoundationDBURL) == 0 || len(m.SyncServiceURL) == 0 {
- return errors.New("Helm Monocular DB and/or Sync server are not configured")
- }
+ m.ChartStore = store
+ m.InitSync()
+ m.syncOnStartup()
return nil
}
-func getReleaseNameEnvVarPrefix(name string) string {
- prefix := strings.ToUpper(name)
- prefix = strings.ReplaceAll(prefix, "-", "_")
- return prefix
+// Destroy does any cleanup for the plugin on exit
+func (m *Monocular) Destroy() {
+ log.Debug("Monocular plugin .. destroy")
}
func (m *Monocular) syncOnStartup() {
-
- // Get the repositories that we currently have
- repos, err := foundationdb.ListRepositories()
- if err != nil {
- log.Errorf("Chart Repository Startup: Unable to sync repositories: %v+", err)
- return
- }
+ // Always sync all repositories on startup
// Get all of the helm endpoints
endpoints, err := m.portalProxy.ListEndpoints()
@@ -158,17 +100,12 @@ func (m *Monocular) syncOnStartup() {
return
}
- helmRepos := make([]string, 0)
+ helmRepos := make(map[string]bool)
for _, ep := range endpoints {
if ep.CNSIType == helmEndpointType {
if ep.SubType == helmRepoEndpointType {
- helmRepos = append(helmRepos, ep.Name)
-
- // Is this an endpoint that we don't have charts for ?
- if !arrayContainsString(repos, ep.Name) {
- log.Infof("Syncing helm repository to chart store: %s", ep.Name)
- m.Sync(interfaces.EndpointRegisterAction, ep)
- }
+ helmRepos[ep.GUID] = true
+ m.Sync(interfaces.EndpointRegisterAction, ep)
} else {
metadata := "{}"
m.portalProxy.UpdateEndpointMetadata(ep.GUID, metadata)
@@ -176,16 +113,15 @@ func (m *Monocular) syncOnStartup() {
}
}
- // Now delete any repositories that are no longer registered as endpoints
- for _, repo := range repos {
- if !arrayContainsString(helmRepos, repo) {
- log.Infof("Removing helm repository from chart store: %s", repo)
- endpoint := &interfaces.CNSIRecord{
- GUID: repo,
- Name: repo,
- CNSIType: helmEndpointType,
+ // Delete any endpoints left in the chart store that are no longer registered
+ // Get all of the endpoints that we have in the Database Chart Store
+ existing, err := m.ChartStore.GetEndpointIDs()
+ if err == nil {
+ for _, id := range existing {
+ if _, ok := helmRepos[id]; !ok {
+ log.Warnf("Endpoint ID %s exists in the Chart Store but does not exist as an endpoint - deleting", id)
+ m.deleteChartStoreForEndpoint(id)
}
- m.Sync(interfaces.EndpointUnregisterAction, endpoint)
}
}
}
@@ -200,17 +136,7 @@ func arrayContainsString(a []string, x string) bool {
return false
}
-func (m *Monocular) ConfigureChartSVC(fdbURL *string, fDB *string, cACertFile string, certFile string, keyFile string, debug *bool) error {
- //TLS options must either be all set to enabled TLS, or none set to disable TLS
- var tlsEnabled = cACertFile != "" && keyFile != "" && certFile != ""
- if !(tlsEnabled || (cACertFile == "" && keyFile == "" && certFile == "")) {
- return errors.New("To enable TLS, all 3 TLS cert paths must be set.")
- }
- m.RepoQueryStore = chartsvc.InitFDBDocLayerConnection(fdbURL, fDB, &tlsEnabled, cACertFile, certFile, keyFile, debug)
-
- return nil
-}
-
+// OnEndpointNotification handles notification that endpoint has been remoevd
func (m *Monocular) OnEndpointNotification(action interfaces.EndpointAction, endpoint *interfaces.CNSIRecord) {
if endpoint.CNSIType == helmEndpointType && endpoint.SubType == helmRepoEndpointType {
m.Sync(action, endpoint)
@@ -239,16 +165,57 @@ func (m *Monocular) AddAdminGroupRoutes(echoGroup *echo.Group) {
// AddSessionGroupRoutes adds the session routes for this plugin to the Echo server
func (m *Monocular) AddSessionGroupRoutes(echoGroup *echo.Group) {
- // Requests to Monocular Instances
- echoGroup.Any("/chartsvc/*", m.handleAPI)
+
+ // API for Helm Chart Repositories - sync and sync status
// Reach out to a monocular instance other than Stratos (like helm hub). This is usually done via `x-cap-cnsi-list`
// however cannot be done for things like img src
echoGroup.Any("/monocular/:guid/chartsvc/*", m.handleMonocularInstance)
- // API for Helm Chart Repositories
- echoGroup.GET("/chartrepos", m.ListRepos)
- echoGroup.POST("/chartrepos/status", m.GetRepoStatuses)
- echoGroup.POST("/chartrepos/:guid", m.SyncRepo)
+ echoGroup.POST("/chartrepos/:guid", m.syncRepo)
+ echoGroup.POST("/chartrepos/status", m.getRepoStatuses)
+
+ // Routes for Chart Store
+ chartSvcGroup := echoGroup.Group("/chartsvc")
+
+ // Routes for the internal chart store
+
+ // Get specific chart version file (used for values.yaml)
+ chartSvcGroup.GET("/v1/assets/:repo/:name/versions/:version/:filename", m.getChartAndVersionFile)
+
+ // Get specific chart version file
+ chartSvcGroup.GET("/v1/charts/:repo/:name/versions/:version/files/:filename", m.getChartAndVersionFile)
+
+ // Get specific chart version of a chart
+ chartSvcGroup.GET("/v1/charts/:repo/:name/versions/:version", m.getChartVersion)
+
+ // Get chart versions
+ chartSvcGroup.GET("/v1/charts/:repo/:name/versions", m.getChartVersions)
+
+ // Get a chart
+ chartSvcGroup.GET("/v1/charts/:repo/:name", m.getChart)
+
+ // // Get list of charts
+ chartSvcGroup.GET("/v1/charts", m.listCharts)
+
+ // Get the chart icon for a specific version
+ chartSvcGroup.GET("/v1/assets/:repo/:chartName/:version/logo", m.getIcon)
+
+ // Get the chart icon
+ chartSvcGroup.GET("/v1/assets/:repo/:chartName/logo", m.getIcon)
+}
+
+// Check if the request if for an external Monocular instance and handle it if so
+func (m *Monocular) processMonocularRequest(c echo.Context) (bool, error) {
+ externalMonocularEndpoint, err := m.isExternalMonocularRequest(c)
+ if err != nil {
+ return true, echo.NewHTTPError(http.StatusBadRequest, err.Error())
+ }
+
+ // If this request is associated with an external monocular instance forward the request on to it
+ if externalMonocularEndpoint != nil {
+ return true, m.baseHandleMonocularInstance(c, externalMonocularEndpoint)
+ }
+ return false, nil
}
// isExternalMonocularRequest .. Should this request go out to an external monocular instance? IF so returns external monocular endpoint
@@ -278,30 +245,6 @@ func (m *Monocular) validateExternalMonocularEndpoint(cnsi string) (*interfaces.
return nil, nil
}
-// Forward requests to the Chart Service API
-func (m *Monocular) handleAPI(c echo.Context) error {
- externalMonocularEndpoint, err := m.isExternalMonocularRequest(c)
- if err != nil {
- return echo.NewHTTPError(http.StatusBadRequest, err.Error())
- }
-
- // If this request is associated with an external monocular instance forward the request on to it
- if externalMonocularEndpoint != nil {
- return m.baseHandleMonocularInstance(c, externalMonocularEndpoint)
- }
-
- // Modify the path to remove our prefix for the Chart Service API
- path := c.Request().URL.Path
- log.Debugf("URL to chartsvc requested: %v", path)
- if strings.Index(path, prefix) == 0 {
- path = path[len(prefix)-1:]
- c.Request().URL.Path = path
- }
- log.Debugf("URL to chartsvc requested after modification: %v", path)
- m.chartSvcRoutes.ServeHTTP(c.Response().Writer, c.Request())
- return nil
-}
-
func (m *Monocular) handleMonocularInstance(c echo.Context) error {
log.Debug("handleMonocularInstance")
guid := c.Param("guid")
@@ -392,59 +335,3 @@ func (m *Monocular) baseHandleMonocularInstance(c echo.Context, monocularEndpoin
return nil
}
-
-// GetChartDownloadUrl ... Get the download url for the bits required to install the given chart
-func (m *Monocular) GetChartDownloadUrl(monocularEndpoint, chartID, version string) (string, error) {
- if len(monocularEndpoint) > 0 {
- // Fetch the monocular endpoint for the url
- endpoint, err := m.validateExternalMonocularEndpoint(monocularEndpoint)
- if err != nil {
- return "", err
- }
- url := endpoint.APIEndpoint
-
- // Fetch the chart, this will give us the url to download the bits
- url.Path += "/chartsvc/v1/charts/" + chartID + "/versions/" + version
- req, err := http.NewRequest(http.MethodGet, url.String(), nil)
- req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
- client := &http.Client{Timeout: 30 * time.Second}
- res, err := client.Do(req)
- if err != nil {
- return "", err
- } else if res.StatusCode >= 400 {
- return "", fmt.Errorf("Couldn't download monocular chart (%+v) from '%+v'", res.StatusCode, req.URL)
- } else if res.Body != nil {
- body, _ := ioutil.ReadAll(res.Body)
- defer res.Body.Close()
-
- // Reach into the chart response for the download URL
- chartVersionResponse := &HelmHubChartResponse{}
- err := json.Unmarshal(body, chartVersionResponse)
- if err != nil {
- return "", err
- }
- if len(chartVersionResponse.Data.Attributes.URLs) < 1 {
- return "", errors.New("Response contained no chart package urls")
- }
- return chartVersionResponse.Data.Attributes.URLs[0], err
- } else {
- return "", errors.New("No body in response to chart request")
- }
- } else {
- store := m.RepoQueryStore
- chart, err := store.GetChart(chartID)
- if err != nil {
- return "", errors.New("Could not find Chart")
- }
-
- // Find the download URL for the version
- for _, chartVersion := range chart.ChartVersions {
- if chartVersion.Version == version {
- if len(chartVersion.URLs) == 1 {
- return chartVersion.URLs[0], nil
- }
- }
- }
- return "", errors.New("Could not find Chart Version")
- }
-}
diff --git a/src/jetstream/plugins/monocular/repository.go b/src/jetstream/plugins/monocular/repository.go
index 18b1f3e600..eeeffa298d 100644
--- a/src/jetstream/plugins/monocular/repository.go
+++ b/src/jetstream/plugins/monocular/repository.go
@@ -2,7 +2,6 @@ package monocular
import (
"encoding/json"
- "errors"
"io/ioutil"
"github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces"
@@ -10,45 +9,11 @@ import (
log "github.com/sirupsen/logrus"
)
-type HelmRepoInfo struct {
- ID string `json:"id"`
- Type string `json:"type"`
- Attributes struct {
- Name string `json:"name"`
- URL string `json:"url"`
- } `json:"attributes"`
-}
-
type helmStatusInfo map[string]bool
-func (m *Monocular) ListRepos(c echo.Context) error {
- log.Debug("ListRepos")
-
- endpoints, err := m.portalProxy.ListEndpoints()
- if err != nil {
- return errors.New("Could not get endpoints")
- }
-
- repos := make([]HelmRepoInfo, 0)
- for _, ep := range endpoints {
- if ep.CNSIType == helmEndpointType {
- // Helm endpoint
- repo := HelmRepoInfo{
- ID: ep.Name,
- Type: "repository",
- }
- repo.Attributes.Name = ep.Name
- repo.Attributes.URL = ep.APIEndpoint.String()
- repos = append(repos, repo)
- }
- }
-
- return c.JSON(200, repos)
-}
-
-// GetRepoStatuses will get the status of the Helm Endpoints requested
-func (m *Monocular) GetRepoStatuses(c echo.Context) error {
- log.Debug("GetRepoStatuses")
+// getRepoStatuses will get the status of the Helm Endpoints requested
+func (m *Monocular) getRepoStatuses(c echo.Context) error {
+ log.Debug("getRepoStatuses")
// Get the list of endpoints we are looking at
// Need to extract the parameters from the request body
diff --git a/src/jetstream/plugins/monocular/chartsvc/utils/responses.go b/src/jetstream/plugins/monocular/responses.go
similarity index 85%
rename from src/jetstream/plugins/monocular/chartsvc/utils/responses.go
rename to src/jetstream/plugins/monocular/responses.go
index 00d6c105f2..036c9aa658 100644
--- a/src/jetstream/plugins/monocular/chartsvc/utils/responses.go
+++ b/src/jetstream/plugins/monocular/responses.go
@@ -14,21 +14,21 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-package utils
+package monocular
//BodyAPIListResponse is an API body response in list format including the number of results pages
type BodyAPIListResponse struct {
- Data *ApiListResponse `json:"data"`
+ Data *APIListResponse `json:"data"`
Meta Meta `json:"meta,omitempty"`
}
//BodyAPIResponse is an API body response in non-list format
type BodyAPIResponse struct {
- Data ApiResponse `json:"data"`
+ Data APIResponse `json:"data"`
}
-//ApiResponse is an API response in non-list format
-type ApiResponse struct {
+//APIResponse is an API response in non-list format
+type APIResponse struct {
ID string `json:"id"`
Type string `json:"type"`
Attributes interface{} `json:"attributes"`
@@ -36,8 +36,8 @@ type ApiResponse struct {
Relationships RelMap `json:"relationships"`
}
-//ApiListResponse is an API response in list format
-type ApiListResponse []*ApiResponse
+//APIListResponse is an API response in list format
+type APIListResponse []*APIResponse
//SelfLink the self-referencing URL to a chart in a response
type SelfLink struct {
diff --git a/src/jetstream/plugins/monocular/store/chart_store_db.go b/src/jetstream/plugins/monocular/store/chart_store_db.go
new file mode 100644
index 0000000000..3ad7e0bd79
--- /dev/null
+++ b/src/jetstream/plugins/monocular/store/chart_store_db.go
@@ -0,0 +1,229 @@
+package store
+
+import (
+ "database/sql"
+ "errors"
+ "fmt"
+ "sort"
+
+ "github.com/cloudfoundry-incubator/stratos/src/jetstream/datastore"
+)
+
+var (
+ saveChartVersion = `INSERT INTO helm_charts (endpoint, name, repo_name, version, created, app_version, description, icon_url, chart_url, source_url, digest, is_latest, update_batch) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)`
+ updateChartVersion = `UPDATE helm_charts SET created=$1, app_version=$2, description=$3, icon_url=$4, chart_url=$5, source_url=$6, digest=$7, is_latest=$8, update_batch=$9 WHERE endpoint=$10 AND name=$11 AND repo_name=$12 AND version=$13`
+ deleteChartVersion = `DELETE FROM helm_charts WHERE endpoint = $1 AND name = $2 and version = $3`
+ deleteForEndpoint = `DELETE FROM helm_charts WHERE endpoint = $1`
+ deleteForBatch = `DELETE FROM helm_charts WHERE endpoint = $1 AND name = $2 and update_batch != $3`
+ renameEndpoint = `UPDATE helm_charts SET repo_name=$1 WHERE endpoint=$2`
+ getLatestCharts = `SELECT endpoint, name, repo_name, version, created, app_version, description, icon_url, chart_url, source_url, digest, is_latest FROM helm_charts WHERE is_latest = true`
+ getLatestChart = `SELECT endpoint, name, repo_name, version, created, app_version, description, icon_url, chart_url, source_url, digest, is_latest FROM helm_charts WHERE repo_name = $1 AND name = $2 AND is_latest = true`
+ getChartVersion = `SELECT endpoint, name, repo_name, version, created, app_version, description, icon_url, chart_url, source_url, digest, is_latest FROM helm_charts WHERE repo_name = $1 AND name = $2 AND version = $3`
+ getChartVersions = `SELECT endpoint, name, repo_name, version, created, app_version, description, icon_url, chart_url, source_url, digest, is_latest FROM helm_charts WHERE repo_name = $1 AND name = $2`
+ getEndpointIDs = `SELECT DISTINCT endpoint FROM helm_charts`
+ updateChartDigest = `UPDATE helm_charts SET is_latest=$1, update_batch=$2 WHERE endpoint=$3 AND name=$4 AND repo_name=$5 AND version=$6`
+)
+
+// InitRepositoryProvider - One time init for the given DB Provider
+func InitRepositoryProvider(databaseProvider string) {
+ saveChartVersion = datastore.ModifySQLStatement(saveChartVersion, databaseProvider)
+ updateChartVersion = datastore.ModifySQLStatement(updateChartVersion, databaseProvider)
+ updateChartVersion = datastore.ModifySQLStatement(updateChartVersion, databaseProvider)
+ updateChartDigest = datastore.ModifySQLStatement(updateChartDigest, databaseProvider)
+ deleteForEndpoint = datastore.ModifySQLStatement(deleteForEndpoint, databaseProvider)
+ deleteForBatch = datastore.ModifySQLStatement(deleteForBatch, databaseProvider)
+ renameEndpoint = datastore.ModifySQLStatement(renameEndpoint, databaseProvider)
+ getLatestCharts = datastore.ModifySQLStatement(getLatestCharts, databaseProvider)
+ getLatestChart = datastore.ModifySQLStatement(getLatestChart, databaseProvider)
+ getChartVersion = datastore.ModifySQLStatement(getChartVersion, databaseProvider)
+ getChartVersions = datastore.ModifySQLStatement(getChartVersions, databaseProvider)
+ getEndpointIDs = datastore.ModifySQLStatement(getEndpointIDs, databaseProvider)
+}
+
+// HelmChartDBStore is a DB-backed Helm Chart repository
+type HelmChartDBStore struct {
+ db *sql.DB
+}
+
+// NewHelmChartDBStore will create a new instance of the AnalysisDBStore
+func NewHelmChartDBStore(dcp *sql.DB) (ChartStore, error) {
+ return &HelmChartDBStore{db: dcp}, nil
+}
+
+func truncate(in string) string {
+ return fmt.Sprintf("%.255s", in)
+}
+
+// Save a Helm Chart to the database
+func (p *HelmChartDBStore) Save(chart ChartStoreRecord, batchID string) error {
+
+ sourceURL := ""
+ if len(chart.Sources) > 0 {
+ sourceURL = chart.Sources[0]
+ }
+
+ // Get the existing record - if it has the same digest, then no need to store it
+ record, err := p.GetChart(chart.Repository, chart.Name, chart.Version)
+ if err == nil && record.Digest == chart.Digest {
+ _, err := p.db.Exec(updateChartDigest, chart.IsLatest, batchID, chart.EndpointID, chart.Name, chart.Repository, chart.Version)
+ return err
+ }
+
+ if err == nil {
+ // The record already exists, so update it
+ _, err := p.db.Exec(updateChartVersion, chart.Created, chart.AppVersion, truncate(chart.Description), truncate(chart.IconURL), truncate(chart.ChartURL), truncate(sourceURL), chart.Digest, chart.IsLatest, batchID, chart.EndpointID, chart.Name, chart.Repository, chart.Version)
+ return err
+ }
+
+ if _, err := p.db.Exec(saveChartVersion, chart.EndpointID, chart.Name, chart.Repository, chart.Version, chart.Created, chart.AppVersion, truncate(chart.Description), truncate(chart.IconURL), truncate(chart.ChartURL), truncate(sourceURL), chart.Digest, chart.IsLatest, batchID); err != nil {
+ return fmt.Errorf("Unable to save Helm Chart Version: %v", err)
+ }
+ return nil
+}
+
+// DeleteBatch will remove all chart versions not with the given batch id
+func (p *HelmChartDBStore) DeleteBatch(endpointID, chart, batchID string) error {
+ if _, err := p.db.Exec(deleteForBatch, endpointID, chart, batchID); err != nil {
+ return fmt.Errorf("Unable to delete Helm Chart Versions for batch ID: %s %v", batchID, err)
+ }
+ return nil
+}
+
+// DeleteForEndpoint will remove all Helm Charts for a given endpoint guid
+func (p *HelmChartDBStore) DeleteForEndpoint(endpointID string) error {
+ if _, err := p.db.Exec(deleteForEndpoint, endpointID); err != nil {
+ return fmt.Errorf("Unable to delete Helm Charts for endpoint: %s %v", endpointID, err)
+ }
+ return nil
+}
+
+// RenameEndpoint will update all charts for a given endpoint to have the new repository name
+func (p *HelmChartDBStore) RenameEndpoint(endpointID, name string) error {
+ if _, err := p.db.Exec(renameEndpoint, name, endpointID); err != nil {
+ return fmt.Errorf("Unable to rename Helm Chart repository for endpoint: %s %v", endpointID, err)
+ }
+ return nil
+}
+
+// GetLatestCharts will get only the info for the latest version of each chart
+func (p *HelmChartDBStore) GetLatestCharts() ([]*ChartStoreRecord, error) {
+
+ rows, err := p.db.Query(getLatestCharts)
+ if err != nil {
+ return nil, fmt.Errorf("Unable to retrieve Helm Charts: %v", err)
+ }
+ defer rows.Close()
+
+ var chartList []*ChartStoreRecord
+
+ for rows.Next() {
+ chart := new(ChartStoreRecord)
+ sourceURL := ""
+ err := rows.Scan(&chart.EndpointID, &chart.Name, &chart.Repository, &chart.Version, &chart.Created, &chart.AppVersion, &chart.Description, &chart.IconURL, &chart.ChartURL, &sourceURL, &chart.Digest, &chart.IsLatest)
+ if err != nil {
+ return nil, fmt.Errorf("Unable to scan Helm Chart records: %v", err)
+ }
+ chart.SemVer = NewSemanticVersion(chart.Version)
+ addSources(chart, sourceURL)
+ chartList = append(chartList, chart)
+ }
+
+ if err = rows.Err(); err != nil {
+ return nil, fmt.Errorf("Unable to list Helm Chart records: %v", err)
+ }
+
+ return chartList, nil
+}
+
+// GetChart gets a single helm chart
+func (p *HelmChartDBStore) GetChart(repo, name, version string) (*ChartStoreRecord, error) {
+
+ var row *sql.Row
+ chart := new(ChartStoreRecord)
+
+ if len(version) == 0 {
+ row = p.db.QueryRow(getLatestChart, repo, name)
+ } else {
+ row = p.db.QueryRow(getChartVersion, repo, name, version)
+ }
+
+ sourceURL := ""
+ err := row.Scan(&chart.EndpointID, &chart.Name, &chart.Repository, &chart.Version, &chart.Created, &chart.AppVersion, &chart.Description, &chart.IconURL, &chart.ChartURL, &sourceURL, &chart.Digest, &chart.IsLatest)
+ switch {
+ case err == sql.ErrNoRows:
+ return chart, errors.New("No match for that chart")
+ case err != nil:
+ return chart, fmt.Errorf("Error trying to find chart record: %v", err)
+ default:
+ // do nothing
+ }
+
+ chart.SemVer = NewSemanticVersion(chart.Version)
+ addSources(chart, sourceURL)
+
+ return chart, nil
+}
+
+// GetChartVersions will get all of the versions for a given chart
+func (p *HelmChartDBStore) GetChartVersions(repo, name string) ([]*ChartStoreRecord, error) {
+ rows, err := p.db.Query(getChartVersions, repo, name)
+ if err != nil {
+ return nil, fmt.Errorf("Unable to retrieve Helm Charts: %v", err)
+ }
+ defer rows.Close()
+
+ var chartList ChartStoreRecordList
+
+ for rows.Next() {
+ chart := new(ChartStoreRecord)
+ sourceURL := ""
+ err := rows.Scan(&chart.EndpointID, &chart.Name, &chart.Repository, &chart.Version, &chart.Created, &chart.AppVersion, &chart.Description, &chart.IconURL, &chart.ChartURL, &sourceURL, &chart.Digest, &chart.IsLatest)
+ if err != nil {
+ return nil, fmt.Errorf("Unable to scan Helm Chart records: %v", err)
+ }
+ chart.SemVer = NewSemanticVersion(chart.Version)
+ addSources(chart, sourceURL)
+ chartList = append(chartList, chart)
+ }
+
+ if err = rows.Err(); err != nil {
+ return nil, fmt.Errorf("Unable to list Helm Chart records: %v", err)
+ }
+
+ // Sort list by version
+ sort.Sort(chartList)
+ return chartList, nil
+}
+
+// GetEndpointIDs will get all unique endpoint IDs from the database
+func (p *HelmChartDBStore) GetEndpointIDs() ([]string, error) {
+ rows, err := p.db.Query(getEndpointIDs)
+ if err != nil {
+ return nil, fmt.Errorf("Unable to retrieve Endpoint IDs: %v", err)
+ }
+ defer rows.Close()
+
+ list := make([]string, 0)
+
+ for rows.Next() {
+ var endpoint string
+ err := rows.Scan(&endpoint)
+ if err != nil {
+ return nil, fmt.Errorf("Unable to scan Helm Chart records for endpoints: %v", err)
+ }
+ list = append(list, endpoint)
+ }
+
+ if err = rows.Err(); err != nil {
+ return nil, fmt.Errorf("Unable to list Helm Chart endpoints: %v", err)
+ }
+
+ return list, nil
+}
+
+func addSources(record *ChartStoreRecord, sourceURL string) {
+ record.Sources = make([]string, 0)
+ if len(sourceURL) > 0 {
+ record.Sources = append(record.Sources, sourceURL)
+ }
+}
diff --git a/src/jetstream/plugins/monocular/store/main.go b/src/jetstream/plugins/monocular/store/main.go
new file mode 100644
index 0000000000..68501e5f0f
--- /dev/null
+++ b/src/jetstream/plugins/monocular/store/main.go
@@ -0,0 +1,28 @@
+package store
+
+// ChartStore is the Helm Chart Store repository
+type ChartStore interface {
+ // This will add or update the given chart
+ Save(chart ChartStoreRecord, batchID string) error
+
+ // Delete chart versions for a given batch
+ DeleteBatch(endpoint, chart, batchID string) error
+
+ // Delete all charts for the given endpoint
+ DeleteForEndpoint(endpoint string) error
+
+ // RenameEndpoint renames an endpoint (==renames helm repository)
+ RenameEndpoint(endpointID, name string) error
+
+ // GetLatestCharts gets all of the latest charts
+ GetLatestCharts() ([]*ChartStoreRecord, error)
+
+ // Version is optional - empty means get latest
+ GetChart(repo, name, version string) (*ChartStoreRecord, error)
+
+ // Get Chart Versions
+ GetChartVersions(repo, name string) ([]*ChartStoreRecord, error)
+
+ // Get Endopoint IDs stored in the chart store
+ GetEndpointIDs() ([]string, error)
+}
diff --git a/src/jetstream/plugins/monocular/store/types.go b/src/jetstream/plugins/monocular/store/types.go
new file mode 100644
index 0000000000..65e5881d50
--- /dev/null
+++ b/src/jetstream/plugins/monocular/store/types.go
@@ -0,0 +1,38 @@
+package store
+
+import (
+ "time"
+)
+
+// ChartStoreRecord represents a Helm Chart Version record
+type ChartStoreRecord struct {
+ EndpointID string `json:"endpoint"`
+ Name string `json:"name"`
+ Repository string `json:"repo_name"`
+ Version string `json:"version"`
+ AppVersion string `json:"app_version"`
+ Description string `json:"description"`
+ IconURL string `json:"icon_url"`
+ ChartURL string `json:"chart_url"`
+ Sources []string `json:"sources"`
+ Created time.Time `json:"created"`
+ Digest string `json:"digest"`
+ IsLatest bool `json:"is_latest"`
+ SemVer SemanticVersion `json:"-"`
+}
+
+type ChartStoreRecordList []*ChartStoreRecord
+
+func (r ChartStoreRecordList) Len() int {
+ return len(r)
+}
+
+func (r ChartStoreRecordList) Swap(i, j int) {
+ r[i], r[j] = r[j], r[i]
+}
+func (r ChartStoreRecordList) Less(i, j int) bool {
+ ci := r[i].SemVer
+ cj := r[j].SemVer
+
+ return ci.LessThan(&cj)
+}
diff --git a/src/jetstream/plugins/monocular/store/version.go b/src/jetstream/plugins/monocular/store/version.go
new file mode 100644
index 0000000000..76a08f5f19
--- /dev/null
+++ b/src/jetstream/plugins/monocular/store/version.go
@@ -0,0 +1,60 @@
+package store
+
+import (
+ semver "github.com/Masterminds/semver/v3"
+)
+
+// SemanticVersion is a semver with support for a plain text version
+// Uses the semver library - which errors if the version can not be parsed
+// This wrapper ensures that if a version can not be parsed as a semver
+// it is treated as a string
+
+type SemanticVersion struct {
+ Version *semver.Version
+ Text string
+ Valid bool
+}
+
+// NewSemanticVersion parses and returns a Semantic Version
+func NewSemanticVersion(version string) SemanticVersion {
+
+ v := SemanticVersion{
+ Text: version,
+ }
+
+ sv, err := semver.NewVersion(version)
+ v.Version = sv
+ v.Valid = err == nil
+
+ return v
+}
+
+func (s *SemanticVersion) LessThan(d *SemanticVersion) bool {
+ if d == nil {
+ return true
+ }
+ if s.Valid && d.Valid {
+ return !s.Version.LessThan(d.Version)
+ } else if s.Valid && !d.Valid {
+ return true
+ } else if !s.Valid && d.Valid {
+ return false
+ }
+
+ return s.Text < d.Text
+}
+
+func (s *SemanticVersion) LessThanReleaseVersions(d *SemanticVersion) bool {
+ if d == nil {
+ return true
+ }
+ if s.Valid && d.Valid {
+ // Check release versions
+ if len(d.Version.Prerelease()) > 0 {
+ return true
+ }
+ return !s.Version.LessThan(d.Version)
+ }
+
+ return s.LessThan(d)
+}
diff --git a/src/jetstream/plugins/monocular/sync-source.sh b/src/jetstream/plugins/monocular/sync-source.sh
deleted file mode 100755
index c9f34721f6..0000000000
--- a/src/jetstream/plugins/monocular/sync-source.sh
+++ /dev/null
@@ -1,38 +0,0 @@
-#!/bin/bash
-
-echo "== Sync Monocular backend code =="
-
-DIRPATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
-echo ${DIRPATH}
-
-pushd ${DIRPATH} >& /dev/null
-rm -rf tmp
-mkdir tmp
-git clone https://github.com/helm/monocular.git ./tmp/monocular
-
-# Copy the golang backend code
-cp -R ./tmp/monocular/cmd/* .
-
-# Add to the chartsvc API
-cat ./chartsvc_main.txt >> ./chartsvc/main.go
-
-# Update packages
-sed -i.bak -e 's/package main/package chartsvc/g' ./chartsvc/*.go
-sed -i.bak -e 's/package main/package chartrepo/g' ./chart-repo/*.go
-
-# Remove .bak files
-find . -name "*.bak" -type f -delete
-
-# Remove tmp folder
-rm -rf ./tmp
-
-# Initialize go modules
-cd ./chartsvc
-go mod init github.com/helm/monocular/chartsvc
-
-cd ../chart-repo
-go mod init github.com/helm/monocular/chartrepo
-
-popd >& /dev/null
-
-echo "All done"
diff --git a/src/jetstream/plugins/monocular/sync.go b/src/jetstream/plugins/monocular/sync.go
index ab1e6c3460..ff1795fa9a 100644
--- a/src/jetstream/plugins/monocular/sync.go
+++ b/src/jetstream/plugins/monocular/sync.go
@@ -2,17 +2,10 @@ package monocular
import (
"encoding/json"
- "fmt"
- "io"
- "net/http"
- "strings"
- "time"
"github.com/cloudfoundry-incubator/stratos/src/jetstream/repository/interfaces"
- "github.com/helm/monocular/chartrepo/common"
"github.com/labstack/echo"
log "github.com/sirupsen/logrus"
- "k8s.io/apimachinery/pkg/util/wait"
)
type SyncJob struct {
@@ -25,14 +18,6 @@ type SyncMetadata struct {
Busy bool `json:"busy"`
}
-const (
- chartRepoPathPrefix = "/v1"
- statusPollInterval = 30
- statusPollTimeout = 320
- syncServiceTimeoutBoundary = 10
- syncServiceReadyPollInterval = 5
-)
-
// Sync Channel
var syncChan = make(chan SyncJob, 100)
@@ -41,9 +26,9 @@ func (m *Monocular) InitSync() {
go m.processSyncRequests()
}
-// SyncRepos is endpoint to force a re-sync of a given Helm Repository
-func (m *Monocular) SyncRepo(c echo.Context) error {
- log.Debug("SyncRepos")
+// syncRepo is endpoint to force a re-sync of a given Helm Repository
+func (m *Monocular) syncRepo(c echo.Context) error {
+ log.Debug("syncRepo")
// Lookup repository by GUID
var p = m.portalProxy
@@ -61,170 +46,71 @@ func (m *Monocular) SyncRepo(c echo.Context) error {
// Sync schedules a sync action for the given endpoint
func (m *Monocular) Sync(action interfaces.EndpointAction, endpoint *interfaces.CNSIRecord) {
+ // Delete and Update are Synchronously handled
+ // Add (Sync) is handled Asynchronously via a SyncJob
+ if action == 0 {
+ // If the sync job is busy, it won't update the status of this new job until it completes the previous one
+ // Set the status to indicate it is pending
+ metadata := SyncMetadata{
+ Status: "Pending",
+ Busy: true,
+ }
+ m.portalProxy.UpdateEndpointMetadata(endpoint.GUID, marshalSyncMetadata(metadata))
- // If the sync job is busy, it won't update the status of this new job until it completes the previou sone
- // Set the status to indicate it is pending
- metadata := SyncMetadata{
- Status: "Pending",
- Busy: true,
- }
- m.portalProxy.UpdateEndpointMetadata(endpoint.GUID, marshalSyncMetadata(metadata))
+ // Add the job to the queue to be processed
+ job := SyncJob{
+ Action: action,
+ Endpoint: endpoint,
+ }
- // Add the job to the queue to be processed
- job := SyncJob{
- Action: action,
- Endpoint: endpoint,
+ // Schedula a sync job
+ syncChan <- job
+ } else if action == 1 {
+ log.Debugf("Deleting Helm Repository: %s", endpoint.Name)
+ m.deleteChartStoreForEndpoint(endpoint.GUID)
+ } else if action == 2 {
+ log.Debugf("Helm Repository has been updated - renaming the Helm repository field in the associated charts")
+ if err := m.ChartStore.RenameEndpoint(endpoint.GUID, endpoint.Name); err != nil {
+ log.Errorf("An error occurred renameing the Helm Repository for endpoint %s to %s - %+v", endpoint.GUID, endpoint.Name, err)
+ }
}
-
- syncChan <- job
}
-func waitForSyncService(syncServiceURL string) error {
- // Ensure that the chart repo sync service is responsive
- for {
- // establish an outer timeout boundary
- timeout := time.Now().Add(time.Minute * syncServiceTimeoutBoundary)
-
- // Make a dummy status request to the chart repo - if it is up we should get a 404
- statusURL := fmt.Sprintf("%s%s/status/%s", syncServiceURL, chartRepoPathPrefix, "none")
- resp, err := http.Get(statusURL)
- if resp != nil {
- defer resp.Body.Close()
- }
- if err == nil {
- log.Info("Sync service is reachable and ready.")
- break
- } else {
- log.Debugf("Result of chart repo request: %v", err)
- log.Info("Sync service not yet ready. Waiting for sync service to be available...")
- }
-
- // If our timeout boundary has been exceeded, bail out
- if timeout.Sub(time.Now()) < 0 {
- return fmt.Errorf("timeout boundary of %d minutes has been exceeded", syncServiceTimeoutBoundary)
- }
+func (m *Monocular) deleteChartStoreForEndpoint(id string) {
+ // Delete the records from the database
+ if err := m.ChartStore.DeleteForEndpoint(id); err != nil {
+ log.Warnf("Unable to delete Helm Charts for endpoint %s - %+v", id, err)
+ }
- // Circle back and try again
- time.Sleep(time.Second * syncServiceReadyPollInterval)
+ // Delete files from the cache
+ if err := m.deleteCacheForEndpoint(id); err != nil {
+ log.Warnf("Unable to delete Helm Chart Cache for endpoint %s - %+v", err)
}
- return nil
}
func (m *Monocular) processSyncRequests() {
log.Info("Helm Repository Sync init")
for job := range syncChan {
- err := waitForSyncService(m.SyncServiceURL)
- if err != nil {
- log.Errorf("Unable to process sync request for %v. Chart Repo not available after %v minutes. %v", job.Endpoint.Name, syncServiceTimeoutBoundary, err)
- continue
- }
log.Debugf("Processing Helm Repository Sync Job: %s", job.Endpoint.Name)
- var repoSyncRequestParams string = fmt.Sprintf("{\"repoURL\":%q}", job.Endpoint.APIEndpoint.String())
- // Could be delete or sync
- if job.Action == 0 {
- log.Debug("Syncing new repository")
- metadata := SyncMetadata{
- Status: "Synchronizing",
- Busy: true,
- }
- m.portalProxy.UpdateEndpointMetadata(job.Endpoint.GUID, marshalSyncMetadata(metadata))
- syncURL := fmt.Sprintf("%s%s/sync/%s", m.SyncServiceURL, chartRepoPathPrefix, job.Endpoint.Name)
-
- //Hit the sync server container endpoint to trigger a sync for given repo
- response, err := putRequest(syncURL, strings.NewReader(repoSyncRequestParams))
- metadata.Busy = false
- if err != nil {
- log.Warn("Request to sync repository failed: %v", err)
- metadata.Status = "Sync Failed"
- } else {
- statusResponse := common.SyncJobStatusResponse{}
- defer response.Body.Close()
- err := json.NewDecoder(response.Body).Decode(&statusResponse)
- if err != nil {
- log.Errorf("Unable to parse response from chart-repo server, sync request may not be processed: %v", err)
- metadata.Status = "Sync Failed"
- } else if statusResponse.Status != common.SyncStatusInProgress {
- log.Errorf("Failed to synchronize repo: %v, response: %v, statusResponse", job.Endpoint.Name, err)
- metadata.Status = "Sync Failed"
- } else {
- metadata.Status = "Synchronizing"
- metadata.Busy = true
- m.updateMetadata(job.Endpoint.GUID, metadata)
- log.Infof("Sync in progress for repository: %s", job.Endpoint.APIEndpoint.String())
- //Now wait for success
- statusURL := fmt.Sprintf("%s%s/status/%s", m.SyncServiceURL, chartRepoPathPrefix, job.Endpoint.Name)
- err := waitForSyncComplete(statusURL)
- metadata.Busy = false
- if err == nil {
- // Need to get the actual status
- status, err := getSyncStatus(statusURL)
- if err == nil {
- metadata.Status = status.Status
- } else {
- metadata.Status = "Sync Failed"
- }
- } else {
- metadata.Status = "Sync Failed"
- log.Errorf("Failed to fetch sync status for repo: %v, %v", job.Endpoint.Name, err)
- }
- }
- }
- m.updateMetadata(job.Endpoint.GUID, metadata)
- } else if job.Action == 1 {
- log.Infof("Deleting Helm Repository: %s", job.Endpoint.Name)
- //Hit the sync server container endpoint to trigger a delete for given repo
- deleteURL := fmt.Sprintf("%s%s/delete/%s", m.SyncServiceURL, chartRepoPathPrefix, job.Endpoint.Name)
- response, err := putRequest(deleteURL, strings.NewReader(repoSyncRequestParams))
- //Extract status from response
- if err != nil {
- log.Warn("Request to delete repository failed: %v+", err)
- } else {
- statusResponse := common.SyncJobStatusResponse{}
- defer response.Body.Close()
- err := json.NewDecoder(response.Body).Decode(&statusResponse)
- if err != nil {
- log.Errorf("Unable to parse response from chart-repo server, delete request may not be processed: %v", err)
- } else if statusResponse.Status != common.DeleteStatusInProgress {
- log.Errorf("Failed to delete repo: %v, response: %v, statusResponse", job.Endpoint.Name, err)
- }
- }
- }
- }
-
- log.Debug("processSyncRequests finished")
-}
-
-func waitForSyncComplete(url string) error {
- return wait.Poll(statusPollInterval*time.Second, time.Duration(statusPollTimeout)*time.Second, func() (bool, error) {
- var complete = false
- statusResponse, err := getSyncStatus(url)
- if err == nil && statusResponse.Status == common.SyncStatusSynced || statusResponse.Status == common.SyncStatusFailed {
- // Note: complete can mean synced okay or sync failed
- complete = true
+ metadata := SyncMetadata{
+ Status: "Synchronizing",
+ Busy: true,
}
- return complete, err
- })
-}
+ m.portalProxy.UpdateEndpointMetadata(job.Endpoint.GUID, marshalSyncMetadata(metadata))
-func getSyncStatus(url string) (*common.SyncJobStatusResponse, error) {
- resp, err := http.Get(url)
- if err == nil {
- defer resp.Body.Close()
- statusResponse := common.SyncJobStatusResponse{}
- err = json.NewDecoder(resp.Body).Decode(&statusResponse)
- if err == nil {
- return &statusResponse, nil
+ chartIndexURL := job.Endpoint.APIEndpoint.String()
+ metadata.Status = "Synchronized"
+ metadata.Busy = false
+ err := m.syncHelmRepository(job.Endpoint.GUID, job.Endpoint.Name, chartIndexURL)
+ if err != nil {
+ log.Warn("Helm Repository sync repository failed for repository %s - %v", job.Endpoint.GUID, err)
+ metadata.Status = "Sync Failed"
}
- }
- return nil, err
-}
-func marshalSyncMetadata(metadata SyncMetadata) string {
- jsonString, err := json.Marshal(metadata)
- if err != nil {
- return ""
+ // Update the job status
+ m.updateMetadata(job.Endpoint.GUID, metadata)
}
- return string(jsonString)
+ log.Debug("processSyncRequests finished")
}
func (m *Monocular) updateMetadata(endpoint string, metadata SyncMetadata) {
@@ -234,24 +120,10 @@ func (m *Monocular) updateMetadata(endpoint string, metadata SyncMetadata) {
}
}
-//https://gist.github.com/maniankara/a10d19960293b34b608ac7ef068a3d63
-func putRequest(url string, data io.Reader) (*http.Response, error) {
- client := &http.Client{}
- req, err := http.NewRequest(http.MethodPut, url, data)
- var resp *http.Response
- if err == nil {
- resp, err = client.Do(req)
- }
- return resp, err
-}
-
-//https://gist.github.com/maniankara/a10d19960293b34b608ac7ef068a3d63
-func getRequest(url string, data io.Reader) (*http.Response, error) {
- client := &http.Client{}
- req, err := http.NewRequest(http.MethodPut, url, data)
- var resp *http.Response
- if err == nil {
- resp, err = client.Do(req)
+func marshalSyncMetadata(metadata SyncMetadata) string {
+ jsonString, err := json.Marshal(metadata)
+ if err != nil {
+ return ""
}
- return resp, err
+ return string(jsonString)
}
diff --git a/src/jetstream/plugins/monocular/sync_worker.go b/src/jetstream/plugins/monocular/sync_worker.go
new file mode 100644
index 0000000000..962c899669
--- /dev/null
+++ b/src/jetstream/plugins/monocular/sync_worker.go
@@ -0,0 +1,147 @@
+package monocular
+
+import (
+ "fmt"
+ "strings"
+ "time"
+
+ yaml "gopkg.in/yaml.v2"
+
+ "github.com/cloudfoundry-incubator/stratos/src/jetstream/plugins/monocular/store"
+ uuid "github.com/satori/go.uuid"
+ log "github.com/sirupsen/logrus"
+)
+
+type syncResult struct {
+ Charts []store.ChartStoreRecord
+ Latest store.ChartStoreRecord
+}
+
+func (m *Monocular) syncHelmRepository(endpointID, repoName, url string) error {
+
+ // Add index.yaml to the URL
+ var downloadURL string
+
+ // Append "index.yaml" to the Chart Repository URL
+ if strings.HasSuffix(url, "/") {
+ downloadURL = fmt.Sprintf("%sindex.yaml", url)
+ } else {
+ downloadURL = fmt.Sprintf("%s/index.yaml", url)
+ }
+
+ // Read the index.html file from the repository
+ httpClient := m.portalProxy.GetHttpClient(false)
+ resp, err := httpClient.Get(downloadURL)
+ if err != nil {
+ return fmt.Errorf("Could not download Helm Repository Index: %s", err)
+ }
+ if resp.StatusCode != 200 {
+ return fmt.Errorf("Could not download Helm Repository Index: %s", resp.Status)
+ }
+
+ defer resp.Body.Close()
+
+ // Marshal to the index structure
+ var index IndexFile
+
+ decoder := yaml.NewDecoder(resp.Body)
+ err = decoder.Decode(&index)
+ if err != nil {
+ return fmt.Errorf("Error marshalling Helm Repository Index: %+v", err)
+ }
+
+ var latestCharts []store.ChartStoreRecord
+ var allCharts []store.ChartStoreRecord
+
+ log.Infof("Helm Repository sync started for %s", repoName)
+ start := time.Now()
+
+ // Iterate over each chart in the index
+ for name, chartVersions := range index.Entries {
+ log.Debugf("Helm Repository Sync: Processing chart: %s", name)
+ syncRsult := m.procesChartVersions(endpointID, repoName, name, chartVersions)
+ latestCharts = append(latestCharts, syncRsult.Latest)
+ allCharts = append(allCharts, syncRsult.Charts...)
+ }
+
+ // Cache latest charts
+ if err = m.cacheCharts(latestCharts); err != nil {
+ log.Warnf("Error caching helm charts: %+v", err)
+ }
+
+ // Finally, delete all files that are no longer referenced in the database
+ if err = m.cleanCacheFiles(endpointID, allCharts); err != nil {
+ log.Errorf("%s", err)
+ }
+
+ elapsed := time.Since(start).Round(time.Second)
+ log.Infof("Helm Repository sync completed for %s (%s)", repoName, elapsed)
+
+ return nil
+}
+
+func (m *Monocular) procesChartVersions(endpoint, repoName, name string, chartVersions []IndexFileMetadata) syncResult {
+
+ result := syncResult{}
+
+ // Find the newest version
+ var latestSemVer *store.SemanticVersion
+ for _, chartVersion := range chartVersions {
+ sv := store.NewSemanticVersion(chartVersion.Version)
+ if sv.LessThanReleaseVersions(latestSemVer) {
+ latestSemVer = &sv
+ }
+ }
+
+ latestVersion := latestSemVer.Text
+
+ // Generate a new batch update id - we use this to remove any charts that we not updated in this sync - these
+ // will have an old batch update id afetr processing
+ batchID := uuid.NewV4().String()
+
+ // Write all versions database
+ for _, chartVersion := range chartVersions {
+ if len(chartVersion.URLs) == 0 {
+ log.Warnf("Can not index Chart %s, Version %s - Chart does not have any Chart URLs", chartVersion.Name, chartVersion.Version)
+ } else {
+ if len(chartVersion.URLs) > 1 {
+ log.Warnf("Chart %s, Version %s - Chart has more than 1 Chart URL - only using the first URL", chartVersion.Name, chartVersion.Version)
+ }
+
+ // Create a record for the Chart Version that we will store in the database
+ record := store.ChartStoreRecord{
+ EndpointID: endpoint,
+ Name: chartVersion.Name,
+ Repository: repoName,
+ Version: chartVersion.Version,
+ AppVersion: chartVersion.AppVersion,
+ Description: chartVersion.Description,
+ IconURL: chartVersion.Icon,
+ ChartURL: chartVersion.URLs[0],
+ Sources: chartVersion.Sources,
+ Created: chartVersion.Created,
+ Digest: chartVersion.Digest,
+ IsLatest: chartVersion.Version == latestVersion,
+ }
+
+ result.Charts = append(result.Charts, record)
+ if record.IsLatest {
+ result.Latest = record
+ }
+
+ if err := m.ChartStore.Save(record, batchID); err != nil {
+ log.Warnf("Error saving Chart %s, Version %s to the database: %+v", record.Name, record.Version, err)
+ }
+
+ // Small delay mainly for SQLite so we don't hog the database connection
+ time.Sleep(2 * time.Millisecond)
+ }
+ }
+
+ // Delete versions not updated in this batch
+ if err := m.ChartStore.DeleteBatch(endpoint, name, batchID); err != nil {
+ log.Warnf("Error deleting old Chart batches: Name %s, Batch ID %s, error: %+v", name, batchID, err)
+ }
+
+ return result
+}
diff --git a/src/jetstream/plugins/monocular/types.go b/src/jetstream/plugins/monocular/types.go
new file mode 100644
index 0000000000..0c828daa13
--- /dev/null
+++ b/src/jetstream/plugins/monocular/types.go
@@ -0,0 +1,70 @@
+package monocular
+
+import "time"
+
+// IndexFile represents the index.yaml structure for a Helm Repository
+type IndexFile struct {
+ APIVersion string `json:"apiVersion,omitempty"`
+ Entries map[string][]IndexFileMetadata `json:"entries,omitempty"`
+}
+
+// IndexFileMetadata represents the metadata for a single chart version
+type IndexFileMetadata struct {
+ Name string `json:"name,omitempty"`
+ AppVersion string `json:"appVersion" yaml:"appVersion"`
+ Description string `json:"description,omitempty"`
+ Digest string `json:"digest,omitempty"`
+ Version string `json:"version,omitempty"`
+ Created time.Time `json:"created"`
+ Icon string `json:"icon,omitempty"`
+ URLs []string `json:"-" yaml:"urls"`
+ Sources []string `json:"-" yaml:"sources"`
+ APIVersion string `json:"-" yaml:"apiVersion"`
+}
+
+// ChartMaintainer describes a Chart maintainer.
+type ChartMaintainer struct {
+ // Name is a user name or organization name
+ Name string `json:"name,omitempty"`
+ // Email is an optional email address to contact the named maintainer
+ Email string `json:"email,omitempty"`
+ // URL is an optional URL to an address for the named maintainer
+ URL string `json:"url,omitempty"`
+}
+
+// ChartMetadata for a Chart file. This models the structure of a Chart.yaml file.
+type ChartMetadata struct {
+ // The name of the chart
+ Name string `json:"name,omitempty"`
+ // The URL to a relevant project page, git repo, or contact person
+ Home string `json:"home,omitempty"`
+ // Source is the URL to the source code of this chart
+ Sources []string `json:"sources,omitempty"`
+ // A SemVer 2 conformant version string of the chart
+ Version string `json:"version,omitempty"`
+ // A one-sentence description of the chart
+ Description string `json:"description,omitempty"`
+ // A list of string keywords
+ Keywords []string `json:"keywords,omitempty"`
+ // A list of name and URL/email address combinations for the maintainer(s)
+ Maintainers []*ChartMaintainer `json:"maintainers,omitempty"`
+ // The URL to an icon file.
+ Icon string `json:"icon,omitempty"`
+ // The API Version of this chart.
+ APIVersion string `json:"apiVersion,omitempty"`
+ // The condition to check to enable chart
+ Condition string `json:"condition,omitempty"`
+ // The tags to check to enable chart
+ Tags string `json:"tags,omitempty"`
+ // The version of the application enclosed inside of this chart.
+ AppVersion string `json:"appVersion,omitempty"`
+ // Whether or not this chart is deprecated
+ Deprecated bool `json:"deprecated,omitempty"`
+ // Annotations are additional mappings uninterpreted by Helm,
+ // made available for inspection by other applications.
+ Annotations map[string]string `json:"annotations,omitempty"`
+ // KubeVersion is a SemVer constraint specifying the version of Kubernetes required.
+ KubeVersion string `json:"kubeVersion,omitempty"`
+ // Specifies the chart type: application or library
+ Type string `json:"type,omitempty"`
+}
diff --git a/src/jetstream/plugins/monocular/utils/urlpoll.go b/src/jetstream/plugins/monocular/utils/urlpoll.go
deleted file mode 100644
index 3e71baddc0..0000000000
--- a/src/jetstream/plugins/monocular/utils/urlpoll.go
+++ /dev/null
@@ -1,144 +0,0 @@
-// https://golang.org/doc%2Fcodewalk%2Furlpoll.go
-
-package utils
-
-import (
- "ioutil"
- "log"
- "net/http"
- "time"
-)
-
-const (
- numPollers = 2 // number of Poller goroutines to launch
- pollInterval = 20 * time.Second // how often to poll each URL
- statusInterval = 10 * time.Second // how often to log status to stdout
- errTimeout = 10 * time.Second // back-off timeout on error
-)
-
-var urls = []string{
- "http://www.google.com/",
- "http://golang.org/",
- "http://blog.golang.org/",
-}
-
-// State represents the last-known state of a URL.
-type State struct {
- url string
- status string
- body string
- error string
-}
-
-// StateMonitor maintains a map that stores the state of the URLs being
-// polled, and prints the current state every updateInterval nanoseconds.
-// It returns a chan State to which resource state should be sent.
-func StateMonitor(updateInterval time.Duration) chan<- State {
- updates := make(chan State)
- urlStatus := make(map[string]string)
- ticker := time.NewTicker(updateInterval)
- go func() {
- for {
- select {
- case <-ticker.C:
- logState(urlStatus)
- case s := <-updates:
- urlStatus[s.url] = s.status
- }
- }
- }()
- return updates
-}
-
-// logState prints a state map.
-func logState(s map[string]string) {
- log.Println("Current state:")
- for k, v := range s {
- log.Printf(" %s %s", k, v)
- }
-}
-
-// Resource represents an HTTP URL to be polled by this program.
-type Resource struct {
- url string
- errCount int
-}
-
-// Poll executes an HTTP HEAD request for url
-// and returns the HTTP status string or an error string.
-func (r *Resource) PollHead() string {
- resp, err := http.Head(r.url)
- if err != nil {
- log.Println("Error", r.url, err)
- r.errCount++
- return err.Error()
- }
- r.errCount = 0
- return resp.Status
-}
-
-// Poll executes an HTTP GET request for url
-// and returns the HTTP status string, body as a string.
-func (r *Resource) PollGet() (string, string, error) {
-
- var body = ""
- resp, err := http.Get(r.url)
- defer resp.Body.Close()
- if err != nil {
- r.errCount++
- return body, resp.Status, err
- }
-
- bodyBytes, err := ioutil.ReadAll(resp.Body)
- if err != nil {
- r.errCount++
- return body, resp.Status, err
- }
-
- r.errCount = 0
- body = string(bodyBytes)
- return body, resp.Status, nil
-}
-
-// Sleep sleeps for an appropriate interval (dependent on error state)
-// before sending the Resource to done.
-func (r *Resource) Sleep(done chan<- *Resource) {
- time.Sleep(pollInterval + errTimeout*time.Duration(r.errCount))
- done <- r
-}
-
-func BodyPoller(in <-chan *Resource, out chan<- *Resource, status chan<- State) {
- for r := range in {
- b, s, e := r.PollGet()
- errorString = ""
- if e != nil {
- errorString = e.Error()
- }
- status <- State{r.url, s, b, errorString}
- out <- r
- }
-}
-
-func main() {
- // Create our input and output channels.
- pending, complete := make(chan *Resource), make(chan *Resource)
-
- // Launch the StateMonitor.
- status := StateMonitor(statusInterval)
-
- // Launch some Poller goroutines.
- for i := 0; i < numPollers; i++ {
- go Poller(pending, complete, status)
- }
-
- // Send some Resources to the pending queue.
- go func() {
- for _, url := range urls {
- pending <- &Resource{url: url}
- }
- }()
-
- for r := range complete {
- go r.Sleep(pending)
- }
-}
diff --git a/src/jetstream/repository/interfaces/endpoints.go b/src/jetstream/repository/interfaces/endpoints.go
index 376a286e77..f843ad254c 100644
--- a/src/jetstream/repository/interfaces/endpoints.go
+++ b/src/jetstream/repository/interfaces/endpoints.go
@@ -18,9 +18,14 @@ type RoutePlugin interface {
AddAdminGroupRoutes(echoContext *echo.Group)
}
+// EndpointAction identifies the type of action for an endpoint notification
type EndpointAction int
const (
+ // EndpointRegisterAction is for when an endpoint is registered
EndpointRegisterAction EndpointAction = iota
+ // EndpointUnregisterAction is for when an endpoint is unregistered
EndpointUnregisterAction
+ // EndpointUpdateAction is for when an endpoint is updated (e.g. renamed)
+ EndpointUpdateAction
)
diff --git a/website/docs/extensions/theming.md b/website/docs/extensions/theming.md
index 50d26d74da..cd55d91cfc 100644
--- a/website/docs/extensions/theming.md
+++ b/website/docs/extensions/theming.md
@@ -13,7 +13,7 @@ Stratos provides a mechanism for customizing the theme, including:
Theme's are best encapsulated in a new npm package. It should contain the usual `package.json` file with a `stratos` section which will contain some of the theme customizations.
-An example of the type of package that can be created can be found in the [ACME example](https://github.com/cloudfoundry/stratos/tree/master/src/frontend/packages/example-theme). To run Stratos with these customizations see [here](/docs/extensions/introduction#acme).
+An example of the type of package that can be created can be found in the [ACME example](https://github.com/cloudfoundry/stratos/tree/master/src/frontend/packages/example-theme). To run Stratos with these customizations see [here](./introduction#acme).
## Colors
Stratos uses Material Design and the [angular-material](https://material.angular.io/) library and uses the same approach to theming.
@@ -53,10 +53,10 @@ Additional Stratos colors can be customized by supplying more colors to the `the
|---|---|
|app-background-color| Base color to show in the background of the application |
|app-background-text-color| Color of text when placed on the basic background |
-|side-nav| See [below](/docs/extensions/theming#side-nav-colors) |
-|status| See [below](/docs/extensions/theming#status-colors)|
+|side-nav| See [below](./theming#side-nav-colors) |
+|status| See [below](./theming#status-colors)|
|subdued-color| Lighter color meant to be a subdued version of the primary color |
-|ansi-colors| See [below](/docs/extensions/theming#ansi-colors)|
+|ansi-colors| See [below](./theming#ansi-colors)|
|header-background-color| Background color for the main stratos header|
|header-foreground-color| Foreground color for the main stratos |
|stratos-title-show-text| Boolean - Show `Stratos` or provided title with the large logo in the about page, default log in page, etc |