Merge "Add TLS to the UI"
This commit is contained in:
commit
f00ae68202
4
.gitignore
vendored
4
.gitignore
vendored
@ -11,6 +11,10 @@ dist
|
||||
/build
|
||||
*.exe
|
||||
|
||||
# local conf files
|
||||
etc/*.pem
|
||||
etc/*.json
|
||||
|
||||
# Only exists if Bazel was run
|
||||
/bazel-out
|
||||
|
||||
|
@ -53,7 +53,7 @@ export class WebsocketService implements OnDestroy {
|
||||
this.ws.close();
|
||||
}
|
||||
|
||||
this.ws = new WebSocket('ws://localhost:8080/ws');
|
||||
this.ws = new WebSocket('wss://localhost:10443/ws');
|
||||
|
||||
this.ws.onmessage = (event) => {
|
||||
this.messageHandler(WebsocketService.messageToObject(event.data));
|
||||
|
15
etc/airshipui.json.example
Executable file
15
etc/airshipui.json.example
Executable file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"webservice": {
|
||||
"host": "<host>",
|
||||
"port": <port>,
|
||||
"publicKey": "<path>/<cert.pem>",
|
||||
"privateKey": "<path>/<key.pem>"
|
||||
},
|
||||
"dashboards": [
|
||||
{
|
||||
"name": "Dash",
|
||||
"baseURL": "https://<FQDN>",
|
||||
"path": "/<example>/<path>"
|
||||
}
|
||||
]
|
||||
}
|
@ -17,7 +17,6 @@ package commands
|
||||
import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
@ -38,11 +37,22 @@ var rootCmd = &cobra.Command{
|
||||
func init() {
|
||||
// Add a 'version' command, in addition to the '--version' option that is auto created
|
||||
rootCmd.AddCommand(newVersionCmd())
|
||||
|
||||
// Add the config file Flag
|
||||
rootCmd.Flags().StringVarP(
|
||||
&configs.UIConfigFile,
|
||||
"conf",
|
||||
"c",
|
||||
"etc/airshipui.json",
|
||||
"This will set the location of the conf file needed to start the UI",
|
||||
)
|
||||
|
||||
// Add the logging level flag
|
||||
rootCmd.Flags().IntVar(
|
||||
&log.LogLevel,
|
||||
"loglevel",
|
||||
6,
|
||||
"This well set the log level, anything at or below that level will be viewed, all others suppressed\n"+
|
||||
"This will set the log level, anything at or below that level will be viewed, all others suppressed\n"+
|
||||
" 6 -- Trace\n"+
|
||||
" 5 -- Debug\n"+
|
||||
" 4 -- Info\n"+
|
||||
@ -53,16 +63,9 @@ 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.Errorf("Error setting config path %s", err)
|
||||
}
|
||||
|
||||
// Read AirshipUI config file
|
||||
if err := configs.SetUIConfig(airshipUIConfigPath); err != nil {
|
||||
log.Errorf("config %s", err)
|
||||
if err := configs.SetUIConfig(); err != nil {
|
||||
log.Fatalf("config %s", err)
|
||||
}
|
||||
|
||||
// start webservice and listen for the the ctl + c to exit
|
||||
@ -83,12 +86,3 @@ 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
|
||||
}
|
||||
|
@ -15,20 +15,28 @@
|
||||
package configs
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
||||
"opendev.org/airship/airshipctl/pkg/config"
|
||||
"opendev.org/airship/airshipui/pkg/cryptography"
|
||||
"opendev.org/airship/airshipui/pkg/log"
|
||||
)
|
||||
|
||||
// variables related to UI config
|
||||
var (
|
||||
UIConfig Config
|
||||
UIConfig Config
|
||||
UIConfigFile string
|
||||
etcDir *string
|
||||
)
|
||||
|
||||
// Config basic structure to hold configuration params for Airship UI
|
||||
type Config struct {
|
||||
WebService *WebService `json:"webservice,omitempty"`
|
||||
AuthMethod *AuthMethod `json:"authMethod,omitempty"`
|
||||
Dashboards []Dashboard `json:"dashboards,omitempty"`
|
||||
}
|
||||
@ -40,6 +48,14 @@ type AuthMethod struct {
|
||||
URL string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
// WebService describes the things we need to know to start the web container
|
||||
type WebService struct {
|
||||
Host string `json:"host,omitempty"`
|
||||
Port int `json:"port,omitempty"`
|
||||
PublicKey string `json:"publicKey,omitempty"`
|
||||
PrivateKey string `json:"privateKey,omitempty"`
|
||||
}
|
||||
|
||||
// Dashboard structure
|
||||
type Dashboard struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
@ -111,11 +127,11 @@ type WsMessage struct {
|
||||
|
||||
// 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 SetUIConfig(filename string) error {
|
||||
f, err := os.Open(filename)
|
||||
// TODO: add watcher to the json file to reload conf on change (maybe not needed)
|
||||
func SetUIConfig() error {
|
||||
f, err := os.Open(UIConfigFile)
|
||||
if err != nil {
|
||||
return err
|
||||
return checkConfigs()
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
@ -129,5 +145,101 @@ func SetUIConfig(filename string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
return checkConfigs()
|
||||
}
|
||||
|
||||
func checkConfigs() error {
|
||||
if UIConfig.WebService == nil {
|
||||
log.Debug("No UI config found, generating ssl keys & host & port info")
|
||||
err := setEtcDir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
privateKeyFile := filepath.Join(*etcDir, "key.pem")
|
||||
publicKeyFile := filepath.Join(*etcDir, "cert.pem")
|
||||
|
||||
err = writeTestSSL(privateKeyFile, publicKeyFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
UIConfig.WebService = &WebService{
|
||||
Host: "localhost",
|
||||
Port: 10443,
|
||||
PublicKey: publicKeyFile,
|
||||
PrivateKey: privateKeyFile,
|
||||
}
|
||||
err = cryptography.TestCertValidity(publicKeyFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bytes, err := json.Marshal(UIConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ioutil.WriteFile(UIConfigFile, bytes, 0440)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeTestSSL(privateKeyFile string, publicKeyFile string) error {
|
||||
// get and write out private key
|
||||
log.Warnf("Generating private key %s. DO NOT USE THIS FOR PRODUCTION", privateKeyFile)
|
||||
privateKey, err := getAndWritePrivateKey(privateKeyFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// get and write out public key
|
||||
log.Warnf("Generating public key %s. DO NOT USE THIS FOR PRODUCTION", publicKeyFile)
|
||||
err = getAndWritePublicKey(publicKeyFile, privateKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getAndWritePrivateKey(fileName string) (*rsa.PrivateKey, error) {
|
||||
privateKeyBytes, privateKey, err := cryptography.GeneratePrivateKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = ioutil.WriteFile(fileName, privateKeyBytes, 0600)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return privateKey, nil
|
||||
}
|
||||
|
||||
func getAndWritePublicKey(fileName string, privateKey *rsa.PrivateKey) error {
|
||||
publicKeyBytes, err := cryptography.GeneratePublicKey(privateKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ioutil.WriteFile(fileName, publicKeyBytes, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setEtcDir() error {
|
||||
if etcDir == nil {
|
||||
dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dir, err = filepath.Abs(filepath.Join(path.Dir(dir), "etc"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
etcDir = &dir
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -1,64 +0,0 @@
|
||||
/*
|
||||
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
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const (
|
||||
fakeFile string = "/fake/config/path"
|
||||
testFile string = "testdata/airshipui.json"
|
||||
invalidTestFile string = "testdata/airshipui_invalid.json"
|
||||
)
|
||||
|
||||
// DummyDashboardsConfig returns an array of populated Dashboard structs
|
||||
func dummyDashboardsConfig() []Dashboard {
|
||||
return []Dashboard{
|
||||
{
|
||||
Name: "dummy_dashboard",
|
||||
BaseURL: "http://dummyhost",
|
||||
Path: "fake/login/path",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func dummyAuthMethodConfig() *AuthMethod {
|
||||
return &AuthMethod{
|
||||
URL: "http://fake.auth.method.com/auth",
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetUIConfig(t *testing.T) {
|
||||
conf := Config{
|
||||
Dashboards: dummyDashboardsConfig(),
|
||||
AuthMethod: dummyAuthMethodConfig(),
|
||||
}
|
||||
|
||||
err := SetUIConfig(testFile)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, conf, UIConfig)
|
||||
|
||||
err = SetUIConfig(invalidTestFile)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestFileNotFound(t *testing.T) {
|
||||
err := SetUIConfig(fakeFile)
|
||||
assert.Error(t, err)
|
||||
}
|
12
pkg/configs/testdata/airshipui.json
vendored
12
pkg/configs/testdata/airshipui.json
vendored
@ -1,12 +0,0 @@
|
||||
{
|
||||
"authMethod": {
|
||||
"url": "http://fake.auth.method.com/auth"
|
||||
},
|
||||
"dashboards": [
|
||||
{
|
||||
"name": "dummy_dashboard",
|
||||
"baseURL": "http://dummyhost",
|
||||
"path": "fake/login/path"
|
||||
}
|
||||
]
|
||||
}
|
6
pkg/configs/testdata/airshipui_invalid.json
vendored
6
pkg/configs/testdata/airshipui_invalid.json
vendored
@ -1,6 +0,0 @@
|
||||
{
|
||||
"authMethod": {
|
||||
"url": "http://fake.auth.method.com/auth"
|
||||
},
|
||||
"dashboards": [],
|
||||
}
|
73
pkg/configs/testdata/config.yaml
vendored
73
pkg/configs/testdata/config.yaml
vendored
@ -1,73 +0,0 @@
|
||||
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: {}
|
19
pkg/configs/testdata/kubeconfig.yaml
vendored
19
pkg/configs/testdata/kubeconfig.yaml
vendored
@ -1,19 +0,0 @@
|
||||
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
|
110
pkg/cryptography/cryptography.go
Executable file
110
pkg/cryptography/cryptography.go
Executable file
@ -0,0 +1,110 @@
|
||||
/*
|
||||
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 cryptography
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
"opendev.org/airship/airshipui/pkg/log"
|
||||
)
|
||||
|
||||
const (
|
||||
keySize = 4096 // 4k key
|
||||
)
|
||||
|
||||
// GeneratePrivateKey will a pem encoded private key and an rsa private key object
|
||||
func GeneratePrivateKey() ([]byte, *rsa.PrivateKey, error) {
|
||||
privateKey, err := rsa.GenerateKey(rand.Reader, keySize)
|
||||
if err != nil {
|
||||
log.Error("Problem generating private key", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
err = pem.Encode(buf, &pem.Block{
|
||||
Type: "RSA PRIVATE KEY",
|
||||
Bytes: x509.MarshalPKCS1PrivateKey(privateKey),
|
||||
})
|
||||
if err != nil {
|
||||
log.Error("Problem generating private key pem", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return buf.Bytes(), privateKey, nil
|
||||
}
|
||||
|
||||
// GeneratePublicKey will create a pem encoded cert
|
||||
func GeneratePublicKey(privateKey *rsa.PrivateKey) ([]byte, error) {
|
||||
template := generateCSR()
|
||||
derCert, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
err = pem.Encode(buf, &pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: derCert,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// generateCSR creates the base information needed to create the certificate
|
||||
func generateCSR() x509.Certificate {
|
||||
return x509.Certificate{
|
||||
SerialNumber: big.NewInt(1),
|
||||
Subject: pkix.Name{
|
||||
CommonName: "localhost",
|
||||
Organization: []string{"Airship UI"},
|
||||
},
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().AddDate(1, 0, 0),
|
||||
BasicConstraintsValid: true,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
|
||||
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
|
||||
}
|
||||
}
|
||||
|
||||
// TestCertValidity will check if the cert defined in the conf is not past its not after date
|
||||
func TestCertValidity(pemFile string) error {
|
||||
r, err := ioutil.ReadFile(pemFile)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
block, _ := pem.Decode(r)
|
||||
_, err = x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// calculate the validity of the cert
|
||||
// TODO: Add a cert check for time based validity here
|
||||
// fmt.Println(cert.NotAfter)
|
||||
return nil
|
||||
}
|
42
pkg/webservice/server_test.go → pkg/cryptography/cryptography_test.go
Normal file → Executable file
42
pkg/webservice/server_test.go → pkg/cryptography/cryptography_test.go
Normal file → Executable file
@ -12,31 +12,35 @@
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package webservice
|
||||
package cryptography
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const (
|
||||
serverAddr string = "localhost:8080"
|
||||
)
|
||||
|
||||
func init() {
|
||||
go WebServer()
|
||||
// wait for the webserver to come up
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
}
|
||||
|
||||
func TestRootURI(t *testing.T) {
|
||||
resp, err := http.Get("http://" + serverAddr)
|
||||
func TestGeneratePrivateKey(t *testing.T) {
|
||||
pem, key, err := GeneratePrivateKey()
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
// this will be not found because of where the webservice starts
|
||||
assert.Equal(t, http.StatusNotFound, resp.StatusCode)
|
||||
require.NotNil(t, key)
|
||||
require.NotNil(t, pem)
|
||||
}
|
||||
|
||||
func TestGeneratePublicKey(t *testing.T) {
|
||||
_, privateKey, err := GeneratePrivateKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
cert, err := GeneratePublicKey(privateKey)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, cert)
|
||||
}
|
||||
|
||||
func TestTestCertValidity(t *testing.T) {
|
||||
_, privateKey, err := GeneratePrivateKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
cert, err := GeneratePublicKey(privateKey)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, cert)
|
||||
}
|
@ -16,6 +16,7 @@ package webservice
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"opendev.org/airship/airshipui/pkg/configs"
|
||||
@ -85,7 +86,11 @@ func WebServer() {
|
||||
// start proxies for web based use
|
||||
startProxies()
|
||||
|
||||
// TODO: pull ports out into conf files
|
||||
log.Info("Attempting to start webservice on localhost:8080")
|
||||
log.Fatal(http.ListenAndServe(":8080", webServerMux))
|
||||
// Calculate the address and start on the host and port specified in the config
|
||||
addr := configs.UIConfig.WebService.Host + ":" + strconv.Itoa(configs.UIConfig.WebService.Port)
|
||||
log.Infof("Attempting to start webservice on %s", addr)
|
||||
log.Fatal(http.ListenAndServeTLS(addr,
|
||||
configs.UIConfig.WebService.PublicKey,
|
||||
configs.UIConfig.WebService.PrivateKey,
|
||||
webServerMux))
|
||||
}
|
||||
|
@ -1,120 +0,0 @@
|
||||
/*
|
||||
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"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"opendev.org/airship/airshipui/pkg/configs"
|
||||
"opendev.org/airship/airshipui/pkg/log"
|
||||
)
|
||||
|
||||
const (
|
||||
// client messages
|
||||
keepalive string = `{"type":"ui","component":"keepalive"}`
|
||||
unknownType string = `{"type":"fake_type","component":"initialize"}`
|
||||
unknownComponent string = `{"type":"ui","component":"fake_component"}`
|
||||
)
|
||||
|
||||
var client *websocket.Conn
|
||||
|
||||
func init() {
|
||||
u := url.URL{Scheme: "ws", Host: serverAddr, Path: "/ws"}
|
||||
var err error
|
||||
client, _, err = websocket.DefaultDialer.Dial(u.String(), nil)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
// get server response to "initialize" message from client which is sent by default
|
||||
var response configs.WsMessage
|
||||
err = client.ReadJSON(&response)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeepalive(t *testing.T) {
|
||||
// get server response to "keepalive" message from client
|
||||
response, err := getResponse(keepalive)
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := configs.WsMessage{
|
||||
SessionID: response.SessionID,
|
||||
Type: configs.UI,
|
||||
Component: configs.Keepalive,
|
||||
// don't fail on timestamp diff
|
||||
Timestamp: response.Timestamp,
|
||||
}
|
||||
|
||||
assert.Equal(t, expected, response)
|
||||
}
|
||||
|
||||
func TestUnknownType(t *testing.T) {
|
||||
response, err := getResponse(unknownType)
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := configs.WsMessage{
|
||||
SessionID: response.SessionID,
|
||||
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) {
|
||||
response, err := getResponse(unknownComponent)
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := configs.WsMessage{
|
||||
SessionID: response.SessionID,
|
||||
Type: configs.UI,
|
||||
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 getResponse(message string) (configs.WsMessage, error) {
|
||||
err := client.WriteJSON(json.RawMessage(message))
|
||||
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
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
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user