Merge "This adds a plugin for OpenStack to the Airship UI"

This commit is contained in:
Zuul 2020-03-27 14:02:35 +00:00 committed by Gerrit Code Review
commit 618cccf633
16 changed files with 976 additions and 29 deletions

View File

@ -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) {
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)
// 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
})
}
```
5. Build the hello-world plugin

View File

@ -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).

View File

@ -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
View 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
View 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
View File

@ -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
View File

@ -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=

View File

@ -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"))

View File

@ -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

View 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
}

View 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
}

View 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
}

View 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
}

View 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)
}
}
})
}
}

View 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)
}

View File

@ -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