Skip to content

Commit

Permalink
[web] Move web initialization to cloudprober init. (cloudprober#758)
Browse files Browse the repository at this point in the history
- [web] Move web initialization to cloudprober init.
- Keep the old web.Init() to avoid breaking existing custom probers
- Check for secrets in config dynamically instead of only in the beginning.
- Add tests for web package.
  • Loading branch information
manugarg authored Jun 2, 2024
1 parent f48cc7d commit b23c2eb
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 29 deletions.
12 changes: 12 additions & 0 deletions cloudprober.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import (
"github.com/cloudprober/cloudprober/prober"
"github.com/cloudprober/cloudprober/probes"
"github.com/cloudprober/cloudprober/surfacers"
"github.com/cloudprober/cloudprober/web"
"google.golang.org/grpc"
"google.golang.org/grpc/channelz/service"
"google.golang.org/grpc/credentials"
Expand Down Expand Up @@ -152,6 +153,17 @@ func Init() error {
}

func InitWithConfigSource(configSrc config.ConfigSource) error {
if err := initWithConfigSource(configSrc); err != nil {
return err
}
return web.InitWithDataFuncs(web.DataFuncs{
GetRawConfig: GetRawConfig,
GetParsedConfig: GetParsedConfig,
GetInfo: GetInfo,
})
}

func initWithConfigSource(configSrc config.ConfigSource) error {
// Return immediately if prober is already initialized.
cloudProber.Lock()
defer cloudProber.Unlock()
Expand Down
6 changes: 0 additions & 6 deletions cmd/cloudprober/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ import (
"github.com/cloudprober/cloudprober/config"
"github.com/cloudprober/cloudprober/config/runconfig"
"github.com/cloudprober/cloudprober/logger"
"github.com/cloudprober/cloudprober/web"
)

var (
Expand Down Expand Up @@ -152,11 +151,6 @@ func main() {
l.Criticalf("Error initializing cloudprober. Err: %v", err)
}

// web.Init sets up web UI for cloudprober.
if err := web.Init(); err != nil {
l.Criticalf("Error initializing web interface. Err: %v", err)
}

startCtx := context.Background()

if *stopTime == 0 {
Expand Down
6 changes: 0 additions & 6 deletions examples/extensions/myprober/myprober.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"github.com/cloudprober/cloudprober/examples/extensions/myprober/myprobe"
"github.com/cloudprober/cloudprober/examples/extensions/myprober/mytargets"
"github.com/cloudprober/cloudprober/logger"
"github.com/cloudprober/cloudprober/web"
)

func main() {
Expand All @@ -23,11 +22,6 @@ func main() {
log.Criticalf("Error initializing cloudprober. Err: %v", err)
}

// web.Init sets up web UI for cloudprober.
if err := web.Init(); err != nil {
log.Criticalf("Error initializing web interface. Err: %v", err)
}

cloudprober.Start(context.Background())

// Wait forever
Expand Down
49 changes: 32 additions & 17 deletions web/web.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2018 The Cloudprober Authors.
// Copyright 2018-2024 The Cloudprober Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -22,11 +22,11 @@ import (
"html/template"
"net/http"

"github.com/cloudprober/cloudprober"
"github.com/cloudprober/cloudprober/config"
"github.com/cloudprober/cloudprober/config/runconfig"
"github.com/cloudprober/cloudprober/internal/alerting"
"github.com/cloudprober/cloudprober/internal/servers"
"github.com/cloudprober/cloudprober/logger"
"github.com/cloudprober/cloudprober/probes"
"github.com/cloudprober/cloudprober/surfacers"
"github.com/cloudprober/cloudprober/web/resources"
Expand Down Expand Up @@ -71,10 +71,10 @@ func execTmpl(tmpl *template.Template, v interface{}) template.HTML {
}

// runningConfig returns cloudprober's running config.
func runningConfig() string {
func runningConfig(fn DataFuncs) string {
var statusBuf bytes.Buffer

probeInfo, surfacerInfo, serverInfo := cloudprober.GetInfo()
probeInfo, surfacerInfo, serverInfo := fn.GetInfo()

err := runningConfigTmpl.Execute(&statusBuf, struct {
ProbesStatus, ServersStatus, SurfacersStatus interface{}
Expand All @@ -99,8 +99,25 @@ func alertsState() string {
return fmt.Sprintf(htmlTmpl, resources.Header(), status)
}

// Init initializes cloudprober web interface handler.
type DataFuncs struct {
GetRawConfig func() string
GetParsedConfig func() string
GetInfo func() (map[string]*probes.ProbeInfo, []*surfacers.SurfacerInfo, []*servers.ServerInfo)
}

func Init() error {
l := logger.Logger{}
l.Warningf("web.Init is a no-op now. Web interface is now initialized by cloudprober.Init(), you don't need to initialize it explicitly.")
return nil
}

var secretConfigRunningMsg = `
<p>Config contains secrets. /config-running is not available.<br>
Visit <a href=/config-parsed>/config-parsed</a> to see the config.<p>
`

// InitWithDataFuncs initializes cloudprober web interface handler.
func InitWithDataFuncs(fn DataFuncs) error {
srvMux := runconfig.DefaultHTTPServeMux()
for _, url := range []string{"/config", "/config-running", "/static/"} {
if webutils.IsHandled(srvMux, url) {
Expand All @@ -109,24 +126,22 @@ func Init() error {
}

srvMux.HandleFunc("/config", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, cloudprober.GetRawConfig())
fmt.Fprint(w, fn.GetRawConfig())
})

parsedConfig := cloudprober.GetParsedConfig()
srvMux.HandleFunc("/config-parsed", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, cloudprober.GetParsedConfig())
fmt.Fprint(w, fn.GetParsedConfig())
})

var configRunning string
if !config.EnvRegex.MatchString(parsedConfig) {
configRunning = runningConfig()
} else {
configRunning = `
<p>Config contains secrets. /config-running is not available.<br>
Visit <a href=/config-parsed>/config-parsed</a> to see the config.<p>
`
}
srvMux.HandleFunc("/config-running", func(w http.ResponseWriter, r *http.Request) {
parsedConfig := fn.GetParsedConfig()
var configRunning string
if !config.EnvRegex.MatchString(parsedConfig) {
configRunning = runningConfig(fn)
} else {
configRunning = secretConfigRunningMsg
}

fmt.Fprint(w, configRunning)
})

Expand Down
112 changes: 112 additions & 0 deletions web/web_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// Copyright 2018-2024 The Cloudprober Authors.
//
// 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
//
// http://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 web provides web interface for cloudprober.
package web

import (
"io"
"net/http"
"net/http/httptest"
"testing"

"github.com/cloudprober/cloudprober/config/runconfig"
"github.com/cloudprober/cloudprober/internal/servers"
"github.com/cloudprober/cloudprober/probes"
"github.com/cloudprober/cloudprober/surfacers"
"github.com/stretchr/testify/assert"
)

func TestInitWithDataFuncs(t *testing.T) {
getInfo := func() (map[string]*probes.ProbeInfo, []*surfacers.SurfacerInfo, []*servers.ServerInfo) {

return nil, nil, nil
}

tests := []struct {
name string
dataFuncs DataFuncs
wantResp map[string]string
wantConfigRunning string
wantErr bool
}{
{
name: "base",
dataFuncs: DataFuncs{
GetRawConfig: func() string { return "raw-config" },
GetParsedConfig: func() string { return "parsed-config" },
GetInfo: getInfo,
},
wantResp: map[string]string{
"/config": "raw-config",
"/config-parsed": "parsed-config",
},
},
{
name: "with-secret",
dataFuncs: DataFuncs{
GetRawConfig: func() string { return "raw-config" },
GetParsedConfig: func() string { return "parsed-config **$password**" },
GetInfo: getInfo,
},
wantResp: map[string]string{
"/config": "raw-config",
"/config-parsed": "parsed-config **$password**",
},
wantConfigRunning: secretConfigRunningMsg,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
oldSrvMux := runconfig.DefaultHTTPServeMux()
defer runconfig.SetDefaultHTTPServeMux(oldSrvMux)
srvMux := http.NewServeMux()
runconfig.SetDefaultHTTPServeMux(srvMux)

httpSrv := httptest.NewServer(srvMux)
defer httpSrv.Close()

if err := InitWithDataFuncs(tt.dataFuncs); (err != nil) != tt.wantErr {
t.Errorf("InitWithDataFuncs() error = %v, wantErr %v", err, tt.wantErr)
}

client := httpSrv.Client()
if tt.wantConfigRunning == "" {
tt.wantConfigRunning = runningConfig(tt.dataFuncs)
}
tt.wantResp["/config-running"] = tt.wantConfigRunning
for path, wantResp := range tt.wantResp {
resp, err := client.Get(httpSrv.URL + path)
if err != nil {
t.Errorf("Error getting %s: %v", path, err)
continue
}
if resp.StatusCode != http.StatusOK {
t.Errorf("Got status code: %d, want: %d", resp.StatusCode, http.StatusOK)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
t.Errorf("Error reading response body: %v", err)
}
assert.Equal(t, wantResp, string(body), "response body")
}

httpSrv.Close()
})
}
}

func TestInit(t *testing.T) {
assert.Nil(t, Init(), "Init() should return nil")
}

0 comments on commit b23c2eb

Please sign in to comment.