diff options
Diffstat (limited to 'src')
76 files changed, 8598 insertions, 2351 deletions
diff --git a/src/k8splugin/go.mod b/src/k8splugin/go.mod index 75cb7c7e..5371bb50 100644 --- a/src/k8splugin/go.mod +++ b/src/k8splugin/go.mod @@ -1,6 +1,7 @@ module github.com/onap/multicloud-k8s/src/k8splugin require ( + github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect github.com/DATA-DOG/go-sqlmock v1.3.3 // indirect github.com/MakeNowJust/heredoc v0.0.0-20171113091838-e9091a26100e // indirect github.com/Masterminds/semver v1.4.2 // indirect @@ -8,26 +9,37 @@ require ( github.com/aokoli/goutils v1.1.0 // indirect github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878 // indirect github.com/chai2010/gettext-go v0.0.0-20170215093142-bf70f2a70fb1 // indirect + github.com/coreos/bbolt v1.3.3 // indirect + github.com/coreos/etcd v3.3.12+incompatible // indirect + github.com/coreos/go-semver v0.3.0 // indirect github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f // indirect github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect + github.com/cyphar/filepath-securejoin v0.2.2 // indirect + github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect + github.com/docker/distribution v2.7.0+incompatible // indirect github.com/docker/docker v0.7.3-0.20190912223608-ad718029b705 // indirect github.com/docker/engine v0.0.0-20190620014054-c513a4c6c298 github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c // indirect github.com/elazarl/goproxy v0.0.0-20190911111923-ecfe977594f1 // indirect github.com/evanphx/json-patch v4.5.0+incompatible // indirect - github.com/fvbommel/util v0.0.2 + github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect + github.com/fatih/camelcase v1.0.0 // indirect github.com/ghodss/yaml v1.0.0 github.com/go-openapi/spec v0.19.3 // indirect github.com/go-stack/stack v1.8.0 // indirect github.com/gobuffalo/packr v1.30.1 // indirect + github.com/gobwas/glob v0.2.3 // indirect github.com/golang/groupcache v0.0.0-20181024230925-c65c006176ff // indirect github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db // indirect + github.com/google/go-cmp v0.2.0 // indirect github.com/googleapis/gnostic v0.2.0 // indirect + github.com/gorilla/context v1.1.1 // indirect github.com/gorilla/handlers v1.3.0 - github.com/gorilla/mux v1.7.0 + github.com/gorilla/mux v1.6.2 github.com/gorilla/websocket v1.4.1 // indirect github.com/gregjones/httpcache v0.0.0-20181110185634-c63ab54fda8f // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.1.0 // indirect + github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect github.com/grpc-ecosystem/grpc-gateway v1.11.1 // indirect github.com/hashicorp/consul v1.4.0 github.com/hashicorp/go-msgpack v0.5.5 // indirect @@ -35,11 +47,20 @@ require ( github.com/hashicorp/memberlist v0.1.5 // indirect github.com/hashicorp/serf v0.8.1 // indirect github.com/huandu/xstrings v1.2.0 // indirect + github.com/imdario/mergo v0.3.5 // indirect github.com/jmoiron/sqlx v1.2.0 // indirect + github.com/jonboulle/clockwork v0.1.0 // indirect + github.com/json-iterator/go v1.1.7 // indirect github.com/lib/pq v1.2.0 // indirect + github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/mitchellh/go-testing-interface v1.0.0 // indirect + github.com/mitchellh/go-wordwrap v1.0.0 // indirect + github.com/modern-go/reflect2 v1.0.1 // indirect github.com/onsi/ginkgo v1.10.1 // indirect github.com/onsi/gomega v1.7.0 // indirect + github.com/opencontainers/go-digest v1.0.0-rc1 // indirect + github.com/pborman/uuid v1.2.0 // indirect + github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pkg/errors v0.8.1 github.com/rubenv/sql-migrate v0.0.0-20190902133344-8926f37f0bc1 // indirect github.com/sirupsen/logrus v1.4.2 @@ -51,46 +72,42 @@ require ( github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c // indirect github.com/xdg/stringprep v1.0.0 // indirect github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect + github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1 // indirect github.com/ziutek/mymysql v1.5.4 // indirect + go.etcd.io/bbolt v1.3.3 // indirect go.etcd.io/etcd v3.3.12+incompatible go.mongodb.org/mongo-driver v1.0.0 - golang.org/x/net v0.0.0-20191004110552-13f9640d40b9 + golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 + golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 // indirect + golang.org/x/time v0.0.0-20181108054448-85acf8d2951c // indirect + google.golang.org/appengine v1.5.0 // indirect gopkg.in/gorp.v1 v1.7.2 // indirect - k8s.io/api v0.18.2 - k8s.io/apiextensions-apiserver v0.18.2 - k8s.io/apimachinery v0.18.2 - k8s.io/cli-runtime v0.18.2 - k8s.io/client-go v12.0.0+incompatible - k8s.io/cloud-provider v0.18.2 - k8s.io/helm v2.16.12+incompatible - k8s.io/kubernetes v1.16.9 + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/square/go-jose.v2 v2.2.2 // indirect + gotest.tools v2.2.0+incompatible // indirect + k8s.io/api v0.0.0-20190831074750-7364b6bdad65 + k8s.io/apiextensions-apiserver v0.0.0-20190409022649-727a075fdec8 // indirect + k8s.io/apimachinery v0.0.0-20190831074630-461753078381 + k8s.io/apiserver v0.0.0-20190409021813-1ec86e4da56c // indirect + k8s.io/cli-runtime v0.0.0-20190913085402-777c64e2902f // indirect + k8s.io/client-go v11.0.1-0.20190409021438-1a26190bd76a+incompatible + k8s.io/cloud-provider v0.0.0-20190409023720-1bc0c81fa51d // indirect + k8s.io/helm v2.14.3+incompatible + k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf // indirect + k8s.io/kubernetes v1.14.1 // indirect k8s.io/utils v0.0.0-20190907131718-3d4f5b7dea0b // indirect - sigs.k8s.io/kustomize v2.0.3+incompatible + sigs.k8s.io/kustomize v2.0.3+incompatible // indirect + vbom.ml/util v0.0.0-20180919145318-efcd4e0f9787 // indirect ) replace ( - k8s.io/api => k8s.io/api v0.16.9 - k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.16.9 - k8s.io/apimachinery => k8s.io/apimachinery v0.16.10-beta.0 - k8s.io/apiserver => k8s.io/apiserver v0.16.9 - k8s.io/cli-runtime => k8s.io/cli-runtime v0.16.9 - k8s.io/client-go => k8s.io/client-go v0.16.9 - k8s.io/cloud-provider => k8s.io/cloud-provider v0.16.9 - k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.16.9 - k8s.io/code-generator => k8s.io/code-generator v0.16.10-beta.0 - k8s.io/component-base => k8s.io/component-base v0.16.9 - k8s.io/cri-api => k8s.io/cri-api v0.16.13-rc.0 - k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.16.9 - k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.16.9 - k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.16.9 - k8s.io/kube-proxy => k8s.io/kube-proxy v0.16.9 - k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.16.9 - k8s.io/kubectl => k8s.io/kubectl v0.16.9 - k8s.io/kubelet => k8s.io/kubelet v0.16.9 - k8s.io/kubernetes => github.com/kubernetes/kubernetes v1.16.9 - k8s.io/legacy-cloud-providers => k8s.io/legacy-cloud-providers v0.16.9 - k8s.io/metrics => k8s.io/metrics v0.16.9 - k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.16.9 + k8s.io/api => k8s.io/api v0.0.0-20190409021203-6e4e0e4f393b + k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.0.0-20190409022649-727a075fdec8 + k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d + k8s.io/apiserver => k8s.io/apiserver v0.0.0-20190409021813-1ec86e4da56c + k8s.io/cli-runtime => k8s.io/cli-runtime v0.0.0-20190409023024-d644b00f3b79 + k8s.io/client-go => k8s.io/client-go v11.0.1-0.20190409021438-1a26190bd76a+incompatible + k8s.io/cloud-provider => k8s.io/cloud-provider v0.0.0-20190409023720-1bc0c81fa51d ) go 1.13 diff --git a/src/k8splugin/go.sum b/src/k8splugin/go.sum index 034f31ff..af489130 100644 --- a/src/k8splugin/go.sum +++ b/src/k8splugin/go.sum @@ -1,104 +1,51 @@ -bitbucket.org/bertimus9/systemstat v0.0.0-20180207000608-0eeff89b0690/go.mod h1:Ulb78X89vxKYgdL24HMTiXYHlyHEvruOj1ZPlqeNEZM= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -github.com/Azure/azure-sdk-for-go v32.5.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= -github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= -github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= -github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= -github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= -github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= -github.com/Azure/go-autorest/autorest/to v0.2.0/go.mod h1:GunWKJp1AEqgMaGLV+iocmRAJWqST1wQYhyyjXJ3SJc= -github.com/Azure/go-autorest/autorest/validation v0.1.0/go.mod h1:Ha3z/SqBeaalWQvokg3NZAlQTalVMtOIAs1aGK7G6u8= -github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= -github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DATA-DOG/go-sqlmock v1.3.3 h1:CWUqKXe0s8A2z6qCgkP4Kru7wC11YoAnoupUKFDnH08= github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= -github.com/GoogleCloudPlatform/k8s-cloud-provider v0.0.0-20190822182118-27a4ced34534/go.mod h1:iroGtC8B3tQiqtds1l+mgk/BBOrxbqjH+eUfFQYRc14= -github.com/JeffAshton/win_pdh v0.0.0-20161109143554-76bb4ee9f0ab/go.mod h1:3VYc5hodBMJ5+l/7J4xAyMeuM2PNuepvHlGs8yilUCA= -github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E= github.com/MakeNowJust/heredoc v0.0.0-20171113091838-e9091a26100e h1:eb0Pzkt15Bm7f2FFYv7sjY7NPFi3cPkS3tv1CcrFBWA= github.com/MakeNowJust/heredoc v0.0.0-20171113091838-e9091a26100e/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E= github.com/Masterminds/semver v1.4.2 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITgsTc= github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/sprig v2.17.1+incompatible h1:PChbxFGKTWsg9IWh+pSZRCSj3zQkVpL6Hd9uWsFwxtc= github.com/Masterminds/sprig v2.17.1+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= -github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= -github.com/Microsoft/hcsshim v0.0.0-20190417211021-672e52e9209d/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= -github.com/OpenPeeDeeP/depguard v1.0.0/go.mod h1:7/4sitnI9YlQgTLLk734QlzXT8DuHVnAyztLplQjk+o= -github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmUx/1V+TNhjQvM= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/Rican7/retry v0.1.0/go.mod h1:FgOROf8P5bebcC1DS0PdOQiqGUridaZvikzUmkFW6gg= -github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= -github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/aokoli/goutils v1.1.0 h1:jy4ghdcYvs5EIoGssZNslIASX5m+KNMfyyKvRQ0TEVE= github.com/aokoli/goutils v1.1.0/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g9DP+DQ= -github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878 h1:EFSB7Zo9Eg91v7MJPVsifUysc/wPdN+NOnVe6bWbdBM= github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878/go.mod h1:3AMJUQhVx52RsWOnlkpikZr01T/yAVN2gn0861vByNg= -github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -github.com/auth0/go-jwt-middleware v0.0.0-20170425171159-5493cabe49f7/go.mod h1:LWMyo4iOLWXHGdBki7NIht1kHru/0wM179h+d3g8ATM= -github.com/aws/aws-sdk-go v1.16.26/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/bazelbuild/bazel-gazelle v0.18.2/go.mod h1:D0ehMSbS+vesFsLGiD6JXu3mVEzOlfUl8wNnq+x/9p0= -github.com/bazelbuild/bazel-gazelle v0.19.1-0.20191105222053-70208cbdc798/go.mod h1:rPwzNHUqEzngx1iVBfO/2X2npKaT3tqPqqHW6rVsn/A= -github.com/bazelbuild/buildtools v0.0.0-20190731111112-f720930ceb60/go.mod h1:5JP0TXzWDHXv8qvxRC4InIazwdyDseBDbzESUMKk1yU= -github.com/bazelbuild/buildtools v0.0.0-20190917191645-69366ca98f89/go.mod h1:5JP0TXzWDHXv8qvxRC4InIazwdyDseBDbzESUMKk1yU= -github.com/bazelbuild/rules_go v0.0.0-20190719190356-6dae44dc5cab/go.mod h1:MC23Dc/wkXEyk3Wpq6lCqz0ZAYOZDw2DR5y3N1q2i7M= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/bifurcation/mint v0.0.0-20180715133206-93c51c6ce115/go.mod h1:zVt7zX3K/aDCk9Tj+VM7YymsX66ERvzCJzw8rFCX2JU= -github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= -github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= -github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= -github.com/bruth/assert v0.0.0-20130823105606-de420fa3b72e/go.mod h1:MT8TZkfLPRir91B19sXF7pmKBma+n6ecyjbqgXabchs= -github.com/caddyserver/caddy v1.0.3/go.mod h1:G+ouvOY32gENkJC+jhgl62TyhvqEsFaDiZ4uw0RzP1E= -github.com/cenkalti/backoff v2.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= -github.com/cespare/prettybench v0.0.0-20150116022406-03b8cfe5406c/go.mod h1:Xe6ZsFhtM8HrDku0pxJ3/Lr51rwykrzgFwpmTzleatY= -github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw= github.com/chai2010/gettext-go v0.0.0-20170215093142-bf70f2a70fb1 h1:HD4PLRzjuCVW79mQ0/pdsalOLHJ+FaEoqJLxfltpb2U= github.com/chai2010/gettext-go v0.0.0-20170215093142-bf70f2a70fb1/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw= -github.com/checkpoint-restore/go-criu v0.0.0-20190109184317-bdb7599cd87b/go.mod h1:TrMrLQfeENAPYPRsJuq3jsqdlRh3lvi6trTZJG8+tho= -github.com/cheekybits/genny v0.0.0-20170328200008-9127e812e1e9/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/cfssl v0.0.0-20180726162950-56268a613adf/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA= -github.com/clusterhq/flocker-go v0.0.0-20160920122132-2b8b7259d313/go.mod h1:P1wt9Z3DP8O6W3rvwCt0REIlshg1InHImaLW0t3ObY0= -github.com/codegangsta/negroni v1.0.0/go.mod h1:v0y3T5G7Y1UlFfyxFn/QLRU4a2EuNau2iZY63YTKWo0= -github.com/container-storage-interface/spec v1.1.0/go.mod h1:6URME8mwIBbpVyZV93Ce5St17xBiQJQY67NDsuohiy4= -github.com/containerd/console v0.0.0-20170925154832-84eeaae905fa/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= -github.com/containerd/containerd v1.0.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/typeurl v0.0.0-20190228175220-2a93cfde8c20/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= -github.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= -github.com/coredns/corefile-migration v1.0.2/go.mod h1:OFwBp/Wc9dJt5cAZzHWMNhK1r5L0p0jDwIBc6j8NC8E= +github.com/coreos/bbolt v1.3.3 h1:n6AiVyVRKQFNb6mJlwESEvvLoDyiTzXX7ORAUlkeBdY= github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.12+incompatible h1:pAWNwdf7QiT1zfaWyqCtNZQWCLByQyA3JrSQyuYAqnQ= github.com/coreos/etcd v3.3.12+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/etcd v3.3.17+incompatible h1:f/Z3EoDSx1yjaIjLQGo1diYUlQYSBrrAQ5vP8NjwXwo= -github.com/coreos/etcd v3.3.17+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= -github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f h1:JOrtw2xFKzlg+cbHpyrpLDmnN1HqhBfnX7WDiW7eG2c= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/coreos/rkt v1.30.0/go.mod h1:O634mlH6U7qk87poQifK6M2rsFNt+FyUTWNMnP1hF1U= 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= @@ -106,318 +53,184 @@ github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2 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/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE= +github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= github.com/docker/distribution v2.7.0+incompatible h1:neUDAlf3wX6Ml4HdqTrbcOHXtfRN0TFIwt6YFL7N9RU= github.com/docker/distribution v2.7.0+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug= -github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v0.7.3-0.20190912223608-ad718029b705 h1:up4REDeXtcm77SlkowEGUuakgjpdNR2N9TkGTZSL4rM= github.com/docker/docker v0.7.3-0.20190912223608-ad718029b705/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/engine v0.0.0-20190620014054-c513a4c6c298 h1:dDGt5n84DvY05kaJT26cw1TDxNW1NymRZ13j0KeEQaw= github.com/docker/engine v0.0.0-20190620014054-c513a4c6c298/go.mod h1:3CPr2caMgTHxxIAZgEMd3uLYPDlRvPqCpyeRf6ncPcY= -github.com/docker/go-connections v0.3.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= -github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/docker/libnetwork v0.0.0-20180830151422-a9cd636e3789/go.mod h1:93m0aTqz6z+g32wla4l4WxTrdtvBRmVzYRkYvasA5Z8= -github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c h1:ZfSZ3P3BedhKGUhzj7BQlPSU4OvT6tfOKe3DVHzOA7s= github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/elazarl/goproxy v0.0.0-20190911111923-ecfe977594f1 h1:yY9rWGoXv1U5pl4gxqlULARMQD7x0QG85lqEXTWysik= github.com/elazarl/goproxy v0.0.0-20190911111923-ecfe977594f1/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= +github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2 h1:dWB6v3RcOy03t/bUadywsbyrQwCqZeNIEX6M1OtSZOM= github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633 h1:H2pdYOb3KQ1/YsqVWoWNLQO+fusocsw354rqGTZtAgw= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/emicklei/go-restful v2.9.5+incompatible h1:spTtZBk5DYEvbxMVutUuTyh1Ao2r4iyvLdACqsl/Ljk= -github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/euank/go-kmsg-parser v2.0.0+incompatible/go.mod h1:MhmAMZ8V4CYH4ybgdRwPr2TU5ThnS43puaKEMpja1uw= -github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M= github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= +github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8= github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= -github.com/fatih/color v1.6.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fvbommel/util v0.0.2/go.mod h1:n7nJJ4dUdRBvS0OR9FZ9zhHvQJX/3DoYiStK6hUtafs= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/ghodss/yaml v0.0.0-20180820084758-c7ce16629ff4/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/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= -github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= -github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= -github.com/go-acme/lego v2.5.0+incompatible/go.mod h1:yzMNe9CasVUhkquNvti5nAtPmG94USbYxYrZfTkIn0M= -github.com/go-bindata/go-bindata v3.1.1+incompatible/go.mod h1:xK8Dsgwmeed+BBsSy2XTopBn/8uK2HWuGSnA11C3Joo= -github.com/go-critic/go-critic v0.3.5-0.20190526074819-1df300866540/go.mod h1:+sE8vrLDS2M0pZkBk0wy6+nLdKexVDrl/jBqQOTDThA= -github.com/go-lintpack/lintpack v0.5.2/go.mod h1:NwZuYi2nUHho8XEIZ6SIxihrnPoqBTDqfpXvXAN0sXM= -github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= -github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= -github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= -github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= -github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= -github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= -github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= -github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= -github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= -github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= -github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= +github.com/go-openapi/jsonpointer v0.19.2 h1:A9+F4Dc/MCNB5jibxf6rRvOvR/iFgQdyNx9eIhnGqq0= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= -github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= -github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.19.2 h1:o20suLFB4Ri0tuzpWtyHlh7E7HnkqTNLq6aR6WVNS1w= github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= -github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= -github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= -github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= -github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs= -github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA= -github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64= github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= -github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= -github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= -github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= github.com/go-openapi/spec v0.19.3 h1:0XRyw8kguri6Yw4SxhsQA/atC88yqrk0+G4YhI2wabc= github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= -github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= -github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= -github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY= github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= -github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= -github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= +github.com/go-openapi/swag v0.19.2 h1:jvO6bCMBEilGwMfHhrd61zIID4oIFdwb76V17SM88dE= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= -github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= -github.com/go-ozzo/ozzo-validation v3.5.0+incompatible/go.mod h1:gsEKFIVnabGBt6mXmxK0MoFy+cZoTJY6mu5Ll3LVLBU= +github.com/go-sql-driver/mysql v1.4.0 h1:7LxgVwFb2hIQtMm87NdgAVfXjnt4OePseqT1tKx+opk= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= 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/go-toolsmith/astcast v1.0.0/go.mod h1:mt2OdQTeAQcY4DQgPSArJjHCcOwlX+Wl/kwN+LbLGQ4= -github.com/go-toolsmith/astcopy v1.0.0/go.mod h1:vrgyG+5Bxrnz4MZWPF+pI4R8h3qKRjjyvV/DSez4WVQ= -github.com/go-toolsmith/astequal v0.0.0-20180903214952-dcb477bfacd6/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY= -github.com/go-toolsmith/astequal v1.0.0/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY= -github.com/go-toolsmith/astfmt v0.0.0-20180903215011-8f8ee99c3086/go.mod h1:mP93XdblcopXwlyN4X4uodxXQhldPGZbcEJIimQHrkg= -github.com/go-toolsmith/astfmt v1.0.0/go.mod h1:cnWmsOAuq4jJY6Ct5YWlVLmcmLMn1JUPuQIHCY7CJDw= -github.com/go-toolsmith/astinfo v0.0.0-20180906194353-9809ff7efb21/go.mod h1:dDStQCHtmZpYOmjRP/8gHHnCCch3Zz3oEgCdZVdtweU= -github.com/go-toolsmith/astp v0.0.0-20180903215135-0af7e3c24f30/go.mod h1:SV2ur98SGypH1UjcPpCatrV5hPazG6+IfNHbkDXBRrk= -github.com/go-toolsmith/astp v1.0.0/go.mod h1:RSyrtpVlfTFGDYRbrjyWP1pYu//tSFcvdYrA8meBmLI= -github.com/go-toolsmith/pkgload v0.0.0-20181119091011-e9e65178eee8/go.mod h1:WoMrjiy4zvdS+Bg6z9jZH82QXwkcgCBX6nOfnmdaHks= -github.com/go-toolsmith/pkgload v1.0.0/go.mod h1:5eFArkbO80v7Z0kdngIxsRXRMTaX4Ilcwuh3clNrQJc= -github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8= -github.com/go-toolsmith/typep v1.0.0/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU= +github.com/gobuffalo/envy v1.7.0 h1:GlXgaiBkmrYMHco6t4j7SacKO4XUjvh5pwXh0f4uxXU= github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= github.com/gobuffalo/logger v1.0.0/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs= +github.com/gobuffalo/packd v0.3.0 h1:eMwymTkA1uXsqxS0Tpoop3Lc0u3kTfiMBE6nKtQU4g4= github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q= +github.com/gobuffalo/packr v1.30.1 h1:hu1fuVR3fXEZR7rXNW3h8rqSML8EVAf6KNm0NKO/wKg= github.com/gobuffalo/packr v1.30.1/go.mod h1:ljMyFO2EcrnzsHsN99cvbq055Y9OhRrIaviy289eRuk= github.com/gobuffalo/packr/v2 v2.5.1/go.mod h1:8f9c96ITobJlPzI44jj+4tHnEKNt0xXWSVlXRN9X1Iw= 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/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d h1:3PaI8p3seN09VjbTYC/QWlUZdZ1qS1zGjy7LH2Wt07I= -github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20181024230925-c65c006176ff h1:kOkM9whyQYodu09SJ6W3NCsHG7crFaJILQ22Gozp3lg= github.com/golang/groupcache v0.0.0-20181024230925-c65c006176ff/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.0.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/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.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8juN+UKMCS/3jFtGICgW8O96FVaZsaxdzDkR4= -github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= -github.com/golangci/errcheck v0.0.0-20181223084120-ef45e06d44b6/go.mod h1:DbHgvLiFKX1Sh2T1w8Q/h4NAI8MHIpzCdnBUDTXU3I0= -github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613/go.mod h1:SyvUF2NxV+sN8upjjeVYr5W7tyxaT1JVtvhKhOn2ii8= -github.com/golangci/go-tools v0.0.0-20190318055746-e32c54105b7c/go.mod h1:unzUULGw35sjyOYjUt0jMTXqHlZPpPc6e+xfO4cd6mM= -github.com/golangci/goconst v0.0.0-20180610141641-041c5f2b40f3/go.mod h1:JXrF4TWy4tXYn62/9x8Wm/K/dm06p8tCKwFRDPZG/1o= -github.com/golangci/gocyclo v0.0.0-20180528134321-2becd97e67ee/go.mod h1:ozx7R9SIwqmqf5pRP90DhR2Oay2UIjGuKheCBCNwAYU= -github.com/golangci/gofmt v0.0.0-20181222123516-0b8337e80d98/go.mod h1:9qCChq59u/eW8im404Q2WWTrnBUQKjpNYKMbU4M7EFU= -github.com/golangci/golangci-lint v1.18.0/go.mod h1:kaqo8l0OZKYPtjNmG4z4HrWLgcYNIJ9B9q3LWri9uLg= -github.com/golangci/gosec v0.0.0-20190211064107-66fb7fc33547/go.mod h1:0qUabqiIQgfmlAmulqxyiGkkyF6/tOGSnY2cnPVwrzU= -github.com/golangci/ineffassign v0.0.0-20190609212857-42439a7714cc/go.mod h1:e5tpTHCfVze+7EpLEozzMB3eafxo2KT5veNg1k6byQU= -github.com/golangci/lint-1 v0.0.0-20190420132249-ee948d087217/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg= -github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca/go.mod h1:tvlJhZqDe4LMs4ZHD0oMUlt9G2LWuDGoisJTBzLMV9o= -github.com/golangci/misspell v0.0.0-20180809174111-950f5d19e770/go.mod h1:dEbvlSfYbMQDtrpRMQU675gSDLDNa8sCPPChZ7PhiVA= -github.com/golangci/prealloc v0.0.0-20180630174525-215b22d4de21/go.mod h1:tf5+bzsHdTM0bsB7+8mt0GUMvjCgwLpTapNZHU8AajI= -github.com/golangci/revgrep v0.0.0-20180526074752-d9c87f5ffaf0/go.mod h1:qOQCunEYvmd/TLamH+7LlVccLvUH5kZNhbCgTHoBbp4= -github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4/go.mod h1:Izgrg8RkN3rCIMLGE9CyYmU9pY2Jer6DgANEnZ/L/cQ= -github.com/golangplus/bytes v0.0.0-20160111154220-45c989fe5450/go.mod h1:Bk6SMAONeMXrxql8uvOKuAZSu8aM5RUGv+1C6IJaEho= -github.com/golangplus/fmt v0.0.0-20150411045040-2a5d6d7d2995/go.mod h1:lJgMEyOkYFkPcDKwRXegd+iM6E7matEszMG5HhwytU8= -github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c h1:964Od4U6p2jUkFxvCydnIczKteheJEzHRToSGK3Bnlw= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/cadvisor v0.34.0/go.mod h1:1nql6U13uTHaLYB8rLS5x9IJc2qT6Xd/Tr1sTX6NE48= -github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg= +github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= -github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -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/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.2.0 h1:l6N3VoaVzTncYYW+9yOz2LJJammFZGBO13sqgEhpy9g= github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= -github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +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.3.0 h1:tsg9qP3mjt1h4Roxp+M1paRjrVBfPSOpBuVclh6YluI= github.com/gorilla/handlers v1.3.0/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -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/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE= -github.com/gostaticanalysis/analysisutil v0.0.3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE= -github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/gregjones/httpcache v0.0.0-20181110185634-c63ab54fda8f h1:ShTPMJQes6tubcjzGMODIVG5hlrCeImaBnZzKF2N8SM= github.com/gregjones/httpcache v0.0.0-20181110185634-c63ab54fda8f/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/go-grpc-middleware v0.0.0-20190222133341-cfaf5686ec79/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.1.0 h1:THDBEeQ9xZ8JEaCLyLQqXMMdRqNr0QAUJTIkQAUtFjg= github.com/grpc-ecosystem/go-grpc-middleware v1.1.0/go.mod h1:f5nM7jw/oeRSadq3xCzHAvxcr8HZnzsqU6ILg/0NiiE= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.3.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= +github.com/grpc-ecosystem/grpc-gateway v1.11.1 h1:/dBYI+n4xIL+Y9SKXQrjlKTmJJDwCSlNLRwZ5nBhIek= github.com/grpc-ecosystem/grpc-gateway v1.11.1/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/hashicorp/consul v1.4.0 h1:PQTW4xCuAExEiSbhrsFsikzbW5gVBoi74BjUvYFyKHw= github.com/hashicorp/consul v1.4.0/go.mod h1:mFrjN1mfidgJfYP1xrJCF+AfRhr6Eaqhb2+sfyn/OOI= +github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.0 h1:wvCrVc9TjDls6+YGAF2hAifE1E5U1+b4tH6KdvN3Gig= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI= github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-rootcerts v0.0.0-20160503143440-6bb64b370b90 h1:VBj0QYQ0u2MCJzBfeYXGexnAl17GsH1yidnoxCqqD9E= github.com/hashicorp/go-rootcerts v0.0.0-20160503143440-6bb64b370b90/go.mod h1:o4zcYY1e0GEZI6eSEr+43QDYmuGglw1qSO6qdHUHCgg= +github.com/hashicorp/go-sockaddr v1.0.0 h1:GeH6tui99pF4NJgfnhp+L6+FfobzVW3Ah46sLo0ICXs= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0 h1:RS8zrF7PhGwyNPOtxSClXXj9HA8feRnJzgnI1RJCSnM= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/golang-lru v0.0.0-20180201235237-0fb14efe8c47/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/memberlist v0.1.5 h1:AYBsgJOW9gab/toO5tEB8lWetVgDKZycqkebJ8xxpqM= github.com/hashicorp/memberlist v0.1.5/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.1 h1:mYs6SMzu72+90OcPa5wr3nfznA4Dw9UyR791ZFNOIf4= github.com/hashicorp/serf v0.8.1/go.mod h1:h/Ru6tmZazX7WO/GDmwdpS975F019L4t5ng5IgwbNrE= -github.com/heketi/heketi v9.0.1-0.20190917153846-c2e2a4ab7ab9+incompatible/go.mod h1:bB9ly3RchcQqsQ9CpyaQwvva7RS5ytVoSoholZQON6o= -github.com/heketi/tests v0.0.0-20151005000721-f3775cbcefd6/go.mod h1:xGMAM8JLi7UkZt1i4FQeQy0R2T8GLUwQhOP5M1gBhy4= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huandu/xstrings v1.2.0 h1:yPeWdRnmynF7p+lLYz0H2tthW9lqhMJrQV/U7yy4wX0= github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4= github.com/imdario/mergo v0.3.5 h1:JboBksRwiiAJWvIYJVo46AfV+IAIKZpfrSzVKj42R4Q= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= -github.com/jimstudt/http-authentication v0.0.0-20140401203705-3eca13d6893a/go.mod h1:wK6yTYYcgjHE1Z1QtXACPDjcFJyBskHEdagmnq3vsP8= -github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA= github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= +github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= 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 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/karrick/godirwalk v1.7.5/go.mod h1:2c9FRhkDxdIbgkOnCEvnSWs71Bhugbl46shStcFDJ34= github.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= -github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= -github.com/kisielk/gotool v0.0.0-20161130080628-0de1eaf82fa3/go.mod h1:jxZFDH7ILpTPQTk+E2s+z4CUas9lVNjIuKR4c5/zKgM= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= -github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= +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/kubernetes/kubernetes v1.16.9 h1:SNn5JAFCIFJcpq8urxnSMoGK87SAgrSPPmUmL/B7jcs= -github.com/kubernetes/kubernetes v1.16.9/go.mod h1:bpUsy1qP0W6EtkxrPluP02p2+wyVN+95lkjPKnLQZtc= -github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/libopenstorage/openstorage v1.0.0/go.mod h1:Sp1sIObHjat1BeXhfMqLZ14wnOzEhNx2YQedreMcUyc= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= -github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= -github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= -github.com/lpabon/godbc v0.1.1/go.mod h1:Jo9QV0cf3U6jZABgiJ2skINAXb9j8m51r07g4KI92ZA= -github.com/lucas-clemente/aes12 v0.0.0-20171027163421-cd47fb39b79f/go.mod h1:JpH9J1c9oX6otFSgdUHwUBUizmKlrMjxWnIAjff4m04= -github.com/lucas-clemente/quic-clients v0.1.0/go.mod h1:y5xVIEoObKqULIKivu+gD/LU90pL73bTdtQjPBvtCBk= -github.com/lucas-clemente/quic-go v0.10.2/go.mod h1:hvaRS9IHjFLMq76puFJeWNfmn+H70QZ/CXoxqw9bzao= -github.com/lucas-clemente/quic-go-certificates v0.0.0-20160823095156-d2f86524cced/go.mod h1:NCcRLrOTZbzhZvixZLlERbJtDtYsmMw8Jc4vS8Z0g58= -github.com/magiconair/properties v1.7.6/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63 h1:nTT4s92Dgz2HlrB2NaMgvlfqHH39OgMhA7z3PK7PGD4= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/marten-seemann/qtls v0.2.3/go.mod h1:xzjG7avBwGGbdZ8dTGxlBnLArsVKLvwmjgmPuiQEcYk= -github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= -github.com/mattn/go-shellwords v1.0.5/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= +github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= 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/mesos/mesos-go v0.0.9/go.mod h1:kPYCMQ9gsOXVAle1OsoY4I1+9kPu8GHkf88aV59fDr4= -github.com/mholt/certmagic v0.6.2-0.20190624175158-6a42ef9fe8c2/go.mod h1:g4cOPxcjV0oFq3qwpjSA30LReKD8AoIfwAY9VvG35NY= +github.com/miekg/dns v1.0.14 h1:9jZdLNd/P4+SfEJ0TNyxYpsK8N4GtfylBLqtbYN1sbA= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/miekg/dns v1.1.3/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/miekg/dns v1.1.4/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/mindprince/gonvml v0.0.0-20171110221305-fee913ce8fb2/go.mod h1:2eu9pRWp8mo84xCg6KswZ+USQHjwgRhNp06sozOdsTY= -github.com/mistifyio/go-zfs v2.1.1+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= -github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-ps v0.0.0-20170309133038-4fdf99ab2936/go.mod h1:r1VsdOzOPt1ZSrGZWFoNhsAedKnEd6r9Np1+5blZCWk= +github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= -github.com/mitchellh/mapstructure v0.0.0-20180220230111-00c29f56e238/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= 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= @@ -427,47 +240,30 @@ github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lN github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/mohae/deepcopy v0.0.0-20170603005431-491d3605edfb/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= -github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/mozilla/tls-observatory v0.0.0-20180409132520-8791a200eb40/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk= -github.com/mrunalp/fileutils v0.0.0-20160930181131-4ee1cc9a8058/go.mod h1:x8F1gnqOkIEiO4rqoeEEEqQbo7HjGMTvyoq3gej4iT0= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/mvdan/xurls v1.1.0/go.mod h1:tQlNn3BED8bE/15hnSL2HLkDeLWpNPAwtw7wkEq44oU= -github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0= -github.com/naoina/toml v0.1.1/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= -github.com/nbutton23/zxcvbn-go v0.0.0-20160627004424-a22cb81b2ecd/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU= -github.com/nbutton23/zxcvbn-go v0.0.0-20171102151520-eafdab6b0663/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU= 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.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo= 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.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= -github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/runc v1.0.0-rc2.0.20190611121236-6cc515888830/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opencontainers/runtime-spec v1.0.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/selinux v1.2.2/go.mod h1:+BLncwf63G4dgOzykXAxcmnFlUaOlkDdmw/CqsW6pjs= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= -github.com/pelletier/go-toml v1.1.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 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/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= -github.com/pquerna/ffjson v0.0.0-20180717144149-af8b230fcd20/go.mod h1:YARuvh7BUWHNhzDq2OM5tzR2RiCcN2D7sapiKyCel/M= github.com/prometheus/client_golang v0.9.2 h1:awm861/B8OKDd2I/6o1dy3ra4BamzKhYOiGItCeZ740= github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8= @@ -476,369 +272,200 @@ github.com/prometheus/common v0.0.0-20181126121408-4724e9255275 h1:PnBWHBf+6L0jO github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a h1:9a8MnZMP0X2nLJdBg+pBmGgkJlSaKC2KaQmTCk1XDtE= github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI= -github.com/quobyte/api v0.1.2/go.mod h1:jL7lIHrmqQ7yh05OJ+eEEdHr0u/kmT1Ff9iHd+4H6VI= -github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= -github.com/robfig/cron v1.1.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rubenv/sql-migrate v0.0.0-20190902133344-8926f37f0bc1 h1:G7j/gxkXAL80NMLOWi6EEctDET1Iuxl3sBMJXDnu2z0= github.com/rubenv/sql-migrate v0.0.0-20190902133344-8926f37f0bc1/go.mod h1:WS0rl9eEliYI8DPnr3TOwz4439pay+qNgzJoVya/DmY= -github.com/rubiojr/go-vhd v0.0.0-20160810183302-0bfd3b39853c/go.mod h1:DM5xW0nvfNNm2uytzsvhI3OnX8uzaRAg8UX/CnDqbto= -github.com/russross/blackfriday v0.0.0-20170610170232-067529f716f4/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= -github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= -github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= -github.com/shirou/gopsutil v0.0.0-20180427012116-c95755e4bcd7/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= -github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc= -github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= -github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= -github.com/sirupsen/logrus v1.0.5/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= -github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/soheilhy/cmux v0.1.3/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/sourcegraph/go-diff v0.5.1/go.mod h1:j2dHj3m8aZgQO8lMTcTnBcXkRRRqi34cd2MNlA9u1mE= -github.com/spf13/afero v1.1.0/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= -github.com/spf13/cast v1.2.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.2/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= 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 v0.0.0-20180109140146-7c0cea34c8ec/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.1/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.0.2/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= -github.com/storageos/go-api v0.0.0-20180912212459-343b3eff91fc/go.mod h1:ZrLn+e0ZuF3Y65PNF6dIwbJPZqfmtCXxFm9ckv0agOY= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= 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/syndtr/gocapability v0.0.0-20160928074757-e7cb7fa329f4/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= -github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= github.com/technosophos/moniker v0.0.0-20180509230615-a5dbd03a2245 h1:DNVk+NIkGS0RbLkjQOLCJb/759yfCysThkMbl7EXxyY= github.com/technosophos/moniker v0.0.0-20180509230615-a5dbd03a2245/go.mod h1:O1c8HleITsZqzNZDjSNzirUGsMT0oGu9LhHKoJrqO+A= -github.com/thecodeteam/goscaleio v0.1.0/go.mod h1:68sdkZAsK8bvEwBlbQnlLS+xU+hvLYM/iQ8KXej1AwM= +github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51 h1:BP2bjP495BBPaBcS5rmqviTfrOkN5rO5ceKAMRZCRFc= github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= -github.com/timakin/bodyclose v0.0.0-20190721030226-87058b9bfcec/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk= -github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= -github.com/ultraware/funlen v0.0.1/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA= -github.com/ultraware/funlen v0.0.2/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA= -github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= -github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasthttp v1.2.0/go.mod h1:4vX61m6KN+xDduDNwXrhIAVZaZaZiQ1luJk8LWSxF3s= -github.com/valyala/quicktemplate v1.1.1/go.mod h1:EH+4AkTd43SvgIbQHYu59/cJyxDoOVRUAfrukLPuGJ4= -github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= -github.com/vishvananda/netlink v0.0.0-20171020171820-b2de5d10e38e/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= -github.com/vishvananda/netns v0.0.0-20171111001504-be1fbeda1936/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= -github.com/vmware/govmomi v0.20.1/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU= 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/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1 h1:j2hhcujLRHAg872RWAV5yaUrEjHEObwDv3aImCaNLek= github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1/go.mod h1:QcJo0QPSfTONNIgpN5RA8prR7fF8nkF6cTWTcNerRO8= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= +go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/etcd v3.3.12+incompatible h1:V6PRYRGpU4k5EajJaaj/GL3hqIdzyPnBU8aPUp+35yw= go.etcd.io/etcd v3.3.12+incompatible/go.mod h1:yaeTdrJi5lOmYerz05bd8+V7KubZs8YSFZfzsF9A6aI= go.mongodb.org/mongo-driver v1.0.0 h1:KxPRDyfB2xXnDE2My8acoOWBQkfv3tz0SaWTRZjJR0c= go.mongodb.org/mongo-driver v1.0.0/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.uber.org/atomic v0.0.0-20181018215023-8dc6146f7569/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/multierr v0.0.0-20180122172545-ddea229ff1df/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/zap v0.0.0-20180814183419-67bc79d13d15/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= -golang.org/x/build v0.0.0-20190927031335-2835ba2e683f/go.mod h1:fYw7AShPAhGMdXqA9gRadk/CcMsvLlClpE5oBwnS3dM= -golang.org/x/crypto v0.0.0-20180426230345-b49d69b5da94/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190424203555-c05e17bb3b2d/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8 h1:1wopBVtVdWnn03fZelqdXTqk7U7zPQCb+T4rbU9ZEoU= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4 h1:ydJNl0ENAG67pFbB+9tfhiL2pYqLhfoaZFw/cjLhY4A= golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975 h1:/Tl7pH94bvbAAHBdZJT947M/+gp0+CqQXDtMRC0fseo= -golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20170915142106-8351a756f30f/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181102091132-c10e9556a7bc/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-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190328230028-74de082e2cca/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190502183928-7f726cade0ab/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/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-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191004110552-13f9640d40b9 h1:rjwSpXsdiK0dV8/Naq3kAw9ymfAeJIyd0upUIElB+lI= -golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= 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-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= golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20171026204733-164713f0dfce/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190122071731-054c452bb702/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190124100055-b90733256f2e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190515120540-06a5c4944438 h1:khxRGsvPk4n2y8I/mLLjp7e5dMTJmH75wvqS6nMwUtY= golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f h1:25KHgbfyiSm6vwQLbM3zZIe1v9p/3ea4Rz+nnM5K/i4= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/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/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.0.0-20170915090833-1cbadb444a80/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c h1:fqgJT0MGcGpPgpWU7VRdRjuArfcOvC4AoJmILihzhDg= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20170915040203-e531a2a1c15f/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181117154741-2ddaf7f79a09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190110163146-51295c7ec13a/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190121143147-24cd39ecf745/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190122202912-9c309ee22fab/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190311215038-5c2858a9cfe5/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190322203728-c1a832b0ad89/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190521203540-521d6ed310dd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190909030654-5b82db07426d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= -gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= -gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.6.1-0.20190607001116-5213b8090861/go.mod h1:btoxGiFvQNVUZQ8W08zLtrVS08CNpINPEfxXxgJL1Q4= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/grpc v1.19.0 h1:cfg4PD8YEdSFnm7qLV4++93WcmhH2nIUhMjhdCvl3j8= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.23.0 h1:AzbTB6ux+okLTzP8Ru1Xs41C303zdcfEht7MQnYJt5A= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/gcfg.v1 v1.2.0/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= -gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= gopkg.in/gorp.v1 v1.7.2 h1:j3DWlAyGVv8whO7AcIWznQ2Yj7yJkn34B8s63GViAAw= gopkg.in/gorp.v1 v1.7.2/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/mcuadros/go-syslog.v2 v2.2.1/go.mod h1:l5LPIyOOyIdQquNg+oU6Z3524YwrcqEm0aKH+5zpt2U= -gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.2.2 h1:orlkJ3myw8CN1nVQHBFfloD+L3egixIa4FvUP6RosSA= gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/warnings.v0 v0.1.1/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gotest.tools v2.1.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= -gotest.tools/gotestsum v0.3.5/go.mod h1:Mnf3e5FUzXbkCfynWBGOwLssY7gTQgCHObK9tMpAriY= -grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.2/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= k8s.io/api v0.0.0-20190409021203-6e4e0e4f393b h1:aBGgKJUM9Hk/3AE8WaZIApnTxG35kbuQba2w+SXqezo= k8s.io/api v0.0.0-20190409021203-6e4e0e4f393b/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA= -k8s.io/api v0.16.9 h1:3vCx0WX9qcg1Hv4aQ/G1tiIKectGVuimvPVTJU4VOCA= -k8s.io/api v0.16.9/go.mod h1:Y7dZNHs1Xy0mSwSlzL9QShi6qkljnN41yR8oWCRTDe8= +k8s.io/api v0.0.0-20190831074750-7364b6bdad65 h1:J9uZfyjvqkRa9p2Z22vCCraDjq1Dwf/x9wXzPf6fqSk= +k8s.io/api v0.0.0-20190831074750-7364b6bdad65/go.mod h1:u09ZxrpPFcoUNEQM2GsqT/KpglKAtXdEcK+tSMilQ3Q= k8s.io/apiextensions-apiserver v0.0.0-20190409022649-727a075fdec8 h1:q1Qvjzs/iEdXF6A1a8H3AKVFDzJNcJn3nXMs6R6qFtA= k8s.io/apiextensions-apiserver v0.0.0-20190409022649-727a075fdec8/go.mod h1:IxkesAMoaCRoLrPJdZNZUQp9NfZnzqaVzLhb2VEQzXE= -k8s.io/apiextensions-apiserver v0.16.9 h1:CE+SWS6PM3MDJiyihW5hnDiqsJ/sjMaSMblqzH37J18= -k8s.io/apiextensions-apiserver v0.16.9/go.mod h1:j/+KedxOeRSPMkvLNyKMbIT3+saXdTO4jTBplTmXJR4= k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d h1:Jmdtdt1ZnoGfWWIIik61Z7nKYgO3J+swQJtPYsP9wHA= k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= -k8s.io/apimachinery v0.16.10-beta.0 h1:l+qmzwWTMIBtFGlo5OpPYoZKCgGLtpAWvIa8Wcr9luU= -k8s.io/apimachinery v0.16.10-beta.0/go.mod h1:Xk2vD2TRRpuWYLQNM6lT9R7DSFZUYG03SarNkbGrnKE= +k8s.io/apimachinery v0.0.0-20190831074630-461753078381 h1:gySvpxrHatsZtG3qOkyPIHjWY7D5ogkrrWnD7+5/RGs= +k8s.io/apimachinery v0.0.0-20190831074630-461753078381/go.mod h1:nL6pwRT8NgfF8TT68DBI8uEePRt89cSvoXUVqbkWHq4= k8s.io/apiserver v0.0.0-20190409021813-1ec86e4da56c h1:k7ALUVzrOEgz4hOF+pr4pePn7TqZ9lB/8Z8ndMSsWSU= k8s.io/apiserver v0.0.0-20190409021813-1ec86e4da56c/go.mod h1:6bqaTSOSJavUIXUtfaR9Os9JtTCm8ZqH2SUl2S60C4w= -k8s.io/apiserver v0.16.9 h1:+gYGD2LFXI9twZpWFyZgh29YfSLyTO27IzgEF12MgJg= -k8s.io/apiserver v0.16.9/go.mod h1:JWzfDIpD8e9rvU+Gn6ew8MfQZq41USj0iwW5+ZLyTLM= k8s.io/cli-runtime v0.0.0-20190409023024-d644b00f3b79 h1:bZyxc0wzVA5KgUfAXZA6z872zDWmyslwfvrr175VF68= k8s.io/cli-runtime v0.0.0-20190409023024-d644b00f3b79/go.mod h1:qWnH3/b8sp/l7EvlDh7ulDU3UWA4P4N1NFbEEP791tM= -k8s.io/cli-runtime v0.16.9 h1:8R6vlzl/Qcb+hRIWQp0vBgMBkruxP5g7RkQdYzWnqfc= -k8s.io/cli-runtime v0.16.9/go.mod h1:gVhdxu/z31/5nsr4yciGJrdODVhBH1mboFYzqMAlsJc= -k8s.io/client-go v0.16.9 h1:6Eh4lMDxFtDzBkqid1AOL3bQ/pPYrulx8l23DXw4mRU= -k8s.io/client-go v0.16.9/go.mod h1:ThjPlh7Kx+XoBFOCt775vx5J7atwY7F/zaFzTco5gL0= +k8s.io/cli-runtime v0.0.0-20190913085402-777c64e2902f h1:gQH9KuiqXEhXWEHnov9bS/ysYPSIYNT1P3BWC9HGI7M= +k8s.io/cli-runtime v0.0.0-20190913085402-777c64e2902f/go.mod h1:TtjkdmxYMLASzYbE8E7AUr/ZrXMcmXLnDLRY4sVWspw= k8s.io/client-go v11.0.1-0.20190409021438-1a26190bd76a+incompatible h1:U5Bt+dab9K8qaUmXINrkXO135kA11/i5Kg1RUydgaMQ= k8s.io/client-go v11.0.1-0.20190409021438-1a26190bd76a+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s= k8s.io/cloud-provider v0.0.0-20190409023720-1bc0c81fa51d h1:ad7UpNUGRx6FbYoK4+xIYyeS2CUShjNKY45YN1ipjLI= k8s.io/cloud-provider v0.0.0-20190409023720-1bc0c81fa51d/go.mod h1:LlIffnLBu+GG7d4ppPzC8UnA1Ex8S+ntmSRVsnr7Xy4= -k8s.io/cloud-provider v0.16.9/go.mod h1:h5w+p2akfq206hhk+gtiUWAHNK093+FxTuSfIlOKoSo= -k8s.io/cluster-bootstrap v0.16.9/go.mod h1:Ou7X3KqHG/I/9dcZK/e4Z8mQMVhxajbQjXPQPB5EA2g= -k8s.io/code-generator v0.16.10-beta.0/go.mod h1:wFdrXdVi/UC+xIfLi+4l9elsTT/uEF61IfcN2wOLULQ= -k8s.io/component-base v0.16.9 h1:ChdRdMGDq9vTq5vJRaQ8VuEHLwhDJ+eAvfNghZqJcck= -k8s.io/component-base v0.16.9/go.mod h1:5iNKIRj8yEaKG+baEkfXgU9JiWpC1WAFGBZ3Xg9fDJk= -k8s.io/cri-api v0.16.13-rc.0/go.mod h1:W6aMMPN5fmxcRGaHnb6BEfoTeS82OsJcsUJyKf+EWYc= -k8s.io/csi-translation-lib v0.16.9/go.mod h1:+y+WYfHErQ/gDn9UpPBqmtOYLrTpedu/vuMhLsiuWI8= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/heapster v1.2.0-beta.1/go.mod h1:h1uhptVXMwC8xtZBYsPXKVi8fpdlYkTs6k949KozGrM= k8s.io/helm v2.14.3+incompatible h1:uzotTcZXa/b2SWVoUzM1xiCXVjI38TuxMujS/1s+3Gw= k8s.io/helm v2.14.3+incompatible/go.mod h1:LZzlS4LQBHfciFOurYBFkCMTaZ0D1l+p0teMg7TSULI= -k8s.io/helm v2.16.12+incompatible h1:K2zhF8+B85Ya1n7n3eH34xwwp5qNUM42TBFENDZJT7w= -k8s.io/helm v2.16.12+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 h1:0VPpR+sizsiivjIfIAQH/rl8tan6jvWkS7lU+0di3lE= k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= -k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= -k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= -k8s.io/kube-aggregator v0.16.9/go.mod h1:Zki0k+m5GSXrMNpTPuaF5MTtuwMNte/JBQ2IDOmY75A= -k8s.io/kube-controller-manager v0.16.9/go.mod h1:PhcH/CYeaMn53OycVUHn9yvtz/n3C0wTF9Zpc/NvSsA= k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf h1:EYm5AW/UUDbnmnI+gK0TJDVK9qPLhM+sRHYanNKw0EQ= k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= -k8s.io/kube-proxy v0.16.9/go.mod h1:UOKCVRn6vgVgjUhV0v/vFdxcv07aIeKH0JyZM9Tli6w= -k8s.io/kube-scheduler v0.16.9/go.mod h1:mDruQFpyAyhsCC0/vZBqGjwp0oyGhSPzkejf9aFH46Q= -k8s.io/kubectl v0.16.9 h1:DBgsfFGf+wQiZyz/Q4gJVxfuNQFR20f/IQ4gj+C4qjU= -k8s.io/kubectl v0.16.9/go.mod h1:FZ8ibvEMKjHC1yfi+vr8eBVX3VpoVOkrcdVJz5e6T3o= -k8s.io/kubelet v0.16.9/go.mod h1:KVj02L3uHVoEDC7buGK7WA/S8b42G8OFbvaYROws+0U= k8s.io/kubernetes v1.14.1 h1:I9F52h5sqVxBmoSsBlNQ0YygNcukDilkpGxUbJRoBoY= k8s.io/kubernetes v1.14.1/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= -k8s.io/legacy-cloud-providers v0.16.9/go.mod h1:BEiLL1gweb+0X4fn2HAQGIFBDOsSAYMcwUk4O9LWn5M= -k8s.io/metrics v0.16.9/go.mod h1:mIG8NlDrZsU1edgU35qlFKP7e4J8snLMXBh5lhR7aL0= -k8s.io/repo-infra v0.0.1-alpha.1/go.mod h1:wO1t9WaB99V80ljbeENTnayuEEwNZt7gECYh/CEyOJ8= -k8s.io/sample-apiserver v0.16.9/go.mod h1:FQx3+vFR9swB9s36sc9dC+IMEMh/OWqw+gODr45KKGE= -k8s.io/utils v0.0.0-20190801114015-581e00157fb1/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= k8s.io/utils v0.0.0-20190907131718-3d4f5b7dea0b h1:eMM0sTvh3KBVGwJfuNcU86P38TJhlVMAICbFPDG3t0M= k8s.io/utils v0.0.0-20190907131718-3d4f5b7dea0b/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= -modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw= -modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= -modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= -modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= -modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I= -mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc= -mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4= -mvdan.cc/unparam v0.0.0-20190209190245-fbb59629db34/go.mod h1:H6SUd1XjIs+qQCyskXg5OFSrilMRUkD8ePJpHKDPaeY= sigs.k8s.io/kustomize v2.0.3+incompatible h1:JUufWFNlI44MdtnjUqVnvh29rR37PQFzPbLXqhyOyX0= sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU= sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= -sigs.k8s.io/structured-merge-diff v1.0.2/go.mod h1:IIgPezJWb76P0hotTxzDbWsMYB8APh18qZnxkomBpxA= sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= -sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= -vbom.ml/util v0.0.0-20160121211510-db5cfe13f5cc h1:MksmcCZQWAQJCTA5T0jgI/0sJ51AVm4Z41MrmfczEoc= -vbom.ml/util v0.0.0-20160121211510-db5cfe13f5cc/go.mod h1:so/NYdZXCz+E3ZpW0uAoCj6uzU2+8OWDFv/HxUSs7kI= vbom.ml/util v0.0.0-20180919145318-efcd4e0f9787 h1:O69FD9pJA4WUZlEwYatBEEkRWKQ5cKodWpdKTrCS/iQ= vbom.ml/util v0.0.0-20180919145318-efcd4e0f9787/go.mod h1:so/NYdZXCz+E3ZpW0uAoCj6uzU2+8OWDFv/HxUSs7kI= diff --git a/src/k8splugin/internal/helm/helm.go b/src/k8splugin/internal/helm/helm.go index 2150758b..d3715fce 100644 --- a/src/k8splugin/internal/helm/helm.go +++ b/src/k8splugin/internal/helm/helm.go @@ -23,6 +23,7 @@ import ( "os" "path/filepath" "regexp" + "sort" "strings" utils "github.com/onap/multicloud-k8s/src/k8splugin/internal" @@ -55,16 +56,17 @@ type Template interface { // TemplateClient implements the Template interface // It will also be used to maintain any localized state type TemplateClient struct { - whitespaceRegex *regexp.Regexp - kubeVersion string - kubeNameSpace string - releaseName string + emptyRegex *regexp.Regexp + kubeVersion string + kubeNameSpace string + releaseName string } // NewTemplateClient returns a new instance of TemplateClient func NewTemplateClient(k8sversion, namespace, releasename string) *TemplateClient { return &TemplateClient{ - whitespaceRegex: regexp.MustCompile(`^\s*$`), + // emptyRegex defines template content that could be considered empty yaml-wise + emptyRegex: regexp.MustCompile(`(?m)\A(^(\s*#.*|\s*)$\n?)*\z`), // defaultKubeVersion is the default value of --kube-version flag kubeVersion: k8sversion, kubeNameSpace: namespace, @@ -209,11 +211,19 @@ func (h *TemplateClient) GenerateKubernetesArtifacts(inputPath string, valueFile continue } rmap := releaseutil.SplitManifests(v) - count := 0 - for _, v1 := range rmap { - key := fmt.Sprintf("%s-%d", k, count) - newRenderedTemplates[key] = v1 - count = count + 1 + + // Iterating over map can yield different order at times + // so first we'll sort keys + sortedKeys := make([]string, len(rmap)) + for k1, _ := range rmap { + sortedKeys = append(sortedKeys, k1) + } + // This makes empty files have the lowest indices + sort.Strings(sortedKeys) + + for k1, v1 := range sortedKeys { + key := fmt.Sprintf("%s-%d", k, k1) + newRenderedTemplates[key] = rmap[v1] } } @@ -232,7 +242,7 @@ func (h *TemplateClient) GenerateKubernetesArtifacts(inputPath string, valueFile } // blank template after execution - if h.whitespaceRegex.MatchString(data) { + if h.emptyRegex.MatchString(data) { continue } @@ -260,7 +270,7 @@ func (h *TemplateClient) GenerateKubernetesArtifacts(inputPath string, valueFile func getGroupVersionKind(data string) (schema.GroupVersionKind, error) { out, err := k8syaml.ToJSON([]byte(data)) if err != nil { - return schema.GroupVersionKind{}, pkgerrors.Wrap(err, "Converting yaml to json") + return schema.GroupVersionKind{}, pkgerrors.Wrap(err, "Converting yaml to json:\n"+data) } simpleMeta := json.SimpleMetaFactory{} diff --git a/src/k8splugin/internal/helm/helm_test.go b/src/k8splugin/internal/helm/helm_test.go index 1e676c52..d25ca091 100644 --- a/src/k8splugin/internal/helm/helm_test.go +++ b/src/k8splugin/internal/helm/helm_test.go @@ -1,5 +1,6 @@ /* * Copyright 2018 Intel Corporation, Inc + * Copyright 2020,2021 Samsung Electronics, Modifications * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -155,6 +156,43 @@ func TestGenerateKubernetesArtifacts(t *testing.T) { }, expectedError: "", }, + { + label: "Generate artifacts from multi-template and empty files v1", + chartPath: "../../mock_files/mock_charts/testchart3", + valueFiles: []string{}, + values: []string{ + "goingEmpty=false", + }, + expectedHashMap: map[string]string{ + "testchart3/templates/multi.yaml-2": "e24cbbefac2c2f700880b8fd041838f2dd48bbc1e099e7c1d2485ae7feb3da0d", + "testchart3/templates/multi.yaml-3": "592a8e5b2c35b8469aa45703a835bc00657bfe36b51eb08427a46e7d22fb1525", + }, + expectedError: "", + }, + { + label: "Generate artifacts from multi-template and empty files v2", + chartPath: "../../mock_files/mock_charts/testchart3", + valueFiles: []string{}, + values: []string{ + "goingEmpty=true", + }, + expectedHashMap: map[string]string{ + "testchart3/templates/multi.yaml-3": "e24cbbefac2c2f700880b8fd041838f2dd48bbc1e099e7c1d2485ae7feb3da0d", + "testchart3/templates/multi.yaml-4": "0bea01e65148584609ede5000c024241ba1c35b440b32ec0a4f7013015715bfe", + "testchart3/templates/multi.yaml-5": "6a5af22538c273b9d4a3156e3b6bb538c655041eae31e93db21a9e178f73ecf0", + }, + expectedError: "", + }, + { + label: "Test simple v3 helm charts support", + chartPath: "../../mock_files/mock_charts/mockv3", + valueFiles: []string{}, + values: []string{}, + expectedError: "", + expectedHashMap: map[string]string{ + "mockv3/templates/deployment.yaml": "259a027a4957e7428eb1d2e774fa1afaa62449521853f8b2916887040bae2ca4", + }, + }, } h := sha256.New() @@ -192,7 +230,7 @@ func TestGenerateKubernetesArtifacts(t *testing.T) { } } if gotHash != expectedHash { - t.Fatalf("Got unexpected hash for %s", f) + t.Fatalf("Got unexpected hash for %s: '%s'; expected: '%s'", f, gotHash, expectedHash) } } } diff --git a/src/k8splugin/mock_files/mock_charts/mockv3/Chart.yaml b/src/k8splugin/mock_files/mock_charts/mockv3/Chart.yaml new file mode 100644 index 00000000..97233399 --- /dev/null +++ b/src/k8splugin/mock_files/mock_charts/mockv3/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: mockv3 +description: A Helm chart for Kubernetes +type: application +version: 0.1.0 +appVersion: 1.16.0 diff --git a/src/k8splugin/mock_files/mock_charts/mockv3/templates/deployment.yaml b/src/k8splugin/mock_files/mock_charts/mockv3/templates/deployment.yaml new file mode 100644 index 00000000..b901e337 --- /dev/null +++ b/src/k8splugin/mock_files/mock_charts/mockv3/templates/deployment.yaml @@ -0,0 +1,32 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: dummy + labels: + dummy: yes +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + dummy: yes + template: + metadata: + labels: + dummy: yes + spec: + containers: + - name: {{ .Chart.Name }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: 80 + protocol: TCP + livenessProbe: + httpGet: + path: / + port: http + readinessProbe: + httpGet: + path: / + port: http diff --git a/src/k8splugin/mock_files/mock_charts/mockv3/values.yaml b/src/k8splugin/mock_files/mock_charts/mockv3/values.yaml new file mode 100644 index 00000000..43850077 --- /dev/null +++ b/src/k8splugin/mock_files/mock_charts/mockv3/values.yaml @@ -0,0 +1,6 @@ +replicaCount: 1 + +image: + repository: nginx + pullPolicy: IfNotPresent + tag: "" diff --git a/src/k8splugin/mock_files/mock_charts/testchart3/Chart.yaml b/src/k8splugin/mock_files/mock_charts/testchart3/Chart.yaml new file mode 100644 index 00000000..adf4e2fe --- /dev/null +++ b/src/k8splugin/mock_files/mock_charts/testchart3/Chart.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +description: A Helm chart for Kubernetes +name: testchart3 +version: 0.1.0 diff --git a/src/k8splugin/mock_files/mock_charts/testchart3/templates/always-empty.yaml b/src/k8splugin/mock_files/mock_charts/testchart3/templates/always-empty.yaml new file mode 100644 index 00000000..121130fc --- /dev/null +++ b/src/k8splugin/mock_files/mock_charts/testchart3/templates/always-empty.yaml @@ -0,0 +1,9 @@ +--- +{{ if eq 0 1 }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: dummy +data: + key1: value1 +{{ end }} diff --git a/src/k8splugin/mock_files/mock_charts/testchart3/templates/multi.yaml b/src/k8splugin/mock_files/mock_charts/testchart3/templates/multi.yaml new file mode 100644 index 00000000..0539cfb4 --- /dev/null +++ b/src/k8splugin/mock_files/mock_charts/testchart3/templates/multi.yaml @@ -0,0 +1,34 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: dummy +data: + key1: value1 +--- +{{ if .Values.goingEmpty }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: dummy +spec: + template: + metadata: + labels: + app: dummy + spec: + container: + - name: dummy + image: dummy +{{ end }} +--- +apiVersion: v1 +kind: Service +metadata: + name: dummy +spec: + ports: + - port: 80 + protocol: TCP + selector: + app: dummy diff --git a/src/k8splugin/mock_files/mock_charts/testchart3/templates/only-comment.yaml b/src/k8splugin/mock_files/mock_charts/testchart3/templates/only-comment.yaml new file mode 100644 index 00000000..aaacc787 --- /dev/null +++ b/src/k8splugin/mock_files/mock_charts/testchart3/templates/only-comment.yaml @@ -0,0 +1,6 @@ +#not so empty? +#Copyright or something similiar +#Some license info +{{/* + empty +*/}} diff --git a/src/k8splugin/mock_files/mock_charts/testchart3/values.yaml b/src/k8splugin/mock_files/mock_charts/testchart3/values.yaml new file mode 100644 index 00000000..912d0dbc --- /dev/null +++ b/src/k8splugin/mock_files/mock_charts/testchart3/values.yaml @@ -0,0 +1 @@ +goingEmpty: true diff --git a/src/tools/emcoui/Dockerfile b/src/tools/emcoui/Dockerfile index 6f0cc2fa..8224d92c 100644 --- a/src/tools/emcoui/Dockerfile +++ b/src/tools/emcoui/Dockerfile @@ -1,3 +1,18 @@ +#======================================================================= +# Copyright (c) 2017-2020 Aarna Networks, Inc. +# All rights reserved. +# ====================================================================== +# 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. +# ======================================================================== + # => Build container FROM node:alpine as builder WORKDIR /app @@ -7,11 +22,14 @@ RUN npm install COPY src ./src COPY public ./public # => Pass the reuired version -RUN REACT_APP_VERSION=v1.0.0 npm run build +RUN REACT_APP_VERSION=v1.2.0 npm run build # => Run container FROM nginx:1.15.2-alpine +# Nginx config +COPY default.conf /etc/nginx/conf.d/ + # Static build COPY --from=builder /app/build /usr/share/nginx/html/ diff --git a/src/tools/emcoui/README.md b/src/tools/emcoui/README.md index 05a43237..945dde0c 100644 --- a/src/tools/emcoui/README.md +++ b/src/tools/emcoui/README.md @@ -1,30 +1,11 @@ -# EMCOUI - -This is a web app for EMCO V2 api's. This is a reactjs based UI app created using google material ui library. - ## Local setup -for running the app in a local setup first install the dependencies by running - -```bash -npm install -``` - -Then run - -```bash -startup.sh -``` +for running the app in a local setup first install the dependencies by running `npm install`. +Then run `startup.sh` ## Production build -for creating a production build, run - -```bash -npm run build -``` - -A production ready build will be available at /build directory +for creating a production build, run `npm run build`. A production ready build will be available at /build directory ## Available scripts @@ -48,17 +29,4 @@ The build is minified and the filenames include the hashes.<br /> ## Building docker image To build a docker image run the below command - -```bash -docker build -t <image_name>:<image_version> . -``` - -## Installing with helm - -All the helm chars are in `helm` directory. - -Update values.yaml in `helm` directory with the required emcoui image and then run - -```bash -helm install --name <app name> --namespace < namespace > -``` +`docker build -t image_name:version .` diff --git a/src/tools/emcoui/default.conf b/src/tools/emcoui/default.conf new file mode 100644 index 00000000..4387a613 --- /dev/null +++ b/src/tools/emcoui/default.conf @@ -0,0 +1,34 @@ +server { + listen 9080; + server_name localhost; + location / { + root /usr/share/nginx/html; + index index.html; + try_files $uri $uri/ /index.html; + } + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } + + location /middleend { + proxy_pass http://middleend.onap4k8s.svc.cluster.local:9081; + } + location /v2/controllers { + proxy_pass http://orchestrator.onap4k8s.svc.cluster.local:9015; + } + location /v2/projects { + proxy_pass http://orchestrator.onap4k8s.svc.cluster.local:9015; + } + location /v2/cluster-providers { + proxy_pass http://clm.onap4k8s.svc.cluster.local:9061; + } + location /v2/ovnaction { + rewrite ^/v2/ovnaction/(.*) /v2/projects/$1 break; + proxy_pass http://ovnaction.onap4k8s.svc.cluster.local:9051; + } + location /v2/ncm { + rewrite ^/v2/ncm/(.*) /v2/cluster-providers/$1 break; + proxy_pass http://ncm.onap4k8s.svc.cluster.local:9031; + } +} diff --git a/src/tools/emcoui/helm/emcoui/templates/configmap.yaml b/src/tools/emcoui/helm/emcoui/templates/configmap.yaml index a9ba34a5..b62f35dc 100644 --- a/src/tools/emcoui/helm/emcoui/templates/configmap.yaml +++ b/src/tools/emcoui/helm/emcoui/templates/configmap.yaml @@ -11,7 +11,27 @@ # 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. -# ======================================================================== +# ======================================================================== +# middleend config +apiVersion: v1 +kind: ConfigMap +metadata: + name: middleend-config +data: + middleend.conf: |- + { + "ownport": "{{ .Values.middleend.service.internalPort }}", + "orchestrator": "orchestrator.{{ .Values.namespace }}.svc.cluster.local:9015", + "clm": "clm.{{ .Values.namespace }}.svc.cluster.local:9061", + "ovnaction": "ovnaction.{{ .Values.namespace }}.svc.cluster.local:9051", + "issuer": "{{ .Values.authproxy.issuer }}", + "redirect_uri": "{{ .Values.authproxy.redirect_uri }}", + "client_id": "{{ .Values.authproxy.client_id }}", + "mongo": "mongo.{{ .Values.namespace }}.svc.cluster.local:27017" + } + +--- +# emcoui config apiVersion: v1 kind: ConfigMap metadata: @@ -19,7 +39,7 @@ metadata: data: my-nginx-config.conf: | server { - listen {{ .Values.service.internalPort }}; + listen {{ .Values.emcoui.service.internalPort }}; server_name localhost; location / { root /usr/share/nginx/html; @@ -30,6 +50,9 @@ data: location = /50x.html { root /usr/share/nginx/html; } + location /middleend { + proxy_pass http://middleend.{{ .Values.namespace }}.svc.cluster.local:9081; + } location /v2/controllers { proxy_pass http://orchestrator.{{ .Values.namespace }}.svc.cluster.local:9015; } diff --git a/src/tools/emcoui/helm/emcoui/templates/deployment.yaml b/src/tools/emcoui/helm/emcoui/templates/deployment.yaml index 11ab6f52..f1609cd3 100644 --- a/src/tools/emcoui/helm/emcoui/templates/deployment.yaml +++ b/src/tools/emcoui/helm/emcoui/templates/deployment.yaml @@ -12,27 +12,58 @@ # See the License for the specific language governing permissions and # limitations under the License. # ======================================================================== +# middleend Deployment +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Values.middleend.service.name }} +spec: + replicas: 1 + selector: + matchLabels: + app: {{ .Values.middleend.service.label }} + template: + metadata: + labels: + app: {{ .Values.middleend.service.label }} + spec: + containers: + - name: {{ .Values.middleend.service.name }} + image: "{{ .Values.middleend.image.repository }}:{{ .Values.middleend.image.tag }}" + imagePullPolicy: Always + ports: + - containerPort: {{ .Values.middleend.service.internalPort }} + volumeMounts: + - mountPath: /opt/emco/config + readOnly: true + name: config + volumes: + - name: config + configMap: + name: middleend-config + +--- # GUI Deployment apiVersion: apps/v1 kind: Deployment metadata: - name: {{ .Values.service.name }} + name: {{ .Values.emcoui.service.name }} spec: replicas: 1 selector: matchLabels: - app: {{ .Values.service.label }} + app: {{ .Values.emcoui.service.label }} template: metadata: labels: - app: {{ .Values.service.label }} + app: {{ .Values.emcoui.service.label }} spec: containers: - - name: {{ .Values.service.name }} - image: {{ .Values.image }} + - name: {{ .Values.emcoui.service.name }} + image: "{{ .Values.emcoui.image.repository }}:{{ .Values.emcoui.image.tag }}" imagePullPolicy: Always ports: - - containerPort: {{ .Values.service.internalPort }} + - containerPort: {{ .Values.emcoui.service.internalPort }} volumeMounts: - mountPath: /etc/nginx/conf.d readOnly: true diff --git a/src/tools/emcoui/helm/emcoui/templates/service.yaml b/src/tools/emcoui/helm/emcoui/templates/service.yaml index 2c09a7de..9c88b8f7 100644 --- a/src/tools/emcoui/helm/emcoui/templates/service.yaml +++ b/src/tools/emcoui/helm/emcoui/templates/service.yaml @@ -12,24 +12,48 @@ # See the License for the specific language governing permissions and # limitations under the License. # ======================================================================== -# GUI Service +# middleend Service apiVersion: v1 kind: Service metadata: - name: {{ .Values.service.name}} + name: {{ .Values.middleend.service.name}} labels: - app: {{ .Values.service.label }} + app: {{ .Values.middleend.service.label }} spec: selector: - app: {{ .Values.service.name }} - type: {{ .Values.service.type }} + app: {{ .Values.middleend.service.name }} + type: {{ .Values.middleend.service.type }} ports: - - name: {{ .Values.service.PortName }} - {{if eq .Values.service.type "NodePort" -}} - port: {{ .Values.service.internalPort }} - nodePort: {{ .Values.global.nodePortPrefixExt | default "302" }}{{ .Values.service.nodePort }} + - name: {{ .Values.middleend.service.PortName }} + {{if eq .Values.middleend.service.type "NodePort" -}} + port: {{ .Values.middleend.service.internalPort }} + nodePort: {{ .Values.global.nodePortPrefixExt | default "302" }}{{ .Values.middleend.service.nodePort }} {{- else -}} - port: {{ .Values.service.externalPort }} - targetPort: {{ .Values.service.internalPort }} + port: {{ .Values.middleend.externalPort }} + targetPort: {{ .Values.middleend.internalPort }} + {{- end}} + protocol: TCP + + +--- +# emcoui service +apiVersion: v1 +kind: Service +metadata: + name: {{ .Values.emcoui.service.name}} + labels: + app: {{ .Values.emcoui.service.label }} +spec: + selector: + app: {{ .Values.emcoui.service.name }} + type: {{ .Values.emcoui.service.type }} + ports: + - name: {{ .Values.emcoui.service.PortName }} + {{if eq .Values.emcoui.service.type "NodePort" -}} + port: {{ .Values.emcoui.service.internalPort }} + nodePort: {{ .Values.global.nodePortPrefixExt | default "302" }}{{ .Values.emcoui.service.nodePort }} + {{- else -}} + port: {{ .Values.emcoui.service.externalPort }} + targetPort: {{ .Values.emcoui.service.internalPort }} {{- end}} protocol: TCP diff --git a/src/tools/emcoui/helm/emcoui/values.yaml b/src/tools/emcoui/helm/emcoui/values.yaml index f52c0719..d764f722 100644 --- a/src/tools/emcoui/helm/emcoui/values.yaml +++ b/src/tools/emcoui/helm/emcoui/values.yaml @@ -11,7 +11,7 @@ # 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. -# ======================================================================== +# ======================================================================== global: nodePortPrefixExt: 304 @@ -19,11 +19,6 @@ global: ################################################################# # Application configuration defaults. ################################################################# -# application image -repository: registry.hub.docker.com -image: emcov2/emcoui:stable -pullPolicy: Always - # default number of instances replicaCount: 1 @@ -44,14 +39,43 @@ readiness: initialDelaySeconds: 10 periodSeconds: 30 -service: - type: NodePort - name: emcoui - portName: emcoui - internalPort: 9080 - externalPort: 9080 - nodePort: 80 - label: emcoui +middleend: + service: + type: NodePort + name: middleend + portName: middleend + internalPort: 9081 + externalPort: 9081 + nodePort: 81 + label: middleend + + image: + registry: registry.hub.docker.com + repository: amcop/middleend + tag: master + pullPolicy: Always + +emcoui: + service: + type: NodePort + name: emcoui + portName: emcoui + internalPort: 9080 + externalPort: 9080 + nodePort: 80 + label: emcoui + + image: + registry: registry.hub.docker.com + repository: amcop/emcoui + tag: master + pullPolicy: Always + +authproxy: + # These values should be updated at the time of deployment + issuer: http://192.168.122.224:31064/auth/realms/EMCO/ + redirect_uri: http://192.168.122.224:30481/middleend/callback + client_id: emcoapp ingress: enabled: false diff --git a/src/tools/emcoui/middle_end/Dockerfile b/src/tools/emcoui/middle_end/Dockerfile new file mode 100644 index 00000000..4e35322c --- /dev/null +++ b/src/tools/emcoui/middle_end/Dockerfile @@ -0,0 +1,32 @@ +#======================================================================= +# Copyright (c) 2017-2020 Aarna Networks, Inc. +# All rights reserved. +# ====================================================================== +# 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. +# ======================================================================== + +FROM golang:1.14.1 + +# Set the Current Working Directory inside the container +WORKDIR /src +COPY ./ ./ +RUN make all + +# Build the Go app +FROM ubuntu:16.04 +WORKDIR /opt/emco +RUN groupadd -r emco && useradd -r -g emco emco +RUN chown emco:emco /opt/emco -R +RUN mkdir ./config +COPY --chown=emco --from=0 /src/middleend ./ + +# Command to run the executable +CMD ["./middleend"] diff --git a/src/tools/emcoui/middle_end/Makefile b/src/tools/emcoui/middle_end/Makefile new file mode 100644 index 00000000..a44fc785 --- /dev/null +++ b/src/tools/emcoui/middle_end/Makefile @@ -0,0 +1,24 @@ +#======================================================================= +# Copyright (c) 2017-2020 Aarna Networks, Inc. +# All rights reserved. +# ====================================================================== +# 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. +# ======================================================================== + +export GO111MODULE=on + +all: clean + CGO_ENABLED=1 GOOS=linux GOARCH=amd64 + @go build -tags netgo -o ./middleend ./main/main.go + +clean: + @find . -name "*so" -delete + @rm -f middleend diff --git a/src/tools/emcoui/middle_end/app/app.go b/src/tools/emcoui/middle_end/app/app.go new file mode 100644 index 00000000..a8511698 --- /dev/null +++ b/src/tools/emcoui/middle_end/app/app.go @@ -0,0 +1,1014 @@ +/* +======================================================================= +Copyright (c) 2017-2020 Aarna Networks, Inc. +All rights reserved. +====================================================================== +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 app + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "log" + "mime/multipart" + "net/http" + + "github.com/gorilla/mux" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/clientcmd" +) + +type deployServiceData struct { + Name string `json:"name"` + Description string `json:"description"` + Spec struct { + ProjectName string `json:"projectName"` + Apps []appsData `json:"appsData"` + } `json:"spec"` +} + +type deployDigData struct { + Name string `json:"name"` + Description string `json:"description"` + CompositeAppName string `json:"compositeApp"` + CompositeProfile string `json:"compositeProfile"` + DigVersion string `json:"version"` + CompositeAppVersion string `json:"compositeAppVersion"` + Spec struct { + ProjectName string `json:"projectName"` + Apps []appsData `json:"appsData"` + } `json:"spec"` +} + +// Exists is for mongo $exists filter +type Exists struct { + Exists string `json:"$exists"` +} + +// This is the json payload that the orchesration API expexts. +type appsData struct { + Metadata struct { + Name string `json:"name"` + Description string `json:"description"` + FileName string `json:"filename"` + } `json:"metadata"` + ProfileMetadata struct { + Name string `json:"name"` + FileName string `json:"filename"` + } `json:"profileMetadata"` + Clusters []struct { + Provider string `json:"provider"` + SelectedClusters []struct { + Name string `json:"name"` + Interfaces []struct { + NetworkName string `json:"networkName"` + IP string `json:"ip"` + Subnet string `json:"subnet"` + } `json:"interfaces"` + } `json:"selectedClusters"` + } `json:"clusters"` +} + +type DigsInProject struct { + Metadata struct { + Name string `json:"name"` + CompositeAppName string `json:"compositeAppName"` + CompositeAppVersion string `json:"compositeAppVersion"` + Description string `json:"description"` + UserData1 string `userData1:"userData1"` + UserData2 string `userData2:"userData2"` + } `json:"metadata"` + Spec struct { + DigIntentsData []DigDeployedIntents `json:"deployedIntents"` + Profile string `json:"profile"` + Version string `json:"version"` + Lcloud string `json:"logicalCloud"` + OverrideValuesObj []OverrideValues `json:"overrideValues"` + GpintArray []*DigsGpint `json:"GenericPlacementIntents,omitempty"` + NwintArray []*DigsNwint `json:"networkCtlIntents,omitempty"` + } `json:"spec"` +} + +type DigsGpint struct { + Metadata apiMetaData `json:"metadata,omitempty"` + Spec struct { + AppIntentArray []PlacementIntentExport `json:"placementIntent,omitempty"` + } `json:"spec,omitempty"` +} + +type DigsNwint struct { + Metadata apiMetaData `json:"metadata,omitempty"` + Spec struct { + WorkloadIntentsArray []*WorkloadIntents `json:"WorkloadIntents,omitempty"` + } `json:"spec,omitempty"` +} +type WorkloadIntents struct { + Metadata apiMetaData `json:"metadata,omitempty"` + Spec struct { + Interfaces []NwInterface `json:"interfaces,omitempty"` + } `json:"spec,omitempty"` +} + +// Project Tree +type ProjectTree struct { + Metadata ProjectMetadata + compositeAppMap map[string]*CompositeAppTree +} + +type treeTraverseFilter struct { + compositeAppName string + compositeAppVersion string + digName string +} + +// Composite app tree +type CompositeAppTree struct { + Metadata CompositeApp + AppsDataArray map[string]*AppsData + ProfileDataArray map[string]*ProfilesData + DigMap map[string]*DigReadData +} + +type DigReadData struct { + DigpData DeploymentIGP + DigIntentsData DigpIntents + GpintMap map[string]*GpintData + NwintMap map[string]*NwintData +} + +type GpintData struct { + Gpint GenericPlacementIntent + AppIntentArray []PlacementIntent +} + +type NwintData struct { + Nwint NetworkCtlIntent + WrkintMap map[string]*WrkintData +} + +type WrkintData struct { + Wrkint NetworkWlIntent + Interfaces []NwInterface +} + +type AppsData struct { + App CompositeApp + CompositeProfile ProfileMeta +} + +type ProfilesData struct { + Profile ProfileMeta + AppProfiles []ProfileMeta +} + +type ClusterMetadata struct { + Metadata apiMetaData `json:"Metadata"` +} + +type apiMetaData struct { + Name string `json:"name"` + Description string `json:"description"` + UserData1 string `userData1:"userData1"` + UserData2 string `userData2:"userData2"` +} + +// The interface +type orchWorkflow interface { + createAnchor() interface{} + createObject() interface{} + getObject() (interface{}, interface{}) + getAnchor() (interface{}, interface{}) + deleteObject() interface{} + deleteAnchor() interface{} +} + +// MiddleendConfig The configmap of the middleent +type MiddleendConfig struct { + OwnPort string `json:"ownport"` + Clm string `json:"clm"` + OrchService string `json:"orchestrator"` + OvnService string `json:"ovnaction"` + Mongo string `json:"mongo"` +} + +// OrchestrationHandler interface, handling the composite app APIs +type OrchestrationHandler struct { + MiddleendConf MiddleendConfig + client http.Client + compositeAppName string + compositeAppDesc string + AppName string + meta []appsData + DigData deployDigData + file map[string]*multipart.FileHeader + dataRead *ProjectTree + treeFilter *treeTraverseFilter + DigpReturnJson []DigsInProject + projectName string + projectDesc string + version string + response struct { + payload map[string][]byte + status map[string]int + } + digpIntents map[string]string + nwCtlIntents map[string]string +} + +// NewAppHandler interface implementing REST callhandler +func NewAppHandler() *OrchestrationHandler { + return &OrchestrationHandler{} +} + +// GetHealth to check connectivity +func (h OrchestrationHandler) GetHealth(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + +} + +func (h OrchestrationHandler) apiGet(url string, statusKey string) (interface{}, []byte, error) { + // prepare and DEL API + request, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, nil, err + } + resp, err := h.client.Do(request) + if err != nil { + return nil, nil, err + } + defer resp.Body.Close() + + // Prepare the response + data, _ := ioutil.ReadAll(resp.Body) + h.response.payload[statusKey] = data + h.response.status[statusKey] = resp.StatusCode + + return resp.StatusCode, data, nil +} + +func (h OrchestrationHandler) apiDel(url string, statusKey string) (interface{}, error) { + // prepare and DEL API + request, err := http.NewRequest("DELETE", url, nil) + if err != nil { + return nil, err + } + resp, err := h.client.Do(request) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + // Prepare the response + data, _ := ioutil.ReadAll(resp.Body) + h.response.payload[statusKey] = data + h.response.status[statusKey] = resp.StatusCode + + return resp.StatusCode, nil +} + +func (h OrchestrationHandler) apiPost(jsonLoad []byte, url string, statusKey string) (interface{}, error) { + // prepare and POST API + request, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonLoad)) + if err != nil { + return nil, err + } + resp, err := h.client.Do(request) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + // Prepare the response + data, _ := ioutil.ReadAll(resp.Body) + h.response.payload[statusKey] = data + h.response.status[statusKey] = resp.StatusCode + + return resp.StatusCode, nil +} + +func (h OrchestrationHandler) apiPostMultipart(jsonLoad []byte, + fh *multipart.FileHeader, url string, statusKey string, fileName string) (interface{}, error) { + // Open the file + file, err := fh.Open() + if err != nil { + return nil, err + } + // Close the file later + defer file.Close() + // Buffer to store our request body as bytes + var requestBody bytes.Buffer + // Create a multipart writer + multiPartWriter := multipart.NewWriter(&requestBody) + // Initialize the file field. Arguments are the field name and file name + // It returns io.Writer + fileWriter, err := multiPartWriter.CreateFormFile("file", fileName) + if err != nil { + return nil, err + } + // Copy the actual file content to the field field's writer + _, err = io.Copy(fileWriter, file) + if err != nil { + return nil, err + } + // Populate other fields + fieldWriter, err := multiPartWriter.CreateFormField("metadata") + if err != nil { + return nil, err + } + + _, err = fieldWriter.Write([]byte(jsonLoad)) + if err != nil { + return nil, err + } + + // We completed adding the file and the fields, let's close the multipart writer + // So it writes the ending boundary + multiPartWriter.Close() + + // By now our original request body should have been populated, + // so let's just use it with our custom request + req, err := http.NewRequest("POST", url, &requestBody) + if err != nil { + return nil, err + } + // We need to set the content type from the writer, it includes necessary boundary as well + req.Header.Set("Content-Type", multiPartWriter.FormDataContentType()) + + // Do the request + resp, err := h.client.Do(req) + if err != nil { + log.Fatalln(err) + return nil, err + } + defer resp.Body.Close() + // Prepare the response + data, _ := ioutil.ReadAll(resp.Body) + h.response.payload[statusKey] = data + h.response.status[statusKey] = resp.StatusCode + + return resp.StatusCode, nil +} +func (h *OrchestrationHandler) prepTreeReq(vars map[string]string) { + // Initialise the project tree with target composite application. + h.treeFilter = &treeTraverseFilter{} + h.treeFilter.compositeAppName = vars["composite-app-name"] + h.treeFilter.compositeAppVersion = vars["version"] + h.treeFilter.digName = vars["deployment-intent-group-name"] +} + +func (h *OrchestrationHandler) DelDig(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + h.projectName = vars["project-name"] + h.treeFilter = nil + + dataPoints := []string{"projectHandler", "compAppHandler", + "digpHandler", + "placementIntentHandler", + "networkIntentHandler"} + h.response.status = make(map[string]int) + h.response.payload = make(map[string][]byte) + + // Initialise the project tree with target composite application. + h.prepTreeReq(vars) + + h.dataRead = &ProjectTree{} + retcode := h.constructTree(dataPoints) + if retcode != nil { + if intval, ok := retcode.(int); ok { + w.WriteHeader(intval) + } else { + w.WriteHeader(500) + } + return + } + + // 1. Call DIG delte workflow + fmt.Printf("Delete wflow start") + deleteDataPoints := []string{"networkIntentHandler", + "placementIntentHandler", + "digpHandler"} + retcode = h.deleteTree(deleteDataPoints) + if retcode != nil { + if intval, ok := retcode.(int); ok { + w.WriteHeader(intval) + } else { + w.WriteHeader(500) + } + return + } + w.WriteHeader(204) +} + +// Delete service workflow +func (h *OrchestrationHandler) DelSvc(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + h.projectName = vars["project-name"] + h.treeFilter = nil + + dataPoints := []string{"projectHandler", "compAppHandler", + "digpHandler", + "ProfileHandler"} + h.response.status = make(map[string]int) + h.response.payload = make(map[string][]byte) + + // Initialise the project tree with target composite application. + h.prepTreeReq(vars) + + h.dataRead = &ProjectTree{} + retcode := h.constructTree(dataPoints) + if retcode != nil { + if intval, ok := retcode.(int); ok { + w.WriteHeader(intval) + } else { + w.WriteHeader(500) + } + return + } + fmt.Printf("tree %+v\n", h.dataRead) + // Check if a dig is present in this composite application + if len(h.dataRead.compositeAppMap[vars["composite-app-name"]].DigMap) != 0 { + w.WriteHeader(409) + w.Write([]byte("Non emtpy DIG in service\n")) + return + } + + // 1. Call delte workflow + fmt.Printf("Delete wflow start") + deleteDataPoints := []string{"ProfileHandler", + "compAppHandler"} + retcode = h.deleteTree(deleteDataPoints) + if retcode != nil { + if intval, ok := retcode.(int); ok { + w.WriteHeader(intval) + } else { + w.WriteHeader(500) + } + return + } + w.WriteHeader(204) +} + +func (h *OrchestrationHandler) getData(I orchWorkflow) (interface{}, interface{}) { + _, retcode := I.getAnchor() + if retcode != 200 { + return nil, retcode + } + dataPointData, retcode := I.getObject() + if retcode != 200 { + return nil, retcode + } + return dataPointData, retcode +} + +func (h *OrchestrationHandler) deleteData(I orchWorkflow) (interface{}, interface{}) { + _ = I.deleteObject() + _ = I.deleteAnchor() + return nil, 204 //FIXME +} + +func (h *OrchestrationHandler) deleteTree(dataPoints []string) interface{} { + //1. Fetch App data + var I orchWorkflow + for _, dataPoint := range dataPoints { + switch dataPoint { + case "projectHandler": + temp := &projectHandler{} + temp.orchInstance = h + I = temp + _, retcode := h.deleteData(I) + if retcode != 204 { + return retcode + } + break + case "compAppHandler": + temp := &compAppHandler{} + temp.orchInstance = h + I = temp + _, retcode := h.deleteData(I) + if retcode != 204 { + return retcode + } + break + case "ProfileHandler": + temp := &ProfileHandler{} + temp.orchInstance = h + I = temp + _, retcode := h.deleteData(I) + if retcode != 204 { + return retcode + } + break + case "digpHandler": + temp := &digpHandler{} + temp.orchInstance = h + I = temp + fmt.Printf("delete digp\n") + _, retcode := h.deleteData(I) + if retcode != 204 { + return retcode + } + break + case "placementIntentHandler": + temp := &placementIntentHandler{} + temp.orchInstance = h + I = temp + _, retcode := h.deleteData(I) + if retcode != 204 { + return retcode + } + break + case "networkIntentHandler": + temp := &networkIntentHandler{} + temp.orchInstance = h + I = temp + _, retcode := h.deleteData(I) + if retcode != 204 { + return retcode + } + break + default: + fmt.Printf("%s\n", dataPoint) + } + } + return nil +} + +func (h *OrchestrationHandler) constructTree(dataPoints []string) interface{} { + //1. Fetch App data + var I orchWorkflow + for _, dataPoint := range dataPoints { + switch dataPoint { + case "projectHandler": + temp := &projectHandler{} + temp.orchInstance = h + I = temp + _, retcode := h.getData(I) + if retcode != 200 { + return retcode + } + break + case "compAppHandler": + temp := &compAppHandler{} + temp.orchInstance = h + I = temp + _, retcode := h.getData(I) + if retcode != 200 { + return retcode + } + break + case "ProfileHandler": + temp := &ProfileHandler{} + temp.orchInstance = h + I = temp + _, retcode := h.getData(I) + if retcode != 200 { + return retcode + } + break + case "digpHandler": + temp := &digpHandler{} + temp.orchInstance = h + I = temp + _, retcode := h.getData(I) + if retcode != 200 { + return retcode + } + break + case "placementIntentHandler": + temp := &placementIntentHandler{} + temp.orchInstance = h + I = temp + _, retcode := h.getData(I) + if retcode != 200 { + return retcode + } + break + case "networkIntentHandler": + temp := &networkIntentHandler{} + temp.orchInstance = h + I = temp + _, retcode := h.getData(I) + if retcode != 200 { + return retcode + } + break + default: + fmt.Printf("%s\n", dataPoint) + } + } + return nil +} + +// This function partest he compositeapp tree read and populates the +// Dig tree +func (h *OrchestrationHandler) copyDigTree() { + dataRead := h.dataRead + h.DigpReturnJson = nil + + for compositeAppName, value := range dataRead.compositeAppMap { + for _, digValue := range dataRead.compositeAppMap[compositeAppName].DigMap { + Dig := DigsInProject{} + SourceDigMetadata := digValue.DigpData.Metadata + + // Copy the metadata + Dig.Metadata.Name = SourceDigMetadata.Name + Dig.Metadata.CompositeAppName = compositeAppName + Dig.Metadata.CompositeAppVersion = value.Metadata.Spec.Version + Dig.Metadata.Description = SourceDigMetadata.Description + Dig.Metadata.UserData1 = SourceDigMetadata.UserData1 + Dig.Metadata.UserData2 = SourceDigMetadata.UserData2 + + // Populate the Spec of dig + SourceDigSpec := digValue.DigpData.Spec + Dig.Spec.DigIntentsData = digValue.DigIntentsData.Intent + Dig.Spec.Profile = SourceDigSpec.Profile + Dig.Spec.Version = SourceDigSpec.Version + Dig.Spec.Lcloud = SourceDigSpec.Lcloud + Dig.Spec.OverrideValuesObj = SourceDigSpec.OverrideValuesObj + + // Pupolate the generic placement intents + SourceGpintMap := digValue.GpintMap + for t, gpintValue := range SourceGpintMap { + fmt.Printf("gpName value %s\n", t) + localGpint := DigsGpint{} + localGpint.Metadata = gpintValue.Gpint.Metadata + //localGpint.Spec.AppIntentArray = gpintValue.AppIntentArray + localGpint.Spec.AppIntentArray = make([]PlacementIntentExport, len(gpintValue.AppIntentArray)) + for k, _ := range gpintValue.AppIntentArray { + localGpint.Spec.AppIntentArray[k].Metadata = gpintValue.AppIntentArray[k].Metadata + localGpint.Spec.AppIntentArray[k].Spec.AppName = + gpintValue.AppIntentArray[k].Spec.AppName + localGpint.Spec.AppIntentArray[k].Spec.Intent.AllofCluster = + make([]AllofExport, len(gpintValue.AppIntentArray[k].Spec.Intent.AllofCluster)) + for i, _ := range gpintValue.AppIntentArray[k].Spec.Intent.AllofCluster { + localGpint.Spec.AppIntentArray[k].Spec.Intent.AllofCluster[i].ProviderName = + gpintValue.AppIntentArray[k].Spec.Intent.AllofCluster[i].ProviderName + localGpint.Spec.AppIntentArray[k].Spec.Intent.AllofCluster[i].ClusterName = + gpintValue.AppIntentArray[k].Spec.Intent.AllofCluster[i].ClusterName + } + } + + Dig.Spec.GpintArray = append(Dig.Spec.GpintArray, &localGpint) + } + // Populate the Nwint intents + SourceNwintMap := digValue.NwintMap + for _, nwintValue := range SourceNwintMap { + localNwint := DigsNwint{} + localNwint.Metadata = nwintValue.Nwint.Metadata + for _, wrkintValue := range nwintValue.WrkintMap { + localWrkint := WorkloadIntents{} + localWrkint.Metadata = wrkintValue.Wrkint.Metadata + localWrkint.Spec.Interfaces = wrkintValue.Interfaces + localNwint.Spec.WorkloadIntentsArray = append(localNwint.Spec.WorkloadIntentsArray, + &localWrkint) + } + Dig.Spec.NwintArray = append(Dig.Spec.NwintArray, &localNwint) + } + h.DigpReturnJson = append(h.DigpReturnJson, Dig) + } + } +} + +// GetSvc get the entrire tree under project/<composite app>/<version> +func (h *OrchestrationHandler) GetAllDigs(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + h.version = vars["version"] + h.projectName = vars["project-name"] + h.response.status = make(map[string]int) + h.response.payload = make(map[string][]byte) + dataPoints := []string{"projectHandler", "compAppHandler", + "digpHandler", + "placementIntentHandler", + "networkIntentHandler"} + + h.dataRead = &ProjectTree{} + h.treeFilter = nil + retcode := h.constructTree(dataPoints) + if retcode != nil { + if intval, ok := retcode.(int); ok { + w.WriteHeader(intval) + } else { + w.WriteHeader(500) + } + return + } + // copy dig tree + h.copyDigTree() + retval, _ := json.Marshal(h.DigpReturnJson) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + w.Write(retval) +} + +// GetSvc get the entrire tree under project/<composite app>/<version> +func (h *OrchestrationHandler) GetSvc(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + h.treeFilter = nil + h.compositeAppName = vars["composite-app-name"] + h.version = vars["version"] + h.projectName = vars["project-name"] + h.response.status = make(map[string]int) + h.response.payload = make(map[string][]byte) + + dataPoints := []string{"compAppHandler", "ProfileHandler", + "digpHandler", + "placementIntentHandler", + "networkIntentHandler"} + h.dataRead = &ProjectTree{} + retcode := h.constructTree(dataPoints) + if retcode != nil { + if intval, ok := retcode.(int); ok { + w.WriteHeader(intval) + } else { + w.WriteHeader(500) + } + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) +} + +// CreateApp exported function which creates the composite application +func (h *OrchestrationHandler) CreateDig(w http.ResponseWriter, r *http.Request) { + var jsonData deployDigData + + decoder := json.NewDecoder(r.Body) + err := decoder.Decode(&jsonData) + if err != nil { + log.Printf("Failed to parse json") + log.Fatalln(err) + } + + h.DigData = jsonData + + if len(h.DigData.Spec.Apps) == 0 { + w.WriteHeader(400) + w.Write([]byte("Bad request, no app metadata\n")) + return + } + + h.client = http.Client{} + + // These maps will get populated by the return status and respones of each V2 API + // that is called during the execution of the workflow. + h.response.payload = make(map[string][]byte) + h.response.status = make(map[string]int) + + // 4. Create DIG + h.digpIntents = make(map[string]string) + h.nwCtlIntents = make(map[string]string) + igHandler := &digpHandler{} + igHandler.orchInstance = h + igpStatus := createDInents(igHandler) + if igpStatus != nil { + if intval, ok := igpStatus.(int); ok { + w.WriteHeader(intval) + } else { + w.WriteHeader(500) + } + w.Write(h.response.payload[h.compositeAppName+"_digp"]) + return + } + + // 3. Create intents + intentHandler := &placementIntentHandler{} + intentHandler.orchInstance = h + intentStatus := addPlacementIntent(intentHandler) + if intentStatus != nil { + if intval, ok := intentStatus.(int); ok { + w.WriteHeader(intval) + } else { + w.WriteHeader(500) + } + w.Write(h.response.payload[h.compositeAppName+"_gpint"]) + return + } + + // If the metadata contains network interface request then call the + // network intent related part of the workflow. + if len(h.DigData.Spec.Apps[0].Clusters[0].SelectedClusters[0].Interfaces) != 0 { + nwHandler := &networkIntentHandler{} + nwHandler.orchInstance = h + nwIntentStatus := addNetworkIntent(nwHandler) + if nwIntentStatus != nil { + if intval, ok := nwIntentStatus.(int); ok { + w.WriteHeader(intval) + } else { + w.WriteHeader(500) + } + w.Write(h.response.payload[h.compositeAppName+"_nwctlint"]) + return + } + } + + w.WriteHeader(201) + w.Write(h.response.payload[h.DigData.Name]) +} + +func (h *OrchestrationHandler) CreateApp(w http.ResponseWriter, r *http.Request) { + var jsonData deployServiceData + + err := r.ParseMultipartForm(16777216) + if err != nil { + log.Fatalln(err) + } + + // Populate the multipart.FileHeader MAP. The key will be the + // filename itself. The metadata Map will be keyed on the application + // name. The metadata has a field file name, so later we can parse the metadata + // Map, and fetch the file headers from this file Map with keys as the filename. + h.file = make(map[string]*multipart.FileHeader) + for _, v := range r.MultipartForm.File { + fh := v[0] + h.file[fh.Filename] = fh + } + + jsn := ([]byte(r.FormValue("servicePayload"))) + err = json.Unmarshal(jsn, &jsonData) + if err != nil { + log.Printf("Failed to parse json") + log.Fatalln(err) + } + + h.compositeAppName = jsonData.Name + h.compositeAppDesc = jsonData.Description + h.projectName = jsonData.Spec.ProjectName + h.meta = jsonData.Spec.Apps + + // Sanity check. For each metadata there should be a + // corresponding file in the multipart request. If it + // not found we fail this API call. + for i := range h.meta { + switch { + case h.file[h.meta[i].Metadata.FileName] == nil: + t := fmt.Sprintf("File %s not in request", h.meta[i].Metadata.FileName) + w.WriteHeader(400) + w.Write([]byte(t)) + fmt.Printf("app file not found\n") + return + case h.file[h.meta[i].ProfileMetadata.FileName] == nil: + t := fmt.Sprintf("File %s not in request", h.meta[i].ProfileMetadata.FileName) + w.WriteHeader(400) + w.Write([]byte(t)) + fmt.Printf("profile file not found\n") + return + default: + fmt.Println("Good request") + } + } + + if len(h.meta) == 0 { + w.WriteHeader(400) + w.Write([]byte("Bad request, no app metadata\n")) + return + } + + h.client = http.Client{} + + // These maps will get populated by the return status and respones of each V2 API + // that is called during the execution of the workflow. + h.response.payload = make(map[string][]byte) + h.response.status = make(map[string]int) + + // 1. create the composite application. the compAppHandler implements the + // orchWorkflow interface. + appHandler := &compAppHandler{} + appHandler.orchInstance = h + appStatus := createCompositeapp(appHandler) + if appStatus != nil { + if intval, ok := appStatus.(int); ok { + w.WriteHeader(intval) + } else { + w.WriteHeader(500) + } + w.Write(h.response.payload[h.compositeAppName]) + return + } + + // 2. create the composite application profiles + profileHandler := &ProfileHandler{} + profileHandler.orchInstance = h + profileStatus := createProfile(profileHandler) + if profileStatus != nil { + if intval, ok := profileStatus.(int); ok { + w.WriteHeader(intval) + } else { + w.WriteHeader(500) + } + w.Write(h.response.payload[h.compositeAppName+"_profile"]) + return + } + + w.WriteHeader(201) + w.Write(h.response.payload[h.compositeAppName]) +} + +func (h *OrchestrationHandler) createCluster(filename string, fh *multipart.FileHeader, clusterName string, + jsonData ClusterMetadata) interface{} { + url := "http://" + h.MiddleendConf.Clm + "/v2/cluster-providers/" + clusterName + "/clusters" + + jsonLoad, _ := json.Marshal(jsonData) + + status, err := h.apiPostMultipart(jsonLoad, fh, url, clusterName, filename) + if err != nil { + return err + } + if status != 201 { + return status + } + fmt.Printf("cluster creation %s status %s\n", clusterName, status) + return nil +} + +func (h *OrchestrationHandler) CheckConnection(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + + parse_err := r.ParseMultipartForm(16777216) + if parse_err != nil { + fmt.Printf("multipart error: %s", parse_err.Error()) + w.WriteHeader(500) + return + } + + var fh *multipart.FileHeader + for _, v := range r.MultipartForm.File { + fh = v[0] + } + file, err := fh.Open() + if err != nil { + fmt.Printf("Failed to open the file: %s", err.Error()) + w.WriteHeader(500) + return + } + defer file.Close() + + // Read the kconfig + kubeconfig, _ := ioutil.ReadAll(file) + + jsonData := ClusterMetadata{} + jsn := ([]byte(r.FormValue("metadata"))) + err = json.Unmarshal(jsn, &jsonData) + if err != nil { + fmt.Printf("Failed to parse json") + w.WriteHeader(500) + return + } + fmt.Printf("metadata %+v\n", jsonData) + + // RESTConfigFromKubeConfig is a convenience method to give back + // a restconfig from your kubeconfig bytes. + config, err := clientcmd.RESTConfigFromKubeConfig(kubeconfig) + if err != nil { + fmt.Printf("Error while reading the kubeconfig: %s", err.Error()) + w.WriteHeader(500) + return + } + + // create the clientset + clientset, err := kubernetes.NewForConfig(config) + if err != nil { + fmt.Printf("Failed to create clientset: %s", err.Error()) + w.WriteHeader(500) + return + } + + _, err = clientset.CoreV1().Pods("").List(context.TODO(), metav1.ListOptions{}) + if err != nil { + fmt.Printf("Failed to establish the connection: %s", err.Error()) + w.WriteHeader(403) + w.Write([]byte("Cluster connectivity failed\n")) + return + } + + fmt.Printf("Successfully established the connection\n") + h.client = http.Client{} + h.response.status = make(map[string]int) + h.response.payload = make(map[string][]byte) + + status := h.createCluster(fh.Filename, fh, vars["cluster-provider-name"], jsonData) + if status != nil { + w.WriteHeader(500) + return + } + + w.WriteHeader(200) + w.Write(h.response.payload[vars["cluster-provider-name"]]) + return +} diff --git a/src/tools/emcoui/middle_end/app/compositeapp.go b/src/tools/emcoui/middle_end/app/compositeapp.go new file mode 100644 index 00000000..daae5b00 --- /dev/null +++ b/src/tools/emcoui/middle_end/app/compositeapp.go @@ -0,0 +1,247 @@ +/* +======================================================================= +Copyright (c) 2017-2020 Aarna Networks, Inc. +All rights reserved. +====================================================================== +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 app + +import ( + "encoding/json" + "fmt" +) + +// CompositeApp application structure +type CompositeApp struct { + Metadata apiMetaData `json:"metadata"` + Spec compositeAppSpec `json:"spec"` +} + +type compositeAppSpec struct { + Version string `json:"version"` +} + +// compAppHandler , This implements the orchworkflow interface +type compAppHandler struct { + orchURL string + orchInstance *OrchestrationHandler +} + +// CompositeAppKey is the mongo key to fetch apps in a composite app +type CompositeAppKey struct { + Cname string `json:"compositeapp"` + Project string `json:"project"` + Cversion string `json:"compositeappversion"` + App interface{} `json:"app"` +} + +func (h *compAppHandler) getObject() (interface{}, interface{}) { + orch := h.orchInstance + respcode := 200 + dataRead := h.orchInstance.dataRead + for _, compositeAppValue := range dataRead.compositeAppMap { + compositeAppMetadata := compositeAppValue.Metadata.Metadata + compositeAppSpec := compositeAppValue.Metadata.Spec + h.orchURL = "http://" + orch.MiddleendConf.OrchService + "/v2/projects/" + + orch.projectName + "/composite-apps/" + compositeAppMetadata.Name + + "/" + compositeAppSpec.Version + "/apps" + + respcode, respdata, err := orch.apiGet(h.orchURL, orch.compositeAppName+"_getapps") + if err != nil { + return nil, 500 + } + if respcode != 200 { + return nil, respcode + } + fmt.Printf("Get app status %s\n", respcode) + compositeAppValue.AppsDataArray = make(map[string]*AppsData, len(respdata)) + var appList []CompositeApp + json.Unmarshal(respdata, &appList) + for _, value := range appList { + var appsDataInstance AppsData + appName := value.Metadata.Name + appsDataInstance.App = value + compositeAppValue.AppsDataArray[appName] = &appsDataInstance + } + } + return nil, respcode +} + +func (h *compAppHandler) getAnchor() (interface{}, interface{}) { + orch := h.orchInstance + dataRead := h.orchInstance.dataRead + respcode := 200 + for _, compositeAppValue := range dataRead.compositeAppMap { + compositeAppMetadata := compositeAppValue.Metadata.Metadata + compositeAppSpec := compositeAppValue.Metadata.Spec + h.orchURL = "http://" + orch.MiddleendConf.OrchService + "/v2/projects/" + + orch.projectName + "/composite-apps/" + compositeAppMetadata.Name + + "/" + compositeAppSpec.Version + + respcode, _, err := orch.apiGet(h.orchURL, orch.compositeAppName+"_getcompositeapp") + if err != nil { + return nil, 500 + } + if respcode != 200 { + return nil, respcode + } + fmt.Printf("Get composite App %s\n", respcode) + //json.Unmarshal(respdata, &dataRead.CompositeApp) + } + return nil, respcode +} + +func (h *compAppHandler) deleteObject() interface{} { + orch := h.orchInstance + dataRead := h.orchInstance.dataRead + for _, compositeAppValue := range dataRead.compositeAppMap { + compositeAppMetadata := compositeAppValue.Metadata.Metadata + compositeAppSpec := compositeAppValue.Metadata.Spec + h.orchURL = "http://" + orch.MiddleendConf.OrchService + "/v2/projects/" + + orch.projectName + "/composite-apps/" + compositeAppMetadata.Name + + "/" + compositeAppSpec.Version + appList := compositeAppValue.AppsDataArray + for _, value := range appList { + url := h.orchURL + "/apps/" + value.App.Metadata.Name + fmt.Printf("Delete app %s\n", url) + resp, err := orch.apiDel(url, compositeAppMetadata.Name+"_delapp") + if err != nil { + return err + } + if resp != 204 { + return resp + } + fmt.Printf("Delete app status %s\n", resp) + } + } + return nil +} + +func (h *compAppHandler) deleteAnchor() interface{} { + orch := h.orchInstance + dataRead := h.orchInstance.dataRead + for _, compositeAppValue := range dataRead.compositeAppMap { + compositeAppMetadata := compositeAppValue.Metadata.Metadata + compositeAppSpec := compositeAppValue.Metadata.Spec + h.orchURL = "http://" + orch.MiddleendConf.OrchService + "/v2/projects/" + + orch.projectName + "/composite-apps/" + compositeAppMetadata.Name + + "/" + compositeAppSpec.Version + fmt.Printf("Delete composite app %s\n", h.orchURL) + resp, err := orch.apiDel(h.orchURL, compositeAppMetadata.Name+"_delcompapp") + if err != nil { + return err + } + if resp != 204 { + return resp + } + fmt.Printf("Delete compapp status %s\n", resp) + } + return nil +} + +// CreateAnchor creates the anchor point for composite applications, +// profiles, intents etc. For example Anchor for the composite application +// will create the composite application resource in the the DB, and all apps +// will get created and uploaded under this anchor point. +func (h *compAppHandler) createAnchor() interface{} { + orch := h.orchInstance + + compAppCreate := CompositeApp{ + Metadata: apiMetaData{ + Name: orch.compositeAppName, + Description: orch.compositeAppDesc, + UserData1: "data 1", + UserData2: "data 2"}, + Spec: compositeAppSpec{ + Version: "v1"}, + } + + jsonLoad, _ := json.Marshal(compAppCreate) + tem := CompositeApp{} + json.Unmarshal(jsonLoad, &tem) + h.orchURL = "http://" + orch.MiddleendConf.OrchService + "/v2/projects/" + + orch.projectName + "/composite-apps" + resp, err := orch.apiPost(jsonLoad, h.orchURL, orch.compositeAppName) + if err != nil { + return err + } + if resp != 201 { + return resp + } + orch.version = "v1" + fmt.Printf("compAppHandler resp %s\n", resp) + + return nil +} + +func (h *compAppHandler) createObject() interface{} { + orch := h.orchInstance + for i := range orch.meta { + fileName := orch.meta[i].Metadata.FileName + appName := orch.meta[i].Metadata.Name + appDesc := orch.meta[i].Metadata.Description + + // Upload the application helm chart + fh := orch.file[fileName] + compAppAdd := CompositeApp{ + Metadata: apiMetaData{ + Name: appName, + Description: appDesc, + UserData1: "data 1", + UserData2: "data2"}, + } + url := h.orchURL + "/" + orch.compositeAppName + "/" + orch.version + "/apps" + + jsonLoad, _ := json.Marshal(compAppAdd) + + status, err := orch.apiPostMultipart(jsonLoad, fh, url, appName, fileName) + if err != nil { + return err + } + if status != 201 { + return status + } + fmt.Printf("Composite app %s createObject status %s\n", appName, status) + } + + return nil +} + +func createCompositeapp(I orchWorkflow) interface{} { + // 1. Create the Anchor point + err := I.createAnchor() + if err != nil { + return err + } + // 2. Create the Objects + err = I.createObject() + if err != nil { + return err + } + return nil +} + +func delCompositeapp(I orchWorkflow) interface{} { + // 1. Delete the object + err := I.deleteObject() + if err != nil { + return err + } + // 2. Delete the Anchor + err = I.deleteAnchor() + if err != nil { + return err + } + return nil +} diff --git a/src/tools/emcoui/middle_end/app/digp.go b/src/tools/emcoui/middle_end/app/digp.go new file mode 100644 index 00000000..b4c83e15 --- /dev/null +++ b/src/tools/emcoui/middle_end/app/digp.go @@ -0,0 +1,322 @@ +/* +======================================================================= +Copyright (c) 2017-2020 Aarna Networks, Inc. +All rights reserved. +====================================================================== +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 app + +import ( + "encoding/json" + "fmt" + "log" +) + +type DeploymentIGP struct { + Metadata apiMetaData `json:"metadata"` + Spec DigpSpec `json:"spec"` +} + +type DigpSpec struct { + Profile string `json:"profile"` + Version string `json:"version"` + Lcloud string `json:"logical-cloud"` + OverrideValuesObj []OverrideValues `json:"override-values"` +} + +// OverrideValues ... +type OverrideValues struct { + AppName string `json:"app-name"` + ValuesObj map[string]string `json:"values"` +} + +type IgpIntents struct { + Metadata apiMetaData `json:"metadata"` + Spec AppIntents `json:"spec"` +} + +type AppIntents struct { + Intent map[string]string `json:"intent"` +} + +type DigpIntents struct { + Intent []DigDeployedIntents `json:"intent"` +} +type DigDeployedIntents struct { + GenericPlacementIntent string `json:"genericPlacementIntent"` + Ovnaction string `json:"ovnaction"` +} + +// digpHandler implements the orchworkflow interface +type digpHandler struct { + orchURL string + orchInstance *OrchestrationHandler +} + +func (h *digpHandler) getAnchor() (interface{}, interface{}) { + orch := h.orchInstance + dataRead := h.orchInstance.dataRead + retcode := 200 + for _, compositeAppValue := range dataRead.compositeAppMap { + compositeAppMetadata := compositeAppValue.Metadata.Metadata + compositeAppSpec := compositeAppValue.Metadata.Spec + var digpList []DeploymentIGP + // This for the cases where the dig name is in the URL + if orch.treeFilter != nil && orch.treeFilter.digName != ""{ + temp:=DeploymentIGP{} + h.orchURL = "http://" + orch.MiddleendConf.OrchService + "/v2/projects/" + + orch.projectName + "/composite-apps/" + compositeAppMetadata.Name + + "/" + compositeAppSpec.Version + + "/deployment-intent-groups/" + orch.treeFilter.digName + retcode, retval, err := orch.apiGet(h.orchURL, orch.compositeAppName+"_digp") + fmt.Printf("Get Digp in composite app %s status %d\n", compositeAppMetadata.Name, retcode) + if err != nil { + fmt.Printf("Failed to read digp") + return nil, 500 + } + if retcode != 200 { + fmt.Printf("Failed to read digp") + return nil, retcode + } + json.Unmarshal(retval, &temp) + digpList = append(digpList, temp) + } else { + h.orchURL = "http://" + orch.MiddleendConf.OrchService + "/v2/projects/" + + orch.projectName + "/composite-apps/" + compositeAppMetadata.Name + + "/" + compositeAppSpec.Version + + "/deployment-intent-groups" + retcode, retval, err := orch.apiGet(h.orchURL, orch.compositeAppName+"_digp") + fmt.Printf("Get Digp in composite app %s status %d\n", compositeAppMetadata.Name, retcode) + if err != nil { + fmt.Printf("Failed to read digp") + return nil, 500 + } + if retcode != 200 { + fmt.Printf("Failed to read digp") + return nil, retcode + } + json.Unmarshal(retval, &digpList) + } + + compositeAppValue.DigMap = make(map[string]*DigReadData, len(digpList)) + for _, digpValue := range digpList { + var Dig DigReadData + Dig.DigpData = digpValue + compositeAppValue.DigMap[digpValue.Metadata.Name] = &Dig + } + } + return nil, retcode +} + +func (h *digpHandler) getObject() (interface{}, interface{}) { + orch := h.orchInstance + dataRead := h.orchInstance.dataRead + for _, compositeAppValue := range dataRead.compositeAppMap { + compositeAppMetadata := compositeAppValue.Metadata.Metadata + compositeAppSpec := compositeAppValue.Metadata.Spec + h.orchURL = "http://" + orch.MiddleendConf.OrchService + "/v2/projects/" + + orch.projectName + "/composite-apps/" + compositeAppMetadata.Name + + "/" + compositeAppSpec.Version + + "/deployment-intent-groups/" + digpList := compositeAppValue.DigMap + for digName, digValue := range digpList { + url := h.orchURL + digName + "/intents" + retcode, retval, err := orch.apiGet(url, compositeAppMetadata.Name+"_digpIntents") + fmt.Printf("Get Dig int composite app %s Dig %s status %d \n", orch.compositeAppName, + digName, retcode) + if err != nil { + fmt.Printf("Failed to read digp intents") + return nil, 500 + } + if retcode != 200 { + fmt.Printf("Failed to read digp intents") + return nil, retcode + + } + err = json.Unmarshal(retval, &digValue.DigIntentsData) + if err != nil { + fmt.Printf("Failed to read intents %s\n", err) + } + } + } + return nil, 200 +} + +func (h *digpHandler) deleteObject() interface{} { + orch := h.orchInstance + dataRead := h.orchInstance.dataRead + for _, compositeAppValue := range dataRead.compositeAppMap { + compositeAppMetadata := compositeAppValue.Metadata.Metadata + compositeAppSpec := compositeAppValue.Metadata.Spec + digpList := compositeAppValue.DigMap + h.orchURL = "http://" + orch.MiddleendConf.OrchService + "/v2/projects/" + + orch.projectName + "/composite-apps/" + compositeAppMetadata.Name + + "/" + compositeAppSpec.Version + + "/deployment-intent-groups/" + + for digName, _ := range digpList { + url := h.orchURL + digName + "/intents/PlacementIntent" + fmt.Printf("dlete intents %s\n", url) + resp, err := orch.apiDel(url, orch.compositeAppName+"_deldigintents") + if err != nil { + return err + } + if resp != 204 { + return resp + } + fmt.Printf("Delete dig intents resp %s\n", resp) + } + } + return nil +} + +func (h *digpHandler) deleteAnchor() interface{} { + orch := h.orchInstance + dataRead := h.orchInstance.dataRead + for _, compositeAppValue := range dataRead.compositeAppMap { + compositeAppMetadata := compositeAppValue.Metadata.Metadata + compositeAppSpec := compositeAppValue.Metadata.Spec + digpList := compositeAppValue.DigMap + h.orchURL = "http://" + orch.MiddleendConf.OrchService + "/v2/projects/" + + orch.projectName + "/composite-apps/" + compositeAppMetadata.Name + + "/" + compositeAppSpec.Version + + "/deployment-intent-groups/" + + // loop through all the intents in the dig + for digName, _ := range digpList { + url := h.orchURL + digName + turl := h.orchURL + digName + "/terminate" + fmt.Printf("delete intents %s\n", url) + jsonLoad, _ := json.Marshal("{}") + resp, err := orch.apiPost(jsonLoad, turl, orch.compositeAppName+"_terminatedig") + //Not checking the status of terminate FIXME + resp, err = orch.apiDel(url, orch.compositeAppName+"_deldig") + if err != nil { + return err + } + if resp != 204 { + return resp + } + fmt.Printf("Delete dig resp %s\n", resp) + } + } + return nil +} + +func (h *digpHandler) createAnchor() interface{} { + digData := h.orchInstance.DigData + orch := h.orchInstance + + digp := DeploymentIGP{ + Metadata: apiMetaData{ + Name: digData.Name, + Description: digData.Description, + UserData1: "data 1", + UserData2: "data2"}, + Spec: DigpSpec{ + Profile: digData.CompositeProfile, + Version: digData.DigVersion, + Lcloud: "unused_logical_cloud", + OverrideValuesObj: make([]OverrideValues, len(digData.Spec.Apps)), + }, + } + overrideVals := digp.Spec.OverrideValuesObj + for i, value := range digData.Spec.Apps { + overrideVals[i].ValuesObj = make(map[string]string) + overrideVals[i].AppName = value.Metadata.Name + overrideVals[i].ValuesObj["Values.global.dcaeCollectorIp"] = "1.8.0" + } + + jsonLoad, _ := json.Marshal(digp) + + // POST the generic placement intent + h.orchURL = "http://" + orch.MiddleendConf.OrchService + "/v2/projects/" + digData.Spec.ProjectName + + "/composite-apps/" + digData.CompositeAppName + "/" + digData.CompositeAppVersion + + "/deployment-intent-groups" + resp, err := orch.apiPost(jsonLoad, h.orchURL, digData.Name) + if err != nil { + return err + } + if resp != 201 { + return resp + } + orch.digpIntents["generic-placement-intent"] = digData.CompositeAppName + "_gpint" + orch.nwCtlIntents["network-controller-intent"] = digData.CompositeAppName + "_nwctlint" + fmt.Printf("Deloyment intent group resp %s\n", resp) + + return nil +} + +func (h *digpHandler) createObject() interface{} { + digData := h.orchInstance.DigData + orch := h.orchInstance + intentName := "PlacementIntent" + igp := IgpIntents{ + Metadata: apiMetaData{ + Name: intentName, + Description: "NA", + UserData1: "data 1", + UserData2: "data2"}, + } + if len(digData.Spec.Apps[0].Clusters[0].SelectedClusters[0].Interfaces) != 0 { + igp.Spec.Intent = make(map[string]string) + igp.Spec.Intent["genericPlacementIntent"] = orch.digpIntents["generic-placement-intent"] + igp.Spec.Intent["ovnaction"] = orch.nwCtlIntents["network-controller-intent"] + } else { + igp.Spec.Intent = make(map[string]string) + igp.Spec.Intent["genericPlacementIntent"] = orch.digpIntents["generic-placement-intent"] + } + + url := h.orchURL + "/" + digData.Name + "/intents" + jsonLoad, _ := json.Marshal(igp) + status, err := orch.apiPost(jsonLoad, url, intentName) + fmt.Printf("DIG name req %s", string(jsonLoad)) + if err != nil { + log.Fatalln(err) + } + if status != 201 { + return status + } + fmt.Printf("Placement intent %s status %s %s\n", intentName, status, url) + + return nil +} + +func createDInents(I orchWorkflow) interface{} { + // 1. Create the Anchor point + err := I.createAnchor() + if err != nil { + return err + } + // 2. Create the Objects + err = I.createObject() + if err != nil { + return err + } + return nil +} + +func delDigp(I orchWorkflow) interface{} { + // 1. Delete the object + err := I.deleteObject() + if err != nil { + return err + } + // 2. Delete the Anchor + err = I.deleteAnchor() + if err != nil { + return err + } + return nil +} diff --git a/src/tools/emcoui/middle_end/app/intents.go b/src/tools/emcoui/middle_end/app/intents.go new file mode 100644 index 00000000..992f7b66 --- /dev/null +++ b/src/tools/emcoui/middle_end/app/intents.go @@ -0,0 +1,664 @@ +/* +======================================================================= +Copyright (c) 2017-2020 Aarna Networks, Inc. +All rights reserved. +====================================================================== +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 app + +import ( + "encoding/json" + "fmt" + "log" + "strconv" +) + +type GenericPlacementIntent struct { + Metadata apiMetaData `json:"metadata"` +} + +type PlacementIntent struct { + Metadata apiMetaData `json:"metadata"` + Spec AppPlacementIntentSpec `json:"spec"` +} +type PlacementIntentExport struct { + Metadata apiMetaData `json:"metadata"` + Spec AppPlacementIntentSpecExport `json:"spec"` +} + +// appPlacementIntentSpec is the spec for per app intent +type AppPlacementIntentSpec struct { + AppName string `json:"app-name"` + Intent arrayIntent `json:"intent"` +} +type arrayIntent struct { + AllofCluster []Allof `json:"allof"` +} +type Allof struct { + ProviderName string `json:"provider-name"` + ClusterName string `json:"cluster-name"` +} +type AppPlacementIntentSpecExport struct { + AppName string `json:"appName"` + Intent arrayIntentExport `json:"intent"` +} +type arrayIntentExport struct { + AllofCluster []AllofExport `json:"allof"` +} +type AllofExport struct { + ProviderName string `json:"providerName"` + ClusterName string `json:"clusterName"` +} + +// plamcentIntentHandler implements the orchworkflow interface +type placementIntentHandler struct { + orchURL string + orchInstance *OrchestrationHandler +} + +type NetworkCtlIntent struct { + Metadata apiMetaData `json:"metadata"` +} + +type NetworkWlIntent struct { + Metadata apiMetaData `json:"metadata"` + Spec WorkloadIntentSpec `json:"spec"` +} + +type WorkloadIntentSpec struct { + AppName string `json:"application-name"` + Resource string `json:"workload-resource"` + Type string `json:"type"` +} +type WorkloadIntentSpecExport struct { + AppName string `json:"applicationName"` + Resource string `json:"workloadResource"` + Type string `json:"type"` +} + +type NwInterface struct { + Metadata apiMetaData `json:"metadata"` + Spec InterfaceSpec `json:"spec"` +} + +type InterfaceSpec struct { + Interface string `json:"interface"` + Name string `json:"name"` + DefaultGateway string `json:"defaultGateway"` + IPAddress string `json:"ipAddress"` + MacAddress string `json:"macAddress"` +} + +// networkIntentHandler implements the orchworkflow interface +type networkIntentHandler struct { + ovnURL string + orchInstance *OrchestrationHandler +} + +func (h *placementIntentHandler) getObject() (interface{}, interface{}) { + orch := h.orchInstance + retcode := 200 + dataRead := h.orchInstance.dataRead + for _, compositeAppValue := range dataRead.compositeAppMap { + compositeAppMetadata := compositeAppValue.Metadata.Metadata + compositeAppSpec := compositeAppValue.Metadata.Spec + Dig := compositeAppValue.DigMap + Apps := compositeAppValue.AppsDataArray + for digName, digValue := range Dig { + h.orchURL = "http://" + orch.MiddleendConf.OrchService + "/v2/projects/" + + orch.projectName + "/composite-apps/" + compositeAppMetadata.Name + + "/" + compositeAppSpec.Version + + "/deployment-intent-groups/" + digName + "/generic-placement-intents" + for gpintName, gpintValue := range digValue.GpintMap { + for appName, _ := range Apps { + var appPint PlacementIntent + url := h.orchURL + "/" + gpintName + "/app-intents/" + appName + "_pint" + retcode, retval, err := orch.apiGet(url, compositeAppMetadata.Name+"_getappPint") + fmt.Printf("Get Gpint App intent in Composite app %s dig %s Gpint %s status %s\n", + orch.compositeAppName, digName, gpintName, retcode) + if err != nil { + fmt.Printf("Failed to read app pint\n") + return nil, 500 + } + if retcode != 200 { + fmt.Printf("Failed to read app pint\n") + return nil, 200 + } + err = json.Unmarshal(retval, &appPint) + if err != nil { + fmt.Printf("Failed to unmarshal json %s\n", err) + return nil, 500 + } + gpintValue.AppIntentArray = append(gpintValue.AppIntentArray, appPint) + } + } + } + } + return nil, retcode +} + +func (h *placementIntentHandler) getAnchor() (interface{}, interface{}) { + orch := h.orchInstance + retcode := 200 + dataRead := h.orchInstance.dataRead + for _, compositeAppValue := range dataRead.compositeAppMap { + compositeAppMetadata := compositeAppValue.Metadata.Metadata + compositeAppSpec := compositeAppValue.Metadata.Spec + Dig := compositeAppValue.DigMap + for digName, digValue := range Dig { + var gpintList []GenericPlacementIntent + h.orchURL = "http://" + orch.MiddleendConf.OrchService + "/v2/projects/" + + orch.projectName + "/composite-apps/" + compositeAppMetadata.Name + + "/" + compositeAppSpec.Version + + "/deployment-intent-groups/" + digName + "/generic-placement-intents" + retcode, retval, err := orch.apiGet(h.orchURL, compositeAppMetadata.Name+"_getgpint") + fmt.Printf("Get Gpint in Composite app %s dig %s status %s\n", orch.compositeAppName, + digName, retcode) + if err != nil { + fmt.Printf("Failed to read gpint\n") + return nil, 500 + } + if retcode != 200 { + fmt.Printf("Failed to read gpint\n") + return nil, retcode + } + json.Unmarshal(retval, &gpintList) + digValue.GpintMap = make(map[string]*GpintData, len(gpintList)) + for _, value := range gpintList { + var GpintDataInstance GpintData + GpintDataInstance.Gpint = value + digValue.GpintMap[value.Metadata.Name] = &GpintDataInstance + } + } + } + return nil, retcode +} + +func (h *placementIntentHandler) deleteObject() interface{} { + orch := h.orchInstance + dataRead := h.orchInstance.dataRead + for _, compositeAppValue := range dataRead.compositeAppMap { + compositeAppMetadata := compositeAppValue.Metadata.Metadata + compositeAppSpec := compositeAppValue.Metadata.Spec + Dig := compositeAppValue.DigMap + Apps := compositeAppValue.AppsDataArray + + // loop through all app intens in the gpint + for digName, digValue := range Dig { + h.orchURL = "http://" + orch.MiddleendConf.OrchService + "/v2/projects/" + + orch.projectName + "/composite-apps/" + compositeAppMetadata.Name + + "/" + compositeAppSpec.Version + + "/deployment-intent-groups/" + digName + "/generic-placement-intents/" + for gpintName, _ := range digValue.GpintMap { + for appName, _ := range Apps { + url := h.orchURL + gpintName + + "/app-intents/" + appName + "_pint" // FIXME when query API works, change this API call to + // query based on app name. + fmt.Printf("Delete gping app intents %s\n", url) + resp, err := orch.apiDel(url, orch.compositeAppName+"_delgpintintents") + if err != nil { + return err + } + if resp != 204 { + return resp + } + fmt.Printf("Delete gpint intents resp %s\n", resp) + } + } + } + } + return nil +} + +func (h placementIntentHandler) deleteAnchor() interface{} { + orch := h.orchInstance + dataRead := h.orchInstance.dataRead + for _, compositeAppValue := range dataRead.compositeAppMap { + compositeAppMetadata := compositeAppValue.Metadata.Metadata + compositeAppSpec := compositeAppValue.Metadata.Spec + Dig := compositeAppValue.DigMap + + // loop through all app intens in the gpint + for digName, digValue := range Dig { + for gpintName, _ := range digValue.GpintMap { + h.orchURL = "http://" + orch.MiddleendConf.OrchService + "/v2/projects/" + + orch.projectName + "/composite-apps/" + compositeAppMetadata.Name + + "/" + compositeAppSpec.Version + + "/deployment-intent-groups/" + digName + "/generic-placement-intents/" + + gpintName + fmt.Printf("Delete gpint %s\n", h.orchURL) + resp, err := orch.apiDel(h.orchURL, compositeAppMetadata.Name+"_delgpints") + if err != nil { + return err + } + if resp != 204 { + return resp + } + fmt.Printf("Delete gpint resp %s\n", resp) + } + } + } + return nil +} + +func (h *placementIntentHandler) createAnchor() interface{} { + orch := h.orchInstance + intentData := h.orchInstance.DigData + + gpi := GenericPlacementIntent{ + Metadata: apiMetaData{ + Name: intentData.CompositeAppName + "_gpint", + Description: "Generic placement intent created from middleend", + UserData1: "data 1", + UserData2: "data2"}, + } + + jsonLoad, _ := json.Marshal(gpi) + // POST the generic placement intent + h.orchURL = "http://" + orch.MiddleendConf.OrchService + "/v2/projects/" + intentData.Spec.ProjectName + + "/composite-apps/" + intentData.CompositeAppName + "/" + intentData.CompositeAppVersion + + "/deployment-intent-groups/" + intentData.Name + url := h.orchURL + "/generic-placement-intents" + resp, err := orch.apiPost(jsonLoad, url, orch.digpIntents["generic-placement-intent"]) + if err != nil { + return err + } + if resp != 201 { + return resp + } + fmt.Printf("Generic placement intent resp %s\n", resp) + + return nil +} + +func (h *placementIntentHandler) createObject() interface{} { + orch := h.orchInstance + intentData := h.orchInstance.DigData + + for _, value := range intentData.Spec.Apps { + appName := value.Metadata.Name + intentName := appName + "_pint" + genericAppIntentName := intentData.CompositeAppName + "_gpint" + providerName := value.Clusters[0].Provider + clusterName := value.Clusters[0].SelectedClusters[0].Name + + pint := PlacementIntent{ + Metadata: apiMetaData{ + Name: intentName, + Description: "NA", + UserData1: "data 1", + UserData2: "data2"}, + Spec: AppPlacementIntentSpec{ + AppName: appName, + Intent: arrayIntent{ + AllofCluster: []Allof{ // FIXME: the logic requires to handle allof/anyof and multi cluster. + Allof{ + ProviderName: providerName, + ClusterName: clusterName}, + }, + }, + }, + } + + url := h.orchURL + "/generic-placement-intents/" + genericAppIntentName + "/app-intents" + jsonLoad, _ := json.Marshal(pint) + status, err := orch.apiPost(jsonLoad, url, intentName) + if err != nil { + log.Fatalln(err) + } + if status != 201 { + return status + } + fmt.Printf("Placement intent %s status %s %s\n", intentName, status, url) + } + + return nil +} + +func addPlacementIntent(I orchWorkflow) interface{} { + // 1. Create the Anchor point + err := I.createAnchor() + if err != nil { + return err + } + // 2. Create the Objects + err = I.createObject() + if err != nil { + return err + } + return nil +} + +func delGpint(I orchWorkflow) interface{} { + // 1. Create the Anchor point + err := I.deleteObject() + if err != nil { + return err + } + // 2. Create the Objects + err = I.deleteAnchor() + if err != nil { + return err + } + return nil +} + +func (h *networkIntentHandler) createAnchor() interface{} { + orch := h.orchInstance + intentData := h.orchInstance.DigData + + nwIntent := NetworkCtlIntent{ + Metadata: apiMetaData{ + Name: intentData.CompositeAppName + "_nwctlint", + Description: "Network Controller created from middleend", + UserData1: "data 1", + UserData2: "data2"}, + } + jsonLoad, _ := json.Marshal(nwIntent) + // POST the network controller intent + h.ovnURL = "http://" + orch.MiddleendConf.OvnService + "/v2/projects/" + intentData.Spec.ProjectName + + "/composite-apps/" + intentData.CompositeAppName + "/" + intentData.CompositeAppVersion + + "/deployment-intent-groups/" + intentData.Name + url := h.ovnURL + "/network-controller-intent" + resp, err := orch.apiPost(jsonLoad, url, orch.nwCtlIntents["network-controller-intent"]) + if err != nil { + return err + } + if resp != 201 { + return resp + } + fmt.Printf("Network contoller intent resp %s\n", resp) + + return nil +} + +func (h *networkIntentHandler) createObject() interface{} { + orch := h.orchInstance + intentData := h.orchInstance.DigData + + for _, value := range intentData.Spec.Apps { + + appName := value.Metadata.Name + intentName := value.Metadata.Name + "_wnwlint" + genericAppIntentName := intentData.CompositeAppName + "_nwctlint" + + wlIntent := NetworkWlIntent{ + Metadata: apiMetaData{ + Name: intentName, + Description: "NA", + UserData1: "data 1", + UserData2: "data2"}, + Spec: WorkloadIntentSpec{ + AppName: appName, + Resource: appName, + Type: "deployment", + }, + } + + url := h.ovnURL + "/network-controller-intent/" + genericAppIntentName + "/workload-intents" + jsonLoad, _ := json.Marshal(wlIntent) + status, err := orch.apiPost(jsonLoad, url, intentName) + if err != nil { + log.Fatalln(err) + } + if status != 201 { + return status + } + fmt.Printf("Workload intent %s status %s %s\n", intentName, status, url) + } + + // Add interfaces for to each application + for _, value := range intentData.Spec.Apps { + interfaces := value.Clusters[0].SelectedClusters[0].Interfaces + for j := range interfaces { + interfaceNum := strconv.Itoa(j) + interfaceName := value.Metadata.Name + "_interface" + interfaceNum + genericAppIntentName := intentData.CompositeAppName + "_nwctlint" + workloadIntent := value.Metadata.Name + "_wnwlint" + + iface := NwInterface{ + Metadata: apiMetaData{ + Name: interfaceName, + Description: "NA", + UserData1: "data 1", + UserData2: "data2"}, + Spec: InterfaceSpec{ + Interface: "eth" + interfaceNum, + Name: interfaces[j].NetworkName, + DefaultGateway: "false", + IPAddress: interfaces[j].IP, + }, + } + + url := h.ovnURL + "/network-controller-intent" + "/" + genericAppIntentName + + "/workload-intents/" + workloadIntent + "/interfaces" + jsonLoad, _ := json.Marshal(iface) + status, err := orch.apiPost(jsonLoad, url, interfaceName) + if err != nil { + log.Fatalln(err) + } + if status != 201 { + return status + } + fmt.Printf("interface %s status %s %s\n", interfaceName, status, url) + } + } + + return nil +} + +func (h *networkIntentHandler) getObject() (interface{}, interface{}) { + orch := h.orchInstance + retcode := 200 + dataRead := h.orchInstance.dataRead + for _, compositeAppValue := range dataRead.compositeAppMap { + compositeAppMetadata := compositeAppValue.Metadata.Metadata + compositeAppSpec := compositeAppValue.Metadata.Spec + Dig := compositeAppValue.DigMap + for digName, digValue := range Dig { + h.ovnURL = "http://" + orch.MiddleendConf.OvnService + "/v2/projects/" + + orch.projectName + "/composite-apps/" + compositeAppMetadata.Name + + "/" + compositeAppSpec.Version + + "/deployment-intent-groups/" + digName + for nwintName, nwintValue := range digValue.NwintMap { + var wrlintList []NetworkWlIntent + wlurl := h.ovnURL + "/network-controller-intent/" + nwintName + "/workload-intents" + retcode, retval, err := orch.apiGet(wlurl, orch.compositeAppName+"_getnwwlint") + fmt.Printf("Get Wrkld intents in Composite app %s dig %s nw intent %s status %d\n", + orch.compositeAppName, digName, nwintName, retcode) + if err != nil { + fmt.Printf("Failed to read nw workload int") + return nil, 500 + } + if retcode != 200 { + fmt.Printf("Failed to read nw workload int") + return nil, retcode + } + json.Unmarshal(retval, &wrlintList) + nwintValue.WrkintMap = make(map[string]*WrkintData, len(wrlintList)) + for _, wrlIntValue := range wrlintList { + var WrkintDataInstance WrkintData + WrkintDataInstance.Wrkint = wrlIntValue + + var ifaceList []NwInterface + ifaceurl := h.ovnURL + "/network-controller-intent/" + nwintName + + "/workload-intents/" + wrlIntValue.Metadata.Name + "/interfaces" + retcode, retval, err := orch.apiGet(ifaceurl, orch.compositeAppName+"_getnwiface") + fmt.Printf("Get interface in Composite app %s dig %s nw intent %s wrkld intent %s status %d\n", + orch.compositeAppName, digName, nwintName, wrlIntValue.Metadata.Name, retcode) + if err != nil { + fmt.Printf("Failed to read nw interface") + return nil, 500 + } + if retcode != 200 { + fmt.Printf("Failed to read nw interface") + return nil, retcode + } + json.Unmarshal(retval, &ifaceList) + WrkintDataInstance.Interfaces = ifaceList + nwintValue.WrkintMap[wrlIntValue.Metadata.Name] = &WrkintDataInstance + } + } + } + } + return nil, retcode +} + +func (h *networkIntentHandler) getAnchor() (interface{}, interface{}) { + orch := h.orchInstance + retcode := 200 + dataRead := h.orchInstance.dataRead + for _, compositeAppValue := range dataRead.compositeAppMap { + compositeAppMetadata := compositeAppValue.Metadata.Metadata + compositeAppSpec := compositeAppValue.Metadata.Spec + Dig := compositeAppValue.DigMap + for digName, digValue := range Dig { + h.ovnURL = "http://" + orch.MiddleendConf.OvnService + "/v2/projects/" + + orch.projectName + "/composite-apps/" + compositeAppMetadata.Name + + "/" + compositeAppSpec.Version + + "/deployment-intent-groups/" + digName + var nwintList []NetworkCtlIntent + + url := h.ovnURL + "/network-controller-intent" + retcode, retval, err := orch.apiGet(url, orch.compositeAppName+"_getnwint") + fmt.Printf("Get Network Ctl intent in Composite app %s dig %s status %d\n", + orch.compositeAppName, digName, retcode) + if err != nil { + fmt.Printf("Failed to read nw int %s\n", err) + return nil, 500 + } + if retcode != 200 { + fmt.Printf("Failed to read nw int") + return nil, retcode + } + json.Unmarshal(retval, &nwintList) + digValue.NwintMap = make(map[string]*NwintData, len(nwintList)) + for _, nwIntValue := range nwintList { + var NwintDataInstance NwintData + NwintDataInstance.Nwint = nwIntValue + digValue.NwintMap[nwIntValue.Metadata.Name] = &NwintDataInstance + } + } + } + return nil, retcode +} + +func (h *networkIntentHandler) deleteObject() interface{} { + orch := h.orchInstance + retcode := 200 + dataRead := h.orchInstance.dataRead + for _, compositeAppValue := range dataRead.compositeAppMap { + compositeAppMetadata := compositeAppValue.Metadata.Metadata + compositeAppSpec := compositeAppValue.Metadata.Spec + Dig := compositeAppValue.DigMap + for digName, digValue := range Dig { + h.ovnURL = "http://" + orch.MiddleendConf.OvnService + "/v2/projects/" + + orch.projectName + "/composite-apps/" + compositeAppMetadata.Name + + "/" + compositeAppSpec.Version + + "/deployment-intent-groups/" + digName + + for nwintName, nwintValue := range digValue.NwintMap { + for wrkintName, wrkintValue := range nwintValue.WrkintMap { + // Delete the interfaces per workload intent. + for _, value := range wrkintValue.Interfaces { + url := h.ovnURL + "network-controller-intent/" + nwintName + "/workload-intents/" + + wrkintName + "/interfaces/" + value.Spec.Name + fmt.Printf("Delete app nw interface %s\n", url) + retcode, err := orch.apiDel(url, orch.compositeAppName+"_delnwinterface") + if err != nil { + return err + } + if retcode != 204 { + return retcode + } + fmt.Printf("Delete nw interface resp %s\n", retcode) + } + // Delete the workload intents. + url := h.ovnURL + "network-controller-intent/" + nwintName + "/workload-intents/" + wrkintName + fmt.Printf("Delete app nw wl intent %s\n", url) + retcode, err := orch.apiDel(url, orch.compositeAppName+"_delnwwrkintent") + if err != nil { + return err + } + if retcode != 204 { + return retcode + } + fmt.Printf("Delete nw wl intent resp %s\n", retcode) + } // For workload intents in network controller intent. + } // For network controller intents in Dig. + } // For Dig. + } // For composite app. + return retcode +} + +func (h networkIntentHandler) deleteAnchor() interface{} { + orch := h.orchInstance + retcode := 200 + dataRead := h.orchInstance.dataRead + for _, compositeAppValue := range dataRead.compositeAppMap { + compositeAppMetadata := compositeAppValue.Metadata.Metadata + compositeAppSpec := compositeAppValue.Metadata.Spec + Dig := compositeAppValue.DigMap + for digName, digValue := range Dig { + h.ovnURL = "http://" + orch.MiddleendConf.OvnService + "/v2/projects/" + + orch.projectName + "/composite-apps/" + compositeAppMetadata.Name + + "/" + compositeAppSpec.Version + + "/deployment-intent-groups/" + digName + for nwintName, _ := range digValue.NwintMap { + // loop through all app intens in the gpint + url := h.ovnURL + "/network-controller-intent/" + nwintName + fmt.Printf("Delete app nw controller intent %s\n", url) + retcode, err := orch.apiDel(url, compositeAppMetadata.Name+"_delnwctlintent") + if err != nil { + return err + } + if retcode != 204 { + return retcode + } + fmt.Printf("Delete nw controller intent %s\n", retcode) + } + } + } + return retcode +} + +func addNetworkIntent(I orchWorkflow) interface{} { + //1. Add network controller Intent + err := I.createAnchor() + if err != nil { + return err + } + + //2. Add network workload intent + err = I.createObject() + if err != nil { + return err + } + + return nil +} + +func delNwintData(I orchWorkflow) interface{} { + // 1. Create the Anchor point + err := I.deleteObject() + if err != nil { + return err + } + // 2. Create the Objects + err = I.deleteAnchor() + if err != nil { + return err + } + return nil +} diff --git a/src/tools/emcoui/middle_end/app/profile.go b/src/tools/emcoui/middle_end/app/profile.go new file mode 100644 index 00000000..fff43cdc --- /dev/null +++ b/src/tools/emcoui/middle_end/app/profile.go @@ -0,0 +1,261 @@ +/* +======================================================================= +Copyright (c) 2017-2020 Aarna Networks, Inc. +All rights reserved. +====================================================================== +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 app + +import ( + "encoding/json" + "fmt" + "log" +) + +// ProfileData captures per app profile +type ProfileData struct { + Name string `json:"profileName"` + AppProfiles map[string]string `json:"appProfile"` +} + +// ProfileMeta is metadta for the profile APIs +type ProfileMeta struct { + Metadata apiMetaData `json:"metadata"` + Spec ProfileSpec `json:"spec"` +} + +// ProfileSpec is the spec for the profile APIs +type ProfileSpec struct { + AppName string `json:"app-name"` +} + +// ProfileHandler This implements the orchworkflow interface +type ProfileHandler struct { + orchURL string + orchInstance *OrchestrationHandler + response struct { + payload map[string][]byte + status map[string]string + } +} + +func (h *ProfileHandler) getObject() (interface{}, interface{}) { + orch := h.orchInstance + dataRead := h.orchInstance.dataRead + retcode := 200 + for _, compositeAppValue := range dataRead.compositeAppMap { + var profileList []ProfileMeta + compositeAppMetadata := compositeAppValue.Metadata.Metadata + compositeAppSpec := compositeAppValue.Metadata.Spec + h.orchURL = "http://" + orch.MiddleendConf.OrchService + "/v2/projects/" + + orch.projectName + "/composite-apps/" + compositeAppMetadata.Name + + "/" + compositeAppSpec.Version + "/composite-profiles" + for profileName, profileValue := range compositeAppValue.ProfileDataArray { + url := h.orchURL + "/" + profileName + "/profiles" + retcode, respval, err := orch.apiGet(url, compositeAppMetadata.Name+"_getprofiles") + fmt.Printf("Get app profiles status %d\n", retcode) + if err != nil { + fmt.Printf("Failed to read profile %s\n", profileName) + return nil, 500 + } + if retcode != 200 { + fmt.Printf("Failed to read profile %s\n", profileName) + return nil, retcode + } + json.Unmarshal(respval, &profileList) + profileValue.AppProfiles = make([]ProfileMeta, len(profileList)) + for appProfileIndex, appProfile := range profileList { + profileValue.AppProfiles[appProfileIndex] = appProfile + } + } + } + return nil, retcode +} + +func (h *ProfileHandler) getAnchor() (interface{}, interface{}) { + orch := h.orchInstance + respcode := 200 + dataRead := h.orchInstance.dataRead + for _, compositeAppValue := range dataRead.compositeAppMap { + var profilemetaList []ProfileMeta + compositeAppMetadata := compositeAppValue.Metadata.Metadata + compositeAppSpec := compositeAppValue.Metadata.Spec + h.orchURL = "http://" + orch.MiddleendConf.OrchService + "/v2/projects/" + + orch.projectName + "/composite-apps/" + compositeAppMetadata.Name + + "/" + compositeAppSpec.Version + "/composite-profiles" + + respcode, respdata, err := orch.apiGet(h.orchURL, compositeAppMetadata.Name+"_getcprofile") + if err != nil { + fmt.Printf("Failed to get composite profiles\n") + return nil, 500 + } + if respcode != 200 { + fmt.Printf("composite profile GET status %d\n", respcode) + return nil, respcode + } + json.Unmarshal(respdata, &profilemetaList) + compositeAppValue.ProfileDataArray = make(map[string]*ProfilesData, len(profilemetaList)) + for _, value := range profilemetaList { + ProfilesDataInstance := ProfilesData{} + ProfilesDataInstance.Profile = value + compositeAppValue.ProfileDataArray[value.Metadata.Name] = &ProfilesDataInstance + } + } + return nil, respcode +} + +func (h *ProfileHandler) deleteObject() interface{} { + orch := h.orchInstance + dataRead := h.orchInstance.dataRead + for _, compositeAppValue := range dataRead.compositeAppMap { + compositeAppMetadata := compositeAppValue.Metadata.Metadata + compositeAppSpec := compositeAppValue.Metadata.Spec + h.orchURL = "http://" + orch.MiddleendConf.OrchService + "/v2/projects/" + + orch.projectName + "/composite-apps/" + compositeAppMetadata.Name + + "/" + compositeAppSpec.Version + "/composite-profiles/" + for profileName, profileValue := range compositeAppValue.ProfileDataArray { + for _, appProfileValue := range profileValue.AppProfiles { + url := h.orchURL + profileName + "/profiles/" + appProfileValue.Metadata.Name + + fmt.Printf("Delete app profiles %s\n", url) + resp, err := orch.apiDel(url, compositeAppMetadata.Name+"_delappProfiles") + if err != nil { + return err + } + if resp != 204 { + return resp + } + fmt.Printf("Delete profiles status %s\n", resp) + } + } + } + return nil +} + +func (h *ProfileHandler) deleteAnchor() interface{} { + orch := h.orchInstance + dataRead := h.orchInstance.dataRead + for _, compositeAppValue := range dataRead.compositeAppMap { + compositeAppMetadata := compositeAppValue.Metadata.Metadata + compositeAppSpec := compositeAppValue.Metadata.Spec + h.orchURL = "http://" + orch.MiddleendConf.OrchService + "/v2/projects/" + + orch.projectName + "/composite-apps/" + compositeAppMetadata.Name + + "/" + compositeAppSpec.Version + "/composite-profiles/" + + for profileName, _ := range compositeAppValue.ProfileDataArray { + url := h.orchURL + profileName + fmt.Printf("Delete profile %s\n", url) + resp, err := orch.apiDel(url, compositeAppMetadata.Name+"_delProfile") + if err != nil { + return err + } + if resp != 204 { + return resp + } + fmt.Printf("Delete profile status %s\n", resp) + } + } + return nil +} + +func (h *ProfileHandler) createAnchor() interface{} { + orch := h.orchInstance + + profileCreate := ProfileMeta{ + Metadata: apiMetaData{ + Name: orch.compositeAppName + "_profile", + Description: "Profile created from middleend", + UserData1: "data 1", + UserData2: "data2"}, + } + jsonLoad, _ := json.Marshal(profileCreate) + h.orchURL = "http://" + orch.MiddleendConf.OrchService + "/v2/projects/" + + orch.projectName + "/composite-apps" + url := h.orchURL + "/" + orch.compositeAppName + "/" + "v1" + "/composite-profiles" + resp, err := orch.apiPost(jsonLoad, url, orch.compositeAppName+"_profile") + if err != nil { + return err + } + if resp != 201 { + return resp + } + fmt.Printf("ProfileHandler resp %s\n", resp) + + return nil +} + +func (h *ProfileHandler) createObject() interface{} { + orch := h.orchInstance + + for i := range orch.meta { + fileName := orch.meta[i].ProfileMetadata.FileName + appName := orch.meta[i].Metadata.Name + profileName := orch.meta[i].Metadata.Name + "_profile" + + // Upload the application helm chart + fh := orch.file[fileName] + profileAdd := ProfileMeta{ + Metadata: apiMetaData{ + Name: profileName, + Description: "NA", + UserData1: "data 1", + UserData2: "data2"}, + Spec: ProfileSpec{ + AppName: appName}, + } + compositeProfilename := orch.compositeAppName + "_profile" + + url := h.orchURL + "/" + orch.compositeAppName + "/" + "v1" + "/" + + "composite-profiles" + "/" + compositeProfilename + "/profiles" + jsonLoad, _ := json.Marshal(profileAdd) + status, err := orch.apiPostMultipart(jsonLoad, fh, url, profileName, fileName) + if err != nil { + log.Fatalln(err) + } + if status != 201 { + return status + } + fmt.Printf("CompositeProfile Profile %s status %s %s\n", profileName, status, url) + } + + return nil +} + +func createProfile(I orchWorkflow) interface{} { + // 1. Create the Anchor point + err := I.createAnchor() + if err != nil { + return err + } + // 2. Create the Objects + err = I.createObject() + if err != nil { + return err + } + return nil +} + +func delProfileData(I orchWorkflow) interface{} { + // 1. Delete the object + err := I.deleteObject() + if err != nil { + return err + } + // 2. Delete the Anchor + err = I.deleteAnchor() + if err != nil { + return err + } + return nil +} diff --git a/src/tools/emcoui/middle_end/app/projects.go b/src/tools/emcoui/middle_end/app/projects.go new file mode 100644 index 00000000..39fe7573 --- /dev/null +++ b/src/tools/emcoui/middle_end/app/projects.go @@ -0,0 +1,187 @@ +/* +======================================================================= +Copyright (c) 2017-2020 Aarna Networks, Inc. +All rights reserved. +====================================================================== +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 app + +import ( + "encoding/json" + "fmt" +) + +// CompositeApp application structure +type ProjectMetadata struct { + Metadata apiMetaData `json:"metadata"` +} + +// CompAppHandler , This implements the orchworkflow interface +type projectHandler struct { + orchURL string + orchInstance *OrchestrationHandler +} + +func (h *projectHandler) getObject() (interface{}, interface{}) { + orch := h.orchInstance + dataRead := h.orchInstance.dataRead + var cappList []CompositeApp + if orch.treeFilter != nil { + temp:=CompositeApp{} + h.orchURL = "http://" + orch.MiddleendConf.OrchService + "/v2/projects/" + + orch.projectName + "/composite-apps/" + orch.treeFilter.compositeAppName+"/"+ + orch.treeFilter.compositeAppVersion + respcode, respdata, err := orch.apiGet(h.orchURL, orch.projectName+"_getcapps") + fmt.Printf("Get capp status %s\n", respcode) + if err != nil { + return nil, 500 + } + if respcode != 200 { + return nil, respcode + } + fmt.Printf("Get capp status %s\n", respcode) + json.Unmarshal(respdata, &temp) + cappList = append(cappList, temp) + } else { + h.orchURL = "http://" + orch.MiddleendConf.OrchService + "/v2/projects/" + + orch.projectName + "/composite-apps" + respcode, respdata, err := orch.apiGet(h.orchURL, orch.projectName+"_getcapps") + fmt.Printf("Get capp status %s\n", respcode) + if err != nil { + return nil, 500 + } + if respcode != 200 { + return nil, respcode + } + fmt.Printf("Get capp status %s\n", respcode) + json.Unmarshal(respdata, &cappList) + } + + dataRead.compositeAppMap = make(map[string]*CompositeAppTree, len(cappList)) + for k, value := range cappList { + fmt.Printf("%+v", cappList[k]) + var cappsDataInstance CompositeAppTree + cappName := value.Metadata.Name + cappsDataInstance.Metadata = value + dataRead.compositeAppMap[cappName] = &cappsDataInstance + } + return nil, 200 +} + +func (h *projectHandler) getAnchor() (interface{}, interface{}) { + orch := h.orchInstance + dataRead := h.orchInstance.dataRead + h.orchURL = "http://" + orch.MiddleendConf.OrchService + "/v2/projects/" + + orch.projectName + + respcode, respdata, err := orch.apiGet(h.orchURL, orch.projectName+"_getProject") + if err != nil { + return nil, 500 + } + if respcode != 200 { + return nil, respcode + } + fmt.Printf("Get project %s\n", respcode) + json.Unmarshal(respdata, &dataRead.Metadata) + return nil, respcode +} + +func (h *projectHandler) deleteObject() interface{} { + orch := h.orchInstance + dataRead := h.orchInstance.dataRead + cappList := dataRead.compositeAppMap + h.orchURL = "http://" + orch.MiddleendConf.OrchService + "/v2/projects/" + + orch.projectName + "/composite-apps" + for compositeAppName, compositeAppValue := range cappList { + url := h.orchURL + "/" + compositeAppName + "/" + compositeAppValue.Metadata.Spec.Version + fmt.Printf("Delete composite app %s\n", url) + resp, err := orch.apiDel(url, compositeAppName+"_delcapp") + if err != nil { + return err + } + if resp != 204 { + return resp + } + fmt.Printf("Delete composite app status %s\n", resp) + } + return nil +} + +func (h *projectHandler) deleteAnchor() interface{} { + orch := h.orchInstance + h.orchURL = "http://" + orch.MiddleendConf.OrchService + "/v2/projects/" + orch.projectName + fmt.Printf("Delete Project %s \n", h.orchURL) + resp, err := orch.apiDel(h.orchURL, orch.projectName+"_delProject") + if err != nil { + return err + } + if resp != 204 { + return resp + } + fmt.Printf("Delete Project status %s\n", resp) + return nil +} + +func (h *projectHandler) createAnchor() interface{} { + orch := h.orchInstance + + projectCreate := ProjectMetadata{ + Metadata: apiMetaData{ + Name: orch.projectName, + Description: orch.projectDesc, + UserData1: "data 1", + UserData2: "data 2"}, + } + + jsonLoad, _ := json.Marshal(projectCreate) + h.orchURL = "http://" + orch.MiddleendConf.OrchService + "/v2/projects/" + orch.projectName + resp, err := orch.apiPost(jsonLoad, h.orchURL, orch.projectName) + if err != nil { + return err + } + if resp != 201 { + return resp + } + orch.version = "v1" + fmt.Printf("projectHandler resp %s\n", resp) + + return nil +} + +func (h *projectHandler) createObject() interface{} { + return nil +} + +func createProject(I orchWorkflow) interface{} { + // 1. Create the Anchor point + err := I.createAnchor() + if err != nil { + return err + } + return nil +} + +func delProject(I orchWorkflow) interface{} { + // 1. Delete the object + err := I.deleteObject() + if err != nil { + return err + } + // 2. Delete the Anchor + err = I.deleteAnchor() + if err != nil { + return err + } + return nil +} diff --git a/src/tools/emcoui/middle_end/authproxy/README.md b/src/tools/emcoui/middle_end/authproxy/README.md new file mode 100644 index 00000000..1d68a431 --- /dev/null +++ b/src/tools/emcoui/middle_end/authproxy/README.md @@ -0,0 +1,16 @@ + +Authproxy is part of middleend and it exposes following 3 apis +1. **/v1/login** + - Redirects user to keycloak login page. + - Sets a cookie with original URL +2. **/v1/callback** + - After successful login gets auth code and exchange it for token. + - Set id_token and access_token in cookie and redirects to original URL +3. **/v1/auth** + - Retrieve idtoken from cookie and verifies the JWT. + - If id_token is valid then access to resources else redirects to login page. + +Required inputs of authproxy comes from authproxy section of helm config +- Issuer +- Redirect URI +- Client id diff --git a/src/tools/emcoui/middle_end/authproxy/authproxy.go b/src/tools/emcoui/middle_end/authproxy/authproxy.go new file mode 100644 index 00000000..78819ef6 --- /dev/null +++ b/src/tools/emcoui/middle_end/authproxy/authproxy.go @@ -0,0 +1,281 @@ +/* +======================================================================= +Copyright (c) 2017-2020 Aarna Networks, Inc. +All rights reserved. +====================================================================== +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 authproxy + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "log" + "net/http" + "net/url" + "strings" + + "github.com/dgrijalva/jwt-go" +) + +type AuthProxy struct { + AuthProxyConf AuthProxyConfig +} + +// AuthProxyConfig holds inputs of authproxy +type AuthProxyConfig struct { + Issuer string `json:"issuer"` + RedirectURI string `json:"redirect_uri"` + ClientID string `json:"client_id"` +} + +// NewAppHandler interface implementing REST callhandler +func NewAppHandler() *AuthProxy { + return &AuthProxy{} +} + +// OpenIDConfiguration struct to map response from OIDC +type OpenIDConfiguration struct { + Issuer string `json:"issuer"` + AuthzEndpoint string `json:"authorization_endpoint"` + TokenEndPoint string `json:"token_endpoint"` + IntrospectionEndpoint string `json:"introspection_endpoint"` + JWKSURI string `json:"jwks_uri"` +} + +// TokenConfig struct holds tokens +type TokenConfig struct { + ACCESSTOKEN string `json:"access_token"` + IDTOKEN string `json:"id_token"` +} + +// RealmConfig struct holds public_key of issuer +type RealmConfig struct { + PublicKey string `json:"public_key"` +} + +var openIDConfig *OpenIDConfiguration +var realmConfig *RealmConfig + +// Loads the openIDconfig from the OIDC only once +func getOpenIDConfig(issuer string) OpenIDConfiguration { + if openIDConfig != nil { + log.Println("openidconfig is not null and returning the cached value") + return *openIDConfig + } + log.Println("openidconfig is null and loading the values") + url := issuer + ".well-known/openid-configuration" + response, err := http.Get(url) + if err != nil { + log.Printf("The openidconfig HTTP request failed with error %s", err) + return *openIDConfig + } + + defer response.Body.Close() + bodyBytes, _ := ioutil.ReadAll(response.Body) + json.Unmarshal(bodyBytes, &openIDConfig) + return *openIDConfig +} + +// LoginHandler redirects to client login page and sets cookie with the original path +func (h AuthProxy) LoginHandler(w http.ResponseWriter, r *http.Request) { + log.Println("LoginHandler start") + + rd := r.FormValue("rd") + scope := r.FormValue("scope") + + log.Printf("[LoginHandler] url Param 'rd' is: %s, 'scope' is: %s\n", string(rd), string(scope)) + redirect := r.Header.Get("X-Auth-Request-Redirect") + log.Println("redirect url from HEADER is: " + redirect) + if len(redirect) == 0 { + redirect = rd + } + + cookie := http.Cookie{ + Name: "org", + Value: redirect, + Path: "/", + Domain: "", + Secure: false, + HttpOnly: false, + } + // Set cookie with original URL + http.SetCookie(w, &cookie) + state := "1234" // Optional parameter included in all login redirects + if len(scope) == 0 { + // generate token with offline_access scope so that it can be stored in cookie and reused + scope = "openid offline_access" + } + + // get authorization endpoint from function openidconfig + authzEndpoint := getOpenIDConfig(h.AuthProxyConf.Issuer).AuthzEndpoint + + // Construct redirect URL with params + u, _ := url.Parse(authzEndpoint) + q := u.Query() + q.Add("client_id", h.AuthProxyConf.ClientID) + // h.AuthProxyConf.RedirectURI is the callback endpoint of middleend. + // after successful authentication, url will be redirected to this one + q.Add("redirect_uri", h.AuthProxyConf.RedirectURI) + q.Add("response_type", "code") + q.Add("scope", scope) + q.Add("state", state) + u.RawQuery = q.Encode() + + log.Println("[LoginHandler] Redireced URL -> " + u.String()) + http.Redirect(w, r, u.String(), http.StatusFound) +} + +/* + * CallbackHandler reads the OIDC config + * Gets token with API and sets id and access tokens in cookies + * Redirects to original URL + */ +func (h AuthProxy) CallbackHandler(w http.ResponseWriter, r *http.Request) { + state := r.FormValue("state") + code := r.FormValue("code") + tokenEndpoint := getOpenIDConfig(h.AuthProxyConf.Issuer).TokenEndPoint + log.Printf("[CallbackHandler] state: %s , code: %s , tokenEndpoint: %s \n", state, code, tokenEndpoint) + + client := http.Client{} + form := url.Values{} + form.Add("client_id", h.AuthProxyConf.ClientID) + form.Add("client_secret", "") + form.Add("grant_type", "authorization_code") + form.Add("code", code) + form.Add("redirect_uri", h.AuthProxyConf.RedirectURI) + request, err := http.NewRequest("POST", tokenEndpoint, strings.NewReader(form.Encode())) + request.Header.Add("Content-Type", "application/x-www-form-urlencoded") + + resp, err := client.Do(request) + if err != nil { + log.Printf("[CallbackHandler] HTTP request to %s failed\n", tokenEndpoint) + log.Println(err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + log.Println("[CallbackHandler] Error while reading response from tokenEndpoint") + log.Println(err) + w.WriteHeader(http.StatusInternalServerError) + return + } + + var tokenConfig TokenConfig + json.Unmarshal(body, &tokenConfig) + log.Printf("[CallbackHandler] access_token: %s \n id_token: %s\n", tokenConfig.ACCESSTOKEN, tokenConfig.IDTOKEN) + + // Construct the original URL with cookie org + var orginalURL string + cookie, err := r.Cookie("org") + if err == nil { + orginalURL = cookie.Value + } + fmt.Println("[CallbackHandler] orginalURL from cookie: " + orginalURL) + + // Create cookies with id_token, access_token + idTokenCookie := http.Cookie{ + Name: "idtoken", + Value: tokenConfig.IDTOKEN, + Path: "/", + Domain: "", + Secure: false, + HttpOnly: false, + } + accessTokencookie := http.Cookie{ + Name: "accesstoken", + Value: tokenConfig.ACCESSTOKEN, + Path: "/", + Domain: "", + Secure: false, + HttpOnly: false, + } + + http.SetCookie(w, &idTokenCookie) + http.SetCookie(w, &accessTokencookie) + + // Finally return the original URL with the cookies + http.Redirect(w, r, orginalURL, http.StatusFound) +} + +// AuthHandler verifies the token and returns response +func (h AuthProxy) AuthHandler(w http.ResponseWriter, r *http.Request) { + log.Println("[AuthHandler] Authenticating the token") + + var idToken string + cookie, err := r.Cookie("idtoken") + if err == nil { + cookieVal := cookie.Value + idToken = cookieVal + } + + if idToken == "" { + log.Println("[AuthHandler] id token is nil ") + w.WriteHeader(http.StatusUnauthorized) + return + } + error := validateToken(h.AuthProxyConf.Issuer, idToken) + if error != nil { + log.Println("[AuthHandler] Issue with token and returning failed response") + w.WriteHeader(http.StatusUnauthorized) + } +} + +/* +* Validates JWT token +* verifies signature, token expiry and invalid check... etc + */ +func validateToken(issuer string, reqToken string) error { + log.Printf("[AuthHandler] Validating JWT token: \n%s\n", reqToken) + + //load realm public key only once + if realmConfig == nil { + log.Println("[AuthHandler] realmconfig is null and loading the value") + response, err := http.Get(issuer) + if err != nil { + log.Printf("[AuthHandler] Error while retreiving issuer details : %s\n", err) + return err + } + defer response.Body.Close() + bodyBytes, _ := ioutil.ReadAll(response.Body) + json.Unmarshal(bodyBytes, &realmConfig) + } + SecretKey := "-----BEGIN CERTIFICATE-----\n" + realmConfig.PublicKey + "\n-----END CERTIFICATE-----" + key, er := jwt.ParseRSAPublicKeyFromPEM([]byte(SecretKey)) + if er != nil { + log.Println("[AuthHandler] Error occured while parsing public key") + log.Println(er) + return er + } + + token, err := jwt.Parse(reqToken, func(token *jwt.Token) (interface{}, error) { + if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok { + return nil, fmt.Errorf("[AuthHandler] Unexpected signing method: %v", token.Header["alg"]) + } + return key, nil + }) + + if err != nil { + log.Println("[AuthHandler] Error while parsing token") + log.Println(err) + return err + } + if _, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { + log.Println("[AuthHandler] Token is valid") + } + return nil +} diff --git a/src/tools/emcoui/middle_end/db/dbconnection.go b/src/tools/emcoui/middle_end/db/dbconnection.go new file mode 100644 index 00000000..5496c39c --- /dev/null +++ b/src/tools/emcoui/middle_end/db/dbconnection.go @@ -0,0 +1,145 @@ +/* +======================================================================= +Copyright (c) 2017-2020 Aarna Networks, Inc. +All rights reserved. +====================================================================== +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 db + +import ( + "encoding/json" + "fmt" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + "golang.org/x/net/context" +) + +// MongoStore is the interface which implements the db.Store interface +type MongoStore struct { + db *mongo.Database +} + +// Key interface +type Key interface { +} + +// DBconn variable of type Store +var DBconn Store + +// Store Interface which implements the data store functions +type Store interface { + HealthCheck() error + Find(coll string, key []byte, tag string) ([][]byte, error) + Unmarshal(inp []byte, out interface{}) error +} + +// NewMongoStore Return mongo client +func NewMongoStore(name string, store *mongo.Database, svcEp string) (Store, error) { + if store == nil { + ip := "mongodb://" + svcEp + clientOptions := options.Client() + clientOptions.ApplyURI(ip) + mongoClient, err := mongo.NewClient(clientOptions) + if err != nil { + return nil, err + } + + err = mongoClient.Connect(context.Background()) + if err != nil { + return nil, err + } + store = mongoClient.Database(name) + } + return &MongoStore{ + db: store, + }, nil +} + +// CreateDBClient creates the DB client. currently only mongo +func CreateDBClient(dbType string, dbName string, svcEp string) error { + var err error + switch dbType { + case "mongo": + DBconn, err = NewMongoStore(dbName, nil, svcEp) + default: + fmt.Println(dbType + "DB not supported") + } + return err +} + +// HealthCheck verifies the database connection +func (m *MongoStore) HealthCheck() error { + _, err := (*mongo.SingleResult).DecodeBytes(m.db.RunCommand(context.Background(), bson.D{{"serverStatus", 1}})) + if err != nil { + fmt.Println("Error getting DB server status: err %s", err) + } + return nil +} + +func (m *MongoStore) Unmarshal(inp []byte, out interface{}) error { + err := bson.Unmarshal(inp, out) + if err != nil { + fmt.Printf("Failed to unmarshall bson") + return err + } + return nil +} + +// Find a document +func (m *MongoStore) Find(coll string, key []byte, tag string) ([][]byte, error) { + var bsonMap bson.M + err := json.Unmarshal([]byte(key), &bsonMap) + if err != nil { + fmt.Println("Failed to unmarshall %s\n", key) + return nil, err + } + + filter := bson.M{ + "$and": []bson.M{bsonMap}, + } + + fmt.Printf("%+v %s\n", filter, tag) + projection := bson.D{ + {tag, 1}, + {"_id", 0}, + } + + c := m.db.Collection(coll) + + cursor, err := c.Find(context.Background(), filter, options.Find().SetProjection(projection)) + if err != nil { + fmt.Println("Failed to find the document %s\n", err) + return nil, err + } + + defer cursor.Close(context.Background()) + var data []byte + var result [][]byte + for cursor.Next(context.Background()) { + d := cursor.Current + switch d.Lookup(tag).Type { + case bson.TypeString: + data = []byte(d.Lookup(tag).StringValue()) + default: + r, err := d.LookupErr(tag) + if err != nil { + fmt.Println("Unable to read data %s %s\n", string(r.Value), err) + } + data = r.Value + } + result = append(result, data) + } + return result, nil +} diff --git a/src/tools/emcoui/middle_end/go.mod b/src/tools/emcoui/middle_end/go.mod new file mode 100644 index 00000000..3d195979 --- /dev/null +++ b/src/tools/emcoui/middle_end/go.mod @@ -0,0 +1,14 @@ +module example.com/middleend + +go 1.14 + +require ( + github.com/dgrijalva/jwt-go v3.2.0+incompatible + github.com/gorilla/handlers v1.5.0 + github.com/gorilla/mux v1.8.0 + github.com/lestrrat-go/jwx v1.0.5 + go.mongodb.org/mongo-driver v1.4.1 + golang.org/x/net v0.0.0-20200707034311-ab3426394381 + k8s.io/apimachinery v0.19.3 + k8s.io/client-go v0.19.3 +) diff --git a/src/tools/emcoui/middle_end/go.sum b/src/tools/emcoui/middle_end/go.sum new file mode 100644 index 00000000..d8e5a43f --- /dev/null +++ b/src/tools/emcoui/middle_end/go.sum @@ -0,0 +1,428 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.51.0/go.mod h1:hWtGJ6gnXH+KgDv+V0zFGDvpi07n3z8ZNj3T1RW0Gcw= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= +github.com/Azure/go-autorest/autorest v0.9.6/go.mod h1:/FALq9T/kS7b5J5qsQ+RSTUdAmGFqi0vUdVNNx8q630= +github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= +github.com/Azure/go-autorest/autorest/adal v0.8.2/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= +github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= +github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= +github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= +github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= +github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +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/aws/aws-sdk-go v1.29.15 h1:0ms/213murpsujhsnxnNKNeVouW60aJqSd992Ks3mxs= +github.com/aws/aws-sdk-go v1.29.15/go.mod h1:1KvfttTE3SPKMpo8g2c6jL3ZKfXtFvKscTgahTma5Xg= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/davecgh/go-spew v1.1.0/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/dgrijalva/jwt-go v1.0.2 h1:KPldsxuKGsS2FPWsNeg9ZO18aCrGKujPoWXn2yo+KQM= +github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= +github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-logr/logr v0.2.0 h1:QvGt2nLcHH0WK9orKa+ppBPAxREcH364nPUedEpK0TY= +github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +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-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +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/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= +github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= +github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg= +github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs= +github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= +github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= +github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk= +github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28= +github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo= +github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk= +github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw= +github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360= +github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg= +github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE= +github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8= +github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= +github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= +github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= +github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= +github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= +github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= +github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= +github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= +github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gnostic v0.4.1 h1:DLJCy1n/vrD4HPjOvYcT8aYQXpPIzoRZONaYwyycI+I= +github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= +github.com/gorilla/handlers v1.5.0 h1:4wjo3sf9azi99c8hTmyaxp9y5S+pFszsy3pP0rAw/lw= +github.com/gorilla/handlers v1.5.0/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.5 h1:JboBksRwiiAJWvIYJVo46AfV+IAIKZpfrSzVKj42R4Q= +github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= +github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= +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/klauspost/compress v1.9.5 h1:U+CaK85mrNNb4k8BNOfgJtJ/gr6kswUCFj6miSzVC6M= +github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lestrrat-go/iter v0.0.0-20200422075355-fc1769541911 h1:FvnrqecqX4zT0wOIbYK1gNgTm0677INEWiFY8UEYggY= +github.com/lestrrat-go/iter v0.0.0-20200422075355-fc1769541911/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc= +github.com/lestrrat-go/jwx v1.0.5 h1:8bVUGXXkR3+YQNwuFof3lLxSJMLtrscHJfGI6ZIBRD0= +github.com/lestrrat-go/jwx v1.0.5/go.mod h1:TPF17WiSFegZo+c20fdpw49QD+/7n4/IsGvEmCSWwT0= +github.com/lestrrat-go/pdebug v0.0.0-20200204225717-4d6bd78da58d/go.mod h1:B06CSso/AWxiPejj+fheUINGeBKeeEZNt8w+EoU7+L8= +github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= +github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +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 v0.0.0-20180714160509-73f8eece6fdc h1:n+nNi93yXLkJvKwXNP9d55HC7lGK4H/SRcwB5IaUZLo= +github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.mongodb.org/mongo-driver v1.4.1 h1:38NSAyDPagwnFpUA/D5SFgbugUYR3NzYRNa4Qk9UxKs= +go.mongodb.org/mongo-driver v1.4.1/go.mod h1:llVBH2pkj9HywK0Dtdt6lDikOjFLbceHVu/Rc0iMKLs= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +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-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5 h1:8dUaAV7K4uHsF56JQWkprecIQKdPHtR9jCHF5nB8uzc= +golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6 h1:pE8b58s1HRDMi8RDc79m0HISf9D4TzseP40cEA6IGfs= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +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-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-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= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4 h1:5/PjkGUjvEU5Gl6BxmvKRPpqo2uNMv4rcHBMwzk/st8= +golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200417140056-c07e33ef3290/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0 h1:UhZDfRO8JRQru4/+LlLE0BRKGF8L+PICnvYZmx/fEGA= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +k8s.io/api v0.19.3 h1:GN6ntFnv44Vptj/b+OnMW7FmzkpDoIDLZRvKX3XH9aU= +k8s.io/api v0.19.3/go.mod h1:VF+5FT1B74Pw3KxMdKyinLo+zynBaMBiAfGMuldcNDs= +k8s.io/apimachinery v0.19.3 h1:bpIQXlKjB4cB/oNpnNnV+BybGPR7iP5oYpsOTEJ4hgc= +k8s.io/apimachinery v0.19.3/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA= +k8s.io/client-go v0.19.3 h1:ctqR1nQ52NUs6LpI0w+a5U+xjYwflFwA13OJKcicMxg= +k8s.io/client-go v0.19.3/go.mod h1:+eEMktZM+MG0KO+PTkci8xnbCZHvj9TqR6Q1XDUIJOM= +k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= +k8s.io/klog/v2 v2.2.0 h1:XRvcwJozkgZ1UQJmfMGpvRthQHOvihEhYtDfAaxMz/A= +k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o= +k8s.io/utils v0.0.0-20200729134348-d5654de09c73 h1:uJmqzgNWG7XyClnU/mLPBWwfKKF1K8Hf8whTseBgJcg= +k8s.io/utils v0.0.0-20200729134348-d5654de09c73/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +sigs.k8s.io/structured-merge-diff/v4 v4.0.1 h1:YXTMot5Qz/X1iBRJhAt+vI+HVttY0WkSqqhKxQ0xVbA= +sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= diff --git a/src/tools/emcoui/middle_end/main/main.go b/src/tools/emcoui/middle_end/main/main.go new file mode 100644 index 00000000..97a31017 --- /dev/null +++ b/src/tools/emcoui/middle_end/main/main.go @@ -0,0 +1,112 @@ +/* +======================================================================= +Copyright (c) 2017-2020 Aarna Networks, Inc. +All rights reserved. +====================================================================== +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" + "encoding/json" + "fmt" + "io/ioutil" + "log" + "net/http" + "os" + "os/signal" + "time" + + "example.com/middleend/app" + "example.com/middleend/authproxy" + "example.com/middleend/db" + "github.com/gorilla/handlers" + "github.com/gorilla/mux" +) + +/* This is the main package of the middleend. This package + * implements the http server which exposes service ar 9891. + * It also intialises an API router which handles the APIs with + * subpath /v1. + */ +func main() { + depHandler := app.NewAppHandler() + authProxyHandler := authproxy.NewAppHandler() + configFile, err := os.Open("/opt/emco/config/middleend.conf") + if err != nil { + fmt.Printf("Failed to read middleend configuration") + return + } + defer configFile.Close() + + // Read the configuration json + byteValue, _ := ioutil.ReadAll(configFile) + json.Unmarshal(byteValue, &depHandler.MiddleendConf) + json.Unmarshal(byteValue, &authProxyHandler.AuthProxyConf) + + // Connect to the DB + err = db.CreateDBClient("mongo", "mco", depHandler.MiddleendConf.Mongo) + if err != nil { + fmt.Println("Failed to connect to DB") + return + } + // Get an instance of the OrchestrationHandler, this type implements + // the APIs i.e CreateApp, ShowApp, DeleteApp. + httpRouter := mux.NewRouter().PathPrefix("/middleend").Subrouter() + loggedRouter := handlers.LoggingHandler(os.Stdout, httpRouter) + log.Println("Starting middle end service") + + httpServer := &http.Server{ + Handler: loggedRouter, + Addr: ":" + depHandler.MiddleendConf.OwnPort, + WriteTimeout: 15 * time.Second, + ReadTimeout: 15 * time.Second, + } + httpRouter.HandleFunc("/healthcheck", depHandler.GetHealth).Methods("GET") + + // POST, GET, DELETE composite apps + httpRouter.HandleFunc("/projects/{project-name}/composite-apps", depHandler.CreateApp).Methods("POST") + //httpRouter.HandleFunc("/projects/{project-name}/composite-apps", depHandler.GetAllCaps).Methods("GET") + httpRouter.HandleFunc("/projects/{project-name}/composite-apps/{composite-app-name}/{version}", + depHandler.GetSvc).Methods("GET") + httpRouter.HandleFunc("/projects/{project-name}/composite-apps/{composite-app-name}/{version}", + depHandler.DelSvc).Methods("DELETE") + // POST, GET, DELETE deployment intent groups + httpRouter.HandleFunc("/projects/{project-name}/composite-apps/{composite-app-name}/{version}/deployment-intent-groups", + depHandler.CreateDig).Methods("POST") + httpRouter.HandleFunc("/projects/{project-name}/deployment-intent-groups", depHandler.GetAllDigs).Methods("GET") + httpRouter.HandleFunc("/projects/{project-name}/composite-apps/{composite-app-name}/{version}/deployment-intent-groups/{deployment-intent-group-name}", + depHandler.DelDig).Methods("DELETE") + + // Authproxy relates APIs + httpRouter.HandleFunc("/login", authProxyHandler.LoginHandler).Methods("GET") + httpRouter.HandleFunc("/callback", authProxyHandler.CallbackHandler).Methods("GET") + httpRouter.HandleFunc("/auth", authProxyHandler.AuthHandler).Methods("GET") + // Cluster createion API + httpRouter.HandleFunc("/clusterproviders/{cluster-provider-name}/clusters", depHandler.CheckConnection).Methods("POST") + + // Start server in a go routine. + go func() { + log.Fatal(httpServer.ListenAndServe()) + }() + + // Gracefull shutdown of the server, + // create a channel and wait for SIGINT + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt) + log.Println("wait for signal") + <-c + log.Println("Bye Bye") + httpServer.Shutdown(context.Background()) +} diff --git a/src/tools/emcoui/public/robots.txt b/src/tools/emcoui/public/robots.txt index e9e57dc4..f2ce78e7 100644 --- a/src/tools/emcoui/public/robots.txt +++ b/src/tools/emcoui/public/robots.txt @@ -1,3 +1,17 @@ +#======================================================================= +# Copyright (c) 2017-2020 Aarna Networks, Inc. +# All rights reserved. +# ====================================================================== +# 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. +# ======================================================================== # https://www.robotstxt.org/robotstxt.html User-agent: * Disallow: diff --git a/src/tools/emcoui/src/App.js b/src/tools/emcoui/src/App.js index 2613ecfd..3a2c5ffc 100644 --- a/src/tools/emcoui/src/App.js +++ b/src/tools/emcoui/src/App.js @@ -11,7 +11,7 @@ // 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. -// ======================================================================== +// ======================================================================== import React from "react"; import { BrowserRouter as Router, @@ -53,7 +53,7 @@ function App() { <Redirect exact from={`${match.path}`} - to={`${match.path}/composite-apps`} + to={`${match.path}/services`} /> <Route path={`${match.path}`} diff --git a/src/tools/emcoui/src/admin/AdminNavigator.js b/src/tools/emcoui/src/admin/AdminNavigator.js index be07cba0..9cee73b1 100644 --- a/src/tools/emcoui/src/admin/AdminNavigator.js +++ b/src/tools/emcoui/src/admin/AdminNavigator.js @@ -11,7 +11,7 @@ // 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. -// ======================================================================== +// ======================================================================== import React, { useState } from "react"; import PropTypes from "prop-types"; import clsx from "clsx"; @@ -37,15 +37,15 @@ const categories = [ url: "/projects", }, { - id: "Clusters", - icon: <DnsRoundedIcon />, - url: "/clusters", - }, - { id: "Controllers", icon: <SettingsIcon />, url: "/controllers", }, + { + id: "Clusters", + icon: <DnsRoundedIcon />, + url: "/clusters", + }, ], }, ]; diff --git a/src/tools/emcoui/src/admin/clusterProvider/ClusterProviderForm.jsx b/src/tools/emcoui/src/admin/clusterProvider/ClusterProviderForm.jsx index 150a1912..57ee7557 100644 --- a/src/tools/emcoui/src/admin/clusterProvider/ClusterProviderForm.jsx +++ b/src/tools/emcoui/src/admin/clusterProvider/ClusterProviderForm.jsx @@ -11,147 +11,157 @@ // 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. -// ======================================================================== -import React from 'react'; -import PropTypes from 'prop-types'; -import { withStyles } from '@material-ui/core/styles'; -import Button from '@material-ui/core/Button'; +// ======================================================================== +import React from "react"; +import PropTypes from "prop-types"; +import { withStyles } from "@material-ui/core/styles"; +import Button from "@material-ui/core/Button"; -import Dialog from '@material-ui/core/Dialog'; -import MuiDialogTitle from '@material-ui/core/DialogTitle'; -import MuiDialogContent from '@material-ui/core/DialogContent'; -import MuiDialogActions from '@material-ui/core/DialogActions'; -import IconButton from '@material-ui/core/IconButton'; -import CloseIcon from '@material-ui/icons/Close'; -import Typography from '@material-ui/core/Typography'; -import { TextField } from '@material-ui/core'; +import Dialog from "@material-ui/core/Dialog"; +import MuiDialogTitle from "@material-ui/core/DialogTitle"; +import MuiDialogContent from "@material-ui/core/DialogContent"; +import MuiDialogActions from "@material-ui/core/DialogActions"; +import IconButton from "@material-ui/core/IconButton"; +import CloseIcon from "@material-ui/icons/Close"; +import Typography from "@material-ui/core/Typography"; +import { TextField } from "@material-ui/core"; import * as Yup from "yup"; -import { Formik } from 'formik'; +import { Formik } from "formik"; const styles = (theme) => ({ - root: { - margin: 0, - padding: theme.spacing(2), - }, - closeButton: { - position: 'absolute', - right: theme.spacing(1), - top: theme.spacing(1), - color: theme.palette.grey[500], - }, + root: { + margin: 0, + padding: theme.spacing(2), + }, + closeButton: { + position: "absolute", + right: theme.spacing(1), + top: theme.spacing(1), + color: theme.palette.grey[500], + }, }); const DialogTitle = withStyles(styles)((props) => { - const { children, classes, onClose, ...other } = props; - return ( - <MuiDialogTitle disableTypography className={classes.root} {...other}> - <Typography variant="h6">{children}</Typography> - {onClose ? ( - <IconButton className={classes.closeButton} onClick={onClose}> - <CloseIcon /> - </IconButton> - ) : null} - </MuiDialogTitle> - ); + const { children, classes, onClose, ...other } = props; + return ( + <MuiDialogTitle disableTypography className={classes.root} {...other}> + <Typography variant="h6">{children}</Typography> + {onClose ? ( + <IconButton className={classes.closeButton} onClick={onClose}> + <CloseIcon /> + </IconButton> + ) : null} + </MuiDialogTitle> + ); }); const DialogActions = withStyles((theme) => ({ - root: { - margin: 0, - padding: theme.spacing(1), - }, + root: { + margin: 0, + padding: theme.spacing(1), + }, }))(MuiDialogActions); const DialogContent = withStyles((theme) => ({ - root: { - padding: theme.spacing(2), - } + root: { + padding: theme.spacing(2), + }, }))(MuiDialogContent); -const schema = Yup.object( - { - name: Yup.string().required(), - description: Yup.string(), - }) +const schema = Yup.object({ + name: Yup.string().required(), + description: Yup.string(), +}); const ClusterProviderForm = (props) => { - const { onClose, item, open, onSubmit } = props; - const buttonLabel = item ? "OK" : "Create" - const title = item ? "Edit Cluster Provider" : "Register Cluster Provider" - const handleClose = () => { - onClose(); - }; - let initialValues = item ? { name: item.metadata.name, description: item.metadata.description } : { name: "", description: "" } + const { onClose, item, open, onSubmit } = props; + const buttonLabel = item ? "OK" : "Create"; + const title = item ? "Edit Cluster Provider" : "Register Cluster Provider"; + const handleClose = () => { + onClose(); + }; + let initialValues = item + ? { name: item.metadata.name, description: item.metadata.description } + : { name: "", description: "" }; - return ( - <Dialog maxWidth={"xs"} onClose={handleClose} aria-labelledby="customized-dialog-title" open={open} disableBackdropClick> - <DialogTitle id="simple-dialog-title">{title}</DialogTitle> - <Formik - initialValues={initialValues} - onSubmit={async values => { - onSubmit(values); - }} - validationSchema={schema} - > - {props => { - const { - values, - touched, - errors, - isSubmitting, - handleChange, - handleBlur, - handleSubmit - } = props; - return ( - <form noValidate onSubmit={handleSubmit}> - <DialogContent dividers> - <TextField - style={{ width: "100%", marginBottom: "10px" }} - id="name" - label="Provider name" - type="text" - value={values.name} - onChange={handleChange} - onBlur={handleBlur} - helperText={(errors.name && touched.name && ( - "Name is required" - ))} - required - error={errors.name && touched.name} - /> - <TextField - style={{ width: "100%", marginBottom: "25px" }} - name="description" - value={values.description} - onChange={handleChange} - onBlur={handleBlur} - id="description" - label="Description" - multiline - rowsMax={4} - /> - </DialogContent> - <DialogActions> - <Button autoFocus onClick={handleClose} color="secondary"> - Cancel - </Button> - <Button autoFocus type="submit" color="primary" disabled={isSubmitting}> - {buttonLabel} - </Button> - </DialogActions> - </form> - ); - }} - </Formik> - </Dialog> - ); + return ( + <Dialog + maxWidth={"xs"} + onClose={handleClose} + aria-labelledby="customized-dialog-title" + open={open} + disableBackdropClick + > + <DialogTitle id="simple-dialog-title">{title}</DialogTitle> + <Formik + initialValues={initialValues} + onSubmit={async (values) => { + onSubmit(values); + }} + validationSchema={schema} + > + {(props) => { + const { + values, + touched, + errors, + isSubmitting, + handleChange, + handleBlur, + handleSubmit, + } = props; + return ( + <form noValidate onSubmit={handleSubmit}> + <DialogContent dividers> + <TextField + style={{ width: "100%", marginBottom: "10px" }} + id="name" + label="Provider name" + type="text" + value={values.name} + onChange={handleChange} + onBlur={handleBlur} + helperText={errors.name && touched.name && "Name is required"} + required + error={errors.name && touched.name} + /> + <TextField + style={{ width: "100%", marginBottom: "25px" }} + name="description" + value={values.description} + onChange={handleChange} + onBlur={handleBlur} + id="description" + label="Description" + multiline + rowsMax={4} + /> + </DialogContent> + <DialogActions> + <Button autoFocus onClick={handleClose} color="secondary"> + Cancel + </Button> + <Button + autoFocus + type="submit" + color="primary" + disabled={isSubmitting} + > + {buttonLabel} + </Button> + </DialogActions> + </form> + ); + }} + </Formik> + </Dialog> + ); }; ClusterProviderForm.propTypes = { - onClose: PropTypes.func.isRequired, - open: PropTypes.bool.isRequired, - item: PropTypes.object + onClose: PropTypes.func.isRequired, + open: PropTypes.bool.isRequired, + item: PropTypes.object, }; export default ClusterProviderForm; diff --git a/src/tools/emcoui/src/admin/clusterProvider/ClusterProvidersAccordian.jsx b/src/tools/emcoui/src/admin/clusterProvider/ClusterProvidersAccordian.jsx index 20317695..192992bc 100644 --- a/src/tools/emcoui/src/admin/clusterProvider/ClusterProvidersAccordian.jsx +++ b/src/tools/emcoui/src/admin/clusterProvider/ClusterProvidersAccordian.jsx @@ -11,7 +11,7 @@ // 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. -// ======================================================================== +// ======================================================================== import React, { useState } from "react"; import { makeStyles } from "@material-ui/core/styles"; import Accordion from "@material-ui/core/Accordion"; @@ -22,11 +22,13 @@ import ExpandMoreIcon from "@material-ui/icons/ExpandMore"; import apiService from "../../services/apiService"; import { Button } from "@material-ui/core"; import DeleteIcon from "@material-ui/icons/Delete"; -import EditIcon from "@material-ui/icons/Edit"; +// import EditIcon from "@material-ui/icons/Edit"; import ClusterForm from "./clusters/ClusterForm"; import ClustersTable from "./clusters/ClusterTable"; import DeleteDialog from "../../common/Dialogue"; -import ClusterProviderForm from "../clusterProvider/ClusterProviderForm"; +import Notification from "../../common/Notification"; + +//import ClusterProviderForm from "../clusterProvider/ClusterProviderForm"; const useStyles = makeStyles((theme) => ({ root: { @@ -47,8 +49,9 @@ export default function ControlledAccordions({ data, setData, ...props }) { const [expanded, setExpanded] = useState(false); const [open, setOpen] = React.useState(false); const [formOpen, setFormOpen] = useState(false); - const [openProviderForm, setOpenProviderForm] = useState(false); + // const [openProviderForm, setOpenProviderForm] = useState(false); const [selectedRowIndex, setSelectedRowIndex] = useState(0); + const [notificationDetails, setNotificationDetails] = useState({}); const handleAccordianOpen = (providerRow) => (event, isExpanded) => { if (!isExpanded) { setExpanded(isExpanded ? providerRow : false); @@ -141,7 +144,7 @@ export default function ControlledAccordions({ data, setData, ...props }) { setSelectedRowIndex(index); setOpen(true); }; - const handleSubmit = (values) => { + const handleSubmit = (values, setSubmitting) => { let metadata = {}; if (values.userData) { metadata = JSON.parse(values.userData); @@ -150,7 +153,6 @@ export default function ControlledAccordions({ data, setData, ...props }) { metadata.description = values.description; const formData = new FormData(); formData.append("file", values.file); - // `{"metadata":{ "name": "${values.name}", "description": "${values.description}" }}` formData.append("metadata", `{"metadata":${JSON.stringify(metadata)}}`); formData.append("providerName", data[selectedRowIndex].metadata.name); apiService @@ -161,12 +163,24 @@ export default function ControlledAccordions({ data, setData, ...props }) { ? (data[selectedRowIndex].clusters = [res]) : data[selectedRowIndex].clusters.push(res); setData([...data]); + setFormOpen(false); + setNotificationDetails({ + show: true, + message: `${values.name} cluster added`, + severity: "success", + }); }) .catch((err) => { - console.log("error adding cluster : ", err); - }) - .finally(() => { - setFormOpen(false); + debugger; + if (err.response.status === 403) { + setNotificationDetails({ + show: true, + message: `${err.response.data}`, + severity: "error", + }); + setSubmitting(false); + } + console.log("error adding cluster : " + err); }); }; const handleFormClose = () => { @@ -198,35 +212,36 @@ export default function ControlledAccordions({ data, setData, ...props }) { setOpen(false); setSelectedRowIndex(0); }; - const handleEdit = (index) => { - setSelectedRowIndex(index); - setOpenProviderForm(true); - }; - const handleCloseProviderForm = () => { - setOpenProviderForm(false); - }; - const handleSubmitProviderForm = (values) => { - let request = { - payload: { metatada: values }, - providerName: data[selectedRowIndex].metadata.name, - }; - apiService - .updateClusterProvider(request) - .then((res) => { - setData((data) => { - data[selectedRowIndex].metadata = res.metadata; - return data; - }); - }) - .catch((err) => { - console.log("error updating cluster provider. " + err); - }) - .finally(() => { - setOpenProviderForm(false); - }); - }; + // const handleEdit = (index) => { + // setSelectedRowIndex(index); + // setOpenProviderForm(true); + // }; + // const handleCloseProviderForm = () => { + // setOpenProviderForm(false); + // }; + // const handleSubmitProviderForm = (values) => { + // let request = { + // payload: { metatada: values }, + // providerName: data[selectedRowIndex].metadata.name, + // }; + // apiService + // .updateClusterProvider(request) + // .then((res) => { + // setData((data) => { + // data[selectedRowIndex].metadata = res.metadata; + // return data; + // }); + // }) + // .catch((err) => { + // console.log("error updating cluster provider. " + err); + // }) + // .finally(() => { + // setOpenProviderForm(false); + // }); + // }; return ( <> + <Notification notificationDetails={notificationDetails} /> {data && data.length > 0 && ( <div className={classes.root}> <ClusterForm @@ -234,12 +249,12 @@ export default function ControlledAccordions({ data, setData, ...props }) { onClose={handleFormClose} onSubmit={handleSubmit} /> - <ClusterProviderForm + {/* <ClusterProviderForm open={openProviderForm} onClose={handleCloseProviderForm} onSubmit={handleSubmitProviderForm} item={data[selectedRowIndex]} - /> + /> */} <DeleteDialog open={open} onClose={handleClose} @@ -288,6 +303,8 @@ export default function ControlledAccordions({ data, setData, ...props }) { > Delete Provider </Button> + {/* + //edit cluster provider is not supported by the api yet <Button variant="outlined" size="small" @@ -299,7 +316,7 @@ export default function ControlledAccordions({ data, setData, ...props }) { }} > Edit Provider - </Button> + </Button> */} </div> <AccordionDetails> {item.clusters && ( diff --git a/src/tools/emcoui/src/admin/clusterProvider/clusters/ClusterForm.jsx b/src/tools/emcoui/src/admin/clusterProvider/clusters/ClusterForm.jsx index 6d9fc83b..6c49cb85 100644 --- a/src/tools/emcoui/src/admin/clusterProvider/clusters/ClusterForm.jsx +++ b/src/tools/emcoui/src/admin/clusterProvider/clusters/ClusterForm.jsx @@ -11,7 +11,7 @@ // 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. -// ======================================================================== +// ======================================================================== import React from "react"; import PropTypes from "prop-types"; import { withStyles } from "@material-ui/core/styles"; @@ -113,8 +113,8 @@ const ClusterForm = (props) => { <DialogTitle id="simple-dialog-title">{title}</DialogTitle> <Formik initialValues={initialValues} - onSubmit={async (values) => { - onSubmit(values); + onSubmit={(values, actions) => { + onSubmit(values, actions.setSubmitting); }} validationSchema={schema} > diff --git a/src/tools/emcoui/src/admin/clusterProvider/clusters/ClusterTable.jsx b/src/tools/emcoui/src/admin/clusterProvider/clusters/ClusterTable.jsx index 1066d472..26bc1ca9 100644 --- a/src/tools/emcoui/src/admin/clusterProvider/clusters/ClusterTable.jsx +++ b/src/tools/emcoui/src/admin/clusterProvider/clusters/ClusterTable.jsx @@ -11,309 +11,528 @@ // 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. -// ======================================================================== -import React, { useState } from 'react'; -import PropTypes from 'prop-types'; -import AddIconOutline from '@material-ui/icons/AddCircleOutline'; -import AddIcon from '@material-ui/icons/Add'; -import Table from '@material-ui/core/Table'; -import TableBody from '@material-ui/core/TableBody'; -import TableCell from '@material-ui/core/TableCell'; -import TableContainer from '@material-ui/core/TableContainer'; -import TableHead from '@material-ui/core/TableHead'; -import TableRow from '@material-ui/core/TableRow'; -import IconButton from '@material-ui/core/IconButton'; -import EditIcon from '@material-ui/icons/Edit'; -import Chip from '@material-ui/core/Chip'; -import SettingsEthernetIcon from '@material-ui/icons/SettingsEthernet'; -import DeleteIcon from '@material-ui/icons/Delete'; -import { makeStyles, TextField, Button } from '@material-ui/core'; +// ======================================================================== +import React, { useState } from "react"; +import PropTypes from "prop-types"; +import AddIconOutline from "@material-ui/icons/AddCircleOutline"; +import AddIcon from "@material-ui/icons/Add"; +import Table from "@material-ui/core/Table"; +import TableBody from "@material-ui/core/TableBody"; +import TableCell from "@material-ui/core/TableCell"; +import TableContainer from "@material-ui/core/TableContainer"; +import TableHead from "@material-ui/core/TableHead"; +import TableRow from "@material-ui/core/TableRow"; +import IconButton from "@material-ui/core/IconButton"; +// import EditIcon from "@material-ui/icons/Edit"; +import Chip from "@material-ui/core/Chip"; +import SettingsEthernetIcon from "@material-ui/icons/SettingsEthernet"; +import DeleteIcon from "@material-ui/icons/Delete"; +import { makeStyles, TextField, Button } from "@material-ui/core"; import NetworkForm from "../networks/NetworkForm"; import apiService from "../../../services/apiService"; import DeleteDialog from "../../../common/Dialogue"; -import CancelOutlinedIcon from '@material-ui/icons/CancelOutlined'; -import CheckIcon from '@material-ui/icons/CheckCircleOutlineOutlined'; -import InfoOutlinedIcon from '@material-ui/icons/InfoOutlined'; +import CancelOutlinedIcon from "@material-ui/icons/CancelOutlined"; +import CheckIcon from "@material-ui/icons/CheckCircleOutlineOutlined"; +import InfoOutlinedIcon from "@material-ui/icons/InfoOutlined"; import NetworkDetailsDialog from "../../../common/DetailsDialog"; -import DoneOutlineIcon from '@material-ui/icons/DoneOutline'; +import DoneOutlineIcon from "@material-ui/icons/DoneOutline"; import ClusterForm from "../clusters/ClusterForm"; +import Notification from "../../../common/Notification"; const useStyles = makeStyles((theme) => ({ - root: { - width: '100%', - }, - heading: { - fontSize: theme.typography.pxToRem(15), - flexBasis: '33.33%', - flexShrink: 0, - }, - secondaryHeading: { - fontSize: theme.typography.pxToRem(15), - color: theme.palette.text.secondary, - }, + root: { + width: "100%", + }, + heading: { + fontSize: theme.typography.pxToRem(15), + flexBasis: "33.33%", + flexShrink: 0, + }, + secondaryHeading: { + fontSize: theme.typography.pxToRem(15), + color: theme.palette.text.secondary, + }, })); const ClusterTable = ({ clustersData, ...props }) => { - const classes = useStyles(); - const [formOpen, setformOpen] = useState(false); - const [networkDetailsOpen, setNetworkDetailsOpen] = useState(false); - const [network, setNetwork] = useState({}); - const [activeRowIndex, setActiveRowIndex] = useState(0); - const [activeNetwork, setActiveNetwork] = useState({}); - const [open, setOpen] = useState(false); - const [openDeleteNetwork, setOpenDeleteNetwork] = useState(false); - const [showAddLabel, setShowAddLabel] = useState(false); - const [labelInput, setLabelInput] = React.useState(""); - const [clusterFormOpen, setClusterFormOpen] = useState(false); - const handleFormClose = () => { + const classes = useStyles(); + const [formOpen, setformOpen] = useState(false); + const [networkDetailsOpen, setNetworkDetailsOpen] = useState(false); + const [network, setNetwork] = useState({}); + const [activeRowIndex, setActiveRowIndex] = useState(0); + const [activeNetwork, setActiveNetwork] = useState({}); + const [open, setOpen] = useState(false); + const [openDeleteNetwork, setOpenDeleteNetwork] = useState(false); + const [showAddLabel, setShowAddLabel] = useState(false); + const [labelInput, setLabelInput] = useState(""); + // const [clusterFormOpen, setClusterFormOpen] = useState(false); + const [notificationDetails, setNotificationDetails] = useState({}); + const handleFormClose = () => { + setformOpen(false); + }; + const handleSubmit = (data) => { + let networkSpec = JSON.parse(data.spec); + let payload = { + metadata: { name: data.name, description: data.description }, + spec: networkSpec, + }; + let request = { + providerName: props.providerName, + clusterName: clustersData[activeRowIndex].metadata.name, + networkType: data.type, + payload: payload, + }; + apiService + .addNetwork(request) + .then((res) => { + let networkType = + data.type === "networks" ? "networks" : "providerNetworks"; + !clustersData[activeRowIndex][networkType] || + clustersData[activeRowIndex][networkType] === null + ? (clustersData[activeRowIndex][networkType] = [res]) + : clustersData[activeRowIndex][networkType].push(res); + }) + .catch((err) => { + console.log("error adding cluster network : ", err); + }) + .finally(() => { + setActiveRowIndex(0); setformOpen(false); - } - const handleSubmit = (data) => { - let networkSpec = JSON.parse(data.spec); - let payload = { metadata: { name: data.name, description: data.description }, spec: networkSpec }; - let request = { providerName: props.providerName, clusterName: clustersData[activeRowIndex].metadata.name, networkType: data.type, payload: payload }; - apiService.addNetwork(request).then(res => { - let networkType = (data.type === "networks" ? "networks" : "providerNetworks"); - (!clustersData[activeRowIndex][networkType] || clustersData[activeRowIndex][networkType] === null) ? (clustersData[activeRowIndex][networkType] = [res]) : clustersData[activeRowIndex][networkType].push(res); - }).catch(err => { - console.log("error adding cluster network : ", err) - }).finally(() => { - setActiveRowIndex(0); - setformOpen(false); + }); + }; + const handleAddNetwork = (index) => { + setActiveRowIndex(index); + setformOpen(true); + }; + const handleDeleteLabel = (index, label, labelIndex) => { + let request = { + providerName: props.providerName, + clusterName: clustersData[index].metadata.name, + labelName: label, + }; + apiService + .deleteClusterLabel(request) + .then((res) => { + console.log("label deleted"); + clustersData[index].labels.splice(labelIndex, 1); + props.onUpdateCluster(props.parentIndex, clustersData); + }) + .catch((err) => { + console.log("error deleting label : ", err); + }); + }; + const handleClose = (el) => { + if (el.target.innerText === "Delete") { + let request = { + providerName: props.providerName, + clusterName: clustersData[activeRowIndex].metadata.name, + }; + apiService + .deleteCluster(request) + .then(() => { + console.log("cluster deleted"); + props.onDeleteCluster(props.parentIndex, activeRowIndex); + }) + .catch((err) => { + console.log("Error deleting cluster : ", +err); + setNotificationDetails({ + show: true, + message: "Unable to remove cluster", + severity: "error", + }); }); } - const handleAddNetwork = (index) => { - setActiveRowIndex(index); - setformOpen(true); - } - const handleDeleteLabel = (index, label, labelIndex) => { - let request = { providerName: props.providerName, clusterName: clustersData[index].metadata.name, labelName: label } - apiService.deleteClusterLabel(request).then(res => { - console.log("label deleted"); - clustersData[index].labels.splice(labelIndex, 1); - props.onUpdateCluster(props.parentIndex, clustersData); - }).catch(err => { console.log("error deleting label : ", err) }) - } - const handleClose = el => { - if (el.target.innerText === "Delete") { - let request = { providerName: props.providerName, clusterName: clustersData[activeRowIndex].metadata.name }; - apiService.deleteCluster(request).then(() => { - console.log("cluster deleted"); - props.onDeleteCluster(props.parentIndex, activeRowIndex); - }).catch(err => { - console.log("Error deleting cluster : ", err) - }) - } - setOpen(false); - setActiveRowIndex(0); - }; + setOpen(false); + setActiveRowIndex(0); + }; - const handleCloseDeleteNetwork = (el) => { - if (el.target.innerText === "Delete") { - let networkName = clustersData[activeRowIndex][activeNetwork.networkType][activeNetwork.networkIndex].metadata.name; - let networkType = (activeNetwork.networkType === "providerNetworks" ? "provider-networks" : "networks"); - let request = { providerName: props.providerName, clusterName: clustersData[activeRowIndex].metadata.name, networkType: networkType, networkName: networkName }; - apiService.deleteClusterNetwork(request).then(() => { - console.log("cluster network deleted"); - clustersData[activeRowIndex][activeNetwork.networkType].splice(activeNetwork.networkIndex, 1); - }).catch(err => { - console.log("Error deleting cluster network : ", err) - }).finally(() => { setActiveRowIndex(0); setActiveNetwork({}); }) - } - setOpenDeleteNetwork(false); - } - const handleDeleteCluster = (index) => { - setActiveRowIndex(index); - setOpen(true); - } - const handleAddLabel = (index) => { - if (labelInput !== "") { - let request = { providerName: props.providerName, clusterName: clustersData[activeRowIndex].metadata.name, payload: { "label-name": labelInput } }; - apiService.addClusterLabel(request) - .then(res => { - (!clustersData[index].labels || clustersData[index].labels === null) ? (clustersData[index].labels = [res]) : clustersData[index].labels.push(res); - }) - .catch(err => { console.log("error adding label", err) }) - .finally(() => { - setShowAddLabel(!showAddLabel); - }) - } + const handleCloseDeleteNetwork = (el) => { + if (el.target.innerText === "Delete") { + let networkName = + clustersData[activeRowIndex][activeNetwork.networkType][ + activeNetwork.networkIndex + ].metadata.name; + let networkType = + activeNetwork.networkType === "providerNetworks" + ? "provider-networks" + : "networks"; + let request = { + providerName: props.providerName, + clusterName: clustersData[activeRowIndex].metadata.name, + networkType: networkType, + networkName: networkName, + }; + apiService + .deleteClusterNetwork(request) + .then(() => { + console.log("cluster network deleted"); + clustersData[activeRowIndex][activeNetwork.networkType].splice( + activeNetwork.networkIndex, + 1 + ); + }) + .catch((err) => { + console.log("Error deleting cluster network : ", err); + }) + .finally(() => { + setActiveRowIndex(0); + setActiveNetwork({}); + }); } - - const handleToggleAddLabel = (index) => { - setShowAddLabel(showAddLabel === index ? false : index); - setActiveRowIndex(index); - setLabelInput(''); + setOpenDeleteNetwork(false); + }; + const handleDeleteCluster = (index) => { + setActiveRowIndex(index); + setOpen(true); + }; + const handleAddLabel = (index) => { + if (labelInput !== "") { + let request = { + providerName: props.providerName, + clusterName: clustersData[activeRowIndex].metadata.name, + payload: { "label-name": labelInput }, + }; + apiService + .addClusterLabel(request) + .then((res) => { + !clustersData[index].labels || clustersData[index].labels === null + ? (clustersData[index].labels = [res]) + : clustersData[index].labels.push(res); + }) + .catch((err) => { + console.log("error adding label", err); + }) + .finally(() => { + setShowAddLabel(!showAddLabel); + }); } - const handleLabelInputChange = (event) => { - setLabelInput(event.target.value); - }; + }; - const handleNetworkDetailOpen = (network) => { - setNetwork(network); - setNetworkDetailsOpen(true); - } - const handleDeleteNetwork = (index, networkIndex, networkType, networkName) => { - setActiveNetwork({ networkIndex: networkIndex, networkType: networkType, name: networkName }); - setActiveRowIndex(index); - setOpenDeleteNetwork(true); - } - const applyNetworkConfig = (clusterName) => { - let request = { providerName: props.providerName, clusterName: clusterName } - apiService.applyNetworkConfig(request) - .then(res => { - console.log("Network config applied"); - }) - .catch(err => { - console.log("Error applying network config : ", err); - if (err.response) - console.log("Network config applied" + err.response.data); - else - console.log("Network config applied" + err); - }); - } - const handleClusterFormClose = () => { - setClusterFormOpen(false); - } - const handleClusterSubmit = (values) => { - const formData = new FormData(); - if (values.file) - formData.append('file', values.file); - formData.append("metadata", `{"metadata":{ "name": "${values.name}", "description": "${values.description}" }}`); - formData.append("providerName", props.providerName); - apiService.updateCluster(formData) - .then(res => { - clustersData[activeRowIndex].metadata = res.metadata; - props.onUpdateCluster(props.parentIndex, clustersData); - }) - .catch(err => { console.log("error updating cluster : ", err) }) - .finally(() => { handleClusterFormClose() }); + const handleToggleAddLabel = (index) => { + setShowAddLabel(showAddLabel === index ? false : index); + setActiveRowIndex(index); + setLabelInput(""); + }; + const handleLabelInputChange = (event) => { + setLabelInput(event.target.value); + }; - } - const handleEditCluster = (index) => { - setActiveRowIndex(index); - setClusterFormOpen(true); - } - return ( + const handleNetworkDetailOpen = (network) => { + setNetwork(network); + setNetworkDetailsOpen(true); + }; + const handleDeleteNetwork = ( + index, + networkIndex, + networkType, + networkName + ) => { + setActiveNetwork({ + networkIndex: networkIndex, + networkType: networkType, + name: networkName, + }); + setActiveRowIndex(index); + setOpenDeleteNetwork(true); + }; + const applyNetworkConfig = (clusterName) => { + let request = { + providerName: props.providerName, + clusterName: clusterName, + }; + apiService + .applyNetworkConfig(request) + .then((res) => { + setNotificationDetails({ + show: true, + message: "Network configuration applied", + severity: "success", + }); + console.log("Network config applied"); + }) + .catch((err) => { + setNotificationDetails({ + show: true, + message: "Error applying network configuration", + severity: "error", + }); + console.log("Error applying network config : ", err); + if (err.response) + console.log("Network config applied" + err.response.data); + else console.log("Network config applied" + err); + }); + }; + // const handleClusterFormClose = () => { + // setClusterFormOpen(false); + // }; + // const handleClusterSubmit = (values) => { + // const formData = new FormData(); + // if (values.file) formData.append("file", values.file); + // formData.append( + // "metadata", + // `{"metadata":{ "name": "${values.name}", "description": "${values.description}" }}` + // ); + // formData.append("providerName", props.providerName); + // apiService + // .updateCluster(formData) + // .then((res) => { + // clustersData[activeRowIndex].metadata = res.metadata; + // props.onUpdateCluster(props.parentIndex, clustersData); + // }) + // .catch((err) => { + // console.log("error updating cluster : ", err); + // }) + // .finally(() => { + // handleClusterFormClose(); + // }); + // }; + //disabling as edit is not supported yet by the api yet + // const handleEditCluster = (index) => { + // setActiveRowIndex(index); + // setClusterFormOpen(true); + // }; + return ( + <> + <Notification notificationDetails={notificationDetails} /> + {clustersData && clustersData.length > 0 && ( <> - {clustersData && (clustersData.length > 0) && - (<> - <ClusterForm item={clustersData[activeRowIndex]} open={clusterFormOpen} onClose={handleClusterFormClose} onSubmit={handleClusterSubmit} /> - <NetworkDetailsDialog onClose={setNetworkDetailsOpen} open={networkDetailsOpen} item={network} type="Network" /> - <NetworkForm onClose={handleFormClose} onSubmit={handleSubmit} open={formOpen} /> - <DeleteDialog open={open} onClose={handleClose} title={"Delete Cluster"} - content={`Are you sure you want to delete "${clustersData[activeRowIndex] ? clustersData[activeRowIndex].metadata.name : ""}" ?`} /> - <DeleteDialog open={openDeleteNetwork} onClose={handleCloseDeleteNetwork} title={"Delete Network"} content={`Are you sure you want to delete "${activeNetwork.name}" ?`} /> - <TableContainer > - <Table className={classes.table}> - <TableHead> - <TableRow> - <TableCell style={{ width: "10%" }}>Name</TableCell> - <TableCell style={{ width: "15%" }}>Description</TableCell> - <TableCell style={{ width: "20%" }}>Networks </TableCell> - <TableCell style={{ width: "35%" }}>Labels </TableCell> - <TableCell style={{ width: "20%" }}>Actions</TableCell> - </TableRow> - </TableHead> - <TableBody> - {clustersData.map((row, index) => ( - <TableRow key={row.metadata.name + "" + index}> - <TableCell >{row.metadata.name}</TableCell> - <TableCell >{row.metadata.description}</TableCell> - <TableCell> - <div> - {row.providerNetworks && (row.providerNetworks.length > 0) && row.providerNetworks.map((providerNetwork, providerNetworkIndex) => - (<Chip - key={providerNetwork.metadata.name + "" + providerNetworkIndex} - size="small" - icon={<InfoOutlinedIcon onClick={() => { handleNetworkDetailOpen(providerNetwork) }} style={{ cursor: "pointer" }} />} - onDelete={(e) => { handleDeleteNetwork(index, providerNetworkIndex, "providerNetworks", providerNetwork.metadata.name) }} - label={providerNetwork.metadata.name} - style={{ marginRight: "10px", marginBottom: "5px" }} - />) - )} + {/* <ClusterForm + item={clustersData[activeRowIndex]} + open={clusterFormOpen} + onClose={handleClusterFormClose} + onSubmit={handleClusterSubmit} + /> */} + <NetworkDetailsDialog + onClose={setNetworkDetailsOpen} + open={networkDetailsOpen} + item={network} + type="Network" + /> + <NetworkForm + onClose={handleFormClose} + onSubmit={handleSubmit} + open={formOpen} + /> + <DeleteDialog + open={open} + onClose={handleClose} + title={"Delete Cluster"} + content={`Are you sure you want to delete "${ + clustersData[activeRowIndex] + ? clustersData[activeRowIndex].metadata.name + : "" + }" ?`} + /> + <DeleteDialog + open={openDeleteNetwork} + onClose={handleCloseDeleteNetwork} + title={"Delete Network"} + content={`Are you sure you want to delete "${activeNetwork.name}" ?`} + /> + <TableContainer> + <Table className={classes.table}> + <TableHead> + <TableRow> + <TableCell style={{ width: "10%" }}>Name</TableCell> + <TableCell style={{ width: "15%" }}>Description</TableCell> + <TableCell style={{ width: "20%" }}>Networks </TableCell> + <TableCell style={{ width: "35%" }}>Labels </TableCell> + <TableCell style={{ width: "20%" }}>Actions</TableCell> + </TableRow> + </TableHead> + <TableBody> + {clustersData.map((row, index) => ( + <TableRow key={row.metadata.name + "" + index}> + <TableCell>{row.metadata.name}</TableCell> + <TableCell>{row.metadata.description}</TableCell> + <TableCell> + <div> + {row.providerNetworks && + row.providerNetworks.length > 0 && + row.providerNetworks.map( + (providerNetwork, providerNetworkIndex) => ( + <Chip + key={ + providerNetwork.metadata.name + + "" + + providerNetworkIndex + } + size="small" + icon={ + <InfoOutlinedIcon + onClick={() => { + handleNetworkDetailOpen(providerNetwork); + }} + style={{ cursor: "pointer" }} + /> + } + onDelete={(e) => { + handleDeleteNetwork( + index, + providerNetworkIndex, + "providerNetworks", + providerNetwork.metadata.name + ); + }} + label={providerNetwork.metadata.name} + style={{ + marginRight: "10px", + marginBottom: "5px", + }} + /> + ) + )} - {row.networks && (row.networks.length > 0) && row.networks.map((network, networkIndex) => - (<Chip - key={network.metadata.name + "" + networkIndex} - size="small" - icon={<InfoOutlinedIcon onClick={() => { handleNetworkDetailOpen(network) }} style={{ cursor: "pointer" }} />} - onDelete={(e) => { handleDeleteNetwork(index, networkIndex, "networks", network.metadata.name) }} - label={network.metadata.name} - style={{ marginRight: "10px", marginBottom: "5px" }} - color="secondary" - />) - )} - </div> - </TableCell> - <TableCell> - {row.labels && (row.labels.length > 0) && row.labels.map((label, labelIndex) => - (<Chip - key={label["label-name"] + "" + labelIndex} - size="small" - icon={<SettingsEthernetIcon />} - label={label["label-name"]} - onDelete={(e) => { handleDeleteLabel(index, label["label-name"], labelIndex) }} - color="primary" - style={{ marginRight: "10px" }} - />) - )} - {(showAddLabel === index) && - <TextField - style={{ height: "24px" }} - size="small" - value={labelInput} - onChange={handleLabelInputChange} - id="outlined-basic" label="Add label" variant="outlined" /> - } - {(showAddLabel === index) && - <IconButton color="primary" onClick={() => { handleAddLabel(index) }}> - <CheckIcon /> - </IconButton> - } - <IconButton color="primary" onClick={() => { handleToggleAddLabel(index) }}> - {!(showAddLabel === index) && <AddIconOutline />} - {(showAddLabel === index) && <CancelOutlinedIcon color="secondary" />} - </IconButton> - </TableCell> - <TableCell> - <Button - variant="outlined" - startIcon={<AddIcon />} - size="small" - color="primary" - title="Add Network" - onClick={() => { handleAddNetwork(index) }}> - Network - </Button> - <IconButton - style={{ color: "green" }} - onClick={() => { applyNetworkConfig(row.metadata.name) }} - title="Apply Network Configuration"> - <DoneOutlineIcon /> - </IconButton> - <IconButton - title="Edit" - onClick={() => { handleEditCluster(index) }} - color="primary"> - <EditIcon /> - </IconButton> - <IconButton - title="Delete" - color="secondary" - onClick={() => { handleDeleteCluster(index) }}> - <DeleteIcon /> - </IconButton> - </TableCell> - </TableRow>))} - </TableBody> - </Table> - </TableContainer> - </>)} - {(!clustersData || (clustersData.length === 0)) && (<span>No Clusters</span>)} - </>) -} + {row.networks && + row.networks.length > 0 && + row.networks.map((network, networkIndex) => ( + <Chip + key={network.metadata.name + "" + networkIndex} + size="small" + icon={ + <InfoOutlinedIcon + onClick={() => { + handleNetworkDetailOpen(network); + }} + style={{ cursor: "pointer" }} + /> + } + onDelete={(e) => { + handleDeleteNetwork( + index, + networkIndex, + "networks", + network.metadata.name + ); + }} + label={network.metadata.name} + style={{ + marginRight: "10px", + marginBottom: "5px", + }} + color="secondary" + /> + ))} + </div> + </TableCell> + <TableCell> + {row.labels && + row.labels.length > 0 && + row.labels.map((label, labelIndex) => ( + <Chip + key={label["label-name"] + "" + labelIndex} + size="small" + icon={<SettingsEthernetIcon />} + label={label["label-name"]} + onDelete={(e) => { + handleDeleteLabel( + index, + label["label-name"], + labelIndex + ); + }} + color="primary" + style={{ marginRight: "10px" }} + /> + ))} + {showAddLabel === index && ( + <TextField + style={{ height: "24px" }} + size="small" + value={labelInput} + onChange={handleLabelInputChange} + id="outlined-basic" + label="Add label" + variant="outlined" + /> + )} + {showAddLabel === index && ( + <IconButton + color="primary" + onClick={() => { + handleAddLabel(index); + }} + > + <CheckIcon /> + </IconButton> + )} + <IconButton + color="primary" + onClick={() => { + handleToggleAddLabel(index); + }} + > + {!(showAddLabel === index) && <AddIconOutline />} + {showAddLabel === index && ( + <CancelOutlinedIcon color="secondary" /> + )} + </IconButton> + </TableCell> + <TableCell> + <Button + variant="outlined" + startIcon={<AddIcon />} + size="small" + color="primary" + title="Add Network" + onClick={() => { + handleAddNetwork(index); + }} + > + Network + </Button> + <IconButton + color="primary" + disabled={ + !( + (row.networks && row.networks.length > 0) || + (row.providerNetworks && + row.providerNetworks.length > 0) + ) + } + onClick={() => { + applyNetworkConfig(row.metadata.name); + }} + title="Apply Network Configuration" + > + <DoneOutlineIcon /> + </IconButton> + {/* + //disabling as edit is not supported yet by the api yet + <IconButton + title="Edit" + onClick={() => { handleEditCluster(index) }} + color="primary"> + <EditIcon /> + </IconButton> */} + <IconButton + title="Delete" + color="secondary" + disabled={ + (row.networks && row.networks.length > 0) || + (row.providerNetworks && + row.providerNetworks.length > 0) || + (row.labels && row.labels.length > 0) + } + onClick={() => { + handleDeleteCluster(index); + }} + > + <DeleteIcon /> + </IconButton> + </TableCell> + </TableRow> + ))} + </TableBody> + </Table> + </TableContainer> + </> + )} + {(!clustersData || clustersData.length === 0) && <span>No Clusters</span>} + </> + ); +}; ClusterTable.propTypes = { - clusters: PropTypes.arrayOf(PropTypes.object) + clusters: PropTypes.arrayOf(PropTypes.object), }; export default ClusterTable; diff --git a/src/tools/emcoui/src/admin/controllers/Controllers.jsx b/src/tools/emcoui/src/admin/controllers/Controllers.jsx index 4a8a502c..4316f6ea 100644 --- a/src/tools/emcoui/src/admin/controllers/Controllers.jsx +++ b/src/tools/emcoui/src/admin/controllers/Controllers.jsx @@ -29,7 +29,8 @@ function Controllers() { apiService .getControllers() .then((res) => { - setControllersData(res); + if (res && res.length > 0) setControllersData(res); + else setControllersData([]); }) .catch((err) => { console.log("error getting controllers : " + err); @@ -53,9 +54,7 @@ function Controllers() { .addController(request) .then((res) => { setControllersData((controllersData) => { - if (controllersData && controllersData.length > 0) - return [...controllersData, res]; - else return [res]; + return [...controllersData, res]; }); }) .catch((err) => { diff --git a/src/tools/emcoui/src/admin/projects/ProjectForm.jsx b/src/tools/emcoui/src/admin/projects/ProjectForm.jsx index 751de5d0..4ea87b2c 100644 --- a/src/tools/emcoui/src/admin/projects/ProjectForm.jsx +++ b/src/tools/emcoui/src/admin/projects/ProjectForm.jsx @@ -11,146 +11,157 @@ // 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. -// ======================================================================== -import React from 'react'; -import PropTypes from 'prop-types'; -import { withStyles } from '@material-ui/core/styles'; -import Button from '@material-ui/core/Button'; +// ======================================================================== +import React from "react"; +import PropTypes from "prop-types"; +import { withStyles } from "@material-ui/core/styles"; +import Button from "@material-ui/core/Button"; -import Dialog from '@material-ui/core/Dialog'; -import MuiDialogTitle from '@material-ui/core/DialogTitle'; -import MuiDialogContent from '@material-ui/core/DialogContent'; -import MuiDialogActions from '@material-ui/core/DialogActions'; -import IconButton from '@material-ui/core/IconButton'; -import CloseIcon from '@material-ui/icons/Close'; -import Typography from '@material-ui/core/Typography'; -import { TextField } from '@material-ui/core'; +import Dialog from "@material-ui/core/Dialog"; +import MuiDialogTitle from "@material-ui/core/DialogTitle"; +import MuiDialogContent from "@material-ui/core/DialogContent"; +import MuiDialogActions from "@material-ui/core/DialogActions"; +import IconButton from "@material-ui/core/IconButton"; +import CloseIcon from "@material-ui/icons/Close"; +import Typography from "@material-ui/core/Typography"; +import { TextField } from "@material-ui/core"; import * as Yup from "yup"; -import { Formik } from 'formik'; +import { Formik } from "formik"; const styles = (theme) => ({ - root: { - margin: 0, - padding: theme.spacing(2), - }, - closeButton: { - position: 'absolute', - right: theme.spacing(1), - top: theme.spacing(1), - color: theme.palette.grey[500], - }, + root: { + margin: 0, + padding: theme.spacing(2), + }, + closeButton: { + position: "absolute", + right: theme.spacing(1), + top: theme.spacing(1), + color: theme.palette.grey[500], + }, }); const DialogTitle = withStyles(styles)((props) => { - const { children, classes, onClose, ...other } = props; - return ( - <MuiDialogTitle disableTypography className={classes.root} {...other}> - <Typography variant="h6">{children}</Typography> - {onClose ? ( - <IconButton className={classes.closeButton} onClick={onClose}> - <CloseIcon /> - </IconButton> - ) : null} - </MuiDialogTitle> - ); + const { children, classes, onClose, ...other } = props; + return ( + <MuiDialogTitle disableTypography className={classes.root} {...other}> + <Typography variant="h6">{children}</Typography> + {onClose ? ( + <IconButton className={classes.closeButton} onClick={onClose}> + <CloseIcon /> + </IconButton> + ) : null} + </MuiDialogTitle> + ); }); const DialogActions = withStyles((theme) => ({ - root: { - margin: 0, - padding: theme.spacing(1), - }, + root: { + margin: 0, + padding: theme.spacing(1), + }, }))(MuiDialogActions); const DialogContent = withStyles((theme) => ({ - root: { - padding: theme.spacing(2), - } + root: { + padding: theme.spacing(2), + }, }))(MuiDialogContent); -const schema = Yup.object( - { - name: Yup.string().required(), - description: Yup.string(), - }) +const schema = Yup.object({ + name: Yup.string().required(), + description: Yup.string(), +}); const ProjectFormFunc = (props) => { - const { onClose, item, open, onSubmit } = props; - const buttonLabel = item ? "OK" : "Create" - const title = item ? "Edit Project" : "Create Project" - const handleClose = () => { - onClose(); - }; - let initialValues = item ? { name: item.metadata.name, description: item.metadata.description } : { name: "", description: "" } + const { onClose, item, open, onSubmit } = props; + const buttonLabel = item ? "OK" : "Create"; + const title = item ? "Edit Project" : "Create Project"; + const handleClose = () => { + onClose(); + }; + let initialValues = item + ? { name: item.metadata.name, description: item.metadata.description } + : { name: "", description: "" }; - return ( - <Dialog maxWidth={"xs"} onClose={handleClose} aria-labelledby="customized-dialog-title" open={open} disableBackdropClick> - <DialogTitle id="simple-dialog-title">{title}</DialogTitle> - <Formik - initialValues={initialValues} - onSubmit={async values => { - onSubmit(values); - }} - validationSchema={schema} - > - {props => { - const { - values, - touched, - errors, - isSubmitting, - handleChange, - handleBlur, - handleSubmit - } = props; - return ( - <form noValidate onSubmit={handleSubmit}> - <DialogContent dividers> - <TextField - style={{ width: "100%", marginBottom: "10px" }} - id="name" - label="Project name" - type="text" - value={values.name} - onChange={handleChange} - onBlur={handleBlur} - helperText={(errors.name && touched.name && ( - "Name is required" - ))} - required - error={errors.name && touched.name} - /> - <TextField - style={{ width: "100%", marginBottom: "25px" }} - name="description" - value={values.description} - onChange={handleChange} - onBlur={handleBlur} - id="description" - label="Description" - multiline - rowsMax={4} - /> - </DialogContent> - <DialogActions> - <Button autoFocus onClick={handleClose} color="secondary"> - Cancel - </Button> - <Button autoFocus type="submit" color="primary" disabled={isSubmitting}> - {buttonLabel} - </Button> - </DialogActions> - </form> - ); - }} - </Formik> - </Dialog> - ); + return ( + <Dialog + maxWidth={"xs"} + onClose={handleClose} + aria-labelledby="customized-dialog-title" + open={open} + disableBackdropClick + > + <DialogTitle id="simple-dialog-title">{title}</DialogTitle> + <Formik + initialValues={initialValues} + onSubmit={async (values) => { + onSubmit(values); + }} + validationSchema={schema} + > + {(props) => { + const { + values, + touched, + errors, + isSubmitting, + handleChange, + handleBlur, + handleSubmit, + } = props; + return ( + <form noValidate onSubmit={handleSubmit}> + <DialogContent dividers> + <TextField + style={{ width: "100%", marginBottom: "10px" }} + id="name" + label="Project name" + type="text" + value={values.name} + onChange={handleChange} + onBlur={handleBlur} + helperText={errors.name && touched.name && "Name is required"} + required + disabled={item} + error={errors.name && touched.name} + /> + <TextField + style={{ width: "100%", marginBottom: "25px" }} + name="description" + value={values.description} + onChange={handleChange} + onBlur={handleBlur} + id="description" + label="Description" + multiline + rowsMax={4} + /> + </DialogContent> + <DialogActions> + <Button autoFocus onClick={handleClose} color="secondary"> + Cancel + </Button> + <Button + autoFocus + type="submit" + color="primary" + disabled={isSubmitting} + > + {buttonLabel} + </Button> + </DialogActions> + </form> + ); + }} + </Formik> + </Dialog> + ); }; ProjectFormFunc.propTypes = { - onClose: PropTypes.func.isRequired, - open: PropTypes.bool.isRequired, + onClose: PropTypes.func.isRequired, + open: PropTypes.bool.isRequired, }; export default ProjectFormFunc; diff --git a/src/tools/emcoui/src/admin/projects/ProjectsTable.jsx b/src/tools/emcoui/src/admin/projects/ProjectsTable.jsx index d96d44fa..fb03155d 100644 --- a/src/tools/emcoui/src/admin/projects/ProjectsTable.jsx +++ b/src/tools/emcoui/src/admin/projects/ProjectsTable.jsx @@ -11,7 +11,7 @@ // 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. -// ======================================================================== +// ======================================================================== import React from "react"; import { withStyles, makeStyles } from "@material-ui/core/styles"; import Table from "@material-ui/core/Table"; @@ -24,118 +24,142 @@ import Paper from "@material-ui/core/Paper"; import { Link } from "react-router-dom"; import IconButton from "@material-ui/core/IconButton"; import EditIcon from "@material-ui/icons/Edit"; -import DeleteDialog from "../../common/Dialogue" +import DeleteDialog from "../../common/Dialogue"; import DeleteIcon from "@material-ui/icons/Delete"; import ProjectForm from "./ProjectForm"; import apiService from "../../services/apiService"; const StyledTableCell = withStyles((theme) => ({ - body: { - fontSize: 14, - }, + body: { + fontSize: 14, + }, }))(TableCell); const StyledTableRow = withStyles((theme) => ({ - root: { - "&:nth-of-type(odd)": { - backgroundColor: theme.palette.action.hover, - }, + root: { + "&:nth-of-type(odd)": { + backgroundColor: theme.palette.action.hover, }, + }, }))(TableRow); const useStyles = makeStyles({ - table: { - minWidth: 350, - }, - cell: { - color: "grey", - }, + table: { + minWidth: 350, + }, + cell: { + color: "grey", + }, }); export default function ProjectsTable(props) { - const classes = useStyles(); - const [open, setOpen] = React.useState(false); - const [openForm, setOpenForm] = React.useState(false); - const [index, setIndex] = React.useState(0); + const classes = useStyles(); + const [open, setOpen] = React.useState(false); + const [openForm, setOpenForm] = React.useState(false); + const [index, setIndex] = React.useState(0); - let handleEdit = index => { - setIndex(index); - setOpenForm(true); - } - const handleClose = el => { - if (el.target.innerText === "Delete") { - apiService.deleteProject(props.data[index].metadata.name).then(() => { - console.log("project deleted"); - props.data.splice(index, 1); - props.setProjectsData([...props.data]); - }).catch(err => { - console.log("Error deleting project : ", err) - }) - } - setOpen(false); - setIndex(0); - }; - const handleFormClose = () => { - setIndex(0); - setOpenForm(false); - }; - const handleDelete = (index) => { - setIndex(index); - setOpen(true); - } - const handleSubmit = (data) => { - let payload = { "metadata": data } - apiService.updateProject(payload).then(res => { - props.data[index] = res; - props.setProjectsData([...props.data]); - }).catch(err => { - console.log("Error updating project : ", err); + let handleEdit = (index) => { + setIndex(index); + setOpenForm(true); + }; + const handleClose = (el) => { + if (el.target.innerText === "Delete") { + apiService + .deleteProject(props.data[index].metadata.name) + .then(() => { + console.log("project deleted"); + props.data.splice(index, 1); + props.setProjectsData([...props.data]); }) - setOpenForm(false); - }; - - return ( - <React.Fragment> - {(props.data && props.data.length > 0) && - <> - <ProjectForm open={openForm} onClose={handleFormClose} item={props.data[index]} onSubmit={handleSubmit} /> - <DeleteDialog open={open} onClose={handleClose} title={"Delete Project"} - content={`Are you sure you want to delete "${props.data[index] ? props.data[index].metadata.name : ""}" ?`} /> - <TableContainer component={Paper}> - <Table className={classes.table} size="small"> - <TableHead> - <TableRow> - <StyledTableCell>Name</StyledTableCell> - <StyledTableCell>Description</StyledTableCell> - <StyledTableCell>Actions</StyledTableCell> - </TableRow> - </TableHead> - <TableBody> - {props.data.map((row, index) => ( - <StyledTableRow key={row.metadata.name + "" + index}> - <StyledTableCell> - {" "} - <Link to={`/app/projects/${row.metadata.name}`}>{row.metadata.name}</Link> - </StyledTableCell> - <StyledTableCell className={classes.cell}> - {row.metadata.description} - </StyledTableCell> - <StyledTableCell className={classes.cell}> - <IconButton onClick={(e) => handleEdit(index)} title="Edit" > - <EditIcon color="primary" /> - </IconButton> - <IconButton onClick={(e) => handleDelete(index)} title="Delete" > - <DeleteIcon color="secondary" /> - </IconButton> - </StyledTableCell> - </StyledTableRow> - ))} - </TableBody> - </Table> - </TableContainer> - </> - } + .catch((err) => { + console.log("Error deleting project : ", err); + }); + } + setOpen(false); + setIndex(0); + }; + const handleFormClose = () => { + setIndex(0); + setOpenForm(false); + }; + const handleDelete = (index) => { + setIndex(index); + setOpen(true); + }; + const handleSubmit = (data) => { + let payload = { metadata: data }; + apiService + .updateProject(payload) + .then((res) => { + props.data[index] = res; + props.setProjectsData([...props.data]); + }) + .catch((err) => { + console.log("Error updating project : ", err); + }); + setOpenForm(false); + }; - </React.Fragment> - ); + return ( + <React.Fragment> + {props.data && props.data.length > 0 && ( + <> + <ProjectForm + open={openForm} + onClose={handleFormClose} + item={props.data[index]} + onSubmit={handleSubmit} + /> + <DeleteDialog + open={open} + onClose={handleClose} + title={"Delete Project"} + content={`Are you sure you want to delete "${ + props.data[index] ? props.data[index].metadata.name : "" + }" ?`} + /> + <TableContainer component={Paper}> + <Table className={classes.table} size="small"> + <TableHead> + <TableRow> + <StyledTableCell>Name</StyledTableCell> + <StyledTableCell>Description</StyledTableCell> + <StyledTableCell>Actions</StyledTableCell> + </TableRow> + </TableHead> + <TableBody> + {props.data.map((row, index) => ( + <StyledTableRow key={row.metadata.name + "" + index}> + <StyledTableCell> + {" "} + <Link to={`/app/projects/${row.metadata.name}`}> + {row.metadata.name} + </Link> + </StyledTableCell> + <StyledTableCell className={classes.cell}> + {row.metadata.description} + </StyledTableCell> + <StyledTableCell className={classes.cell}> + <IconButton + onClick={(e) => handleEdit(index)} + title="Edit" + > + <EditIcon color="primary" /> + </IconButton> + <IconButton + onClick={(e) => handleDelete(index)} + title="Delete" + > + <DeleteIcon color="secondary" /> + </IconButton> + </StyledTableCell> + </StyledTableRow> + ))} + </TableBody> + </Table> + </TableContainer> + </> + )} + </React.Fragment> + ); } diff --git a/src/tools/emcoui/src/appbase/AppBase.js b/src/tools/emcoui/src/appbase/AppBase.js index 5dd3b53b..76dc4d8e 100644 --- a/src/tools/emcoui/src/appbase/AppBase.js +++ b/src/tools/emcoui/src/appbase/AppBase.js @@ -11,7 +11,7 @@ // 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. -// ======================================================================== +// ======================================================================== import React from "react"; import PropTypes from "prop-types"; import { ThemeProvider, withStyles } from "@material-ui/core/styles"; @@ -25,6 +25,7 @@ import theme from "../theme/Theme"; import apiService from "../services/apiService"; import DeploymentIntentGroups from "../deploymentIntentGroups/DeploymentIntentGroups"; import { Switch, Route, Link } from "react-router-dom"; +// import Dashboard from "../dashboard/DashboardView"; const drawerWidth = 256; const styles = { @@ -45,11 +46,10 @@ const styles = { }, main: { flex: 1, - padding: theme.spacing(6, 4), + padding: theme.spacing(3, 4, 6, 4), background: "#eaeff1", }, footer: { - // padding: theme.spacing(2), background: "#eaeff1", }, }; @@ -63,17 +63,6 @@ class AppBase extends React.Component { }; } - componentDidMount() { - apiService - .getCompositeApps({ projectName: this.state.projectName }) - .then((response) => { - this.setState({ data: response }); - }) - .catch((err) => { - console.log("Unable to get composite apps"); - }) - .finally(); - } setMobileOpen = (mobileOpen) => { this.setState({ mobileOpen }); }; @@ -114,15 +103,15 @@ class AppBase extends React.Component { path={`${this.props.match.url}/404`} component={() => <div>Page Not found</div>} /> - <Route - exact - path={`${this.props.match.url}/composite-apps`} - > + {/* <Route exact path={`${this.props.match.url}/dashboard`}> + <Dashboard projectName={this.state.projectName} /> + </Route> */} + <Route exact path={`${this.props.match.url}/services`}> <CompositeApps projectName={this.state.projectName} /> </Route> <Route exact - path={`${this.props.match.url}/composite-apps/:appname/:version`} + path={`${this.props.match.url}/services/:appname/:version`} > <CompositeApp projectName={this.state.projectName} /> </Route> diff --git a/src/tools/emcoui/src/appbase/Content.js b/src/tools/emcoui/src/appbase/Content.js index 2c907acb..eff9a561 100644 --- a/src/tools/emcoui/src/appbase/Content.js +++ b/src/tools/emcoui/src/appbase/Content.js @@ -95,7 +95,7 @@ function Content(props) { </AppBar> <div className={classes.contentWrapper}> <Typography color="textSecondary" align="center"> - No composite apps for this project yet + No services for this project yet </Typography> </div> </Paper> diff --git a/src/tools/emcoui/src/appbase/Header.js b/src/tools/emcoui/src/appbase/Header.js index 0222151f..19f148a4 100644 --- a/src/tools/emcoui/src/appbase/Header.js +++ b/src/tools/emcoui/src/appbase/Header.js @@ -11,7 +11,7 @@ // 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. -// ======================================================================== +// ======================================================================== import React from "react"; import PropTypes from "prop-types"; import AppBar from "@material-ui/core/AppBar"; @@ -57,18 +57,18 @@ function Header(props) { let headerName = ""; let getHeaderName = () => { - if (location.pathname === `${props.match.url}/composite-apps`) { - headerName = "Composite Apps"; + if (location.pathname === `${props.match.url}/dashboard`) { + headerName = "Dashboard"; + } else if (location.pathname === `${props.match.url}/services`) { + headerName = "Services"; } else if ( location.pathname === `${props.match.url}/deployment-intent-group` ) { headerName = "Deployment Intent Groups"; - } else if (location.pathname.includes("composite-apps")) { + } else if (location.pathname.includes("services")) { headerName = - "Composite Apps / " + - location.pathname - .slice(location.pathname.indexOf("composite-apps")) - .slice(15); + "services / " + + location.pathname.slice(location.pathname.indexOf("services")).slice(9); } else if (location.pathname === `${props.match.url}/projects`) { headerName = "Projects"; } else if (location.pathname === `${props.match.url}/clusters`) { diff --git a/src/tools/emcoui/src/appbase/Navigator.js b/src/tools/emcoui/src/appbase/Navigator.js index 2df2c009..e8f16367 100644 --- a/src/tools/emcoui/src/appbase/Navigator.js +++ b/src/tools/emcoui/src/appbase/Navigator.js @@ -11,7 +11,7 @@ // 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. -// ======================================================================== +// ======================================================================== import React, { useState } from "react"; import PropTypes from "prop-types"; import clsx from "clsx"; @@ -23,7 +23,7 @@ import ListItem from "@material-ui/core/ListItem"; import ListItemIcon from "@material-ui/core/ListItemIcon"; import ListItemText from "@material-ui/core/ListItemText"; import HomeIcon from "@material-ui/icons/Home"; -import AppsIcon from "@material-ui/icons/Apps"; +import DeviceHubIcon from "@material-ui/icons/DeviceHub"; import DnsRoundedIcon from "@material-ui/icons/DnsRounded"; import { withRouter, Link } from "react-router-dom"; @@ -32,9 +32,9 @@ const categories = [ id: "1", children: [ { - id: "Composite Apps", - icon: <AppsIcon />, - url: "/composite-apps", + id: "Services", + icon: <DeviceHubIcon />, + url: "/services", }, { id: "Deployment Intent Groups", @@ -85,8 +85,8 @@ const styles = (theme) => ({ marginTop: theme.spacing(2), }, version: { - fontSize: "15px" - } + fontSize: "15px", + }, }); function Navigator(props) { @@ -99,11 +99,20 @@ function Navigator(props) { setActiveTab(location.pathname); } return ( - <Drawer PaperProps={props.PaperProps} variant={props.variant} open={props.open} onClose={props.onClose}> + <Drawer + PaperProps={props.PaperProps} + variant={props.variant} + open={props.open} + onClose={props.onClose} + > <List disablePadding> - <Link style={{ textDecoration: "none" }} to='/'> + <Link style={{ textDecoration: "none" }} to="/"> <ListItem - className={clsx(classes.firebase, classes.item, classes.itemCategory)} + className={clsx( + classes.firebase, + classes.item, + classes.itemCategory + )} > <ListItemText classes={{ @@ -111,14 +120,29 @@ function Navigator(props) { }} > ONAP4K8s - </ListItemText> - <span - className={clsx(classes.version)} - >{process.env.REACT_APP_VERSION}</span> + </ListItemText> + <span className={clsx(classes.version)}> + {process.env.REACT_APP_VERSION} + </span> </ListItem> </Link> - <ListItem className={clsx(classes.item, classes.itemCategory)}> + {/* <Link + style={{ textDecoration: "none" }} + to={{ + pathname: `${props.match.url}/dashboard`, + activeItem: "childId", + }} + key={"childId"} + > */} + <ListItem + button + className={clsx( + classes.item, + classes.itemCategory, + activeItem.includes("dashboard") && classes.itemActiveItem + )} + > <ListItemIcon className={classes.itemIcon}> <HomeIcon /> </ListItemIcon> @@ -130,10 +154,18 @@ function Navigator(props) { Dashboard </ListItemText> </ListItem> + {/* </Link> */} {categories.map(({ id, children }) => ( <React.Fragment key={id}> {children.map(({ id: childId, icon, url }) => ( - <Link style={{ textDecoration: "none" }} to={{ pathname: `${props.match.url}${url}`, activeItem: childId }} key={childId}> + <Link + style={{ textDecoration: "none" }} + to={{ + pathname: `${props.match.url}${url}`, + activeItem: childId, + }} + key={childId} + > <ListItem button className={clsx( diff --git a/src/tools/emcoui/src/assets/icons/empty.svg b/src/tools/emcoui/src/assets/icons/empty.svg new file mode 100644 index 00000000..f4e020ed --- /dev/null +++ b/src/tools/emcoui/src/assets/icons/empty.svg @@ -0,0 +1 @@ +<svg enable-background="new 0 0 24 24" height="512" viewBox="0 0 24 24" width="512" xmlns="http://www.w3.org/2000/svg"><path d="m21.5 22h-19c-1.378 0-2.5-1.121-2.5-2.5v-7c0-.07.015-.141.044-.205l3.969-8.82c.404-.896 1.299-1.475 2.28-1.475h11.414c.981 0 1.876.579 2.28 1.475l3.969 8.82c.029.064.044.135.044.205v7c0 1.379-1.122 2.5-2.5 2.5zm-20.5-9.393v6.893c0 .827.673 1.5 1.5 1.5h19c.827 0 1.5-.673 1.5-1.5v-6.893l-3.925-8.723c-.242-.536-.779-.884-1.368-.884h-11.414c-.589 0-1.126.348-1.368.885z"/><path d="m16.807 17h-9.614c-.622 0-1.186-.391-1.404-.973l-1.014-2.703c-.072-.194-.26-.324-.468-.324h-3.557c-.276 0-.5-.224-.5-.5s.224-.5.5-.5h3.557c.622 0 1.186.391 1.405.973l1.013 2.703c.073.194.261.324.468.324h9.613c.208 0 .396-.13.468-.324l1.013-2.703c.22-.582.784-.973 1.406-.973h3.807c.276 0 .5.224.5.5s-.224.5-.5.5h-3.807c-.208 0-.396.13-.468.324l-1.013 2.703c-.219.582-.784.973-1.405.973z"/></svg>
\ No newline at end of file diff --git a/src/tools/emcoui/src/common/ExpandableCard.jsx b/src/tools/emcoui/src/common/ExpandableCard.jsx new file mode 100644 index 00000000..1d2ea9e6 --- /dev/null +++ b/src/tools/emcoui/src/common/ExpandableCard.jsx @@ -0,0 +1,94 @@ +//======================================================================= +// Copyright (c) 2017-2020 Aarna Networks, Inc. +// All rights reserved. +// ====================================================================== +// 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. +// ======================================================================== + +import React, { useState } from "react"; +import { makeStyles } from "@material-ui/core/styles"; +import clsx from "clsx"; +import Card from "@material-ui/core/Card"; +import CardHeader from "@material-ui/core/CardHeader"; +import CardContent from "@material-ui/core/CardContent"; +import Collapse from "@material-ui/core/Collapse"; +import IconButton from "@material-ui/core/IconButton"; +import ExpandMoreIcon from "@material-ui/icons/ExpandMore"; +import StorageIcon from "@material-ui/icons/Storage"; +import ErrorIcon from "@material-ui/icons/Error"; + +const useStyles = makeStyles((theme) => ({ + root: { + width: "100%", + }, + expand: { + transform: "rotate(0deg)", + marginLeft: "auto", + transition: theme.transitions.create("transform", { + duration: theme.transitions.duration.shortest, + }), + }, + expandOpen: { + transform: "rotate(180deg)", + }, +})); +const ExpandableCard = (props) => { + const classes = useStyles(); + const [expanded, setExpanded] = useState(false); + + const handleExpandClick = () => { + if (!expanded) { + setExpanded(!expanded); + } else { + setExpanded(!expanded); + } + }; + + return ( + <> + <Card className={classes.root}> + <CardHeader + onClick={handleExpandClick} + avatar={ + <> + <StorageIcon fontSize="large" /> + </> + } + action={ + <> + {props.error && ( + <ErrorIcon color="error" style={{ verticalAlign: "middle" }} /> + )} + <IconButton + className={clsx(classes.expand, { + [classes.expandOpen]: expanded, + })} + onClick={handleExpandClick} + aria-expanded={expanded} + > + <ExpandMoreIcon /> + </IconButton> + </> + } + title={props.title} + subheader={props.description} + /> + <Collapse in={expanded} timeout="auto" unmountOnExit> + <CardContent>{props.content}</CardContent> + </Collapse> + </Card> + </> + ); +}; + +ExpandableCard.propTypes = {}; + +export default ExpandableCard; diff --git a/src/tools/emcoui/src/common/FileUpload.jsx b/src/tools/emcoui/src/common/FileUpload.jsx index 847951e9..97d34bc2 100644 --- a/src/tools/emcoui/src/common/FileUpload.jsx +++ b/src/tools/emcoui/src/common/FileUpload.jsx @@ -11,58 +11,64 @@ // 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. -// ======================================================================== -import React from 'react'; -import PropTypes from 'prop-types'; +// ======================================================================== +import React from "react"; +import PropTypes from "prop-types"; import FileCopyIcon from "@material-ui/icons/FileCopy"; import CloudUploadIcon from "@material-ui/icons/CloudUpload"; -import './fileUpload.css' +import "./fileUpload.css"; const FileUpload = (props) => { - return ( - <> - <div className="file-upload"> - <div - className="file-upload-wrap" - style={{ - border: props.file && props.file.name && "2px dashed rgba(0, 131, 143, 1)" - }} - > - <input - required - className="file-upload-input" - type="file" - accept={props.accept ? props.accept : "*"} - name="file" - onBlur={props.handleBlur ? props.handleBlur : null} - onChange={(event) => { - props.setFieldValue("file", event.currentTarget.files[0]); - }} - /> + return ( + <> + <div className="file-upload"> + <div + className="file-upload-wrap" + style={{ + border: + props.file && + props.file.name && + "2px dashed rgba(0, 131, 143, 1)", + }} + > + <input + required + className="file-upload-input" + type="file" + accept={props.accept ? props.accept : "*"} + name="file" + onBlur={props.handleBlur ? props.handleBlur : null} + onChange={(event) => { + props.setFieldValue(props.name, event.currentTarget.files[0]); + }} + /> - <div className="file-upload-text"> - {(props.file && props.file.name) ? (<> - <span> - <FileCopyIcon color="primary" /> - </span> - <span style={{ fontWeight: 600 }}>{props.file.name}</span> - </>) : (<> - <span> - <CloudUploadIcon /> - </span> - <span> - Drag And Drop or Click To Upload - </span> - </>)} - </div> - </div> - </div> - </>); + <div className="file-upload-text"> + {props.file && props.file.name ? ( + <> + <span> + <FileCopyIcon color="primary" /> + </span> + <span style={{ fontWeight: 600 }}>{props.file.name}</span> + </> + ) : ( + <> + <span> + <CloudUploadIcon /> + </span> + <span>Drag And Drop or Click To Upload</span> + </> + )} + </div> + </div> + </div> + </> + ); }; FileUpload.propTypes = { - handleBlur: PropTypes.func, - setFieldValue: PropTypes.func.isRequired, + handleBlur: PropTypes.func, + setFieldValue: PropTypes.func.isRequired, }; export default FileUpload; diff --git a/src/tools/emcoui/src/common/Form.jsx b/src/tools/emcoui/src/common/Form.jsx index e9fe3a2d..6e8eee2e 100644 --- a/src/tools/emcoui/src/common/Form.jsx +++ b/src/tools/emcoui/src/common/Form.jsx @@ -11,144 +11,155 @@ // 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. -// ======================================================================== -import React from 'react'; -import PropTypes from 'prop-types'; -import { withStyles } from '@material-ui/core/styles'; -import Button from '@material-ui/core/Button'; +// ======================================================================== +import React from "react"; +import PropTypes from "prop-types"; +import { withStyles } from "@material-ui/core/styles"; +import Button from "@material-ui/core/Button"; -import Dialog from '@material-ui/core/Dialog'; -import MuiDialogTitle from '@material-ui/core/DialogTitle'; -import MuiDialogContent from '@material-ui/core/DialogContent'; -import MuiDialogActions from '@material-ui/core/DialogActions'; -import IconButton from '@material-ui/core/IconButton'; -import CloseIcon from '@material-ui/icons/Close'; -import Typography from '@material-ui/core/Typography'; -import { TextField } from '@material-ui/core'; +import Dialog from "@material-ui/core/Dialog"; +import MuiDialogTitle from "@material-ui/core/DialogTitle"; +import MuiDialogContent from "@material-ui/core/DialogContent"; +import MuiDialogActions from "@material-ui/core/DialogActions"; +import IconButton from "@material-ui/core/IconButton"; +import CloseIcon from "@material-ui/icons/Close"; +import Typography from "@material-ui/core/Typography"; +import { TextField } from "@material-ui/core"; import * as Yup from "yup"; -import { Formik } from 'formik'; +import { Formik } from "formik"; const styles = (theme) => ({ - root: { - margin: 0, - padding: theme.spacing(2), - }, - closeButton: { - position: 'absolute', - right: theme.spacing(1), - top: theme.spacing(1), - color: theme.palette.grey[500], - }, + root: { + margin: 0, + padding: theme.spacing(2), + }, + closeButton: { + position: "absolute", + right: theme.spacing(1), + top: theme.spacing(1), + color: theme.palette.grey[500], + }, }); const DialogTitle = withStyles(styles)((props) => { - const { children, classes, onClose, ...other } = props; - return ( - <MuiDialogTitle disableTypography className={classes.root} {...other}> - <Typography variant="h6">{children}</Typography> - {onClose ? ( - <IconButton className={classes.closeButton} onClick={onClose}> - <CloseIcon /> - </IconButton> - ) : null} - </MuiDialogTitle> - ); + const { children, classes, onClose, ...other } = props; + return ( + <MuiDialogTitle disableTypography className={classes.root} {...other}> + <Typography variant="h6">{children}</Typography> + {onClose ? ( + <IconButton className={classes.closeButton} onClick={onClose}> + <CloseIcon /> + </IconButton> + ) : null} + </MuiDialogTitle> + ); }); const DialogActions = withStyles((theme) => ({ - root: { - margin: 0, - padding: theme.spacing(1), - }, + root: { + margin: 0, + padding: theme.spacing(1), + }, }))(MuiDialogActions); const DialogContent = withStyles((theme) => ({ - root: { - padding: theme.spacing(2), - } + root: { + padding: theme.spacing(2), + }, }))(MuiDialogContent); -const schema = Yup.object( - { - name: Yup.string().required(), - description: Yup.string(), - }) +const schema = Yup.object({ + name: Yup.string().required(), + description: Yup.string(), +}); const CreateForm = (props) => { - const { onClose, item, open, onSubmit } = props; - const buttonLabel = item ? "OK" : "Create" - const title = item ? "Edit" : "Create" - const handleClose = () => { - onClose(); - }; - let initialValues = item ? { name: item.metadata.name, description: item.metadata.description } : { name: "", description: "" } + const { onClose, item, open, onSubmit } = props; + const buttonLabel = item ? "OK" : "Create"; + const title = item ? "Edit" : "Create"; + const handleClose = () => { + onClose(); + }; + let initialValues = item + ? { name: item.metadata.name, description: item.metadata.description } + : { name: "", description: "" }; - return ( - <Dialog maxWidth={"xs"} onClose={handleClose} aria-labelledby="customized-dialog-title" open={open} disableBackdropClick> - <DialogTitle id="simple-dialog-title">{title}</DialogTitle> - <Formik - initialValues={initialValues} - onSubmit={async values => { - onSubmit(values); - }} - validationSchema={schema} - > - {props => { - const { - touched, - errors, - isSubmitting, - handleChange, - handleBlur, - handleSubmit - } = props; - return ( - <form noValidate onSubmit={handleSubmit}> - <DialogContent dividers> - <TextField - style={{ width: "100%", marginBottom: "10px" }} - id="name" - label="Name" - name="name" - type="text" - onChange={handleChange} - onBlur={handleBlur} - helperText={(errors.name && touched.name && ( - "Name is required" - ))} - required - error={errors.name && touched.name} - /> - <TextField - style={{ width: "100%", marginBottom: "25px" }} - name="description" - onChange={handleChange} - onBlur={handleBlur} - id="description" - label="Description" - multiline - rowsMax={4} - /> - </DialogContent> - <DialogActions> - <Button autoFocus onClick={handleClose} color="secondary"> - Cancel - </Button> - <Button autoFocus type="submit" color="primary" disabled={isSubmitting}> - {buttonLabel} - </Button> - </DialogActions> - </form> - ); - }} - </Formik> - </Dialog> - ); + return ( + <Dialog + maxWidth={"xs"} + onClose={handleClose} + aria-labelledby="customized-dialog-title" + open={open} + disableBackdropClick + > + <DialogTitle id="simple-dialog-title">{title}</DialogTitle> + <Formik + initialValues={initialValues} + onSubmit={async (values) => { + onSubmit(values); + }} + validationSchema={schema} + > + {(props) => { + const { + touched, + errors, + isSubmitting, + handleChange, + handleBlur, + handleSubmit, + submitCount, + } = props; + return ( + <form noValidate onSubmit={handleSubmit}> + <DialogContent dividers> + <TextField + style={{ width: "100%", marginBottom: "10px" }} + id="name" + label="Name" + name="name" + type="text" + onChange={handleChange} + onBlur={handleBlur} + helperText={errors.name && touched.name && "Name is required"} + required + error={errors.name && touched.name} + /> + <TextField + style={{ width: "100%", marginBottom: "25px" }} + name="description" + onChange={handleChange} + onBlur={handleBlur} + id="description" + label="Description" + multiline + rowsMax={4} + /> + </DialogContent> + <DialogActions> + <Button autoFocus onClick={handleClose} color="secondary"> + Cancel + </Button> + <Button + autoFocus + type="submit" + color="primary" + disabled={isSubmitting || submitCount > 0} + > + {buttonLabel} + </Button> + </DialogActions> + </form> + ); + }} + </Formik> + </Dialog> + ); }; CreateForm.propTypes = { - onClose: PropTypes.func.isRequired, - open: PropTypes.bool.isRequired, + onClose: PropTypes.func.isRequired, + open: PropTypes.bool.isRequired, }; export default CreateForm; diff --git a/src/tools/emcoui/src/compositeApps/CompositeApp.jsx b/src/tools/emcoui/src/compositeApps/CompositeApp.jsx index 34d07fbc..8b2c2b10 100644 --- a/src/tools/emcoui/src/compositeApps/CompositeApp.jsx +++ b/src/tools/emcoui/src/compositeApps/CompositeApp.jsx @@ -11,7 +11,7 @@ // 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. -// ======================================================================== +// ======================================================================== import React from "react"; import Tab from "@material-ui/core/Tab"; import Tabs from "@material-ui/core/Tabs"; @@ -19,15 +19,15 @@ import Paper from "@material-ui/core/Paper"; import { withStyles } from "@material-ui/core/styles"; import Box from "@material-ui/core/Box"; import PropTypes from "prop-types"; -import Apps from "../compositeApps/apps/Apps"; -import CompositeProfiles from "../compositeApps/compositeProfiles/CompositeProfiles"; -import Intents from "../compositeApps/intents/GenericPlacementIntents"; import BackIcon from "@material-ui/icons/ArrowBack"; import { withRouter } from "react-router-dom"; import { IconButton } from "@material-ui/core"; import apiService from "../services/apiService"; import Spinner from "../common/Spinner"; -import NetworkIntent from "../networkIntents/NetworkIntents"; +import Apps from "../compositeApps/apps/Apps"; +import CompositeProfiles from "../compositeApps/compositeProfiles/CompositeProfiles"; +// import Intents from "../compositeApps/intents/GenericPlacementIntents"; +// import NetworkIntent from "../networkIntents/NetworkIntents"; const lightColor = "rgba(255, 255, 255, 0.7)"; @@ -134,8 +134,6 @@ class CompositeApp extends React.Component { > <Tab label="Apps" /> <Tab label="Composite Profiles" /> - <Tab label="Generic Placement Intents" /> - <Tab label="Network Controller Intents" /> </Tabs> {this.state.isLoading && <Spinner />} @@ -158,22 +156,6 @@ class CompositeApp extends React.Component { appsData={this.state.appsData} /> </TabPanel> - <TabPanel value={this.state.activeTab} index={2}> - <Intents - projectName={this.props.projectName} - compositeAppName={this.state.compositeAppName} - compositeAppVersion={this.state.compositeAppVersion} - appsData={this.state.appsData} - /> - </TabPanel> - <TabPanel value={this.state.activeTab} index={3}> - <NetworkIntent - projectName={this.props.projectName} - compositeAppName={this.state.compositeAppName} - compositeAppVersion={this.state.compositeAppVersion} - appsData={this.state.appsData} - /> - </TabPanel> </> )} </Paper> @@ -181,7 +163,5 @@ class CompositeApp extends React.Component { ); } } - CompositeApp.propTypes = {}; - export default withStyles(styles)(withRouter(CompositeApp)); diff --git a/src/tools/emcoui/src/compositeApps/CompositeAppTable.jsx b/src/tools/emcoui/src/compositeApps/CompositeAppTable.jsx index 220d7df8..926ab545 100644 --- a/src/tools/emcoui/src/compositeApps/CompositeAppTable.jsx +++ b/src/tools/emcoui/src/compositeApps/CompositeAppTable.jsx @@ -11,7 +11,7 @@ // 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. -// ======================================================================== +// ======================================================================== import React, { useState } from "react"; import { withStyles, makeStyles } from "@material-ui/core/styles"; import Table from "@material-ui/core/Table"; @@ -21,13 +21,15 @@ import TableContainer from "@material-ui/core/TableContainer"; import TableHead from "@material-ui/core/TableHead"; import TableRow from "@material-ui/core/TableRow"; import Paper from "@material-ui/core/Paper"; -import { Link } from "react-router-dom"; +// import { Link } from "react-router-dom"; import IconButton from "@material-ui/core/IconButton"; import EditIcon from "@material-ui/icons/Edit"; -import DeleteIcon from '@material-ui/icons/Delete'; +import DeleteIcon from "@material-ui/icons/Delete"; import CreateCompositeAppForm from "./dialogs/CompositeAppForm"; import apiService from "../services/apiService"; import DeleteDialog from "../common/Dialogue"; +import { Link, withRouter } from "react-router-dom"; +import Notification from "../common/Notification"; const StyledTableCell = withStyles((theme) => ({ body: { @@ -52,50 +54,80 @@ const useStyles = makeStyles({ }, }); -export default function CustomizedTables({ data, ...props }) { +function CustomizedTables({ data, ...props }) { const classes = useStyles(); const [openForm, setOpenForm] = useState(false); const [activeRowIndex, setActiveRowIndex] = useState(0); const [row, setRow] = useState({}); const [open, setOpen] = useState(false); + const [notificationDetails, setNotificationDetails] = useState({}); let onEditCompositeApp = (row, index) => { setActiveRowIndex(index); setRow(row); setOpenForm(true); - } + // props.history.push(`services/${row.metadata.name}/${row.spec.version}`); + }; const handleCloseForm = (fields) => { if (fields) { - let request = { payload: { name: fields.name, description: fields.description, spec: { version: fields.version } }, projectName: props.projectName, compositeAppVersion: row.spec.version }; - apiService.updateCompositeApp(request).then(res => { - let updatedData = data.slice(); - updatedData.splice(activeRowIndex, 1); - updatedData.push(res); - props.handleUpdateState(updatedData); - }).catch(err => { - console.log("error creating composite app : ", err) - }).finally(() => { - setOpenForm(false); - }); - } - else { + let request = { + payload: { + name: fields.name, + description: fields.description, + spec: { version: fields.version }, + }, + projectName: props.projectName, + compositeAppVersion: row.spec.version, + }; + apiService + .updateCompositeApp(request) + .then((res) => { + let updatedData = data.slice(); + updatedData.splice(activeRowIndex, 1); + updatedData.push(res); + props.handleUpdateState(updatedData); + }) + .catch((err) => { + console.log("error creating composite app : ", err); + }) + .finally(() => { + setOpenForm(false); + }); + } else { setOpenForm(false); } }; const handleDeleteCompositeApp = (index) => { setActiveRowIndex(index); setOpen(true); - } - const handleClose = el => { + }; + const handleClose = (el) => { if (el.target.innerText === "Delete") { - let request = { projectName: props.projectName, compositeAppName: data[activeRowIndex].metadata.name, compositeAppVersion: data[activeRowIndex].spec.version }; - apiService.deleteCompositeApp(request).then(() => { - console.log("cluster deleted"); - data.splice(activeRowIndex, 1); - let updatedData = data.slice(); - props.handleUpdateState(updatedData); - }).catch(err => { - console.log("Error deleting cluster : ", err) - }) + let request = { + projectName: props.projectName, + compositeAppName: data[activeRowIndex].metadata.name, + compositeAppVersion: data[activeRowIndex].spec.version, + }; + apiService + .deleteCompositeApp(request) + .then(() => { + console.log("cluster deleted"); + data.splice(activeRowIndex, 1); + let updatedData = data.slice(); + props.handleUpdateState(updatedData); + }) + .catch((err) => { + console.log("Error deleting cluster : ", err); + let message = "Error deleting service"; + if (err.response.data.includes("Non emtpy DIG in service")) { + message = + "Error deleting service : please delete deployment intent group first"; + } + setNotificationDetails({ + show: true, + message: message, + severity: "error", + }); + }); } setOpen(false); setActiveRowIndex(0); @@ -103,11 +135,22 @@ export default function CustomizedTables({ data, ...props }) { return ( <> - {data && (data.length > 0) && - (<> - <CreateCompositeAppForm open={openForm} handleClose={handleCloseForm} item={row} /> - <DeleteDialog open={open} onClose={handleClose} title={"Delete Cluster"} - content={`Are you sure you want to delete "${data[activeRowIndex] ? data[activeRowIndex].metadata.name : ""}" ?`} /> + <Notification notificationDetails={notificationDetails} /> + {data && data.length > 0 && ( + <> + <CreateCompositeAppForm + open={openForm} + handleClose={handleCloseForm} + item={row} + /> + <DeleteDialog + open={open} + onClose={handleClose} + title={"Delete Service"} + content={`Are you sure you want to delete "${ + data[activeRowIndex] ? data[activeRowIndex].metadata.name : "" + }" ?`} + /> <TableContainer component={Paper}> <Table className={classes.table} size="small"> <TableHead> @@ -122,8 +165,12 @@ export default function CustomizedTables({ data, ...props }) { {data.map((row, index) => ( <StyledTableRow key={row.metadata.name}> <StyledTableCell> - {" "} - <Link to={`composite-apps/${row.metadata.name}/${row.spec.version}`}>{row.metadata.name}</Link> + <Link + to={`services/${row.metadata.name}/${row.spec.version}`} + > + {row.metadata.name} + </Link> + {/* {row.metadata.name} */} </StyledTableCell> <StyledTableCell className={classes.cell}> {row.metadata.description} @@ -132,10 +179,18 @@ export default function CustomizedTables({ data, ...props }) { {row.spec.version} </StyledTableCell> <StyledTableCell className={classes.cell}> - <IconButton onClick={(e) => onEditCompositeApp(row, index)} title="Edit"> + {/* <IconButton + onClick={(e) => onEditCompositeApp(row, index)} + title="Edit" + > <EditIcon color="primary" /> - </IconButton> - <IconButton color="secondary" onClick={() => { handleDeleteCompositeApp(index) }}> + </IconButton> */} + <IconButton + color="secondary" + onClick={() => { + handleDeleteCompositeApp(index); + }} + > <DeleteIcon /> </IconButton> </StyledTableCell> @@ -144,7 +199,11 @@ export default function CustomizedTables({ data, ...props }) { </TableBody> </Table> </TableContainer> - </>)} - {(!data || (data.length === 0)) && (<span>No Clusters</span>)} - </>) + </> + )} + {(!data || data.length === 0) && <span>No Composite Apps</span>} + </> + ); } + +export default withRouter(CustomizedTables); diff --git a/src/tools/emcoui/src/compositeApps/CompositeApps.jsx b/src/tools/emcoui/src/compositeApps/CompositeApps.jsx index e7901ff9..5c540039 100644 --- a/src/tools/emcoui/src/compositeApps/CompositeApps.jsx +++ b/src/tools/emcoui/src/compositeApps/CompositeApps.jsx @@ -11,14 +11,15 @@ // 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. -// ======================================================================== +// ======================================================================== import React from "react"; import CompositeAppTable from "./CompositeAppTable"; -import { withStyles, Button, Grid } from "@material-ui/core"; +import { withStyles, Button, Grid, Typography } from "@material-ui/core"; import CreateCompositeAppForm from "./dialogs/CompositeAppForm"; import AddIcon from "@material-ui/icons/Add"; import apiService from "../services/apiService"; import Spinner from "../common/Spinner"; +import { ReactComponent as EmptyIcon } from "../assets/icons/empty.svg"; const styles = { root: { display: "flex", @@ -57,22 +58,43 @@ class CompositeApps extends React.Component { handleClose = (fields) => { if (fields) { - let request = { - payload: { - metadata: { name: fields.name, description: fields.description }, - spec: { version: fields.version }, - }, - projectName: this.props.projectName, + const formData = new FormData(); + let appsData = []; + fields.apps.forEach((app) => { + //add files for each app + formData.append(`${app.appName}_file`, app.file); + formData.append(`${app.appName}_profile`, app.profilePackageFile); + appsData.push({ + metadata: { + name: app.appName, + description: app.description ? app.description : "na", + filename: app.file.name, + }, + profileMetadata: { + name: `${app.appName}_profile`, + filename: app.profilePackageFile.name, + }, + clusters: app.clusters, + }); + }); + + let servicePayload = { + name: fields.name, + description: fields.description, + spec: { projectName: this.props.projectName, appsData }, }; + formData.append("servicePayload", JSON.stringify(servicePayload)); + let request = { projectName: this.props.projectName, payload: formData }; apiService - .createCompositeApp(request) + .addService(request) .then((res) => { + console.log("create service response : " + res); if (this.state.data && this.state.data.length > 0) this.setState({ data: [...this.state.data, res] }); else this.setState({ data: [res] }); }) .catch((err) => { - console.log("error creating composite app : ", err); + console.log("error adding app : ", err); }); } this.setState({ open: false }); @@ -88,32 +110,48 @@ class CompositeApps extends React.Component { {this.state.isLoading && <Spinner />} {!this.state.isLoading && ( <> - <Button - variant="outlined" - color="primary" - startIcon={<AddIcon />} - onClick={this.handleCreateCompositeApp} - > - Create Composite App - </Button> <CreateCompositeAppForm open={this.state.open} handleClose={this.handleClose} /> <Grid container spacing={2} alignItems="center"> - <Grid item xs style={{ marginTop: "20px" }}> - {this.state.data && this.state.data.length > 0 && ( + <Grid item xs={12}> + <Button + variant="outlined" + color="primary" + startIcon={<AddIcon />} + onClick={this.handleCreateCompositeApp} + > + Add service + </Button> + </Grid> + {this.state.data && this.state.data.length > 0 && ( + <Grid item xs={12}> <CompositeAppTable data={this.state.data} projectName={this.props.projectName} handleUpdateState={this.handleUpdateState} /> - )} - {(!this.state.data || this.state.data.length === 0) && ( - <span>No Composite Apps</span> - )} - </Grid> + </Grid> + )} </Grid> + {(!this.state.data || this.state.data.length === 0) && ( + <Grid + container + spacing={2} + direction="column" + alignItems="center" + > + <Grid style={{ marginTop: "60px" }} item xs={6}> + <EmptyIcon style={{ height: "100px", width: "100px" }} /> + </Grid> + <Grid item xs={12}> + <Typography variant="h6"> + No service found, start by adding a service + </Typography> + </Grid> + </Grid> + )} </> )} </> diff --git a/src/tools/emcoui/src/compositeApps/apps/Apps.jsx b/src/tools/emcoui/src/compositeApps/apps/Apps.jsx index 14be60c2..0e3638b3 100644 --- a/src/tools/emcoui/src/compositeApps/apps/Apps.jsx +++ b/src/tools/emcoui/src/compositeApps/apps/Apps.jsx @@ -11,7 +11,7 @@ // 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. -// ======================================================================== +// ======================================================================== import React, { useState } from "react"; import { makeStyles } from "@material-ui/core/styles"; import Card from "@material-ui/core/Card"; @@ -19,7 +19,7 @@ import CardContent from "@material-ui/core/CardContent"; import IconButton from "@material-ui/core/IconButton"; import Typography from "@material-ui/core/Typography"; import DeleteIcon from "@material-ui/icons/Delete"; -import EditIcon from "@material-ui/icons/Edit"; +// import EditIcon from "@material-ui/icons/Edit"; import { Grid, Button, Tooltip } from "@material-ui/core"; import AddIcon from "@material-ui/icons/Add"; import apiService from "../../services/apiService"; @@ -144,7 +144,7 @@ const Apps = ({ data, onStateChange, ...props }) => { }; return ( <> - <Button + {/* <Button variant="outlined" color="primary" startIcon={<AddIcon />} @@ -152,7 +152,7 @@ const Apps = ({ data, onStateChange, ...props }) => { size="small" > Add App - </Button> + </Button> */} <AppForm open={formOpen} onClose={handleFormClose} @@ -200,6 +200,7 @@ const Apps = ({ data, onStateChange, ...props }) => { </Typography> </CardContent> <div className={classes.controls}> + {/* //edit app api is not implemented yet <IconButton onClick={handleEditApp.bind(this, value)} color="primary" @@ -212,7 +213,7 @@ const Apps = ({ data, onStateChange, ...props }) => { onClick={() => handleDeleteApp(index)} > <DeleteIcon /> - </IconButton> + </IconButton> */} </div> </div> </Card> diff --git a/src/tools/emcoui/src/compositeApps/compositeProfiles/CompositeProfileCard.jsx b/src/tools/emcoui/src/compositeApps/compositeProfiles/CompositeProfileCard.jsx index 58161e8e..d565eba7 100644 --- a/src/tools/emcoui/src/compositeApps/compositeProfiles/CompositeProfileCard.jsx +++ b/src/tools/emcoui/src/compositeApps/compositeProfiles/CompositeProfileCard.jsx @@ -11,7 +11,7 @@ // 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. -// ======================================================================== +// ======================================================================== import React, { useState } from "react"; import { makeStyles } from "@material-ui/core/styles"; import clsx from "clsx"; @@ -35,7 +35,7 @@ import TableCell from "@material-ui/core/TableCell"; import Paper from "@material-ui/core/Paper"; import TableBody from "@material-ui/core/TableBody"; import apiService from "../../services/apiService"; -import EditIcon from "@material-ui/icons/Edit"; +// import EditIcon from "@material-ui/icons/Edit"; import DeleteIcon from "@material-ui/icons/Delete"; import ProfileForm from "./ProfileForm"; import DeleteDialog from "../../common/Dialogue"; @@ -196,7 +196,7 @@ export default function RecipeReviewCard(props) { /> <Collapse in={expanded} timeout="auto" unmountOnExit> <CardContent> - <Button + {/* <Button disabled={!(props.appsData && props.appsData.length > 0)} variant="outlined" size="small" @@ -208,11 +208,12 @@ export default function RecipeReviewCard(props) { }} > Add Profile - </Button> - <Button + </Button> */} + {/* <Button variant="outlined" size="small" color="secondary" + disabled={data && data.length > 0} style={{ float: "right" }} startIcon={<DeleteIcon />} onClick={() => { @@ -220,7 +221,7 @@ export default function RecipeReviewCard(props) { }} > Delete Composite Profile - </Button> + </Button> */} {data && data.length > 0 && ( <> <DeleteDialog @@ -238,7 +239,7 @@ export default function RecipeReviewCard(props) { <StyledTableCell>Name</StyledTableCell> <StyledTableCell>Description</StyledTableCell> <StyledTableCell>App</StyledTableCell> - <StyledTableCell>Actions</StyledTableCell> + {/* <StyledTableCell>Actions</StyledTableCell> */} </TableRow> </TableHead> <TableBody> @@ -253,7 +254,9 @@ export default function RecipeReviewCard(props) { <StyledTableCell> {profile.spec["app-name"]} </StyledTableCell> - <StyledTableCell> + {/* <StyledTableCell> + + //edit profile api is not implemented yet <IconButton onClick={(e) => handleEdit(index)} title="Edit" @@ -266,7 +269,7 @@ export default function RecipeReviewCard(props) { > <DeleteIcon color="secondary" /> </IconButton> - </StyledTableCell> + </StyledTableCell> */} </StyledTableRow> ))} </TableBody> @@ -275,7 +278,7 @@ export default function RecipeReviewCard(props) { </> )} {!(props.appsData && props.appsData.length > 0) && ( - <div>No apps found for adding profile</div> + <div>No app found for adding profile</div> )} </CardContent> </Collapse> diff --git a/src/tools/emcoui/src/compositeApps/compositeProfiles/CompositeProfiles.jsx b/src/tools/emcoui/src/compositeApps/compositeProfiles/CompositeProfiles.jsx index 25ecaaee..26629e03 100644 --- a/src/tools/emcoui/src/compositeApps/compositeProfiles/CompositeProfiles.jsx +++ b/src/tools/emcoui/src/compositeApps/compositeProfiles/CompositeProfiles.jsx @@ -11,7 +11,7 @@ // 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. -// ======================================================================== +// ======================================================================== import React, { useState, useEffect } from "react"; import Card from "./CompositeProfileCard"; import { Button, Grid } from "@material-ui/core"; @@ -82,7 +82,6 @@ const CompositeProfiles = (props) => { compositeAppVersion: props.compositeAppVersion, compositeProfileName: data[index].metadata.name, }; - console.log(request); apiService .deleteCompositeProfile(request) .then(() => { @@ -115,7 +114,7 @@ const CompositeProfiles = (props) => { }"`} /> - <Button + {/* <Button disabled={isLoading} variant="outlined" color="primary" @@ -123,7 +122,7 @@ const CompositeProfiles = (props) => { onClick={handleAddCompositeProfile} > Add Composite Profile - </Button> + </Button> */} <Form onClose={handleCloseForm} open={openForm} onSubmit={handleSubmit} /> <Grid container diff --git a/src/tools/emcoui/src/compositeApps/dialogs/AppForm.jsx b/src/tools/emcoui/src/compositeApps/dialogs/AppForm.jsx new file mode 100644 index 00000000..12dd7dd8 --- /dev/null +++ b/src/tools/emcoui/src/compositeApps/dialogs/AppForm.jsx @@ -0,0 +1,149 @@ +//======================================================================= +// Copyright (c) 2017-2020 Aarna Networks, Inc. +// All rights reserved. +// ====================================================================== +// 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. +// ======================================================================== +import React from "react"; +import ExpandableCard from "../../common/ExpandableCard"; +import { Grid, Paper, TextField } from "@material-ui/core"; +import FileUpload from "../../common/FileUpload"; + +function AppDetailsForm({ formikProps, ...props }) { + return ( + <> + <Paper + style={{ + width: "100%", + padding: "20px", + maxHeight: "395px", + overflowY: "auto", + scrollbarWidth: "thin", + }} + > + <Grid container spacing={3}> + <Grid item xs={6}> + <TextField + fullWidth + value={formikProps.values.apps[props.index].appName} + name={`apps[${props.index}].appName`} + id="app-name" + label="App name" + size="small" + onChange={formikProps.handleChange} + onBlur={formikProps.handleBlur} + required + helperText={ + formikProps.errors.apps && + formikProps.errors.apps[props.index] && + formikProps.errors.apps[props.index].appName + } + error={ + formikProps.errors.apps && + formikProps.errors.apps[props.index] && + formikProps.errors.apps[props.index].appName && + true + } + /> + </Grid> + <Grid item xs={6}> + <TextField + fullWidth + value={formikProps.values.apps[props.index].description} + name={`apps[${props.index}].description`} + id="app-description" + label="Description" + multiline + onChange={formikProps.handleChange} + onBlur={formikProps.handleBlur} + rowsMax={4} + /> + </Grid> + <Grid item xs={6}> + <label + style={{ marginTop: "20px" }} + className="MuiFormLabel-root MuiInputLabel-root" + htmlFor="file" + id="file-label" + > + App tgz file + <span className="MuiFormLabel-asterisk MuiInputLabel-asterisk"> +  * + </span> + </label> + <FileUpload + setFieldValue={formikProps.setFieldValue} + file={formikProps.values.apps[props.index].file} + onBlur={formikProps.handleBlur} + name={`apps[${props.index}].file`} + accept={".tgz"} + /> + {formikProps.errors.apps && + formikProps.errors.apps[props.index] && + formikProps.errors.apps[props.index].file && ( + <p style={{ color: "#f44336" }}> + {formikProps.errors.apps[props.index].file} + </p> + )} + </Grid> + <Grid item xs={6}> + <label + style={{ marginTop: "20px" }} + className="MuiFormLabel-root MuiInputLabel-root" + htmlFor="file" + id="file-label" + > + Profile tar file + <span className="MuiFormLabel-asterisk MuiInputLabel-asterisk"> +  * + </span> + </label> + <FileUpload + setFieldValue={formikProps.setFieldValue} + file={formikProps.values.apps[props.index].profilePackageFile} + onBlur={formikProps.handleBlur} + name={`apps[${props.index}].profilePackageFile`} + accept={".tar.gz, .tar"} + /> + {formikProps.errors.apps && + formikProps.errors.apps[props.index] && + formikProps.errors.apps[props.index].profilePackageFile && ( + <p style={{ color: "#f44336" }}> + {formikProps.errors.apps[props.index].profilePackageFile} + </p> + )} + </Grid> + </Grid> + </Paper> + </> + ); +} + +const AppForm = (props) => { + return ( + <ExpandableCard + error={ + props.formikProps.errors.apps && + props.formikProps.errors.apps[props.index] + } + title={props.name} + description={props.description} + content={ + <AppDetailsForm + formikProps={props.formikProps} + name={props.name} + index={props.index} + /> + } + /> + ); +}; +export default AppForm; diff --git a/src/tools/emcoui/src/compositeApps/dialogs/AppFormGeneral.jsx b/src/tools/emcoui/src/compositeApps/dialogs/AppFormGeneral.jsx new file mode 100644 index 00000000..e2272ae8 --- /dev/null +++ b/src/tools/emcoui/src/compositeApps/dialogs/AppFormGeneral.jsx @@ -0,0 +1,129 @@ +//======================================================================= +// Copyright (c) 2017-2020 Aarna Networks, Inc. +// All rights reserved. +// ====================================================================== +// 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. +// ======================================================================== +import { Grid, Paper, TextField } from "@material-ui/core"; +import FileUpload from "../../common/FileUpload"; +import React from "react"; + +function AppFormGeneral({ formikProps, ...props }) { + return ( + <> + <Paper + style={{ + width: "100%", + padding: "20px", + maxHeight: "395px", + overflowY: "auto", + scrollbarWidth: "thin", + }} + > + <Grid container spacing={3}> + <Grid item xs={6}> + <TextField + fullWidth + value={formikProps.values.apps[props.index].appName} + name={`apps[${props.index}].appName`} + id="app-name" + label="App name" + size="small" + onChange={formikProps.handleChange} + onBlur={formikProps.handleBlur} + required + helperText={ + formikProps.errors.apps && + formikProps.errors.apps[props.index] && + formikProps.errors.apps[props.index].appName + } + error={ + formikProps.errors.apps && + formikProps.errors.apps[props.index] && + formikProps.errors.apps[props.index].appName && + true + } + /> + </Grid> + <Grid item xs={6}> + <TextField + fullWidth + value={formikProps.values.apps[props.index].description} + name={`apps[${props.index}].description`} + id="app-description" + label="Description" + multiline + onChange={formikProps.handleChange} + onBlur={formikProps.handleBlur} + rowsMax={4} + /> + </Grid> + <Grid item xs={6}> + <label + style={{ marginTop: "20px" }} + className="MuiFormLabel-root MuiInputLabel-root" + htmlFor="file" + id="file-label" + > + App tgz file + <span className="MuiFormLabel-asterisk MuiInputLabel-asterisk"> +  * + </span> + </label> + <FileUpload + setFieldValue={formikProps.setFieldValue} + file={formikProps.values.apps[props.index].file} + onBlur={formikProps.handleBlur} + name={`apps[${props.index}].file`} + accept={".tgz"} + /> + {formikProps.errors.apps && + formikProps.errors.apps[props.index] && + formikProps.errors.apps[props.index].file && ( + <p style={{ color: "#f44336" }}> + {formikProps.errors.apps[props.index].file} + </p> + )} + </Grid> + <Grid item xs={6}> + <label + style={{ marginTop: "20px" }} + className="MuiFormLabel-root MuiInputLabel-root" + htmlFor="file" + id="file-label" + > + Profile tar file + <span className="MuiFormLabel-asterisk MuiInputLabel-asterisk"> +  * + </span> + </label> + <FileUpload + setFieldValue={formikProps.setFieldValue} + file={formikProps.values.apps[props.index].profilePackageFile} + onBlur={formikProps.handleBlur} + name={`apps[${props.index}].profilePackageFile`} + accept={".tar.gz, .tar"} + /> + {formikProps.errors.apps && + formikProps.errors.apps[props.index] && + formikProps.errors.apps[props.index].profilePackageFile && ( + <p style={{ color: "#f44336" }}> + {formikProps.errors.apps[props.index].profilePackageFile} + </p> + )} + </Grid> + </Grid> + </Paper> + </> + ); +} + +export default AppFormGeneral; diff --git a/src/tools/emcoui/src/compositeApps/dialogs/AppFormPlacement.jsx b/src/tools/emcoui/src/compositeApps/dialogs/AppFormPlacement.jsx new file mode 100644 index 00000000..c52c2b42 --- /dev/null +++ b/src/tools/emcoui/src/compositeApps/dialogs/AppFormPlacement.jsx @@ -0,0 +1,83 @@ +//======================================================================= +// Copyright (c) 2017-2020 Aarna Networks, Inc. +// All rights reserved. +// ====================================================================== +// 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. +// ======================================================================== +import React from "react"; +import PropTypes from "prop-types"; +import { Grid, Paper, Typography } from "@material-ui/core"; +import EnhancedTable from "./SortableTable"; + +function AppFormPlacement({ + formikProps, + index, + clusterProviders, + handleRowSelect, + ...props +}) { + return ( + <> + <Typography variant="subtitle1" style={{ float: "left" }}> + Select Clusters + <span className="MuiFormLabel-asterisk MuiInputLabel-asterisk"> *</span> + </Typography> + {formikProps.errors.apps && + formikProps.errors.apps[index] && + formikProps.errors.apps[index].clusters && ( + <span + style={{ + color: "#f44336", + marginRight: "35px", + float: "right", + }} + > + {typeof formikProps.errors.apps[index].clusters === "string" && + formikProps.errors.apps[index].clusters} + </span> + )} + <Grid + container + spacing={3} + style={{ + height: "400px", + overflowY: "auto", + width: "100%", + scrollbarWidth: "thin", + }} + > + {clusterProviders && + clusterProviders.length > 0 && + clusterProviders.map((clusterProvider) => ( + <Grid key={clusterProvider.name} item xs={12}> + <Paper> + <EnhancedTable + key={clusterProvider.name} + tableName={clusterProvider.name} + clusters={clusterProvider.clusters} + formikValues={formikProps.values.apps[index].clusters} + onRowSelect={handleRowSelect} + /> + </Paper> + </Grid> + ))} + </Grid> + </> + ); +} + +AppFormPlacement.propTypes = { + formikProps: PropTypes.object, + index: PropTypes.number, + handleRowSelect: PropTypes.func, +}; + +export default AppFormPlacement; diff --git a/src/tools/emcoui/src/compositeApps/dialogs/AppNetworkForm.jsx b/src/tools/emcoui/src/compositeApps/dialogs/AppNetworkForm.jsx new file mode 100644 index 00000000..055dc3e6 --- /dev/null +++ b/src/tools/emcoui/src/compositeApps/dialogs/AppNetworkForm.jsx @@ -0,0 +1,524 @@ +//======================================================================= +// Copyright (c) 2017-2020 Aarna Networks, Inc. +// All rights reserved. +// ====================================================================== +// 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. +// ======================================================================== +import React, { useState } from "react"; +import { makeStyles } from "@material-ui/core/styles"; +import Button from "@material-ui/core/Button"; +import Typography from "@material-ui/core/Typography"; +import { Grid, IconButton } from "@material-ui/core"; +import { TextField, Select, MenuItem, InputLabel } from "@material-ui/core"; +import AddIcon from "@material-ui/icons/Add"; +import CardContent from "@material-ui/core/CardContent"; +import Card from "@material-ui/core/Card"; +import apiService from "../../services/apiService"; +import DeleteIcon from "@material-ui/icons/Delete"; +import { Formik } from "formik"; +import Notification from "../../common/Notification"; + +function NetworkForm({ formikProps, ...props }) { + const [clusters, setClusters] = useState(props.clusters); + const [notificationDetails, setNotificationDetails] = useState({}); + const useStyles = makeStyles({ + root: { + minWidth: 275, + }, + title: { + fontSize: 14, + }, + pos: { + marginBottom: 12, + }, + }); + + const handleAddNetworkInterface = (providerIndex, clusterIndex, values) => { + let updatedFields = []; + if ( + values.apps[props.index].clusters[providerIndex].selectedClusters[ + clusterIndex + ].interfaces + ) { + updatedFields = [ + ...values.apps[props.index].clusters[providerIndex].selectedClusters[ + clusterIndex + ].interfaces, + { + networkName: "", + ip: "", + subnet: "", + }, + ]; + } else { + updatedFields = [ + { + networkName: "", + ip: "", + subnet: "", + }, + ]; + } + + let request = { + providerName: values.apps[props.index].clusters[providerIndex].provider, + clusterName: + values.apps[props.index].clusters[providerIndex].selectedClusters[ + clusterIndex + ].name, + }; + apiService + .getClusterProviderNetworks(request) + .then((networks) => { + let networkData = []; + if (networks && networks.length > 0) { + networks.forEach((network) => { + networkData.push({ + name: network.metadata.name, + subnets: network.spec.ipv4Subnets, + }); + }); + } + + apiService + .getClusterNetworks(request) + .then((clusterNetworks) => { + if (clusterNetworks && clusterNetworks.length > 0) { + clusterNetworks.forEach((clusterNetwork) => { + networkData.push({ + name: clusterNetwork.metadata.name, + subnets: clusterNetwork.spec.ipv4Subnets, + }); + }); + } + //add interface entry onyl of there is atlease one available network + if (networkData.length > 0) { + setClusters((clusters) => { + clusters[providerIndex].selectedClusters[ + clusterIndex + ].interfaces = updatedFields; + clusters[providerIndex].selectedClusters[ + clusterIndex + ].networks = networkData; + clusters[providerIndex].selectedClusters[ + clusterIndex + ].availableNetworks = getAvailableNetworks( + clusters[providerIndex].selectedClusters[clusterIndex] + ); + return clusters; + }); + formikProps.setFieldValue( + `apps[${props.index}].clusters[${providerIndex}].selectedClusters[${clusterIndex}].interfaces`, + updatedFields + ); + } else { + setNotificationDetails({ + show: true, + message: `No network available for this cluster`, + severity: "warning", + }); + } + }) + .catch((err) => { + console.log("error getting cluster networks : ", err); + }); + }) + .catch((err) => { + console.log("error getting cluster provider networks : ", err); + }) + .finally(() => { + return updatedFields; + }); + }; + + const handleSelectNetowrk = ( + e, + providerIndex, + clusterIndex, + interfaceIndex + ) => { + setClusters((clusters) => { + clusters[providerIndex].selectedClusters[clusterIndex].interfaces[ + interfaceIndex + ] = { + networkName: e.target.value, + ip: "", + subnet: "", + }; + clusters[providerIndex].selectedClusters[ + clusterIndex + ].availableNetworks = getAvailableNetworks( + clusters[providerIndex].selectedClusters[clusterIndex], + "handleAddNetworkInterface" + ); + return clusters; + }); + formikProps.handleChange(e); + }; + const handleRemoveNetwork = (providerIndex, clusterIndex, interfaceIndex) => { + setClusters((clusters) => { + clusters[providerIndex].selectedClusters[clusterIndex].interfaces.splice( + interfaceIndex, + 1 + ); + clusters[providerIndex].selectedClusters[ + clusterIndex + ].availableNetworks = getAvailableNetworks( + clusters[providerIndex].selectedClusters[clusterIndex] + ); + return clusters; + }); + formikProps.setFieldValue( + `apps[${props.index}].clusters[${providerIndex}].selectedClusters[${clusterIndex}].interfaces`, + clusters[providerIndex].selectedClusters[clusterIndex].interfaces + ); + }; + const getAvailableNetworks = (cluster) => { + let availableNetworks = []; + cluster.networks.forEach((network) => { + let match = false; + cluster.interfaces.forEach((networkInterface) => { + if (network.name === networkInterface.networkName) { + match = true; + return; + } + }); + if (!match) availableNetworks.push(network); + }); + return availableNetworks; + }; + + const classes = useStyles(); + return ( + <> + <Notification notificationDetails={notificationDetails} /> + <Grid + key="networkForm" + container + spacing={3} + style={{ + height: "400px", + overflowY: "auto", + width: "100%", + scrollbarWidth: "thin", + }} + > + {(!clusters || clusters.length < 1) && ( + <Grid item xs={12}> + <Typography variant="h6">No clusters selected</Typography> + </Grid> + )} + {clusters && + clusters.map((cluster, providerIndex) => ( + <Grid key={cluster.provider + providerIndex} item xs={12}> + <Card className={classes.root}> + <CardContent> + <Grid container spacing={2}> + <Grid item xs={12}> + <Typography + className={classes.title} + color="textSecondary" + gutterBottom + > + {cluster.provider} + </Typography> + </Grid> + {cluster.selectedClusters.map( + (selectedCluster, clusterIndex) => ( + <React.Fragment key={selectedCluster.name}> + <Grid item xs={12}> + <Typography>{selectedCluster.name}</Typography> + </Grid> + <Formik> + {() => { + const { + values, + errors, + handleChange, + handleBlur, + } = formikProps; + return ( + <> + {selectedCluster.interfaces && + selectedCluster.interfaces.length > 0 + ? selectedCluster.interfaces.map( + (networkInterface, interfaceIndex) => ( + <Grid + spacing={1} + container + item + key={interfaceIndex} + xs={12} + > + <Grid item xs={4}> + <InputLabel id="network-select-label"> + Network + </InputLabel> + <Select + fullWidth + labelId="network-select-label" + id="network-select" + name={`apps[${props.index}].clusters[${providerIndex}].selectedClusters[${clusterIndex}].interfaces[${interfaceIndex}].networkName`} + value={ + values.apps[props.index] + .clusters[providerIndex] + .selectedClusters[ + clusterIndex + ].interfaces[interfaceIndex] + .networkName + } + onChange={(e) => { + handleSelectNetowrk( + e, + providerIndex, + clusterIndex, + interfaceIndex + ); + }} + > + {values.apps[props.index] + .clusters[providerIndex] + .selectedClusters[ + clusterIndex + ].interfaces[interfaceIndex] + .networkName && ( + <MenuItem + key={ + values.apps[props.index] + .clusters[providerIndex] + .selectedClusters[ + clusterIndex + ].interfaces[ + interfaceIndex + ].networkName + } + value={ + values.apps[props.index] + .clusters[providerIndex] + .selectedClusters[ + clusterIndex + ].interfaces[ + interfaceIndex + ].networkName + } + > + { + values.apps[props.index] + .clusters[providerIndex] + .selectedClusters[ + clusterIndex + ].interfaces[ + interfaceIndex + ].networkName + } + </MenuItem> + )} + {selectedCluster.availableNetworks && + selectedCluster.availableNetworks.map( + (network) => ( + <MenuItem + key={network.name} + value={network.name} + > + {network.name} + </MenuItem> + ) + )} + </Select> + </Grid> + + <Grid item xs={4}> + <InputLabel id="subnet-select-label"> + Subnet + </InputLabel> + <Select + fullWidth + labelId="subnet-select-label" + id="subnet-select-label" + name={`apps[${props.index}].clusters[${providerIndex}].selectedClusters[${clusterIndex}].interfaces[${interfaceIndex}].subnet`} + value={ + values.apps[props.index] + .clusters[providerIndex] + .selectedClusters[ + clusterIndex + ].interfaces[interfaceIndex] + .subnet + } + onChange={handleChange} + > + {values.apps[props.index] + .clusters[providerIndex] + .selectedClusters[ + clusterIndex + ].interfaces[interfaceIndex] + .networkName === "" + ? null + : selectedCluster.networks + .filter( + (network) => + network.name === + values.apps[ + props.index + ].clusters[ + providerIndex + ].selectedClusters[ + clusterIndex + ].interfaces[ + interfaceIndex + ].networkName + )[0] + .subnets.map((subnet) => ( + <MenuItem + key={subnet.name} + value={subnet.name} + > + {subnet.name}( + {subnet.subnet}) + </MenuItem> + ))} + </Select> + </Grid> + <Grid item xs={3}> + <TextField + width={"65%"} + name={`apps[${props.index}].clusters[${providerIndex}].selectedClusters[${clusterIndex}].interfaces[${interfaceIndex}].ip`} + onBlur={handleBlur} + id="ip" + label="IP Address" + value={ + values.apps[props.index] + .clusters[providerIndex] + .selectedClusters[ + clusterIndex + ].interfaces[interfaceIndex] + .ip + } + onChange={handleChange} + helperText={ + (errors.apps && + errors.apps[props.index] && + errors.apps[props.index] + .clusters && + errors.apps[props.index] + .clusters[clusterIndex] && + errors.apps[props.index] + .clusters[clusterIndex] + .selectedClusters[ + clusterIndex + ] && + errors.apps[props.index] + .clusters[clusterIndex] + .selectedClusters[ + clusterIndex + ].interfaces[ + interfaceIndex + ] && + errors.apps[props.index] + .clusters[clusterIndex] + .selectedClusters[ + clusterIndex + ].interfaces[interfaceIndex] + .ip) || + "blank for auto assign" + } + error={ + errors.apps && + errors.apps[props.index] && + errors.apps[props.index] + .clusters && + errors.apps[props.index] + .clusters[clusterIndex] && + errors.apps[props.index] + .clusters[clusterIndex] + .selectedClusters[ + clusterIndex + ] && + errors.apps[props.index] + .clusters[clusterIndex] + .selectedClusters[ + clusterIndex + ].interfaces[ + interfaceIndex + ] && + errors.apps[props.index] + .clusters[clusterIndex] + .selectedClusters[ + clusterIndex + ].interfaces[interfaceIndex] + .ip && + true + } + /> + </Grid> + <Grid item xs={1}> + <IconButton + color="secondary" + onClick={() => { + handleRemoveNetwork( + providerIndex, + clusterIndex, + interfaceIndex + ); + }} + > + <DeleteIcon fontSize="small" /> + </IconButton> + </Grid> + </Grid> + ) + ) + : null} + <Grid + key={selectedCluster.name + "addButton"} + item + xs={12} + > + <Button + variant="outlined" + size="small" + fullWidth + color="primary" + disabled={ + selectedCluster.interfaces && + selectedCluster.interfaces.length > 0 && + selectedCluster.networks.length === + selectedCluster.interfaces.length + } + onClick={() => { + handleAddNetworkInterface( + providerIndex, + clusterIndex, + values + ); + }} + startIcon={<AddIcon />} + > + Add Network Interface + </Button> + </Grid> + </> + ); + }} + </Formik> + </React.Fragment> + ) + )} + </Grid> + </CardContent> + </Card> + </Grid> + ))} + </Grid> + </> + ); +} + +export default NetworkForm; diff --git a/src/tools/emcoui/src/compositeApps/dialogs/CompositeAppForm.jsx b/src/tools/emcoui/src/compositeApps/dialogs/CompositeAppForm.jsx index 29e17cd7..751ea8eb 100644 --- a/src/tools/emcoui/src/compositeApps/dialogs/CompositeAppForm.jsx +++ b/src/tools/emcoui/src/compositeApps/dialogs/CompositeAppForm.jsx @@ -11,185 +11,298 @@ // 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. -// ======================================================================== -import React from "react"; -import { withStyles } from "@material-ui/core/styles"; +// ======================================================================== +import React, { useState } from "react"; import Button from "@material-ui/core/Button"; import Dialog from "@material-ui/core/Dialog"; -import MuiDialogTitle from "@material-ui/core/DialogTitle"; -import MuiDialogContent from "@material-ui/core/DialogContent"; -import MuiDialogActions from "@material-ui/core/DialogActions"; +import AppBar from "@material-ui/core/AppBar"; +import Toolbar from "@material-ui/core/Toolbar"; +import IconButton from "@material-ui/core/IconButton"; import Typography from "@material-ui/core/Typography"; -import { TextField } from '@material-ui/core'; -const styles = (theme) => ({ - root: { - margin: 0, - padding: theme.spacing(2), +import CloseIcon from "@material-ui/icons/Close"; +import Slide from "@material-ui/core/Slide"; +import { Grid } from "@material-ui/core"; +import { TextField } from "@material-ui/core"; +import { makeStyles } from "@material-ui/core/styles"; +import AddIcon from "@material-ui/icons/Add"; +import NewAppForm from "../../common/Form"; +import AppForm from "./AppForm"; +import { Formik, FieldArray } from "formik"; +import * as Yup from "yup"; + +const Transition = React.forwardRef(function Transition(props, ref) { + return <Slide direction="up" ref={ref} {...props} />; +}); + +const useStyles = makeStyles((theme) => ({ + tableRoot: { + width: "100%", }, - closeButton: { + paper: { + width: "100%", + marginBottom: theme.spacing(2), + }, + table: { + minWidth: 550, + }, + visuallyHidden: { + border: 0, + clip: "rect(0 0 0 0)", + height: 1, + margin: -1, + overflow: "hidden", + padding: 0, position: "absolute", - right: theme.spacing(1), - top: theme.spacing(1), - color: theme.palette.grey[500], + top: 20, + width: 1, }, -}); - - -const DialogContent = withStyles((theme) => ({ - root: { - padding: theme.spacing(2), + appBar: { + position: "relative", + }, + title: { + marginLeft: theme.spacing(2), + flex: 1, + }, + demo: { + backgroundColor: theme.palette.background.paper, }, -}))(MuiDialogContent); - -const DialogActions = withStyles((theme) => ({ root: { - margin: 0, - padding: theme.spacing(1), + flexGrow: 1, + backgroundColor: theme.palette.background.paper, + display: "flex", + height: 424, }, -}))(MuiDialogActions); - + tabs: { + borderRight: `1px solid ${theme.palette.divider}`, + }, +})); -class CreateCompositeAppForm extends React.Component { - constructor(props) { - super(props) - this.state = { - fields: { name: "", version: "", description: "" }, - errors: {} - } - this.handleChange = this.handleChange.bind(this); - this.submituserRegistrationForm = this.submituserRegistrationForm.bind(this); - } +const PROFILE_SUPPORTED_FORMATS = [ + ".tgz", + ".tar.gz", + ".tar", + "application/x-tar", + "application/x-tgz", + "application/x-compressed", + "application/x-gzip", + "application/x-compressed-tar", + "application/gzip", +]; +const APP_PACKAGE_SUPPORTED_FORMATS = [ + ".tgz", + ".tar.gz", + ".tar", + "application/x-tar", + "application/x-tgz", + "application/x-compressed", + "application/x-gzip", + "application/x-compressed-tar", +]; +const serviceBasicValidationSchema = Yup.object({ + name: Yup.string().required(), + description: Yup.string(), + apps: Yup.array() + .of( + Yup.object({ + appName: Yup.string().required("App name is required"), + file: Yup.mixed() + .required("An app package file is required") + .test( + "fileFormat", + "Unsupported file format", + (value) => + value && APP_PACKAGE_SUPPORTED_FORMATS.includes(value.type) + ), + profilePackageFile: Yup.mixed() + .required("A profile package file is required") + .test( + "fileFormat", + "Unsupported file format", + (value) => value && PROFILE_SUPPORTED_FORMATS.includes(value.type) + ), + }) + ) + .required("At least one app is required"), +}); - componentDidMount = () => { - if (this.props.item) { - this.title = "Edit Composite App"; - this.buttonLabel = "Update"; - this.isEdit = true; - } - else { - this.title = "New Composite App"; - this.buttonLabel = "Create"; - this.isEdit = false; - } +const CreateCompositeAppForm = ({ open, handleClose }) => { + const classes = useStyles(); + const [openForm, setOpenForm] = useState(false); + const handleCloseForm = () => { + setOpenForm(false); }; - - componentDidUpdate = (prevProps, prevState) => { - if (this.props.item && ((prevProps.item !== this.props.item))) { - this.setState({ fields: { ...this.props.item.metadata, version: this.props.item.spec.version } }); - } - } - - resetFields = () => { - if (!this.isEdit) { - this.setState({ - fields: { name: "", version: "", description: "" }, - errors: {} - }); - } - else { - this.setState({ fields: { ...this.props.item.metadata, version: this.props.item.spec.version } }); - } - } - - handleClose = () => { - this.resetFields(); - this.props.handleClose(); + const handleAddApp = () => { + setOpenForm(true); }; - - submituserRegistrationForm(e) { - e.preventDefault(); - if (this.validateForm()) { - this.resetFields(); - this.props.handleClose(this.state.fields); - } - } - - validateForm() { - let fields = this.state.fields; - let errors = {}; - let formIsValid = true; - - if (!fields["name"]) { - formIsValid = false; - errors["name"] = "*Please enter your username."; - } - - if (typeof fields["name"] !== "string") { - if (!fields["name"].match(/^[a-zA-Z ]*$/)) { - formIsValid = false; - errors["name"] = "*Please enter alphabet characters only."; - } - } - this.setState({ - errors: errors - }); - return formIsValid; - } - - handleChange = (e) => { - this.setState({ fields: { ...this.state.fields, [e.target.name]: e.target.value } }); - } - - render = () => { - const { classes } = this.props; - return ( - <> + let initialValues = { name: "", description: "", apps: [] }; + return ( + <> + {open && ( <Dialog - maxWidth={"xs"} - onClose={this.handleClose} - aria-labelledby="customized-dialog-title" - open={this.props.open} - disableBackdropClick + open={open} + onClose={() => { + handleClose(); + }} + fullScreen + TransitionComponent={Transition} > - <MuiDialogTitle disableTypography className={classes.root} > - <Typography variant="h6">{this.title}</Typography> - </MuiDialogTitle> + <Formik + initialValues={initialValues} + onSubmit={(values, { setSubmitting }) => { + setSubmitting(false); + handleClose(values); + }} + validationSchema={serviceBasicValidationSchema} + > + {(props) => { + const { + values, + touched, + errors, + isSubmitting, + handleChange, + handleBlur, + handleSubmit, + } = props; + return ( + <> + <form noValidate onSubmit={handleSubmit}> + <AppBar className={classes.appBar}> + <Toolbar> + <IconButton + edge="start" + color="inherit" + onClick={() => { + handleClose(); + }} + aria-label="close" + > + <CloseIcon /> + </IconButton> + <Typography variant="h6" className={classes.title}> + Add Service + </Typography> + <Button + type="submit" + autoFocus + variant="contained" + disabled={isSubmitting} + > + SUBMIT + </Button> + </Toolbar> + </AppBar> + <div style={{ padding: "12px" }}> + <Grid + container + direction="row" + justify="center" + alignItems="center" + style={{ marginTop: "40px" }} + spacing={3} + > + <Grid item xs={6}> + <Grid container spacing={3}> + {errors.apps && + touched.apps && + typeof errors.apps !== "object" && ( + <Grid item xs={12} sm={12}> + <Typography>{errors.apps}</Typography> + </Grid> + )} + + <Grid item xs={12} sm={6}> + <TextField + fullWidth + name="name" + id="input-name" + label="Name" + variant="outlined" + size="small" + value={values.name} + onChange={handleChange} + onBlur={handleBlur} + required + helperText={ + errors.name && + touched.name && + "Name is required" + } + error={errors.name && touched.name} + /> + </Grid> + <Grid item xs={12} sm={6}> + <TextField + fullWidth + name="description" + id="input-description" + label="Description" + variant="outlined" + size="small" + value={values.description} + onChange={handleChange} + onBlur={handleBlur} + /> + </Grid> - <form onSubmit={this.submituserRegistrationForm}> - <DialogContent dividers> - <TextField - style={{ width: "40%", marginBottom: "10px" }} - name="name" - value={this.state.fields.name} - id="input-name" - label="Name" - helperText="Name should be unique" - onChange={this.handleChange} - required - /> - <TextField - style={{ width: "40%", marginBottom: "20px", float: "right" }} - name="version" - value={this.state.fields.version} - onChange={this.handleChange} - id="input-version" - label="Version" - required - /> - <TextField - style={{ width: "100%", marginBottom: "25px" }} - name="description" - value={this.state.fields.description} - onChange={this.handleChange} - id="input-description" - label="Description" - multiline - rowsMax={4} - /> - </DialogContent> - <DialogActions> - <Button autoFocus onClick={this.handleClose} color="secondary"> - Cancel - </Button> - <Button autoFocus type="submit" color="primary"> - {this.buttonLabel} - </Button> - </DialogActions> - </form> + <FieldArray + name="apps" + render={(arrayHelpers) => ( + <> + <NewAppForm + open={openForm} + onClose={handleCloseForm} + onSubmit={(values) => { + arrayHelpers.push({ + appName: values.name, + description: values.description, + }); + setOpenForm(false); + }} + /> + {values.apps && + values.apps.length > 0 && + values.apps.map((app, index) => ( + <Grid key={index} item sm={12} xs={12}> + <AppForm + formikProps={props} + name={app.appName} + description={app.description} + index={index} + initialValues={values} + /> + </Grid> + ))} + </> + )} + /> + <Grid item xs={12}> + <Button + variant="outlined" + size="small" + fullWidth + color="primary" + onClick={() => { + handleAddApp(); + }} + startIcon={<AddIcon />} + > + Add App + </Button> + </Grid> + </Grid> + </Grid> + </Grid> + </div> + </form> + </> + ); + }} + </Formik> </Dialog> - </> - ); - } -} -export default withStyles(styles)(CreateCompositeAppForm) + )} + </> + ); +}; +export default CreateCompositeAppForm; diff --git a/src/tools/emcoui/src/compositeApps/dialogs/SortableTable.jsx b/src/tools/emcoui/src/compositeApps/dialogs/SortableTable.jsx new file mode 100644 index 00000000..f1a6ac2d --- /dev/null +++ b/src/tools/emcoui/src/compositeApps/dialogs/SortableTable.jsx @@ -0,0 +1,410 @@ +//======================================================================= +// Copyright (c) 2017-2020 Aarna Networks, Inc. +// All rights reserved. +// ====================================================================== +// 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. +// ======================================================================== +import Table from "@material-ui/core/Table"; +import TableBody from "@material-ui/core/TableBody"; +import TableCell from "@material-ui/core/TableCell"; +import TableContainer from "@material-ui/core/TableContainer"; +import TableHead from "@material-ui/core/TableHead"; +import TableRow from "@material-ui/core/TableRow"; +import TableSortLabel from "@material-ui/core/TableSortLabel"; +import { makeStyles } from "@material-ui/core/styles"; +import React, { useEffect, useState } from "react"; +import Checkbox from "@material-ui/core/Checkbox"; +import PropTypes from "prop-types"; +import Typography from "@material-ui/core/Typography"; +import Toolbar from "@material-ui/core/Toolbar"; + +import clsx from "clsx"; +import TablePagination from "@material-ui/core/TablePagination"; +import { lighten } from "@material-ui/core/styles"; + +function descendingComparator(a, b, orderBy) { + if (b[orderBy] < a[orderBy]) { + return -1; + } + if (b[orderBy] > a[orderBy]) { + return 1; + } + return 0; +} + +function getComparator(order, orderBy) { + return order === "desc" + ? (a, b) => descendingComparator(a, b, orderBy) + : (a, b) => -descendingComparator(a, b, orderBy); +} + +function stableSort(array, comparator) { + const stabilizedThis = array.map((el, index) => [el, index]); + stabilizedThis.sort((a, b) => { + const order = comparator(a[0], b[0]); + if (order !== 0) return order; + return a[1] - b[1]; + }); + return stabilizedThis.map((el) => el[0]); +} + +const headCells = [ + { + id: "name", + numeric: false, + sortable: true, + disablePadding: true, + label: "Cluster", + }, + { + id: "description", + numeric: true, + sortable: false, + disablePadding: false, + label: "Description", + }, +]; + +function EnhancedTableHead(props) { + const { + classes, + onSelectAllClick, + order, + orderBy, + numSelected, + rowCount, + onRequestSort, + } = props; + const createSortHandler = (property) => (event) => { + onRequestSort(event, property); + }; + + return ( + <TableHead> + <TableRow> + <TableCell padding="checkbox"> + <Checkbox + indeterminate={numSelected > 0 && numSelected < rowCount} + checked={rowCount > 0 && numSelected === rowCount} + onChange={onSelectAllClick} + /> + </TableCell> + {headCells.map((headCell) => + headCell.sortable ? ( + <TableCell + style={{ fontWeight: "520" }} + key={headCell.id} + align={headCell.numeric ? "right" : "left"} + padding={headCell.disablePadding ? "none" : "default"} + sortDirection={orderBy === headCell.id ? order : false} + > + <TableSortLabel + active={orderBy === headCell.id} + direction={orderBy === headCell.id ? order : "asc"} + onClick={createSortHandler(headCell.id)} + > + {headCell.label} + {orderBy === headCell.id ? ( + <span className={classes.visuallyHidden}> + {order === "desc" + ? "sorted descending" + : "sorted ascending"} + </span> + ) : null} + </TableSortLabel> + </TableCell> + ) : ( + <TableCell + key={headCell.id} + style={{ fontWeight: "520" }} + padding={headCell.disablePadding ? "none" : "default"} + align={headCell.numeric ? "right" : "left"} + > + {headCell.label} + </TableCell> + ) + )} + </TableRow> + </TableHead> + ); +} + +EnhancedTableHead.propTypes = { + classes: PropTypes.object.isRequired, + numSelected: PropTypes.number.isRequired, + onRequestSort: PropTypes.func.isRequired, + onSelectAllClick: PropTypes.func.isRequired, + order: PropTypes.oneOf(["asc", "desc"]).isRequired, + orderBy: PropTypes.string.isRequired, + rowCount: PropTypes.number.isRequired, +}; + +const useToolbarStyles = makeStyles((theme) => ({ + root: { + paddingLeft: theme.spacing(2), + paddingRight: theme.spacing(1), + }, + highlight: + theme.palette.type === "light" + ? { + color: theme.palette.primary.main, + backgroundColor: lighten(theme.palette.primary.light, 0.85), + } + : { + color: theme.palette.text.primary, + backgroundColor: theme.palette.primary.dark, + }, + title: { + flex: "1 1 100%", + }, +})); + +const EnhancedTableToolbar = (props) => { + const classes = useToolbarStyles(); + const { numSelected } = props; + + return ( + <Toolbar + className={clsx(classes.root, { + [classes.highlight]: numSelected > 0, + })} + > + <Typography + className={classes.title} + variant="h6" + id="tableTitle" + component="div" + > + {props.tableName} + </Typography> + + <Typography + className={classes.title} + style={{ textAlign: "right" }} + color="inherit" + variant="subtitle1" + component="div" + > + {numSelected} selected + </Typography> + </Toolbar> + ); +}; + +EnhancedTableToolbar.propTypes = { + numSelected: PropTypes.number.isRequired, +}; + +const useStyles = makeStyles((theme) => ({ + tableRoot: { + width: "100%", + }, + paper: { + width: "100%", + marginBottom: theme.spacing(2), + }, + table: { + minWidth: 550, + }, + visuallyHidden: { + border: 0, + clip: "rect(0 0 0 0)", + height: 1, + margin: -1, + overflow: "hidden", + padding: 0, + position: "absolute", + top: 20, + width: 1, + }, + appBar: { + position: "relative", + }, + title: { + marginLeft: theme.spacing(2), + flex: 1, + }, + demo: { + backgroundColor: theme.palette.background.paper, + }, + root: { + flexGrow: 1, + backgroundColor: theme.palette.background.paper, + display: "flex", + height: 424, + }, + tabs: { + borderRight: `1px solid ${theme.palette.divider}`, + }, +})); + +function EnhancedTable({ + clusters, + formikValues, + tableName, + onRowSelect, + ...props +}) { + const classes = useStyles(); + const [order, setOrder] = useState("asc"); + const [orderBy, setOrderBy] = useState("name"); + const [selected, setSelected] = useState([]); + const [page, setPage] = useState(0); + const [rowsPerPage, setRowsPerPage] = useState(5); + const [rows, setRows] = useState([]); + + const handleRequestSort = (event, property) => { + const isAsc = orderBy === property && order === "asc"; + setOrder(isAsc ? "desc" : "asc"); + setOrderBy(property); + }; + + useEffect(() => { + if (formikValues) { + let formikClusterData = formikValues.filter( + (cluster) => cluster.provider === tableName + ); + if (formikClusterData && formikClusterData.length > 0) { + let data = []; + formikClusterData[0].selectedClusters.forEach((selectedCluster) => { + data.push(selectedCluster.name); + }); + setSelected(data); + } + } + setRows(clusters); + }, []); + + useEffect(() => { + onRowSelect(tableName, selected); + }, [selected]); + + const handleSelectAllClick = (event) => { + if (event.target.checked) { + const newSelecteds = rows.map((n) => n.name); + setSelected(newSelecteds); + return; + } + setSelected([]); + }; + + const handleClick = (event, name) => { + const selectedIndex = selected.indexOf(name); + let newSelected = []; + + if (selectedIndex === -1) { + newSelected = newSelected.concat(selected, name); + } else if (selectedIndex === 0) { + newSelected = newSelected.concat(selected.slice(1)); + } else if (selectedIndex === selected.length - 1) { + newSelected = newSelected.concat(selected.slice(0, -1)); + } else if (selectedIndex > 0) { + newSelected = newSelected.concat( + selected.slice(0, selectedIndex), + selected.slice(selectedIndex + 1) + ); + } + setSelected(newSelected); + }; + + const handleChangePage = (event, newPage) => { + setPage(newPage); + }; + + const handleChangeRowsPerPage = (event) => { + setRowsPerPage(parseInt(event.target.value, 10)); + setPage(0); + }; + + const isSelected = (name) => selected.indexOf(name) !== -1; + + const emptyRows = + rowsPerPage - Math.min(rowsPerPage, rows.length - page * rowsPerPage); + + return ( + <div className={classes.tableRoot}> + <EnhancedTableToolbar + tableName={tableName} + numSelected={selected.length} + /> + <TableContainer> + <Table + className={classes.table} + aria-labelledby="tableTitle" + size={"small"} + aria-label="enhanced table" + > + <EnhancedTableHead + classes={classes} + numSelected={selected.length} + order={order} + orderBy={orderBy} + onSelectAllClick={handleSelectAllClick} + onRequestSort={handleRequestSort} + rowCount={rows.length} + /> + <TableBody> + {stableSort(rows, getComparator(order, orderBy)) + .slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) + .map((row, index) => { + const isItemSelected = isSelected(row.name); + const labelId = `enhanced-table-checkbox-${index}`; + + return ( + <TableRow + hover + onClick={(event) => handleClick(event, row.name)} + role="checkbox" + aria-checked={isItemSelected} + tabIndex={-1} + key={row.name} + selected={isItemSelected} + > + <TableCell padding="checkbox"> + <Checkbox + checked={isItemSelected} + inputProps={{ "aria-labelledby": labelId }} + /> + </TableCell> + <TableCell + component="th" + id={labelId} + scope="row" + padding="none" + > + {row.name} + </TableCell> + <TableCell align="right">{row.description}</TableCell> + </TableRow> + ); + })} + {emptyRows > 0 && ( + <TableRow style={{ height: 33 * emptyRows }}> + <TableCell colSpan={6} /> + </TableRow> + )} + </TableBody> + </Table> + </TableContainer> + <TablePagination + rowsPerPageOptions={[5, 10, 25]} + component="div" + count={rows.length} + rowsPerPage={rowsPerPage} + page={page} + onChangePage={handleChangePage} + onChangeRowsPerPage={handleChangeRowsPerPage} + /> + </div> + ); +} + +export default EnhancedTable; diff --git a/src/tools/emcoui/src/compositeApps/intents/AppPlacementIntentTable.jsx b/src/tools/emcoui/src/compositeApps/intents/AppPlacementIntentTable.jsx index 6dfb8279..09f70c99 100644 --- a/src/tools/emcoui/src/compositeApps/intents/AppPlacementIntentTable.jsx +++ b/src/tools/emcoui/src/compositeApps/intents/AppPlacementIntentTable.jsx @@ -11,7 +11,7 @@ // 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. -// ======================================================================== +// ======================================================================== import React, { useState } from "react"; import { TableContainer, @@ -24,7 +24,7 @@ import { } from "@material-ui/core"; import Paper from "@material-ui/core/Paper"; import TableBody from "@material-ui/core/TableBody"; -import EditIcon from "@material-ui/icons/Edit"; +// import EditIcon from "@material-ui/icons/Edit"; import DeleteIcon from "@material-ui/icons/Delete"; import PropTypes from "prop-types"; import apiService from "../../services/apiService"; @@ -106,31 +106,42 @@ const AppPlacementIntentTable = ({ data, setData, ...props }) => { <StyledTableCell>{entry.name}</StyledTableCell> <StyledTableCell>{entry.description}</StyledTableCell> <StyledTableCell> - {entry.allOf.map((intent, index) => ( - <Paper - key={index} - style={{ width: "max-content" }} - variant="outlined" - > - <label>Cluster Provider : </label> - <label style={{ fontWeight: "bold" }}> - {intent["provider-name"]}, - </label> - <label>Labels : </label> - <Chip - style={{ marginRight: "10px" }} - size="small" - label={intent["cluster-label-name"]} - color="primary" + {entry.allOf && + entry.allOf.map((intent, index) => ( + <Paper + key={index} + style={{ width: "max-content" }} variant="outlined" - /> - </Paper> - ))} + > + <label>Cluster Provider : </label> + <label style={{ fontWeight: "bold" }}> + {intent["provider-name"]} + </label> + <label>, Cluster : </label> + <label style={{ fontWeight: "bold" }}> + {intent["cluster-name"]} + </label> + {intent["cluster-label-name"] && ( + <> + <label>, Labels : </label> + <Chip + style={{ marginRight: "10px" }} + size="small" + label={intent["cluster-label-name"]} + color="primary" + variant="outlined" + /> + </> + )} + </Paper> + ))} </StyledTableCell> <StyledTableCell> + {/* + //edit app placement api has not been implemented yet <IconButton onClick={(e) => handleEdit(index)} title="Edit"> <EditIcon color="primary" /> - </IconButton> + </IconButton> */} <IconButton onClick={(e) => handleDelete(index)} title="Delete" diff --git a/src/tools/emcoui/src/compositeApps/intents/GenericPlacementIntentCard.jsx b/src/tools/emcoui/src/compositeApps/intents/GenericPlacementIntentCard.jsx index bb43972c..9946c92d 100644 --- a/src/tools/emcoui/src/compositeApps/intents/GenericPlacementIntentCard.jsx +++ b/src/tools/emcoui/src/compositeApps/intents/GenericPlacementIntentCard.jsx @@ -11,7 +11,7 @@ // 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. -// ======================================================================== +// ======================================================================== import React, { useState } from "react"; import { makeStyles } from "@material-ui/core/styles"; import clsx from "clsx"; @@ -168,6 +168,10 @@ const GenericPlacementIntentCard = (props) => { variant="outlined" size="small" color="secondary" + disabled={ + appPlacementIntentData.applications && + appPlacementIntentData.applications.length > 0 + } style={{ float: "right" }} startIcon={<DeleteIcon />} onClick={() => { @@ -190,6 +194,9 @@ const GenericPlacementIntentCard = (props) => { } /> )} + {!(props.appsData && props.appsData.length > 0) && ( + <div>No app found for adding app placement intent</div> + )} </CardContent> </Collapse> </Card> diff --git a/src/tools/emcoui/src/deploymentIntentGroups/DIGform.jsx b/src/tools/emcoui/src/deploymentIntentGroups/DIGform.jsx index f0cf1e1d..ee1fec74 100644 --- a/src/tools/emcoui/src/deploymentIntentGroups/DIGform.jsx +++ b/src/tools/emcoui/src/deploymentIntentGroups/DIGform.jsx @@ -11,245 +11,112 @@ // 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. -// ======================================================================== -import React, { useState, useEffect } from 'react'; -import PropTypes from 'prop-types'; -import { withStyles } from '@material-ui/core/styles'; -import Button from '@material-ui/core/Button'; -import Dialog from '@material-ui/core/Dialog'; -import MuiDialogTitle from '@material-ui/core/DialogTitle'; -import MuiDialogContent from '@material-ui/core/DialogContent'; -import MuiDialogActions from '@material-ui/core/DialogActions'; -import IconButton from '@material-ui/core/IconButton'; -import CloseIcon from '@material-ui/icons/Close'; -import Typography from '@material-ui/core/Typography'; -import { TextField, InputLabel, NativeSelect, FormControl, FormHelperText } from '@material-ui/core'; -import * as Yup from "yup"; -import { Formik } from 'formik'; +// ======================================================================== +import React, { useState, useEffect } from "react"; +import PropTypes from "prop-types"; +import { withStyles } from "@material-ui/core/styles"; +import Button from "@material-ui/core/Button"; +import Dialog from "@material-ui/core/Dialog"; +import MuiDialogTitle from "@material-ui/core/DialogTitle"; +import MuiDialogContent from "@material-ui/core/DialogContent"; +import MuiDialogActions from "@material-ui/core/DialogActions"; +import IconButton from "@material-ui/core/IconButton"; +import CloseIcon from "@material-ui/icons/Close"; +import Typography from "@material-ui/core/Typography"; +import Stepper from "./Stepper"; import apiService from "../services/apiService"; const styles = (theme) => ({ - root: { - margin: 0, - padding: theme.spacing(2), - }, - closeButton: { - position: 'absolute', - right: theme.spacing(1), - top: theme.spacing(1), - color: theme.palette.grey[500], - }, + root: { + margin: 0, + padding: theme.spacing(2), + }, + closeButton: { + position: "absolute", + right: theme.spacing(1), + top: theme.spacing(1), + color: theme.palette.grey[500], + }, }); const DialogTitle = withStyles(styles)((props) => { - const { children, classes, onClose, ...other } = props; - return ( - <MuiDialogTitle disableTypography className={classes.root} {...other}> - <Typography variant="h6">{children}</Typography> - {onClose ? ( - <IconButton className={classes.closeButton} onClick={onClose}> - <CloseIcon /> - </IconButton> - ) : null} - </MuiDialogTitle> - ); + const { children, classes, onClose, ...other } = props; + return ( + <MuiDialogTitle disableTypography className={classes.root} {...other}> + <Typography variant="h6">{children}</Typography> + {onClose ? ( + <IconButton className={classes.closeButton} onClick={onClose}> + <CloseIcon /> + </IconButton> + ) : null} + </MuiDialogTitle> + ); }); const DialogActions = withStyles((theme) => ({ - root: { - margin: 0, - padding: theme.spacing(1), - }, + root: { + margin: 0, + padding: theme.spacing(1), + }, }))(MuiDialogActions); const DialogContent = withStyles((theme) => ({ - root: { - padding: theme.spacing(2), - } + root: { + padding: theme.spacing(2), + }, }))(MuiDialogContent); -const schema = Yup.object( - { - name: Yup.string().required(), - description: Yup.string(), - version: Yup.string().required(), - compositeProfile: Yup.string().required(), - overrideValues: Yup.array().of(Yup.object()).typeError("Invalid override values, expected array"), - }) - const DIGform = (props) => { - const { onClose, item, open, onSubmit } = props; - const buttonLabel = item ? "OK" : "Create"; - const title = item ? "Edit Deployment Intent Group" : "Create Deployment Intent Group"; - const [selectedAppIndex, setSelectedAppIndex] = useState(0); - const handleClose = () => { - onClose(); - }; - useEffect(() => { - props.data.compositeApps.forEach(compositeApp => { - let request = { projectName: props.projectName, compositeAppName: compositeApp.metadata.name, compositeAppVersion: compositeApp.spec.version } - apiService.getCompositeProfiles(request).then(res => { - compositeApp.profiles = res; - }).catch(error => { - console.log("error getting cluster providers : ", error) - }).finally(() => { - }) + const { onClose, item, open, onSubmit } = props; + const title = item + ? "Edit Deployment Intent Group" + : "Create Deployment Intent Group"; + const handleClose = () => { + onClose(); + }; + useEffect(() => { + props.data.compositeApps.forEach((compositeApp) => { + let request = { + projectName: props.projectName, + compositeAppName: compositeApp.metadata.name, + compositeAppVersion: compositeApp.spec.version, + }; + apiService + .getCompositeProfiles(request) + .then((res) => { + compositeApp.profiles = res; }) - }, [props.data.compositeApps, props.projectName]); - let initialValues = item ? - { name: item.metadata.name, description: item.metadata.description, overrideValues: JSON.stringify(item.spec["override-values"]), compositeApp: item.compositeAppName, compositeProfile: item.spec.profile, version: item.spec.version } : - { name: "", description: "", overrideValues: undefined, compositeApp: props.data.compositeApps[0].metadata.name, compositeProfile: "", version: "" } - - const handleSetCompositeApp = (val) => { - props.data.compositeApps.forEach((ca, index) => { - if (ca.metadata.name === val) - setSelectedAppIndex(index); - }); - } - - return ( - <Dialog maxWidth={"xs"} onClose={handleClose} aria-labelledby="customized-dialog-title" open={open} disableBackdropClick> - <DialogTitle id="simple-dialog-title">{title}</DialogTitle> - <Formik - initialValues={initialValues} - onSubmit={async values => { - values.compositeAppVersion = props.data.compositeApps[selectedAppIndex].spec.version; - onSubmit(values); - }} - validationSchema={schema} - > - {formicProps => { - const { - values, - touched, - errors, - isSubmitting, - handleChange, - handleBlur, - handleSubmit - } = formicProps; - return ( - <form noValidate onSubmit={handleSubmit} onChange={handleChange}> - <DialogContent dividers> - <div style={{ width: "45%", float: "left" }}> - <InputLabel shrink htmlFor="compositeApp-label-placeholder"> - Composite App - </InputLabel> - <NativeSelect - name="compositeApp" - onChange={(e) => { handleChange(e); handleSetCompositeApp(e.target.value) }} - onBlur={handleBlur} - disabled={item ? true : false} - inputProps={{ - name: 'compositeApp', - id: 'compositeApps-label-placeholder', - }} - > - {item && (<option >{values.compositeApp}</option>)} - {props.data && props.data.compositeApps.map(compositeApp => - (<option value={compositeApp.metadata.name} key={compositeApp.metadata.name} >{compositeApp.metadata.name}</option>) - )} - </NativeSelect> - </div> - - <FormControl style={{ width: "45%", float: "right" }} required error={errors.compositeProfile && touched.compositeProfile}> - <InputLabel htmlFor="compositeProfile-label-placeholder"> - Composite Profile - </InputLabel> - <NativeSelect - name="compositeProfile" - onChange={handleChange} - onBlur={handleBlur} - disabled={item ? true : false} - required - inputProps={{ - name: 'compositeProfile', - id: 'compositeProfile-label-placeholder', - }} - > - <option value="" /> - {props.data.compositeApps[selectedAppIndex].profiles && props.data.compositeApps[selectedAppIndex].profiles.map(compositeProfile => - (<option value={compositeProfile.metadata.name} key={compositeProfile.metadata.name} >{compositeProfile.metadata.name}</option>) - )} - </NativeSelect> - {errors.compositeProfile && touched.compositeProfile && <FormHelperText>Required</FormHelperText>} - </FormControl> - <TextField - style={{ width: "45%", float: "left", marginTop: "10px" }} - id="name" - label="Name" - type="text" - value={values.name} - onChange={handleChange} - onBlur={handleBlur} - helperText={(errors.name && touched.name && ( - "Name is required" - ))} - required - error={errors.name && touched.name} - /> - <TextField - style={{ width: "45%", float: "right", marginTop: "10px" }} - id="version" - label="Version" - type="text" - name="version" - onChange={handleChange} - onBlur={handleBlur} - helperText={(errors.version && touched.version && ( - "Version is required" - ))} - required - error={errors.version && touched.version} - /> - <TextField - style={{ width: "100%", marginTop: "20px" }} - id="overrideValues" - label="Override Values" - type="text" - value={values.overrideValues} - onChange={handleChange} - onBlur={handleBlur} - required - multiline - rows={4} - variant="outlined" - error={errors.overrideValues && touched.overrideValues} - helperText={(errors.overrideValues && touched.overrideValues && ( - (errors["overrideValues"]) - ))} - /> - <TextField - style={{ width: "100%", marginBottom: "25px", marginTop: "10px" }} - name="description" - value={values.description} - onChange={handleChange} - onBlur={handleBlur} - id="description" - label="Description" - multiline - rowsMax={4} - /> - </DialogContent> - <DialogActions> - <Button autoFocus onClick={handleClose} color="secondary"> - Cancel - </Button> - <Button autoFocus type="submit" color="primary" disabled={isSubmitting}> - {buttonLabel} - </Button> - </DialogActions> - </form> - ); - }} - </Formik> - </Dialog> - ); + .catch((error) => { + console.log("error getting cluster providers : ", error); + }) + .finally(() => {}); + }); + }, [props.data.compositeApps, props.projectName]); + return ( + <Dialog + maxWidth={"md"} + fullWidth={true} + onClose={handleClose} + open={open} + disableBackdropClick + > + <DialogTitle id="customized-dialog-title" onClose={handleClose}> + {title} + </DialogTitle> + <DialogContent dividers> + <Stepper + data={props.data} + projectName={props.projectName} + onSubmit={onSubmit} + /> + </DialogContent> + </Dialog> + ); }; DIGform.propTypes = { - onClose: PropTypes.func.isRequired, - open: PropTypes.bool.isRequired, + onClose: PropTypes.func.isRequired, + open: PropTypes.bool.isRequired, }; export default DIGform; diff --git a/src/tools/emcoui/src/deploymentIntentGroups/DIGtable.jsx b/src/tools/emcoui/src/deploymentIntentGroups/DIGtable.jsx index 5710b52b..3e22dc4c 100644 --- a/src/tools/emcoui/src/deploymentIntentGroups/DIGtable.jsx +++ b/src/tools/emcoui/src/deploymentIntentGroups/DIGtable.jsx @@ -11,7 +11,7 @@ // 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. -// ======================================================================== +// ======================================================================== import React, { useState } from "react"; import { withStyles, makeStyles } from "@material-ui/core/styles"; import Table from "@material-ui/core/Table"; @@ -22,14 +22,14 @@ import TableHead from "@material-ui/core/TableHead"; import TableRow from "@material-ui/core/TableRow"; import Paper from "@material-ui/core/Paper"; import IconButton from "@material-ui/core/IconButton"; -import EditIcon from "@material-ui/icons/Edit"; +// import EditIcon from "@material-ui/icons/Edit"; import DeleteDialog from "../common/Dialogue"; -import AddIcon from "@material-ui/icons/Add"; +// import AddIcon from "@material-ui/icons/Add"; import DeleteIcon from "@material-ui/icons/Delete"; import GetAppIcon from "@material-ui/icons/GetApp"; import apiService from "../services/apiService"; -import { Button } from "@material-ui/core"; -import IntentsForm from "./IntentsForm"; +// import { Button } from "@material-ui/core"; +// import IntentsForm from "./IntentsForm"; import Notification from "../common/Notification"; const StyledTableCell = withStyles((theme) => ({ @@ -58,20 +58,15 @@ const useStyles = makeStyles({ export default function DIGtable({ data, setData, ...props }) { const classes = useStyles(); const [open, setOpen] = useState(false); - // const [openForm, setOpenForm] = useState(false); const [index, setIndex] = useState(0); - const [openIntentsForm, setOpenIntentsForm] = useState(false); + // const [openIntentsForm, setOpenIntentsForm] = useState(false); const [notificationDetails, setNotificationDetails] = useState({}); - let handleEdit = (index) => { - // setIndex(index); - // setOpenForm(true); - }; const handleClose = (el) => { if (el.target.innerText === "Delete") { let request = { projectName: props.projectName, - compositeAppName: data[index].compositeAppName, - compositeAppVersion: data[index].compositeAppVersion, + compositeAppName: data[index].metadata.compositeAppName, + compositeAppVersion: data[index].metadata.compositeAppVersion, deploymentIntentGroupName: data[index].metadata.name, }; apiService @@ -92,50 +87,46 @@ export default function DIGtable({ data, setData, ...props }) { setIndex(index); setOpen(true); }; - const handleAddIntent = (index) => { - setIndex(index); - setOpenIntentsForm(true); - }; - const handleCloseIntentsForm = () => { - setOpenIntentsForm(false); - }; - const handleSubmitIntentForm = (values) => { - setOpenIntentsForm(false); - let request = { - projectName: props.projectName, - compositeAppName: values.compositeAppName, - compositeAppVersion: values.compositeAppVersion, - deploymentIntentGroupName: values.deploymentIntentGroupName, - payload: { - metadata: { name: values.name, description: values.description }, - spec: { - intent: { - genericPlacementIntent: values.genericPlacementIntent, - }, - }, - }, - }; - if (values.networkControllerIntent && values.networkControllerIntent !== "") - request.payload.spec.intent.ovnaction = values.networkControllerIntent; - apiService - .addIntentsToDeploymentIntentGroup(request) - .then((res) => { - if (data[index].intent) { - data[index].intent.push(res.spec.intent); - } else { - data[index].intent = [res.spec.intent]; - } - setData([...data]); - }) - .catch((err) => { - console.log("error adding intent to deployment intent group"); - }); - }; + // const handleCloseIntentsForm = () => { + // setOpenIntentsForm(false); + // }; + // const handleSubmitIntentForm = (values) => { + // setOpenIntentsForm(false); + // let request = { + // projectName: props.projectName, + // compositeAppName: values.compositeAppName, + // compositeAppVersion: values.compositeAppVersion, + // deploymentIntentGroupName: values.deploymentIntentGroupName, + // payload: { + // metadata: { name: values.name, description: values.description }, + // spec: { + // intent: { + // genericPlacementIntent: values.genericPlacementIntent, + // }, + // }, + // }, + // }; + // if (values.networkControllerIntent && values.networkControllerIntent !== "") + // request.payload.spec.intent.ovnaction = values.networkControllerIntent; + // apiService + // .addIntentsToDeploymentIntentGroup(request) + // .then((res) => { + // if (data[index].intent) { + // data[index].intent.push(res.spec.intent); + // } else { + // data[index].intent = [res.spec.intent]; + // } + // setData([...data]); + // }) + // .catch((err) => { + // console.log("error adding intent to deployment intent group"); + // }); + // }; const handleInstantiate = (index) => { let request = { projectName: props.projectName, - compositeAppName: data[index].compositeAppName, - compositeAppVersion: data[index].compositeAppVersion, + compositeAppName: data[index].metadata.compositeAppName, + compositeAppVersion: data[index].metadata.compositeAppVersion, deploymentIntentGroupName: data[index].metadata.name, }; apiService @@ -184,13 +175,6 @@ export default function DIGtable({ data, setData, ...props }) { <Notification notificationDetails={notificationDetails} /> {data && data.length > 0 && ( <> - <IntentsForm - projectName={props.projectName} - open={openIntentsForm} - onClose={handleCloseIntentsForm} - onSubmit={handleSubmitIntentForm} - data={data[index]} - /> <DeleteDialog open={open} onClose={handleClose} @@ -207,7 +191,7 @@ export default function DIGtable({ data, setData, ...props }) { <StyledTableCell>Version</StyledTableCell> <StyledTableCell>Profile</StyledTableCell> <StyledTableCell>Composite App</StyledTableCell> - <StyledTableCell>Intents</StyledTableCell> + {/* <StyledTableCell>Intents</StyledTableCell> */} <StyledTableCell>Description</StyledTableCell> <StyledTableCell style={{ width: "15%" }}> Actions @@ -225,54 +209,39 @@ export default function DIGtable({ data, setData, ...props }) { {row.spec.profile} </StyledTableCell> <StyledTableCell className={classes.cell}> - {row.compositeAppName} + {row.metadata.compositeAppName} </StyledTableCell> - { + {/* { <StyledTableCell className={classes.cell}> - {row.intent - ? row.intent.map((intentEntry) => { - return Object.keys(intentEntry) - .map(function (k) { - return intentEntry[k]; - }) - .join(" | "); - }) - : ""} + {Object.keys(row.spec.deployedIntents[0]).map(function ( + key, + index + ) { + if ( + index === 0 || + row.spec.deployedIntents[0][key] === "" + ) + return row.spec.deployedIntents[0][key]; + else return ", " + row.spec.deployedIntents[0][key]; + })} </StyledTableCell> - } + } */} <StyledTableCell className={classes.cell}> {row.metadata.description} </StyledTableCell> <StyledTableCell className={classes.cell}> - <Button - variant="outlined" - color="primary" - size="small" - onClick={() => { - handleAddIntent(index); - }} - startIcon={<AddIcon />} - > - Intents - </Button> <IconButton - disabled={!(row.intent && row.intent.length > 0)} + color={"primary"} + // disabled={ + // !( + // row.spec.deployedIntents && + // row.spec.deployedIntents.length > 0 + // ) + // } title="Instantiate" onClick={(e) => handleInstantiate(index)} > - <GetAppIcon - color={ - !(row.intent && row.intent.length > 0) - ? "" - : "primary" - } - /> - </IconButton> - <IconButton - onClick={(e) => handleEdit(index)} - title="Edit" - > - <EditIcon color="primary" /> + <GetAppIcon /> </IconButton> <IconButton onClick={(e) => handleDelete(index)} diff --git a/src/tools/emcoui/src/deploymentIntentGroups/DeploymentIntentGroups.jsx b/src/tools/emcoui/src/deploymentIntentGroups/DeploymentIntentGroups.jsx index c1f73bb7..132b9fc3 100644 --- a/src/tools/emcoui/src/deploymentIntentGroups/DeploymentIntentGroups.jsx +++ b/src/tools/emcoui/src/deploymentIntentGroups/DeploymentIntentGroups.jsx @@ -11,14 +11,15 @@ // 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. -// ======================================================================== +// ======================================================================== import React, { useEffect, useState } from "react"; import DIGtable from "./DIGtable"; -import { withStyles, Button, Grid } from "@material-ui/core"; +import { withStyles, Button, Grid, Typography } from "@material-ui/core"; import AddIcon from "@material-ui/icons/Add"; import apiService from "../services/apiService"; import Spinner from "../common/Spinner"; import DIGform from "./DIGform"; +import { ReactComponent as EmptyIcon } from "../assets/icons/empty.svg"; const styles = { root: { @@ -44,84 +45,59 @@ const DeploymentIntentGroups = (props) => { setOpen(true); }; const handleSubmit = (inputFields) => { - let payload = { - metadata: { - name: inputFields.name, - description: inputFields.description, - }, - spec: { - profile: inputFields.compositeProfile, - version: inputFields.version, - }, - projectName: props.projectName, - compositeAppName: inputFields.compositeApp, - compositeAppVersion: inputFields.compositeAppVersion, - }; - if (inputFields.overrideValues && inputFields.overrideValues !== "") { - payload.spec["override-values"] = JSON.parse(inputFields.overrideValues); + try { + let payload = { + spec: { + projectName: props.projectName, + appsData: inputFields.intents.apps, + }, + }; + if (inputFields.overrideValues && inputFields.overrideValues !== "") { + payload.spec["override-values"] = JSON.parse( + inputFields.overrideValues + ); + } + payload = { ...payload, ...inputFields.general }; + apiService + .createDeploymentIntentGroup(payload) + .then((response) => { + response.metadata.compositeAppName = inputFields.general.compositeApp; + response.metadata.compositeAppVersion = + inputFields.general.compositeAppVersion; + data && data.length > 0 + ? setData([...data, response]) + : setData([response]); + }) + .catch((error) => { + console.log("error creating DIG : ", error); + }) + .finally(() => { + setIsloading(false); + setOpen(false); + }); + } catch (error) { + console.error(error); } - apiService - .createDeploymentIntentGroup(payload) - .then((response) => { - response.compositeAppName = inputFields.compositeApp; - response.compositeAppVersion = inputFields.compositeAppVersion; - data && data.length > 0 - ? setData([...data, response]) - : setData([response]); - }) - .catch((error) => { - console.log("error creating DIG : ", error); - }) - .finally(() => { - setIsloading(false); - setOpen(false); - }); }; useEffect(() => { + let getDigs = () => { + apiService + .getDeploymentIntentGroups({ projectName: props.projectName }) + .then((res) => { + setData(res); + }) + .catch((err) => { + console.log("error getting deplotment intent groups : " + err); + }) + .finally(() => setIsloading(false)); + }; + apiService .getCompositeApps({ projectName: props.projectName }) .then((response) => { - const getDigIntents = (input) => { - let request = { - projectName: props.projectName, - compositeAppName: input.compositeAppName, - compositeAppVersion: input.compositeAppVersion, - deploymentIntentGroupName: input.metadata.name, - }; - apiService - .getDeploymentIntentGroupIntents(request) - .then((res) => { - input.intent = res.intent; - }) - .catch((err) => {}) - .finally(() => { - setData((data) => [...data, input]); - }); - }; - response.forEach((compositeApp) => { - let request = { - projectName: props.projectName, - compositeAppName: compositeApp.metadata.name, - compositeAppVersion: compositeApp.spec.version, - }; - apiService - .getDeploymentIntentGroups(request) - .then((digResponse) => { - digResponse.forEach((res) => { - res.compositeAppName = compositeApp.metadata.name; - res.compositeAppVersion = compositeApp.spec.version; - getDigIntents(res); - }); - }) - .catch((error) => { - console.log("unable to get deployment intent groups", error); - }) - .finally(() => { - setCompositeApps(response); - setIsloading(false); - }); - }); + setCompositeApps(response); + getDigs(); }) .catch((err) => { console.log("Unable to get composite apps : ", err); @@ -131,16 +107,8 @@ const DeploymentIntentGroups = (props) => { return ( <> {isLoading && <Spinner />} - {!isLoading && compositeApps && compositeApps.length > 0 && ( + {!isLoading && compositeApps && ( <> - <Button - variant="outlined" - color="primary" - startIcon={<AddIcon />} - onClick={onCreateDIG} - > - Create Deployment Intent Group - </Button> <DIGform projectName={props.projectName} open={open} @@ -148,15 +116,41 @@ const DeploymentIntentGroups = (props) => { onSubmit={handleSubmit} data={{ compositeApps: compositeApps }} /> - <Grid container spacing={2} alignItems="center"> - <Grid item xs style={{ marginTop: "20px" }}> - <DIGtable - data={data} - setData={setData} - projectName={props.projectName} - /> - </Grid> + <Grid item xs={12}> + <Button + variant="outlined" + color="primary" + startIcon={<AddIcon />} + onClick={onCreateDIG} + > + Create Deployment Intent Group + </Button> </Grid> + + {data && data.length > 0 && ( + <Grid container spacing={2} alignItems="center"> + <Grid item xs style={{ marginTop: "20px" }}> + <DIGtable + data={data} + setData={setData} + projectName={props.projectName} + /> + </Grid> + </Grid> + )} + + {(data === null || (data && data.length < 1)) && ( + <Grid container spacing={2} direction="column" alignItems="center"> + <Grid style={{ marginTop: "60px" }} item xs={6}> + <EmptyIcon style={{ height: "100px", width: "100px" }} /> + </Grid> + <Grid item xs={12}> + <Typography variant="h6"> + No deployment group found, start by adding a deployment group + </Typography> + </Grid> + </Grid> + )} </> )} </> diff --git a/src/tools/emcoui/src/deploymentIntentGroups/DigFormApp.jsx b/src/tools/emcoui/src/deploymentIntentGroups/DigFormApp.jsx new file mode 100644 index 00000000..e0662455 --- /dev/null +++ b/src/tools/emcoui/src/deploymentIntentGroups/DigFormApp.jsx @@ -0,0 +1,201 @@ +//======================================================================= +// Copyright (c) 2017-2020 Aarna Networks, Inc. +// All rights reserved. +// ====================================================================== +// 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. +// ======================================================================== +import { makeStyles } from "@material-ui/core/styles"; +import PropTypes from "prop-types"; +import Tabs from "@material-ui/core/Tabs"; +import Tab from "@material-ui/core/Tab"; +import Box from "@material-ui/core/Box"; +import React, { useState } from "react"; +import Typography from "@material-ui/core/Typography"; +import { Formik } from "formik"; +import ExpandableCard from "../common/ExpandableCard"; +import AppPlacementForm from "../compositeApps/dialogs/AppFormPlacement"; +import NetworkForm from "../compositeApps/dialogs/AppNetworkForm"; + +const useStyles = makeStyles((theme) => ({ + tableRoot: { + width: "100%", + }, + paper: { + width: "100%", + marginBottom: theme.spacing(2), + }, + table: { + minWidth: 550, + }, + visuallyHidden: { + border: 0, + clip: "rect(0 0 0 0)", + height: 1, + margin: -1, + overflow: "hidden", + padding: 0, + position: "absolute", + top: 20, + width: 1, + }, + appBar: { + position: "relative", + }, + title: { + marginLeft: theme.spacing(2), + flex: 1, + }, + demo: { + backgroundColor: theme.palette.background.paper, + }, + root: { + flexGrow: 1, + backgroundColor: theme.palette.background.paper, + display: "flex", + height: 424, + }, + tabs: { + borderRight: `1px solid ${theme.palette.divider}`, + }, +})); +function TabPanel(props) { + const { children, value, index, ...other } = props; + return ( + <div + role="tabpanel" + hidden={value !== index} + id={`vertical-tabpanel-${index}`} + aria-labelledby={`vertical-tab-${index}`} + {...other} + > + {value === index && <Box style={{ padding: "0 24px" }}>{children}</Box>} + </div> + ); +} + +function AppDetailsForm({ formikProps, ...props }) { + const classes = useStyles(); + const [value, setValue] = useState(0); + const handleChange = (event, newValue) => { + setValue(newValue); + }; + const handleRowSelect = (clusterProvider, selectedClusters) => { + if ( + !formikProps.values.apps[props.index].clusters || + formikProps.values.apps[props.index].clusters === undefined + ) { + if (selectedClusters.length > 0) { + let selectedClusterData = []; + selectedClusters.forEach((selectedCluster) => { + selectedClusterData.push({ name: selectedCluster, interfaces: [] }); + }); + formikProps.setFieldValue(`apps[${props.index}].clusters`, [ + { + provider: clusterProvider, + selectedClusters: selectedClusterData, + }, + ]); + } + } else { + let selectedClusterData = []; + //filter out the value of cluster provider so that it can be completely replaced by the new values + let updatedClusterValues = formikProps.values.apps[ + props.index + ].clusters.filter((cluster) => cluster.provider !== clusterProvider); + selectedClusters.forEach((selectedCluster) => { + selectedClusterData.push({ name: selectedCluster, interfaces: [] }); + }); + if (selectedClusters.length > 0) + updatedClusterValues.push({ + provider: clusterProvider, + selectedClusters: selectedClusterData, + }); + formikProps.setFieldValue( + `apps[${props.index}].clusters`, + updatedClusterValues + ); + } + }; + return ( + <div className={classes.root}> + <Formik> + {() => { + return ( + <> + <Tabs + orientation="vertical" + variant="scrollable" + value={value} + onChange={handleChange} + aria-label="Vertical tabs example" + className={classes.tabs} + > + <Tab label="Placement" {...a11yProps(1)} /> + <Tab label="Network" {...a11yProps(2)} /> + </Tabs> + <TabPanel style={{ width: "85%" }} value={value} index={0}> + <AppPlacementForm + formikProps={formikProps} + index={props.index} + clusterProviders={props.clusterProviders} + handleRowSelect={handleRowSelect} + /> + </TabPanel> + <TabPanel style={{ width: "85%" }} value={value} index={1}> + <Typography variant="subtitle1">Select Network</Typography> + <NetworkForm + clusters={formikProps.values.apps[props.index].clusters} + formikProps={formikProps} + index={props.index} + /> + </TabPanel> + </> + ); + }} + </Formik> + </div> + ); +} + +TabPanel.propTypes = { + children: PropTypes.node, + index: PropTypes.any.isRequired, + value: PropTypes.any.isRequired, +}; + +function a11yProps(index) { + return { + id: `vertical-tab-${index}`, + "aria-controls": `vertical-tabpanel-${index}`, + }; +} + +const AppForm2 = (props) => { + return ( + <ExpandableCard + error={ + props.formikProps.errors.apps && + props.formikProps.errors.apps[props.index] + } + title={props.name} + description={props.description} + content={ + <AppDetailsForm + formikProps={props.formikProps} + name={props.name} + index={props.index} + clusterProviders={props.clusterProviders} + /> + } + /> + ); +}; +export default AppForm2; diff --git a/src/tools/emcoui/src/deploymentIntentGroups/DigFormGeneral.jsx b/src/tools/emcoui/src/deploymentIntentGroups/DigFormGeneral.jsx new file mode 100644 index 00000000..5b5c4191 --- /dev/null +++ b/src/tools/emcoui/src/deploymentIntentGroups/DigFormGeneral.jsx @@ -0,0 +1,265 @@ +//======================================================================= +// Copyright (c) 2017-2020 Aarna Networks, Inc. +// All rights reserved. +// ====================================================================== +// 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. +// ======================================================================== +import React, { useEffect, useState } from "react"; +import { Formik } from "formik"; +import * as Yup from "yup"; + +import { + Button, + DialogActions, + FormControl, + FormHelperText, + Grid, + InputLabel, + MenuItem, + Select, + TextField, +} from "@material-ui/core"; + +const schema = Yup.object({ + name: Yup.string().required(), + description: Yup.string(), + version: Yup.string() + .matches(/^[A-Za-z0-9\\s]+$/, "Special characters and space not allowed") + .required("Version is required"), + compositeProfile: Yup.string().required(), + overrideValues: Yup.array() + .of(Yup.object()) + .typeError("Invalid override values, expected array"), +}); + +function DigFormGeneral(props) { + const { item, onSubmit } = props; + const [selectedAppIndex, setSelectedAppIndex] = useState(0); //let the first composite app as default selection + useEffect(() => { + if (item) { + props.data.compositeApps.forEach((ca, index) => { + if (ca.metadata.name === item.compositeApp) { + setSelectedAppIndex(index); + } + }); + } + }, []); + + let initialValues = item + ? { + ...item, + } + : { + name: "", + description: "", + overrideValues: undefined, + compositeApp: props.data.compositeApps[selectedAppIndex].metadata.name, + compositeProfile: "", + version: "", + }; + + const handleSetCompositeApp = (val) => { + props.data.compositeApps.forEach((ca, index) => { + if (ca.metadata.name === val) setSelectedAppIndex(index); + }); + }; + return ( + <Formik + initialValues={initialValues} + onSubmit={(values) => { + values.compositeAppVersion = + props.data.compositeApps[selectedAppIndex].spec.version; + onSubmit(values); + }} + validationSchema={schema} + > + {(formicProps) => { + const { + values, + touched, + errors, + isSubmitting, + handleChange, + handleBlur, + handleSubmit, + } = formicProps; + return ( + <form noValidate onSubmit={handleSubmit} onChange={handleChange}> + <Grid container spacing={4} justify="center"> + <Grid container item xs={12} spacing={8}> + <Grid item xs={12} md={6}> + <TextField + fullWidth + id="name" + label="Name" + type="text" + value={values.name} + onChange={handleChange} + onBlur={handleBlur} + helperText={ + errors.name && touched.name && "Name is required" + } + required + error={errors.name && touched.name} + /> + </Grid> + <Grid item xs={12} md={6}> + <TextField + fullWidth + id="version" + label="Version" + type="text" + name="version" + value={values.version} + onChange={handleChange} + onBlur={handleBlur} + helperText={ + errors.version && touched.version && errors["version"] + } + required + error={errors.version && touched.version} + /> + </Grid> + </Grid> + + <Grid item container xs={12} spacing={8}> + <Grid item xs={12} md={6}> + <InputLabel shrink htmlFor="compositeApp-label-placeholder"> + Composite App + </InputLabel> + <Select + fullWidth + name="compositeApp" + value={values.compositeApp} + onChange={(e) => { + handleChange(e); + handleSetCompositeApp(e.target.value); + }} + onBlur={handleBlur} + inputProps={{ + name: "compositeApp", + id: "compositeApps-label-placeholder", + }} + > + {props.data && + props.data.compositeApps.map((compositeApp) => ( + <MenuItem + value={compositeApp.metadata.name} + key={compositeApp.metadata.name} + > + {compositeApp.metadata.name} + </MenuItem> + ))} + </Select> + </Grid> + <Grid item xs={12} md={6}> + <FormControl + fullWidth + required + error={errors.compositeProfile && touched.compositeProfile} + > + <InputLabel htmlFor="compositeProfile-label-placeholder"> + Composite Profile + </InputLabel> + <Select + name="compositeProfile" + onChange={handleChange} + onBlur={handleBlur} + required + value={values.compositeProfile} + inputProps={{ + name: "compositeProfile", + id: "compositeProfile-label-placeholder", + }} + > + {props.data.compositeApps[selectedAppIndex].profiles && + props.data.compositeApps[selectedAppIndex].profiles.map( + (compositeProfile) => ( + <MenuItem + value={compositeProfile.metadata.name} + key={compositeProfile.metadata.name} + > + {compositeProfile.metadata.name} + </MenuItem> + ) + )} + </Select> + {errors.compositeProfile && touched.compositeProfile && ( + <FormHelperText>Required</FormHelperText> + )} + </FormControl> + </Grid> + </Grid> + + <Grid item container xs={12} spacing={8}> + <Grid item xs={12} md={6}> + <TextField + fullWidth + name="description" + value={values.description} + onChange={handleChange} + onBlur={handleBlur} + id="description" + label="Description" + multiline + rowsMax={4} + /> + </Grid> + <Grid item xs={12} md={6}> + <TextField + fullWidth + id="overrideValues" + label="Override Values" + type="text" + value={values.overrideValues} + onChange={handleChange} + onBlur={handleBlur} + multiline + rows={4} + variant="outlined" + error={errors.overrideValues && touched.overrideValues} + helperText={ + errors.overrideValues && + touched.overrideValues && + errors["overrideValues"] + } + /> + </Grid> + </Grid> + <Grid item xs={12}> + <DialogActions> + <Button + autoFocus + disabled + onClick={props.onClickBack} + color="secondary" + > + Back + </Button> + <Button + autoFocus + type="submit" + color="primary" + disabled={isSubmitting} + > + Next + </Button> + </DialogActions> + </Grid> + </Grid> + </form> + ); + }} + </Formik> + ); +} + +export default DigFormGeneral; diff --git a/src/tools/emcoui/src/deploymentIntentGroups/DigFormIntents.jsx b/src/tools/emcoui/src/deploymentIntentGroups/DigFormIntents.jsx new file mode 100644 index 00000000..580044ac --- /dev/null +++ b/src/tools/emcoui/src/deploymentIntentGroups/DigFormIntents.jsx @@ -0,0 +1,160 @@ +//======================================================================= +// Copyright (c) 2017-2020 Aarna Networks, Inc. +// All rights reserved. +// ====================================================================== +// 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. +// ======================================================================== +import React, { useEffect, useState } from "react"; +import { Formik } from "formik"; +import * as Yup from "yup"; +import AppForm from "./DigFormApp"; +import apiService from "../services/apiService"; + +import { Button, DialogActions, Grid } from "@material-ui/core"; + +DigFormIntents.propTypes = {}; +const schema = Yup.object({ + apps: Yup.array() + .of( + Yup.object({ + clusters: Yup.array() + .of( + Yup.object({ + provider: Yup.string(), + selectedClusters: Yup.array().of( + Yup.object({ + name: Yup.string(), + interfaces: Yup.array().of( + Yup.object({ + networkName: Yup.string().required(), + subnet: Yup.string().required(), + ip: Yup.string().matches( + /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/, + "invalid ip address" + ), + }) + ), + }) + ), + }) + ) + .required("Select at least one cluster"), + }) + ) + .required("At least one app is required"), +}); + +function DigFormIntents(props) { + const { onSubmit, appsData } = props; + const [isLoading, setIsloading] = useState(true); + const [clusterProviders, setClusterProviders] = useState([]); + let initialValues = { apps: appsData }; + useEffect(() => { + let clusterProviderData = []; + apiService + .getClusterProviders() + .then((res) => { + res.forEach((clusterProvider, providerIndex) => { + clusterProviderData.push({ + name: clusterProvider.metadata.name, + clusters: [], + }); + apiService + .getClusters(clusterProvider.metadata.name) + .then((clusters) => { + clusters.forEach((cluster) => { + clusterProviderData[providerIndex].clusters.push({ + name: cluster.metadata.name, + description: cluster.metadata.description, + }); + }); + if (providerIndex + 1 === res.length) { + setClusterProviders(clusterProviderData); + setIsloading(false); + } + }) + .catch((err) => { + console.log( + `error getting clusters for ${clusterProvider.metadata.name} : ` + + err + ); + }); + }); + }) + .catch((err) => { + console.log("error getting cluster providers : " + err); + }); + }, []); + useEffect(() => {}, []); + + return ( + <Formik + initialValues={initialValues} + onSubmit={(values) => { + values.compositeAppVersion = onSubmit(values); + }} + validationSchema={schema} + > + {(formikProps) => { + const { + values, + isSubmitting, + handleChange, + handleSubmit, + } = formikProps; + return ( + !isLoading && ( + <form noValidate onSubmit={handleSubmit} onChange={handleChange}> + <Grid container spacing={4} justify="center"> + {initialValues.apps && + initialValues.apps.length > 0 && + initialValues.apps.map((app, index) => ( + <Grid key={index} item sm={12} xs={12}> + <AppForm + clusterProviders={clusterProviders} + formikProps={formikProps} + name={app.metadata.name} + description={app.metadata.description} + index={index} + initialValues={values} + /> + </Grid> + ))} + + <Grid item xs={12}> + <DialogActions> + <Button + autoFocus + onClick={props.onClickBack} + color="secondary" + > + Back + </Button> + <Button + autoFocus + type="submit" + color="primary" + disabled={isSubmitting} + > + Submit + </Button> + </DialogActions> + </Grid> + </Grid> + </form> + ) + ); + }} + </Formik> + ); +} + +export default DigFormIntents; diff --git a/src/tools/emcoui/src/deploymentIntentGroups/Stepper.jsx b/src/tools/emcoui/src/deploymentIntentGroups/Stepper.jsx new file mode 100644 index 00000000..746f49e3 --- /dev/null +++ b/src/tools/emcoui/src/deploymentIntentGroups/Stepper.jsx @@ -0,0 +1,118 @@ +//======================================================================= +// Copyright (c) 2017-2020 Aarna Networks, Inc. +// All rights reserved. +// ====================================================================== +// 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. +// ======================================================================== +import React, { useState } from "react"; +import { makeStyles } from "@material-ui/core/styles"; +import Stepper from "@material-ui/core/Stepper"; +import Step from "@material-ui/core/Step"; +import StepLabel from "@material-ui/core/StepLabel"; +import DigFormGeneral from "./DigFormGeneral"; +import DigFormIntents from "./DigFormIntents"; +import apiService from "../services/apiService"; + +const useStyles = makeStyles((theme) => ({ + root: { + width: "100%", + }, + backButton: { + marginRight: theme.spacing(1), + }, + instructions: { + marginTop: theme.spacing(1), + marginBottom: theme.spacing(1), + }, +})); + +function getSteps() { + return ["General", "Intents"]; +} + +export default function HorizontalStepper(props) { + const classes = useStyles(); + const [activeStep, setActiveStep] = useState(0); + const [generalData, setGeneralData] = useState(null); + const [intentsData, setIntentsData] = useState(null); + const [appsData, setAppsData] = useState([]); + + const steps = getSteps(); + + function getStepContent(stepIndex) { + switch (stepIndex) { + case 0: + return ( + <DigFormGeneral + data={props.data} + onSubmit={handleGeneralFormSubmit} + item={generalData} + /> + ); + case 1: + return ( + <DigFormIntents + appsData={appsData} + onSubmit={handleIntentsFormSubmit} + onClickBack={handleBack} + item={intentsData} + /> + ); + default: + return "Unknown stepIndex"; + } + } + + const handleNext = () => { + setActiveStep((prevActiveStep) => prevActiveStep + 1); + }; + + const handleBack = () => { + setActiveStep((prevActiveStep) => prevActiveStep - 1); + }; + const handleGeneralFormSubmit = (values) => { + setGeneralData(values); + let request = { + projectName: props.projectName, + compositeAppName: values.compositeApp, + compositeAppVersion: values.compositeAppVersion, + }; + apiService + .getApps(request) + .then((res) => { + setAppsData(res); + handleNext((prevActiveStep) => prevActiveStep + 1); + }) + .catch((err) => { + console.log("Error getting apps : " + err); + }); + }; + + const handleIntentsFormSubmit = (values) => { + setIntentsData(values); + let digPayload = { general: generalData, intents: values }; + props.onSubmit(digPayload); + }; + return ( + <div className={classes.root}> + <Stepper activeStep={activeStep} alternativeLabel> + {steps.map((label) => ( + <Step key={label}> + <StepLabel>{label}</StepLabel> + </Step> + ))} + </Stepper> + <div> + <div>{getStepContent(activeStep)}</div> + </div> + </div> + ); +} diff --git a/src/tools/emcoui/src/networkIntents/NetworkIntentCard.jsx b/src/tools/emcoui/src/networkIntents/NetworkIntentCard.jsx index b641737b..ed93bd0f 100644 --- a/src/tools/emcoui/src/networkIntents/NetworkIntentCard.jsx +++ b/src/tools/emcoui/src/networkIntents/NetworkIntentCard.jsx @@ -11,7 +11,7 @@ // 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. -// ======================================================================== +// ======================================================================== import React, { useState } from "react"; import { makeStyles } from "@material-ui/core/styles"; import clsx from "clsx"; @@ -54,7 +54,7 @@ const NetworkIntentCard = (props) => { const [expanded, setExpanded] = useState(false); const [workloadData, setWorkloadData] = useState([]); const handleExpandClick = () => { - if (!expanded && workloadData.length < 1) { + if (!expanded && workloadData && workloadData.length < 1) { let request = { projectName: props.projectName, compositeAppName: props.compositeAppName, @@ -189,6 +189,7 @@ const NetworkIntentCard = (props) => { variant="outlined" size="small" color="secondary" + disabled={workloadData && workloadData.length > 0} style={{ float: "right" }} startIcon={<DeleteIcon />} onClick={props.onDeleteNetworkControllerIntent.bind( @@ -210,6 +211,9 @@ const NetworkIntentCard = (props) => { } /> )} + {!(props.appsData && props.appsData.length > 0) && ( + <div>No app found for adding workload intent</div> + )} </CardContent> </Collapse> </Card> diff --git a/src/tools/emcoui/src/networkIntents/WorkloadIntentTable.jsx b/src/tools/emcoui/src/networkIntents/WorkloadIntentTable.jsx index 5e196ed9..540de8a0 100644 --- a/src/tools/emcoui/src/networkIntents/WorkloadIntentTable.jsx +++ b/src/tools/emcoui/src/networkIntents/WorkloadIntentTable.jsx @@ -11,207 +11,268 @@ // 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. -// ======================================================================== -import React, { useState } from 'react'; -import { TableContainer, Table, TableRow, TableHead, withStyles, Chip, TableCell } from '@material-ui/core'; +// ======================================================================== +import React, { useState } from "react"; +import { + TableContainer, + Table, + TableRow, + TableHead, + withStyles, + Chip, + TableCell, +} from "@material-ui/core"; import Paper from "@material-ui/core/Paper"; import TableBody from "@material-ui/core/TableBody"; import EditIcon from "@material-ui/icons/Edit"; import DeleteIcon from "@material-ui/icons/Delete"; -import PropTypes from 'prop-types'; +import PropTypes from "prop-types"; import apiService from "../services/apiService"; import DeleteDialog from "../common/Dialogue"; -import InfoOutlinedIcon from '@material-ui/icons/InfoOutlined'; -import IconButton from '@material-ui/core/IconButton'; -import AddIconOutline from '@material-ui/icons/AddCircleOutline'; +import InfoOutlinedIcon from "@material-ui/icons/InfoOutlined"; +import IconButton from "@material-ui/core/IconButton"; +import AddIconOutline from "@material-ui/icons/AddCircleOutline"; import Form from "./InterfaceForm"; import InterfaceDetailsDialog from "../common/DetailsDialog"; - const StyledTableCell = withStyles((theme) => ({ - body: { - fontSize: 14, - }, + body: { + fontSize: 14, + }, }))(TableCell); const StyledTableRow = withStyles((theme) => ({ - root: { - "&:nth-of-type(odd)": { - backgroundColor: theme.palette.action.hover, - }, + root: { + "&:nth-of-type(odd)": { + backgroundColor: theme.palette.action.hover, }, + }, }))(TableRow); const WokloadIntentTable = ({ data, setData, ...props }) => { - const [formOpen, setFormOpen] = useState(false); - const [index, setIndex] = useState(0); - const [openDialog, setOpenDialog] = useState(false); - const [openInterfaceDetails, setOpenInterfaceDetails] = useState(false); - const [selectedInterface, setSelectedInterface] = useState({}); - const [openInterfaceDialog, setOpenInterfaceDialog] = useState(false); - const handleDelete = (index) => { - setIndex(index); - setOpenDialog(true); - } - const handleEdit = () => { + const [formOpen, setFormOpen] = useState(false); + const [index, setIndex] = useState(0); + const [openDialog, setOpenDialog] = useState(false); + const [openInterfaceDetails, setOpenInterfaceDetails] = useState(false); + const [selectedInterface, setSelectedInterface] = useState({}); + const [openInterfaceDialog, setOpenInterfaceDialog] = useState(false); + const handleDelete = (index) => { + setIndex(index); + setOpenDialog(true); + }; + const handleEdit = () => {}; + const handleInterfaceDetailOpen = (entry) => { + setSelectedInterface(entry); + setOpenInterfaceDetails(true); + }; + const handleDeleteInterface = (index, entry) => { + setIndex(index); + setSelectedInterface(entry); + setOpenInterfaceDialog(true); + }; + const handleAddInterface = (index) => { + setIndex(index); + setFormOpen(true); + }; + const handleCloseForm = () => { + setFormOpen(false); + }; + const handleCloseInterfaceDialog = (el) => { + if (el.target.innerText === "Delete") { + let request = { + projectName: props.projectName, + compositeAppName: props.compositeAppName, + compositeAppVersion: props.compositeAppVersion, + networkControllerIntentName: props.networkControllerIntentName, + workloadIntentName: data[index].metadata.name, + interfaceName: selectedInterface.metadata.name, + }; + apiService + .deleteInterface(request) + .then(() => { + console.log("Interface deleted"); + let updatedInterfaceData = data[index].interfaces.filter(function ( + obj + ) { + return obj.metadata.name !== selectedInterface.metadata.name; + }); + data[index].interfaces = updatedInterfaceData; + setData([...data]); + }) + .catch((err) => { + console.log("Error deleting interface : ", err); + }) + .finally(() => { + setIndex(0); + setSelectedInterface({}); + }); } - const handleInterfaceDetailOpen = (entry) => { - setSelectedInterface(entry); - setOpenInterfaceDetails(true); + setOpenInterfaceDialog(false); + }; + const handleCloseDialog = (el) => { + if (el.target.innerText === "Delete") { + let request = { + projectName: props.projectName, + compositeAppName: props.compositeAppName, + compositeAppVersion: props.compositeAppVersion, + networkControllerIntentName: props.networkControllerIntentName, + workloadIntentName: data[index].metadata.name, + }; + apiService + .deleteWorkloadIntent(request) + .then(() => { + console.log("workload intent deleted"); + data.splice(index, 1); + setData([...data]); + }) + .catch((err) => { + console.log("Error deleting workload intent : ", err); + }) + .finally(() => { + setIndex(0); + }); } - - const handleDeleteInterface = (index, entry) => { - setIndex(index); - setSelectedInterface(entry); - setOpenInterfaceDialog(true); - } - const handleAddInterface = (index) => { - setIndex(index); - setFormOpen(true); - } - const handleCloseForm = () => { - setFormOpen(false); - } - const handleCloseInterfaceDialog = (el) => { - if (el.target.innerText === "Delete") { - let request = { - projectName: props.projectName, - compositeAppName: props.compositeAppName, - compositeAppVersion: props.compositeAppVersion, - networkControllerIntentName: props.networkControllerIntentName, - workloadIntentName: data[index].metadata.name, - interfaceName: selectedInterface.metadata.name - } - apiService.deleteInterface(request).then(() => { - console.log("Interface deleted"); - let updatedInterfaceData = data[index].interfaces.filter(function (obj) { - return obj.metadata.name !== selectedInterface.metadata.name; - }); - data[index].interfaces = updatedInterfaceData; - setData([...data]); - }).catch(err => { - console.log("Error deleting interface : ", err) - }).finally(() => { - setIndex(0); - setSelectedInterface({}); - }) - } - setOpenInterfaceDialog(false); - } - const handleCloseDialog = (el) => { - if (el.target.innerText === "Delete") { - let request = { - projectName: props.projectName, - compositeAppName: props.compositeAppName, - compositeAppVersion: props.compositeAppVersion, - networkControllerIntentName: props.networkControllerIntentName, - workloadIntentName: data[index].metadata.name - } - apiService.deleteWorkloadIntent(request).then(() => { - console.log("workload intent deleted"); - data.splice(index, 1); - setData([...data]); - }).catch(err => { - console.log("Error deleting workload intent : ", err) - }).finally(() => { - setIndex(0); - }) + setOpenDialog(false); + }; + const handleSubmit = (values) => { + let spec = values.spec ? JSON.parse(values.spec) : ""; + let request = { + payload: { + metadata: { name: values.name, description: values.description }, + spec: spec, + }, + projectName: props.projectName, + compositeAppName: props.compositeAppName, + compositeAppVersion: props.compositeAppVersion, + networkControllerIntentName: props.networkControllerIntentName, + workloadIntentName: data[index].metadata.name, + }; + apiService + .addInterface(request) + .then((res) => { + if (data[index].interfaces && data[index].interfaces.length > 0) { + data[index].interfaces.push(res); + } else { + data[index].interfaces = [res]; } - setOpenDialog(false); - } - const handleSubmit = (values) => { - let spec = values.spec ? JSON.parse(values.spec) : ""; - let request = { - payload: { metadata: { name: values.name, description: values.description }, spec: spec }, - projectName: props.projectName, - compositeAppName: props.compositeAppName, - compositeAppVersion: props.compositeAppVersion, - networkControllerIntentName: props.networkControllerIntentName, - workloadIntentName: data[index].metadata.name - }; - apiService.addInterface(request) - .then(res => { - if (data[index].interfaces && data[index].interfaces.length > 0) { - data[index].interfaces.push(res); - } - else { - data[index].interfaces = [res]; - } - setData([...data]); - }) - .catch(err => { - console.log("error creating composite profile : ", err); - }) - .finally(() => { setFormOpen(false); }) - } - return ( - <> - <InterfaceDetailsDialog open={openInterfaceDetails} onClose={setOpenInterfaceDetails} item={selectedInterface} type="Interface" /> - <Form open={formOpen} onClose={handleCloseForm} onSubmit={handleSubmit} /> - <DeleteDialog open={openDialog} onClose={handleCloseDialog} title={"Delete Profile"} - content={`Are you sure you want to delete "${data && data[index] ? data[index].metadata.name : ""}"`} /> - <DeleteDialog open={openInterfaceDialog} onClose={handleCloseInterfaceDialog} title={"Delete Interface"} - content={`Are you sure you want to delete "${selectedInterface.metadata ? selectedInterface.metadata.name : ""}"`} /> - <TableContainer component={Paper}> - <Table> - <TableHead> - <TableRow> - <StyledTableCell>Name</StyledTableCell> - <StyledTableCell>Description</StyledTableCell> - <StyledTableCell>App</StyledTableCell> - <StyledTableCell>Workload Resource</StyledTableCell> - <StyledTableCell style={{ width: "27%" }}>Interfaces</StyledTableCell> - <StyledTableCell>Actions</StyledTableCell> - </TableRow> - </TableHead> - <TableBody> - {data.map((entry, index) => - <StyledTableRow key={entry.metadata.name + index}> - <StyledTableCell> - {entry.metadata.name} - </StyledTableCell> - <StyledTableCell > - {entry.metadata.description} - </StyledTableCell> - <StyledTableCell > - {entry.spec["application-name"]} - </StyledTableCell> - <StyledTableCell> - {entry.spec["workload-resource"]} - </StyledTableCell> - <StyledTableCell> - {entry.interfaces && (entry.interfaces.length > 0) && entry.interfaces.map((interfaceEntry, interfacekIndex) => - (<Chip - key={interfaceEntry.metadata.name + "" + interfacekIndex} - size="small" - icon={<InfoOutlinedIcon onClick={() => { handleInterfaceDetailOpen(interfaceEntry) }} style={{ cursor: "pointer" }} />} - onDelete={(e) => { handleDeleteInterface(index, interfaceEntry) }} - label={interfaceEntry.spec.ipAddress} - style={{ marginRight: "10px", marginBottom: "5px" }} - />) - )} - <IconButton color="primary" onClick={() => { handleAddInterface(index) }}> - <AddIconOutline /> - </IconButton> - </StyledTableCell> - <StyledTableCell > - <IconButton onClick={(e) => handleEdit(index)} title="Edit" > - <EditIcon color="primary" /> - </IconButton> - <IconButton onClick={(e) => handleDelete(index)} title="Delete" > - <DeleteIcon color="secondary" /> - </IconButton> - </StyledTableCell> - </StyledTableRow> - )} - </TableBody> - </Table> - </TableContainer></> - ); + setData([...data]); + }) + .catch((err) => { + console.log("error creating composite profile : ", err); + }) + .finally(() => { + setFormOpen(false); + }); + }; + return ( + <> + <InterfaceDetailsDialog + open={openInterfaceDetails} + onClose={setOpenInterfaceDetails} + item={selectedInterface} + type="Interface" + /> + <Form open={formOpen} onClose={handleCloseForm} onSubmit={handleSubmit} /> + <DeleteDialog + open={openDialog} + onClose={handleCloseDialog} + title={"Delete Profile"} + content={`Are you sure you want to delete "${ + data && data[index] ? data[index].metadata.name : "" + }"`} + /> + <DeleteDialog + open={openInterfaceDialog} + onClose={handleCloseInterfaceDialog} + title={"Delete Interface"} + content={`Are you sure you want to delete "${ + selectedInterface.metadata ? selectedInterface.metadata.name : "" + }"`} + /> + <TableContainer component={Paper}> + <Table> + <TableHead> + <TableRow> + <StyledTableCell>Name</StyledTableCell> + <StyledTableCell>Description</StyledTableCell> + <StyledTableCell>App</StyledTableCell> + <StyledTableCell>Workload Resource</StyledTableCell> + <StyledTableCell style={{ width: "27%" }}> + Interfaces + </StyledTableCell> + <StyledTableCell>Actions</StyledTableCell> + </TableRow> + </TableHead> + <TableBody> + {data.map((entry, index) => ( + <StyledTableRow key={entry.metadata.name + index}> + <StyledTableCell>{entry.metadata.name}</StyledTableCell> + <StyledTableCell>{entry.metadata.description}</StyledTableCell> + <StyledTableCell> + {entry.spec["application-name"]} + </StyledTableCell> + <StyledTableCell> + {entry.spec["workload-resource"]} + </StyledTableCell> + <StyledTableCell> + {entry.interfaces && + entry.interfaces.length > 0 && + entry.interfaces.map((interfaceEntry, interfacekIndex) => ( + <Chip + key={ + interfaceEntry.metadata.name + "" + interfacekIndex + } + size="small" + icon={ + <InfoOutlinedIcon + onClick={() => { + handleInterfaceDetailOpen(interfaceEntry); + }} + style={{ cursor: "pointer" }} + /> + } + onDelete={(e) => { + handleDeleteInterface(index, interfaceEntry); + }} + label={interfaceEntry.spec.ipAddress} + style={{ marginRight: "10px", marginBottom: "5px" }} + /> + ))} + <IconButton + color="primary" + onClick={() => { + handleAddInterface(index); + }} + > + <AddIconOutline /> + </IconButton> + </StyledTableCell> + <StyledTableCell> + {/* + //edit workload intent api has not been added yet + <IconButton onClick={(e) => handleEdit(index)} title="Edit" > + <EditIcon color="primary" /> + </IconButton> */} + <IconButton + color="secondary" + disabled={entry.interfaces && entry.interfaces.length > 0} + onClick={(e) => handleDelete(index)} + title="Delete" + > + <DeleteIcon /> + </IconButton> + </StyledTableCell> + </StyledTableRow> + ))} + </TableBody> + </Table> + </TableContainer> + </> + ); }; WokloadIntentTable.propTypes = { - data: PropTypes.arrayOf(PropTypes.object).isRequired, - setData: PropTypes.func.isRequired + data: PropTypes.arrayOf(PropTypes.object).isRequired, + setData: PropTypes.func.isRequired, }; export default WokloadIntentTable; diff --git a/src/tools/emcoui/src/services/apiService.js b/src/tools/emcoui/src/services/apiService.js index 4bff9305..0c830768 100644 --- a/src/tools/emcoui/src/services/apiService.js +++ b/src/tools/emcoui/src/services/apiService.js @@ -11,10 +11,9 @@ // 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. -// ======================================================================== +// ======================================================================== import axios from "axios"; axios.defaults.baseURL = process.env.REACT_APP_BACKEND || ""; - //orchestrator //projects const createProject = (request) => { @@ -45,6 +44,14 @@ const getCompositeApps = (request) => { return res.data; }); }; +const addService = ({ projectName, ...request }) => { + return axios + .post(`/middleend/projects/${projectName}/composite-apps`, request.payload) + .then((res) => { + return res.data; + }); +}; + const createCompositeApp = ({ projectName, ...request }) => { return axios .post(`/v2/projects/${projectName}/composite-apps`, request.payload) @@ -65,7 +72,7 @@ const updateCompositeApp = (request) => { const deleteCompositeApp = (request) => { return axios .delete( - `/v2/projects/${request.projectName}/composite-apps/${request.compositeAppName}/${request.compositeAppVersion}` + `/middleend/projects/${request.projectName}/composite-apps/${request.compositeAppName}/${request.compositeAppVersion}` ) .then((res) => { return res.data; @@ -302,15 +309,10 @@ const deleteInterface = (request) => { }; //deployment intent group -const createDeploymentIntentGroup = ({ - projectName, - compositeAppName, - compositeAppVersion, - ...request -}) => { +const createDeploymentIntentGroup = (request) => { return axios .post( - `/v2/projects/${projectName}/composite-apps/${compositeAppName}/${compositeAppVersion}/deployment-intent-groups`, + `/middleend/projects/${request.spec.projectName}/composite-apps/${request.compositeApp}/${request.compositeAppVersion}/deployment-intent-groups`, { ...request } ) .then((res) => { @@ -329,9 +331,7 @@ const addIntentsToDeploymentIntentGroup = (request) => { }; const getDeploymentIntentGroups = (request) => { return axios - .get( - `/v2/projects/${request.projectName}/composite-apps/${request.compositeAppName}/${request.compositeAppVersion}/deployment-intent-groups` - ) + .get(`/middleend/projects/${request.projectName}/deployment-intent-groups`) .then((res) => { return res.data; }); @@ -349,7 +349,7 @@ const editDeploymentIntentGroup = (request) => { const deleteDeploymentIntentGroup = (request) => { return axios .delete( - `/v2/projects/${request.projectName}/composite-apps/${request.compositeAppName}/${request.compositeAppVersion}/deployment-intent-groups/${request.deploymentIntentGroupName}` + `/middleend/projects/${request.projectName}/composite-apps/${request.compositeAppName}/${request.compositeAppVersion}/deployment-intent-groups/${request.deploymentIntentGroupName}` ) .then((res) => { return res.data; @@ -421,7 +421,7 @@ const updateClusterProvider = (request) => { const addCluster = (request) => { return axios .post( - `/v2/cluster-providers/${request.get("providerName")}/clusters`, + `/middleend/clusterproviders/${request.get("providerName")}/clusters`, request ) .then((res) => { @@ -577,6 +577,7 @@ const vimService = { getCompositeApps, getProfiles, createCompositeApp, + addService, updateCompositeApp, deleteCompositeApp, getApps, diff --git a/src/tools/emcoui/startup.sh b/src/tools/emcoui/startup.sh index b1d42ffc..8e2b90e0 100755 --- a/src/tools/emcoui/startup.sh +++ b/src/tools/emcoui/startup.sh @@ -1,2 +1,17 @@ +#======================================================================= +# Copyright (c) 2017-2020 Aarna Networks, Inc. +# All rights reserved. +# ====================================================================== +# 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. +# ======================================================================== + # startup script for development. Give backend address if backend server is not running locally -REACT_APP_BACKEND= npm start +REACT_APP_BACKEND=http://emco npm start |