Add testing to airshipui
1. Change embedded structs to pointers so json omitempty will behave as expected and omit empty structs from ws messages 2. Add dummy airshipui.json config file for testing 3. Add testutil package to build dummy config objects 4. Add tests for configs package 5. Add tests for webservice package 6. Add tests for integrations/ctl package Change-Id: I1a2ac543898cbbae96c764a983e7e9b73946a9d9
This commit is contained in:
parent
b42f594bdb
commit
7548c4bb9c
2
go.mod
2
go.mod
@ -7,9 +7,11 @@ require (
|
||||
github.com/gorilla/websocket v1.4.2
|
||||
github.com/spf13/cobra v0.0.6
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.4.0
|
||||
github.com/vmware-tanzu/octant v0.12.0
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a
|
||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527
|
||||
gopkg.in/src-d/go-git.v4 v4.13.1
|
||||
k8s.io/api v0.17.4
|
||||
k8s.io/apimachinery v0.17.4
|
||||
opendev.org/airship/airshipctl v0.0.0-20200518155418-7276dd68d8d0
|
||||
|
@ -20,6 +20,7 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
@ -51,6 +52,13 @@ func init() {
|
||||
}
|
||||
|
||||
func launch(cmd *cobra.Command, args []string) {
|
||||
// set default config path
|
||||
// TODO: do we want to make this a flag that can be passed in?
|
||||
airshipUIConfigPath, err := getDefaultConfigPath()
|
||||
if err != nil {
|
||||
log.Printf("Error setting config path %s", err)
|
||||
}
|
||||
|
||||
sigs := make(chan os.Signal)
|
||||
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
|
||||
|
||||
@ -59,7 +67,7 @@ func launch(cmd *cobra.Command, args []string) {
|
||||
waitgrp := sync.WaitGroup{}
|
||||
|
||||
// Read AirshipUI config file
|
||||
if err := configs.GetConfigFromFile(); err == nil {
|
||||
if err := configs.SetUIConfig(airshipUIConfigPath); err == nil {
|
||||
// launch any plugins marked as autoStart: true in airshipui.json
|
||||
for _, p := range configs.UIConfig.Plugins {
|
||||
if p.Executable.AutoStart {
|
||||
@ -134,3 +142,12 @@ func Execute() {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func getDefaultConfigPath() (string, error) {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return filepath.FromSlash(home + "/.airship/airshipui.json"), nil
|
||||
}
|
||||
|
@ -18,7 +18,6 @@ import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"opendev.org/airship/airshipctl/pkg/config"
|
||||
)
|
||||
@ -30,9 +29,9 @@ var (
|
||||
|
||||
// Config basic structure to hold configuration params for Airship UI
|
||||
type Config struct {
|
||||
AuthMethod AuthMethod `json:"authMethod,omitempty"`
|
||||
Plugins []Plugin `json:"plugins,omitempty"`
|
||||
Clusters []Cluster `json:"clusters,omitempty"`
|
||||
AuthMethod *AuthMethod `json:"authMethod,omitempty"`
|
||||
Plugins []Plugin `json:"plugins,omitempty"`
|
||||
Clusters []Cluster `json:"clusters,omitempty"`
|
||||
}
|
||||
|
||||
// AuthMethod structure to hold authentication parameters
|
||||
@ -44,18 +43,24 @@ type AuthMethod struct {
|
||||
|
||||
// Plugin structure to hold plugin specific parameters
|
||||
type Plugin struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Dashboard struct {
|
||||
Protocol string `json:"protocol,omitempty"`
|
||||
FQDN string `json:"fqdn,omitempty"`
|
||||
Port uint16 `json:"port,omitempty"`
|
||||
Path string `json:"path,omitempty"`
|
||||
} `json:"dashboard"`
|
||||
Executable struct {
|
||||
AutoStart bool `json:"autoStart,omitempty"`
|
||||
Filepath string `json:"filepath,omitempty"`
|
||||
Args []string `json:"args,omitempty"`
|
||||
} `json:"executable"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Dashboard *PluginDashboard `json:"dashboard,omitempty"`
|
||||
Executable *Executable `json:"executable"`
|
||||
}
|
||||
|
||||
// PluginDashboard structure to hold web dashboard parameters for plugins
|
||||
type PluginDashboard struct {
|
||||
Protocol string `json:"protocol,omitempty"`
|
||||
FQDN string `json:"fqdn,omitempty"`
|
||||
Port uint16 `json:"port,omitempty"`
|
||||
Path string `json:"path,omitempty"`
|
||||
}
|
||||
|
||||
// Executable structure to hold parameters for launching an executable plugin
|
||||
type Executable struct {
|
||||
AutoStart bool `json:"autoStart,omitempty"`
|
||||
Filepath string `json:"filepath,omitempty"`
|
||||
Args []string `json:"args,omitempty"`
|
||||
}
|
||||
|
||||
// Dashboard structure
|
||||
@ -132,39 +137,42 @@ type WsMessage struct {
|
||||
Message string `json:"message,omitempty"`
|
||||
|
||||
// information related to the init of the UI
|
||||
Dashboards []Cluster `json:"dashboards,omitempty"`
|
||||
Plugins []Plugin `json:"plugins,omitempty"`
|
||||
Authentication AuthMethod `json:"authentication,omitempty"`
|
||||
AuthInfoOptions config.AuthInfoOptions `json:"authInfoOptions,omitempty"`
|
||||
ContextOptions config.ContextOptions `json:"contextOptions,omitempty"`
|
||||
ClusterOptions config.ClusterOptions `json:"clusterOptions,omitempty"`
|
||||
Dashboards []Cluster `json:"dashboards,omitempty"`
|
||||
Plugins []Plugin `json:"plugins,omitempty"`
|
||||
Authentication *AuthMethod `json:"authentication,omitempty"`
|
||||
AuthInfoOptions *config.AuthInfoOptions `json:"authInfoOptions,omitempty"`
|
||||
ContextOptions *config.ContextOptions `json:"contextOptions,omitempty"`
|
||||
ClusterOptions *config.ClusterOptions `json:"clusterOptions,omitempty"`
|
||||
}
|
||||
|
||||
// GetConfigFromFile reads configuration file and returns error on any error reading the file
|
||||
// SetUIConfig sets the UIConfig object with values obtained from
|
||||
// airshipui.json, located at 'filename'
|
||||
// TODO: add watcher to the json file to reload conf on change
|
||||
func GetConfigFromFile() error {
|
||||
var fileName string
|
||||
home, err := os.UserHomeDir()
|
||||
func SetUIConfig(filename string) error {
|
||||
bytes, err := getBytesFromFile(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fileName = filepath.FromSlash(home + "/.airship/airshipui.json")
|
||||
|
||||
jsonFile, err := os.Open(fileName)
|
||||
err = json.Unmarshal(bytes, &UIConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer jsonFile.Close()
|
||||
|
||||
byteValue, err := ioutil.ReadAll(jsonFile)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(byteValue, &UIConfig)
|
||||
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
|
||||
func getBytesFromFile(filename string) ([]byte, error) {
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
bytes, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return bytes, nil
|
||||
}
|
||||
|
51
internal/configs/configs_test.go
Normal file
51
internal/configs/configs_test.go
Normal file
@ -0,0 +1,51 @@
|
||||
/*
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package configs_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"opendev.org/airship/airshipui/internal/configs"
|
||||
"opendev.org/airship/airshipui/testutil"
|
||||
)
|
||||
|
||||
const (
|
||||
fakeFile string = "/fake/config/path"
|
||||
testFile string = "testdata/airshipui.json"
|
||||
)
|
||||
|
||||
func TestSetUIConfig(t *testing.T) {
|
||||
conf := configs.Config{
|
||||
Clusters: []configs.Cluster{
|
||||
testutil.DummyClusterConfig(),
|
||||
},
|
||||
Plugins: []configs.Plugin{
|
||||
testutil.DummyPluginWithDashboardConfig(),
|
||||
testutil.DummyPluginNoDashboard(),
|
||||
},
|
||||
AuthMethod: testutil.DummyAuthMethodConfig(),
|
||||
}
|
||||
|
||||
err := configs.SetUIConfig(testFile)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, conf, configs.UIConfig)
|
||||
}
|
||||
|
||||
func TestFileNotFound(t *testing.T) {
|
||||
err := configs.SetUIConfig(fakeFile)
|
||||
assert.Error(t, err)
|
||||
}
|
55
internal/configs/testdata/airshipui.json
vendored
Normal file
55
internal/configs/testdata/airshipui.json
vendored
Normal file
@ -0,0 +1,55 @@
|
||||
{
|
||||
"authMethod": {
|
||||
"url": "http://fake.auth.method.com/auth"
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
"name": "dummy_plugin_with_dash",
|
||||
"dashboard": {
|
||||
"protocol": "http",
|
||||
"fqdn": "localhost",
|
||||
"port": 80,
|
||||
"path": "index.html"
|
||||
},
|
||||
"executable": {
|
||||
"autoStart": true,
|
||||
"filepath": "/fake/path/to/executable",
|
||||
"args": [
|
||||
"--fakeflag",
|
||||
"fakevalue"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "dummy_plugin_no_dash",
|
||||
"executable": {
|
||||
"autoStart": true,
|
||||
"filepath": "/fake/path/to/executable",
|
||||
"args": [
|
||||
"--fakeflag",
|
||||
"fakevalue"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"clusters": [
|
||||
{
|
||||
"name": "dummy_cluster",
|
||||
"baseFqdn": "dummy.cluster.local",
|
||||
"namespaces": [
|
||||
{
|
||||
"name": "dummy_namespace",
|
||||
"dashboards": [
|
||||
{
|
||||
"name": "dummy_dashboard",
|
||||
"protocol": "http",
|
||||
"hostname": "dummyhost",
|
||||
"port": 80,
|
||||
"path": "fake/login/path"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -16,6 +16,8 @@ package ctl
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"text/template"
|
||||
|
||||
"opendev.org/airship/airshipctl/pkg/environment"
|
||||
@ -23,6 +25,13 @@ import (
|
||||
"opendev.org/airship/airshipui/internal/configs"
|
||||
)
|
||||
|
||||
// obtain base path of caller so references to html
|
||||
// template files still work from outside the package
|
||||
var (
|
||||
_, b, _, _ = runtime.Caller(0)
|
||||
basepath = filepath.Dir(b)
|
||||
)
|
||||
|
||||
// maintain the state of a potentially long running process
|
||||
var runningRequests map[configs.WsSubComponentType]bool = make(map[configs.WsSubComponentType]bool)
|
||||
|
||||
|
@ -16,6 +16,7 @@ package ctl
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"opendev.org/airship/airshipctl/pkg/bootstrap/isogen"
|
||||
"opendev.org/airship/airshipui/internal/configs"
|
||||
@ -77,5 +78,5 @@ func getBaremetalHTML() (string, error) {
|
||||
p.ButtonText = "In Progress"
|
||||
}
|
||||
|
||||
return getHTML("./internal/integrations/ctl/templates/baremetal.html", p)
|
||||
return getHTML(filepath.Join(basepath, "/templates/baremetal.html"), p)
|
||||
}
|
||||
|
69
internal/integrations/ctl/baremetal_test.go
Normal file
69
internal/integrations/ctl/baremetal_test.go
Normal file
@ -0,0 +1,69 @@
|
||||
/*
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package ctl
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"opendev.org/airship/airshipui/internal/configs"
|
||||
)
|
||||
|
||||
const (
|
||||
testBaremetalHTML string = "testdata/baremetal.html"
|
||||
)
|
||||
|
||||
func TestHandleDefaultBaremetalRequest(t *testing.T) {
|
||||
html, err := ioutil.ReadFile(testBaremetalHTML)
|
||||
require.NoError(t, err)
|
||||
|
||||
request := configs.WsMessage{
|
||||
Type: configs.AirshipCTL,
|
||||
Component: configs.Baremetal,
|
||||
SubComponent: configs.GetDefaults,
|
||||
}
|
||||
|
||||
response := HandleBaremetalRequest(request)
|
||||
|
||||
expected := configs.WsMessage{
|
||||
Type: configs.AirshipCTL,
|
||||
Component: configs.Baremetal,
|
||||
SubComponent: configs.GetDefaults,
|
||||
HTML: string(html),
|
||||
}
|
||||
|
||||
assert.Equal(t, expected, response)
|
||||
}
|
||||
|
||||
func TestHandleUnknownBaremetalSubComponent(t *testing.T) {
|
||||
request := configs.WsMessage{
|
||||
Type: configs.AirshipCTL,
|
||||
Component: configs.Baremetal,
|
||||
SubComponent: "fake_subcomponent",
|
||||
}
|
||||
|
||||
response := HandleBaremetalRequest(request)
|
||||
|
||||
expected := configs.WsMessage{
|
||||
Type: configs.AirshipCTL,
|
||||
Component: configs.Baremetal,
|
||||
SubComponent: "fake_subcomponent",
|
||||
Error: "Subcomponent fake_subcomponent not found",
|
||||
}
|
||||
|
||||
assert.Equal(t, expected, response)
|
||||
}
|
@ -16,6 +16,7 @@ package ctl
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"opendev.org/airship/airshipctl/pkg/config"
|
||||
"opendev.org/airship/airshipui/internal/configs"
|
||||
@ -126,7 +127,7 @@ func getCredentialTableRows() string {
|
||||
}
|
||||
|
||||
func getConfigHTML() (string, error) {
|
||||
return getHTML("./internal/integrations/ctl/templates/config.html", ctlPage{
|
||||
return getHTML(filepath.Join(basepath, "/templates/config.html"), ctlPage{
|
||||
ClusterRows: getClusterTableRows(),
|
||||
ContextRows: getContextTableRows(),
|
||||
CredentialRows: getCredentialTableRows(),
|
||||
@ -137,7 +138,7 @@ func getConfigHTML() (string, error) {
|
||||
|
||||
// SetCluster will take ui cluster info, translate them into CTL commands and send a response back to the UI
|
||||
func setCluster(request configs.WsMessage) (string, error) {
|
||||
modified, err := config.RunSetCluster(&request.ClusterOptions, c.settings.Config, true)
|
||||
modified, err := config.RunSetCluster(request.ClusterOptions, c.settings.Config, true)
|
||||
|
||||
var message string
|
||||
if modified {
|
||||
@ -153,7 +154,7 @@ func setCluster(request configs.WsMessage) (string, error) {
|
||||
|
||||
// SetContext will take ui context info, translate them into CTL commands and send a response back to the UI
|
||||
func setContext(request configs.WsMessage) (string, error) {
|
||||
modified, err := config.RunSetContext(&request.ContextOptions, c.settings.Config, true)
|
||||
modified, err := config.RunSetContext(request.ContextOptions, c.settings.Config, true)
|
||||
|
||||
var message string
|
||||
if modified {
|
||||
@ -167,7 +168,7 @@ func setContext(request configs.WsMessage) (string, error) {
|
||||
|
||||
// SetContext will take ui context info, translate them into CTL commands and send a response back to the UI
|
||||
func setCredential(request configs.WsMessage) (string, error) {
|
||||
modified, err := config.RunSetAuthInfo(&request.AuthInfoOptions, c.settings.Config, true)
|
||||
modified, err := config.RunSetAuthInfo(request.AuthInfoOptions, c.settings.Config, true)
|
||||
|
||||
var message string
|
||||
if modified {
|
||||
|
88
internal/integrations/ctl/config_test.go
Normal file
88
internal/integrations/ctl/config_test.go
Normal file
@ -0,0 +1,88 @@
|
||||
/*
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package ctl
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"opendev.org/airship/airshipui/internal/configs"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"opendev.org/airship/airshipctl/pkg/config"
|
||||
"opendev.org/airship/airshipctl/pkg/environment"
|
||||
)
|
||||
|
||||
const (
|
||||
testConfigHTML string = "testdata/config.html"
|
||||
testKubeConfig string = "testdata/kubeconfig.yaml"
|
||||
testAirshipConfig string = "testdata/config.yaml"
|
||||
)
|
||||
|
||||
func TestHandleDefaultConfigRequest(t *testing.T) {
|
||||
html, err := ioutil.ReadFile(testConfigHTML)
|
||||
require.NoError(t, err)
|
||||
|
||||
// point airshipctl client toward test configs
|
||||
c.settings = &environment.AirshipCTLSettings{
|
||||
AirshipConfigPath: testAirshipConfig,
|
||||
KubeConfigPath: testKubeConfig,
|
||||
Config: config.NewConfig(),
|
||||
}
|
||||
|
||||
err = c.settings.Config.LoadConfig(
|
||||
c.settings.AirshipConfigPath,
|
||||
c.settings.KubeConfigPath,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
// simulate incoming WsMessage from websocket client
|
||||
request := configs.WsMessage{
|
||||
Type: configs.AirshipCTL,
|
||||
Component: configs.CTLConfig,
|
||||
SubComponent: configs.GetDefaults,
|
||||
}
|
||||
|
||||
response := HandleConfigRequest(request)
|
||||
|
||||
expected := configs.WsMessage{
|
||||
Type: configs.AirshipCTL,
|
||||
Component: configs.CTLConfig,
|
||||
SubComponent: configs.GetDefaults,
|
||||
HTML: string(html),
|
||||
}
|
||||
|
||||
assert.Equal(t, expected, response)
|
||||
}
|
||||
|
||||
func TestHandleUnknownConfigSubComponent(t *testing.T) {
|
||||
request := configs.WsMessage{
|
||||
Type: configs.AirshipCTL,
|
||||
Component: configs.CTLConfig,
|
||||
SubComponent: "fake_subcomponent",
|
||||
}
|
||||
|
||||
response := HandleConfigRequest(request)
|
||||
|
||||
expected := configs.WsMessage{
|
||||
Type: configs.AirshipCTL,
|
||||
Component: configs.CTLConfig,
|
||||
SubComponent: "fake_subcomponent",
|
||||
Error: "Subcomponent fake_subcomponent not found",
|
||||
}
|
||||
|
||||
assert.Equal(t, expected, response)
|
||||
}
|
@ -16,6 +16,7 @@ package ctl
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"opendev.org/airship/airshipctl/pkg/document/pull"
|
||||
"opendev.org/airship/airshipui/internal/configs"
|
||||
@ -61,7 +62,7 @@ func (c *Client) docPull() (string, error) {
|
||||
}
|
||||
|
||||
func getDocumentHTML() (string, error) {
|
||||
return getHTML("./internal/integrations/ctl/templates/document.html", ctlPage{
|
||||
return getHTML(filepath.Join(basepath, "/templates/document.html"), ctlPage{
|
||||
Title: "Document",
|
||||
Version: getAirshipCTLVersion(),
|
||||
})
|
||||
|
69
internal/integrations/ctl/document_test.go
Normal file
69
internal/integrations/ctl/document_test.go
Normal file
@ -0,0 +1,69 @@
|
||||
/*
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package ctl
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"opendev.org/airship/airshipui/internal/configs"
|
||||
)
|
||||
|
||||
const (
|
||||
testDocumentHTML string = "testdata/document.html"
|
||||
)
|
||||
|
||||
func TestHandleDefaultDocumentRequest(t *testing.T) {
|
||||
html, err := ioutil.ReadFile(testDocumentHTML)
|
||||
require.NoError(t, err)
|
||||
|
||||
request := configs.WsMessage{
|
||||
Type: configs.AirshipCTL,
|
||||
Component: configs.Document,
|
||||
SubComponent: configs.GetDefaults,
|
||||
}
|
||||
|
||||
response := HandleDocumentRequest(request)
|
||||
|
||||
expected := configs.WsMessage{
|
||||
Type: configs.AirshipCTL,
|
||||
Component: configs.Document,
|
||||
SubComponent: configs.GetDefaults,
|
||||
HTML: string(html),
|
||||
}
|
||||
|
||||
assert.Equal(t, expected, response)
|
||||
}
|
||||
|
||||
func TestHandleUnknownDocumentSubComponent(t *testing.T) {
|
||||
request := configs.WsMessage{
|
||||
Type: configs.AirshipCTL,
|
||||
Component: configs.Document,
|
||||
SubComponent: "fake_subcomponent",
|
||||
}
|
||||
|
||||
response := HandleDocumentRequest(request)
|
||||
|
||||
expected := configs.WsMessage{
|
||||
Type: configs.AirshipCTL,
|
||||
Component: configs.Document,
|
||||
SubComponent: "fake_subcomponent",
|
||||
Error: "Subcomponent fake_subcomponent not found",
|
||||
}
|
||||
|
||||
assert.Equal(t, expected, response)
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
<h1>Airship CTL {{.Title}} Base Information</h1>
|
||||
<p>Version: {{.Version}}</p>
|
||||
|
||||
<h2>Generate ISO</h2>
|
||||
<button type="button" class="btn btn-info" id="GenIsoBtn" onclick="baremetalAction(this)" style="width: 150px;" {{.Disabled}}>{{.ButtonText}}</button>
|
||||
<h1>Airship CTL {{.Title}} Base Information</h1>
|
||||
<p>Version: {{.Version}}</p>
|
||||
|
||||
<h2>Generate ISO</h2>
|
||||
<button type="button" class="btn btn-info" id="GenIsoBtn" onclick="baremetalAction(this)" style="width: 150px;" {{.Disabled}}>{{.ButtonText}}</button>
|
||||
|
@ -1,162 +1,160 @@
|
||||
<h1>Airship CTL {{.Title}} Base Information</h1>
|
||||
<p>Version: {{.Version}}</p>
|
||||
|
||||
<!-- Cluster details in accordion -->
|
||||
<button class="accordion">Cluster</button>
|
||||
<div class="panel">
|
||||
<p>
|
||||
<table class="table" id="ClusterTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Bootstrap Info</th>
|
||||
<th scope="col">Cluster Kube Conf</th>
|
||||
<th scope="col">Management Configuration</th>
|
||||
<th scope="col">Location Of Origin</th>
|
||||
<th scope="col">Server</th>
|
||||
<th scope="col">Certificate Authority</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{.ClusterRows}}
|
||||
</tbody>
|
||||
</table>
|
||||
<button type="button" class="btn btn-info" id="ClusterBtn" onclick="addConfigModal(this)">Add</button>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- This is used by the cluster add modal and is injected into the DOM but not displayed except by the popup -->
|
||||
<div id="ClusterModalTemplate" style="display:none">
|
||||
<h2>Add Cluster Member</h2>
|
||||
<table class="table" id="ClusterAddTable">
|
||||
<thead>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col">Type</th>
|
||||
<th scope="col">Server</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><input type="text"></td>
|
||||
<td>
|
||||
<select>
|
||||
<option value="ephemeral">ephemeral</option>
|
||||
<option value="target">target</option>
|
||||
</select>
|
||||
</td>
|
||||
<td><input type="text"></td>
|
||||
<td>
|
||||
<button class="btn btn-success" onclick="saveConfigDialog(this)">Save</button>
|
||||
<button class="btn btn-dark" onclick="closeDialog(this)">Cancel</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Context details in accordion -->
|
||||
<button class="accordion">Context</button>
|
||||
<div class="panel">
|
||||
<p>
|
||||
<table class="table" id="ContextTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Context Kube Conf</th>
|
||||
<th scope="col">Manifest</th>
|
||||
<th scope="col">Location Of Origin</th>
|
||||
<th scope="col">Cluster</th>
|
||||
<th scope="col">User</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{.ContextRows}}
|
||||
</tbody>
|
||||
</table>
|
||||
</p>
|
||||
<button type="button" class="btn btn-info" id="ContextBtn" onclick="addConfigModal(this)">Add</button>
|
||||
</div>
|
||||
|
||||
<!-- This is used by the context add modal and is injected into the DOM but not displayed except by the popup -->
|
||||
<div id="ContextModalTemplate" style="display:none">
|
||||
<h2>Add Context</h2>
|
||||
<table class="table" id="ContextAddTable">
|
||||
<thead>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col">Type</th>
|
||||
<th scope="col">Cluster</th>
|
||||
<th scope="col">User</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><input type="text"></td>
|
||||
<td>
|
||||
<select>
|
||||
<option value="ephemeral">ephemeral</option>
|
||||
<option value="target">target</option>
|
||||
</select>
|
||||
</td>
|
||||
<td><input type="text"></td>
|
||||
<td><input type="text"></td>
|
||||
<td>
|
||||
<button class="btn btn-success" onclick="saveConfigDialog(this)">Save</button>
|
||||
<button class="btn btn-dark" onclick="closeDialog(this)">Cancel</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Credential details in accordion -->
|
||||
<button class="accordion">Credential</button>
|
||||
<div class="panel">
|
||||
<p>
|
||||
<table class="table" id="CredentialTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Location Of Origin</th>
|
||||
<th scope="col">Username</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{.CredentialRows}}
|
||||
</tbody>
|
||||
</table>
|
||||
<button type="button" class="btn btn-info" id="CredentialBtn" onclick="addConfigModal(this)">Add</button>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- This is used by the credential add modal and is injected into the DOM but not displayed except by the popup -->
|
||||
<div id="CredentialModalTemplate" style="display:none">
|
||||
<h2>Add Credential</h2>
|
||||
<table class="table" id="CredentialAddTable">
|
||||
<thead>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col">User</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><input type="text"></td>
|
||||
<td><input type="text"></td>
|
||||
<td>
|
||||
<button class="btn btn-success" onclick="saveConfigDialog(this)">Save</button>
|
||||
<button class="btn btn-dark" onclick="closeDialog(this)">Cancel</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
||||
<h1>Airship CTL {{.Title}} Base Information</h1>
|
||||
<p>Version: {{.Version}}</p>
|
||||
|
||||
<!-- Cluster details in accordion -->
|
||||
<button class="accordion">Cluster</button>
|
||||
<div class="panel">
|
||||
<p>
|
||||
<table class="table" id="ClusterTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Bootstrap Info</th>
|
||||
<th scope="col">Cluster Kube Conf</th>
|
||||
<th scope="col">Management Configuration</th>
|
||||
<th scope="col">Location Of Origin</th>
|
||||
<th scope="col">Server</th>
|
||||
<th scope="col">Certificate Authority</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{.ClusterRows}}
|
||||
</tbody>
|
||||
</table>
|
||||
<button type="button" class="btn btn-info" id="ClusterBtn" onclick="addConfigModal(this)">Add</button>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- This is used by the cluster add modal and is injected into the DOM but not displayed except by the popup -->
|
||||
<div id="ClusterModalTemplate" style="display:none">
|
||||
<h2>Add Cluster Member</h2>
|
||||
<table class="table" id="ClusterAddTable">
|
||||
<thead>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col">Type</th>
|
||||
<th scope="col">Server</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><input type="text"></td>
|
||||
<td>
|
||||
<select>
|
||||
<option value="ephemeral">ephemeral</option>
|
||||
<option value="target">target</option>
|
||||
</select>
|
||||
</td>
|
||||
<td><input type="text"></td>
|
||||
<td>
|
||||
<button class="btn btn-success" onclick="saveConfigDialog(this)">Save</button>
|
||||
<button class="btn btn-dark" onclick="closeDialog(this)">Cancel</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Context details in accordion -->
|
||||
<button class="accordion">Context</button>
|
||||
<div class="panel">
|
||||
<p>
|
||||
<table class="table" id="ContextTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Context Kube Conf</th>
|
||||
<th scope="col">Manifest</th>
|
||||
<th scope="col">Location Of Origin</th>
|
||||
<th scope="col">Cluster</th>
|
||||
<th scope="col">User</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{.ContextRows}}
|
||||
</tbody>
|
||||
</table>
|
||||
</p>
|
||||
<button type="button" class="btn btn-info" id="ContextBtn" onclick="addConfigModal(this)">Add</button>
|
||||
</div>
|
||||
|
||||
<!-- This is used by the context add modal and is injected into the DOM but not displayed except by the popup -->
|
||||
<div id="ContextModalTemplate" style="display:none">
|
||||
<h2>Add Context</h2>
|
||||
<table class="table" id="ContextAddTable">
|
||||
<thead>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col">Type</th>
|
||||
<th scope="col">Cluster</th>
|
||||
<th scope="col">User</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><input type="text"></td>
|
||||
<td>
|
||||
<select>
|
||||
<option value="ephemeral">ephemeral</option>
|
||||
<option value="target">target</option>
|
||||
</select>
|
||||
</td>
|
||||
<td><input type="text"></td>
|
||||
<td><input type="text"></td>
|
||||
<td>
|
||||
<button class="btn btn-success" onclick="saveConfigDialog(this)">Save</button>
|
||||
<button class="btn btn-dark" onclick="closeDialog(this)">Cancel</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Credential details in accordion -->
|
||||
<button class="accordion">Credential</button>
|
||||
<div class="panel">
|
||||
<p>
|
||||
<table class="table" id="CredentialTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Location Of Origin</th>
|
||||
<th scope="col">Username</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{.CredentialRows}}
|
||||
</tbody>
|
||||
</table>
|
||||
<button type="button" class="btn btn-info" id="CredentialBtn" onclick="addConfigModal(this)">Add</button>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- This is used by the credential add modal and is injected into the DOM but not displayed except by the popup -->
|
||||
<div id="CredentialModalTemplate" style="display:none">
|
||||
<h2>Add Credential</h2>
|
||||
<table class="table" id="CredentialAddTable">
|
||||
<thead>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col">User</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><input type="text"></td>
|
||||
<td><input type="text"></td>
|
||||
<td>
|
||||
<button class="btn btn-success" onclick="saveConfigDialog(this)">Save</button>
|
||||
<button class="btn btn-dark" onclick="closeDialog(this)">Cancel</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<h1>Airship CTL {{.Title}} Base Information</h1>
|
||||
<p>Version: {{.Version}}</p>
|
||||
|
||||
<h2>Document Pull</h2>
|
||||
<button type="button" class="btn btn-info" id="DocPullBtn" onclick="documentAction(this)" style="width: 150px;">Document Pull</button>
|
||||
<h1>Airship CTL {{.Title}} Base Information</h1>
|
||||
<p>Version: {{.Version}}</p>
|
||||
|
||||
<h2>Document Pull</h2>
|
||||
<button type="button" class="btn btn-info" id="DocPullBtn" onclick="documentAction(this)" style="width: 150px;">Document Pull</button>
|
||||
|
5
internal/integrations/ctl/testdata/baremetal.html
vendored
Normal file
5
internal/integrations/ctl/testdata/baremetal.html
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
<h1>Airship CTL Baremetal Base Information</h1>
|
||||
<p>Version: devel</p>
|
||||
|
||||
<h2>Generate ISO</h2>
|
||||
<button type="button" class="btn btn-info" id="GenIsoBtn" onclick="baremetalAction(this)" style="width: 150px;" >Generate ISO</button>
|
160
internal/integrations/ctl/testdata/config.html
vendored
Normal file
160
internal/integrations/ctl/testdata/config.html
vendored
Normal file
@ -0,0 +1,160 @@
|
||||
<h1>Airship CTL Config Base Information</h1>
|
||||
<p>Version: devel</p>
|
||||
|
||||
<!-- Cluster details in accordion -->
|
||||
<button class="accordion">Cluster</button>
|
||||
<div class="panel">
|
||||
<p>
|
||||
<table class="table" id="ClusterTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Bootstrap Info</th>
|
||||
<th scope="col">Cluster Kube Conf</th>
|
||||
<th scope="col">Management Configuration</th>
|
||||
<th scope="col">Location Of Origin</th>
|
||||
<th scope="col">Server</th>
|
||||
<th scope="col">Certificate Authority</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td><div contenteditable=true>default</div></td><td><div contenteditable=true>kubernetes_target</div></td><td><div contenteditable=true>default</div></td><td>testdata/kubeconfig.yaml</td><td><div contenteditable=true>https://10.0.0.1:6553</div></td><td><div contenteditable=true>pki/cluster-ca.pem</div></td><td><button type="button" class="btn btn-success" onclick="saveConfig(this)">Save</button></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<button type="button" class="btn btn-info" id="ClusterBtn" onclick="addConfigModal(this)">Add</button>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- This is used by the cluster add modal and is injected into the DOM but not displayed except by the popup -->
|
||||
<div id="ClusterModalTemplate" style="display:none">
|
||||
<h2>Add Cluster Member</h2>
|
||||
<table class="table" id="ClusterAddTable">
|
||||
<thead>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col">Type</th>
|
||||
<th scope="col">Server</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><input type="text"></td>
|
||||
<td>
|
||||
<select>
|
||||
<option value="ephemeral">ephemeral</option>
|
||||
<option value="target">target</option>
|
||||
</select>
|
||||
</td>
|
||||
<td><input type="text"></td>
|
||||
<td>
|
||||
<button class="btn btn-success" onclick="saveConfigDialog(this)">Save</button>
|
||||
<button class="btn btn-dark" onclick="closeDialog(this)">Cancel</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Context details in accordion -->
|
||||
<button class="accordion">Context</button>
|
||||
<div class="panel">
|
||||
<p>
|
||||
<table class="table" id="ContextTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Context Kube Conf</th>
|
||||
<th scope="col">Manifest</th>
|
||||
<th scope="col">Location Of Origin</th>
|
||||
<th scope="col">Cluster</th>
|
||||
<th scope="col">User</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td><div contenteditable=true>kubernetes_target</div></td><td><div contenteditable=true></div></td><td>testdata/kubeconfig.yaml</td><td><div contenteditable=true>kubernetes_target</div></td><td><div contenteditable=true>admin</div></td><td><button type="button" class="btn btn-success" onclick="saveConfig(this)">Save</button></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</p>
|
||||
<button type="button" class="btn btn-info" id="ContextBtn" onclick="addConfigModal(this)">Add</button>
|
||||
</div>
|
||||
|
||||
<!-- This is used by the context add modal and is injected into the DOM but not displayed except by the popup -->
|
||||
<div id="ContextModalTemplate" style="display:none">
|
||||
<h2>Add Context</h2>
|
||||
<table class="table" id="ContextAddTable">
|
||||
<thead>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col">Type</th>
|
||||
<th scope="col">Cluster</th>
|
||||
<th scope="col">User</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><input type="text"></td>
|
||||
<td>
|
||||
<select>
|
||||
<option value="ephemeral">ephemeral</option>
|
||||
<option value="target">target</option>
|
||||
</select>
|
||||
</td>
|
||||
<td><input type="text"></td>
|
||||
<td><input type="text"></td>
|
||||
<td>
|
||||
<button class="btn btn-success" onclick="saveConfigDialog(this)">Save</button>
|
||||
<button class="btn btn-dark" onclick="closeDialog(this)">Cancel</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Credential details in accordion -->
|
||||
<button class="accordion">Credential</button>
|
||||
<div class="panel">
|
||||
<p>
|
||||
<table class="table" id="CredentialTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Location Of Origin</th>
|
||||
<th scope="col">Username</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
<button type="button" class="btn btn-info" id="CredentialBtn" onclick="addConfigModal(this)">Add</button>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- This is used by the credential add modal and is injected into the DOM but not displayed except by the popup -->
|
||||
<div id="CredentialModalTemplate" style="display:none">
|
||||
<h2>Add Credential</h2>
|
||||
<table class="table" id="CredentialAddTable">
|
||||
<thead>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col">User</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><input type="text"></td>
|
||||
<td><input type="text"></td>
|
||||
<td>
|
||||
<button class="btn btn-success" onclick="saveConfigDialog(this)">Save</button>
|
||||
<button class="btn btn-dark" onclick="closeDialog(this)">Cancel</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
73
internal/integrations/ctl/testdata/config.yaml
vendored
Normal file
73
internal/integrations/ctl/testdata/config.yaml
vendored
Normal file
@ -0,0 +1,73 @@
|
||||
apiVersion: airshipit.org/v1alpha1
|
||||
bootstrapInfo:
|
||||
default:
|
||||
builder:
|
||||
networkConfigFileName: network-config
|
||||
outputMetadataFileName: output-metadata.yaml
|
||||
userDataFileName: user-data
|
||||
container:
|
||||
containerRuntime: docker
|
||||
image: quay.io/airshipit/isogen:latest-debian_stable
|
||||
volume: /srv/iso:/config
|
||||
remoteDirect:
|
||||
isoUrl: http://localhost:8099/debian-custom.iso
|
||||
dummy_bootstrap_config:
|
||||
builder:
|
||||
networkConfigFileName: netconfig
|
||||
outputMetadataFileName: output-metadata.yaml
|
||||
userDataFileName: user-data
|
||||
container:
|
||||
containerRuntime: docker
|
||||
image: dummy_image:dummy_tag
|
||||
volume: /dummy:dummy
|
||||
clusters:
|
||||
kubernetes:
|
||||
clusterType:
|
||||
target:
|
||||
bootstrapInfo: default
|
||||
clusterKubeconf: kubernetes_target
|
||||
managementConfiguration: default
|
||||
contexts:
|
||||
admin@kubernetes:
|
||||
contextKubeconf: kubernetes_target
|
||||
currentContext: admin@kubernetes
|
||||
kind: Config
|
||||
managementConfiguration:
|
||||
default:
|
||||
insecure: true
|
||||
systemActionRetries: 30
|
||||
systemRebootDelay: 30
|
||||
type: redfish
|
||||
dummy_management_config:
|
||||
insecure: true
|
||||
type: redfish
|
||||
manifests:
|
||||
default:
|
||||
primaryRepositoryName: primary
|
||||
repositories:
|
||||
primary:
|
||||
checkout:
|
||||
branch: master
|
||||
commitHash: ""
|
||||
force: false
|
||||
tag: ""
|
||||
url: https://opendev.org/airship/treasuremap
|
||||
subPath: treasuremap/manifests/site
|
||||
targetPath: /tmp/default
|
||||
dummy_manifest:
|
||||
primaryRepositoryName: primary
|
||||
repositories:
|
||||
primary:
|
||||
auth:
|
||||
sshKey: testdata/test-key.pem
|
||||
type: ssh-key
|
||||
checkout:
|
||||
branch: ""
|
||||
commitHash: ""
|
||||
force: false
|
||||
tag: v1.0.1
|
||||
url: http://dummy.url.com/manifests.git
|
||||
subPath: manifests/site/test-site
|
||||
targetPath: /var/tmp/
|
||||
users:
|
||||
admin: {}
|
5
internal/integrations/ctl/testdata/document.html
vendored
Normal file
5
internal/integrations/ctl/testdata/document.html
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
<h1>Airship CTL Document Base Information</h1>
|
||||
<p>Version: devel</p>
|
||||
|
||||
<h2>Document Pull</h2>
|
||||
<button type="button" class="btn btn-info" id="DocPullBtn" onclick="documentAction(this)" style="width: 150px;">Document Pull</button>
|
19
internal/integrations/ctl/testdata/kubeconfig.yaml
vendored
Normal file
19
internal/integrations/ctl/testdata/kubeconfig.yaml
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
apiVersion: v1
|
||||
clusters:
|
||||
- cluster:
|
||||
certificate-authority: pki/cluster-ca.pem
|
||||
server: https://10.0.0.1:6553
|
||||
name: kubernetes_target
|
||||
contexts:
|
||||
- context:
|
||||
cluster: kubernetes_target
|
||||
user: admin
|
||||
name: admin@kubernetes
|
||||
current-context: admin@kubernetes
|
||||
kind: Config
|
||||
preferences: {}
|
||||
users:
|
||||
- name: admin
|
||||
user:
|
||||
client-certificate: pki/admin.pem
|
||||
client-key: pki/admin-key.pem
|
76
internal/webservice/alerts_test.go
Normal file
76
internal/webservice/alerts_test.go
Normal file
@ -0,0 +1,76 @@
|
||||
/*
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package webservice
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"opendev.org/airship/airshipui/internal/configs"
|
||||
)
|
||||
|
||||
func TestSendAlert(t *testing.T) {
|
||||
client, err := NewTestClient()
|
||||
require.NoError(t, err)
|
||||
defer client.Close()
|
||||
|
||||
// construct and send alert from server to client
|
||||
SendAlert(configs.Error, "Test Alert", true)
|
||||
|
||||
var response configs.WsMessage
|
||||
err = client.ReadJSON(&response)
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := configs.WsMessage{
|
||||
Type: configs.Alert,
|
||||
Component: configs.Error,
|
||||
Message: "Test Alert",
|
||||
Fade: true,
|
||||
// don't fail on timestamp diff
|
||||
Timestamp: response.Timestamp,
|
||||
}
|
||||
|
||||
assert.Equal(t, expected, response)
|
||||
}
|
||||
|
||||
func TestSendAlertNoWebSocket(t *testing.T) {
|
||||
// test requires that ws == nil
|
||||
conn := ws
|
||||
ws = nil
|
||||
defer func() {
|
||||
ws = conn
|
||||
Alerts = nil
|
||||
}()
|
||||
|
||||
// queue should be empty
|
||||
Alerts = nil
|
||||
|
||||
SendAlert(configs.Info, "Test Alert", true)
|
||||
|
||||
// ws is nil, so the queue should now have 1 Alert
|
||||
assert.Len(t, Alerts, 1)
|
||||
|
||||
expected := configs.WsMessage{
|
||||
Type: configs.Alert,
|
||||
Component: configs.Info,
|
||||
Message: "Test Alert",
|
||||
Fade: true,
|
||||
// don't fail on timestamp diff
|
||||
Timestamp: Alerts[0].Timestamp,
|
||||
}
|
||||
|
||||
assert.Equal(t, expected, Alerts[0])
|
||||
}
|
@ -187,7 +187,7 @@ func WebServer() {
|
||||
|
||||
func clientInit(configs.WsMessage) configs.WsMessage {
|
||||
// if no auth method is supplied start with minimal functionality
|
||||
if len(configs.UIConfig.AuthMethod.URL) == 0 {
|
||||
if configs.UIConfig.AuthMethod == nil {
|
||||
isAuthenticated = true
|
||||
}
|
||||
|
||||
|
303
internal/webservice/server_test.go
Normal file
303
internal/webservice/server_test.go
Normal file
@ -0,0 +1,303 @@
|
||||
/*
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package webservice
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"opendev.org/airship/airshipui/internal/configs"
|
||||
"opendev.org/airship/airshipui/testutil"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const (
|
||||
serverAddr string = "localhost:8080"
|
||||
testBaremetalHTML string = "../integrations/ctl/testdata/baremetal.html"
|
||||
testDocumentHTML string = "../integrations/ctl/testdata/document.html"
|
||||
|
||||
// client messages
|
||||
initialize string = `{"type":"electron","component":"initialize"}`
|
||||
keepalive string = `{"type":"electron","component":"keepalive"}`
|
||||
unknownType string = `{"type":"fake_type","component":"initialize"}`
|
||||
unknownComponent string = `{"type":"electron","component":"fake_component"}`
|
||||
document string = `{"type":"airshipctl","component":"document","subcomponent":"getDefaults"}`
|
||||
baremetal string = `{"type":"airshipctl","component":"baremetal","subcomponent":"getDefaults"}`
|
||||
config string = `{"type":"airshipctl","component":"config","subcomponent":"getDefaults"}`
|
||||
)
|
||||
|
||||
func init() {
|
||||
go WebServer()
|
||||
}
|
||||
|
||||
func TestClientInit(t *testing.T) {
|
||||
client, err := NewTestClient()
|
||||
require.NoError(t, err)
|
||||
defer client.Close()
|
||||
|
||||
// simulate config provided by airshipui.json
|
||||
configs.UIConfig = testutil.DummyCompleteConfig()
|
||||
|
||||
// get server response to "initialize" message from client
|
||||
response, err := getResponse(client, initialize)
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := configs.WsMessage{
|
||||
Type: configs.Electron,
|
||||
Component: configs.Initialize,
|
||||
IsAuthenticated: false,
|
||||
Dashboards: []configs.Cluster{
|
||||
testutil.DummyClusterConfig(),
|
||||
},
|
||||
Plugins: []configs.Plugin{
|
||||
testutil.DummyPluginWithDashboardConfig(),
|
||||
testutil.DummyPluginNoDashboard(),
|
||||
},
|
||||
Authentication: testutil.DummyAuthMethodConfig(),
|
||||
// don't fail on timestamp diff
|
||||
Timestamp: response.Timestamp,
|
||||
}
|
||||
|
||||
assert.Equal(t, expected, response)
|
||||
}
|
||||
|
||||
func TestClientInitNoAuth(t *testing.T) {
|
||||
client, err := NewTestClient()
|
||||
require.NoError(t, err)
|
||||
defer client.Close()
|
||||
|
||||
// simulate config provided by airshipui.json
|
||||
configs.UIConfig = testutil.DummyConfigNoAuth()
|
||||
|
||||
isAuthenticated = false
|
||||
|
||||
response, err := getResponse(client, initialize)
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := configs.WsMessage{
|
||||
Type: configs.Electron,
|
||||
Component: configs.Initialize,
|
||||
// isAuthenticated should now be true in response
|
||||
IsAuthenticated: true,
|
||||
Dashboards: []configs.Cluster{
|
||||
testutil.DummyClusterConfig(),
|
||||
},
|
||||
Plugins: []configs.Plugin{
|
||||
testutil.DummyPluginWithDashboardConfig(),
|
||||
testutil.DummyPluginNoDashboard(),
|
||||
},
|
||||
// don't fail on timestamp diff
|
||||
Timestamp: response.Timestamp,
|
||||
}
|
||||
|
||||
assert.Equal(t, expected, response)
|
||||
}
|
||||
|
||||
func TestKeepalive(t *testing.T) {
|
||||
client, err := NewTestClient()
|
||||
require.NoError(t, err)
|
||||
defer client.Close()
|
||||
|
||||
// get server response to "keepalive" message from client
|
||||
response, err := getResponse(client, keepalive)
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := configs.WsMessage{
|
||||
Type: configs.Electron,
|
||||
Component: configs.Keepalive,
|
||||
// don't fail on timestamp diff
|
||||
Timestamp: response.Timestamp,
|
||||
}
|
||||
|
||||
assert.Equal(t, expected, response)
|
||||
}
|
||||
|
||||
func TestUnknownType(t *testing.T) {
|
||||
client, err := NewTestClient()
|
||||
require.NoError(t, err)
|
||||
defer client.Close()
|
||||
|
||||
response, err := getResponse(client, unknownType)
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := configs.WsMessage{
|
||||
Type: "fake_type",
|
||||
Component: configs.Initialize,
|
||||
// don't fail on timestamp diff
|
||||
Timestamp: response.Timestamp,
|
||||
Error: "Requested type: fake_type, not found",
|
||||
}
|
||||
|
||||
assert.Equal(t, expected, response)
|
||||
}
|
||||
|
||||
func TestUnknownComponent(t *testing.T) {
|
||||
client, err := NewTestClient()
|
||||
require.NoError(t, err)
|
||||
defer client.Close()
|
||||
|
||||
response, err := getResponse(client, unknownComponent)
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := configs.WsMessage{
|
||||
Type: configs.Electron,
|
||||
Component: "fake_component",
|
||||
// don't fail on timestamp diff
|
||||
Timestamp: response.Timestamp,
|
||||
Error: "Requested component: fake_component, not found",
|
||||
}
|
||||
|
||||
assert.Equal(t, expected, response)
|
||||
}
|
||||
|
||||
func TestHandleAuth(t *testing.T) {
|
||||
client, err := NewTestClient()
|
||||
require.NoError(t, err)
|
||||
defer client.Close()
|
||||
|
||||
isAuthenticated = false
|
||||
|
||||
// trigger web server's handleAuth function
|
||||
_, err = http.Get("http://localhost:8080/auth")
|
||||
require.NoError(t, err)
|
||||
|
||||
var response configs.WsMessage
|
||||
err = client.ReadJSON(&response)
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := configs.WsMessage{
|
||||
Type: configs.Electron,
|
||||
Component: configs.Authcomplete,
|
||||
// don't fail on timestamp diff
|
||||
Timestamp: response.Timestamp,
|
||||
}
|
||||
|
||||
// isAuthenticated should now be true after auth complete
|
||||
assert.Equal(t, isAuthenticated, true)
|
||||
assert.Equal(t, expected, response)
|
||||
}
|
||||
|
||||
func TestHandleDocumentRequest(t *testing.T) {
|
||||
client, err := NewTestClient()
|
||||
require.NoError(t, err)
|
||||
defer client.Close()
|
||||
|
||||
expectedHTML, err := ioutil.ReadFile(testDocumentHTML)
|
||||
require.NoError(t, err)
|
||||
|
||||
response, err := getResponse(client, document)
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := configs.WsMessage{
|
||||
Type: configs.AirshipCTL,
|
||||
Component: configs.Document,
|
||||
SubComponent: configs.GetDefaults,
|
||||
HTML: string(expectedHTML),
|
||||
// don't fail on timestamp diff
|
||||
Timestamp: response.Timestamp,
|
||||
}
|
||||
|
||||
assert.Equal(t, expected, response)
|
||||
}
|
||||
|
||||
func TestHandleBaremetalRequest(t *testing.T) {
|
||||
client, err := NewTestClient()
|
||||
require.NoError(t, err)
|
||||
defer client.Close()
|
||||
|
||||
expectedHTML, err := ioutil.ReadFile(testBaremetalHTML)
|
||||
require.NoError(t, err)
|
||||
|
||||
response, err := getResponse(client, baremetal)
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := configs.WsMessage{
|
||||
Type: configs.AirshipCTL,
|
||||
Component: configs.Baremetal,
|
||||
SubComponent: configs.GetDefaults,
|
||||
HTML: string(expectedHTML),
|
||||
// don't fail on timestamp diff
|
||||
Timestamp: response.Timestamp,
|
||||
}
|
||||
|
||||
assert.Equal(t, expected, response)
|
||||
}
|
||||
|
||||
func TestHandleConfigRequest(t *testing.T) {
|
||||
client, err := NewTestClient()
|
||||
require.NoError(t, err)
|
||||
defer client.Close()
|
||||
|
||||
response, err := getResponse(client, config)
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := configs.WsMessage{
|
||||
Type: configs.AirshipCTL,
|
||||
Component: configs.CTLConfig,
|
||||
SubComponent: configs.GetDefaults,
|
||||
}
|
||||
|
||||
assert.Equal(t, expected.Type, response.Type)
|
||||
assert.Equal(t, expected.Component, response.Component)
|
||||
assert.Equal(t, expected.SubComponent, response.SubComponent)
|
||||
|
||||
// NOTE(mfuller): integrations/ctl 'client' gets initialized
|
||||
// *before* any env vars can be set here in tests, so client
|
||||
// will always be initialized with default config file locations.
|
||||
// Client is not exported, so we can't set it directly here. We'll
|
||||
// simply make sure there's no Error value and that HTML has
|
||||
// len > 0. Full testing of this response is covered in the
|
||||
// integrations/ctl tests.
|
||||
|
||||
assert.Len(t, response.Error, 0)
|
||||
assert.Greater(t, len(response.HTML), 0)
|
||||
}
|
||||
|
||||
func getResponse(client *websocket.Conn, message string) (configs.WsMessage, error) {
|
||||
err := client.WriteJSON(json.RawMessage(message))
|
||||
if err != nil {
|
||||
return configs.WsMessage{}, err
|
||||
}
|
||||
|
||||
var response configs.WsMessage
|
||||
err = client.ReadJSON(&response)
|
||||
if err != nil {
|
||||
return configs.WsMessage{}, err
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func NewTestClient() (*websocket.Conn, error) {
|
||||
var err error
|
||||
var client *websocket.Conn
|
||||
u := url.URL{Scheme: "ws", Host: serverAddr, Path: "/ws"}
|
||||
// allow multiple attempts to establish websocket in case server isn't ready
|
||||
for i := 0; i < 5; i++ {
|
||||
client, _, err = websocket.DefaultDialer.Dial(u.String(), nil)
|
||||
if err == nil {
|
||||
return client, nil
|
||||
}
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
return nil, err
|
||||
}
|
131
testutil/testconfig.go
Normal file
131
testutil/testconfig.go
Normal file
@ -0,0 +1,131 @@
|
||||
/*
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package testutil
|
||||
|
||||
import "opendev.org/airship/airshipui/internal/configs"
|
||||
|
||||
// DummyDashboardConfig returns a populated Dashboard struct
|
||||
func DummyDashboardConfig() configs.Dashboard {
|
||||
return configs.Dashboard{
|
||||
Name: "dummy_dashboard",
|
||||
Protocol: "http",
|
||||
Hostname: "dummyhost",
|
||||
Port: 80,
|
||||
Path: "fake/login/path",
|
||||
}
|
||||
}
|
||||
|
||||
// DummyPluginDashboardConfig returns a populated PluginDashboard struct
|
||||
func DummyPluginDashboardConfig() configs.PluginDashboard {
|
||||
return configs.PluginDashboard{
|
||||
Protocol: "http",
|
||||
FQDN: "localhost",
|
||||
Port: 80,
|
||||
Path: "index.html",
|
||||
}
|
||||
}
|
||||
|
||||
// DummyExecutableConfig returns a populated Executable struct
|
||||
func DummyExecutableConfig() configs.Executable {
|
||||
return configs.Executable{
|
||||
AutoStart: true,
|
||||
Filepath: "/fake/path/to/executable",
|
||||
Args: []string{
|
||||
"--fakeflag",
|
||||
"fakevalue",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DummyAuthMethodConfig returns a populated AuthMethod struct
|
||||
func DummyAuthMethodConfig() *configs.AuthMethod {
|
||||
return &configs.AuthMethod{
|
||||
URL: "http://fake.auth.method.com/auth",
|
||||
}
|
||||
}
|
||||
|
||||
// DummyPluginWithDashboardConfig returns a populated Plugin struct
|
||||
// with a populated PluginDashboard
|
||||
func DummyPluginWithDashboardConfig() configs.Plugin {
|
||||
d := DummyPluginDashboardConfig()
|
||||
e := DummyExecutableConfig()
|
||||
|
||||
return configs.Plugin{
|
||||
Name: "dummy_plugin_with_dash",
|
||||
Dashboard: &d,
|
||||
Executable: &e,
|
||||
}
|
||||
}
|
||||
|
||||
// DummyPluginNoDashboard returns a populated Plugin struct
|
||||
// but omits the optional PluginDashboard
|
||||
func DummyPluginNoDashboard() configs.Plugin {
|
||||
e := DummyExecutableConfig()
|
||||
|
||||
return configs.Plugin{
|
||||
Name: "dummy_plugin_no_dash",
|
||||
Executable: &e,
|
||||
}
|
||||
}
|
||||
|
||||
// DummyNamespaceConfig returns a populated Namespace struct with
|
||||
// a single Dashboard
|
||||
func DummyNamespaceConfig() configs.Namespace {
|
||||
d := DummyDashboardConfig()
|
||||
|
||||
return configs.Namespace{
|
||||
Name: "dummy_namespace",
|
||||
Dashboards: []configs.Dashboard{d},
|
||||
}
|
||||
}
|
||||
|
||||
// DummyClusterConfig returns a populated Cluster struct with
|
||||
// a single Namespace
|
||||
func DummyClusterConfig() configs.Cluster {
|
||||
n := DummyNamespaceConfig()
|
||||
|
||||
return configs.Cluster{
|
||||
Name: "dummy_cluster",
|
||||
BaseFqdn: "dummy.cluster.local",
|
||||
Namespaces: []configs.Namespace{n},
|
||||
}
|
||||
}
|
||||
|
||||
// DummyConfigNoAuth returns a populated Config struct but omits
|
||||
// the optional AuthMethod
|
||||
func DummyConfigNoAuth() configs.Config {
|
||||
p := DummyPluginWithDashboardConfig()
|
||||
pn := DummyPluginNoDashboard()
|
||||
c := DummyClusterConfig()
|
||||
|
||||
return configs.Config{
|
||||
Plugins: []configs.Plugin{p, pn},
|
||||
Clusters: []configs.Cluster{c},
|
||||
}
|
||||
}
|
||||
|
||||
// DummyCompleteConfig returns a fully populated Config struct
|
||||
func DummyCompleteConfig() configs.Config {
|
||||
a := DummyAuthMethodConfig()
|
||||
p := DummyPluginWithDashboardConfig()
|
||||
pn := DummyPluginNoDashboard()
|
||||
c := DummyClusterConfig()
|
||||
|
||||
return configs.Config{
|
||||
AuthMethod: a,
|
||||
Plugins: []configs.Plugin{p, pn},
|
||||
Clusters: []configs.Cluster{c},
|
||||
}
|
||||
}
|
@ -60,7 +60,7 @@ function addServiceDashboards(json) { // eslint-disable-line no-unused-vars
|
||||
function addPluginDashboards(json) { // eslint-disable-line no-unused-vars
|
||||
if (json !== undefined) {
|
||||
for (let i = 0; i < json.length; i++) {
|
||||
if (json[i].executable.autoStart && json[i].dashboard.fqdn !== undefined) {
|
||||
if (json[i].executable.autoStart && json[i].dashboard !== undefined) {
|
||||
let dash = json[i].dashboard;
|
||||
let url = `${dash.protocol}://${dash.fqdn}:${dash.port}/${dash.path || ""}`;
|
||||
addDashboard("PluginDropdown", json[i].name, url);
|
||||
|
6
web/package-lock.json
generated
6
web/package-lock.json
generated
@ -657,9 +657,9 @@
|
||||
}
|
||||
},
|
||||
"entities": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-2.0.2.tgz",
|
||||
"integrity": "sha512-dmD3AvJQBUjKpcNkoqr+x+IF0SdRtPz9Vk0uTy4yWqga9ibB6s4v++QFWNohjiUGoMlF552ZvNyXDxz5iW0qmw==",
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-2.0.3.tgz",
|
||||
"integrity": "sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ==",
|
||||
"dev": true
|
||||
},
|
||||
"env-paths": {
|
||||
|
Loading…
x
Reference in New Issue
Block a user