Merge "This adds a plugin for OpenStack to the Airship UI"
This commit is contained in:
commit
618cccf633
@ -68,6 +68,14 @@ Airship UI utilizes the Octant plugin to drive some of the functionality. Octan
|
||||
|
||||
var pluginName = "hello-world"
|
||||
|
||||
// HelloWorldPlugin is a required struct to be an octant compliant plugin
|
||||
type HelloWorldPlugin struct{}
|
||||
|
||||
// return a new hello world struct
|
||||
func newHelloWorldPlugin() *HelloWorldPlugin {
|
||||
return &HelloWorldPlugin{}
|
||||
}
|
||||
|
||||
// This is a sample plugin showing the features of Octant's plugin API.
|
||||
func main() {
|
||||
// Remove the prefix from the go logger since Octant will print logs with timestamps.
|
||||
@ -78,9 +86,11 @@ Airship UI utilizes the Octant plugin to drive some of the functionality. Octan
|
||||
IsModule: true,
|
||||
}
|
||||
|
||||
hwp := newHelloWorldPlugin()
|
||||
|
||||
// Set up what should happen when Octant calls this plugin.
|
||||
options := []service.PluginOption{
|
||||
service.WithNavigation(handleNavigation, initRoutes),
|
||||
service.WithNavigation(hwp.handleNavigation, hwp.initRoutes),
|
||||
}
|
||||
|
||||
// Use the plugin service helper to register this plugin.
|
||||
@ -94,8 +104,8 @@ Airship UI utilizes the Octant plugin to drive some of the functionality. Octan
|
||||
p.Serve()
|
||||
}
|
||||
|
||||
// handleNavigation controls what is displayed on the navigation pane
|
||||
func handleNavigation(request *service.NavigationRequest) (navigation.Navigation, error) {
|
||||
// handles the navigation pane interation
|
||||
func (hwp *HelloWorldPlugin) handleNavigation(request *service.NavigationRequest) (navigation.Navigation, error) {
|
||||
return navigation.Navigation{
|
||||
Title: "Hello World Plugin",
|
||||
Path: request.GeneratePath(),
|
||||
@ -105,15 +115,15 @@ Airship UI utilizes the Octant plugin to drive some of the functionality. Octan
|
||||
|
||||
// initRoutes routes for this plugin. In this example, there is a global catch all route
|
||||
// that will return the content for every single path.
|
||||
func initRoutes(router *service.Router) {
|
||||
router.HandleFunc("*", func(request *service.Request) (component.ContentResponse, error) {
|
||||
contentResponse := component.NewContentResponse(component.TitleFromString("Hello World Title"))
|
||||
helloWorld := component.NewText("Hello World just some text on the page")
|
||||
contentResponse.Add(helloWorld)
|
||||
// NOTE: this will print out messages only if the -v is added to the start of octent or airship
|
||||
log.Printf("Sample debugging message: %v\n", helloWorld)
|
||||
return *contentResponse, nil
|
||||
})
|
||||
func (hwp *HelloWorldPlugin) initRoutes(router *service.Router) {
|
||||
router.HandleFunc("*", hwp.routeHandler)
|
||||
}
|
||||
|
||||
func (hwp *HelloWorldPlugin) routeHandler(request service.Request) (component.ContentResponse, error) {
|
||||
contentResponse := component.NewContentResponse(component.TitleFromString("Hello World Title"))
|
||||
helloWorld := component.NewText("Hello World just some text on the page")
|
||||
contentResponse.Add(helloWorld)
|
||||
return *contentResponse, nil
|
||||
}
|
||||
```
|
||||
5. Build the hello-world plugin
|
||||
|
@ -36,6 +36,10 @@ airshipui is an executable that wraps Octant. When it is launched, it processes
|
||||
necessary custom startup tasks such as reading the airshipctl config file, then normally calls the function to instantiate Octant.
|
||||
This repository also contains airship plugins that will be generated as standard octant plugins, which are separate binaries.
|
||||
|
||||
## Plugins
|
||||
|
||||
[OpenStack Plugin documentation](./cmd/openstack/README.md)
|
||||
|
||||
## Developer's Guide
|
||||
|
||||
Step by step sample installation and more details can be found in the [Developer's Guide](DevelopersGuide.md).
|
@ -14,14 +14,11 @@ func main() {
|
||||
// Remove the prefix from the go logger since Octant will print logs with timestamps.
|
||||
log.SetPrefix("")
|
||||
|
||||
description := fmt.Sprintf("Argo UI version %s", environment.Version())
|
||||
description := fmt.Sprintf("%s UI version %s", pluginName, environment.Version())
|
||||
|
||||
// Use the plugin service helper to register this plugin.
|
||||
p, err := plugin.Register(pluginName, description)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
plugin.Register(pluginName, description)
|
||||
|
||||
// The plugin can log and the log messages will show up in Octant.
|
||||
log.Printf("%s is starting", pluginName)
|
||||
p.Serve()
|
||||
log.Printf("%s plugin is starting", pluginName)
|
||||
}
|
||||
|
53
cmd/openstack/README.md
Executable file
53
cmd/openstack/README.md
Executable file
@ -0,0 +1,53 @@
|
||||
# AirshipUI OpenStack Plugin Documentation
|
||||
|
||||
## OpenStack Plugin Requirements
|
||||
In order to make a connection to the OpenStack APIs the system needs enough information to attach to it. There are 2 ways to accomplish this.
|
||||
|
||||
### File Based OpenStack Connection Details
|
||||
|
||||
Create an openstack.json file in the correct location for your system.
|
||||
|
||||
Windows:
|
||||
```
|
||||
C:\Users\<USER_ID>\AppData\Local\octant\etc\openstack.json
|
||||
```
|
||||
|
||||
Linux:
|
||||
```
|
||||
~/.config/octant/etc/openstack.json
|
||||
```
|
||||
|
||||
The file contents should look like this:
|
||||
```
|
||||
{
|
||||
"identityEndpoint": "http://<OPENSTACK_HOST>/identity/v3",
|
||||
"username": "<USERNAME>",
|
||||
"password": "<PASSWORD>",
|
||||
"tenantName": "<TENANT hint demo is the default>",
|
||||
"domainID": "<DOMAINID hint default is the default>"
|
||||
}
|
||||
```
|
||||
|
||||
### Environment Based OpenStack Connection Details
|
||||
|
||||
The following environment variables are required to be set in the same shell that is executing the Octant or AirshipUI binary:
|
||||
```
|
||||
OS_USER_DOMAIN_ID
|
||||
OS_AUTH_URL
|
||||
OS_PROJECT_DOMAIN_ID
|
||||
OS_REGION_NAME
|
||||
OS_PROJECT_NAME
|
||||
OS_IDENTITY_API_VERSION
|
||||
OS_TENANT_NAME
|
||||
OS_TENANT_ID
|
||||
OS_AUTH_TYPE
|
||||
OS_PASSWORD
|
||||
OS_USERNAME
|
||||
OS_VOLUME_API_VERSION
|
||||
OS_TOKEN
|
||||
OS_USERID
|
||||
```
|
||||
|
||||
This plugin should now have enough information to start and display data from your OpenStack instance
|
||||
|
||||
The next time you run Airship UI the OpenStack plugin will be available at http://127.0.0.1:7777/#/openstack
|
24
cmd/openstack/main.go
Executable file
24
cmd/openstack/main.go
Executable file
@ -0,0 +1,24 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"opendev.org/airship/airshipui/internal/environment"
|
||||
plugin "opendev.org/airship/airshipui/internal/plugin/openstack"
|
||||
)
|
||||
|
||||
var pluginName = "openstack"
|
||||
|
||||
func main() {
|
||||
// Remove the prefix from the go logger since Octant will print logs with timestamps.
|
||||
log.SetPrefix("")
|
||||
|
||||
description := fmt.Sprintf("%s version %s", pluginName, environment.Version())
|
||||
|
||||
// Use the plugin service helper to register this plugin.
|
||||
plugin.Register(pluginName, description)
|
||||
|
||||
// The plugin can log and the log messages will show up in Octant.
|
||||
log.Printf("%s is starting", pluginName)
|
||||
}
|
7
go.mod
7
go.mod
@ -4,12 +4,15 @@ go 1.12
|
||||
|
||||
require (
|
||||
github.com/go-cmd/cmd v1.2.0
|
||||
github.com/golang/mock v1.4.3
|
||||
github.com/gophercloud/gophercloud v0.9.0
|
||||
github.com/spf13/cobra v0.0.6
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/vmware-tanzu/octant v0.10.2-0.20200320182255-15a53b6af867
|
||||
github.com/stretchr/testify v1.5.1
|
||||
github.com/vmware-tanzu/octant v0.11.0
|
||||
k8s.io/api v0.17.3
|
||||
k8s.io/apimachinery v0.17.3
|
||||
opendev.org/airship/airshipctl v0.0.0-20200319213630-b2a602fa07e0
|
||||
opendev.org/airship/airshipctl v0.0.0-20200326153008-ffacc190e95c
|
||||
)
|
||||
|
||||
replace k8s.io/client-go => k8s.io/client-go v0.0.0-20191114101535-6c5935290e33
|
||||
|
57
go.sum
57
go.sum
@ -47,6 +47,7 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/apache/thrift v0.12.0 h1:pODnxUFNcjP9UTLZGTdeh+j16A8lJbRvD3rOtrk/7bs=
|
||||
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
@ -94,6 +95,7 @@ github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5m
|
||||
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
|
||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||
github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
||||
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
||||
github.com/elazarl/goproxy v0.0.0-20190421051319-9d40249d3c2f/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
||||
github.com/elazarl/goproxy v0.0.0-20190703090003-6125c262ffb0 h1:ZMEV8o5EYDSweKafp0aPe65/raLEZ7CF9ab9UDMaIMk=
|
||||
github.com/elazarl/goproxy v0.0.0-20190703090003-6125c262ffb0/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
||||
@ -152,6 +154,8 @@ github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a
|
||||
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/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
|
||||
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
@ -162,6 +166,8 @@ github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/mock v1.4.3 h1:GV+pQPG/EUUbkh47niozDcADz6go/dUwhVzdUQHIVRw=
|
||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
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 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
|
||||
@ -181,6 +187,8 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
|
||||
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/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/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
||||
@ -188,12 +196,15 @@ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gnostic v0.0.0-20170426233943-68f4ded48ba9/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
|
||||
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
|
||||
github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
|
||||
github.com/googleapis/gnostic v0.2.0 h1:l6N3VoaVzTncYYW+9yOz2LJJammFZGBO13sqgEhpy9g=
|
||||
github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
|
||||
github.com/googleapis/gnostic v0.3.0 h1:CcQijm0XKekKjP/YCz28LXVSpgguuB+nCxaSjCe09y0=
|
||||
github.com/googleapis/gnostic v0.3.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
|
||||
github.com/gophercloud/gophercloud v0.1.0 h1:P/nh25+rzXouhytV2pUHBb65fnds26Ghl8/391+sT5o=
|
||||
github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
|
||||
github.com/gophercloud/gophercloud v0.9.0 h1:eJHQQFguQRv2FatH2d2VXH2ueTe2XzjgjwFjFS7SGcs=
|
||||
github.com/gophercloud/gophercloud v0.9.0/go.mod h1:gmC5oQqMDOMO1t1gq5DquX/yAU808e/4mzjjDA76+Ss=
|
||||
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
|
||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/handlers v1.4.0 h1:XulKRWSQK5uChr4pEgSE4Tc/OcmnU9GJuSwdog/tZsA=
|
||||
@ -306,6 +317,7 @@ github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w=
|
||||
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/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.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo=
|
||||
@ -388,6 +400,7 @@ github.com/src-d/gcfg v1.4.0 h1:xXbNR5AlLSA315x2UO+fTSSAXCDf+Ar38/6oyGbDKQ4=
|
||||
github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI=
|
||||
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 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
|
||||
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=
|
||||
@ -395,13 +408,24 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0
|
||||
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/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||
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-tanzu/octant v0.10.2-0.20200302175445-a52392c6f48e h1:nayXBqq/FKj9MF3+QbKwfvuInEhuGS3XDUJMRPBgBXo=
|
||||
github.com/vmware-tanzu/octant v0.10.2-0.20200302175445-a52392c6f48e/go.mod h1:ree4Qu6ULumg1MlGrAG7avEQx3+NjWPc0Zs4nQWOmQE=
|
||||
github.com/vmware-tanzu/octant v0.10.2-0.20200320182255-15a53b6af867 h1:KdLbukBYm0E0yuo6TQ2m5v/zHLlLoxaTF2crGCxqB4U=
|
||||
github.com/vmware-tanzu/octant v0.10.2-0.20200320182255-15a53b6af867/go.mod h1:ree4Qu6ULumg1MlGrAG7avEQx3+NjWPc0Zs4nQWOmQE=
|
||||
github.com/vmware-tanzu/octant v0.10.2 h1:sqXei1wyxhajUt90HGHPEyRTv12T8Mwv6vyKKdLUYg4=
|
||||
github.com/vmware-tanzu/octant v0.10.2/go.mod h1:ree4Qu6ULumg1MlGrAG7avEQx3+NjWPc0Zs4nQWOmQE=
|
||||
github.com/vmware-tanzu/octant v0.11.0 h1:1ganDAIiswCL2QDu/C0pczWoraSMIWMtq79GOCqnrr8=
|
||||
github.com/vmware-tanzu/octant v0.11.0/go.mod h1:ree4Qu6ULumg1MlGrAG7avEQx3+NjWPc0Zs4nQWOmQE=
|
||||
github.com/vmware/govmomi v0.20.1/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU=
|
||||
github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70=
|
||||
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
@ -455,6 +479,7 @@ golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLL
|
||||
golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc h1:gkKoSkUmnU6bpS/VhkuO27bzQeSA51uaEfbOW5dNb68=
|
||||
golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191204025024-5ee1b9f4859a h1:+HHJiFUXVOIS9mr1ThqkQD1N8vpFCfCShqADBM12KTc=
|
||||
golang.org/x/net v0.0.0-20191204025024-5ee1b9f4859a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
@ -468,6 +493,8 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58 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-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@ -490,9 +517,12 @@ golang.org/x/sys v0.0.0-20190621203818-d432491b9138 h1:t8BZD9RDjkm9/h7yYN6kE8oae
|
||||
golang.org/x/sys v0.0.0-20190621203818-d432491b9138/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e h1:9vRrk9YW2BTzLP0VCB9ZDjU4cPqkg+IDWL7XgxA1yxQ=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/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-20170915032832-14c0d48ead0c/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=
|
||||
@ -516,6 +546,9 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn
|
||||
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-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
|
||||
golang.org/x/tools v0.0.0-20191203134012-c197fd4bf371/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
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=
|
||||
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=
|
||||
@ -544,6 +577,7 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks
|
||||
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/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
@ -565,6 +599,7 @@ 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.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.7/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.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
||||
@ -579,6 +614,8 @@ k8s.io/api v0.0.0-20191114100352-16d7abae0d2a h1:86XISgFlG7lPOWj6wYLxd+xqhhVt/WQ
|
||||
k8s.io/api v0.0.0-20191114100352-16d7abae0d2a/go.mod h1:qetVJgs5i8jwdFIdoOZ70ks0ecgU+dYwqZ2uD1srwOU=
|
||||
k8s.io/api v0.17.3 h1:XAm3PZp3wnEdzekNkcmj/9Y1zdmQYJ1I4GKSBBZ8aG0=
|
||||
k8s.io/api v0.17.3/go.mod h1:YZ0OTkuw7ipbe305fMpIdf3GLXZKRigjtZaV5gzC2J0=
|
||||
k8s.io/api v0.18.0 h1:lwYk8Vt7rsVTwjRU6pzEsa9YNhThbmbocQlKvNBB4EQ=
|
||||
k8s.io/api v0.18.0/go.mod h1:q2HRQkfDzHMBZL9l/y9rH63PkQl4vae0xRT+8prbrK8=
|
||||
k8s.io/apiextensions-apiserver v0.0.0-20181213153335-0fe22c71c476 h1:Ws9zfxsgV19Durts9ftyTG7TO0A/QLhmu98VqNWLiH8=
|
||||
k8s.io/apiextensions-apiserver v0.0.0-20181213153335-0fe22c71c476/go.mod h1:IxkesAMoaCRoLrPJdZNZUQp9NfZnzqaVzLhb2VEQzXE=
|
||||
k8s.io/apimachinery v0.0.0-20190313205120-d7deff9243b1/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0=
|
||||
@ -588,6 +625,8 @@ k8s.io/apimachinery v0.0.0-20191028221656-72ed19daf4bb h1:ZUNsbuPdXWrj0rZziRfCWc
|
||||
k8s.io/apimachinery v0.0.0-20191028221656-72ed19daf4bb/go.mod h1:llRdnznGEAqC3DcNm6yEj472xaFVfLM7hnYofMb12tQ=
|
||||
k8s.io/apimachinery v0.17.3 h1:f+uZV6rm4/tHE7xXgLyToprg6xWairaClGVkm2t8omg=
|
||||
k8s.io/apimachinery v0.17.3/go.mod h1:gxLnyZcGNdZTCLnq3fgzyg2A5BVCHTNDFrw8AmuJ+0g=
|
||||
k8s.io/apimachinery v0.18.0 h1:fuPfYpk3cs1Okp/515pAf0dNhL66+8zk8RLbSX+EgAE=
|
||||
k8s.io/apimachinery v0.18.0/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA=
|
||||
k8s.io/cli-runtime v0.0.0-20191114110141-0a35778df828 h1:g5LpwfPTlzVksjrqp3EuSFg2ihl9LZVaRhnMas5sB/s=
|
||||
k8s.io/cli-runtime v0.0.0-20191114110141-0a35778df828/go.mod h1:r9ARs2FUnSgInbeN4+mo9nFzf7oqUtRZ3tcuEcoelR4=
|
||||
k8s.io/client-go v0.0.0-20191114101535-6c5935290e33 h1:07mhG/2oEoo3N+sHVOo0L9PJ/qvbk3N5n2dj8IWefnQ=
|
||||
@ -608,6 +647,8 @@ k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf h1:EYm5AW/UUDbnmnI+gK0TJD
|
||||
k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
|
||||
k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a h1:UcxjrRMyNx/i/y8G7kPvLyy7rfbeuf1PYyBf973pgyU=
|
||||
k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
|
||||
k8s.io/kube-openapi v0.0.0-20200121204235-bf4fb3bd569c h1:/KUFqjjqAcY4Us6luF5RDNZ16KJtb49HfR3ZHB9qYXM=
|
||||
k8s.io/kube-openapi v0.0.0-20200121204235-bf4fb3bd569c/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E=
|
||||
k8s.io/kubectl v0.0.0-20191114113550-6123e1c827f7 h1:J4a49dHJowmwpCnPsIlbUlh1XE+Elq4P++/uSbKPtR0=
|
||||
k8s.io/kubectl v0.0.0-20191114113550-6123e1c827f7/go.mod h1:MYrMrU6JgEeGVz2sFggJizAfyoRjwsP4iTQcP8iQS00=
|
||||
k8s.io/kubernetes v1.14.0 h1:6T2iAEoOYQnzQb3WvPlUkcczEEXZ7+YPlAO8olwujRw=
|
||||
@ -625,14 +666,30 @@ modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs
|
||||
modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I=
|
||||
opendev.org/airship/airshipctl v0.0.0-20200319213630-b2a602fa07e0 h1:PPW+Zyjw96vwdnrKUnlpOjtajihO/1unrOe8C5bH1ok=
|
||||
opendev.org/airship/airshipctl v0.0.0-20200319213630-b2a602fa07e0/go.mod h1:2Ox64Dscbq8HGJjP9s3Mvpfgcn+lRxBKP7n1vD/RnwE=
|
||||
opendev.org/airship/airshipctl v0.0.0-20200326153008-ffacc190e95c h1:mZYOp/L+7WWlxr6Epq1v7Hi5t6RKm/gt15pEj2R2bPA=
|
||||
opendev.org/airship/airshipctl v0.0.0-20200326153008-ffacc190e95c/go.mod h1:ILbbxTS02MO4yh7C5iMtz0KzxYFYwSRgiyPhYQ4tWsY=
|
||||
opendev.org/airship/go-redfish v0.0.0-20200110185254-3ab47e28bae8 h1:wIvgHcZH/l7H/eTIjZXGfqurtarlaAv2CtnO/8xYCis=
|
||||
opendev.org/airship/go-redfish v0.0.0-20200110185254-3ab47e28bae8/go.mod h1:FEjYcb3bYBWGpQIqtvVM0NrT5eyjlCOCj5JNf4lI+6s=
|
||||
opendev.org/airship/go-redfish v0.0.0-20200318103738-db034d1d753a h1:4ggAMTwpfu/w3ZXOIJ9tfYF37JIYn+eNCA4O10NduZ0=
|
||||
opendev.org/airship/go-redfish v0.0.0-20200318103738-db034d1d753a/go.mod h1:FEjYcb3bYBWGpQIqtvVM0NrT5eyjlCOCj5JNf4lI+6s=
|
||||
opendev.org/airship/go-redfish/client v0.0.0-20200110185254-3ab47e28bae8 h1:ZDtcVfkn6EzA6Kr5I7C3EuN8qsuByeMPII9TnTx1zXQ=
|
||||
opendev.org/airship/go-redfish/client v0.0.0-20200110185254-3ab47e28bae8/go.mod h1:LPq6GmtxEl3Pwf0Ora40EOm46Hz425T9kjsOfG5HKmQ=
|
||||
opendev.org/airship/go-redfish/client v0.0.0-20200318103738-db034d1d753a h1:S1dmsP5Cc6OQjAd6OgIKMcNPBiGjh5TDbijVjNE/VGU=
|
||||
opendev.org/airship/go-redfish/client v0.0.0-20200318103738-db034d1d753a/go.mod h1:s0hwuUpBsRXOrhN0NR+fNVivXGyWgHKpqtyq7qYjpew=
|
||||
rsc.io/quote/v3 v3.1.0 h1:9JKUTTIUgS6kzR9mK1YuGKv6Nl+DijDNIc0ghT58FaY=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
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/kustomize/v3 v3.2.0 h1:EKcEubO29vCbigcMoNynfyZH+ANWkML2UHWibt1Do7o=
|
||||
sigs.k8s.io/kustomize/v3 v3.2.0/go.mod h1:ztX4zYc/QIww3gSripwF7TBOarBTm5BvyAMem0kCzOE=
|
||||
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e h1:4Z09Hglb792X0kfOBBJUPFEyvVfQWrYT/l8h5EKA6JQ=
|
||||
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
|
||||
sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=
|
||||
sigs.k8s.io/structured-merge-diff/v3 v3.0.0 h1:dOmIZBMfhcHS09XZkMyUgkq5trg3/jRyJYFZUiaOp8E=
|
||||
sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=
|
||||
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
|
||||
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=
|
||||
|
@ -19,15 +19,25 @@ import (
|
||||
v1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
// ArgoPlugin is an example of a plugin that registers navigation and action handlers
|
||||
type ArgoPlugin struct{}
|
||||
|
||||
// NewArgoPlugin returns the basic argo plugin struct as required by octant
|
||||
func NewArgoPlugin() *ArgoPlugin {
|
||||
return &ArgoPlugin{}
|
||||
}
|
||||
|
||||
// Register registers the plugin with Octant
|
||||
func Register(name string, description string) (*service.Plugin, error) {
|
||||
a := NewArgoPlugin()
|
||||
|
||||
capabilities := &plugin.Capabilities{
|
||||
IsModule: true,
|
||||
}
|
||||
|
||||
// Set up what should happen when Octant calls this plugin.
|
||||
options := []service.PluginOption{
|
||||
service.WithNavigation(handleNavigation, initRoutes),
|
||||
service.WithNavigation(a.handleNavigation, a.initRoutes),
|
||||
}
|
||||
|
||||
// Use the plugin service helper to register this plugin.
|
||||
@ -38,7 +48,7 @@ func Register(name string, description string) (*service.Plugin, error) {
|
||||
// be called frequently from Octant. Navigation is a tree of `Navigation` structs.
|
||||
// The plugin can use whatever paths it likes since these paths can be namespaced to the
|
||||
// the plugin.
|
||||
func handleNavigation(request *service.NavigationRequest) (navigation.Navigation, error) {
|
||||
func (a *ArgoPlugin) handleNavigation(request *service.NavigationRequest) (navigation.Navigation, error) {
|
||||
return navigation.Navigation{
|
||||
Title: "Argo UI",
|
||||
Path: request.GeneratePath(),
|
||||
@ -48,7 +58,7 @@ func handleNavigation(request *service.NavigationRequest) (navigation.Navigation
|
||||
|
||||
// initRoutes routes for this plugin. In this example, there is a global catch all route
|
||||
// that will return the content for every single path.
|
||||
func initRoutes(router *service.Router) {
|
||||
func (a *ArgoPlugin) initRoutes(router *service.Router) {
|
||||
router.HandleFunc("", func(request service.Request) (component.ContentResponse, error) {
|
||||
response := component.NewContentResponse(component.TitleFromString("Argo UI"))
|
||||
|
||||
|
@ -1,19 +1,39 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/vmware-tanzu/octant/pkg/plugin/service"
|
||||
"github.com/vmware-tanzu/octant/pkg/plugin/service/fake"
|
||||
)
|
||||
|
||||
type fakeRequest struct {
|
||||
ctrl *gomock.Controller
|
||||
path, title string
|
||||
}
|
||||
|
||||
func (f *fakeRequest) DashboardClient() service.Dashboard {
|
||||
return fake.NewMockDashboard(f.ctrl)
|
||||
}
|
||||
|
||||
func (f *fakeRequest) Path() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (f *fakeRequest) Context() context.Context {
|
||||
return context.TODO()
|
||||
}
|
||||
|
||||
func TestRegister(t *testing.T) {
|
||||
p, err := Register("argo-ui", "Argo UI test version")
|
||||
plugin, err := Register("argo-ui", "Argo UI test version")
|
||||
if err != nil {
|
||||
t.Fatalf("Registering the plugin returned an error")
|
||||
}
|
||||
|
||||
err = p.Validate()
|
||||
err = plugin.Validate()
|
||||
if err != nil {
|
||||
t.Fatalf("Validating the plugin returned an error")
|
||||
}
|
||||
@ -21,8 +41,7 @@ func TestRegister(t *testing.T) {
|
||||
|
||||
func TestRoutes(t *testing.T) {
|
||||
router := service.NewRouter()
|
||||
|
||||
initRoutes(router)
|
||||
NewArgoPlugin().initRoutes(router)
|
||||
|
||||
tests := []struct {
|
||||
path string
|
||||
|
164
internal/plugin/openstack/compute.go
Executable file
164
internal/plugin/openstack/compute.go
Executable file
@ -0,0 +1,164 @@
|
||||
/*
|
||||
Copyright (c) 2020 AT&T. All Rights Reserved.
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/openstack"
|
||||
"github.com/gophercloud/gophercloud/openstack/compute/v2/flavors"
|
||||
"github.com/gophercloud/gophercloud/openstack/compute/v2/images"
|
||||
"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"
|
||||
"github.com/gophercloud/gophercloud/pagination"
|
||||
"github.com/vmware-tanzu/octant/pkg/view/component"
|
||||
)
|
||||
|
||||
// gets OpenStack flavors that are available for the tenant
|
||||
// https://docs.openstack.org/nova/latest/user/flavors.html
|
||||
func getFlavors(osp *OpenstackPlugin) component.Component {
|
||||
rows := []component.TableRow{}
|
||||
|
||||
// TODO: Determine if the error needs to be handled from this function
|
||||
flavors.ListDetail(computeClientHelper(osp), flavors.ListOpts{AccessType: flavors.AllAccess}).EachPage(
|
||||
func(page pagination.Page) (bool, error) {
|
||||
flavorList, err := flavors.ExtractFlavors(page)
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("compute flavor Error: %s\n", err)
|
||||
}
|
||||
|
||||
for _, flavor := range flavorList {
|
||||
name := flavor.Name
|
||||
flavorCache[flavor.ID] = name
|
||||
|
||||
rows = append(rows, component.TableRow{
|
||||
"Name": component.NewText(name),
|
||||
"VCPUs": component.NewText(strconv.Itoa(flavor.VCPUs)),
|
||||
"RAM": component.NewText(strconv.Itoa(flavor.RAM)),
|
||||
"Root Disk": component.NewText(strconv.Itoa(flavor.Disk)),
|
||||
"Ephemeral Disk": component.NewText(strconv.Itoa(flavor.Ephemeral)),
|
||||
"Swap Disk": component.NewText(strconv.Itoa(flavor.Swap)),
|
||||
"RX/TX factor": component.NewText(strconv.FormatFloat(flavor.RxTxFactor, 'f', 1, 64)),
|
||||
"Public": component.NewText(strconv.FormatBool(flavor.IsPublic)),
|
||||
})
|
||||
}
|
||||
|
||||
return true, nil
|
||||
})
|
||||
|
||||
return component.NewTableWithRows(
|
||||
"Flavors",
|
||||
"No flavors found",
|
||||
component.NewTableCols("Name", "VCPUs", "RAM", "Root Disk", "Ephemeral Disk",
|
||||
"Swap Disk", "RX/TX factor", "Public"),
|
||||
rows)
|
||||
}
|
||||
|
||||
// gets OpenStack images that are available for the tenant
|
||||
// https://docs.openstack.org/image-guide/
|
||||
func getImages(osp *OpenstackPlugin) component.Component {
|
||||
rows := []component.TableRow{}
|
||||
|
||||
// TODO: Determine if the error needs to be handled from this function
|
||||
images.ListDetail(computeClientHelper(osp), images.ListOpts{}).EachPage(
|
||||
func(page pagination.Page) (bool, error) {
|
||||
list, err := images.ExtractImages(page)
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("Image list error: %s\n", err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, image := range list {
|
||||
name := image.Name
|
||||
imageCache[image.ID] = name
|
||||
|
||||
rows = append(rows, component.TableRow{
|
||||
"Name": component.NewText(name),
|
||||
"Type": component.NewText(strconv.Itoa(image.Progress)),
|
||||
"Status": component.NewText(image.Status),
|
||||
})
|
||||
}
|
||||
|
||||
return true, nil
|
||||
})
|
||||
|
||||
return component.NewTableWithRows(
|
||||
"Images",
|
||||
"No images found",
|
||||
component.NewTableCols("Name", "Type", "Status"), rows)
|
||||
}
|
||||
|
||||
// gets OpenStack vms that are configured for the tenant
|
||||
// https://docs.openstack.org/python-openstackclient/latest/cli/command-objects/server.html
|
||||
func getVMs(osp *OpenstackPlugin) component.Component {
|
||||
rows := []component.TableRow{}
|
||||
|
||||
// TODO: Determine if the error needs to be handled from this function
|
||||
servers.List(computeClientHelper(osp), servers.ListOpts{AllTenants: true}).EachPage(
|
||||
func(page pagination.Page) (bool, error) {
|
||||
list, err := servers.ExtractServers(page)
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("Server list error: %s\n", err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, server := range list {
|
||||
name := server.Name
|
||||
vmCache[server.ID] = name
|
||||
|
||||
// pull together the addresses from the structure
|
||||
var addresses string
|
||||
var tmp []string
|
||||
for subnetName := range server.Addresses {
|
||||
for _, v := range server.Addresses[subnetName].([]interface{}) {
|
||||
tmp = append(tmp, v.(map[string]interface{})["addr"].(string))
|
||||
}
|
||||
}
|
||||
|
||||
if len(tmp) > 1 {
|
||||
addresses = strings.Join(tmp, ", ")
|
||||
} else {
|
||||
addresses = tmp[0]
|
||||
}
|
||||
|
||||
rows = append(rows, component.TableRow{
|
||||
"Instance Name": component.NewText(name),
|
||||
"Image Name": component.NewText(imageCache[server.Image["id"].(string)]),
|
||||
"IP Address": component.NewText(addresses),
|
||||
"Flavor": component.NewText(flavorCache[server.Flavor["id"].(string)]),
|
||||
"Key Pair": component.NewText(server.KeyName),
|
||||
"Status": component.NewText(server.Status),
|
||||
"Created": component.NewText(server.Created.Local().String()),
|
||||
})
|
||||
}
|
||||
|
||||
return true, nil
|
||||
})
|
||||
|
||||
return component.NewTableWithRows(
|
||||
"Servers",
|
||||
"No servers found",
|
||||
component.NewTableCols("Instance Name", "Image Name", "IP Address", "Flavor", "Key Pair", "Status", "Created"), rows)
|
||||
}
|
||||
|
||||
// helper function to create a compute specific gophercloud client
|
||||
func computeClientHelper(osp *OpenstackPlugin) *gophercloud.ServiceClient {
|
||||
client, err := openstack.NewComputeV2(osp.provider, gophercloud.EndpointOpts{
|
||||
Type: "compute",
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("Compute Client Error: %s\n", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
return client
|
||||
}
|
138
internal/plugin/openstack/identity.go
Executable file
138
internal/plugin/openstack/identity.go
Executable file
@ -0,0 +1,138 @@
|
||||
/*
|
||||
Copyright (c) 2020 AT&T. All Rights Reserved.
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strconv"
|
||||
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/openstack"
|
||||
"github.com/gophercloud/gophercloud/openstack/identity/v3/domains"
|
||||
"github.com/gophercloud/gophercloud/openstack/identity/v3/projects"
|
||||
"github.com/gophercloud/gophercloud/openstack/identity/v3/users"
|
||||
"github.com/gophercloud/gophercloud/pagination"
|
||||
"github.com/vmware-tanzu/octant/pkg/view/component"
|
||||
)
|
||||
|
||||
// gets OpenStack domains that are viewable by the tenant
|
||||
// https://docs.openstack.org/security-guide/identity/domains.html
|
||||
func getDomains(osp *OpenstackPlugin) component.Component {
|
||||
rows := []component.TableRow{}
|
||||
|
||||
// TODO: Determine if the error needs to be handled from this function
|
||||
domains.List(identityClientHelper(osp), domains.ListOpts{}).EachPage(
|
||||
func(page pagination.Page) (bool, error) {
|
||||
domainList, err := domains.ExtractDomains(page)
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("Broken at user list %v\n", err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, domain := range domainList {
|
||||
name := domain.Name
|
||||
domainCache[domain.ID] = name
|
||||
rows = append(rows, component.TableRow{
|
||||
"Name": component.NewText(name),
|
||||
"Description": component.NewText(domain.Description),
|
||||
"Enabled": component.NewText(strconv.FormatBool(domain.Enabled)),
|
||||
})
|
||||
}
|
||||
|
||||
return true, nil
|
||||
})
|
||||
|
||||
return component.NewTableWithRows(
|
||||
"Domains",
|
||||
"No domains found",
|
||||
component.NewTableCols("Name", "Description", "Enabled"), rows)
|
||||
}
|
||||
|
||||
// gets OpenStack projects that are viewable by the tenant
|
||||
// https://docs.openstack.org/keystone/latest/admin/cli-manage-projects-users-and-roles.html
|
||||
func getProjects(osp *OpenstackPlugin) component.Component {
|
||||
rows := []component.TableRow{}
|
||||
|
||||
// TODO: Determine if the error needs to be handled from this function
|
||||
projects.List(identityClientHelper(osp), projects.ListOpts{}).EachPage(
|
||||
func(page pagination.Page) (bool, error) {
|
||||
projectList, err := projects.ExtractProjects(page)
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("Broken at project list %v\n", err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, project := range projectList {
|
||||
name := project.Name
|
||||
projectCache[project.ID] = name
|
||||
|
||||
rows = append(rows, component.TableRow{
|
||||
"Enabled": component.NewText(strconv.FormatBool(project.Enabled)),
|
||||
"Description": component.NewText(project.Description),
|
||||
"Name": component.NewText(name),
|
||||
})
|
||||
}
|
||||
|
||||
return true, nil
|
||||
})
|
||||
|
||||
return component.NewTableWithRows(
|
||||
"Projects",
|
||||
"No projects found",
|
||||
component.NewTableCols("Name", "Enabled", "Description"), rows)
|
||||
}
|
||||
|
||||
// gets OpenStack uesrs that are viewable by the tenant
|
||||
// https://docs.openstack.org/keystone/latest/user/index.html
|
||||
func getUsers(osp *OpenstackPlugin) component.Component {
|
||||
rows := []component.TableRow{}
|
||||
|
||||
// TODO: Determine if the error needs to be handled from this function
|
||||
users.List(identityClientHelper(osp), users.ListOpts{}).EachPage(
|
||||
func(page pagination.Page) (bool, error) {
|
||||
networkList, err := users.ExtractUsers(page)
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("Broken at user list %v\n", err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, user := range networkList {
|
||||
email := ""
|
||||
if user.Extra["email"] != nil {
|
||||
email = user.Extra["email"].(string)
|
||||
}
|
||||
|
||||
rows = append(rows, component.TableRow{
|
||||
"Name": component.NewText(user.Name),
|
||||
"Email": component.NewText(email),
|
||||
"Description": component.NewText(user.Description),
|
||||
"Enabled": component.NewText(strconv.FormatBool(user.Enabled)),
|
||||
"Domain Name": component.NewText(domainCache[user.DomainID]),
|
||||
})
|
||||
}
|
||||
|
||||
return true, nil
|
||||
})
|
||||
|
||||
return component.NewTableWithRows(
|
||||
"Users",
|
||||
"No users found",
|
||||
component.NewTableCols("Name", "Email", "Description", "Enabled", "Domain Name"), rows)
|
||||
}
|
||||
|
||||
// helper function to create an identity specific gophercloud client
|
||||
func identityClientHelper(osp *OpenstackPlugin) *gophercloud.ServiceClient {
|
||||
client, err := openstack.NewIdentityV3(osp.provider, gophercloud.EndpointOpts{})
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("Identity Client Error: %s\n", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
return client
|
||||
}
|
124
internal/plugin/openstack/network.go
Executable file
124
internal/plugin/openstack/network.go
Executable file
@ -0,0 +1,124 @@
|
||||
/*
|
||||
Copyright (c) 2020 AT&T. All Rights Reserved.
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/openstack"
|
||||
"github.com/gophercloud/gophercloud/openstack/networking/v2/networks"
|
||||
"github.com/gophercloud/gophercloud/openstack/networking/v2/subnets"
|
||||
"github.com/gophercloud/gophercloud/pagination"
|
||||
"github.com/vmware-tanzu/octant/pkg/view/component"
|
||||
)
|
||||
|
||||
// gets OpenStack networks available for the tenant
|
||||
// https://docs.openstack.org/api-ref/network/v2/#networks
|
||||
func getNetworks(osp *OpenstackPlugin) component.Component {
|
||||
rows := []component.TableRow{}
|
||||
|
||||
// TODO: Determine if the error needs to be handled from this function
|
||||
networks.List(networkClientHelper(osp), networks.ListOpts{}).EachPage(
|
||||
func(page pagination.Page) (bool, error) {
|
||||
networkList, err := networks.ExtractNetworks(page)
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("Network retrival error: %s\n", err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, n := range networkList {
|
||||
// pull together the subnet names from the cache
|
||||
var subnetString string
|
||||
if len(n.Subnets) > 1 {
|
||||
var tmp []string
|
||||
for subnetID := range n.Subnets {
|
||||
name := subnetCache[n.Subnets[subnetID]]
|
||||
if len(name) > 0 {
|
||||
tmp = append(tmp, name)
|
||||
}
|
||||
}
|
||||
subnetString = strings.Join(tmp, ", ")
|
||||
} else {
|
||||
subnetString = subnetCache[n.Subnets[0]]
|
||||
}
|
||||
|
||||
rows = append(rows, component.TableRow{
|
||||
"Name": component.NewText(n.Name),
|
||||
"Project": component.NewText(projectCache[n.ProjectID]),
|
||||
"Subnets": component.NewText(subnetString),
|
||||
"Shared": component.NewText(strconv.FormatBool(n.Shared)),
|
||||
"Status": component.NewText(n.Status),
|
||||
"Admin State": component.NewText(strconv.FormatBool(n.AdminStateUp)),
|
||||
"Availability Zones": component.NewText(strings.Join(n.AvailabilityZoneHints, ", ")),
|
||||
})
|
||||
}
|
||||
|
||||
return true, nil
|
||||
})
|
||||
|
||||
return component.NewTableWithRows(
|
||||
"Networks",
|
||||
"No networks found",
|
||||
component.NewTableCols("Name", "Project", "Subnets", "Shared", "Status",
|
||||
"Admin State", "Availability Zones"),
|
||||
rows)
|
||||
}
|
||||
|
||||
// gets OpenStack subnets available for the tenant
|
||||
// https://docs.openstack.org/api-ref/network/v2/#subnets
|
||||
func getSubnets(osp *OpenstackPlugin) component.Component {
|
||||
rows := []component.TableRow{}
|
||||
|
||||
// TODO: Determine if the error needs to be handled from this function
|
||||
subnets.List(networkClientHelper(osp), subnets.ListOpts{}).EachPage(
|
||||
func(page pagination.Page) (bool, error) {
|
||||
networkList, err := subnets.ExtractSubnets(page)
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("Subnet list error: %s\n", err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, subnet := range networkList {
|
||||
cidr := subnet.CIDR
|
||||
name := subnet.Name
|
||||
subnetCache[subnet.ID] = name + ": " + cidr
|
||||
|
||||
rows = append(rows, component.TableRow{
|
||||
"Name": component.NewText(name),
|
||||
"Network Address": component.NewText(cidr),
|
||||
"IP Version": component.NewText("IPv" + strconv.Itoa(subnet.IPVersion)),
|
||||
"Gateway IP": component.NewText(subnet.GatewayIP),
|
||||
})
|
||||
}
|
||||
|
||||
return true, nil
|
||||
})
|
||||
|
||||
return component.NewTableWithRows(
|
||||
"Subnets",
|
||||
"No subnets found",
|
||||
component.NewTableCols("Name", "Network Address", "IP Version", "Gateway IP"), rows)
|
||||
}
|
||||
|
||||
// helper function to create a network specific gophercloud client
|
||||
func networkClientHelper(osp *OpenstackPlugin) *gophercloud.ServiceClient {
|
||||
client, err := openstack.NewNetworkV2(osp.provider, gophercloud.EndpointOpts{
|
||||
Name: "neutron",
|
||||
Region: "RegionOne",
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("Network Client Error: %s\n", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
return client
|
||||
}
|
197
internal/plugin/openstack/register.go
Executable file
197
internal/plugin/openstack/register.go
Executable file
@ -0,0 +1,197 @@
|
||||
/*
|
||||
Copyright (c) 2020 AT&T. All Rights Reserved.
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/openstack"
|
||||
"github.com/vmware-tanzu/octant/pkg/navigation"
|
||||
"github.com/vmware-tanzu/octant/pkg/plugin"
|
||||
"github.com/vmware-tanzu/octant/pkg/plugin/service"
|
||||
"github.com/vmware-tanzu/octant/pkg/view/component"
|
||||
)
|
||||
|
||||
// OpenstackPlugin is an example of a plugin that registers navigation and action handlers
|
||||
// and performs some action against a remote API.
|
||||
type OpenstackPlugin struct {
|
||||
provider *gophercloud.ProviderClient
|
||||
}
|
||||
|
||||
// The gophercloud.AuthOptions struct ignores several key variables when attempting to unmarshal the json
|
||||
// this is a workaround until a pull request can be created / accepted for gophercloud
|
||||
// they may have reasons to not expose these variables, but it's not clear as to why this is the case
|
||||
type optJSON struct {
|
||||
IdentityEndpoint string `json:"identityEndpoint"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
DomainID string `json:"domainID"`
|
||||
TenantName string `json:"tenantName"`
|
||||
}
|
||||
|
||||
// domainCache keeps a record of the domains for use with other reporting features
|
||||
// This is updated whenever we go into the getDomains compute function
|
||||
var domainCache = map[string]string{}
|
||||
|
||||
// projectCache keeps a record of the flavors for use with other reporting features
|
||||
// This is updated whenever we go into the getFlavors compute function
|
||||
var flavorCache = map[string]string{}
|
||||
|
||||
// imageCache keeps a record of the images for use with other reporting features
|
||||
// This is updated whenever we go into the getImages compute function
|
||||
var imageCache = map[string]string{}
|
||||
|
||||
// projectCache keeps a record of the projects for use with other reporting features
|
||||
// This is updated whenever we go into the getProjects identity function
|
||||
var projectCache = map[string]string{}
|
||||
|
||||
// subnetCache keeps a record of the subnets for use with other reporting features
|
||||
// This is updated whenever we go into the getSubnets network function
|
||||
var subnetCache = map[string]string{}
|
||||
|
||||
// vmCache keeps a record of the servers for use with other reporting features
|
||||
// This is updated whenever we go into the getVms compute function
|
||||
var vmCache = map[string]string{}
|
||||
|
||||
// NewOpenstackPlugin authenticates to Openstack and return a new openstack plugin struct
|
||||
// this requires an openstack.json in the home dir of the user running the process
|
||||
func NewOpenstackPlugin() *OpenstackPlugin {
|
||||
opts, err := getOpenstackOpts()
|
||||
if err != nil {
|
||||
log.Printf("Error getting authentication opts %s\n", err)
|
||||
}
|
||||
|
||||
client, err := openstack.AuthenticatedClient(opts)
|
||||
if err != nil {
|
||||
log.Printf("Error establishing client %s\n", err)
|
||||
}
|
||||
|
||||
return &OpenstackPlugin{client}
|
||||
}
|
||||
|
||||
// helper function to bring in the connection opts for OpenStack
|
||||
func getOpenstackOpts() (gophercloud.AuthOptions, error) {
|
||||
/* The following environment variables are required for this function to work
|
||||
* - OS_USER_DOMAIN_ID
|
||||
* - OS_AUTH_URL
|
||||
* - OS_PROJECT_DOMAIN_ID
|
||||
* - OS_REGION_NAME
|
||||
* - OS_PROJECT_NAME
|
||||
* - OS_IDENTITY_API_VERSION
|
||||
* - OS_TENANT_NAME
|
||||
* - OS_TENANT_ID
|
||||
* - OS_AUTH_TYPE
|
||||
* - OS_PASSWORD
|
||||
* - OS_USERNAME
|
||||
* - OS_VOLUME_API_VERSION
|
||||
* - OS_TOKEN
|
||||
* - OS_USERID
|
||||
*/
|
||||
opts, err := openstack.AuthOptionsFromEnv()
|
||||
if err != nil {
|
||||
// fall back on file if environment isn't setup
|
||||
opts, err = getOptsFromFile()
|
||||
}
|
||||
|
||||
return opts, err
|
||||
}
|
||||
|
||||
// fall back function if the environment isn't setup, hopefully file based is
|
||||
func getOptsFromFile() (gophercloud.AuthOptions, error) {
|
||||
var fileName string
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
if runtime.GOOS == "windows" {
|
||||
fileName = home + "\\AppData\\Local\\octant\\etc\\openstack.json"
|
||||
} else {
|
||||
fileName = home + "/.config/octant/etc/openstack.json"
|
||||
}
|
||||
|
||||
jsonFile, err := os.Open(fileName)
|
||||
defer jsonFile.Close()
|
||||
|
||||
if err != nil {
|
||||
log.Printf("Error opening file %s\n", err)
|
||||
}
|
||||
|
||||
byteValue, _ := ioutil.ReadAll(jsonFile)
|
||||
|
||||
var tmp optJSON
|
||||
json.Unmarshal(byteValue, &tmp)
|
||||
|
||||
opts := gophercloud.AuthOptions{
|
||||
IdentityEndpoint: tmp.IdentityEndpoint,
|
||||
Username: tmp.Username,
|
||||
Password: tmp.Password,
|
||||
DomainID: tmp.DomainID,
|
||||
TenantName: tmp.TenantName,
|
||||
AllowReauth: true,
|
||||
}
|
||||
|
||||
return opts, err
|
||||
}
|
||||
|
||||
// Register the plugin with octant
|
||||
func Register(name string, description string) (*service.Plugin, error) {
|
||||
osp := NewOpenstackPlugin()
|
||||
|
||||
// Remove the prefix from the go logger since Octant will print logs with timestamps.
|
||||
log.SetPrefix("")
|
||||
|
||||
// Tell Octant to call this plugin when printing configuration or tabs for Pods
|
||||
capabilities := &plugin.Capabilities{
|
||||
IsModule: true,
|
||||
}
|
||||
|
||||
// Set up what should happen when Octant calls this plugin.
|
||||
options := []service.PluginOption{
|
||||
service.WithNavigation(osp.handleNavigation, osp.initRoutes),
|
||||
}
|
||||
|
||||
// Use the plugin service helper to register this plugin.
|
||||
return service.Register(name, description, capabilities, options...)
|
||||
}
|
||||
|
||||
// handlePrint creates a navigation tree for this plugin. Navigation is dynamic and will
|
||||
// be called frequently from Octant. Navigation is a tree of `Navigation` structs.
|
||||
// The plugin can use whatever paths it likes since these paths can be namespaced to the
|
||||
// the plugin.
|
||||
func (osp *OpenstackPlugin) handleNavigation(request *service.NavigationRequest) (navigation.Navigation, error) {
|
||||
return navigation.Navigation{
|
||||
Title: "OpenStack",
|
||||
Path: request.GeneratePath(),
|
||||
IconName: "cloud",
|
||||
}, nil
|
||||
}
|
||||
|
||||
// initRoutes routes for this plugin. In this example, there is a global catch all route
|
||||
// that will return the content for every single path.
|
||||
func (osp *OpenstackPlugin) initRoutes(router *service.Router) {
|
||||
router.HandleFunc("", osp.routeHandler)
|
||||
}
|
||||
|
||||
// Adds the OpenStack components to the visualization
|
||||
func (osp *OpenstackPlugin) routeHandler(request service.Request) (component.ContentResponse, error) {
|
||||
response := component.NewContentResponse(component.TitleFromString("OpenStack"))
|
||||
response.Add(getDomains(osp))
|
||||
response.Add(getUsers(osp))
|
||||
response.Add(getFlavors(osp))
|
||||
response.Add(getSubnets(osp))
|
||||
response.Add(getNetworks(osp))
|
||||
response.Add(getImages(osp))
|
||||
response.Add(getProjects(osp))
|
||||
response.Add(getVMs(osp))
|
||||
response.Add(getVolumes(osp))
|
||||
|
||||
return *response, nil
|
||||
}
|
78
internal/plugin/openstack/register_test.go
Executable file
78
internal/plugin/openstack/register_test.go
Executable file
@ -0,0 +1,78 @@
|
||||
/*
|
||||
Copyright (c) 2020 AT&T. All Rights Reserved.
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/vmware-tanzu/octant/pkg/plugin/service"
|
||||
"github.com/vmware-tanzu/octant/pkg/plugin/service/fake"
|
||||
)
|
||||
|
||||
type fakeRequest struct {
|
||||
ctrl *gomock.Controller
|
||||
path, title string
|
||||
}
|
||||
|
||||
func (f *fakeRequest) DashboardClient() service.Dashboard {
|
||||
return fake.NewMockDashboard(f.ctrl)
|
||||
}
|
||||
|
||||
func (f *fakeRequest) Path() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (f *fakeRequest) Context() context.Context {
|
||||
return context.TODO()
|
||||
}
|
||||
|
||||
func TestRegister(t *testing.T) {
|
||||
plugin, err := Register("openstack", "OpenStack test version")
|
||||
if err != nil {
|
||||
t.Fatalf("Registering the plugin returned an error")
|
||||
}
|
||||
|
||||
err = plugin.Validate()
|
||||
if err != nil {
|
||||
t.Fatalf("Validating the plugin returned an error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoutes(t *testing.T) {
|
||||
router := service.NewRouter()
|
||||
NewOpenstackPlugin().initRoutes(router)
|
||||
|
||||
tests := []struct {
|
||||
path string
|
||||
exists bool
|
||||
}{
|
||||
{
|
||||
path: "",
|
||||
exists: true,
|
||||
},
|
||||
{
|
||||
path: "/not-real",
|
||||
exists: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
test := test // pin the value so that the following function literal binds to it
|
||||
t.Run(fmt.Sprintf("Path='%s'", test.path), func(t *testing.T) {
|
||||
_, found := router.Match(test.path)
|
||||
|
||||
if test.exists != found {
|
||||
if found {
|
||||
t.Errorf("Found path '%s' when it should not exist.", test.path)
|
||||
} else {
|
||||
t.Errorf("Didn't find path '%s' when it should exist.", test.path)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
69
internal/plugin/openstack/volume.go
Executable file
69
internal/plugin/openstack/volume.go
Executable file
@ -0,0 +1,69 @@
|
||||
/*
|
||||
Copyright (c) 2020 AT&T. All Rights Reserved.
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gophercloud/gophercloud"
|
||||
"github.com/gophercloud/gophercloud/openstack"
|
||||
"github.com/gophercloud/gophercloud/openstack/blockstorage/v2/volumes"
|
||||
"github.com/gophercloud/gophercloud/pagination"
|
||||
"github.com/vmware-tanzu/octant/pkg/view/component"
|
||||
)
|
||||
|
||||
// gets OpenStack volumes viewable by the tenant
|
||||
// https://docs.openstack.org/cinder/latest/
|
||||
func getVolumes(osp *OpenstackPlugin) component.Component {
|
||||
client, err := openstack.NewBlockStorageV2(osp.provider, gophercloud.EndpointOpts{})
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("NewIdentityV3 error: %v", err)
|
||||
}
|
||||
|
||||
rows := []component.TableRow{}
|
||||
|
||||
// TODO: Determine if the error needs to be handled from this function
|
||||
volumes.List(client, volumes.ListOpts{}).EachPage(
|
||||
func(page pagination.Page) (bool, error) {
|
||||
volumeList, err := volumes.ExtractVolumes(page)
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("Broken at volume list %v\n", err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, volume := range volumeList {
|
||||
// extract the potentially multiple devices
|
||||
var attachedDevices []string
|
||||
for index := range volume.Attachments {
|
||||
attachment := volume.Attachments[index]
|
||||
attachedDevices = append(attachedDevices, vmCache[attachment.ServerID]+" at "+attachment.Device)
|
||||
}
|
||||
|
||||
rows = append(rows, component.TableRow{
|
||||
"Name": component.NewText(volume.Name),
|
||||
"Description": component.NewText(volume.Description),
|
||||
"Size": component.NewText(strconv.Itoa(volume.Size) + "GiB"),
|
||||
"Status": component.NewText(volume.Status),
|
||||
"Type": component.NewText(volume.VolumeType),
|
||||
"Attached To": component.NewText(strings.Join(attachedDevices, ", ")),
|
||||
"Availability Zone": component.NewText(volume.AvailabilityZone),
|
||||
"Bootable": component.NewText(volume.Bootable),
|
||||
"Encrypted": component.NewText(strconv.FormatBool(volume.Encrypted)),
|
||||
})
|
||||
}
|
||||
|
||||
return true, nil
|
||||
})
|
||||
|
||||
return component.NewTableWithRows(
|
||||
"Volumes",
|
||||
"No volumes found",
|
||||
component.NewTableCols("Name", "Description", "Size", "Status", "Group", "Type", "Attached To",
|
||||
"Availability Zone", "Bootable", "Encrypted"), rows)
|
||||
}
|
@ -3,7 +3,7 @@ set -x
|
||||
|
||||
tools_bin_dir="${BASH_SOURCE%/*}"
|
||||
download_url=https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh
|
||||
version=v1.21.0
|
||||
version=v1.24.0
|
||||
|
||||
if ! curl -sfL "$download_url" | sh -s -- -b "$tools_bin_dir/bin" "$version"; then
|
||||
printf "Something went wrong while installing golangci-lint\n" 1>&2
|
||||
|
Loading…
x
Reference in New Issue
Block a user