Skip to content

Commit

Permalink
feat(ui): bootstrap via UI #123
Browse files Browse the repository at this point in the history
  • Loading branch information
christophenne authored and kosmoz committed Feb 19, 2024
1 parent 7093e80 commit c8c0576
Show file tree
Hide file tree
Showing 13 changed files with 228 additions and 79 deletions.
3 changes: 2 additions & 1 deletion cmd/glasskube/cmd/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ var bootstrapCmd = &cobra.Command{
Args: cobra.NoArgs,
PreRun: cliutils.SetupClientContext(false),
Run: func(cmd *cobra.Command, args []string) {
cfg, _ := cliutils.RequireConfig(config.Kubeconfig)
client := bootstrap.NewBootstrapClient(
cliutils.RequireConfig(config.Kubeconfig),
cfg,
bootstrapCmdOptions.url,
cmd.Root().Version,
bootstrapCmdOptions.bootstrapType,
Expand Down
14 changes: 2 additions & 12 deletions cmd/glasskube/cmd/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"fmt"
"os"

"github.com/glasskube/glasskube/internal/bootstrap"
"github.com/glasskube/glasskube/internal/config"
"github.com/glasskube/glasskube/internal/web"
"github.com/glasskube/glasskube/pkg/client"
Expand All @@ -25,7 +24,7 @@ var serveCmd = &cobra.Command{
Args: cobra.ExactArgs(0),
Run: func(cmd *cobra.Command, args []string) {
var support *web.ServerConfigSupport
cfg, err := kubeconfig.New(config.Kubeconfig)
cfg, rawCfg, err := kubeconfig.New(config.Kubeconfig)
if err != nil {
support = &web.ServerConfigSupport{
KubeconfigError: err,
Expand All @@ -35,19 +34,10 @@ var serveCmd = &cobra.Command{
support.KubeconfigMissing = true
}
}
if support == nil {
isBootstrapped, err := bootstrap.IsBootstrapped(cmd.Context(), cfg)
if !isBootstrapped || err != nil {
support = &web.ServerConfigSupport{
BootstrapMissing: !isBootstrapped,
BootstrapCheckError: err,
}
}
}

var ctx = cmd.Context()
if cfg != nil {
ctx, err = client.SetupContext(ctx, cfg)
ctx, err = client.SetupContext(ctx, cfg, rawCfg)
if err != nil {
fmt.Fprintf(os.Stderr, "An error occurred starting the webserver:\n\n%v\n", err)
os.Exit(1)
Expand Down
10 changes: 6 additions & 4 deletions internal/cliutils/kubeconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"fmt"
"os"

"k8s.io/client-go/tools/clientcmd/api"

"github.com/glasskube/glasskube/pkg/kubeconfig"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
Expand All @@ -19,16 +21,16 @@ If you want to test glasskube locally, check out minikube: https://minikube.sigs
`, clientcmd.RecommendedHomeFile)
)

func RequireConfig(filePath string) *rest.Config {
if config, err := kubeconfig.New(filePath); err != nil {
func RequireConfig(filePath string) (*rest.Config, *api.Config) {
if config, rawConfig, err := kubeconfig.New(filePath); err != nil {
if clientcmd.IsEmptyConfig(err) {
fmt.Fprintln(os.Stderr, helpEmptyConfig)
} else {
fmt.Fprintf(os.Stderr, "Your kubeconfig file is invalid:\n\n%v\n", err)
}
os.Exit(1)
return nil
return nil, nil
} else {
return config
return config, rawConfig
}
}
6 changes: 3 additions & 3 deletions internal/cliutils/setupclientcontext.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"fmt"
"os"

"github.com/glasskube/glasskube/internal/bootstrap"
"github.com/glasskube/glasskube/pkg/bootstrap"

"github.com/glasskube/glasskube/internal/config"
"github.com/glasskube/glasskube/pkg/client"
Expand All @@ -13,11 +13,11 @@ import (

func SetupClientContext(requireBootstrapped bool) func(cmd *cobra.Command, args []string) {
return func(cmd *cobra.Command, args []string) {
cfg := RequireConfig(config.Kubeconfig)
cfg, rawCfg := RequireConfig(config.Kubeconfig)
if requireBootstrapped {
bootstrap.RequireBootstrapped(cmd.Context(), cfg)
}
if ctx, err := client.SetupContext(cmd.Context(), cfg); err != nil {
if ctx, err := client.SetupContext(cmd.Context(), cfg, rawCfg); err != nil {
fmt.Fprintf(os.Stderr, "Error setting up the client:\n\n%v\n", err)
os.Exit(1)
} else {
Expand Down
9 changes: 2 additions & 7 deletions internal/web/components/pkg_detail_btns/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"fmt"
"html/template"
"io"
"os"

"github.com/glasskube/glasskube/api/v1alpha1"
"github.com/glasskube/glasskube/pkg/client"
Expand All @@ -28,19 +27,15 @@ func getSwap(id string) string {
return fmt.Sprintf("outerHTML:#%s", id)
}

func Render(w io.Writer, tmpl *template.Template, pkgName string, status *client.PackageStatus, manifest *v1alpha1.PackageManifest) {
func Render(w io.Writer, tmpl *template.Template, pkgName string, status *client.PackageStatus, manifest *v1alpha1.PackageManifest) error {
id := getId(pkgName)
err := tmpl.ExecuteTemplate(w, TemplateId, &pkgDetailBtnsInput{
return tmpl.ExecuteTemplate(w, TemplateId, &pkgDetailBtnsInput{
ContainerId: id,
Swap: getSwap(id),
PackageName: pkgName,
Status: status,
Manifest: manifest,
})
if err != nil {
fmt.Fprintf(os.Stderr, "An error occurred rendering %v for %v: \n%v\n"+
"This is most likely a BUG!", TemplateId, pkgName, err)
}
}

func ForPkgDetailBtns(pkgName string, status *client.PackageStatus, manifest *v1alpha1.PackageManifest) *pkgDetailBtnsInput {
Expand Down
9 changes: 2 additions & 7 deletions internal/web/components/pkg_overview_btn/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"fmt"
"html/template"
"io"
"os"

"github.com/glasskube/glasskube/api/v1alpha1"
"github.com/glasskube/glasskube/pkg/client"
Expand All @@ -25,19 +24,15 @@ func getButtonId(pkgName string) string {
return fmt.Sprintf("%v-%v", TemplateId, pkgName)
}

func Render(w io.Writer, tmpl *template.Template, pkgName string, status *client.PackageStatus, manifest *v1alpha1.PackageManifest) {
func Render(w io.Writer, tmpl *template.Template, pkgName string, status *client.PackageStatus, manifest *v1alpha1.PackageManifest) error {
buttonId := getButtonId(pkgName)
err := tmpl.ExecuteTemplate(w, TemplateId, &pkgOverviewBtnInput{
return tmpl.ExecuteTemplate(w, TemplateId, &pkgOverviewBtnInput{
ButtonId: buttonId,
Swap: fmt.Sprintf("outerHTML:#%s", buttonId),
PackageName: pkgName,
Status: status,
Manifest: manifest,
})
if err != nil {
fmt.Fprintf(os.Stderr, "An error occurred rendering %v for %v: \n%v\n"+
"This is most likely a BUG!", TemplateId, pkgName, err)
}
}

func ForPkgOverviewBtn(pkgTeaser *list.PackageTeaserWithStatus) *pkgOverviewBtnInput {
Expand Down
96 changes: 83 additions & 13 deletions internal/web/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ import (
"os"
"syscall"

"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd/api"

"github.com/glasskube/glasskube/internal/config"
"github.com/glasskube/glasskube/pkg/bootstrap"

"github.com/glasskube/glasskube/pkg/manifest"

"github.com/glasskube/glasskube/pkg/describe"
Expand Down Expand Up @@ -51,6 +57,8 @@ type server struct {
port int32
listener net.Listener
ctx context.Context
cfg *rest.Config
rawCfg *api.Config
support *ServerConfigSupport
pkgClient *client.PackageV1Alpha1Client
wsHub *WsHub
Expand All @@ -72,13 +80,19 @@ func (s *server) broadcastPkgStatusUpdate(
) {
go func() {
var bf bytes.Buffer
pkg_overview_btn.Render(&bf, pkgOverviewBtnTmpl, pkgName, status, manifest)
s.wsHub.Broadcast <- bf.Bytes()
err := pkg_overview_btn.Render(&bf, pkgOverviewBtnTmpl, pkgName, status, manifest)
checkTmplError(err, fmt.Sprintf("%s (%s)", pkg_overview_btn.TemplateId, pkgName))
if err == nil {
s.wsHub.Broadcast <- bf.Bytes()
}
}()
go func() {
var bf bytes.Buffer
pkg_detail_btns.Render(&bf, pkgDetailBtnsTmpl, pkgName, status, manifest)
s.wsHub.Broadcast <- bf.Bytes()
err := pkg_detail_btns.Render(&bf, pkgDetailBtnsTmpl, pkgName, status, manifest)
checkTmplError(err, fmt.Sprintf("%s (%s)", pkg_overview_btn.TemplateId, pkgName))
if err == nil {
s.wsHub.Broadcast <- bf.Bytes()
}
}()
}

Expand All @@ -87,6 +101,22 @@ func (s *server) Start(ctx context.Context, support *ServerConfigSupport) error
return errors.New("server is already listening")
}

s.cfg = client.ConfigFromContext(ctx)
s.rawCfg = client.RawConfigFromContext(ctx)

if support == nil {
isBootstrapped, err := bootstrap.IsBootstrapped(ctx, s.cfg)
if !isBootstrapped || err != nil {
support = &ServerConfigSupport{
BootstrapMissing: !isBootstrapped,
BootstrapCheckError: err,
}
}
if err != nil {
fmt.Fprintf(os.Stderr, "\nFailed to check whether Glasskube is bootstrapped: %v\n\n", err)
}
}

s.support = support
s.ctx = ctx
s.pkgClient = client.FromContext(ctx)
Expand All @@ -104,6 +134,7 @@ func (s *server) Start(ctx context.Context, support *ServerConfigSupport) error
router.Handle("/favicon.ico", fileServer)
router.HandleFunc("/ws", s.wsHub.handler)
router.HandleFunc("/support", s.supportPage)
router.HandleFunc("/bootstrap", s.bootstrapPage)
router.HandleFunc("/packages", s.packages)
router.HandleFunc("/packages/install", s.install)
router.HandleFunc("/packages/uninstall", s.uninstall)
Expand Down Expand Up @@ -216,9 +247,7 @@ func (s *server) packages(w http.ResponseWriter, r *http.Request) {
return
}
err = pkgsPageTmpl.Execute(w, packages)
if err != nil {
fmt.Fprintf(os.Stderr, "An error occurred rendering the response: \n%v\n", err)
}
checkTmplError(err, "packages")
}

func (s *server) packageDetail(w http.ResponseWriter, r *http.Request) {
Expand All @@ -237,22 +266,63 @@ func (s *server) packageDetail(w http.ResponseWriter, r *http.Request) {
"Status": status,
"Manifest": manifest,
})
if err != nil {
fmt.Fprintf(os.Stderr, "An error occurred rendering the package detail page of %v: \n%v\n", pkgName, err)
}
checkTmplError(err, fmt.Sprintf("package-detail (%s)", pkgName))
}

func (s *server) supportPage(w http.ResponseWriter, r *http.Request) {
if s.support != nil {
err := supportPageTmpl.Execute(w, s.support)
if err != nil {
fmt.Fprintf(os.Stderr, "An error occurred rendering the support page: \n%v\n", err)
if s.support.BootstrapMissing {
http.Redirect(w, r, "/bootstrap", http.StatusFound)
return
}
err := supportPageTmpl.Execute(w, s.support)
checkTmplError(err, "support")
} else {
http.Redirect(w, r, "/", http.StatusFound)
}
}

func (s *server) bootstrapPage(w http.ResponseWriter, r *http.Request) {
if r.Method == "POST" {
client := bootstrap.NewBootstrapClient(
s.cfg,
"",
config.Version,
bootstrap.BootstrapTypeAio,
)
if err := client.Bootstrap(s.ctx); err != nil {
fmt.Fprintf(os.Stderr, "\nAn error occurred during bootstrap:\n%v\n", err)
err := bootstrapPageTmpl.ExecuteTemplate(w, "bootstrap-failure", nil)
checkTmplError(err, "bootstrap-failure")
} else {
s.support = nil
err := bootstrapPageTmpl.ExecuteTemplate(w, "bootstrap-success", nil)
checkTmplError(err, "bootstrap-success")
}
} else {
if s.support != nil && s.support.BootstrapMissing {
// try to validate bootstrap again, if it failed last time:
if s.support.BootstrapCheckError != nil {
isBootstrapped, err := bootstrap.IsBootstrapped(s.ctx, s.cfg)
if err != nil {
fmt.Fprintf(os.Stderr, "\nFailed to check whether Glasskube is bootstrapped: %v\n\n", err)
} else if isBootstrapped {
s.support = nil
http.Redirect(w, r, "/", http.StatusFound)
return
}
}
err := bootstrapPageTmpl.Execute(w, &map[string]any{
"Support": s.support,
"CurrentContext": s.rawCfg.CurrentContext,
})
checkTmplError(err, "bootstrap")
} else {
http.Redirect(w, r, "/", http.StatusFound)
}
}
}

func isPortConflictError(err error) bool {
if opErr, ok := err.(*net.OpError); ok {
if osErr, ok := opErr.Err.(*os.SyscallError); ok {
Expand Down
11 changes: 11 additions & 0 deletions internal/web/templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package web
import (
"fmt"
"html/template"
"os"
"path"

"github.com/glasskube/glasskube/internal/web/components/pkg_detail_btns"
Expand All @@ -14,6 +15,7 @@ var (
pkgsPageTmpl *template.Template
pkgPageTmpl *template.Template
supportPageTmpl *template.Template
bootstrapPageTmpl *template.Template
pkgOverviewBtnTmpl *template.Template
pkgDetailBtnsTmpl *template.Template
templatesDir = "templates"
Expand All @@ -40,8 +42,17 @@ func loadTemplates() {
ParseFS(embededFs, path.Join(pagesDir, "package.html"), path.Join(componentsDir, "*.html")))
supportPageTmpl = template.Must(template.Must(baseTemplate.Clone()).
ParseFS(embededFs, path.Join(pagesDir, "support.html"), path.Join(componentsDir, "*.html")))
bootstrapPageTmpl = template.Must(template.Must(baseTemplate.Clone()).
ParseFS(embededFs, path.Join(pagesDir, "bootstrap.html"), path.Join(componentsDir, "*.html")))
pkgOverviewBtnTmpl = template.Must(template.New(pkg_overview_btn.TemplateId).
ParseFS(embededFs, path.Join(componentsDir, "pkg-overview-btn.html")))
pkgDetailBtnsTmpl = template.Must(template.New(pkg_detail_btns.TemplateId).
ParseFS(embededFs, path.Join(componentsDir, "pkg-detail-btns.html")))
}

func checkTmplError(e error, tmplName string) {
if e != nil {
fmt.Fprintf(os.Stderr, "\nUnexpected error rendering %v: %v\n – This is most likely a BUG – "+
"Please report it here: https://github.com/glasskube/glasskube\n\n", tmplName, e)
}
}
Loading

0 comments on commit c8c0576

Please sign in to comment.