Move URLs from default section of config to DB
This change moves the callback_url, metadata_url and webhooks_url from the config to the database. The goal is to move as much as possible from the config to the DB, in preparation for a potential refactor that will allow GARM to scale out. This would allow multiple nodes to share a single source of truth. Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
This commit is contained in:
parent
7ee235aeb0
commit
9748aa47af
22 changed files with 1067 additions and 177 deletions
173
cmd/garm-cli/cmd/controller.go
Normal file
173
cmd/garm-cli/cmd/controller.go
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
// Copyright 2023 Cloudbase Solutions SRL
|
||||
//
|
||||
// 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 cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
apiClientController "github.com/cloudbase/garm/client/controller"
|
||||
apiClientControllerInfo "github.com/cloudbase/garm/client/controller_info"
|
||||
"github.com/cloudbase/garm/params"
|
||||
)
|
||||
|
||||
var controllerCmd = &cobra.Command{
|
||||
Use: "controller",
|
||||
Aliases: []string{"controller-info"},
|
||||
SilenceUsage: true,
|
||||
Short: "Controller operations",
|
||||
Long: `Query or update information about the current controller.`,
|
||||
Run: nil,
|
||||
}
|
||||
|
||||
var controllerShowCmd = &cobra.Command{
|
||||
Use: "show",
|
||||
Short: "Show information",
|
||||
Long: `Show information about the current controller.`,
|
||||
SilenceUsage: true,
|
||||
RunE: func(_ *cobra.Command, _ []string) error {
|
||||
if needsInit {
|
||||
return errNeedsInitError
|
||||
}
|
||||
|
||||
showInfo := apiClientControllerInfo.NewControllerInfoParams()
|
||||
response, err := apiCli.ControllerInfo.ControllerInfo(showInfo, authToken)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return formatInfo(response.Payload)
|
||||
},
|
||||
}
|
||||
|
||||
var controllerUpdateCmd = &cobra.Command{
|
||||
Use: "update",
|
||||
Short: "Update controller information",
|
||||
Long: `Update information about the current controller.
|
||||
|
||||
Warning: Dragons ahead, please read carefully.
|
||||
|
||||
Changing the URLs for the controller metadata, callback and webhooks, will
|
||||
impact the controller's ability to manage webhooks and runners.
|
||||
|
||||
As GARM can be set up behind a reverse proxy or through several layers of
|
||||
network address translation or load balancing, we need to explicitly tell
|
||||
GARM how to reach each of these URLs. Internally, GARM sets up API endpoints
|
||||
as follows:
|
||||
|
||||
* /webhooks - the base URL for the webhooks. Github needs to reach this URL.
|
||||
* /api/v1/metadata - the metadata URL. Your runners need to be able to reach this URL.
|
||||
* /api/v1/callbacks - the callback URL. Your runners need to be able to reach this URL.
|
||||
|
||||
You need to expose these endpoints to the interested parties (github or
|
||||
your runners), then you need to update the controller with the URLs you set up.
|
||||
|
||||
For example, if you set the webhooks URL in your reverse proxy to
|
||||
https://garm.example.com/garm-hooks, this still needs to point to the "/webhooks"
|
||||
URL in the GARM backend, but in the controller info you need to set the URL to
|
||||
https://garm.example.com/garm-hooks using:
|
||||
|
||||
garm-cli controller update --webhook-url=https://garm.example.com/garm-hooks
|
||||
|
||||
If you expose GARM to the outside world directly, or if you don't rewrite the URLs
|
||||
above in your reverse proxy config, use the above 3 endpoints without change,
|
||||
substituting garm.example.com with the correct hostname or IP address.
|
||||
|
||||
In most cases, you will have a GARM backend (say 192.168.100.10) and a reverse
|
||||
proxy in front of it exposed as https://garm.example.com. If you don't rewrite
|
||||
the URLs in the reverse proxy, and you just point to your backend, you can set
|
||||
up the GARM controller URLs as:
|
||||
|
||||
garm-cli controller update \
|
||||
--webhook-url=https://garm.example.com/webhooks \
|
||||
--metadata-url=https://garm.example.com/api/v1/metadata \
|
||||
--callback-url=https://garm.example.com/api/v1/callbacks
|
||||
`,
|
||||
SilenceUsage: true,
|
||||
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||
if needsInit {
|
||||
return errNeedsInitError
|
||||
}
|
||||
|
||||
params := params.UpdateControllerParams{}
|
||||
if cmd.Flags().Changed("metadata-url") {
|
||||
params.MetadataURL = &metadataURL
|
||||
}
|
||||
if cmd.Flags().Changed("callback-url") {
|
||||
params.CallbackURL = &callbackURL
|
||||
}
|
||||
if cmd.Flags().Changed("webhook-url") {
|
||||
params.WebhookURL = &webhookURL
|
||||
}
|
||||
|
||||
if params.WebhookURL == nil && params.MetadataURL == nil && params.CallbackURL == nil {
|
||||
cmd.Help()
|
||||
return fmt.Errorf("at least one of metadata-url, callback-url or webhook-url must be provided")
|
||||
}
|
||||
|
||||
updateUrlsReq := apiClientController.NewUpdateControllerParams()
|
||||
updateUrlsReq.Body = params
|
||||
|
||||
info, err := apiCli.Controller.UpdateController(updateUrlsReq, authToken)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error updating controller: %w", err)
|
||||
}
|
||||
formatInfo(info.Payload)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func renderControllerInfoTable(info params.ControllerInfo) string {
|
||||
t := table.NewWriter()
|
||||
header := table.Row{"Field", "Value"}
|
||||
|
||||
if info.WebhookURL == "" {
|
||||
info.WebhookURL = "N/A"
|
||||
}
|
||||
|
||||
if info.ControllerWebhookURL == "" {
|
||||
info.ControllerWebhookURL = "N/A"
|
||||
}
|
||||
|
||||
t.AppendHeader(header)
|
||||
t.AppendRow(table.Row{"Controller ID", info.ControllerID})
|
||||
if info.Hostname != "" {
|
||||
t.AppendRow(table.Row{"Hostname", info.Hostname})
|
||||
}
|
||||
t.AppendRow(table.Row{"Metadata URL", info.MetadataURL})
|
||||
t.AppendRow(table.Row{"Callback URL", info.CallbackURL})
|
||||
t.AppendRow(table.Row{"Webhook Base URL", info.WebhookURL})
|
||||
t.AppendRow(table.Row{"Controller Webhook URL", info.ControllerWebhookURL})
|
||||
return t.Render()
|
||||
}
|
||||
|
||||
func formatInfo(info params.ControllerInfo) error {
|
||||
fmt.Println(renderControllerInfoTable(info))
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
controllerUpdateCmd.Flags().StringVarP(&metadataURL, "metadata-url", "m", "", "The metadata URL for the controller (ie. https://garm.example.com/api/v1/metadata)")
|
||||
controllerUpdateCmd.Flags().StringVarP(&callbackURL, "callback-url", "c", "", "The callback URL for the controller (ie. https://garm.example.com/api/v1/callbacks)")
|
||||
controllerUpdateCmd.Flags().StringVarP(&webhookURL, "webhook-url", "w", "", "The webhook URL for the controller (ie. https://garm.example.com/webhooks)")
|
||||
|
||||
controllerCmd.AddCommand(
|
||||
controllerShowCmd,
|
||||
controllerUpdateCmd,
|
||||
)
|
||||
|
||||
rootCmd.AddCommand(controllerCmd)
|
||||
}
|
||||
|
|
@ -1,84 +0,0 @@
|
|||
// Copyright 2023 Cloudbase Solutions SRL
|
||||
//
|
||||
// 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 cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
apiClientControllerInfo "github.com/cloudbase/garm/client/controller_info"
|
||||
"github.com/cloudbase/garm/params"
|
||||
)
|
||||
|
||||
var infoCmd = &cobra.Command{
|
||||
Use: "controller-info",
|
||||
SilenceUsage: true,
|
||||
Short: "Information about controller",
|
||||
Long: `Query information about the current controller.`,
|
||||
Run: nil,
|
||||
}
|
||||
|
||||
var infoShowCmd = &cobra.Command{
|
||||
Use: "show",
|
||||
Short: "Show information",
|
||||
Long: `Show information about the current controller.`,
|
||||
SilenceUsage: true,
|
||||
RunE: func(_ *cobra.Command, _ []string) error {
|
||||
if needsInit {
|
||||
return errNeedsInitError
|
||||
}
|
||||
|
||||
showInfo := apiClientControllerInfo.NewControllerInfoParams()
|
||||
response, err := apiCli.ControllerInfo.ControllerInfo(showInfo, authToken)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return formatInfo(response.Payload)
|
||||
},
|
||||
}
|
||||
|
||||
func formatInfo(info params.ControllerInfo) error {
|
||||
t := table.NewWriter()
|
||||
header := table.Row{"Field", "Value"}
|
||||
|
||||
if info.WebhookURL == "" {
|
||||
info.WebhookURL = "N/A"
|
||||
}
|
||||
|
||||
if info.ControllerWebhookURL == "" {
|
||||
info.ControllerWebhookURL = "N/A"
|
||||
}
|
||||
|
||||
t.AppendHeader(header)
|
||||
t.AppendRow(table.Row{"Controller ID", info.ControllerID})
|
||||
t.AppendRow(table.Row{"Hostname", info.Hostname})
|
||||
t.AppendRow(table.Row{"Metadata URL", info.MetadataURL})
|
||||
t.AppendRow(table.Row{"Callback URL", info.CallbackURL})
|
||||
t.AppendRow(table.Row{"Webhook Base URL", info.WebhookURL})
|
||||
t.AppendRow(table.Row{"Controller Webhook URL", info.ControllerWebhookURL})
|
||||
fmt.Println(t.Render())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
infoCmd.AddCommand(
|
||||
infoShowCmd,
|
||||
)
|
||||
|
||||
rootCmd.AddCommand(infoCmd)
|
||||
}
|
||||
|
|
@ -16,12 +16,15 @@ package cmd
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
openapiRuntimeClient "github.com/go-openapi/runtime/client"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
apiClientController "github.com/cloudbase/garm/client/controller"
|
||||
apiClientFirstRun "github.com/cloudbase/garm/client/first_run"
|
||||
apiClientLogin "github.com/cloudbase/garm/client/login"
|
||||
"github.com/cloudbase/garm/cmd/garm-cli/common"
|
||||
|
|
@ -29,6 +32,12 @@ import (
|
|||
"github.com/cloudbase/garm/params"
|
||||
)
|
||||
|
||||
var (
|
||||
callbackURL string
|
||||
metadataURL string
|
||||
webhookURL string
|
||||
)
|
||||
|
||||
// initCmd represents the init command
|
||||
var initCmd = &cobra.Command{
|
||||
Use: "init",
|
||||
|
|
@ -52,10 +61,13 @@ garm-cli init --name=dev --url=https://runner.example.com --username=admin --pas
|
|||
}
|
||||
}
|
||||
|
||||
url := strings.TrimSuffix(loginURL, "/")
|
||||
if err := promptUnsetInitVariables(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ensureDefaultEndpoints(url)
|
||||
|
||||
newUserReq := apiClientFirstRun.NewFirstRunParams()
|
||||
newUserReq.Body = params.NewUserParams{
|
||||
Username: loginUserName,
|
||||
|
|
@ -63,9 +75,6 @@ garm-cli init --name=dev --url=https://runner.example.com --username=admin --pas
|
|||
FullName: loginFullName,
|
||||
Email: loginEmail,
|
||||
}
|
||||
|
||||
url := strings.TrimSuffix(loginURL, "/")
|
||||
|
||||
initAPIClient(url, "")
|
||||
|
||||
response, err := apiCli.FirstRun.FirstRun(newUserReq, authToken)
|
||||
|
|
@ -90,17 +99,50 @@ garm-cli init --name=dev --url=https://runner.example.com --username=admin --pas
|
|||
Token: token.Payload.Token,
|
||||
})
|
||||
|
||||
authToken = openapiRuntimeClient.BearerToken(token.Payload.Token)
|
||||
cfg.ActiveManager = loginProfileName
|
||||
|
||||
if err := cfg.SaveConfig(); err != nil {
|
||||
return errors.Wrap(err, "saving config")
|
||||
}
|
||||
|
||||
renderUserTable(response.Payload)
|
||||
updateUrlsReq := apiClientController.NewUpdateControllerParams()
|
||||
updateUrlsReq.Body = params.UpdateControllerParams{
|
||||
MetadataURL: &metadataURL,
|
||||
CallbackURL: &callbackURL,
|
||||
WebhookURL: &webhookURL,
|
||||
}
|
||||
|
||||
controllerInfoResponse, err := apiCli.Controller.UpdateController(updateUrlsReq, authToken)
|
||||
renderResponseMessage(response.Payload, controllerInfoResponse.Payload, err)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func ensureDefaultEndpoints(loginURL string) (err error) {
|
||||
if metadataURL == "" {
|
||||
metadataURL, err = url.JoinPath(loginURL, "api/v1/callbacks")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if callbackURL == "" {
|
||||
callbackURL, err = url.JoinPath(loginURL, "api/v1/callbacks")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if webhookURL == "" {
|
||||
webhookURL, err = url.JoinPath(loginURL, "webhooks")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func promptUnsetInitVariables() error {
|
||||
var err error
|
||||
if loginUserName == "" {
|
||||
|
|
@ -123,6 +165,7 @@ func promptUnsetInitVariables() error {
|
|||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -133,13 +176,16 @@ func init() {
|
|||
initCmd.Flags().StringVarP(&loginURL, "url", "a", "", "The base URL for the runner manager API")
|
||||
initCmd.Flags().StringVarP(&loginUserName, "username", "u", "", "The desired administrative username")
|
||||
initCmd.Flags().StringVarP(&loginEmail, "email", "e", "", "Email address")
|
||||
initCmd.Flags().StringVarP(&metadataURL, "metadata-url", "m", "", "The metadata URL for the controller (ie. https://garm.example.com/api/v1/metadata)")
|
||||
initCmd.Flags().StringVarP(&callbackURL, "callback-url", "c", "", "The callback URL for the controller (ie. https://garm.example.com/api/v1/callbacks)")
|
||||
initCmd.Flags().StringVarP(&webhookURL, "webhook-url", "w", "", "The webhook URL for the controller (ie. https://garm.example.com/webhooks)")
|
||||
initCmd.Flags().StringVarP(&loginFullName, "full-name", "f", "", "Full name of the user")
|
||||
initCmd.Flags().StringVarP(&loginPassword, "password", "p", "", "The admin password")
|
||||
initCmd.MarkFlagRequired("name") //nolint
|
||||
initCmd.MarkFlagRequired("url") //nolint
|
||||
}
|
||||
|
||||
func renderUserTable(user params.User) {
|
||||
func renderUserTable(user params.User) string {
|
||||
t := table.NewWriter()
|
||||
header := table.Row{"Field", "Value"}
|
||||
t.AppendHeader(header)
|
||||
|
|
@ -148,5 +194,54 @@ func renderUserTable(user params.User) {
|
|||
t.AppendRow(table.Row{"Username", user.Username})
|
||||
t.AppendRow(table.Row{"Email", user.Email})
|
||||
t.AppendRow(table.Row{"Enabled", user.Enabled})
|
||||
fmt.Println(t.Render())
|
||||
return t.Render()
|
||||
}
|
||||
|
||||
func renderResponseMessage(user params.User, controllerInfo params.ControllerInfo, err error) {
|
||||
userTable := renderUserTable(user)
|
||||
controllerInfoTable := renderControllerInfoTable(controllerInfo)
|
||||
|
||||
headerMsg := `Congrats! Your controller is now initialized.
|
||||
|
||||
Following are the details of the admin user and details about the controller.
|
||||
|
||||
Admin user information:
|
||||
|
||||
%s
|
||||
`
|
||||
|
||||
controllerMsg := `Controller information:
|
||||
|
||||
%s
|
||||
|
||||
Make sure that the URLs in the table above are reachable by the relevant parties.
|
||||
|
||||
The metadata and callback URLs *must* be accessible by the runners that GARM spins up.
|
||||
The base webhook and the controller webhook URLs must be accessible by GitHub or GHES.
|
||||
`
|
||||
|
||||
controllerErrorMsg := `WARNING: Failed to set the required controller URLs with error: %q
|
||||
|
||||
Please run:
|
||||
|
||||
garm-cli controller show
|
||||
|
||||
To make sure that the callback, metadata and webhook URLs are set correctly. If not,
|
||||
you must set them up by running:
|
||||
|
||||
garm-cli controller update \
|
||||
--metadata-url=<metadata-url> \
|
||||
--callback-url=<callback-url> \
|
||||
--webhook-url=<webhook-url>
|
||||
|
||||
See the help message for garm-cli controller update for more information.
|
||||
`
|
||||
var ctrlMsg string
|
||||
if err != nil {
|
||||
ctrlMsg = fmt.Sprintf(controllerErrorMsg, err)
|
||||
} else {
|
||||
ctrlMsg = fmt.Sprintf(controllerMsg, controllerInfoTable)
|
||||
}
|
||||
|
||||
fmt.Printf("%s\n%s\n", fmt.Sprintf(headerMsg, userTable), ctrlMsg)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ package common
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/manifoldco/promptui"
|
||||
"github.com/nbutton23/zxcvbn-go"
|
||||
|
|
@ -45,7 +46,7 @@ func PromptPassword(label string) (string, error) {
|
|||
return result, nil
|
||||
}
|
||||
|
||||
func PromptString(label string) (string, error) {
|
||||
func PromptString(label string, a ...interface{}) (string, error) {
|
||||
validate := func(input string) error {
|
||||
if len(input) == 0 {
|
||||
return errors.New("empty input not allowed")
|
||||
|
|
@ -54,7 +55,7 @@ func PromptString(label string) (string, error) {
|
|||
}
|
||||
|
||||
prompt := promptui.Prompt{
|
||||
Label: label,
|
||||
Label: fmt.Sprintf(label, a...),
|
||||
Validate: validate,
|
||||
}
|
||||
result, err := prompt.Run()
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ import (
|
|||
"github.com/cloudbase/garm/database"
|
||||
"github.com/cloudbase/garm/database/common"
|
||||
"github.com/cloudbase/garm/metrics"
|
||||
"github.com/cloudbase/garm/params"
|
||||
"github.com/cloudbase/garm/runner" //nolint:typecheck
|
||||
runnerMetrics "github.com/cloudbase/garm/runner/metrics"
|
||||
garmUtil "github.com/cloudbase/garm/util"
|
||||
|
|
@ -142,6 +143,38 @@ func setupLogging(ctx context.Context, logCfg config.Logging, hub *websocket.Hub
|
|||
slog.SetDefault(slog.New(wrapped))
|
||||
}
|
||||
|
||||
func maybeUpdateURLsFromConfig(cfg config.Config, store common.Store) error {
|
||||
info, err := store.ControllerInfo()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "fetching controller info")
|
||||
}
|
||||
|
||||
var updateParams params.UpdateControllerParams
|
||||
|
||||
if info.MetadataURL == "" && cfg.Default.MetadataURL != "" {
|
||||
updateParams.MetadataURL = &cfg.Default.MetadataURL
|
||||
}
|
||||
|
||||
if info.CallbackURL == "" && cfg.Default.CallbackURL != "" {
|
||||
updateParams.CallbackURL = &cfg.Default.CallbackURL
|
||||
}
|
||||
|
||||
if info.WebhookURL == "" && cfg.Default.WebhookURL != "" {
|
||||
updateParams.WebhookURL = &cfg.Default.WebhookURL
|
||||
}
|
||||
|
||||
if updateParams.MetadataURL == nil && updateParams.CallbackURL == nil && updateParams.WebhookURL == nil {
|
||||
// nothing to update
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err = store.UpdateController(updateParams)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "updating controller info")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
if *version {
|
||||
|
|
@ -181,6 +214,10 @@ func main() {
|
|||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if err := maybeUpdateURLsFromConfig(*cfg, db); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
runner, err := runner.NewRunner(ctx, *cfg, db)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to create controller: %+v", err)
|
||||
|
|
@ -212,12 +249,17 @@ func main() {
|
|||
log.Fatal(err)
|
||||
}
|
||||
|
||||
urlsRequiredMiddleware, err := auth.NewUrlsRequiredMiddleware(db)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
metricsMiddleware, err := auth.NewMetricsMiddleware(cfg.JWTAuth)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
router := routers.NewAPIRouter(controller, jwtMiddleware, initMiddleware, instanceMiddleware, cfg.Default.EnableWebhookManagement)
|
||||
router := routers.NewAPIRouter(controller, jwtMiddleware, initMiddleware, urlsRequiredMiddleware, instanceMiddleware, cfg.Default.EnableWebhookManagement)
|
||||
|
||||
// start the metrics collector
|
||||
if cfg.Metrics.Enable {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue