Skip to content

Commit

Permalink
Run frontend from go binary (#720)
Browse files Browse the repository at this point in the history
* Serve frontend from go

* Build UI static files

* Change metrics server port

* Add API config fields in deployment

* Fix serving API on 8080 port

* Add makefile rule to build ui

* Change the order of makefile rules

* eslint

* eslint

* Fix dockerfile copy

* Add doc describing how to deploy BTP Manager with UI

* Fix docs

* Add cleanup info, prerequisites info and fix formatting

* Typos

* Apply suggestions from doc review

Co-authored-by: Iwona Langer <[email protected]>

* Rename ui.md to 09-10-ui.md

---------

Co-authored-by: Iwona Langer <[email protected]>
  • Loading branch information
szwedm and IwonaLanger authored Jun 17, 2024
1 parent 7b96474 commit 5adbe6e
Show file tree
Hide file tree
Showing 12 changed files with 179 additions and 22 deletions.
14 changes: 14 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
# Build UI static files
FROM node:22.3.0 as ui-builder

WORKDIR /workspace

COPY ui/package.json ./
RUN npm install

COPY ui/ ./
RUN npm run build

# Build the manager binary
FROM golang:1.22.3 as builder

Expand All @@ -12,6 +23,9 @@ RUN go mod download
# Copy the go source
COPY . ./

# Copy UI static files
COPY --from=ui-builder /workspace/build ui/build

# Build
# the GOARCH has not a default value to allow the binary be built according to the host where the command
# was called. For example, if we call make docker-build in a local env which has the Apple Silicon M1 SO
Expand Down
7 changes: 5 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and
$(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..."

.PHONY: test
test: manifests kustomize generate fmt vet envtest ginkgo test-docs ## Run tests.
test: manifests kustomize generate ui-build fmt vet envtest ginkgo test-docs ## Run tests.
@. ./scripts/testing/set-env-vars.sh; \
go test -skip=TestAPIs ./... -timeout $(SUITE_TIMEOUT) -coverprofile cover.out -v; \
if [ "$(USE_EXISTING_CLUSTER)" == "true" ]; then $(GINKGO) controllers; else $(GINKGO) $(GINKGO_PARALLEL_FLAG) controllers; fi
Expand Down Expand Up @@ -192,7 +192,7 @@ go-lint-install: ## linter config in file at root of project -> '.golangci.yaml'
fi;

.PHONY: go-lint
go-lint: go-lint-install ## linter config in file at root of project -> '.golangci.yaml'
go-lint: ui-build go-lint-install ## linter config in file at root of project -> '.golangci.yaml'
golangci-lint run --timeout=$(GOLINT_TIMEOUT)

.PHONY: fix
Expand Down Expand Up @@ -224,3 +224,6 @@ clean-ports: ## Clean the ports
.PHONE: webapp-dev
webapp-dev: clean-ports webapp ## Run webapp for development

.PHONY: ui-build
ui-build: ## Build the UI
cd ui && npm install && npm run build
11 changes: 11 additions & 0 deletions config/manager/manager.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,20 @@ spec:
- --leader-elect
image: controller:latest
name: manager
env:
- name: API_PORT
value: "8080"
- name: API_READ_TIMEOUT
value: "30s"
- name: API_WRITE_TIMEOUT
value: "90s"
- name: API_IDLE_TIMEOUT
value: "120s"
ports:
- name: http
containerPort: 8080
- name: metrics
containerPort: 9090
securityContext:
allowPrivilegeEscalation: false
capabilities:
Expand Down
72 changes: 72 additions & 0 deletions docs/contributor/09-10-ui.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# BTP Manager User Interface

> [!WARNING]
> This feature is in the experimental stage and is not yet available in the main branch or official releases.
> Use the latest development image to test the UI: europe-docker.pkg.dev/kyma-project/dev/btp-manager:PR-720
## Prerequisites
For clusters different from Kyma (for example, k3d) you need to install the [prerequisites](../../deployments/prerequisites.yaml).
```shell
kubectl apply -f deployments/prerequisites.yaml
```


## Run BTP Manager with UI
Follow the steps below to run BTP Manager with UI:
1. Connect `kubectl` to your cluster by setting the **KUBECONFIG** environment variable.
```shell
export KUBECONFIG=<path-to-kubeconfig>
```
2. Make sure the `btp-operator` module is disabled and there are no existing BtpOperator custom resources (CRs) and deployments of BTP Manager and the SAP BTP service operator.
```shell
kubectl get btpoperators -A
kubectl get deployment -n kyma-system btp-manager-controller-manager
kubectl get deployment -n kyma-system sap-btp-operator-controller-manager
```
3. Clone the `btp-manager` repository and checkout to the `sm-integration` branch.
```shell
git clone https://github.com/kyma-project/btp-manager.git
git checkout sm-integration
```
4. Set the **IMG** environment variable to the image of BTP Manager with UI.
```shell
export IMG=europe-docker.pkg.dev/kyma-project/dev/btp-manager:PR-720
```
5. Run `deploy` makefile rule to deploy BTP Manager with UI.
```shell
make deploy
```
6. Check if BTP Manager deployment is running.
```shell
kubectl get deployment -n kyma-system btp-manager-controller-manager
```
If you encounter the following error during Pod creation due to Warden's admission webhook:
```
Error creating: admission webhook "validation.webhook.warden.kyma-project.io" denied the request: Pod images europe-docker.pkg.dev/kyma-project/dev/btp-manager:PR-720 validation failed
```
you must scale the BTP Manager deployment to 0 replicas, delete the webhook, and the scale the deployment back to 1 replica.
```shell
kubectl scale deployment -n kyma-system btp-manager-controller-manager --replicas=0
kubectl delete validatingwebhookconfigurations.admissionregistration.k8s.io validation.webhook.warden.kyma-project.io
kubectl scale deployment -n kyma-system btp-manager-controller-manager --replicas=1
```
7. Apply BtpOperator CR to create the Secret with credentials to access Service Manager.
```shell
kubectl apply -n kyma-system -f examples/btp-operator.yaml
```
8. Port-forward to BTP Manager deployment.
```shell
kubectl port-forward -n kyma-system deployment/btp-manager-controller-manager 8080:8080
```
9. Access the UI by opening `localhost:8080` in your browser.
### Cleanup
After testing the UI, you can delete the BtpOperator CR and BTP Manager deployment.
1. Delete the BtpOperator CR.
```shell
kubectl delete -n kyma-system btpoperator btpoperator
```
2. Delete BTP Manager deployment by running the `undeploy` makefile rule.
```shell
make undeploy
```
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ require (
github.com/onsi/gomega v1.33.1
github.com/prometheus/client_golang v1.19.1
github.com/stretchr/testify v1.9.0
github.com/vrischmann/envconfig v1.3.0
go.uber.org/zap v1.27.0
golang.org/x/oauth2 v0.19.0
gopkg.in/yaml.v3 v3.0.1
Expand Down
10 changes: 10 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand Down Expand Up @@ -53,6 +54,8 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
Expand All @@ -64,6 +67,7 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/onsi/ginkgo/v2 v2.18.0 h1:W9Y7IWXxPUpAit9ieMOLI7PJZGaW22DTKgiVAuhDTLc=
github.com/onsi/ginkgo/v2 v2.18.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To=
github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk=
Expand All @@ -86,8 +90,11 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/vrischmann/envconfig v1.3.0 h1:4XIvQTXznxmWMnjouj0ST5lFo/WAYf5Exgl3x82crEk=
github.com/vrischmann/envconfig v1.3.0/go.mod h1:bbvxFYJdRSpXrhS63mBFtKJzkDiNkyArOLXtY6q0kuI=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
Expand Down Expand Up @@ -142,13 +149,16 @@ gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuB
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
k8s.io/api v0.30.1 h1:kCm/6mADMdbAxmIh0LBjS54nQBE+U4KmbCfIkF5CpJY=
Expand Down
42 changes: 30 additions & 12 deletions internal/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,46 @@ package api
import (
"context"
"encoding/json"
"fmt"
"log"
"log/slog"
"net/http"
"strings"

"log/slog"
"time"

"github.com/kyma-project/btp-manager/internal/api/vm"
clusterobject "github.com/kyma-project/btp-manager/internal/cluster-object"
servicemanager "github.com/kyma-project/btp-manager/internal/service-manager"
)

type Config struct {
Port string `envconfig:"default=8080"`
ReadTimeout time.Duration `envconfig:"default=30s"`
WriteTimeout time.Duration `envconfig:"default=90s"`
IdleTimeout time.Duration `envconfig:"default=120s"`
}

type API struct {
server *http.Server
serviceManager *servicemanager.Client
secretProvider *clusterobject.SecretProvider
slogger *slog.Logger
frontendFS http.FileSystem
logger *slog.Logger
}

func NewAPI(serviceManager *servicemanager.Client, secretProvider *clusterobject.SecretProvider) *API {
slogger := slog.Default()
return &API{serviceManager: serviceManager, secretProvider: secretProvider, slogger: slogger}
func NewAPI(cfg Config, serviceManager *servicemanager.Client, secretProvider *clusterobject.SecretProvider, fs http.FileSystem) *API {
srv := &http.Server{
Addr: fmt.Sprintf(":%s", cfg.Port),
ReadTimeout: cfg.ReadTimeout,
WriteTimeout: cfg.WriteTimeout,
IdleTimeout: cfg.IdleTimeout,
}
return &API{
server: srv,
serviceManager: serviceManager,
secretProvider: secretProvider,
frontendFS: fs,
logger: slog.Default()}
}

func (a *API) Start() {
Expand All @@ -32,13 +53,10 @@ func (a *API) Start() {
mux.HandleFunc("GET /api/service-instance/{id}", a.GetServiceInstance)
mux.HandleFunc("GET /api/service-offerings/{namespace}/{name}", a.ListServiceOfferings)
mux.HandleFunc("GET /api/service-offering/{id}", a.GetServiceOffering)
mux.Handle("GET /", http.FileServer(a.frontendFS))
a.server.Handler = mux

go func() {
err := http.ListenAndServe(":3006", mux)
if err != nil {
a.slogger.Error("failed to Start listening", "error", err)
}
}()
log.Fatal(a.server.ListenAndServe())
}

func (a *API) CreateServiceInstance(writer http.ResponseWriter, request *http.Request) {
Expand Down
13 changes: 11 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import (
clusterobject "github.com/kyma-project/btp-manager/internal/cluster-object"
btpmanagermetrics "github.com/kyma-project/btp-manager/internal/metrics"
servicemanager "github.com/kyma-project/btp-manager/internal/service-manager"
"github.com/kyma-project/btp-manager/ui"
"github.com/vrischmann/envconfig"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
Expand Down Expand Up @@ -86,13 +88,20 @@ func main() {
var enableLeaderElection bool
parseCmdFlags(&probeAddr, &metricsAddr, &enableLeaderElection)

var cfg api.Config
err := envconfig.InitWithPrefix(&cfg, "API")
if err != nil {
setupLog.Error(err, "unable to load API config")
os.Exit(1)
}

restCfg := ctrl.GetConfigOrDie()
signalContext := ctrl.SetupSignalHandler()

mgr := setupManager(restCfg, &probeAddr, &metricsAddr, &enableLeaderElection, signalContext)
sp := getSecretProvider(restCfg)
sm := setupSMClient(sp, signalContext)
api := api.NewAPI(sm.Client, sp)
api := api.NewAPI(cfg, sm.Client, sp, ui.NewUIStaticFS())

// start components
go mgr.start()
Expand All @@ -107,7 +116,7 @@ func main() {

func parseCmdFlags(probeAddr *string, metricsAddr *string, enableLeaderElection *bool) {
flag.StringVar(probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
flag.StringVar(metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.")
flag.StringVar(metricsAddr, "metrics-bind-address", ":9090", "The address the metric endpoint binds to.")
flag.BoolVar(enableLeaderElection, "leader-elect", false,
"Enable leader election for controller manager. "+
"Enabling this will ensure there is only one active controller manager.")
Expand Down
4 changes: 2 additions & 2 deletions ui/src/components/SecretsView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ function SecretsView(props: any) {
props.handler(formatSecretText("", ""));
});
setLoading(false);
}, []);
}, [props]);

if (loading) {
return <ui5.Loader progress="100%"/>
Expand Down Expand Up @@ -68,7 +68,7 @@ function SecretsView(props: any) {
style={{width: "20vw"}}
onChange={(e) => {
// @ts-ignore
const secret = e .target.value;
const secret = e.target.value;
props.handler(secret);
props.setPageContent(<ServiceOfferingsView secret={secret} />);
}}
Expand Down
2 changes: 1 addition & 1 deletion ui/src/shared/api.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
function api(url :string) {
return `http://localhost:3006/api/${url}`
return `http://localhost:8080/api/${url}`
}

export default api;
6 changes: 3 additions & 3 deletions ui/src/shared/validator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,20 @@ function Ok(value: any) {
return false
}

if (value == undefined) {
if (value === undefined) {
return false
}

if (!value) {
return false
}

if ( typeof value == 'string' && value == "") {
if ( typeof value == 'string' && value === "") {
return false
}

if (Array.isArray(value)) {
if (value.length == 0) {
if (value.length === 0) {
return false
}
}
Expand Down
19 changes: 19 additions & 0 deletions ui/web.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package ui

import (
"embed"
"io/fs"
"log"
"net/http"
)

//go:embed all:build
var build embed.FS

func NewUIStaticFS() http.FileSystem {
uiFS, err := fs.Sub(build, "build")
if err != nil {
log.Fatal("cannot load ui files:", err)
}
return http.FS(uiFS)
}

0 comments on commit 5adbe6e

Please sign in to comment.