Merge "Add testing to airshipui"
This commit is contained in:
commit
afa71c0997
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