Log panel for arbitrary raw log messages

This change does the following:
1.  Break the circular reference between CTL and the webservice
2.  Establishes the log panel at the bottom of the screen
so that raw log messages can be show.
3.  Transfers the CTL logs to the UI by overwriting the CTL
logger.  This requires a new CTL client each transaction
4.  Adds in net/http specific logging for the backend

Change-Id: If7b01426c112669a11ffe8132f2cff59a4635db4
This commit is contained in:
Schiefelbein, Andrew 2020-08-25 14:26:39 -05:00
parent b7260326eb
commit bce4060414
15 changed files with 239 additions and 70 deletions

View File

@ -65,3 +65,28 @@
.h3 { .h3 {
font-size: 12px; font-size: 12px;
} }
.logs-action-buttons {
padding-bottom: 20px;
}
.logs-headers-align .mat-expansion-panel-header-title,
.logs-headers-align .mat-expansion-panel-header-description {
flex-basis: 0;
}
.logs-headers-align .mat-expansion-panel-header-description {
justify-content: space-between;
align-items: center;
}
.logs-headers-align .mat-form-field + .mat-form-field {
margin-left: 8px;
}
.log-panel {
height: 20vh !important;
overflow: scroll;
display: flex;
flex-direction: column-reverse;
}

View File

@ -61,6 +61,16 @@
</mat-toolbar> </mat-toolbar>
<router-outlet></router-outlet> <router-outlet></router-outlet>
<span class="page-body"></span> <span class="page-body"></span>
<mat-accordion class="logs-headers-align" style="display:none" id="logAccordion">
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-title>
Logs
</mat-panel-title>
</mat-expansion-panel-header>
<div id="logPanel" class="log-panel"></div>
</mat-expansion-panel>
</mat-accordion>
<mat-toolbar class="toolbar-footer"> <mat-toolbar class="toolbar-footer">
<h3>Airship UI &copy; {{ this.currentYear }}</h3> <h3>Airship UI &copy; {{ this.currentYear }}</h3>
<span class="spacer"></span> <span class="spacer"></span>

View File

@ -1,4 +1,5 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit, ViewChild } from '@angular/core';
import { MatAccordion } from '@angular/material/expansion';
import { environment } from 'src/environments/environment'; import { environment } from 'src/environments/environment';
import { IconService } from 'src/services/icon/icon.service'; import { IconService } from 'src/services/icon/icon.service';
import { WebsocketService } from 'src/services/websocket/websocket.service'; import { WebsocketService } from 'src/services/websocket/websocket.service';
@ -15,6 +16,8 @@ import { AuthGuard } from 'src/services/auth-guard/auth-guard.service';
}) })
export class AppComponent implements OnInit, WSReceiver { export class AppComponent implements OnInit, WSReceiver {
@ViewChild(MatAccordion) accordion: MatAccordion;
className = this.constructor.name; className = this.constructor.name;
type = 'ui'; type = 'ui';
component = 'any'; component = 'any';
@ -52,11 +55,22 @@ export class AppComponent implements OnInit, WSReceiver {
if (message.hasOwnProperty('error')) { if (message.hasOwnProperty('error')) {
this.websocketService.printIfToast(message); this.websocketService.printIfToast(message);
} else { } else {
if (message.hasOwnProperty('dashboards')) { switch (message.component) {
this.updateDashboards(message.dashboards); case 'log':
} else { Log.Debug(new LogMessage('Log message received in app', this.className, message));
// TODO (aschiefe): determine what should be notifications and what should be 86ed const panel = document.getElementById('logPanel');
Log.Debug(new LogMessage('Message received in app', this.className, message)); panel.appendChild(document.createTextNode(message.message));
panel.appendChild(document.createElement('br'));
break;
case 'initialize':
Log.Debug(new LogMessage('Initialize message received in app', this.className, message));
if (message.hasOwnProperty('dashboards')) {
this.updateDashboards(message.dashboards);
}
break;
default:
Log.Debug(new LogMessage('Uncategorized message received in app', this.className, message));
break;
} }
} }
} }

View File

@ -1,7 +1,7 @@
import { async, TestBed } from '@angular/core/testing'; import { async, TestBed } from '@angular/core/testing';
import { AuthGuard } from './auth-guard.service'; import { AuthGuard } from './auth-guard.service';
import { RouterTestingModule } from '@angular/router/testing'; import { RouterTestingModule } from '@angular/router/testing';
import {ToastrModule} from 'ngx-toastr'; import { ToastrModule } from 'ngx-toastr';
describe('AuthGuardService', () => { describe('AuthGuardService', () => {
let service: AuthGuard; let service: AuthGuard;

View File

@ -1,9 +1,9 @@
import { Injectable, OnDestroy } from '@angular/core'; import { Injectable } from '@angular/core';
import { Router, CanActivate, Event as RouterEvent, NavigationStart, NavigationEnd, NavigationCancel, NavigationError } from '@angular/router'; import { Router, CanActivate, Event as RouterEvent, NavigationStart, NavigationEnd, NavigationCancel, NavigationError } from '@angular/router';
import { Log } from 'src/services/log/log.service'; import { Log } from 'src/services/log/log.service';
import { LogMessage } from 'src/services/log/log-message'; import { LogMessage } from 'src/services/log/log-message';
import { WebsocketService } from 'src/services/websocket/websocket.service'; import { WebsocketService } from 'src/services/websocket/websocket.service';
import { WSReceiver, WebsocketMessage, Authentication } from 'src/services/websocket/websocket.models'; import { WSReceiver, WebsocketMessage } from 'src/services/websocket/websocket.models';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -28,10 +28,23 @@ export class AuthGuard implements WSReceiver, CanActivate {
// blank out the local storage so we can't get re authenticate // blank out the local storage so we can't get re authenticate
localStorage.removeItem('airshipUI-token'); localStorage.removeItem('airshipUI-token');
// turn off the log panel, no logs for you!
AuthGuard.toggleLogPanel(false);
// best to begin at the beginning so send the user back to /login // best to begin at the beginning so send the user back to /login
this.router.navigate(['/login']); this.router.navigate(['/login']);
} }
// flip the log panel according to where we are in the world
public static toggleLogPanel(authenticated): void {
const accordion = document.getElementById('logAccordion');
if (authenticated && accordion.style.display === 'none') {
accordion.style.display = '';
} else if (!authenticated) {
accordion.style.display = 'none';
}
}
constructor(private websocketService: WebsocketService, private router: Router) { constructor(private websocketService: WebsocketService, private router: Router) {
// create a static router so other components can access it if needs be // create a static router so other components can access it if needs be
AuthGuard.router = router; AuthGuard.router = router;
@ -101,6 +114,9 @@ export class AuthGuard implements WSReceiver, CanActivate {
// flip the link if we're in or out of the fold // flip the link if we're in or out of the fold
this.toggleAuthButton(authenticated); this.toggleAuthButton(authenticated);
// flip the visibility of the log panel depending on the disposition of the user
AuthGuard.toggleLogPanel(authenticated);
return authenticated; return authenticated;
} }
@ -115,6 +131,8 @@ export class AuthGuard implements WSReceiver, CanActivate {
} }
} }
// test the auth token to see if we can let the user see the page // test the auth token to see if we can let the user see the page
// TODO: maybe RBAC type of stuff may need to go here // TODO: maybe RBAC type of stuff may need to go here
private isAuthenticated(): boolean { private isAuthenticated(): boolean {

1
go.sum
View File

@ -514,6 +514,7 @@ github.com/gophercloud/gophercloud v0.6.0/go.mod h1:GICNByuaEBibcjmjvI7QvYJSZEbG
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20191106031601-ce3c9ade29de/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20191106031601-ce3c9ade29de/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33 h1:893HsJqtxp9z1SF76gg6hY70hRY1wVlTSnC/h1yUDCo=
github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=

View File

@ -22,6 +22,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"opendev.org/airship/airshipui/pkg/configs" "opendev.org/airship/airshipui/pkg/configs"
"opendev.org/airship/airshipui/pkg/ctl"
"opendev.org/airship/airshipui/pkg/log" "opendev.org/airship/airshipui/pkg/log"
"opendev.org/airship/airshipui/pkg/webservice" "opendev.org/airship/airshipui/pkg/webservice"
) )
@ -68,6 +69,10 @@ func launch(cmd *cobra.Command, args []string) {
log.Fatalf("config %s", err) log.Fatalf("config %s", err)
} }
// allows for the circular reference to the webservice package to be broken and allow for the sending
// of arbitrary messages from any package to the websocket
ctl.Init()
// start webservice and listen for the the ctl + c to exit // start webservice and listen for the the ctl + c to exit
c := make(chan os.Signal) c := make(chan os.Signal)
signal.Notify(c, os.Interrupt, syscall.SIGTERM) signal.Notify(c, os.Interrupt, syscall.SIGTERM)

View File

@ -96,6 +96,7 @@ const (
Baremetal WsComponentType = "baremetal" Baremetal WsComponentType = "baremetal"
Document WsComponentType = "document" Document WsComponentType = "document"
Auth WsComponentType = "auth" Auth WsComponentType = "auth"
Log WsComponentType = "log"
// auth sub components // auth sub components
Approved WsSubComponentType = "approved" Approved WsSubComponentType = "approved"

View File

@ -16,7 +16,10 @@ package ctl
import ( import (
"opendev.org/airship/airshipctl/pkg/environment" "opendev.org/airship/airshipctl/pkg/environment"
"opendev.org/airship/airshipctl/pkg/log"
"opendev.org/airship/airshipui/pkg/configs" "opendev.org/airship/airshipui/pkg/configs"
uiLog "opendev.org/airship/airshipui/pkg/log"
"opendev.org/airship/airshipui/pkg/webservice"
) )
// CTLFunctionMap is a function map for the CTL functions that is referenced in the webservice // CTLFunctionMap is a function map for the CTL functions that is referenced in the webservice
@ -34,22 +37,72 @@ type Client struct {
settings *environment.AirshipCTLSettings settings *environment.AirshipCTLSettings
} }
// NewClient initializes the airshipctl client for external usage. // LogInterceptor is just a struct to hold a pointer to the remote channel
func NewClient() *Client { type LogInterceptor struct {
response configs.WsMessage
}
// Init allows for the circular reference to the webservice package to be broken and allow for the sending
// of arbitrary messages from any package to the websocket
func Init() {
webservice.AppendToFunctionMap(configs.CTL, map[configs.WsComponentType]func(configs.WsMessage) configs.WsMessage{
configs.Baremetal: HandleBaremetalRequest,
configs.Document: HandleDocumentRequest,
})
}
// NewDefaultClient initializes the airshipctl client for external usage with default logging.
func NewDefaultClient() *Client {
settings := &environment.AirshipCTLSettings{} settings := &environment.AirshipCTLSettings{}
// ensure no error if airship config doesn't exist // ensure no error if airship config doesn't exist
settings.Create = true settings.Create = true
settings.InitConfig() settings.InitConfig()
c := &Client{ client := &Client{
settings: settings, settings: settings,
} }
// set verbosity to true // set verbosity to true
c.settings.Debug = true client.settings.Debug = true
return c return client
} }
// initialize the connection to airshipctl // NewClient initializes the airshipctl client for external usage with the logging overridden.
var c *Client = NewClient() func NewClient(request configs.WsMessage) *Client {
client := NewDefaultClient()
// init the interceptor to send messages to the UI
// TODO: Unsure how this will be handled with overlapping runs
log.Init(client.settings.Debug, NewLogInterceptor(request))
return client
}
// NewLogInterceptor will construct a channel writer for use with the logger
func NewLogInterceptor(request configs.WsMessage) *LogInterceptor {
// TODO: determine if we're only getting stub responses and if we don't have to pick things out that we care about
// This is a stub response used by the writer to kick out messages to the UI
response := configs.WsMessage{
Type: configs.UI,
Component: configs.Log,
SessionID: request.SessionID,
}
return &LogInterceptor{
response: response,
}
}
// Write satisfies the implementation of io.Writer.
// The intention is to hijack the log output for a progress bar on the UI
func (cw *LogInterceptor) Write(data []byte) (n int, err error) {
response := cw.response
response.Message = string(data)
if err = webservice.WebSocketSend(response); err != nil {
uiLog.Errorf("Error receiving / sending message: %s\n", err)
return len(data), err
}
return len(data), nil
}

View File

@ -36,8 +36,10 @@ func HandleBaremetalRequest(request configs.WsMessage) configs.WsMessage {
switch subComponent { switch subComponent {
case configs.GenerateISO: case configs.GenerateISO:
// since this is long running cache it up // since this is long running cache it up
// TODO: Test before running the geniso
runningRequests[subComponent] = true runningRequests[subComponent] = true
message, err = c.generateIso() client := NewClient(request)
message, err = client.generateIso()
// now that we're done forget we did anything // now that we're done forget we did anything
delete(runningRequests, subComponent) delete(runningRequests, subComponent)
default: default:

View File

@ -41,26 +41,29 @@ func HandleDocumentRequest(request configs.WsMessage) configs.WsMessage {
var err error var err error
var message string var message string
var id string var id string
client := NewClient(request)
switch request.SubComponent { switch request.SubComponent {
case configs.DocPull: case configs.DocPull:
message, err = c.docPull() message, err = client.docPull()
case configs.YamlWrite: case configs.YamlWrite:
id = request.ID id = request.ID
response.Name, response.YAML, err = writeYamlFile(id, request.YAML) response.Name, response.YAML, err = client.writeYamlFile(id, request.YAML)
message = fmt.Sprintf("File '%s' saved successfully", response.Name) message = fmt.Sprintf("File '%s' saved successfully", response.Name)
case configs.GetYaml: case configs.GetYaml:
id = request.ID id = request.ID
response.Name, response.YAML, err = getYaml(id) response.Name, response.YAML, err = client.getYaml(id)
case configs.GetPhaseTree: case configs.GetPhaseTree:
response.Data, err = GetPhaseTree() response.Data, err = client.GetPhaseTree()
case configs.GetPhaseDocuments: case configs.GetPhaseDocuments:
id = request.ID id = request.ID
response.Data, err = GetPhaseDocuments(request.ID) response.Data, err = GetPhaseDocuments(request.ID)
case configs.GetPhaseSourceFiles: case configs.GetPhaseSourceFiles:
id = request.ID id = request.ID
response.Data, err = GetPhaseSourceFiles(request.ID) response.Data, err = client.GetPhaseSourceFiles(request.ID)
case configs.GetTarget: case configs.GetTarget:
message = getTarget() message = client.getTarget()
default: default:
err = fmt.Errorf("Subcomponent %s not found", request.SubComponent) err = fmt.Errorf("Subcomponent %s not found", request.SubComponent)
} }
@ -75,7 +78,7 @@ func HandleDocumentRequest(request configs.WsMessage) configs.WsMessage {
return response return response
} }
func getTarget() string { func (c *Client) getTarget() string {
m, err := c.settings.Config.CurrentContextManifest() m, err := c.settings.Config.CurrentContextManifest()
if err != nil { if err != nil {
return "unknown" return "unknown"
@ -84,11 +87,11 @@ func getTarget() string {
return filepath.Join(m.TargetPath, m.SubPath) return filepath.Join(m.TargetPath, m.SubPath)
} }
func getYaml(id string) (string, string, error) { func (c *Client) getYaml(id string) (string, string, error) {
obj := index[id] obj := index[id]
switch t := obj.(type) { switch t := obj.(type) {
case string: case string:
return getFileYaml(t) return c.getFileYaml(t)
case document.Document: case document.Document:
return getDocumentYaml(t) return getDocumentYaml(t)
default: default:
@ -106,7 +109,7 @@ func getDocumentYaml(doc document.Document) (string, string, error) {
return title, base64.StdEncoding.EncodeToString(bytes), nil return title, base64.StdEncoding.EncodeToString(bytes), nil
} }
func getFileYaml(path string) (string, string, error) { func (c *Client) getFileYaml(path string) (string, string, error) {
ccm, err := c.settings.Config.CurrentContextManifest() ccm, err := c.settings.Config.CurrentContextManifest()
if err != nil { if err != nil {
return "", "", err return "", "", err
@ -139,7 +142,7 @@ func getFileYaml(path string) (string, string, error) {
return title, base64.StdEncoding.EncodeToString(bytes), nil return title, base64.StdEncoding.EncodeToString(bytes), nil
} }
func writeYamlFile(id, yaml64 string) (string, string, error) { func (c *Client) writeYamlFile(id, yaml64 string) (string, string, error) {
path, ok := index[id].(string) path, ok := index[id].(string)
if !ok { if !ok {
return "", "", fmt.Errorf("ID %s not found", id) return "", "", fmt.Errorf("ID %s not found", id)
@ -155,7 +158,7 @@ func writeYamlFile(id, yaml64 string) (string, string, error) {
return "", "", err return "", "", err
} }
return getFileYaml(path) return c.getFileYaml(path)
} }
func (c *Client) docPull() (string, error) { func (c *Client) docPull() (string, error) {

View File

@ -40,16 +40,21 @@ type PhaseObj struct {
} }
func buildPhaseIndex() map[string]PhaseObj { func buildPhaseIndex() map[string]PhaseObj {
client := NewDefaultClient()
return client.buildPhaseIndex()
}
func (client *Client) buildPhaseIndex() map[string]PhaseObj {
idx := map[string]PhaseObj{} idx := map[string]PhaseObj{}
// get target path from ctl settings // get target path from ctl settings
tp, err := c.settings.Config.CurrentContextTargetPath() tp, err := client.settings.Config.CurrentContextTargetPath()
if err != nil { if err != nil {
log.Errorf("Error building phase index: %s", err) log.Errorf("Error building phase index: %s", err)
return nil return nil
} }
cmd := phase.Cmd{AirshipCTLSettings: c.settings} cmd := phase.Cmd{AirshipCTLSettings: client.settings}
plan, err := cmd.Plan() plan, err := cmd.Plan()
if err != nil { if err != nil {
@ -81,41 +86,39 @@ func buildPhaseIndex() map[string]PhaseObj {
// GetPhaseTree builds the initial structure of the phase tree // GetPhaseTree builds the initial structure of the phase tree
// consisting of phase Groups and Phases. Individual phase source // consisting of phase Groups and Phases. Individual phase source
// files or rendered documents will be lazy loaded as needed // files or rendered documents will be lazy loaded as needed
func GetPhaseTree() ([]KustomNode, error) { func (client *Client) GetPhaseTree() ([]KustomNode, error) {
nodes := []KustomNode{} nodes := []KustomNode{}
grpMap := map[string][]KustomNode{} grpMap := map[string][]KustomNode{}
for id, po := range phaseIndex {
if phaseIndex != nil { pNode := KustomNode{
for id, po := range phaseIndex { ID: id,
pNode := KustomNode{ Name: fmt.Sprintf("Phase: %s", po.Name),
ID: id, IsPhaseNode: true,
Name: fmt.Sprintf("Phase: %s", po.Name),
IsPhaseNode: true,
}
children, err := GetPhaseSourceFiles(id)
if err != nil {
// TODO(mfuller): push an error to UI so it can be handled by
// toastr service, pending refactor of webservice and configs pkgs
log.Errorf("Error building tree for phase '%s': %s", po.Name, err)
pNode.HasError = true
} else {
pNode.Children = children
}
grpMap[po.Group] = append(grpMap[po.Group], pNode)
} }
for name, phases := range grpMap { children, err := client.GetPhaseSourceFiles(id)
gNode := KustomNode{ if err != nil {
ID: uuid.New().String(), // TODO(mfuller): push an error to UI so it can be handled by
Name: fmt.Sprintf("Group: %s", name), // toastr service, pending refactor of webservice and configs pkgs
Children: phases, log.Errorf("Error building tree for phase '%s': %s", po.Name, err)
} pNode.HasError = true
nodes = append(nodes, gNode) } else {
pNode.Children = children
} }
grpMap[po.Group] = append(grpMap[po.Group], pNode)
} }
for name, phases := range grpMap {
gNode := KustomNode{
ID: uuid.New().String(),
Name: fmt.Sprintf("Group: %s", name),
Children: phases,
}
nodes = append(nodes, gNode)
}
return nodes, nil return nodes, nil
} }
@ -201,7 +204,7 @@ func sortDocuments(path string) (map[string]map[string][]document.Document, erro
// all of the directories that will be traversed when kustomize // all of the directories that will be traversed when kustomize
// builds the document bundle. The tree hierarchy is: // builds the document bundle. The tree hierarchy is:
// kustomize "type" (like function) -> directory name -> file name // kustomize "type" (like function) -> directory name -> file name
func GetPhaseSourceFiles(id string) ([]KustomNode, error) { func (client *Client) GetPhaseSourceFiles(id string) ([]KustomNode, error) {
if index == nil { if index == nil {
index = map[string]interface{}{} index = map[string]interface{}{}
} }
@ -213,7 +216,7 @@ func GetPhaseSourceFiles(id string) ([]KustomNode, error) {
return nil, err return nil, err
} }
dm, err := createDirsMap(dirs) dm, err := client.createDirsMap(dirs)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -326,10 +329,10 @@ func getKustomizeDirs(entrypoint string) ([]string, error) {
} }
// helper function to group kustomize dirs by type (i.e. function, composite, etc) // helper function to group kustomize dirs by type (i.e. function, composite, etc)
func createDirsMap(dirs []string) (map[string][][]string, error) { func (client *Client) createDirsMap(dirs []string) (map[string][][]string, error) {
dm := map[string][][]string{} dm := map[string][][]string{}
tp, err := c.settings.Config.CurrentContextTargetPath() tp, err := client.settings.Config.CurrentContextTargetPath()
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -110,6 +110,11 @@ func Writer() io.Writer {
return airshipLog.Writer() return airshipLog.Writer()
} }
// Logger is used by things like net/http to overwrite their standard logging
func Logger() *log.Logger {
return airshipLog
}
func writeLog(level int, v ...interface{}) { func writeLog(level int, v ...interface{}) {
// determine if we need to display the logs // determine if we need to display the logs
if level <= LogLevel { if level <= LogLevel {

View File

@ -15,6 +15,7 @@
package webservice package webservice
import ( import (
"crypto/tls"
"net/http" "net/http"
"strconv" "strconv"
@ -54,6 +55,17 @@ func serveFile(w http.ResponseWriter, r *http.Request) {
} }
} }
// getCertificates returns the cert chain in a way that the net/http server struct expects
func getCertificates() []tls.Certificate {
cert, err := tls.LoadX509KeyPair(configs.UIConfig.WebService.PublicKey, configs.UIConfig.WebService.PrivateKey)
if err != nil {
log.Fatal("Unable to load certificates, check the definition in etc/airshipui.json")
}
var certSlice []tls.Certificate
certSlice = append(certSlice, cert)
return certSlice
}
// WebServer will run the handler functions for WebSockets // WebServer will run the handler functions for WebSockets
func WebServer() { func WebServer() {
webServerMux := http.NewServeMux() webServerMux := http.NewServeMux()
@ -72,8 +84,19 @@ func WebServer() {
// Calculate the address and start on the host and port specified in the config // 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) addr := configs.UIConfig.WebService.Host + ":" + strconv.Itoa(configs.UIConfig.WebService.Port)
log.Infof("Attempting to start webservice on %s", addr) log.Infof("Attempting to start webservice on %s", addr)
log.Fatal(http.ListenAndServeTLS(addr,
configs.UIConfig.WebService.PublicKey, // configure logging & TLS for the http server
configs.UIConfig.WebService.PrivateKey, server := &http.Server{
webServerMux)) Addr: addr,
TLSConfig: &tls.Config{
InsecureSkipVerify: false,
ServerName: configs.UIConfig.WebService.Host,
Certificates: getCertificates(),
},
Handler: webServerMux,
ErrorLog: log.Logger(),
}
// kick off the server, and good luck
log.Fatal(server.ListenAndServeTLS("", ""))
} }

View File

@ -24,11 +24,10 @@ import (
"github.com/google/uuid" "github.com/google/uuid"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
"opendev.org/airship/airshipui/pkg/configs" "opendev.org/airship/airshipui/pkg/configs"
"opendev.org/airship/airshipui/pkg/ctl"
"opendev.org/airship/airshipui/pkg/log" "opendev.org/airship/airshipui/pkg/log"
) )
// session is a struct to hold information about a given session // Session is a struct to hold information about a given session
type session struct { type session struct {
id string id string
jwt string jwt string
@ -52,7 +51,14 @@ var functionMap = map[configs.WsRequestType]map[configs.WsComponentType]func(con
configs.Keepalive: keepaliveReply, configs.Keepalive: keepaliveReply,
configs.Auth: handleAuth, configs.Auth: handleAuth,
}, },
configs.CTL: ctl.CTLFunctionMap, }
// AppendToFunctionMap allows us to break up the circular reference from the other packages
// It does however require them to implement an init function to append them
// TODO: maybe some form of an interface to enforce this may be necessary?
func AppendToFunctionMap(requestType configs.WsRequestType,
functions map[configs.WsComponentType]func(configs.WsMessage) configs.WsMessage) {
functionMap[requestType] = functions
} }
// handle the origin request & upgrade to websocket // handle the origin request & upgrade to websocket